Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.loopers.application.ranking;

import com.loopers.domain.ranking.Period;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
@RequiredArgsConstructor
public class DailyRankingService {
private final StringRedisTemplate redisTemplate;

public RankingV1Dto.ProductRankingPageResponse getDailyRanking(String date, Period period, int page, int size) {
if (page < 1) page = 1;
if (size < 1) size = 20;

String key = "ranking:all:" + date;
ZSetOperations<String, String> zset = redisTemplate.opsForZSet();

Long total = zset.size(key);
long totalElements = (total == null) ? 0 : total;

if (totalElements == 0) {
return new RankingV1Dto.ProductRankingPageResponse(date, Period.DAILY, date, date, page, size, 0, 0, List.of());
}

long start = (long) (page - 1) * size;
long end = start + size - 1;

if (start >= totalElements) {
int totalPages = (int) Math.ceil((double) totalElements / size);
return new RankingV1Dto.ProductRankingPageResponse(date, Period.DAILY, date, date, page, size, totalElements, totalPages, List.of());
}

Set<ZSetOperations.TypedTuple<String>> tuples =
zset.reverseRangeWithScores(key, start, end);

List<RankingV1Dto.ProductRankingResponse> items = new ArrayList<>();
if (tuples != null) {
long rank = start + 1;
for (var t : tuples) {
String member = t.getValue();
Double score = t.getScore();
if (member == null || score == null) continue;

items.add(new RankingV1Dto.ProductRankingResponse(
rank++,
Long.parseLong(member),
score
));
}
Comment on lines +46 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

Long.parseLong(member)์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ

Redis์—์„œ ๊ฐ€์ ธ์˜จ member ๊ฐ’์ด ์œ ํšจํ•œ ์ˆซ์ž๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ NumberFormatException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์ด ๋ณด์žฅ๋œ๋‹ค๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ๋ฐฉ์–ด์  ์ฝ”๋”ฉ์„ ์œ„ํ•ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

๐Ÿ”Ž ๋ฐฉ์–ด์  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ œ์•ˆ
             for (var t : tuples) {
                 String member = t.getValue();
                 Double score = t.getScore();
                 if (member == null || score == null) continue;

+                Long productId;
+                try {
+                    productId = Long.parseLong(member);
+                } catch (NumberFormatException e) {
+                    continue; // ๋˜๋Š” ๋กœ๊น… ํ›„ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
+                }
+
                 items.add(new RankingV1Dto.ProductRankingResponse(
                         rank++,
-                        Long.parseLong(member),
+                        productId,
                         score
                 ));
             }
๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/DailyRankingService.java
around lines 46 to 56, parsing the Redis member string with
Long.parseLong(member) can throw NumberFormatException for non-numeric or
malformed values; wrap the parse in a defensive check (e.g., trim and validate
with a numeric check or regex) or a try-catch around Long.parseLong, log a
warning including the offending member and skip adding that entry (do not
increment rank for skipped items), and ensure null/blank members are also
handled consistently so the loop continues safely without crashing.

}

int totalPages = (int) Math.ceil((double) totalElements / size);
return new RankingV1Dto.ProductRankingPageResponse(date, Period.DAILY, date, date, page, size, totalElements, totalPages, items);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.loopers.application.ranking;

import com.loopers.domain.ranking.MonthlyRankingMv;
import com.loopers.domain.ranking.MonthlyRankingRepository;
import com.loopers.domain.ranking.Period;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class MonthlyRankingService {
private static final DateTimeFormatter BASIC = DateTimeFormatter.BASIC_ISO_DATE;

private final MonthlyRankingRepository monthlyRankingRepository;

public RankingV1Dto.ProductRankingPageResponse getMonthlyTop100(String date, Period period, int page, int size) {
int safePage = Math.max(page, 1);
int safeSize = Math.max(size, 1);

LocalDate end = LocalDate.parse(date, BASIC);
String endDate = end.format(BASIC);

String startDate = end.minusDays(30).format(BASIC);

System.out.println("startDate = " + startDate);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

ํ”„๋กœ๋•์…˜ ๋กœ๊น… ๋ฐฉ์‹์œผ๋กœ ๊ต์ฒด ํ•„์š”

System.out.println์€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์— ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ ์ ˆํ•œ ๋กœ๊น… ํ”„๋ ˆ์ž„์›Œํฌ(SLF4J)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ ๋ ˆ๋ฒจ๊ณผ ์ถœ๋ ฅ ์œ„์น˜๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณ€๊ฒฝํ•˜์„ธ์š”.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •์‚ฌํ•ญ

ํด๋ž˜์Šค ์ƒ๋‹จ์— logger ์„ ์–ธ์„ ์ถ”๊ฐ€:

+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Component
 @RequiredArgsConstructor
 public class MonthlyRankingService {

๊ทธ๋ฆฌ๊ณ  System.out.println์„ ๋กœ๊ฑฐ๋กœ ๊ต์ฒด:

-        System.out.println("startDate = " + startDate);
+        log.debug("Monthly ranking calculation - startDate: {}, endDate: {}", startDate, endDate);

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/MonthlyRankingService.java
around line 30, replace the System.out.println call with SLF4J logging: add a
private static final Logger logger =
LoggerFactory.getLogger(MonthlyRankingService.class) declaration at the top of
the class (ensure org.slf4j.Logger and org.slf4j.LoggerFactory are imported),
then change the println to logger.debug or logger.info("startDate = {}",
startDate) as appropriate for the message severity so logs are managed by the
configured logging framework.


long totalElements = monthlyRankingRepository.countMonthly(startDate, endDate);

if (totalElements == 0) {
return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
0,
0,
List.of()
);
}

int totalPages = (int) Math.ceil((double) totalElements / safeSize);

long offset = (long) (safePage - 1) * safeSize;
if (offset >= totalElements) {
return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
totalElements,
totalPages,
List.of()
);
}

List<MonthlyRankingMv> rows = monthlyRankingRepository.getMonthlyTop100(endDate, startDate, safePage, safeSize);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Find repository interfaces and service implementation
echo "=== ํŒŒ์ผ ์œ„์น˜ ํ™•์ธ ==="
find . -type f -name "*RankingRepository*" -o -name "MonthlyRankingService.java" | head -20

echo -e "\n=== MonthlyRankingService ํŒŒ์ผ ์ฐพ๊ธฐ ==="
fd MonthlyRankingService.java

echo -e "\n=== ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ํŒŒ์ผ๋“ค ์ฐพ๊ธฐ ==="
fd "RankingRepository" --type f

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 1111


๐Ÿ Script executed:

#!/bin/bash
# MonthlyRankingService์˜ line 32์™€ 65 ํ™•์ธ
echo "=== MonthlyRankingService.java ๋‚ด์šฉ (๋ผ์ธ 25-75) ==="
cat -n apps/commerce-api/src/main/java/com/loopers/application/ranking/MonthlyRankingService.java | sed -n '25,75p'

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 2190


๐Ÿ Script executed:

#!/bin/bash
# ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ํ™•์ธ
echo "=== MonthlyRankingRepository getMonthlyTop100 ๋ฉ”์„œ๋“œ ==="
rg "getMonthlyTop100" -A 2 -B 1

echo -e "\n=== MonthlyRankingRepository countMonthly ๋ฉ”์„œ๋“œ ==="
rg "countMonthly" -A 2 -B 1

echo -e "\n=== WeeklyRankingRepository getWeeklyTop100 ๋ฉ”์„œ๋“œ ==="
rg "getWeeklyTop100" -A 2 -B 1

echo -e "\n=== WeeklyRankingRepository countWeekly ๋ฉ”์„œ๋“œ ==="
rg "countWeekly" -A 2 -B 1

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 9011


๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ๊ฐ„ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ ์ผ๊ด€์„ฑ ํ†ต์ผ ํ•„์š”

Line 65์˜ getMonthlyTop100(endDate, startDate, ...)์™€ Line 32์˜ countMonthly(startDate, endDate) ํ˜ธ์ถœ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๊ฐ€ ๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. MonthlyRankingRepository ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๋‘ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๊ฐ€ ์ผ๊ด€๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ (getMonthlyTop100์€ endDate ๋จผ์ €, countMonthly๋Š” startDate ๋จผ์ €), WeeklyRankingRepository์™€ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ํ†ต์ผํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ๋…ผ๋ฆฌ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฉ”์„œ๋“œ๋“ค์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€ํ•˜๋ฉด ํ˜ผ๋™์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/MonthlyRankingService.java
around line 65, the call getMonthlyTop100(endDate, startDate, ...) uses the
opposite parameter order to countMonthly(startDate, endDate) (called at line
32); make the parameter ordering consistent with WeeklyRankingRepository and
other methods by changing getMonthlyTop100's signature and all its usages to
accept (startDate, endDate, ...) instead of (endDate, startDate), then update
the repository implementation and any callers (including this line 65) to pass
startDate first and endDate second so both methods use the same
startDate,endDate ordering.


List<RankingV1Dto.ProductRankingResponse> items = new ArrayList<>(rows.size());
long rank = offset + 1;

for (MonthlyRankingMv row : rows) {
items.add(new RankingV1Dto.ProductRankingResponse(
rank++,
row.getProductId(),
row.getScore().doubleValue()
));
}

return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
totalElements,
totalPages,
items
);
}
}
Original file line number Diff line number Diff line change
@@ -1,70 +1,31 @@
package com.loopers.application.ranking;

