diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java index 2d69d45fb..bd7151a5e 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/HotBoardsRetrievalController.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -25,16 +24,13 @@ public class HotBoardsRetrievalController { private final RetrieveHotBoardsUseCase retrieveHotBoardsUseCase; @Operation(summary = "[G] 커뮤니티 인기 게시글 목록 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + - "반응(이모지), 댓글 수를 합친 결과가 높은 순으로 size만큼 조회 가능
" + "인기 게시글 선정 타입 설정 가능") @PreAuthorize("hasRole('GUEST')") @GetMapping("/hot") public ApiResponse> retrieveHotBoards( - @Min(message = "min.board.size", value = 0) - @RequestParam(name = "size", defaultValue = "5") int size, @RequestParam(name = "type") HotBoardStrategyType type ) { - List boards = retrieveHotBoardsUseCase.retrieveHotBoards(size, type); + List boards = retrieveHotBoardsUseCase.retrieveHotBoards(type); return ApiResponse.success(boards); } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java new file mode 100644 index 000000000..8ebd20644 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/RedisHotBoardPersistenceAdapter.java @@ -0,0 +1,38 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class RedisHotBoardPersistenceAdapter implements + RegisterHotBoardPort, + RetrieveHotBoardPort, + RemoveHotBoardPort { + + private static final String HOT_BOARDS_KEY = "hotBoards"; + + private final RedisTemplate redisTemplate; + + @Override + public void save(String boardId) { + redisTemplate.opsForList().rightPush(HOT_BOARDS_KEY, boardId); + } + + @Override + public List findAll() { + List hotBoards = redisTemplate.opsForList().range(HOT_BOARDS_KEY, 0, -1); + return (hotBoards != null) ? hotBoards : List.of(); + } + + @Override + public void clearHotBoard() { + redisTemplate.delete(HOT_BOARDS_KEY); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java index 59b4f0a79..2278e44ab 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveHotBoardsUseCase.java @@ -6,5 +6,5 @@ import java.util.List; public interface RetrieveHotBoardsUseCase { - List retrieveHotBoards(int size, HotBoardStrategyType type); + List retrieveHotBoards(HotBoardStrategyType type); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java new file mode 100644 index 000000000..20e66b609 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterHotBoardPort.java @@ -0,0 +1,5 @@ +package page.clab.api.domain.community.board.application.port.out; + +public interface RegisterHotBoardPort { + void save(String boardId); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java new file mode 100644 index 000000000..c0fbe82c6 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RemoveHotBoardPort.java @@ -0,0 +1,5 @@ +package page.clab.api.domain.community.board.application.port.out; + +public interface RemoveHotBoardPort { + void clearHotBoard(); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java new file mode 100644 index 000000000..c356f8d6e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveHotBoardPort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.community.board.application.port.out; + +import java.util.List; + +public interface RetrieveHotBoardPort { + List findAll(); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java similarity index 72% rename from src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java rename to src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java index f0370345b..d668537df 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DefaultHotBoardService.java @@ -2,12 +2,16 @@ import com.drew.lang.annotations.NotNull; import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; +import page.clab.api.domain.community.board.application.port.out.RegisterHotBoardPort; +import page.clab.api.domain.community.board.application.port.out.RemoveHotBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveHotBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; @@ -22,9 +26,12 @@ @Service("default") @RequiredArgsConstructor -public class DefaultHotBoardsRetrievalService implements HotBoardsStrategy { +public class DefaultHotBoardService implements HotBoardsStrategy { private final RetrieveBoardPort retrieveBoardPort; + private final RetrieveHotBoardPort retrieveHotBoardPort; + private final RegisterHotBoardPort registerHotBoardPort; + private final RemoveHotBoardPort removeHotBoardPort; private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; @@ -32,31 +39,46 @@ public class DefaultHotBoardsRetrievalService implements HotBoardsStrategy { @Transactional @Override - public List retrieveHotBoards(int size) { - List hotBoards = getHotBoards(size); + public List retrieveHotBoards() { + List hotBoardIds = retrieveHotBoardPort.findAll(); - return hotBoards.stream() + return hotBoardIds.stream() + .map(hotBoardId -> retrieveBoardPort.getById(Long.parseLong(hotBoardId))) .map(board -> mapToBoardListResponseDto(board, getMemberDetailedInfoByBoard(board))) .toList(); } - private List getHotBoards(int size) { - // 만약 게시글의 총 개수가 size보다 적다면 모든 게시글 반환 + @Transactional + @Scheduled(cron = "0 0 0 * * MON") // 매주 월요일 00:00 실행 + public void saveHotBoards() { + clearHotBoards(); // 저장된 지난 인기 게시글 초기화 + + List hotBoardIds = getHotBoards().stream() + .map(Board::getId) + .map(String::valueOf) + .toList(); + + hotBoardIds.forEach(registerHotBoardPort::save); + } + + private List getHotBoards() { + // 만약 게시글의 총 개수가 5개보다 적다면 모든 게시글 반환 List allBoards = retrieveBoardPort.findAll(); - if (allBoards.size() < size) { + if (allBoards.size() < 5) { return sortBoardsByReactionAndDateWithLimit(allBoards.size(), allBoards); } - List hotBoards = getHotBoardsForWeek(1, size); + List hotBoards = getHotBoardsForWeek(1, 5); int weeksAgo = 2; // 필요한 수량을 확보할 때까지 반복해서 이전 주로 이동하여 인기 게시글 보충 - while (hotBoards.size() < size) { - List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, size - hotBoards.size()); + while (hotBoards.size() < 5) { + List additionalBoards = getLatestHotBoardForWeek(weeksAgo++, 5 - hotBoards.size()); if (additionalBoards != null && !additionalBoards.isEmpty()) { hotBoards.addAll(additionalBoards); } } + return hotBoards; } @@ -108,4 +130,8 @@ private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetail return mapper.toListDto(board, memberInfo, commentCount); } + + private void clearHotBoards() { + removeHotBoardPort.clearHotBoard(); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java index 90ec3c9ad..b358965fc 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsRetrievalService.java @@ -16,8 +16,8 @@ public class HotBoardsRetrievalService implements RetrieveHotBoardsUseCase { private final Map strategies; @Override - public List retrieveHotBoards(int size, HotBoardStrategyType type) { + public List retrieveHotBoards(HotBoardStrategyType type) { HotBoardsStrategy hotBoardsStrategy = strategies.get(type.getKey()); - return hotBoardsStrategy.retrieveHotBoards(size); + return hotBoardsStrategy.retrieveHotBoards(); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java index 7a9f0311d..f438cfe60 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/HotBoardsStrategy.java @@ -5,5 +5,5 @@ import java.util.List; public interface HotBoardsStrategy { - List retrieveHotBoards(int size); + List retrieveHotBoards(); }