diff --git a/src/main/java/com/matzip/place/application/port/RankingTempStore.java b/src/main/java/com/matzip/place/application/port/RankingTempStore.java new file mode 100644 index 0000000..609a43c --- /dev/null +++ b/src/main/java/com/matzip/place/application/port/RankingTempStore.java @@ -0,0 +1,17 @@ +package com.matzip.place.application.port; + +import com.matzip.place.api.response.PlaceRankingResponseDto; +import com.matzip.place.domain.Campus; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +public interface RankingTempStore { + + Optional> get(LocalDate date, Campus campus); + + void set(LocalDate date, Campus campus, List rankingResponseDtos); + + void remove(LocalDate date, Campus campus); +} diff --git a/src/main/java/com/matzip/place/application/service/PlaceReadService.java b/src/main/java/com/matzip/place/application/service/PlaceReadService.java index a3f9135..567182f 100644 --- a/src/main/java/com/matzip/place/application/service/PlaceReadService.java +++ b/src/main/java/com/matzip/place/application/service/PlaceReadService.java @@ -6,13 +6,13 @@ import com.matzip.place.api.response.CategoryPlaceResponseDto; import com.matzip.place.api.response.MapSearchResponseDto; import com.matzip.place.api.response.PlaceDetailResponseDto; +import com.matzip.place.application.port.RankingTempStore; import com.matzip.place.domain.entity.*; import com.matzip.place.api.response.PlaceRankingResponseDto; import com.matzip.place.domain.*; import com.matzip.place.infra.repository.*; import com.matzip.place.dto.CategoryDto; import com.matzip.place.dto.TagDto; -import com.matzip.user.domain.User; import com.matzip.user.infra.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; @@ -39,8 +39,10 @@ public class PlaceReadService { private final PlaceLikeRepository placeLikeRepository; private final UserRepository userRepository; private final ViewCountService viewCountService; + private final RankingTempStore rankingTempStore; private static final int RANKING_SIZE = 10; + private static final int MINIMUM_RANKING_SIZE = 3; @Transactional public PlaceDetailResponseDto getPlaceDetail(Long placeId, Long userId) { @@ -95,18 +97,17 @@ public List findPlacesInMapBounds(MapSearchRequestDto requ public List getRanking(Campus campus, String sort) { if ("views".equals(sort)) { - return getDailyRankingByViews(campus); + return getRankingByViewsWithFallback(campus); } // TODO: 찜 많은 맛집 기능 구현 후 추가하기 - return getDailyRankingByViews(campus); + return getRankingByViewsWithFallback(campus); } - private List getDailyRankingByViews(Campus campus) { - LocalDate today = LocalDate.now(); + public List getDailyRankingByViews(Campus campus, LocalDate date) { Pageable topN = PageRequest.of(0, RANKING_SIZE); - List dailyRankings = dailyViewCountRepository.findDailyRankingByCampus(campus, today, topN); + List dailyRankings = dailyViewCountRepository.findDailyRankingByCampus(campus, date, topN); return dailyRankings.stream() .map(dailyViewCount -> { @@ -117,6 +118,18 @@ private List getDailyRankingByViews(Campus campus) { .collect(Collectors.toList()); } + private List getRankingByViewsWithFallback(Campus campus) { + LocalDate today = LocalDate.now(); + List todaysRanking = getDailyRankingByViews(campus, today); + + if (todaysRanking.size() < MINIMUM_RANKING_SIZE) { + LocalDate yesterday = today.minusDays(1); + return rankingTempStore.get(yesterday, campus) + .orElseGet(() -> getDailyRankingByViews(campus, yesterday)); + } + return todaysRanking; + } + /** * Place의 연관 데이터를 조회하는 공통 메서드 */ diff --git a/src/main/java/com/matzip/place/infra/cache/PlaceRankingTempStoreMemory.java b/src/main/java/com/matzip/place/infra/cache/PlaceRankingTempStoreMemory.java new file mode 100644 index 0000000..91265fb --- /dev/null +++ b/src/main/java/com/matzip/place/infra/cache/PlaceRankingTempStoreMemory.java @@ -0,0 +1,41 @@ +package com.matzip.place.infra.cache; + +import com.matzip.place.api.response.PlaceRankingResponseDto; +import com.matzip.place.application.port.RankingTempStore; +import com.matzip.place.domain.Campus; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class PlaceRankingTempStoreMemory implements RankingTempStore { + + private final Map> store = new ConcurrentHashMap<>(); + + + @Override + public Optional> get(LocalDate date, Campus campus) { + String key = generateKey(date, campus); + return Optional.ofNullable(store.get(key)); + } + + @Override + public void set(LocalDate date, Campus campus, List rankingResponseDtos) { + String key = generateKey(date, campus); + store.put(key, rankingResponseDtos); + } + + @Override + public void remove(LocalDate date, Campus campus) { + String key = generateKey(date, campus); + store.remove(key); + } + + private String generateKey(LocalDate date, Campus campus) { + return date.toString() + ":" + campus.name(); + } +} diff --git a/src/main/java/com/matzip/place/infra/scheduler/PlaceRankingScheduler.java b/src/main/java/com/matzip/place/infra/scheduler/PlaceRankingScheduler.java new file mode 100644 index 0000000..30a7d9a --- /dev/null +++ b/src/main/java/com/matzip/place/infra/scheduler/PlaceRankingScheduler.java @@ -0,0 +1,34 @@ +package com.matzip.place.infra.scheduler; + +import com.matzip.place.api.response.PlaceRankingResponseDto; +import com.matzip.place.application.port.RankingTempStore; +import com.matzip.place.application.service.PlaceReadService; +import com.matzip.place.domain.Campus; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; + +@Component +@RequiredArgsConstructor +public class PlaceRankingScheduler { + + private final PlaceReadService placeReadService; + private final RankingTempStore rankingTempStore; + + @Scheduled(cron = "0 5 0 * * *") + public void cachePreviousDayPlaceRankings() { + LocalDate yesterday = LocalDate.now().minusDays(1); + LocalDate twoDaysAgo = LocalDate.now().minusDays(2); + + Arrays.stream(Campus.values()).forEach(campus -> { + List ranking = placeReadService.getDailyRankingByViews(campus, yesterday); + rankingTempStore.set(yesterday, campus, ranking); + + rankingTempStore.remove(twoDaysAgo, campus); + }); + } +}