Skip to content

Commit bbeb399

Browse files
authored
refactor: 알라딘 외부 검색 API 호출 시 totalResult를 200개로 제한, lastPage 컬럼 값 추가 (#89)
* [BOOK-249] fix: apis,infra - 200개 요청 제한 및 lastPage 컬럼 값 추가(#88) * [BOOK-249] refactor: apis,infra 코드리뷰 반영 (#88)
1 parent 574ca24 commit bbeb399

File tree

7 files changed

+79
-20
lines changed

7 files changed

+79
-20
lines changed

apis/src/main/kotlin/org/yapp/apis/book/dto/response/BookSearchResponse.kt

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@ data class BookSearchResponse private constructor(
1919
@field:Schema(description = "검색 결과 제목", example = "데미안")
2020
val title: String?,
2121

22-
@field:Schema(
23-
description = "검색 결과 링크",
24-
example = "http://www.aladin.co.kr/shop/wsarch.aspx?SearchTarget=Book&query=%EC%95%84%EB%B0%94%EC%9D%98+%ED%95%B4%EB%B0%A9%EC%9D%BC%EC%A7%80&partner=openAPI"
25-
)
26-
val link: String?,
27-
2822
@field:Schema(description = "출간일", example = "2025-07-30")
2923
val pubDate: String?,
3024

@@ -46,6 +40,9 @@ data class BookSearchResponse private constructor(
4640
@field:Schema(description = "검색 카테고리 이름", example = "소설/시/희곡")
4741
val searchCategoryName: String?,
4842

43+
@field:Schema(description = "마지막 페이지 여부", example = "false")
44+
val lastPage: Boolean,
45+
4946
@field:Schema(description = "검색된 책 목록")
5047
val books: List<BookSummary>
5148
) {
@@ -54,26 +51,27 @@ data class BookSearchResponse private constructor(
5451
}
5552

5653
companion object {
57-
fun from(response: AladinSearchResponse): BookSearchResponse {
54+
fun of(response: AladinSearchResponse, isLastPage: Boolean): BookSearchResponse { // Added isLastPage parameter
5855
return BookSearchResponse(
5956
version = response.version,
6057
title = response.title,
61-
link = response.link,
6258
pubDate = response.pubDate,
6359
totalResults = response.totalResults,
6460
startIndex = response.startIndex,
6561
itemsPerPage = response.itemsPerPage,
6662
query = response.query,
6763
searchCategoryId = response.searchCategoryId,
6864
searchCategoryName = response.searchCategoryName,
65+
lastPage = isLastPage,
6966
books = response.item.map {
7067
BookSummary.of(
7168
isbn = it.isbn,
7269
isbn13 = it.isbn13,
7370
title = it.title,
7471
author = AuthorExtractor.extractAuthors(it.author),
7572
publisher = it.publisher,
76-
coverImageUrl = it.cover
73+
coverImageUrl = it.cover,
74+
link = it.link
7775
)
7876
}
7977
)
@@ -101,6 +99,12 @@ data class BookSearchResponse private constructor(
10199
)
102100
val coverImageUrl: String,
103101

102+
@field:Schema(
103+
description = "알라딘 도서 상세 페이지 링크",
104+
example = "http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=3680175"
105+
)
106+
val link: String, // Added link field
107+
104108
@field:Schema(description = "사용자의 책 상태", example = "BEFORE_REGISTRATION")
105109
val userBookStatus: BookStatus
106110
) {
@@ -115,7 +119,8 @@ data class BookSearchResponse private constructor(
115119
title: String?,
116120
author: String?,
117121
publisher: String?,
118-
coverImageUrl: String
122+
coverImageUrl: String,
123+
link: String // Added link
119124
): BookSummary {
120125
require(!title.isNullOrBlank()) { "Title is required" }
121126

@@ -126,6 +131,7 @@ data class BookSearchResponse private constructor(
126131
author = author,
127132
publisher = publisher?.let { BookDataValidator.removeParenthesesFromPublisher(it) },
128133
coverImageUrl = coverImageUrl,
134+
link = link,
129135
userBookStatus = BookStatus.BEFORE_REGISTRATION
130136
)
131137
}

apis/src/main/kotlin/org/yapp/apis/book/exception/BookErrorCode.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ enum class BookErrorCode(
99
private val message: String
1010
) : BaseErrorCode {
1111

12-
/* 500 INTERNAL_SERVER_ERROR */
13-
ALADIN_API_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "BOOK_500_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
14-
ALADIN_API_LOOKUP_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "BOOK_500_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다.");
12+
/* 403 FORBIDDEN */
13+
ALADIN_API_SEARCH_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_01", "알라딘 도서 검색 API 호출에 실패했습니다."),
14+
ALADIN_API_LOOKUP_FAILED(HttpStatus.FORBIDDEN, "BOOK_403_02", "알라딘 도서 상세 조회 API 호출에 실패했습니다."),
15+
ALADIN_API_RESULT_LIMIT(HttpStatus.FORBIDDEN, "BOOK_403_03", "검색 결과가 200개를 초과하여 모든 결과를 조회했습니다.");
1516

1617
override fun getHttpStatus(): HttpStatus = httpStatus
1718
override fun getCode(): String = code

apis/src/main/kotlin/org/yapp/apis/book/service/AladinBookQueryService.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class AladinBookQueryService(
2222
private val aladinApi: AladinApi
2323
) : BookQueryService {
2424
private val log = KotlinLogging.logger {}
25+
private val MAX_ALADIN_RESULTS = 200 // Added constant
2526

2627
override fun searchBooks(request: BookSearchRequest): BookSearchResponse {
2728
log.info { "Service - Converting BookSearchRequest to AladinBookSearchRequest and calling Aladin API for book search." }
@@ -50,23 +51,33 @@ class AladinBookQueryService(
5051
validIsbn13?.let { item.copy(isbn13 = it) }
5152
}
5253

54+
val cappedTotalResults = minOf(response.totalResults ?: 0, MAX_ALADIN_RESULTS)
55+
val currentFetchedCount = (request.start ?: 1) * (response.itemsPerPage ?: 0)
56+
57+
if (currentFetchedCount > MAX_ALADIN_RESULTS) {
58+
throw BookException(
59+
BookErrorCode.ALADIN_API_RESULT_LIMIT
60+
)
61+
}
62+
63+
val isLastPage = currentFetchedCount >= cappedTotalResults
64+
5365
val filteredResponse = AladinSearchResponse(
5466
version = response.version,
5567
title = response.title,
5668
link = response.link,
5769
pubDate = response.pubDate,
58-
totalResults = transformedItems.size,
70+
totalResults = response.totalResults,
5971
startIndex = response.startIndex,
6072
itemsPerPage = response.itemsPerPage,
6173
query = response.query,
6274
searchCategoryId = response.searchCategoryId,
6375
searchCategoryName = response.searchCategoryName,
6476
item = transformedItems
6577
)
66-
6778
log.info { "After filtering - Full Response: $filteredResponse" }
6879

69-
return BookSearchResponse.from(filteredResponse)
80+
return BookSearchResponse.of(filteredResponse, isLastPage) // Passed isLastPage
7081
}
7182

7283
override fun getBookDetail(request: BookDetailRequest): BookDetailResponse {

apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordController.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping
1515
import org.springframework.web.bind.annotation.RequestParam
1616
import org.springframework.web.bind.annotation.RestController
1717
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
18+
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
1819
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
1920
import org.yapp.apis.readingrecord.usecase.ReadingRecordUseCase
2021
import org.yapp.domain.readingrecord.ReadingRecordSortType
@@ -61,7 +62,7 @@ class ReadingRecordController(
6162
@RequestParam(required = false) sort: ReadingRecordSortType?,
6263
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
6364
pageable: Pageable
64-
): ResponseEntity<Page<ReadingRecordResponse>> {
65+
): ResponseEntity<ReadingRecordPageResponse> {
6566
val response = readingRecordUseCase.getReadingRecordsByUserBookId(
6667
userId = userId,
6768
userBookId = userBookId,

apis/src/main/kotlin/org/yapp/apis/readingrecord/controller/ReadingRecordControllerApi.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.springframework.http.ResponseEntity
1616
import org.springframework.security.core.annotation.AuthenticationPrincipal
1717
import org.springframework.web.bind.annotation.*
1818
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
19+
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse
1920
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
2021
import org.yapp.apis.readingrecord.dto.response.SeedStatsResponse
2122
import org.yapp.domain.readingrecord.ReadingRecordSortType
@@ -105,7 +106,7 @@ interface ReadingRecordControllerApi {
105106
@RequestParam(required = false) @Parameter(description = "정렬 방식 (PAGE_NUMBER_ASC, PAGE_NUMBER_DESC, CREATED_DATE_ASC, CREATED_DATE_DESC)") sort: ReadingRecordSortType?,
106107
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC)
107108
@Parameter(description = "페이지네이션 정보 (페이지 번호, 페이지 크기, 정렬 방식)") pageable: Pageable
108-
): ResponseEntity<Page<ReadingRecordResponse>>
109+
): ResponseEntity<ReadingRecordPageResponse>
109110

110111
@Operation(
111112
summary = "씨앗 통계 조회",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.yapp.apis.readingrecord.dto.response
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import org.springframework.data.domain.Page
5+
6+
@Schema(
7+
name = "ReadingRecordPageResponse",
8+
description = "독서 기록 페이징 조회 응답"
9+
)
10+
data class ReadingRecordPageResponse private constructor(
11+
@field:Schema(description = "마지막 페이지 여부", example = "false")
12+
val lastPage: Boolean,
13+
14+
@field:Schema(description = "총 결과 개수", example = "42")
15+
val totalResults: Int,
16+
17+
@field:Schema(description = "현재 페이지 번호 (0부터 시작)", example = "0")
18+
val startIndex: Int,
19+
20+
@field:Schema(description = "한 페이지당 아이템 개수", example = "10")
21+
val itemsPerPage: Int,
22+
23+
@field:Schema(description = "독서 기록 목록")
24+
val readingRecords: List<ReadingRecordResponse>
25+
) {
26+
companion object {
27+
fun from(page: Page<ReadingRecordResponse>): ReadingRecordPageResponse {
28+
return ReadingRecordPageResponse(
29+
lastPage = page.isLast,
30+
totalResults = page.totalElements.toInt(),
31+
startIndex = page.number,
32+
itemsPerPage = page.size,
33+
readingRecords = page.content
34+
)
35+
}
36+
}
37+
}

apis/src/main/kotlin/org/yapp/apis/readingrecord/usecase/ReadingRecordUseCase.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.springframework.data.domain.Pageable
55
import org.springframework.transaction.annotation.Transactional
66
import org.yapp.apis.book.service.UserBookService
77
import org.yapp.apis.readingrecord.dto.request.CreateReadingRecordRequest
8+
import org.yapp.apis.readingrecord.dto.response.ReadingRecordPageResponse // Added import
89
import org.yapp.apis.readingrecord.dto.response.ReadingRecordResponse
910
import org.yapp.apis.readingrecord.dto.response.SeedStatsResponse
1011
import org.yapp.apis.readingrecord.service.ReadingRecordService
@@ -55,15 +56,16 @@ class ReadingRecordUseCase(
5556
userBookId: UUID,
5657
sort: ReadingRecordSortType?,
5758
pageable: Pageable
58-
): Page<ReadingRecordResponse> {
59+
): ReadingRecordPageResponse {
5960
userService.validateUserExists(userId)
6061
userBookService.validateUserBookExists(userBookId, userId)
6162

62-
return readingRecordService.getReadingRecordsByDynamicCondition(
63+
val page = readingRecordService.getReadingRecordsByDynamicCondition( // Stored in a variable
6364
userBookId = userBookId,
6465
sort = sort,
6566
pageable = pageable
6667
)
68+
return ReadingRecordPageResponse.from(page) // Converted to new DTO
6769
}
6870

6971
fun getSeedStats(

0 commit comments

Comments
 (0)