Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.hansung.leafly.domain.book.service;

import com.hansung.leafly.domain.book.web.dto.SearchRes;

import java.util.List;

public interface BookService {
List<SearchRes> search(String keyword);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.hansung.leafly.domain.book.service;

import com.hansung.leafly.domain.book.web.dto.AladinSearchResponse;
import com.hansung.leafly.domain.book.web.dto.BookRes;
import com.hansung.leafly.domain.book.web.dto.SearchRes;
import com.hansung.leafly.infra.aladin.AladinClient;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;

@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService {
private final AladinClient aladinClient;

// 제목 검색
@Override
public List<SearchRes> search(String keyword) {
AladinSearchResponse response = aladinClient.search(keyword);

if (response == null || response.item() == null) {
return List.of();
}

return response.item().stream()
.map(item -> SearchRes.from(
item,
false //좋아요 기능 구현 후 수정 필요
))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.hansung.leafly.domain.book.web.controller;

import com.hansung.leafly.domain.book.service.BookService;
import com.hansung.leafly.domain.book.web.dto.SearchRes;
import com.hansung.leafly.global.response.SuccessResponse;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/books")
@RequiredArgsConstructor
@Validated
public class BookController {
private final BookService bookService;

@GetMapping
public ResponseEntity<SuccessResponse<List<SearchRes>>> search(
@RequestParam @Size(min = 2, message = "검색어는 2글자 이상이어야 합니다.")
String keyword
){
List<SearchRes> res = bookService.search(keyword);
return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(res));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hansung.leafly.domain.book.web.dto;

public record AladinBookItem(
String title, // 책 제목
String author, // 저자
String publisher, // 출판사
String pubDate, // 출판일 (yyyy-MM-dd)
String description, // 책 설명
String isbn, // ISBN10
String isbn13, // ISBN13
String cover, // 표지 URL
String categoryName, // 카테고리명 (예: 컴퓨터/IT > 소프트웨어)
double customerReviewRank // 알라딘 사용자 평점
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hansung.leafly.domain.book.web.dto;

import java.util.List;

public record AladinSearchResponse(
List<AladinBookItem> item
) {}

64 changes: 64 additions & 0 deletions src/main/java/com/hansung/leafly/domain/book/web/dto/BookRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.hansung.leafly.domain.book.web.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public class BookRes {

@JsonProperty("item")
private List<Item> items;

public List<Item> getItems() {
return items;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Item {
private String title;
private String author;
private String pubDate;
private String publisher;
private String isbn;
private String isbn13;
private String description;
private String cover;
private Integer priceStandard;
private Integer priceSales;
private Double customerReviewRank;
private Integer categoryId;
private String categoryName;
private SubInfo subInfo;

// ✅ Getter & Setter
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getPubDate() { return pubDate; }
public String getPublisher() { return publisher; }
public String getIsbn() { return isbn; }
public String getIsbn13() { return isbn13; }
public String getDescription() { return description; }
public String getCover() { return cover; }
public Integer getPriceStandard() { return priceStandard; }
public Integer getPriceSales() { return priceSales; }
public Double getCustomerReviewRank() { return customerReviewRank; }
public Integer getCategoryId() { return categoryId; }
public String getCategoryName() { return categoryName; }
public void setSubInfo(SubInfo subInfo) { this.subInfo = subInfo; }

// ✅ 책 실제 쪽수 반환 (subInfo.itemPage)
public Integer getItemPage() {
return (subInfo != null) ? subInfo.getItemPage() : null;
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class SubInfo {
private Integer itemPage; // 실제 책의 쪽수

public Integer getItemPage() { return itemPage; }
public void setItemPage(Integer itemPage) { this.itemPage = itemPage; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hansung.leafly.domain.book.web.dto;

public record SearchRes (
String isbn,
String title,
String author,
String cover,
double rating,
boolean isLiked
){
public static SearchRes from(AladinBookItem item, boolean isLiked) {
return new SearchRes(
item.isbn13(),
item.title(),
item.author(),
item.cover(),
item.customerReviewRank(),
isLiked
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.hansung.leafly.global.response.ErrorResponse;
import com.hansung.leafly.global.response.code.ErrorResponseCode;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand Down Expand Up @@ -103,6 +104,24 @@ public ResponseEntity<ErrorResponse<?>> handleBaseException(BaseException e){
return ResponseEntity.status(errorResponse.getHttpStatus()).body(errorResponse);
}

// RequestParam, PathVariable 등 검증(@Validated) 실패 시 발생
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse<?>> handleConstraintViolationException(ConstraintViolationException e) {
log.error("ConstraintViolationException : {}", e.getMessage(), e);

// 여러 위반 중 첫 번째 메시지 선택
String message = e.getConstraintViolations().stream()
.findFirst()
.map(v -> v.getMessage())
.orElse("유효하지 않은 요청 파라미터입니다.");

ErrorResponse<?> errorResponse = ErrorResponse.of(
ErrorResponseCode.INVALID_HTTP_MESSAGE_PARAMETER,
message
);
return ResponseEntity.status(errorResponse.getHttpStatus()).body(errorResponse);
}

// 나머지 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse<?>> handleException(Exception e){
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/com/hansung/leafly/infra/aladin/AladinClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.hansung.leafly.infra.aladin;

import com.hansung.leafly.domain.book.web.dto.AladinSearchResponse;
import com.hansung.leafly.domain.book.web.dto.BookRes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

@Component
public class AladinClient {

@Value("${spring.aladin.isbn.url}")
private String ISBN_URL;

@Value("${spring.aladin.search.url}")
private String SEARCH_URL;

@Value("${spring.aladin.key}")
private String TTB_KEY;

private final RestTemplate restTemplate = new RestTemplate();

public BookRes fetchBookByIsbn(String isbn) {
String uri = UriComponentsBuilder.fromHttpUrl(ISBN_URL)
.queryParam("ttbkey", TTB_KEY)
.queryParam("itemIdType", "ISBN13")
.queryParam("ItemId", isbn)
.queryParam("output", "js")
.queryParam("Version", "20131101")
.queryParam("Cover", "Big")
.build()
.toUriString();

return restTemplate.getForObject(uri, BookRes.class);
}

public AladinSearchResponse search(String keyword) {
String uri = UriComponentsBuilder.fromHttpUrl(SEARCH_URL)
.queryParam("ttbkey", TTB_KEY)
.queryParam("Query", keyword)
.queryParam("QueryType", "Title")
.queryParam("SearchTarget", "Book")
.queryParam("MaxResults", 20)
.queryParam("output", "js")
.queryParam("Version", "20131101")
.build()
.toUriString();

return restTemplate.getForObject(uri, AladinSearchResponse.class);
}
}

7 changes: 6 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ spring.jpa.properties.hibernate.use_sql_comments=true

#JWT
jwt.secret=${jwt.secret}
jwt.expiration=${jwt.expiration}
jwt.expiration=${jwt.expiration}

#Aladin
spring.aladin.isbn.url=${ISBN_URL}
spring.aladin.search.url=${SEARCH_URL}
spring.aladin.key=${ALADIN_API_KEY}