import com.loopers.interfaces.api.ranking.RankingV1Dto;
import com.loopers.domain.ranking.Period;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
@RequiredArgsConstructor
public class RankingFacade {
private final StringRedisTemplate redisTemplate;
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
private static final DateTimeFormatter YYYYMMDD = DateTimeFormatter.BASIC_ISO_DATE;

public RankingV1Dto.ProductRankingPageResponse getDailyProductRanking(int page, int size) {
if (page < 1) page = 1;
if (size < 1) size = 20;

String date = LocalDate.now(KST).format(YYYYMMDD);

String key = "ranking:all:" + date;
ZSetOperations<String, String> zset = redisTemplate.opsForZSet();

Long total = zset.size(key);
long totalElements = (total == null) ? 0 : total;

if (totalElements == 0) {
return new RankingV1Dto.ProductRankingPageResponse(date, page, size, 0, 0, List.of());
}

long start = (long) (page - 1) * size;
long end = start + size - 1;

if (start >= totalElements) {
int totalPages = (int) Math.ceil((double) totalElements / size);
return new RankingV1Dto.ProductRankingPageResponse(date, page, size, totalElements, totalPages, List.of());
}

Set<ZSetOperations.TypedTuple<String>> tuples =
zset.reverseRangeWithScores(key, start, end);

List<RankingV1Dto.ProductRankingResponse> items = new ArrayList<>();
if (tuples != null) {
long rank = start + 1;
for (var t : tuples) {
String member = t.getValue();
Double score = t.getScore();
if (member == null || score == null) continue;

items.add(new RankingV1Dto.ProductRankingResponse(
rank++,
Long.parseLong(member),
score
));
private final DailyRankingService dailyRankingService;
private final WeeklyRankingService weeklyRankingService;
private final MonthlyRankingService monthlyRankingService;

public RankingV1Dto.ProductRankingPageResponse getProductRanking(String date, Period period, int page, int size) {
switch (period) {
case DAILY -> {
return dailyRankingService.getDailyRanking(date, period, page, size);
}
case WEEKLY -> {
return weeklyRankingService.getWeeklyTop100(date, period, page, size);
}
case MONTHLY -> {
return monthlyRankingService.getMonthlyTop100(date, period, page, size);
}
default -> throw new CoreException(ErrorType.BAD_REQUEST, "์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ธฐ๊ฐ„์ž…๋‹ˆ๋‹ค.");
}

int totalPages = (int) Math.ceil((double) totalElements / size);
return new RankingV1Dto.ProductRankingPageResponse(date, page, size, totalElements, totalPages, items);
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package com.loopers.interfaces.api.ranking;

import java.util.List;

public class RankingV1Dto {
public record ProductRankingResponse(
Long rank,
Long productId,
double score
) {}

public record ProductRankingPageResponse(
String date,
int page,
int size,
long totalElements,
int totalPages,
List<ProductRankingResponse> items
) {}
}
package com.loopers.application.ranking;

import com.loopers.domain.ranking.Period;

import java.util.List;

public class RankingV1Dto {
public record ProductRankingResponse(
Long rank,
Long productId,
double score
) {}

public record ProductRankingPageResponse(
String date,
Period period,
String startDate,
String endDate,
int page,
int size,
long totalElements,
int totalPages,
List<ProductRankingResponse> items
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.loopers.application.ranking;

import com.loopers.domain.ranking.Period;
import com.loopers.domain.ranking.WeeklyRankingMv;
import com.loopers.domain.ranking.WeeklyRankingRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class WeeklyRankingService {
private static final DateTimeFormatter BASIC = DateTimeFormatter.BASIC_ISO_DATE;

private final WeeklyRankingRepository weeklyRankingRepository;

public RankingV1Dto.ProductRankingPageResponse getWeeklyTop100(String date, Period period, int page, int size) {
int safePage = Math.max(page, 1);
int safeSize = Math.max(size, 1);

LocalDate end = LocalDate.parse(date, BASIC);
String endDate = end.format(BASIC);

String startDate = end.minusDays(6).format(BASIC);

System.out.println("startDate = " + startDate);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

ํ”„๋กœ๋•์…˜ ๋กœ๊น… ๋ฐฉ์‹์œผ๋กœ ๊ต์ฒด ํ•„์š”

MonthlyRankingService์™€ ๋™์ผํ•œ ์ด์Šˆ: System.out.println์€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์— ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. SLF4J ๋กœ๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

๐Ÿ”Ž ์ œ์•ˆํ•˜๋Š” ์ˆ˜์ •์‚ฌํ•ญ
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Component
 @RequiredArgsConstructor
 public class WeeklyRankingService {
-        System.out.println("startDate = " + startDate);
+        log.debug("Weekly ranking calculation - startDate: {}, endDate: {}", startDate, endDate);

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In
apps/commerce-api/src/main/java/com/loopers/application/ranking/WeeklyRankingService.java
around line 30, replace the System.out.println call with SLF4J logging: add a
private static final org.slf4j.Logger logger =
org.slf4j.LoggerFactory.getLogger(WeeklyRankingService.class); import the SLF4J
classes, then change the print statement to logger.info("startDate = {}",
startDate); ensuring you use parameterized logging and remove the
System.out.println.


long totalElements = weeklyRankingRepository.countWeekly(startDate, endDate);

if (totalElements == 0) {
return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
0,
0,
List.of()
);
}

int totalPages = (int) Math.ceil((double) totalElements / safeSize);

long offset = (long) (safePage - 1) * safeSize;
if (offset >= totalElements) {
return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
totalElements,
totalPages,
List.of()
);
}

List<WeeklyRankingMv> rows = weeklyRankingRepository.getWeeklyTop100(endDate, startDate, safePage, safeSize);

List<RankingV1Dto.ProductRankingResponse> items = new ArrayList<>(rows.size());
long rank = offset + 1;

for (WeeklyRankingMv row : rows) {
items.add(new RankingV1Dto.ProductRankingResponse(
rank++,
row.getProductId(),
row.getScore().doubleValue()
));
}

return new RankingV1Dto.ProductRankingPageResponse(
date,
period,
startDate,
endDate,
safePage,
safeSize,
totalElements,
totalPages,
items
);
}

}
Loading