From cd5ce518ddadd3f08fa7457b16b2d88600a7903b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=8F=84=EB=AA=A8?= Date: Sun, 12 Nov 2023 16:37:49 +0900 Subject: [PATCH] =?UTF-8?q?[#142]=20feat:=20Sorted=20set=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=9D=B8=EA=B8=B0=20=ED=83=9C=EA=B7=B8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#142] feat: RedisTemplate Bean 등록 * [#142] feat: Failure Register Enevet와 Handler 생성 * [#142] feat: 실패 기록 저장 시 추천 태그 스코어 증가 * [#142] feat: 추천 태그 조회 --- .../common/consts/TodaysFailConst.java | 1 + .../domains/failure/domain/Failure.java | 8 ++++ .../events/FailureRegisterEvent.java | 14 ++++++ .../handler/FailureRegisterEventHandler.java | 44 +++++++++++++++++++ .../config/redis/RedisCacheConfig.java | 12 +++++ .../todaysfail/api/web/tag/TagController.java | 11 +++-- .../dto/response/TagRecommendResponse.java | 9 ++++ .../web/tag/usecase/TagRecommendUseCase.java | 26 +++++++++++ 8 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 TodaysFail-Domain/src/main/java/com/todaysfail/events/FailureRegisterEvent.java create mode 100644 TodaysFail-Domain/src/main/java/com/todaysfail/events/handler/FailureRegisterEventHandler.java create mode 100644 TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/dto/response/TagRecommendResponse.java create mode 100644 TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/usecase/TagRecommendUseCase.java diff --git a/TodaysFail-Common/src/main/java/com/todaysfail/common/consts/TodaysFailConst.java b/TodaysFail-Common/src/main/java/com/todaysfail/common/consts/TodaysFailConst.java index a385bc7..2f63682 100644 --- a/TodaysFail-Common/src/main/java/com/todaysfail/common/consts/TodaysFailConst.java +++ b/TodaysFail-Common/src/main/java/com/todaysfail/common/consts/TodaysFailConst.java @@ -12,6 +12,7 @@ public class TodaysFailConst { public static final String TOKEN_ISSUER = "TodaysFail"; public static final String ACCESS_TOKEN = "ACCESS_TOKEN"; public static final String REFRESH_TOKEN = "REFRESH_TOKEN"; + public static final String RECOMMEND_TAG_KEY = "RecommendTag"; public static final int MILLI_TO_SECOND = 1000; public static final int SERVICE_UNAVAILABLE = 503; diff --git a/TodaysFail-Domain/src/main/java/com/todaysfail/domains/failure/domain/Failure.java b/TodaysFail-Domain/src/main/java/com/todaysfail/domains/failure/domain/Failure.java index 2e904f4..e36d5f0 100644 --- a/TodaysFail-Domain/src/main/java/com/todaysfail/domains/failure/domain/Failure.java +++ b/TodaysFail-Domain/src/main/java/com/todaysfail/domains/failure/domain/Failure.java @@ -1,8 +1,10 @@ package com.todaysfail.domains.failure.domain; +import com.todaysfail.aop.event.Events; import com.todaysfail.common.BaseTimeEntity; import com.todaysfail.config.converter.LongArrayConverter; import com.todaysfail.domains.failure.exception.FailureNotOwnedByUserException; +import com.todaysfail.events.FailureRegisterEvent; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -12,6 +14,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.PostPersist; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -48,6 +51,11 @@ public class Failure extends BaseTimeEntity { private boolean secret; + @PostPersist + void registerEvent() { + Events.raise(new FailureRegisterEvent(this)); + } + public boolean isMine(Long userId) { return this.userId.equals(userId); } diff --git a/TodaysFail-Domain/src/main/java/com/todaysfail/events/FailureRegisterEvent.java b/TodaysFail-Domain/src/main/java/com/todaysfail/events/FailureRegisterEvent.java new file mode 100644 index 0000000..38557bd --- /dev/null +++ b/TodaysFail-Domain/src/main/java/com/todaysfail/events/FailureRegisterEvent.java @@ -0,0 +1,14 @@ +package com.todaysfail.events; + +import com.todaysfail.aop.event.DomainEvent; +import com.todaysfail.domains.failure.domain.Failure; +import lombok.Getter; + +@Getter +public class FailureRegisterEvent extends DomainEvent { + private final Failure failure; + + public FailureRegisterEvent(Failure failure) { + this.failure = failure; + } +} diff --git a/TodaysFail-Domain/src/main/java/com/todaysfail/events/handler/FailureRegisterEventHandler.java b/TodaysFail-Domain/src/main/java/com/todaysfail/events/handler/FailureRegisterEventHandler.java new file mode 100644 index 0000000..381b4de --- /dev/null +++ b/TodaysFail-Domain/src/main/java/com/todaysfail/events/handler/FailureRegisterEventHandler.java @@ -0,0 +1,44 @@ +package com.todaysfail.events.handler; + +import static com.todaysfail.common.consts.TodaysFailConst.RECOMMEND_TAG_KEY; + +import com.todaysfail.common.annotation.EventHandler; +import com.todaysfail.domains.failure.domain.Failure; +import com.todaysfail.domains.tag.domain.Tag; +import com.todaysfail.domains.tag.port.TagQueryPort; +import com.todaysfail.events.FailureRegisterEvent; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@EventHandler +@RequiredArgsConstructor +public class FailureRegisterEventHandler { + private final TagQueryPort tagQueryPort; + private final RedisTemplate redisTemplate; + + @Async + @TransactionalEventListener( + classes = FailureRegisterEvent.class, + phase = TransactionPhase.AFTER_COMMIT) + public void handleUserRegisterEvent(FailureRegisterEvent event) { + final Failure failure = event.getFailure(); + log.info("[DOMAIN EVENT : FailureRegisterEvent] failureId: {}", failure.getId()); + + List tagIds = failure.getTags(); + List tags = tagQueryPort.queryAllByIds(tagIds); + + tags.stream() + .forEach( + tag -> { + redisTemplate + .opsForZSet() + .incrementScore(RECOMMEND_TAG_KEY, tag.getTagName(), 1); + }); + } +} diff --git a/TodaysFail-Infrastructure/src/main/java/com/todaysfail/config/redis/RedisCacheConfig.java b/TodaysFail-Infrastructure/src/main/java/com/todaysfail/config/redis/RedisCacheConfig.java index 7e0ddd2..d7903c7 100644 --- a/TodaysFail-Infrastructure/src/main/java/com/todaysfail/config/redis/RedisCacheConfig.java +++ b/TodaysFail-Infrastructure/src/main/java/com/todaysfail/config/redis/RedisCacheConfig.java @@ -8,7 +8,9 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -32,4 +34,14 @@ public CacheManager oidcCacheManager(RedisConnectionFactory cf) { .cacheDefaults(redisCacheConfiguration) .build(); } + + @Bean + public RedisTemplate redisTemplate( + RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + return redisTemplate; + } } diff --git a/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/TagController.java b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/TagController.java index 32d8bf1..46f9636 100644 --- a/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/TagController.java +++ b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/TagController.java @@ -1,7 +1,9 @@ package com.todaysfail.api.web.tag; +import com.todaysfail.api.web.tag.dto.response.TagRecommendResponse; import com.todaysfail.api.web.tag.dto.response.TagResponse; import com.todaysfail.api.web.tag.usecase.TagPopularUseCase; +import com.todaysfail.api.web.tag.usecase.TagRecommendUseCase; import com.todaysfail.api.web.tag.usecase.TagSearchUseCase; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -21,6 +23,7 @@ public class TagController { private final TagSearchUseCase tagSearchUseCase; private final TagPopularUseCase tagPopularUseCase; + private final TagRecommendUseCase tagRecommendUseCase; @Operation(summary = "태그를 검색합니다. (5개)") @GetMapping("/search") @@ -34,7 +37,9 @@ public List popular() { return tagPopularUseCase.execute(); } - // @Operation(summary = "추천 태그를 조회합니다.") - // @GetMapping("/recommend") - // TODO: 추천 태그 조회 API 구현 + @Operation(summary = "추천 태그를 조회합니다.") + @GetMapping("/recommend") + public List recommendTag() { + return tagRecommendUseCase.execute(); + } } diff --git a/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/dto/response/TagRecommendResponse.java b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/dto/response/TagRecommendResponse.java new file mode 100644 index 0000000..084146a --- /dev/null +++ b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/dto/response/TagRecommendResponse.java @@ -0,0 +1,9 @@ +package com.todaysfail.api.web.tag.dto.response; + +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; + +public record TagRecommendResponse(String tagName, double score) { + public static TagRecommendResponse from(TypedTuple tuple) { + return new TagRecommendResponse(tuple.getValue(), tuple.getScore()); + } +} diff --git a/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/usecase/TagRecommendUseCase.java b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/usecase/TagRecommendUseCase.java new file mode 100644 index 0000000..5d45efe --- /dev/null +++ b/TodaysFail-Interface/src/main/java/com/todaysfail/api/web/tag/usecase/TagRecommendUseCase.java @@ -0,0 +1,26 @@ +package com.todaysfail.api.web.tag.usecase; + +import static com.todaysfail.common.consts.TodaysFailConst.RECOMMEND_TAG_KEY; + +import com.todaysfail.api.web.tag.dto.response.TagRecommendResponse; +import com.todaysfail.common.annotation.UseCase; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; + +@UseCase +@RequiredArgsConstructor +public class TagRecommendUseCase { + private final RedisTemplate redisTemplate; + + public List execute() { + String key = RECOMMEND_TAG_KEY; + ZSetOperations ZSetOperations = redisTemplate.opsForZSet(); + Set> typedTuples = ZSetOperations.reverseRangeWithScores(key, 0, 9); + return typedTuples.stream().map(TagRecommendResponse::from).collect(Collectors.toList()); + } +}