-
Notifications
You must be signed in to change notification settings - Fork 44
[volume-10] 주간/월간 랭킹 - 김선민 #417
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: seonminKim1122
Are you sure you want to change the base?
Changes from all commits
6c4ca6d
1d4fe40
bc5b863
79a9621
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,11 @@ | ||
| package com.loopers.domain.ranking; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| public interface MonthlyRankingRepository { | ||
|
|
||
| List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit); | ||
|
|
||
| long countByBaseDate(LocalDate baseDate); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package com.loopers.domain.ranking; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.ZonedDateTime; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Entity | ||
| @Table(name = "mv_product_rank_monthly") | ||
| public class MvProductRankMonthly { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(name = "product_id", nullable = false) | ||
| private Long productId; | ||
|
|
||
| @Column(name = "`rank`", nullable = false) | ||
| private int rank; | ||
|
|
||
| @Column(name = "score", nullable = false) | ||
| private double score; | ||
|
|
||
| @Column(name = "total_like", nullable = false) | ||
| private long totalLike; | ||
|
|
||
| @Column(name = "total_order", nullable = false) | ||
| private long totalOrder; | ||
|
|
||
| @Column(name = "total_view", nullable = false) | ||
| private long totalView; | ||
|
|
||
| @Column(name = "total_sales", nullable = false) | ||
| private long totalSales; | ||
|
|
||
| @Column(name = "base_date", nullable = false) | ||
| private LocalDate baseDate; | ||
|
|
||
| @Column(name = "created_at", nullable = false, updatable = false) | ||
| private ZonedDateTime createdAt; | ||
|
|
||
| private MvProductRankMonthly(Long productId, int rank, double score, | ||
| long totalLike, long totalOrder, long totalView, | ||
| long totalSales, LocalDate baseDate) { | ||
| this.productId = productId; | ||
| this.rank = rank; | ||
| this.score = score; | ||
| this.totalLike = totalLike; | ||
| this.totalOrder = totalOrder; | ||
| this.totalView = totalView; | ||
| this.totalSales = totalSales; | ||
| this.baseDate = baseDate; | ||
| } | ||
|
|
||
| public static MvProductRankMonthly of(Long productId, int rank, double score, | ||
| long totalLike, long totalOrder, long totalView, | ||
| long totalSales, LocalDate baseDate) { | ||
| return new MvProductRankMonthly(productId, rank, score, totalLike, totalOrder, totalView, totalSales, baseDate); | ||
| } | ||
|
|
||
| @PrePersist | ||
| private void prePersist() { | ||
| this.createdAt = ZonedDateTime.now(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package com.loopers.domain.ranking; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.ZonedDateTime; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Entity | ||
| @Table(name = "mv_product_rank_weekly") | ||
| public class MvProductRankWeekly { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @Column(name = "product_id", nullable = false) | ||
| private Long productId; | ||
|
|
||
| @Column(name = "`rank`", nullable = false) | ||
| private int rank; | ||
|
|
||
| @Column(name = "score", nullable = false) | ||
| private double score; | ||
|
|
||
| @Column(name = "total_like", nullable = false) | ||
| private long totalLike; | ||
|
|
||
| @Column(name = "total_order", nullable = false) | ||
| private long totalOrder; | ||
|
|
||
| @Column(name = "total_view", nullable = false) | ||
| private long totalView; | ||
|
|
||
| @Column(name = "total_sales", nullable = false) | ||
| private long totalSales; | ||
|
|
||
| @Column(name = "base_date", nullable = false) | ||
| private LocalDate baseDate; | ||
|
|
||
| @Column(name = "created_at", nullable = false, updatable = false) | ||
| private ZonedDateTime createdAt; | ||
|
|
||
| private MvProductRankWeekly(Long productId, int rank, double score, | ||
| long totalLike, long totalOrder, long totalView, | ||
| long totalSales, LocalDate baseDate) { | ||
| this.productId = productId; | ||
| this.rank = rank; | ||
| this.score = score; | ||
| this.totalLike = totalLike; | ||
| this.totalOrder = totalOrder; | ||
| this.totalView = totalView; | ||
| this.totalSales = totalSales; | ||
| this.baseDate = baseDate; | ||
| } | ||
|
|
||
| public static MvProductRankWeekly of(Long productId, int rank, double score, | ||
| long totalLike, long totalOrder, long totalView, | ||
| long totalSales, LocalDate baseDate) { | ||
| return new MvProductRankWeekly(productId, rank, score, totalLike, totalOrder, totalView, totalSales, baseDate); | ||
| } | ||
|
|
||
| @PrePersist | ||
| private void prePersist() { | ||
| this.createdAt = ZonedDateTime.now(); | ||
| } | ||
|
Comment on lines
+45
to
+70
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.
또한 동일한 타임스탬프 규약이 추가 테스트: 엔티티 저장 후 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.loopers.domain.ranking; | ||
|
|
||
| public enum RankingPeriod { | ||
| DAILY, | ||
| WEEKLY, | ||
| MONTHLY | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.loopers.domain.ranking; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| public interface WeeklyRankingRepository { | ||
|
|
||
| List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit); | ||
|
|
||
| long countByBaseDate(LocalDate baseDate); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||
| package com.loopers.infrastructure.ranking; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import com.loopers.domain.ranking.MonthlyRankingRepository; | ||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.data.domain.PageRequest; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Repository; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||
| @Repository | ||||||||||||||||||||||||||||||||||||
| public class JpaMonthlyRankingRepository implements MonthlyRankingRepository { | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private final MonthlyRankingJpaRepository jpaRepository; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||
| public List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit) { | ||||||||||||||||||||||||||||||||||||
| int pageNumber = (int) (offset / limit); | ||||||||||||||||||||||||||||||||||||
| PageRequest pageRequest = PageRequest.of(pageNumber, (int) limit); | ||||||||||||||||||||||||||||||||||||
| return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+22
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
fd JpaWeeklyRankingRepository.java | xargs -I{} sed -n '1,80p' {}Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 1007 🏁 Script executed: rg "offset\s*/\s*limit|PageRequest\.of" --type java -B 2 -A 2Repository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 14089 🏁 Script executed: fd "Repository" --extension java | xargs grep -l "offset.*limit\|limit.*offset" 2>/dev/nullRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 943 🏁 Script executed: cat apps/commerce-api/src/test/java/com/loopers/infrastructure/ranking/JpaMonthlyRankingRepositoryTest.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4146 🏁 Script executed: cat apps/commerce-api/src/test/java/com/loopers/infrastructure/ranking/JpaWeeklyRankingRepositoryTest.javaRepository: Loopers-dev-lab/loop-pack-be-l2-vol3-java Length of output: 4127
수정: ♻️ 제안 수정- `@Override`
- public List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit) {
- int pageNumber = (int) (offset / limit);
- PageRequest pageRequest = PageRequest.of(pageNumber, (int) limit);
- return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest);
- }
+ `@Override`
+ public List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit) {
+ if (limit <= 0) {
+ return List.of();
+ }
+ if (offset % limit != 0) {
+ throw new IllegalArgumentException(
+ "offset(" + offset + ") must be a multiple of limit(" + limit + ")");
+ }
+ int pageNumber = Math.toIntExact(offset / limit);
+ PageRequest pageRequest = PageRequest.of(pageNumber, Math.toIntExact(limit));
+ return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||
| public long countByBaseDate(LocalDate baseDate) { | ||||||||||||||||||||||||||||||||||||
| return jpaRepository.countByBaseDate(baseDate); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||
| package com.loopers.infrastructure.ranking; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import com.loopers.domain.ranking.WeeklyRankingRepository; | ||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||
| import org.springframework.data.domain.PageRequest; | ||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Repository; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||
| @Repository | ||||||||||||||||||||||||||||||||||
| public class JpaWeeklyRankingRepository implements WeeklyRankingRepository { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| private final WeeklyRankingJpaRepository jpaRepository; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||
| public List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit) { | ||||||||||||||||||||||||||||||||||
| int pageNumber = (int) (offset / limit); | ||||||||||||||||||||||||||||||||||
| PageRequest pageRequest = PageRequest.of(pageNumber, (int) limit); | ||||||||||||||||||||||||||||||||||
| return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+22
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.
🛠 제안 수정안 `@Override`
public List<Long> findProductIdsByBaseDate(LocalDate baseDate, long offset, long limit) {
- int pageNumber = (int) (offset / limit);
- PageRequest pageRequest = PageRequest.of(pageNumber, (int) limit);
- return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest);
+ if (limit <= 0) {
+ return List.of();
+ }
+ if (offset % limit != 0) {
+ throw new IllegalArgumentException("offset must be a multiple of limit: offset=" + offset + ", limit=" + limit);
+ }
+ int pageNumber = Math.toIntExact(offset / limit);
+ PageRequest pageRequest = PageRequest.of(pageNumber, Math.toIntExact(limit));
+ return jpaRepository.findProductIdsByBaseDate(baseDate, pageRequest);
}추가 테스트: 참고로 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||
| public long countByBaseDate(LocalDate baseDate) { | ||||||||||||||||||||||||||||||||||
| return jpaRepository.countByBaseDate(baseDate); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.loopers.infrastructure.ranking; | ||
|
|
||
| import com.loopers.domain.ranking.MvProductRankMonthly; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| public interface MonthlyRankingJpaRepository extends JpaRepository<MvProductRankMonthly, Long> { | ||
|
|
||
| @Query("SELECT m.productId FROM MvProductRankMonthly m WHERE m.baseDate = :baseDate ORDER BY m.rank ASC") | ||
| List<Long> findProductIdsByBaseDate(@Param("baseDate") LocalDate baseDate, Pageable pageable); | ||
|
|
||
| long countByBaseDate(LocalDate baseDate); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.loopers.infrastructure.ranking; | ||
|
|
||
| import com.loopers.domain.ranking.MvProductRankWeekly; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| public interface WeeklyRankingJpaRepository extends JpaRepository<MvProductRankWeekly, Long> { | ||
|
|
||
| @Query("SELECT w.productId FROM MvProductRankWeekly w WHERE w.baseDate = :baseDate ORDER BY w.rank ASC") | ||
| List<Long> findProductIdsByBaseDate(@Param("baseDate") LocalDate baseDate, Pageable pageable); | ||
|
|
||
| long countByBaseDate(LocalDate baseDate); | ||
| } |
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/date인자의 null 방어를 명시해야 한다.period가 null이면switch (period)에서NullPointerException이 발생하고 ApiControllerAdvice 경로가 아닌 일반 500으로 응답되어 오류 포맷이 어긋난다. Controller에서 기본값DAILY를 보장한다고 하지만, 내부 호출자(스케줄러·테스트 등)를 고려해 퍼사드 진입부에서Objects.requireNonNull또는CoreException으로 명시적으로 처리하는 편이 안전하다.date역시 null 시TemporalAdjusters에서 NPE가 발생한다.추가 테스트:
getPage(null, null, 1, 20)호출 시 기대한 예외 타입이 던져지는지 단위 테스트를 보강한다.🛡️ 수정안
`@Transactional`(readOnly = true) public PageResponse<RankingInfo> getPage(LocalDate date, RankingPeriod period, int page, int size) { + Objects.requireNonNull(date, "date"); + Objects.requireNonNull(period, "period"); long offset = (long) (page - 1) * size;🤖 Prompt for AI Agents