Skip to content

Commit 2dffa78

Browse files
committed
feat(ranking): 랭킹 API 및 관련 엔티티 추가
- 일간, 주간, 월간 랭킹 조회 API 명세 및 구현 - 랭킹 데이터 처리 로직 추가 - 기존 코드 리팩토링 및 불필요한 메서드 제거
1 parent 139cbc3 commit 2dffa78

9 files changed

Lines changed: 543 additions & 764 deletions

File tree

apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1ApiSpec.java

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
import io.swagger.v3.oas.annotations.responses.ApiResponses;
66
import io.swagger.v3.oas.annotations.tags.Tag;
77

8-
import java.time.LocalDate;
9-
108
import org.springframework.data.domain.Pageable;
119
import org.springframework.data.web.PageableDefault;
1210
import org.springframework.web.bind.annotation.PathVariable;
1311
import org.springframework.web.bind.annotation.RequestHeader;
1412
import org.springframework.web.bind.annotation.RequestParam;
1513

16-
import com.loopers.domain.ranking.RankingPeriod;
1714
import com.loopers.interfaces.api.ApiResponse;
1815
import com.loopers.interfaces.api.common.PageResponse;
1916

@@ -34,41 +31,6 @@ ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getProducts(
3431
@RequestParam(required = false) String productName
3532
);
3633

