From 0d4f66e7ecda24d07629dfa1daf0b57d71e228c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:37:44 +0900 Subject: [PATCH 01/25] =?UTF-8?q?[feat]=20BookApiAdapter=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/adapter/out/api/BookApiAdapter.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java diff --git a/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java b/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java new file mode 100644 index 000000000..d07c91879 --- /dev/null +++ b/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java @@ -0,0 +1,21 @@ +package konkuk.thip.book.adapter.out.api; + +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; +import konkuk.thip.book.application.port.out.SearchBookQueryPort; +import konkuk.thip.util.NaverApiUtil; +import konkuk.thip.util.NaverBookXmlParser; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class BookApiAdapter implements SearchBookQueryPort { + + private final NaverApiUtil naverApiUtil; + + @Override + public NaverBookParseResult findBooksByKeyword(String keyword, int start) { + String xml = naverApiUtil.searchBook(keyword, start); // 네이버 API 호출 + return NaverBookXmlParser.parse(xml); // XML 파싱 + 페이징 정보 포함 + } +} \ No newline at end of file From 23c9591718ba0909f86d2c791f72c5002bc5f2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:37:52 +0900 Subject: [PATCH 02/25] =?UTF-8?q?[feat]=20BookDto=20=EC=9E=91=EC=84=B1=20(?= =?UTF-8?q?#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/book/adapter/in/web/response/BookDto.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java b/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java new file mode 100644 index 000000000..2750096a4 --- /dev/null +++ b/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java @@ -0,0 +1,9 @@ +package konkuk.thip.book.adapter.in.web.response; + +public record BookDto( + String title, + String imageUrl, + String authorName, + String publisher, + String isbn +) {} From a785e947f906e77fd0cf303359591414840cf2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:38:07 +0900 Subject: [PATCH 03/25] =?UTF-8?q?[feat]=20BookQueryController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/adapter/in/web/BookQueryController.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java b/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java index f66ee2015..4192a1cb7 100644 --- a/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java +++ b/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java @@ -1,10 +1,20 @@ package konkuk.thip.book.adapter.in.web; +import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; +import konkuk.thip.book.application.port.in.BookSearchUseCase; +import konkuk.thip.common.dto.BaseResponse; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor public class BookQueryController { -} + private final BookSearchUseCase bookSearchUseCase; + + @GetMapping("/books") + public BaseResponse getBookSearchList(@RequestParam final String keyword, + @RequestParam final int page) { + return BaseResponse.ok(bookSearchUseCase.searchBooks(keyword, page)); + } +} \ No newline at end of file From ae8d7ffe7ac17d77e1c1b310fa25508646e6cc03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:38:21 +0900 Subject: [PATCH 04/25] =?UTF-8?q?[feat]=20BookSearchService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BookSearchService.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/application/service/BookSearchService.java diff --git a/src/main/java/konkuk/thip/book/application/service/BookSearchService.java b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java new file mode 100644 index 000000000..1a65abb80 --- /dev/null +++ b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java @@ -0,0 +1,68 @@ +package konkuk.thip.book.application.service; + +import konkuk.thip.book.adapter.in.web.response.BookDto; +import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; +import konkuk.thip.book.application.port.in.BookSearchUseCase; +import konkuk.thip.book.application.port.out.SearchBookQueryPort; +import konkuk.thip.common.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + +@Service +@RequiredArgsConstructor +public class BookSearchService implements BookSearchUseCase { + + private static final int PAGE_SIZE = 10; + private final SearchBookQueryPort SearchBookQueryPort; + + + @Override + public GetBookSearchListResponse searchBooks(String keyword, int page) { + + if (keyword == null || keyword.isBlank()) { + throw new BusinessException(BOOK_KEYWORD_REQUIRED); + } + + if (page < 1) { + throw new BusinessException(BOOK_PAGE_NUMBER_INVALID); + } + + //유저의 최근검색어 로직 추가 + + int start = (page - 1) * PAGE_SIZE + 1; //검색 시작 위치 + NaverBookParseResult result = SearchBookQueryPort.findBooksByKeyword(keyword, start); + + int totalElements = result.total(); + int totalPages = (totalElements + PAGE_SIZE - 1) / PAGE_SIZE; + if ( totalElements!=0 && page > totalPages) { + throw new BusinessException(BOOK_SEARCH_PAGE_OUT_OF_RANGE); + } + boolean last = (page >= totalPages); + boolean first = (page == 1); + + List bookDtos = result.books().stream() + .map(book -> new BookDto( + book.getTitle(), + book.getImageUrl(), + book.getAuthorName(), + book.getPublisher(), + book.getIsbn() + )) + .toList(); + + return new GetBookSearchListResponse( + bookDtos, + page, + totalElements, + totalPages, + last, + first + ); + } + +} \ No newline at end of file From 30972bb409d5408d9d8591e0c3139ce1cfe04f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:38:48 +0900 Subject: [PATCH 05/25] =?UTF-8?q?[remove]=20=EB=8D=94=EB=AF=B8=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/adapter/in/web/request/DummyRequest.java | 7 ------- .../thip/book/application/port/in/DummyUseCase.java | 5 ----- .../thip/book/application/service/BookService.java | 11 ----------- 3 files changed, 23 deletions(-) delete mode 100644 src/main/java/konkuk/thip/book/adapter/in/web/request/DummyRequest.java delete mode 100644 src/main/java/konkuk/thip/book/application/port/in/DummyUseCase.java delete mode 100644 src/main/java/konkuk/thip/book/application/service/BookService.java diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/request/DummyRequest.java b/src/main/java/konkuk/thip/book/adapter/in/web/request/DummyRequest.java deleted file mode 100644 index 6e72beef9..000000000 --- a/src/main/java/konkuk/thip/book/adapter/in/web/request/DummyRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.book.adapter.in.web.request; - -import lombok.Getter; - -@Getter -public class DummyRequest { -} diff --git a/src/main/java/konkuk/thip/book/application/port/in/DummyUseCase.java b/src/main/java/konkuk/thip/book/application/port/in/DummyUseCase.java deleted file mode 100644 index 4f29a36c7..000000000 --- a/src/main/java/konkuk/thip/book/application/port/in/DummyUseCase.java +++ /dev/null @@ -1,5 +0,0 @@ -package konkuk.thip.book.application.port.in; - -public interface DummyUseCase { - -} diff --git a/src/main/java/konkuk/thip/book/application/service/BookService.java b/src/main/java/konkuk/thip/book/application/service/BookService.java deleted file mode 100644 index d85dca239..000000000 --- a/src/main/java/konkuk/thip/book/application/service/BookService.java +++ /dev/null @@ -1,11 +0,0 @@ -package konkuk.thip.book.application.service; - -import konkuk.thip.book.application.port.in.DummyUseCase; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class BookService implements DummyUseCase { - -} From 30f235d1b61daa05b6ed0dc11c1735f70601d625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:39:01 +0900 Subject: [PATCH 06/25] =?UTF-8?q?[remove]=20BookSearchUseCase=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/book/application/port/in/BookSearchUseCase.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java diff --git a/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java b/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java new file mode 100644 index 000000000..87e020df1 --- /dev/null +++ b/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java @@ -0,0 +1,9 @@ +package konkuk.thip.book.application.port.in; + +import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; + +public interface BookSearchUseCase { + + GetBookSearchListResponse searchBooks(String keyword, int page); + +} From 54df077e0167f99fc60d8490372ee85a3cc24b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:39:24 +0900 Subject: [PATCH 07/25] =?UTF-8?q?[refactor]=20BusinessException=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=A9=94=EC=84=B8=EC=A7=80=20=EB=A6=AC?= =?UTF-8?q?=ED=8E=99=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/exception/BusinessException.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/exception/BusinessException.java b/src/main/java/konkuk/thip/common/exception/BusinessException.java index e87c37d47..15b60ade5 100644 --- a/src/main/java/konkuk/thip/common/exception/BusinessException.java +++ b/src/main/java/konkuk/thip/common/exception/BusinessException.java @@ -8,11 +8,12 @@ public class BusinessException extends RuntimeException { private final ErrorCode errorCode; public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); this.errorCode = errorCode; } public BusinessException(ErrorCode errorCode, Exception e) { - super(e); + super(errorCode.getMessage(), e); this.errorCode = errorCode; } } From 48b213fb212102569d1dfc7c5747dcd7502138f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:39:40 +0900 Subject: [PATCH 08/25] =?UTF-8?q?[feat]=20book=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/exception/code/ErrorCode.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 38fa09194..b538f025d 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -20,10 +20,25 @@ public enum ErrorCode implements ResponseCode { /** * 60000 : alias error */ - ALIAS_NOT_FOUND(HttpStatus.NOT_FOUND, 60001, "존재하지 않는 ALIAS 입니다."); + ALIAS_NOT_FOUND(HttpStatus.NOT_FOUND, 60001, "존재하지 않는 ALIAS 입니다."), + + + /** + * 80000 : book error + */ + BOOK_KEYWORD_ENCODING_FAILED(HttpStatus.BAD_REQUEST, 80000, "검색어 인코딩에 실패했습니다."), + BOOK_NAVER_API_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 80001,"네이버 API 요청에 실패하였습니다."), + BOOK_NAVER_API_PARSING_ERROR(HttpStatus.BAD_REQUEST, 80002,"네이버 API 응답 파싱에 실패하였습니다."), + BOOK_NAVER_API_URL_ERROR(HttpStatus.BAD_REQUEST, 80003,"네이버 API URL이 잘못되었습니다."), + BOOK_NAVER_API_URL_HTTP_CONNECT_FAILED(HttpStatus.BAD_REQUEST, 80004,"네이버 API 요청 중, HTTP 연결에 실패하였습니다."), + BOOK_NAVER_API_RESPONSE_ERROR(HttpStatus.BAD_REQUEST, 80005,"네이버 API 응답에 실패하였습니다."), + BOOK_SEARCH_PAGE_OUT_OF_RANGE(HttpStatus.BAD_REQUEST, 80006,"검색어 페이지가 범위를 벗어났습니다."), + BOOK_KEYWORD_REQUIRED(HttpStatus.BAD_REQUEST, 80007, "검색어는 필수 입력값입니다."), + BOOK_PAGE_NUMBER_INVALID(HttpStatus.BAD_REQUEST, 80008, "페이지 번호는 1 이상의 값이어야 합니다."); + + - ; private final HttpStatus httpStatus; private final int code; From 546260b94439fd0b1bf281cab82929b4575432a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:39:58 +0900 Subject: [PATCH 09/25] =?UTF-8?q?[refactor]=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/dto/ErrorResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/dto/ErrorResponse.java b/src/main/java/konkuk/thip/common/dto/ErrorResponse.java index 40a253b5b..6b983a124 100644 --- a/src/main/java/konkuk/thip/common/dto/ErrorResponse.java +++ b/src/main/java/konkuk/thip/common/dto/ErrorResponse.java @@ -8,7 +8,7 @@ @JsonPropertyOrder({"success", "code", "message"}) public class ErrorResponse { - @JsonProperty("isSuccess:") + @JsonProperty("isSuccess") private final boolean success; private final int code; From 683c964e4a6525e5fcd62a27503303eefa80f677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:40:14 +0900 Subject: [PATCH 10/25] =?UTF-8?q?[feat]=20GetBookSearchListResponse=20dto?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/response/GetBookSearchListResponse.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java b/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java new file mode 100644 index 000000000..96d831d2d --- /dev/null +++ b/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java @@ -0,0 +1,16 @@ +package konkuk.thip.book.adapter.in.web.response; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record GetBookSearchListResponse( + List searchResult, // 책 목록 + int page, // 현재 페이지 (1부터 시작) + long totalElements, // 전체 데이터 개수 + int totalPages, // 전체 페이지 수 + boolean last, // 마지막 페이지 여부 + boolean first // 첫 페이지 여부 +) { +} \ No newline at end of file From 7a08730103ff470868c48c397d89e486e8c1da6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:40:27 +0900 Subject: [PATCH 11/25] =?UTF-8?q?[feat]=20NaverApiUtil=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/util/NaverApiUtil.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/main/java/konkuk/thip/util/NaverApiUtil.java diff --git a/src/main/java/konkuk/thip/util/NaverApiUtil.java b/src/main/java/konkuk/thip/util/NaverApiUtil.java new file mode 100644 index 000000000..b897be658 --- /dev/null +++ b/src/main/java/konkuk/thip/util/NaverApiUtil.java @@ -0,0 +1,107 @@ +package konkuk.thip.util; + +import konkuk.thip.common.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + +@RequiredArgsConstructor +@Component +public class NaverApiUtil { + + @Value("${naver.clientId}") + private String clientId; + @Value("${naver.clientSecret}") + private String clientSecret; + + private final String NAVER_BOOK_SEARCH_URL = "https://openapi.naver.com/v1/search/book.xml?query="; //책 검색 결과 조회 + + + public String searchBook(String keyword, int start){ + String query = keywordToEncoding(keyword); + String url = buildSearchApiUrl(query, start); + + Map requestHeaders = new HashMap<>(); + requestHeaders.put("X-Naver-Client-Id", clientId); + requestHeaders.put("X-Naver-Client-Secret", clientSecret); + + return get(url,requestHeaders); + } + + private String buildSearchApiUrl(String query,Integer start) { + return NAVER_BOOK_SEARCH_URL+query+"&start="+start; + } + + private String keywordToEncoding(String keyword) { + String text = null; + try { + text = URLEncoder.encode(keyword, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new BusinessException(BOOK_KEYWORD_ENCODING_FAILED); + } + return text; + } + + + String get(String apiUrl, Map requestHeaders){ + HttpURLConnection con = connect(apiUrl); + try { + con.setRequestMethod("GET"); + for(Map.Entry header :requestHeaders.entrySet()) { + con.setRequestProperty(header.getKey(), header.getValue()); + } + + int responseCode = con.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출 + return readBody(con.getInputStream()); + } else { // 오류 발생 + return readBody(con.getErrorStream()); + } + } catch (IOException e) { + throw new BusinessException(BOOK_NAVER_API_REQUEST_ERROR); + } finally { + con.disconnect(); + } + } + + + private HttpURLConnection connect(String apiUrl){ + try { + URL url = new URL(apiUrl); + return (HttpURLConnection)url.openConnection(); + } catch (MalformedURLException e) { + throw new BusinessException(BOOK_NAVER_API_URL_ERROR); + } catch (IOException e) { + throw new BusinessException(BOOK_NAVER_API_URL_HTTP_CONNECT_FAILED); + } + } + + + private String readBody(InputStream body){ + InputStreamReader streamReader = new InputStreamReader(body); + + try (BufferedReader lineReader = new BufferedReader(streamReader)) { + StringBuilder responseBody = new StringBuilder(); + + String line; + while ((line = lineReader.readLine()) != null) { + responseBody.append(line); + } + + return responseBody.toString(); + } catch (IOException e) { + throw new BusinessException(BOOK_NAVER_API_RESPONSE_ERROR); + } + } + +} From 5f5372161729df7ffb857afbc90174d8b9081213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:40:46 +0900 Subject: [PATCH 12/25] =?UTF-8?q?[feat]=20NaverBookParseResult=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/api/dto/NaverBookParseResult.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java diff --git a/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java b/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java new file mode 100644 index 000000000..3e84e6875 --- /dev/null +++ b/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java @@ -0,0 +1,15 @@ +package konkuk.thip.book.adapter.out.api.dto; + +import konkuk.thip.book.domain.Book; + +import java.util.List; + + +public record NaverBookParseResult( + List books, + int total, + int start) { + public static NaverBookParseResult of(List books, int total, int start) { + return new NaverBookParseResult(books, total, start); + } +} From c677a30c808dcf73caf910d7cf46f546a21cbe55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:40:56 +0900 Subject: [PATCH 13/25] =?UTF-8?q?[feat]=20NaverBookXmlParser=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/util/NaverBookXmlParser.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/main/java/konkuk/thip/util/NaverBookXmlParser.java diff --git a/src/main/java/konkuk/thip/util/NaverBookXmlParser.java b/src/main/java/konkuk/thip/util/NaverBookXmlParser.java new file mode 100644 index 000000000..30099821c --- /dev/null +++ b/src/main/java/konkuk/thip/util/NaverBookXmlParser.java @@ -0,0 +1,64 @@ +package konkuk.thip.util; + +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; +import konkuk.thip.book.domain.Book; +import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.common.exception.code.ErrorCode; +import org.w3c.dom.*; +import javax.xml.parsers.*; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import org.xml.sax.InputSource; + +public class NaverBookXmlParser { + + public static NaverBookParseResult parse(String xml) { + List books = new ArrayList<>(); + int total = -1; + int start = -1; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + InputSource is = new InputSource(new StringReader(xml)); + Document doc = builder.parse(is); + + NodeList channelNodes = doc.getElementsByTagName("channel"); + if (channelNodes.getLength() > 0) { + Element channel = (Element) channelNodes.item(0); + total = Integer.parseInt(getTagValue(channel, "total")); + start = Integer.parseInt(getTagValue(channel, "start")); + + NodeList itemNodes = channel.getElementsByTagName("item"); + for (int i = 0; i < itemNodes.getLength(); i++) { + Element item = (Element) itemNodes.item(i); + String title = getTagValue(item, "title"); + String imageUrl = getTagValue(item, "image"); + String authorName = getTagValue(item, "author"); + String publisher = getTagValue(item, "publisher"); + String isbn = getTagValue(item, "isbn"); + Book book = Book.builder() + .title(title) + .imageUrl(imageUrl) + .authorName(authorName) + .publisher(publisher) + .isbn(isbn) + .build(); + books.add(book); + } + } + } catch (Exception e) { + throw new BusinessException(ErrorCode.BOOK_NAVER_API_PARSING_ERROR); + } + return NaverBookParseResult.of(books, total, start); + } + + private static String getTagValue(Element element, String tag) { + NodeList nodeList = element.getElementsByTagName(tag); + if (nodeList.getLength() > 0 && nodeList.item(0).getFirstChild() != null) { + return nodeList.item(0).getFirstChild().getNodeValue(); + } + return ""; + } + +} From 246d03fbd816bae12f1e65736a506d6b0eb0c714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:41:03 +0900 Subject: [PATCH 14/25] =?UTF-8?q?[feat]=20SearchBookQueryPort=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/application/port/out/SearchBookQueryPort.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/konkuk/thip/book/application/port/out/SearchBookQueryPort.java diff --git a/src/main/java/konkuk/thip/book/application/port/out/SearchBookQueryPort.java b/src/main/java/konkuk/thip/book/application/port/out/SearchBookQueryPort.java new file mode 100644 index 000000000..739649bfd --- /dev/null +++ b/src/main/java/konkuk/thip/book/application/port/out/SearchBookQueryPort.java @@ -0,0 +1,7 @@ +package konkuk.thip.book.application.port.out; + +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; + +public interface SearchBookQueryPort { + NaverBookParseResult findBooksByKeyword(String keyword, int start); +} From fd16a79350f6e80ddba33ac7f9db49ab6dbcc12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:41:21 +0900 Subject: [PATCH 15/25] =?UTF-8?q?[test]=20BookQueryControllerTest=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/BookQueryControllerTest.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java new file mode 100644 index 000000000..20a1e4938 --- /dev/null +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java @@ -0,0 +1,77 @@ +package konkuk.thip.book.adapter.in.web; + +import konkuk.thip.common.exception.code.ErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +class BookQueryControllerTest { + + @Autowired + private MockMvc mockMvc; + + + @Test + @DisplayName("책 검색 API 정상 호출 - 키워드와 페이지 번호가 주어졌을 때") + void searchBooks_success() throws Exception { + mockMvc.perform(get("/books") + .param("keyword", "테스트") + .param("page", "1") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.data.searchResult").isArray()) + .andExpect(jsonPath("$.data.page").value(1)) + .andExpect(jsonPath("$.data.first").value(true)); + } + + @Test + @DisplayName("책 검색 API 실패 - 페이지가 범위를 벗어났을 때 400 에러 발생") + void searchBooks_pageOutOfRange() throws Exception { + mockMvc.perform(get("/books") + .param("keyword", "테스트") + .param("page", "99999") // totalPages보다 큰 값으로 가정 + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess").value(false)) + .andExpect(jsonPath("$.code").value(ErrorCode.BOOK_SEARCH_PAGE_OUT_OF_RANGE.getCode())) + .andExpect(jsonPath("$.message", containsString("검색어 페이지가 범위를 벗어났습니다"))); + } + + + @Test + @DisplayName("책 검색 API 실패 - 키워드가 비어서 넘어올 때 400 에러 발생") + void searchBooks_keywordMissing_badRequest() throws Exception { + mockMvc.perform(get("/books") + .param("page", "1") + .param("keyword", "") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.BOOK_KEYWORD_REQUIRED.getCode())) + .andExpect(jsonPath("$.message", containsString("검색어는 필수 입력값입니다"))); + } + + @Test + @DisplayName("책 검색 API 실패 - 페이지 번호가 1 미만일 때 400 에러 발생") + void searchBooks_pageInvalid_badRequest() throws Exception { + mockMvc.perform(get("/books") + .param("keyword", "테스트") + .param("page", "0") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.BOOK_PAGE_NUMBER_INVALID.getCode())) + .andExpect(jsonPath("$.message", containsString("페이지 번호는 1 이상의 값이어야 합니다"))); + } +} From 02a8d41db1ea5ed0babb923885411e28348b92f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sat, 28 Jun 2025 01:42:08 +0900 Subject: [PATCH 16/25] =?UTF-8?q?[test]=20NaverApiUtilTest=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/util/NaverApiUtilTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/test/java/konkuk/thip/util/NaverApiUtilTest.java diff --git a/src/test/java/konkuk/thip/util/NaverApiUtilTest.java b/src/test/java/konkuk/thip/util/NaverApiUtilTest.java new file mode 100644 index 000000000..1bdbe63be --- /dev/null +++ b/src/test/java/konkuk/thip/util/NaverApiUtilTest.java @@ -0,0 +1,49 @@ +package konkuk.thip.util; + +import konkuk.thip.common.exception.BusinessException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static konkuk.thip.common.exception.code.ErrorCode.BOOK_NAVER_API_REQUEST_ERROR; +import static org.assertj.core.api.Assertions.*; + +class NaverApiUtilTest { + + @Test + @DisplayName("정상 응답을 모킹하여 반환") + void searchBook_success_mocking() { + // given + NaverApiUtil naverApiUtil = Mockito.spy(new NaverApiUtil()); + + // get 메서드를 spy로 모킹 (실제 네트워크 호출 없이 원하는 응답 반환) + String expectedXml = "11"; + Mockito.doReturn(expectedXml) + .when(naverApiUtil) + .get(Mockito.anyString(), Mockito.anyMap()); + + // when + String result = naverApiUtil.searchBook("테스트", 1); + + // then + assertThat(result).contains("1"); + assertThat(result).contains("1"); + } + + @Test + @DisplayName("get 메서드에서 예외가 발생하면 BusinessException이 발생") + void searchBook_ioException() { + // given + NaverApiUtil naverApiUtil = Mockito.spy(new NaverApiUtil()); + + // get 메서드가 예외를 던지도록 설정 + Mockito.doThrow(new BusinessException(BOOK_NAVER_API_REQUEST_ERROR)) + .when(naverApiUtil) + .get(Mockito.anyString(), Mockito.anyMap()); + + // when & then + assertThatThrownBy(() -> naverApiUtil.searchBook("테스트", 1)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(BOOK_NAVER_API_REQUEST_ERROR.getMessage()); + } +} From f12ecc4876ea3dee2e6dc456eda42d6f078730b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:42:15 +0900 Subject: [PATCH 17/25] =?UTF-8?q?[refactor]=20NaverApiUtil=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java b/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java index d07c91879..50f02c712 100644 --- a/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java +++ b/src/main/java/konkuk/thip/book/adapter/out/api/BookApiAdapter.java @@ -2,8 +2,6 @@ import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; import konkuk.thip.book.application.port.out.SearchBookQueryPort; -import konkuk.thip.util.NaverApiUtil; -import konkuk.thip.util.NaverBookXmlParser; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; From 2a3b2a8a43b0697a73f267daf788c7839eea9be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:42:33 +0900 Subject: [PATCH 18/25] =?UTF-8?q?[refactor]=20NaverApiUtil=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EA=B2=80=EC=83=89?= =?UTF-8?q?=20url=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{util => book/adapter/out/api}/NaverApiUtil.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename src/main/java/konkuk/thip/{util => book/adapter/out/api}/NaverApiUtil.java (93%) diff --git a/src/main/java/konkuk/thip/util/NaverApiUtil.java b/src/main/java/konkuk/thip/book/adapter/out/api/NaverApiUtil.java similarity index 93% rename from src/main/java/konkuk/thip/util/NaverApiUtil.java rename to src/main/java/konkuk/thip/book/adapter/out/api/NaverApiUtil.java index b897be658..13a340403 100644 --- a/src/main/java/konkuk/thip/util/NaverApiUtil.java +++ b/src/main/java/konkuk/thip/book/adapter/out/api/NaverApiUtil.java @@ -1,4 +1,4 @@ -package konkuk.thip.util; +package konkuk.thip.book.adapter.out.api; import konkuk.thip.common.exception.BusinessException; import lombok.RequiredArgsConstructor; @@ -23,9 +23,10 @@ public class NaverApiUtil { private String clientId; @Value("${naver.clientSecret}") private String clientSecret; + @Value("${naver.bookSearchUrl}") + private String bookSearchUrl; - private final String NAVER_BOOK_SEARCH_URL = "https://openapi.naver.com/v1/search/book.xml?query="; //책 검색 결과 조회 - + public static final int PAGE_SIZE = 10; public String searchBook(String keyword, int start){ String query = keywordToEncoding(keyword); @@ -39,7 +40,7 @@ public String searchBook(String keyword, int start){ } private String buildSearchApiUrl(String query,Integer start) { - return NAVER_BOOK_SEARCH_URL+query+"&start="+start; + return bookSearchUrl+query+"&display="+PAGE_SIZE+"&start="+start; } private String keywordToEncoding(String keyword) { From 41e2ec44782d3b9629df686611f79a7638248a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:42:46 +0900 Subject: [PATCH 19/25] =?UTF-8?q?[refactor]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/api}/NaverApiUtilTest.java | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) rename src/test/java/konkuk/thip/{util => book/adapter/out/api}/NaverApiUtilTest.java (50%) diff --git a/src/test/java/konkuk/thip/util/NaverApiUtilTest.java b/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java similarity index 50% rename from src/test/java/konkuk/thip/util/NaverApiUtilTest.java rename to src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java index 1bdbe63be..b7ad7ae63 100644 --- a/src/test/java/konkuk/thip/util/NaverApiUtilTest.java +++ b/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java @@ -1,22 +1,44 @@ -package konkuk.thip.util; +package konkuk.thip.book.adapter.out.api; import konkuk.thip.common.exception.BusinessException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.lang.reflect.Field; + import static konkuk.thip.common.exception.code.ErrorCode.BOOK_NAVER_API_REQUEST_ERROR; import static org.assertj.core.api.Assertions.*; class NaverApiUtilTest { + private NaverApiUtil createTestUtil() { + NaverApiUtil util = Mockito.spy(new NaverApiUtil()); + // @Value로 주입되는 필드를 직접 세팅 + try { + Field clientIdField = NaverApiUtil.class.getDeclaredField("clientId"); + clientIdField.setAccessible(true); + clientIdField.set(util, "dummy-client-id"); + + Field clientSecretField = NaverApiUtil.class.getDeclaredField("clientSecret"); + clientSecretField.setAccessible(true); + clientSecretField.set(util, "dummy-client-secret"); + + Field bookSearchUrlField = NaverApiUtil.class.getDeclaredField("bookSearchUrl"); + bookSearchUrlField.setAccessible(true); + bookSearchUrlField.set(util, "https://dummy-url.com/search?query="); + } catch (Exception e) { + throw new RuntimeException(e); + } + return util; + } + @Test - @DisplayName("정상 응답을 모킹하여 반환") + @DisplayName("NaverApiUtil - 정상적으로 XML 응답을 반환한다 (외부 API 성공 케이스)") void searchBook_success_mocking() { // given - NaverApiUtil naverApiUtil = Mockito.spy(new NaverApiUtil()); + NaverApiUtil naverApiUtil = createTestUtil(); - // get 메서드를 spy로 모킹 (실제 네트워크 호출 없이 원하는 응답 반환) String expectedXml = "11"; Mockito.doReturn(expectedXml) .when(naverApiUtil) @@ -31,12 +53,11 @@ void searchBook_success_mocking() { } @Test - @DisplayName("get 메서드에서 예외가 발생하면 BusinessException이 발생") + @DisplayName("NaverApiUtil - get 메서드에서 예외 발생 시 BusinessException 발생") void searchBook_ioException() { // given - NaverApiUtil naverApiUtil = Mockito.spy(new NaverApiUtil()); + NaverApiUtil naverApiUtil = createTestUtil(); - // get 메서드가 예외를 던지도록 설정 Mockito.doThrow(new BusinessException(BOOK_NAVER_API_REQUEST_ERROR)) .when(naverApiUtil) .get(Mockito.anyString(), Mockito.anyMap()); From 1864fc33a2695a3e8e5c6c0fa2bc6e01447eea27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:43:32 +0900 Subject: [PATCH 20/25] =?UTF-8?q?[refactor]=20BookDto=EC=9D=B4=EB=84=88?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EB=B0=8F=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC,=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20response=EA=B0=92=20=EB=B6=84=EB=A6=AC=20(?= =?UTF-8?q?#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/adapter/in/web/response/BookDto.java | 9 ---- .../response/GetBookSearchListResponse.java | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 10 deletions(-) delete mode 100644 src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java b/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java deleted file mode 100644 index 2750096a4..000000000 --- a/src/main/java/konkuk/thip/book/adapter/in/web/response/BookDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package konkuk.thip.book.adapter.in.web.response; - -public record BookDto( - String title, - String imageUrl, - String authorName, - String publisher, - String isbn -) {} diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java b/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java index 96d831d2d..f67f07df6 100644 --- a/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java +++ b/src/main/java/konkuk/thip/book/adapter/in/web/response/GetBookSearchListResponse.java @@ -1,16 +1,57 @@ package konkuk.thip.book.adapter.in.web.response; +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; import lombok.Builder; import java.util.List; +import static konkuk.thip.book.adapter.out.api.NaverApiUtil.PAGE_SIZE; + @Builder public record GetBookSearchListResponse( List searchResult, // 책 목록 int page, // 현재 페이지 (1부터 시작) + int size, // 한 페이지에 포함되는 데이터 수 (페이지 크기) long totalElements, // 전체 데이터 개수 int totalPages, // 전체 페이지 수 boolean last, // 마지막 페이지 여부 boolean first // 첫 페이지 여부 ) { -} \ No newline at end of file + public static GetBookSearchListResponse of(NaverBookParseResult result, int page) { + int totalElements = result.total(); + int totalPages = (int) Math.ceil((double) totalElements / PAGE_SIZE); + boolean last = (page >= totalPages); + boolean first = (page == 1); + + List bookDtos = result.naverBooks().stream() + .map(BookDto::of) + .toList(); + + return new GetBookSearchListResponse( + bookDtos, + page, + PAGE_SIZE, + totalElements, + totalPages, + last, + first + ); + } + public record BookDto( + String title, + String imageUrl, + String authorName, + String publisher, + String isbn + ) { + public static BookDto of(NaverBookParseResult.NaverBook naverBook) { + return new BookDto( + naverBook.title(), + naverBook.imageUrl(), + naverBook.author(), + naverBook.publisher(), + naverBook.isbn() + ); + } + } +} From 4536fdcdd9dfd193e6a7fe654ee48ddbda0f9dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:43:49 +0900 Subject: [PATCH 21/25] =?UTF-8?q?[refactor]=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BookSearchService.java | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/src/main/java/konkuk/thip/book/application/service/BookSearchService.java b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java index 1a65abb80..ae390c33c 100644 --- a/src/main/java/konkuk/thip/book/application/service/BookSearchService.java +++ b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java @@ -1,7 +1,5 @@ package konkuk.thip.book.application.service; -import konkuk.thip.book.adapter.in.web.response.BookDto; -import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; import konkuk.thip.book.application.port.in.BookSearchUseCase; import konkuk.thip.book.application.port.out.SearchBookQueryPort; @@ -9,20 +7,17 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - +import static konkuk.thip.book.adapter.out.api.NaverApiUtil.PAGE_SIZE; import static konkuk.thip.common.exception.code.ErrorCode.*; @Service @RequiredArgsConstructor public class BookSearchService implements BookSearchUseCase { - private static final int PAGE_SIZE = 10; - private final SearchBookQueryPort SearchBookQueryPort; - + private final SearchBookQueryPort searchBookQueryPort; @Override - public GetBookSearchListResponse searchBooks(String keyword, int page) { + public NaverBookParseResult searchBooks(String keyword, int page) { if (keyword == null || keyword.isBlank()) { throw new BusinessException(BOOK_KEYWORD_REQUIRED); @@ -35,34 +30,15 @@ public GetBookSearchListResponse searchBooks(String keyword, int page) { //유저의 최근검색어 로직 추가 int start = (page - 1) * PAGE_SIZE + 1; //검색 시작 위치 - NaverBookParseResult result = SearchBookQueryPort.findBooksByKeyword(keyword, start); + NaverBookParseResult result = searchBookQueryPort.findBooksByKeyword(keyword, start); int totalElements = result.total(); int totalPages = (totalElements + PAGE_SIZE - 1) / PAGE_SIZE; if ( totalElements!=0 && page > totalPages) { throw new BusinessException(BOOK_SEARCH_PAGE_OUT_OF_RANGE); } - boolean last = (page >= totalPages); - boolean first = (page == 1); - - List bookDtos = result.books().stream() - .map(book -> new BookDto( - book.getTitle(), - book.getImageUrl(), - book.getAuthorName(), - book.getPublisher(), - book.getIsbn() - )) - .toList(); - return new GetBookSearchListResponse( - bookDtos, - page, - totalElements, - totalPages, - last, - first - ); + return result; } } \ No newline at end of file From 3ddd51183d1f994b655430e8b85061323a5affdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:44:06 +0900 Subject: [PATCH 22/25] =?UTF-8?q?[refactor]=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/book/adapter/in/web/BookQueryController.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java b/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java index 4192a1cb7..3bb6113f3 100644 --- a/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java +++ b/src/main/java/konkuk/thip/book/adapter/in/web/BookQueryController.java @@ -1,6 +1,7 @@ package konkuk.thip.book.adapter.in.web; import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; import konkuk.thip.book.application.port.in.BookSearchUseCase; import konkuk.thip.common.dto.BaseResponse; import lombok.RequiredArgsConstructor; @@ -15,6 +16,8 @@ public class BookQueryController { @GetMapping("/books") public BaseResponse getBookSearchList(@RequestParam final String keyword, @RequestParam final int page) { - return BaseResponse.ok(bookSearchUseCase.searchBooks(keyword, page)); + NaverBookParseResult result = bookSearchUseCase.searchBooks(keyword, page); + return BaseResponse.ok(GetBookSearchListResponse.of(result, page)); } -} \ No newline at end of file + +} From f7a5ce8cd4dc9b07219050f65ad7094e15842220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:44:22 +0900 Subject: [PATCH 23/25] =?UTF-8?q?[refactor]=20UseCase=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EA=B0=92=20=EB=B6=84=EB=A6=AC=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/book/application/port/in/BookSearchUseCase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java b/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java index 87e020df1..3dd13ebf0 100644 --- a/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java +++ b/src/main/java/konkuk/thip/book/application/port/in/BookSearchUseCase.java @@ -1,9 +1,9 @@ package konkuk.thip.book.application.port.in; -import konkuk.thip.book.adapter.in.web.response.GetBookSearchListResponse; +import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; public interface BookSearchUseCase { - GetBookSearchListResponse searchBooks(String keyword, int page); + NaverBookParseResult searchBooks(String keyword, int page); } From 0e752fe450a88649b71eda0fb2ba9abee83fcad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:44:46 +0900 Subject: [PATCH 24/25] =?UTF-8?q?[refactor]=20NaverBookParseResult=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B0=92=20NaverBook=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/api/dto/NaverBookParseResult.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java b/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java index 3e84e6875..1ee7ec7af 100644 --- a/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java +++ b/src/main/java/konkuk/thip/book/adapter/out/api/dto/NaverBookParseResult.java @@ -1,15 +1,23 @@ package konkuk.thip.book.adapter.out.api.dto; -import konkuk.thip.book.domain.Book; +import lombok.Builder; import java.util.List; public record NaverBookParseResult( - List books, + List naverBooks, int total, int start) { - public static NaverBookParseResult of(List books, int total, int start) { + @Builder + public record NaverBook( + String title, + String imageUrl, + String author, + String publisher, + String isbn + ) {} + public static NaverBookParseResult of(List books, int total, int start) { return new NaverBookParseResult(books, total, start); } } From 7f884dab18e34a58c00c6fbead2fc2b8aef1835b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=ED=9D=AC=EC=A7=84?= Date: Sun, 29 Jun 2025 17:44:54 +0900 Subject: [PATCH 25/25] =?UTF-8?q?[refactor]=20NaverBookParseResult=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B0=92=20NaverBook=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/api}/NaverBookXmlParser.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) rename src/main/java/konkuk/thip/{util => book/adapter/out/api}/NaverBookXmlParser.java (83%) diff --git a/src/main/java/konkuk/thip/util/NaverBookXmlParser.java b/src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java similarity index 83% rename from src/main/java/konkuk/thip/util/NaverBookXmlParser.java rename to src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java index 30099821c..26e3c2828 100644 --- a/src/main/java/konkuk/thip/util/NaverBookXmlParser.java +++ b/src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java @@ -1,7 +1,6 @@ -package konkuk.thip.util; +package konkuk.thip.book.adapter.out.api; import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult; -import konkuk.thip.book.domain.Book; import konkuk.thip.common.exception.BusinessException; import konkuk.thip.common.exception.code.ErrorCode; import org.w3c.dom.*; @@ -14,7 +13,7 @@ public class NaverBookXmlParser { public static NaverBookParseResult parse(String xml) { - List books = new ArrayList<>(); + List Naverbooks = new ArrayList<>(); int total = -1; int start = -1; try { @@ -34,23 +33,23 @@ public static NaverBookParseResult parse(String xml) { Element item = (Element) itemNodes.item(i); String title = getTagValue(item, "title"); String imageUrl = getTagValue(item, "image"); - String authorName = getTagValue(item, "author"); + String author = getTagValue(item, "author"); String publisher = getTagValue(item, "publisher"); String isbn = getTagValue(item, "isbn"); - Book book = Book.builder() + NaverBookParseResult.NaverBook naverBook = NaverBookParseResult.NaverBook.builder() .title(title) .imageUrl(imageUrl) - .authorName(authorName) + .author(author) .publisher(publisher) .isbn(isbn) .build(); - books.add(book); + Naverbooks.add(naverBook); } } } catch (Exception e) { throw new BusinessException(ErrorCode.BOOK_NAVER_API_PARSING_ERROR); } - return NaverBookParseResult.of(books, total, start); + return NaverBookParseResult.of(Naverbooks, total, start); } private static String getTagValue(Element element, String tag) {