Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package in.koreatech.koin.domain.community.article.repository;

import static in.koreatech.koin.domain.community.article.service.ArticleService.LOST_ITEM_BOARD_ID;
import static in.koreatech.koin.domain.community.article.service.ArticleService.NOTICE_BOARD_ID;

import java.time.LocalDate;
Expand Down Expand Up @@ -29,6 +30,8 @@ public interface ArticleRepository extends Repository<Article, Integer> {

Page<Article> findAll(Pageable pageable);

Page<Article> findAllByBoardIdNot(Integer boardId, PageRequest pageRequest);

Page<Article> findAllByBoardId(Integer boardId, PageRequest pageRequest);

Page<Article> findAllByIdIn(List<Integer> articleIds, PageRequest pageRequest);
Expand All @@ -43,8 +46,10 @@ public interface ArticleRepository extends Repository<Article, Integer> {
LEFT JOIN FETCH l.author
LEFT JOIN FETCH a.koinNotice
WHERE a.id IN :ids
AND a.board.id <> :excludedBoardId
""")
List<Article> findAllForHotArticlesByIdIn(List<Integer> ids);
List<Article> findAllForHotArticlesByIdInExcludingBoardId(@Param("ids") List<Integer> ids,
@Param("excludedBoardId") Integer excludedBoardId);

default Article getById(Integer articleId) {
Article found = findById(articleId)
Expand Down Expand Up @@ -72,11 +77,12 @@ default Article getById(Integer articleId) {
Page<Article> findAllByBoardIdAndTitleContaining(@Param("boardId") Integer boardId, @Param("query") String query, Pageable pageable);

@Query(
value = "SELECT * FROM new_articles WHERE MATCH(title) AGAINST(CONCAT(:query, '*') IN BOOLEAN MODE) AND is_deleted = false",
countQuery = "SELECT count(*) FROM new_articles WHERE MATCH(title) AGAINST(CONCAT(:query, '*') IN BOOLEAN MODE) AND is_deleted = false",
value = "SELECT * FROM new_articles WHERE MATCH(title) AGAINST(CONCAT(:query, '*') IN BOOLEAN MODE) AND board_id <> :excludedBoardId AND is_deleted = false",
countQuery = "SELECT count(*) FROM new_articles WHERE MATCH(title) AGAINST(CONCAT(:query, '*') IN BOOLEAN MODE) AND board_id <> :excludedBoardId AND is_deleted = false",
nativeQuery = true
)
Page<Article> findAllByTitleContaining(@Param("query") String query, Pageable pageable);
Page<Article> findAllByTitleContainingExcludingBoardId(@Param("query") String query,
@Param("excludedBoardId") Integer excludedBoardId, Pageable pageable);

@Query(
value = "SELECT * FROM new_articles WHERE is_notice = true AND MATCH(title) AGAINST(CONCAT(:query, '*') IN BOOLEAN MODE) AND is_deleted = false",
Expand All @@ -87,6 +93,8 @@ default Article getById(Integer articleId) {

Long countBy();

Long countByBoardIdNot(Integer boardId);

@Query(value = "SELECT * FROM new_articles a "
+ "WHERE a.id < :articleId AND a.is_notice = true AND a.is_deleted = false "
+ "ORDER BY a.id DESC LIMIT 1", nativeQuery = true)
Expand All @@ -98,9 +106,10 @@ default Article getById(Integer articleId) {
Optional<Article> findPreviousArticle(@Param("articleId") Integer articleId, @Param("boardId") Integer boardId);

@Query(value = "SELECT * FROM new_articles a "
+ "WHERE a.id < :articleId AND a.is_deleted = false "
+ "WHERE a.id < :articleId AND a.board_id <> :excludedBoardId AND a.is_deleted = false "
+ "ORDER BY a.id DESC LIMIT 1", nativeQuery = true)
Optional<Article> findPreviousAllArticle(@Param("articleId") Integer articleId);
Optional<Article> findPreviousAllArticle(@Param("articleId") Integer articleId,
@Param("excludedBoardId") Integer excludedBoardId);

@Query(value = "SELECT * FROM new_articles a "
+ "WHERE a.id > :articleId AND a.is_notice = true AND a.is_deleted = false "
Expand All @@ -113,9 +122,10 @@ default Article getById(Integer articleId) {
Optional<Article> findNextArticle(@Param("articleId") Integer articleId, @Param("boardId") Integer boardId);

@Query(value = "SELECT * FROM new_articles a "
+ "WHERE a.id > :articleId AND a.is_deleted = false "
+ "WHERE a.id > :articleId AND a.board_id <> :excludedBoardId AND a.is_deleted = false "
+ "ORDER BY a.id ASC LIMIT 1", nativeQuery = true)
Optional<Article> findNextAllArticle(@Param("articleId") Integer articleId);
Optional<Article> findNextAllArticle(@Param("articleId") Integer articleId,
@Param("excludedBoardId") Integer excludedBoardId);

default Article getPreviousArticle(Board board, Article article) {
if (board.isNotice() && board.getId().equals(NOTICE_BOARD_ID)) {
Expand All @@ -125,7 +135,7 @@ default Article getPreviousArticle(Board board, Article article) {
}

default Article getPreviousAllArticle(Article article) {
return findPreviousAllArticle(article.getId()).orElse(null);
return findPreviousAllArticle(article.getId(), LOST_ITEM_BOARD_ID).orElse(null);
}

default Article getNextArticle(Board board, Article article) {
Expand All @@ -136,7 +146,7 @@ default Article getNextArticle(Board board, Article article) {
}

default Article getNextAllArticle(Article article) {
return findNextAllArticle(article.getId()).orElse(null);
return findNextAllArticle(article.getId(), LOST_ITEM_BOARD_ID).orElse(null);
}

@Query("""
Expand All @@ -152,18 +162,22 @@ default Article getNextAllArticle(Article article) {
OR (ka IS NULL AND a.createdAt > :registeredAt)
)
AND a.isDeleted = false
AND a.board.id <> :excludedBoardId
ORDER BY (a.hit + COALESCE(ka.portalHit, 0)) DESC, a.id DESC
""")
List<Article> findMostHitArticles(LocalDate registeredAt, Pageable pageable);
List<Article> findMostHitArticlesExcludingBoardId(@Param("registeredAt") LocalDate registeredAt,
Pageable pageable, @Param("excludedBoardId") Integer excludedBoardId);

@Query(value = "SELECT a.* FROM new_articles a "
+ "LEFT JOIN new_koreatech_articles ka ON ka.article_id = a.id "
+ "WHERE ( "
+ " (ka.article_id IS NOT NULL AND ka.registered_at > :registeredAt) "
+ " OR (ka.article_id IS NULL AND a.created_at > :registeredAt) "
+ ") "
+ "AND a.board_id <> :excludedBoardId "
+ "AND a.is_deleted = false ", nativeQuery = true)
List<Article> findAllByRegisteredAtIsAfter(LocalDate registeredAt);
List<Article> findAllByRegisteredAtIsAfterExcludingBoardId(@Param("registeredAt") LocalDate registeredAt,
@Param("excludedBoardId") Integer excludedBoardId);

@Query("SELECT a.title FROM Article a WHERE a.id = :id")
String getTitleById(@Param("id") Integer id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import java.time.Clock;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;
Expand All @@ -14,22 +16,22 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import in.koreatech.koin.common.model.Criteria;
import in.koreatech.koin.domain.community.article.dto.ArticleHotKeywordResponse;
import in.koreatech.koin.domain.community.article.dto.ArticleResponse;
import in.koreatech.koin.domain.community.article.dto.ArticlesResponse;
import in.koreatech.koin.domain.community.article.dto.HotArticleItemResponse;
import in.koreatech.koin.domain.community.article.exception.ArticleBoardMisMatchException;
import in.koreatech.koin.domain.community.article.model.Article;
import in.koreatech.koin.domain.community.article.model.Board;
import in.koreatech.koin.domain.community.article.model.KeywordRankingManager;
import in.koreatech.koin.domain.community.article.model.redis.ArticleHitUser;
import in.koreatech.koin.domain.community.article.model.redis.PopularKeywordTracker;
import in.koreatech.koin.domain.community.article.model.KeywordRankingManager;
import in.koreatech.koin.domain.community.article.repository.ArticleRepository;
import in.koreatech.koin.domain.community.article.repository.BoardRepository;
import in.koreatech.koin.domain.community.article.repository.redis.ArticleHitUserRepository;
import in.koreatech.koin.domain.community.article.repository.redis.HotArticleRepository;
import in.koreatech.koin.global.exception.custom.KoinIllegalArgumentException;
import in.koreatech.koin.common.model.Criteria;
import in.koreatech.koin.infrastructure.s3.client.S3Client;
import lombok.RequiredArgsConstructor;

Expand All @@ -39,6 +41,7 @@
public class ArticleService {

public static final int NOTICE_BOARD_ID = 4;
public static final int LOST_ITEM_BOARD_ID = 14;
private static final int HOT_ARTICLE_BEFORE_DAYS = 30;
private static final int HOT_ARTICLE_LIMIT = 10;
private static final int MAXIMUM_SEARCH_LENGTH = 100;
Expand All @@ -49,7 +52,6 @@ public class ArticleService {
Sort.Order.desc("id")
);


private final ArticleRepository articleRepository;
private final BoardRepository boardRepository;
private final HotArticleRepository hotArticleRepository;
Expand All @@ -75,11 +77,13 @@ public ArticleResponse getArticle(Integer boardId, Integer articleId, String pub
}

public ArticlesResponse getArticles(Integer boardId, Integer page, Integer limit, Integer userId) {
Long total = articleRepository.countBy();
Long total = boardId == null
? articleRepository.countByBoardIdNot(LOST_ITEM_BOARD_ID)
: articleRepository.countBy();
Criteria criteria = Criteria.of(page, limit, total.intValue());
PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT);
if (boardId == null) {
Page<Article> articles = articleRepository.findAll(pageRequest);
Page<Article> articles = articleRepository.findAllByBoardIdNot(LOST_ITEM_BOARD_ID, pageRequest);
return ArticlesResponse.of(articles, criteria, userId);
}
if (boardId == NOTICE_BOARD_ID) {
Expand All @@ -92,27 +96,58 @@ public ArticlesResponse getArticles(Integer boardId, Integer page, Integer limit

public List<HotArticleItemResponse> getHotArticles() {
List<Integer> hotArticlesIds = hotArticleRepository.getHotArticles(HOT_ARTICLE_LIMIT);
List<Article> articles = articleRepository.findAllForHotArticlesByIdIn(hotArticlesIds);
List<Article> cachedArticles = articleRepository.findAllForHotArticlesByIdInExcludingBoardId(
hotArticlesIds,
LOST_ITEM_BOARD_ID
);

Map<Integer, Article> articleMap = articles.stream()
Map<Integer, Article> articleMap = cachedArticles.stream()
.collect(Collectors.toMap(Article::getId, article -> article));

List<Article> cacheList = new ArrayList<>(hotArticlesIds.stream()
.map(articleMap::get)
.filter(Objects::nonNull)
.toList());
List<Article> result = new ArrayList<>(HOT_ARTICLE_LIMIT);
Set<Integer> seen = new HashSet<>(HOT_ARTICLE_LIMIT * 2);

for (Integer id : hotArticlesIds) {
Article article = articleMap.get(id);

if (article == null) {
continue;
}

if (cacheList.size() < HOT_ARTICLE_LIMIT) {
List<Article> highestHitArticles = articleRepository.findMostHitArticles(
if (seen.add(article.getId())) {
result.add(article);

if (result.size() == HOT_ARTICLE_LIMIT) {
break;
}
}
}

if (result.size() < HOT_ARTICLE_LIMIT) {
List<Article> highestHitArticles = articleRepository.findMostHitArticlesExcludingBoardId(
LocalDate.now(clock).minusDays(HOT_ARTICLE_BEFORE_DAYS),
PageRequest.of(0, HOT_ARTICLE_LIMIT)
PageRequest.of(0, HOT_ARTICLE_LIMIT),
LOST_ITEM_BOARD_ID
);
cacheList.addAll(highestHitArticles);
return cacheList.stream().limit(HOT_ARTICLE_LIMIT)
.map(HotArticleItemResponse::from)
.toList();

for (Article article : highestHitArticles) {
if (article == null) {
continue;
}

if (seen.add(article.getId())) {
result.add(article);

if (result.size() == HOT_ARTICLE_LIMIT) {
break;
}
}
}
}
return cacheList.stream().map(HotArticleItemResponse::from).toList();

return result.stream()
.map(HotArticleItemResponse::from)
.toList();
}

@Transactional
Expand All @@ -123,7 +158,7 @@ public ArticlesResponse searchArticles(String query, Integer boardId, Integer pa
PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), NATIVE_ARTICLES_SORT);
Page<Article> articles;
if (boardId == null) {
articles = articleRepository.findAllByTitleContaining(query, pageRequest);
articles = articleRepository.findAllByTitleContainingExcludingBoardId(query, LOST_ITEM_BOARD_ID, pageRequest);
} else if (boardId == NOTICE_BOARD_ID) {
articles = articleRepository.findAllByIsNoticeIsTrueAndTitleContaining(query, pageRequest);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ public void updateHotArticles() {
List<ArticleHit> articleHits = articleHitRepository.findAll();
articleHitRepository.deleteAll();
List<Article> allArticles =
articleRepository.findAllByRegisteredAtIsAfter(LocalDate.now(clock).minusDays(HOT_ARTICLE_BEFORE_DAYS));
articleRepository.findAllByRegisteredAtIsAfterExcludingBoardId(
LocalDate.now(clock).minusDays(HOT_ARTICLE_BEFORE_DAYS),
ArticleService.LOST_ITEM_BOARD_ID
);
articleHitRepository.saveAll(allArticles.stream().map(ArticleHit::from).toList());

Map<Integer, Integer> articlesIdWithHit = new HashMap<>();
Expand Down
Loading