37-
38-
@Operation(
39-
summary = "랭킹 상품 목록 조회",
40-
description = "일자 기준 랭킹 상품 목록을 페이징하여 조회합니다. date 파라미터가 없으면 오늘 날짜 기준으로 조회합니다."
41-
)
42-
@ApiResponses({
43-
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
44-
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청")
45-
})
46-
ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProducts(
47-
@PageableDefault(size = 20) Pageable pageable,
48-
@Parameter(description = "조회 날짜 (yyyy-MM-dd 형식, 선택)", example = "2025-12-23")
49-
@RequestParam(required = false) LocalDate date
50-
);
51-
52-
@Operation(
53-
summary = "기간별 랭킹 상품 목록 조회",
54-
description = "기간별(일간/주간/월간) 랭킹 상품 목록을 페이징하여 조회합니다."
55-
)
56-
@ApiResponses({
57-
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
58-
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청")
59-
})
60-
ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProductsByPeriod(
61-
@Parameter(description = "랭킹 기간", example = "DAILY", required = true)
62-
@RequestParam RankingPeriod period,
63-
@PageableDefault(size = 20) Pageable pageable,
64-
@Parameter(description = "조회 날짜 (DAILY용, yyyy-MM-dd 형식)", example = "2025-12-23")
65-
@RequestParam(required = false) LocalDate date,
66-
@Parameter(description = "조회 주차 (WEEKLY용, yyyy-Www 형식)", example = "2024-W52")
67-
@RequestParam(required = false) String yearWeek,
68-
@Parameter(description = "조회 월 (MONTHLY용, yyyy-MM 형식)", example = "2024-12")
69-
@RequestParam(required = false) String yearMonth
70-
);
71-
7234
@Operation(
7335
summary = "상품 상세 조회",
7436
description = "상품 ID로 상품 상세 정보를 조회합니다. 로그인한 사용자의 경우 좋아요 여부도 함께 조회됩니다."

apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductV1Controller.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.loopers.interfaces.api.product;
22

3-
import java.time.LocalDate;
4-
53
import org.springframework.data.domain.Page;
64
import org.springframework.data.domain.Pageable;
75
import org.springframework.data.web.PageableDefault;
@@ -11,7 +9,6 @@
119
import com.loopers.application.product.ProductFacade;
1210
import com.loopers.application.product.ProductInfo;
1311
import com.loopers.domain.product.dto.ProductSearchFilter;
14-
import com.loopers.domain.ranking.RankingPeriod;
1512
import com.loopers.interfaces.api.ApiResponse;
1613
import com.loopers.interfaces.api.common.PageResponse;
1714
import com.loopers.support.Uris;
@@ -37,32 +34,6 @@ public ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getProducts(
3734
return ApiResponse.success(PageResponse.from(responsePage));
3835
}
3936

40-
@GetMapping(Uris.Ranking.GET_RANKING)
41-
@Override
42-
public ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProducts(
43-
@PageableDefault(size = 20) Pageable pageable,
44-
@RequestParam(required = false) LocalDate date
45-
) {
46-
Page<ProductInfo> products = productFacade.getRankingProducts(pageable, date);
47-
Page<ProductV1Dtos.ProductListResponse> responsePage = products.map(ProductV1Dtos.ProductListResponse::from);
48-
return ApiResponse.success(PageResponse.from(responsePage));
49-
}
50-
51-
@GetMapping(Uris.Ranking.GET_RANKING_BY_PERIOD)
52-
@Override
53-
public ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProductsByPeriod(
54-
@RequestParam RankingPeriod period,
55-
@PageableDefault(size = 20) Pageable pageable,
56-
@RequestParam(required = false) LocalDate date,
57-
@RequestParam(required = false) String yearWeek,
58-
@RequestParam(required = false) String yearMonth
59-
) {
60-
Page<ProductInfo> products = productFacade.getRankingProductsByPeriod(
61-
period, pageable, date, yearWeek, yearMonth);
62-
Page<ProductV1Dtos.ProductListResponse> responsePage = products.map(ProductV1Dtos.ProductListResponse::from);
63-
return ApiResponse.success(PageResponse.from(responsePage));
64-
}
65-
6637
@GetMapping(Uris.Product.GET_DETAIL)
6738
@Override
6839
public ApiResponse<ProductV1Dtos.ProductDetailResponse> getProductDetail(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.loopers.interfaces.api.ranking;
2+
3+
import com.loopers.domain.ranking.RankingPeriod;
4+
import com.loopers.interfaces.api.ApiResponse;
5+
import com.loopers.interfaces.api.common.PageResponse;
6+
import com.loopers.interfaces.api.product.ProductV1Dtos;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.Parameter;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import org.springframework.data.domain.Pageable;
11+
12+
import java.time.LocalDate;
13+
14+
/**
15+
* 랭킹 API 명세
16+
* - 일간/주간/월간 랭킹 조회 API
17+
*/
18+
@Tag(name = "Ranking", description = "상품 랭킹 API")
19+
public interface RankingV1ApiSpec {
20+
21+
@Operation(
22+
summary = "일간 랭킹 조회",
23+
description = "특정 날짜의 상품 랭킹을 조회합니다. 날짜 미지정 시 오늘 기준이며, 데이터가 없으면 어제 랭킹으로 fallback됩니다."
24+
)
25+
ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProducts(
26+
Pageable pageable,
27+
@Parameter(description = "조회할 날짜 (YYYY-MM-DD)", example = "2024-12-26")
28+
LocalDate date
29+
);
30+
31+
@Operation(
32+
summary = "기간별 랭킹 조회",
33+
description = "주간/월간 랭킹을 조회합니다. Java 8 Date API를 활용하여 파라미터를 자동 처리합니다."
34+
)
35+
ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProductsByPeriod(
36+
@Parameter(description = "랭킹 기간", example = "WEEKLY")
37+
RankingPeriod period,
38+
Pageable pageable,
39+
@Parameter(description = "기준 날짜 (YYYY-MM-DD)", example = "2024-12-26")
40+
LocalDate date,
41+
@Parameter(description = "연도-주차 (YYYY-WNN)", example = "2024-W52")
42+
String yearWeek,
43+
@Parameter(description = "연도-월 (YYYY-MM)", example = "2024-12")
44+
String yearMonth
45+
);
46+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.loopers.interfaces.api.ranking;
2+
3+
import com.loopers.application.product.ProductFacade;
4+
import com.loopers.application.product.ProductInfo;
5+
import com.loopers.domain.ranking.RankingPeriod;
6+
import com.loopers.interfaces.api.ApiResponse;
7+
import com.loopers.interfaces.api.common.PageResponse;
8+
import com.loopers.interfaces.api.product.ProductV1Dtos;
9+
import com.loopers.support.Uris;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.data.domain.Page;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.web.PageableDefault;
14+
import org.springframework.web.bind.annotation.*;
15+
16+
import java.time.LocalDate;
17+
import java.time.YearMonth;
18+
import java.time.format.DateTimeFormatter;
19+
import java.time.temporal.WeekFields;
20+
import java.util.Locale;
21+
22+
/**
23+
* 랭킹 전용 Controller
24+
* - 일간/주간/월간 랭킹 API 제공
25+
* - Java 8 Date API 활용
26+
*/
27+
@RestController
28+
@RequiredArgsConstructor
29+
public class RankingV1Controller implements RankingV1ApiSpec {
30+
31+
private final ProductFacade productFacade;
32+
33+
@GetMapping(Uris.Ranking.GET_RANKING)
34+
@Override
35+
public ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProducts(
36+
@PageableDefault(size = 20) Pageable pageable,
37+
@RequestParam(required = false) LocalDate date
38+
) {
39+
Page<ProductInfo> products = productFacade.getRankingProducts(pageable, date);
40+
Page<ProductV1Dtos.ProductListResponse> responsePage = products.map(ProductV1Dtos.ProductListResponse::from);
41+
return ApiResponse.success(PageResponse.from(responsePage));
42+
}
43+
44+
@GetMapping(Uris.Ranking.GET_RANKING_BY_PERIOD)
45+
@Override
46+
public ApiResponse<PageResponse<ProductV1Dtos.ProductListResponse>> getRankingProductsByPeriod(
47+
@RequestParam RankingPeriod period,
48+
@PageableDefault(size = 20) Pageable pageable,
49+
@RequestParam(required = false) LocalDate date,
50+
@RequestParam(required = false) String yearWeek,
51+
@RequestParam(required = false) String yearMonth
52+
) {
53+
// Java 8 Date API를 활용한 파라미터 검증 및 변환
54+
String processedYearWeek = processYearWeekParameter(yearWeek, date);
55+
String processedYearMonth = processYearMonthParameter(yearMonth, date);
56+
57+
Page<ProductInfo> products = productFacade.getRankingProductsByPeriod(
58+
period, pageable, date, processedYearWeek, processedYearMonth);
59+
Page<ProductV1Dtos.ProductListResponse> responsePage = products.map(ProductV1Dtos.ProductListResponse::from);
60+
return ApiResponse.success(PageResponse.from(responsePage));
61+
}
62+
63+
/**
64+
* yearWeek 파라미터 처리
65+
* - 파라미터가 없으면 현재 주차로 설정
66+
* - Java 8 WeekFields 활용
67+
*/
68+
private String processYearWeekParameter(String yearWeek, LocalDate date) {
69+
if (yearWeek != null && !yearWeek.trim().isEmpty()) {
70+
return yearWeek;
71+
}
72+
73+
// date가 있으면 해당 날짜의 주차, 없으면 현재 주차
74+
LocalDate targetDate = date != null ? date : LocalDate.now();
75+
76+
WeekFields weekFields = WeekFields.of(Locale.getDefault());
77+
int year = targetDate.getYear();
78+
int week = targetDate.get(weekFields.weekOfYear());
79+
80+
return String.format("%d-W%02d", year, week);
81+
}
82+
83+
/**
84+
* yearMonth 파라미터 처리
85+
* - 파라미터가 없으면 현재 월로 설정
86+
* - Java 8 YearMonth 활용
87+
*/
88+
private String processYearMonthParameter(String yearMonth, LocalDate date) {
89+
if (yearMonth != null && !yearMonth.trim().isEmpty()) {
90+
return yearMonth;
91+
}
92+
93+
// date가 있으면 해당 날짜의 월, 없으면 현재 월
94+
LocalDate targetDate = date != null ? date : LocalDate.now();
95+
YearMonth ym = YearMonth.from(targetDate);
96+
97+
return ym.format(DateTimeFormatter.ofPattern("yyyy-MM"));
98+
}
99+
}

0 commit comments

Comments
 (0)