66import org .springframework .data .redis .core .ZSetOperations ;
77import org .springframework .stereotype .Service ;
88
9+ import java .time .DayOfWeek ;
910import java .time .LocalDate ;
11+ import java .time .YearMonth ;
12+ import java .time .format .DateTimeFormatter ;
1013import java .util .*;
1114
12- /**
13- * 랭킹 조회 서비스 (commerce-api용)
14- */
1515@ Slf4j
1616@ Service
1717@ RequiredArgsConstructor
1818public class RankingService {
1919
2020 private final RedisTemplate <String , String > redisTemplate ;
21+ private final RankingRepository rankingRepository ;
22+
23+ private static final String KEY_PREFIX = "ranking:all:" ;
24+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter .ofPattern ("yyyyMMdd" );
2125
22- /**
23- * Top-N 랭킹 조회 (점수 포함)
24- */
2526 public List <RankingEntry > getTopNWithScores (LocalDate date , int n ) {
26- String key = RankingKey . daily ( date );
27+ String key = KEY_PREFIX + date . format ( DATE_FORMATTER );
2728
2829 try {
2930 Set <ZSetOperations .TypedTuple <String >> tuples = redisTemplate .opsForZSet ()
@@ -40,11 +41,8 @@ public List<RankingEntry> getTopNWithScores(LocalDate date, int n) {
4041 }
4142 }
4243
43- /**
44- * 페이지네이션 랭킹 조회
45- */
4644 public List <RankingEntry > getRankingPage (LocalDate date , int page , int size ) {
47- String key = RankingKey . daily ( date );
45+ String key = KEY_PREFIX + date . format ( DATE_FORMATTER );
4846 long start = (long ) page * size ;
4947 long end = start + size - 1 ;
5048
@@ -63,13 +61,8 @@ public List<RankingEntry> getRankingPage(LocalDate date, int page, int size) {
6361 }
6462 }
6563
66- /**
67- * 특정 상품의 순위 조회
68- *
69- * @return 순위 (1부터 시작), 랭킹에 없으면 null
70- */
7164 public Long getRank (Long productId , LocalDate date ) {
72- String key = RankingKey . daily ( date );
65+ String key = KEY_PREFIX + date . format ( DATE_FORMATTER );
7366 String member = productId .toString ();
7467
7568 try {
@@ -81,36 +74,96 @@ public Long getRank(Long productId, LocalDate date) {
8174 }
8275 }
8376
84- /**
85- * 특정 상품의 점수 조회
86- */
87- public Double getScore (Long productId , LocalDate date ) {
88- String key = RankingKey .daily (date );
89- String member = productId .toString ();
77+ public Long getRankingSize (LocalDate date ) {
78+ String key = KEY_PREFIX + date .format (DATE_FORMATTER );
9079
9180 try {
92- return redisTemplate .opsForZSet ().score (key , member );
81+ Long size = redisTemplate .opsForZSet ().zCard (key );
82+ return size != null ? size : 0L ;
9383 } catch (Exception e ) {
94- log .error ("점수 조회 실패: key={}, productId={}" , key , productId , e );
84+ log .error ("랭킹 사이즈 조회 실패: key={}" , key , e );
85+ return 0L ;
86+ }
87+ }
88+
89+ public List <RankingEntry > getWeeklyRankingPage (LocalDate date , int page , int size ) {
90+ LocalDate weekStart = date .with (DayOfWeek .MONDAY );
91+ LocalDate weekEnd = date .with (DayOfWeek .SUNDAY );
92+ int offset = page * size ;
93+
94+ try {
95+ List <WeeklyRanking > ranks = rankingRepository .findWeeklyByDateOrderByRank (weekStart , weekEnd , size , offset );
96+ return ranks .stream ()
97+ .map (r -> new RankingEntry (r .getProductId (), r .getScore ()))
98+ .toList ();
99+ } catch (Exception e ) {
100+ log .error ("주간 랭킹 페이지 조회 실패: weekStart={}, weekEnd={}, page={}, size={}" ,
101+ weekStart , weekEnd , page , size , e );
102+ return Collections .emptyList ();
103+ }
104+ }
105+
106+ public List <RankingEntry > getWeeklyTopN (LocalDate date , int n ) {
107+ return getWeeklyRankingPage (date , 0 , n );
108+ }
109+
110+ public Long getWeeklyRank (Long productId , LocalDate date ) {
111+ LocalDate weekStart = date .with (DayOfWeek .MONDAY );
112+ LocalDate weekEnd = date .with (DayOfWeek .SUNDAY );
113+
114+ try {
115+ return rankingRepository .findWeeklyByProductIdAndDate (productId , weekStart , weekEnd )
116+ .map (r -> (long ) r .getRank ())
117+ .orElse (null );
118+ } catch (Exception e ) {
119+ log .error ("주간 순위 조회 실패: productId={}, weekStart={}, weekEnd={}" , productId , weekStart , weekEnd , e );
95120 return null ;
96121 }
97122 }
98123
99- /**
100- * 랭킹에 진입한 상품 수 조회
101- */
102- public Long getRankingSize (LocalDate date ) {
103- String key = RankingKey .daily (date );
124+ public Long getWeeklyRankingSize (LocalDate date ) {
125+ LocalDate weekStart = date .with (DayOfWeek .MONDAY );
126+ LocalDate weekEnd = date .with (DayOfWeek .SUNDAY );
127+ return rankingRepository .countWeeklyByDate (weekStart , weekEnd );
128+ }
129+
130+ public List <RankingEntry > getMonthlyRankingPage (LocalDate date , int page , int size ) {
131+ YearMonth yearMonth = YearMonth .from (date );
132+ int offset = page * size ;
104133
105134 try {
106- Long size = redisTemplate .opsForZSet ().zCard (key );
107- return size != null ? size : 0L ;
135+ List <MonthlyRanking > ranks = rankingRepository .findMonthlyByPeriodOrderByRank (yearMonth , size , offset );
136+ return ranks .stream ()
137+ .map (r -> new RankingEntry (r .getProductId (), r .getScore ()))
138+ .toList ();
108139 } catch (Exception e ) {
109- log .error ("랭킹 사이즈 조회 실패: key={}" , key , e );
110- return 0L ;
140+ log .error ("월간 랭킹 페이지 조회 실패: yearMonth={}, page={}, size={}" , yearMonth , page , size , e );
141+ return Collections .emptyList ();
142+ }
143+ }
144+
145+ public List <RankingEntry > getMonthlyTopN (LocalDate date , int n ) {
146+ return getMonthlyRankingPage (date , 0 , n );
147+ }
148+
149+ public Long getMonthlyRank (Long productId , LocalDate date ) {
150+ YearMonth yearMonth = YearMonth .from (date );
151+
152+ try {
153+ return rankingRepository .findMonthlyByProductIdAndPeriod (productId , yearMonth )
154+ .map (r -> (long ) r .getRank ())
155+ .orElse (null );
156+ } catch (Exception e ) {
157+ log .error ("월간 순위 조회 실패: productId={}, yearMonth={}" , productId , yearMonth , e );
158+ return null ;
111159 }
112160 }
113161
162+ public Long getMonthlyRankingSize (LocalDate date ) {
163+ YearMonth yearMonth = YearMonth .from (date );
164+ return rankingRepository .countMonthlyByPeriod (yearMonth );
165+ }
166+
114167 private List <RankingEntry > convertToRankingEntries (Set <ZSetOperations .TypedTuple <String >> tuples ) {
115168 List <RankingEntry > entries = new ArrayList <>();
116169 for (ZSetOperations .TypedTuple <String > tuple : tuples ) {
0 commit comments