Skip to content

Commit ae41a7a

Browse files
committed
feat: 상품 랭킹 API 구현 및 DTO 클래스 추가
1 parent 1855152 commit ae41a7a

3 files changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.loopers.interfaces.api.ranking;
2+
3+
import com.loopers.interfaces.api.ApiResponse;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.Parameter;
6+
import io.swagger.v3.oas.annotations.media.Content;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
13+
@Tag(name = "Ranking API", description = "상품 랭킹 API")
14+
public interface RankingV1ApiSpec {
15+
16+
@Operation(summary = "랭킹 페이지 조회", description = "일간 상품 랭킹을 페이지로 조회합니다.")
17+
@ApiResponses(value = {
18+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
19+
responseCode = "200",
20+
description = "조회 성공",
21+
content = @Content(schema = @Schema(implementation = RankingV1Dto.RankingPageResponse.class))
22+
)
23+
})
24+
@GetMapping
25+
ApiResponse<RankingV1Dto.RankingPageResponse> getRankings(
26+
@Parameter(description = "날짜 (yyyyMMdd 형식, 기본값: 오늘)")
27+
@RequestParam(required = false) String date,
28+
29+
@Parameter(description = "페이지 번호 (0부터 시작)")
30+
@RequestParam(defaultValue = "0") int page,
31+
32+
@Parameter(description = "페이지 크기")
33+
@RequestParam(defaultValue = "20") int size
34+
);
35+
36+
@Operation(summary = "Top-N 랭킹 조회", description = "오늘의 Top-N 상품을 조회합니다.")
37+
@ApiResponses(value = {
38+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
39+
responseCode = "200",
40+
description = "조회 성공",
41+
content = @Content(schema = @Schema(implementation = RankingV1Dto.TopNResponse.class))
42+
)
43+
})
44+
@GetMapping("/top")
45+
ApiResponse<RankingV1Dto.TopNResponse> getTopN(
46+
@Parameter(description = "날짜 (yyyyMMdd 형식, 기본값: 오늘)")
47+
@RequestParam(required = false) String date,
48+
49+
@Parameter(description = "조회할 상위 N개")
50+
@RequestParam(defaultValue = "10") int n
51+
);
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.loopers.interfaces.api.ranking;
2+
3+
import com.loopers.application.ranking.RankingCommand;
4+
import com.loopers.application.ranking.RankingFacade;
5+
import com.loopers.application.ranking.RankingPageInfo;
6+
import com.loopers.domain.ranking.RankingInfo;
7+
import com.loopers.interfaces.api.ApiResponse;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
import java.time.LocalDate;
13+
import java.time.format.DateTimeFormatter;
14+
import java.util.List;
15+
16+
@RestController
17+
@RequestMapping("/api/v1/rankings")
18+
@RequiredArgsConstructor
19+
public class RankingV1Controller implements RankingV1ApiSpec {
20+
21+
private final RankingFacade rankingFacade;
22+
23+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
24+
25+
@Override
26+
public ApiResponse<RankingV1Dto.RankingPageResponse> getRankings(String date, int page, int size) {
27+
RankingCommand command = RankingCommand.of(date, page, size);
28+
RankingPageInfo pageInfo = rankingFacade.getRankingPage(command);
29+
return ApiResponse.success(RankingV1Dto.RankingPageResponse.from(pageInfo));
30+
}
31+
32+
@Override
33+
public ApiResponse<RankingV1Dto.TopNResponse> getTopN(String date, int n) {
34+
LocalDate targetDate = parseDate(date);
35+
List<RankingInfo> rankings = rankingFacade.getTopN(targetDate, n);
36+
return ApiResponse.success(RankingV1Dto.TopNResponse.of(rankings, targetDate));
37+
}
38+
39+
private LocalDate parseDate(String date) {
40+
if (date == null || date.isBlank()) {
41+
return LocalDate.now();
42+
}
43+
return LocalDate.parse(date, DATE_FORMATTER);
44+
}
45+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.loopers.interfaces.api.ranking;
2+
3+
import com.loopers.application.ranking.RankingPageInfo;
4+
import com.loopers.domain.ranking.RankingInfo;
5+
6+
import java.time.LocalDate;
7+
import java.time.format.DateTimeFormatter;
8+
import java.util.List;
9+
10+
public class RankingV1Dto {
11+
12+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
13+
14+
public record RankingPageResponse(
15+
List<RankingItemResponse> rankings,
16+
String date,
17+
int page,
18+
int size,
19+
Long totalCount,
20+
int totalPages
21+
) {
22+
public static RankingPageResponse from(RankingPageInfo info) {
23+
List<RankingItemResponse> items = info.rankings().stream()
24+
.map(RankingItemResponse::from)
25+
.toList();
26+
27+
return new RankingPageResponse(
28+
items,
29+
info.date().format(DATE_FORMATTER),
30+
info.page(),
31+
info.size(),
32+
info.totalCount(),
33+
info.totalPages()
34+
);
35+
}
36+
}
37+
38+
public record RankingItemResponse(
39+
Long productId,
40+
String productName,
41+
Long price,
42+
String brandName,
43+
Long rank,
44+
Double score
45+
) {
46+
public static RankingItemResponse from(RankingInfo info) {
47+
return new RankingItemResponse(
48+
info.productId(),
49+
info.productName(),
50+
info.price(),
51+
info.brandName(),
52+
info.rank(),
53+
info.score()
54+
);
55+
}
56+
}
57+
58+
public record TopNResponse(
59+
List<RankingItemResponse> rankings,
60+
String date,
61+
int size
62+
) {
63+
public static TopNResponse of(List<RankingInfo> rankings, LocalDate date) {
64+
List<RankingItemResponse> items = rankings.stream()
65+
.map(RankingItemResponse::from)
66+
.toList();
67+
68+
return new TopNResponse(items, date.format(DATE_FORMATTER), items.size());
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)