Skip to content

Commit

Permalink
Merge pull request #174 from prgrms-web-devcourse-final-project/feature/
Browse files Browse the repository at this point in the history
#173-letters-keyword-frequent

Feature/#173 letters keyword frequent
  • Loading branch information
l2yujw authored Dec 8, 2024
2 parents bba96fb + 979591c commit 9272e0f
Show file tree
Hide file tree
Showing 15 changed files with 107 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import org.springframework.web.bind.annotation.RestController;
import postman.bottler.global.response.ApiResponse;
import postman.bottler.keyword.dto.request.UserKeywordRequestDTO;
import postman.bottler.keyword.dto.response.FrequentKeywordsDTO;
import postman.bottler.keyword.dto.response.KeywordResponseDTO;
import postman.bottler.keyword.dto.response.UserKeywordResponseDTO;
import postman.bottler.keyword.service.KeywordService;
import postman.bottler.keyword.service.LetterKeywordService;
import postman.bottler.keyword.service.UserKeywordService;
import postman.bottler.user.auth.CustomUserDetails;

Expand All @@ -23,6 +25,7 @@ public class KeywordController {

private final UserKeywordService userKeywordService;
private final KeywordService keywordService;
private final LetterKeywordService letterKeywordService;

@Operation(
summary = "유저 키워드 목록 조회",
Expand Down Expand Up @@ -58,4 +61,16 @@ public ApiResponse<KeywordResponseDTO> getKeywordList() {
KeywordResponseDTO result = keywordService.getKeywords();
return ApiResponse.onSuccess(result);
}

@Operation(
summary = "사용자의 자주 쓰는 키워드 조회",
description = "현재 사용자의 자주 쓰는 키워드를 조회합니다"
)
@GetMapping("/frequent")
public ApiResponse<FrequentKeywordsDTO> getTopFrequentKeywords(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
FrequentKeywordsDTO result = letterKeywordService.getTopFrequentKeywords(userDetails.getUserId());
return ApiResponse.onSuccess(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package postman.bottler.keyword.dto.response;

import java.util.List;

public record FrequentKeywordsDTO(
List<String> keywords
) {
public static FrequentKeywordsDTO from(List<String> keywords) {
return new FrequentKeywordsDTO(
keywords
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package postman.bottler.keyword.infra;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
Expand Down Expand Up @@ -59,4 +60,22 @@ private List<Long> getRandomLetters(int limit, List<Long> excludedLetterIds) {
.limit(limit)
.fetch();
}

public List<LetterKeywordEntity> getFrequentKeywords(List<Long> letterIds) {
QLetterKeywordEntity letterKeyword = QLetterKeywordEntity.letterKeywordEntity;

return queryFactory
.select(Projections.constructor(LetterKeywordEntity.class,
letterKeyword.letterId.min(),
letterKeyword.keyword,
letterKeyword.isDeleted
))
.from(letterKeyword)
.where(letterKeyword.letterId.in(letterIds)
.and(letterKeyword.isDeleted.eq(false)))
.groupBy(letterKeyword.keyword)
.orderBy(letterKeyword.keyword.count().desc())
.limit(5)
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@ public List<Long> getMatchedLetters(List<String> userKeywords, List<Long> letter
public void markKeywordsAsDeleted(List<Long> letterIds) {
jdbcRepository.batchUpdateIsDeleted(letterIds);
}

@Override
public List<LetterKeyword> getFrequentKeywords(List<Long> letterIds) {
return queryDslRepository.getFrequentKeywords(letterIds).stream()
.map(LetterKeywordEntity::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class LetterKeywordEntity {
public LetterKeywordEntity(Long letterId, String keyword, boolean isDeleted) {
this.letterId = letterId;
this.keyword = keyword;
this.isDeleted = false;
this.isDeleted = isDeleted;
}

public static LetterKeywordEntity from(LetterKeyword letterKeyword) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class AsyncRecommendationService {
private final RecommendService recommendService;
private final UserKeywordService userKeywordService;
private final LetterBoxService letterBoxService;
private static final int RECOMMENDATION_LIMIT = 3;
private static final int RECOMMENDATION_LIMIT = 10;
private final RedisLetterService redisLetterService;

@Async
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public interface LetterKeywordRepository {
List<Long> getMatchedLetters(List<String> userKeywords, List<Long> letterIds, int limit);

void markKeywordsAsDeleted(List<Long> letterIds);

List<LetterKeyword> getFrequentKeywords(List<Long> letterIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import postman.bottler.keyword.domain.LetterKeyword;
import postman.bottler.keyword.dto.response.FrequentKeywordsDTO;
import postman.bottler.letter.service.LetterService;

@Slf4j
@Service
@RequiredArgsConstructor
public class LetterKeywordService {

private final LetterKeywordRepository letterKeywordRepository;
private final LetterService letterService;

@Transactional
public void createLetterKeywords(Long letterId, List<String> keywords) {
Expand All @@ -33,4 +38,15 @@ public List<String> getKeywords(Long letterId) {
public void markKeywordsAsDeleted(List<Long> letterIds) {
letterKeywordRepository.markKeywordsAsDeleted(letterIds);
}


@Transactional(readOnly = true)
public FrequentKeywordsDTO getTopFrequentKeywords(Long userId) {
List<Long> letterIds = letterService.findAllByUserId(userId);
List<LetterKeyword> frequentKeywords = letterKeywordRepository.getFrequentKeywords(letterIds);
List<String> keywords = frequentKeywords.stream()
.map(LetterKeyword::getKeyword)
.toList();
return FrequentKeywordsDTO.from(keywords);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import postman.bottler.keyword.util.RedisLetterKeyUtil;
import postman.bottler.letter.domain.BoxType;
import postman.bottler.letter.domain.Letter;
import postman.bottler.letter.domain.LetterType;
import postman.bottler.letter.dto.LetterBoxDTO;
import postman.bottler.letter.exception.LetterNotFoundException;
Expand All @@ -26,11 +28,13 @@ public class RedisLetterService {
private static final int MAX_RECOMMENDATIONS = 3;
private final LetterService letterService;

@Transactional
public void saveRecommendationsTemp(Long userId, List<Long> recommendations) {
String key = RedisLetterKeyUtil.getTempRecommendationKey(userId);
redisTemplate.opsForValue().set(key, recommendations);
}

@Transactional
public RecommendNotificationRequestDTO updateRecommendationsFromTemp(Long userId) {
String tempKey = RedisLetterKeyUtil.getTempRecommendationKey(userId);
String activeKey = RedisLetterKeyUtil.getActiveRecommendationKey(userId);
Expand All @@ -41,10 +45,11 @@ public RecommendNotificationRequestDTO updateRecommendationsFromTemp(Long userId
Long recommendId = findFirstValidLetter(tempRecommendations);
updateActiveRecommendations(recommendId, activeRecommendations, activeKey);
saveLetterToBox(userId, recommendId);

Letter letter = letterService.findLetter(recommendId);

redisTemplate.delete(tempKey);

return RecommendNotificationRequestDTO.of(userId, recommendId);
return RecommendNotificationRequestDTO.of(userId, recommendId, letter.getLabel());
}

private void saveLetterToBox(Long userId, Long letterId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ public interface LetterJpaRepository extends JpaRepository<LetterEntity, Long> {

@Query("SELECT l FROM LetterEntity l WHERE l.id IN :letterIds AND l.isDeleted = false")
List<LetterEntity> findAllByIds(List<Long> letterIds);

@Query("SELECT l FROM LetterEntity l WHERE l.userId = :userId AND l.isDeleted = false")
List<LetterEntity> findAllByUserId(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ public List<Letter> findAllByIds(List<Long> letterIds) {
public boolean checkLetterExists(Long letterId) {
return letterJpaRepository.existsById(letterId);
}

@Override
public List<Letter> findAllByUserId(Long userId) {
return letterJpaRepository.findAllByUserId(userId).stream()
.map(LetterEntity::toDomain)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public interface LetterRepository {
List<Letter> findAllByIds(List<Long> letterIds);

boolean checkLetterExists(Long letterId);

List<Letter> findAllByUserId(Long userId);
}
10 changes: 9 additions & 1 deletion src/main/java/postman/bottler/letter/service/LetterService.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public ReceiverDTO getReceiverInfoById(Long letterId) {
return ReceiverDTO.from(letter);
}

private Letter findLetter(Long letterId) {
@Transactional(readOnly = true)
public Letter findLetter(Long letterId) {
return letterRepository.findById(letterId)
.orElseThrow(() -> new LetterNotFoundException("키워드 편지가 존재하지 않습니다."));
}
Expand All @@ -89,4 +90,11 @@ public List<NotificationLabelRequestDTO> getLabels(List<Long> ids) {
.map(find -> new NotificationLabelRequestDTO(find.getId(), find.getLabel()))
.toList();
}

@Transactional(readOnly = true)
public List<Long> findAllByUserId(Long userId) {
return letterRepository.findAllByUserId(userId).stream()
.map(Letter::getId)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

public record RecommendNotificationRequestDTO(
Long userId,
Long letterId
Long letterId,
String label
) {
public static RecommendNotificationRequestDTO of(Long userId, Long letterId) {
return new RecommendNotificationRequestDTO(userId, letterId);
public static RecommendNotificationRequestDTO of(Long userId, Long letterId, String label) {
return new RecommendNotificationRequestDTO(userId, letterId, label);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spring:
dialect: org.hibernate.dialect.MySQLDialect
format_sql: true
highlight_sql: true
show-sql: false
show-sql: true
data:
redis:
host: ${REDIS_HOST}
Expand Down

0 comments on commit 9272e0f

Please sign in to comment.