Skip to content

Commit 3a33ab2

Browse files
committed
feat: RankingService 클래스 추가 및 랭킹 조회 기능 구현
1 parent ee72acc commit 3a33ab2

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.loopers.domain.ranking;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.data.redis.core.RedisTemplate;
6+
import org.springframework.data.redis.core.ZSetOperations;
7+
import org.springframework.stereotype.Service;
8+
9+
import java.time.LocalDate;
10+
import java.util.*;
11+
12+
/**
13+
* 랭킹 조회 서비스 (commerce-api용)
14+
*/
15+
@Slf4j
16+
@Service
17+
@RequiredArgsConstructor
18+
public class RankingService {
19+
20+
private final RedisTemplate<String, String> redisTemplate;
21+
22+
/**
23+
* Top-N 랭킹 조회 (점수 포함)
24+
*/
25+
public List<RankingEntry> getTopNWithScores(LocalDate date, int n) {
26+
String key = RankingKey.daily(date);
27+
28+
try {
29+
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
30+
.reverseRangeWithScores(key, 0, n - 1);
31+
32+
if (tuples == null || tuples.isEmpty()) {
33+
return Collections.emptyList();
34+
}
35+
36+
return convertToRankingEntries(tuples);
37+
} catch (Exception e) {
38+
log.error("Top-N 랭킹 조회 실패: key={}, n={}", key, n, e);
39+
return Collections.emptyList();
40+
}
41+
}
42+
43+
/**
44+
* 페이지네이션 랭킹 조회
45+
*/
46+
public List<RankingEntry> getRankingPage(LocalDate date, int page, int size) {
47+
String key = RankingKey.daily(date);
48+
long start = (long) page * size;
49+
long end = start + size - 1;
50+
51+
try {
52+
Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
53+
.reverseRangeWithScores(key, start, end);
54+
55+
if (tuples == null || tuples.isEmpty()) {
56+
return Collections.emptyList();
57+
}
58+
59+
return convertToRankingEntries(tuples);
60+
} catch (Exception e) {
61+
log.error("랭킹 페이지 조회 실패: key={}, page={}, size={}", key, page, size, e);
62+
return Collections.emptyList();
63+
}
64+
}
65+
66+
/**
67+
* 특정 상품의 순위 조회
68+
*
69+
* @return 순위 (1부터 시작), 랭킹에 없으면 null
70+
*/
71+
public Long getRank(Long productId, LocalDate date) {
72+
String key = RankingKey.daily(date);
73+
String member = productId.toString();
74+
75+
try {
76+
Long rank = redisTemplate.opsForZSet().reverseRank(key, member);
77+
return rank != null ? rank + 1 : null;
78+
} catch (Exception e) {
79+
log.error("순위 조회 실패: key={}, productId={}", key, productId, e);
80+
return null;
81+
}
82+
}
83+
84+
/**
85+
* 특정 상품의 점수 조회
86+
*/
87+
public Double getScore(Long productId, LocalDate date) {
88+
String key = RankingKey.daily(date);
89+
String member = productId.toString();
90+
91+
try {
92+
return redisTemplate.opsForZSet().score(key, member);
93+
} catch (Exception e) {
94+
log.error("점수 조회 실패: key={}, productId={}", key, productId, e);
95+
return null;
96+
}
97+
}
98+
99+
/**
100+
* 랭킹에 진입한 상품 수 조회
101+
*/
102+
public Long getRankingSize(LocalDate date) {
103+
String key = RankingKey.daily(date);
104+
105+
try {
106+
Long size = redisTemplate.opsForZSet().zCard(key);
107+
return size != null ? size : 0L;
108+
} catch (Exception e) {
109+
log.error("랭킹 사이즈 조회 실패: key={}", key, e);
110+
return 0L;
111+
}
112+
}
113+
114+
private List<RankingEntry> convertToRankingEntries(Set<ZSetOperations.TypedTuple<String>> tuples) {
115+
List<RankingEntry> entries = new ArrayList<>();
116+
for (ZSetOperations.TypedTuple<String> tuple : tuples) {
117+
if (tuple.getValue() != null && tuple.getScore() != null) {
118+
entries.add(new RankingEntry(
119+
Long.parseLong(tuple.getValue()),
120+
tuple.getScore()
121+
));
122+
}
123+
}
124+
return entries;
125+
}
126+
}

0 commit comments

Comments
 (0)