-
Notifications
You must be signed in to change notification settings - Fork 35
Feat/volume 10 #242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: jeonga1022
Are you sure you want to change the base?
Feat/volume 10 #242
Changes from 28 commits
61b0df6
77225a4
bca67df
7275f5b
bc18cc4
d47efeb
e38d21b
3cf0dd6
dc137cf
82bd67b
e4a6149
569b5ae
045e6cb
c5a89f6
f994a91
7c253b2
ed9adee
b88de75
b1e72df
cdd6f76
3dc3c47
fc113a2
d2369ac
4a065de
db4572f
dc52982
08290df
01ed56a
f162468
fe77482
cec5275
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| package com.loopers.application.ranking; | ||
|
|
||
| import com.loopers.domain.product.Product; | ||
| import com.loopers.domain.product.ProductRepository; | ||
| import com.loopers.infrastructure.ranking.ProductRankMonthly; | ||
| import com.loopers.infrastructure.ranking.ProductRankMonthlyRepository; | ||
| import com.loopers.infrastructure.ranking.ProductRankWeekly; | ||
| import com.loopers.infrastructure.ranking.ProductRankWeeklyRepository; | ||
| import com.loopers.infrastructure.ranking.RankingEntry; | ||
| import com.loopers.infrastructure.ranking.RankingRedisService; | ||
| import com.loopers.interfaces.api.ranking.RankingDto; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.DayOfWeek; | ||
| import java.time.LocalDate; | ||
| import java.time.YearMonth; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class RankingFacade { | ||
|
|
||
| private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); | ||
|
|
||
| private final RankingRedisService rankingRedisService; | ||
| private final ProductRepository productRepository; | ||
| private final ProductRankWeeklyRepository productRankWeeklyRepository; | ||
| private final ProductRankMonthlyRepository productRankMonthlyRepository; | ||
|
|
||
| public RankingDto.RankingListResponse getRankings(String period, String dateStr, int page, int size) { | ||
| return switch (period.toLowerCase()) { | ||
| case "weekly" -> getWeeklyRankings(dateStr, page, size); | ||
| case "monthly" -> getMonthlyRankings(dateStr, page, size); | ||
| default -> getDailyRankings(dateStr, page, size); | ||
| }; | ||
| } | ||
|
|
||
| private RankingDto.RankingListResponse getDailyRankings(String dateStr, int page, int size) { | ||
| LocalDate date = parseDate(dateStr); | ||
| int offset = page * size; | ||
|
|
||
| List<RankingEntry> entries = rankingRedisService.getTopProducts(date, offset, size); | ||
|
|
||
| if (entries.isEmpty()) { | ||
| return new RankingDto.RankingListResponse(List.of(), page, size, 0); | ||
| } | ||
|
|
||
| long totalCount = rankingRedisService.getTotalCount(date); | ||
|
|
||
| List<Long> productIds = entries.stream() | ||
| .map(RankingEntry::productId) | ||
| .toList(); | ||
|
|
||
| Map<Long, Product> productMap = productRepository.findAllByIdIn(productIds).stream() | ||
| .collect(Collectors.toMap(Product::getId, p -> p)); | ||
|
|
||
| List<RankingDto.RankingResponse> rankings = new ArrayList<>(); | ||
| int rank = offset + 1; | ||
| for (RankingEntry entry : entries) { | ||
| Product product = productMap.get(entry.productId()); | ||
| if (product != null) { | ||
| rankings.add(new RankingDto.RankingResponse( | ||
| rank++, | ||
| product.getId(), | ||
| product.getName(), | ||
| product.getPrice(), | ||
| entry.score() | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| return new RankingDto.RankingListResponse(rankings, page, size, totalCount); | ||
| } | ||
|
|
||
| private RankingDto.RankingListResponse getWeeklyRankings(String dateStr, int page, int size) { | ||
| LocalDate date = parseDate(dateStr); | ||
| LocalDate weekStart = date.with(DayOfWeek.MONDAY); | ||
|
|
||
| List<ProductRankWeekly> weeklyRanks = productRankWeeklyRepository | ||
| .findByPeriodStartOrderByRankingAsc(weekStart); | ||
|
|
||
| if (weeklyRanks.isEmpty()) { | ||
| return new RankingDto.RankingListResponse(List.of(), page, size, 0); | ||
| } | ||
|
|
||
| int offset = page * size; | ||
| int toIndex = Math.min(offset + size, weeklyRanks.size()); | ||
| if (offset >= weeklyRanks.size()) { | ||
| return new RankingDto.RankingListResponse(List.of(), page, size, weeklyRanks.size()); | ||
| } | ||
|
|
||
| List<ProductRankWeekly> pagedRanks = weeklyRanks.subList(offset, toIndex); | ||
|
|
||
| List<Long> productIds = pagedRanks.stream() | ||
| .map(ProductRankWeekly::getProductId) | ||
| .toList(); | ||
|
|
||
| Map<Long, Product> productMap = productRepository.findAllByIdIn(productIds).stream() | ||
| .collect(Collectors.toMap(Product::getId, p -> p)); | ||
|
|
||
| List<RankingDto.RankingResponse> rankings = new ArrayList<>(); | ||
| for (ProductRankWeekly rank : pagedRanks) { | ||
| Product product = productMap.get(rank.getProductId()); | ||
| if (product != null) { | ||
| rankings.add(new RankingDto.RankingResponse( | ||
| rank.getRanking(), | ||
| product.getId(), | ||
| product.getName(), | ||
| product.getPrice(), | ||
| rank.getScore().doubleValue() | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| return new RankingDto.RankingListResponse(rankings, page, size, weeklyRanks.size()); | ||
| } | ||
|
|
||
| private RankingDto.RankingListResponse getMonthlyRankings(String dateStr, int page, int size) { | ||
| LocalDate date = parseDate(dateStr); | ||
| YearMonth yearMonth = YearMonth.from(date); | ||
| LocalDate monthStart = yearMonth.atDay(1); | ||
|
|
||
| List<ProductRankMonthly> monthlyRanks = productRankMonthlyRepository | ||
| .findByPeriodStartOrderByRankingAsc(monthStart); | ||
|
|
||
| if (monthlyRanks.isEmpty()) { | ||
| return new RankingDto.RankingListResponse(List.of(), page, size, 0); | ||
| } | ||
|
|
||
| int offset = page * size; | ||
| int toIndex = Math.min(offset + size, monthlyRanks.size()); | ||
| if (offset >= monthlyRanks.size()) { | ||
| return new RankingDto.RankingListResponse(List.of(), page, size, monthlyRanks.size()); | ||
| } | ||
|
|
||
| List<ProductRankMonthly> pagedRanks = monthlyRanks.subList(offset, toIndex); | ||
|
|
||
| List<Long> productIds = pagedRanks.stream() | ||
| .map(ProductRankMonthly::getProductId) | ||
| .toList(); | ||
|
|
||
| Map<Long, Product> productMap = productRepository.findAllByIdIn(productIds).stream() | ||
| .collect(Collectors.toMap(Product::getId, p -> p)); | ||
|
|
||
| List<RankingDto.RankingResponse> rankings = new ArrayList<>(); | ||
| for (ProductRankMonthly rank : pagedRanks) { | ||
| Product product = productMap.get(rank.getProductId()); | ||
| if (product != null) { | ||
| rankings.add(new RankingDto.RankingResponse( | ||
| rank.getRanking(), | ||
| product.getId(), | ||
| product.getName(), | ||
| product.getPrice(), | ||
| rank.getScore().doubleValue() | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| return new RankingDto.RankingListResponse(rankings, page, size, monthlyRanks.size()); | ||
| } | ||
|
|
||
| private LocalDate parseDate(String dateStr) { | ||
| if (dateStr == null || dateStr.isBlank()) { | ||
| return LocalDate.now(); | ||
| } | ||
| return LocalDate.parse(dateStr, DATE_FORMATTER); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.loopers.domain.product; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Index; | ||
| import jakarta.persistence.PrePersist; | ||
| import jakarta.persistence.Table; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Entity | ||
| @Table(name = "product_view_logs", indexes = { | ||
| @Index(name = "idx_product_id_created_at", columnList = "product_id, created_at") | ||
| }) | ||
|
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐งฉ Analysis chain๐ Script executed: #!/bin/bash
# Finding ProductViewLogRepository interface and usage
# Search for ProductViewLogRepository interface
fd "ProductViewLogRepository" -t f
# If file found, examine it
if [ -f "$(fd 'ProductViewLogRepository' -t f | head -1)" ]; then
echo "=== ProductViewLogRepository ==="
cat "$(fd 'ProductViewLogRepository' -t f | head -1)"
fiRepository: Loopers-dev-lab/loopers-spring-java-template Length of output: 479 ๐ Script executed: #!/bin/bash
# Search for ProductViewLog usage in services
rg -n "ProductViewLog" --type java -A 3 -B 2 | head -100Repository: Loopers-dev-lab/loopers-spring-java-template Length of output: 8980 ๐ Script executed: #!/bin/bash
# Search for query methods related to ProductViewLog
rg -n "findBy|OrderBy|Between" --type java | grep -i "productviewlog\|view.*log" -A 2 -B 2Repository: Loopers-dev-lab/loopers-spring-java-template Length of output: 882 ํ์ฌ ์ธ๋ฑ์ค๊ฐ ์ค์ ์ฟผ๋ฆฌ ํจํด๊ณผ ๋ง์ง ์์ต๋๋ค.
ํฅํ ์๊ฐ ๋ฒ์ ์กฐํ๋ ์ํ๋ณ ์กฐํ ๋ก๊ทธ ์ฟผ๋ฆฌ๊ฐ ํ์ํ ๋๊น์ง ์ธ๋ฑ์ค ์ ์๋ฅผ ์ ๊ฑฐํ๊ฑฐ๋, ์ค์ ์ฟผ๋ฆฌ ์๊ตฌ์ฌํญ์ด ํ์ ๋๋ฉด ๊ทธ๋ ์ถ๊ฐํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ๐ค Prompt for AI Agents |
||
| public class ProductViewLog { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(name = "product_id", nullable = false) | ||
| private Long productId; | ||
|
|
||
| @Column(name = "created_at", nullable = false, updatable = false) | ||
| private LocalDateTime createdAt; | ||
|
|
||
| protected ProductViewLog() { | ||
| } | ||
|
|
||
| private ProductViewLog(Long productId) { | ||
| this.productId = productId; | ||
| } | ||
|
|
||
| public static ProductViewLog create(Long productId) { | ||
| return new ProductViewLog(productId); | ||
| } | ||
|
|
||
| @PrePersist | ||
| private void prePersist() { | ||
| this.createdAt = LocalDateTime.now(); | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public Long getProductId() { | ||
| return productId; | ||
| } | ||
|
|
||
| public LocalDateTime getCreatedAt() { | ||
| return createdAt; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.loopers.domain.product; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface ProductViewLogRepository { | ||
|
|
||
| List<ProductViewLog> saveAll(List<ProductViewLog> logs); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
periodํ๋ผ๋ฏธํฐ null ์ฒดํฌ ๋๋ฝperiod.toLowerCase()ํธ์ถ ์period๊ฐ null์ด๋ฉดNullPointerException์ด ๋ฐ์ํฉ๋๋ค. API ๋ ์ด์ด์์ ๊ธฐ๋ณธ๊ฐ์ ๋ณด์ฅํ์ง ์๋๋ค๋ฉด ๋ฐฉ์ด ๋ก์ง์ด ํ์ํฉ๋๋ค.๐ ์์ ์ ์
public RankingDto.RankingListResponse getRankings(String period, String dateStr, int page, int size) { + String normalizedPeriod = (period == null) ? "daily" : period.toLowerCase(); - return switch (period.toLowerCase()) { + return switch (normalizedPeriod) { case "weekly" -> getWeeklyRankings(dateStr, page, size); case "monthly" -> getMonthlyRankings(dateStr, page, size); default -> getDailyRankings(dateStr, page, size); }; }๐ค Prompt for AI Agents