Skip to content
Closed
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
Expand Up @@ -56,4 +56,12 @@ public void delete() {
public void restore() {
this.isDeleted = false;
}

public String getKeyword() {
return articleKeyword.getKeyword();
}

public Integer getUserId() {
return user.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package in.koreatech.koin.domain.community.keyword.model;

import java.util.Objects;

public class KeywordMatchResult {

private final Integer articleId;
private final Integer userId;
private String keyword;

private KeywordMatchResult(Integer articleId, Integer userId, String keyword) {
this.articleId = articleId;
this.userId = userId;
this.keyword = keyword;
}

public static KeywordMatchResult of(Integer articleId, Integer userId, String keyword) {
return new KeywordMatchResult(articleId, userId, keyword);
}

public void updateKeywordIfLonger(String candidate) {
if (candidate.trim().length() > this.keyword.trim().length()) {
this.keyword = candidate;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof KeywordMatchResult that)) return false;
return Objects.equals(articleId, that.articleId) && Objects.equals(userId, that.userId);
}

@Override
public int hashCode() {
return Objects.hash(articleId, userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,6 @@ default ArticleKeywordUserMap getById(Integer keywordUserMapId) {

List<ArticleKeywordUserMap> findAllByUserId(Integer userId);

@Query("""
SELECT akw.keyword FROM ArticleKeywordUserMap akum
JOIN akum.articleKeyword akw
WHERE akum.user.id = :userId
""")
List<String> findAllKeywordByUserId(@Param("userId") Integer userId);

@Query(value = """
SELECT * FROM article_keyword_user_map akum
WHERE akum.keyword_id = :articleKeywordId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,96 +1,53 @@
package in.koreatech.koin.domain.community.util;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.Set;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Component;

import in.koreatech.koin.domain.community.article.model.Article;
import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword;
import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap;
import in.koreatech.koin.common.event.ArticleKeywordEvent;
import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordRepository;
import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordUserMapRepository;
import lombok.RequiredArgsConstructor;
import in.koreatech.koin.domain.community.keyword.model.KeywordMatchResult;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Component
public class KeywordExtractor {

private static final int KEYWORD_BATCH_SIZE = 100;
public List<KeywordMatchResult> matchKeyword(
List<Article> articles, List<ArticleKeywordUserMap> articleKeywordUserMaps, Integer authorId
) {
Set<KeywordMatchResult> keywordMatchResults = new HashSet<>();

private final ArticleKeywordRepository articleKeywordRepository;
private final ArticleKeywordUserMapRepository articleKeywordUserMapRepository;

public List<ArticleKeywordEvent> matchKeyword(List<Article> articles, Integer authorId) {
Map<Integer, Map<Integer, String>> matchedKeywordByUserIdByArticleId = new LinkedHashMap<>();
int offset = 0;

while (true) {
Pageable pageable = PageRequest.of(offset / KEYWORD_BATCH_SIZE, KEYWORD_BATCH_SIZE);
List<ArticleKeyword> keywords = articleKeywordRepository.findAll(pageable);

if (keywords.isEmpty()) {
break;
}
List<Integer> keywordIds = keywords.stream()
.map(ArticleKeyword::getId)
.toList();
Map<Integer, List<ArticleKeywordUserMap>> userMapsByKeywordId = articleKeywordUserMapRepository
.findAllByArticleKeywordIdIn(keywordIds)
.stream()
.filter(keywordUserMap -> !keywordUserMap.getIsDeleted())
.collect(Collectors.groupingBy(
keywordUserMap -> keywordUserMap.getArticleKeyword().getId(),
LinkedHashMap::new,
Collectors.toList()
));

for (Article article : articles) {
String title = article.getTitle();
for (ArticleKeyword keyword : keywords) {
if (!title.contains(keyword.getKeyword())) {
continue;
}
Map<Integer, String> matchedKeywordByUserId = matchedKeywordByUserIdByArticleId
.computeIfAbsent(article.getId(), ignored -> new LinkedHashMap<>());

for (ArticleKeywordUserMap keywordUserMap :
userMapsByKeywordId.getOrDefault(keyword.getId(), List.of())) {
Integer userId = keywordUserMap.getUser().getId();
matchedKeywordByUserId.merge(
userId,
keyword.getKeyword(),
this::pickHigherPriorityKeyword
);
}
for (Article article : articles) {
for (ArticleKeywordUserMap articleKeywordUserMap : articleKeywordUserMaps) {
if (isMatchable(article, articleKeywordUserMap, authorId)) {
addOrUpdateResult(keywordMatchResults, article, articleKeywordUserMap);
}
}
offset += KEYWORD_BATCH_SIZE;
}

List<ArticleKeywordEvent> keywordEvents = new ArrayList<>();
for (Article article : articles) {
Map<Integer, String> matchedKeywordByUserId = matchedKeywordByUserIdByArticleId.get(article.getId());
if (matchedKeywordByUserId != null && !matchedKeywordByUserId.isEmpty()) {
keywordEvents.add(new ArticleKeywordEvent(article.getId(), authorId, matchedKeywordByUserId));
}
}
return keywordMatchResults.stream().toList();
}

return keywordEvents;
private boolean isMatchable(Article article, ArticleKeywordUserMap articleKeywordUserMap, Integer authorId) {
return !Objects.equals(articleKeywordUserMap.getUserId(), authorId)
&& article.getTitle().contains(articleKeywordUserMap.getKeyword());
}

private String pickHigherPriorityKeyword(String previousKeyword, String candidateKeyword) {
if (candidateKeyword.length() > previousKeyword.length()) {
return candidateKeyword;
}
return previousKeyword;
private void addOrUpdateResult(
Set<KeywordMatchResult> keywordMatchResults, Article article, ArticleKeywordUserMap articleKeywordUserMap
) {
KeywordMatchResult keywordMatchResult = KeywordMatchResult.of(
article.getId(), articleKeywordUserMap.getUserId(), articleKeywordUserMap.getKeyword()
);

keywordMatchResults.stream()
.filter(result -> result.equals(keywordMatchResult))
.findFirst()
.ifPresentOrElse(
existing -> existing.updateKeywordIfLonger(articleKeywordUserMap.getKeyword()),
() -> keywordMatchResults.add(keywordMatchResult)
);
}
}
Loading