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,16 @@
package com.hansung.leafly.domain.book.exception;

import com.hansung.leafly.global.response.code.BaseResponseCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@AllArgsConstructor
public enum BookErrorCode implements BaseResponseCode{
BOOK_NOT_FOUND("BOOK_404_1", 404, "해당 ISBN으로 검색된 책이 없습니다.");

private final String code;
private final int httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.hansung.leafly.domain.book.exception;

import com.hansung.leafly.global.exception.BaseException;

public class BookNotFoundException extends BaseException {
public BookNotFoundException() {
super(BookErrorCode.BOOK_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.hansung.leafly.domain.book.service;

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

import java.util.List;

public interface BookService {
List<SearchRes> search(String keyword, BookFilterReq req);

BookInfoRes details(Long isbn);
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
package com.hansung.leafly.domain.book.service;

import com.hansung.leafly.domain.book.entity.enums.BookGenre;
import com.hansung.leafly.domain.book.exception.BookNotFoundException;
import com.hansung.leafly.domain.book.web.dto.*;
import com.hansung.leafly.infra.aladin.AladinClient;
import com.hansung.leafly.infra.aladin.dto.BookRes;
import com.hansung.leafly.infra.openai.OpenAiClient;
import com.hansung.leafly.infra.openai.dto.BookSummaryAiRes;
import com.hansung.leafly.infra.openai.prompt.RecommendationPrompt;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import lombok.extern.slf4j.Slf4j;
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;
private final OpenAiClient openAiClient;

// 검색
@Override
public List<SearchRes> search(String keyword, BookFilterReq req) {
AladinSearchResponse response = aladinClient.search(keyword);
AladinSearchRes response = aladinClient.search(keyword);

if (response == null || response.item() == null) {
return List.of();
Expand All @@ -39,6 +43,39 @@ public List<SearchRes> search(String keyword, BookFilterReq req) {
.toList();
}

@Override
public BookInfoRes details(Long isbn) {
// ISBN 기반 상세 정보 조회
BookRes bookRes = aladinClient.fetchBookByIsbn(String.valueOf(isbn));
if (bookRes == null ||
bookRes.getItems() == null ||
bookRes.getItems().isEmpty() ||
bookRes.getItems().get(0) == null) {
throw new BookNotFoundException();
}

BookDetailRes detail = BookDetailRes.from(bookRes);

// BookSummaryPrompt 생성 → AI 요약 + 태그 생성
String prompt = RecommendationPrompt.build(detail);
BookSummaryAiRes summaryAi = openAiClient.summarizeBook(prompt);

// 해당 카테고리 기반 추천 목록 검색
String categoryId = String.valueOf(bookRes.getItems().get(0).getCategoryId());
AladinSearchRes categorySearchRes = aladinClient.searchByCategory(categoryId);
List<RecommendRes> recommendations = toRecommendationList(categorySearchRes);

//boolean liked = likeService.isBookLiked(memberId, isbn13);

return BookInfoRes.of(
detail,
summaryAi.summary(),
summaryAi.tags(),
recommendations,
false
);
}

//카테고리 필터링
private boolean matchesGenre(AladinBookItem item, List<BookGenre> targetGenres) {
String middle = extractMiddleCategory(item.categoryName());
Expand All @@ -57,4 +94,15 @@ private String extractMiddleCategory(String categoryName) {
String[] tokens = categoryName.split(">");
return tokens.length >= 2 ? tokens[1].trim() : "";
}

private List<RecommendRes> toRecommendationList(AladinSearchRes res) {
return res.item().stream()
.map(item -> new RecommendRes(
item.isbn13(),
item.title(),
item.author(),
item.cover()
))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.hansung.leafly.domain.book.service.BookService;
import com.hansung.leafly.domain.book.web.dto.BookFilterReq;
import com.hansung.leafly.domain.book.web.dto.BookInfoRes;
import com.hansung.leafly.domain.book.web.dto.SearchRes;
import com.hansung.leafly.global.response.SuccessResponse;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -30,4 +31,13 @@ public ResponseEntity<SuccessResponse<List<SearchRes>>> search(
List<SearchRes> res = bookService.search(keyword, req);
return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(res));
}

@GetMapping("/{isbn}")
public ResponseEntity<SuccessResponse<BookInfoRes>> details(
@PathVariable Long isbn
){
BookInfoRes res = bookService.details(isbn);
return ResponseEntity.status(HttpStatus.OK).body(SuccessResponse.from(res));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.List;

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.hansung.leafly.domain.book.web.dto;

import com.hansung.leafly.infra.aladin.dto.BookRes;

public record BookDetailRes(
String title, //책 제목
String author, //책 저자
String publisher, //출판사
String pubDate, //출판일
String description, //간략 소개
String isbn13, //isbn값
String cover, //책표지
int priceStandard, //원가
int priceSales //할인가
) {
public static BookDetailRes from(BookRes res) {
BookRes.Item item = res.getItems().get(0);

return new BookDetailRes(
item.getTitle(),
item.getAuthor(),
item.getPublisher(),
item.getPubDate(),
item.getDescription(),
item.getIsbn13(),
item.getCover(),
item.getPriceStandard(),
item.getPriceSales()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.hansung.leafly.domain.book.web.dto;

import java.util.List;

public record BookInfoRes(
BookDetailRes bookDetail, // 책 상세 정보
String aiSummary, // AI가 생성한 요약 문장
List<String> aiTags, // AI가 선정한 태그들
List<RecommendRes> recommendations, // 추천 책 리스트
boolean isLiked
){
public static BookInfoRes of(
BookDetailRes bookDetailRes,
String aiSummary,
List<String> aiTags,
List<RecommendRes> recommendations,
boolean isLiked
) {
return new BookInfoRes(
bookDetailRes,
aiSummary,
aiTags,
recommendations,
isLiked
);
}}
64 changes: 0 additions & 64 deletions src/main/java/com/hansung/leafly/domain/book/web/dto/BookRes.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.hansung.leafly.domain.book.web.dto;

public record RecommendRes (
String isbn,
String title,
String author,
String cover
) {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hansung.leafly.domain.recommend.service;

import com.hansung.leafly.domain.book.web.dto.AladinSearchResponse;
import com.hansung.leafly.domain.book.web.dto.AladinSearchRes;
import com.hansung.leafly.domain.book.web.dto.SearchRes;
import com.hansung.leafly.domain.member.entity.Member;
import com.hansung.leafly.domain.member.entity.Onboarding;
Expand Down Expand Up @@ -41,7 +41,7 @@ public List<RecommendRes> recommendations(Member member) {

//실제 존재하는 책 필터링
for (var book : aiRes.books()) {
AladinSearchResponse searchRes = aladinClient.search(book.title());
AladinSearchRes searchRes = aladinClient.search(book.title());

if (searchRes == null || searchRes.item() == null || searchRes.item().isEmpty()) {
continue;
Expand Down
25 changes: 21 additions & 4 deletions src/main/java/com/hansung/leafly/infra/aladin/AladinClient.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 com.hansung.leafly.domain.book.web.dto.AladinSearchRes;
import com.hansung.leafly.domain.book.web.dto.RecommendRes;
import com.hansung.leafly.infra.aladin.dto.BookRes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
Expand Down Expand Up @@ -35,7 +36,7 @@ public BookRes fetchBookByIsbn(String isbn) {
return restTemplate.getForObject(uri, BookRes.class);
}

public AladinSearchResponse search(String keyword) {
public AladinSearchRes search(String keyword) {
String uri = UriComponentsBuilder.fromHttpUrl(SEARCH_URL)
.queryParam("ttbkey", TTB_KEY)
.queryParam("Query", keyword)
Expand All @@ -47,7 +48,23 @@ public AladinSearchResponse search(String keyword) {
.build()
.toUriString();

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

public AladinSearchRes searchByCategory(String categoryId) {
String uri = UriComponentsBuilder.fromHttpUrl(SEARCH_URL)
.queryParam("ttbkey", TTB_KEY)
.queryParam("Query", "*") // 전체 검색
.queryParam("QueryType", "Keyword")
.queryParam("SearchTarget", "Book")
.queryParam("CategoryId", categoryId)
.queryParam("MaxResults", 6)
.queryParam("output", "js")
.queryParam("Version", "20131101")
.build()
.toUriString();

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

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

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class BookRes {

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

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

@Getter
@NoArgsConstructor
@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;
}
}
Loading