Skip to content

Commit 5ebfb0b

Browse files
authored
Merge pull request #177 from Project-BookLog/feat/176/home
Feat/176/home
2 parents 135dea5 + faea772 commit 5ebfb0b

2 files changed

Lines changed: 74 additions & 48 deletions

File tree

booklog/src/main/java/com/example/booklog/domain/onboarding/dto/BookRecommendationCardResponse.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public class BookRecommendationCardResponse {
2020
*/
2121
private Long bookId;
2222

23+
/**
24+
* 카드 타이틀 (예: "작가", "장르", "분위기")
25+
*/
26+
private String title;
27+
2328
/**
2429
* 도서 제목
2530
*/

booklog/src/main/java/com/example/booklog/domain/onboarding/service/OnboardingRecommendationService.java

Lines changed: 69 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import java.util.stream.Collectors;
2929

3030
/**
31-
* 온보딩 기반 도서 추천 서비스
31+
* 온보딩 기반 도서 추천 서비스//햣
3232
* - 사용자의 온보딩 키워드 + 최근 검색어를 바탕으로 도서 추천
3333
* - GPT를 통한 작가/장르 추론
3434
* - Kakao Book API를 통한 도서 검색
@@ -171,7 +171,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateLibr
171171

172172
if (!authorInfo.isEmpty()) {
173173
BookRecommendationCardResponse card = generateLibraryBasedCard(
174-
book.getTitle(), authorInfo, recentSearches);
174+
book.getTitle(), authorInfo, recentSearches, "작가");
175175
if (card != null) {
176176
books.add(card);
177177
}
@@ -208,7 +208,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateLibr
208208

209209
if (!authorInfo.isEmpty()) {
210210
BookRecommendationCardResponse card = generateLibraryBasedCard(
211-
book.getTitle(), authorInfo, recentSearches);
211+
book.getTitle(), authorInfo, recentSearches, "장르");
212212
if (card != null) {
213213
books.add(card);
214214
}
@@ -245,7 +245,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateLibr
245245

246246
if (!authorInfo.isEmpty()) {
247247
BookRecommendationCardResponse card = generateLibraryBasedCard(
248-
book.getTitle(), authorInfo, recentSearches);
248+
book.getTitle(), authorInfo, recentSearches, "분위기");
249249
if (card != null) {
250250
books.add(card);
251251
}
@@ -318,7 +318,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateAuth
318318
// 2개의 도서 추천
319319
for (int i = 0; i < 2; i++) {
320320
BookRecommendationCardResponse card = generateOnboardingBasedCard(
321-
criteria, recentSearches, profile);
321+
criteria, recentSearches, profile, "분위기");
322322
if (card != null) {
323323
books.add(card);
324324
}
@@ -366,7 +366,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateGenr
366366
// 2개의 도서 추천
367367
for (int i = 0; i < 2; i++) {
368368
BookRecommendationCardResponse card = generateOnboardingBasedCard(
369-
criteria, recentSearches, profile);
369+
criteria, recentSearches, profile, "문체");
370370
if (card != null) {
371371
books.add(card);
372372
}
@@ -408,7 +408,7 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateMood
408408
// 2개의 도서 추천
409409
for (int i = 0; i < 2; i++) {
410410
BookRecommendationCardResponse card = generateOnboardingBasedCard(
411-
criteria, recentSearches, profile);
411+
criteria, recentSearches, profile, "몰입도");
412412
if (card != null) {
413413
books.add(card);
414414
}
@@ -432,7 +432,8 @@ private OnboardingBasedRecommendationResponse.RecommendationSection generateMood
432432
private BookRecommendationCardResponse generateLibraryBasedCard(
433433
String bookTitle,
434434
String authorInfo,
435-
String recentSearches) {
435+
String recentSearches,
436+
String title) {
436437

437438
// GPT 호출 - 서재 도서 기반 유사 도서 추천
438439
GptRecommendationRequest gptRequest = GptRecommendationRequest.builder()
@@ -473,6 +474,7 @@ private BookRecommendationCardResponse generateLibraryBasedCard(
473474

474475
return BookRecommendationCardResponse.builder()
475476
.bookId(bookId)
477+
.title(title)
476478
.bookTitle(recommendedBook.getTitle())
477479
.author(recommendedAuthor)
478480
.publisher(recommendedBook.getPublisher())
@@ -500,48 +502,27 @@ private OnboardingBasedRecommendationResponse generatePopularBooksRecommendation
500502

501503
List<Books> popularBooks = booksRepository.findAll(pageRequest).getContent();
502504

503-
List<BookRecommendationCardResponse> allRecommendations = popularBooks.stream()
504-
.map(book -> {
505-
String authorInfo = book.getBookAuthors().stream()
506-
.filter(ba -> ba.getRole() == com.example.booklog.domain.library.books.entity.AuthorRole.AUTHOR)
507-
.map(ba -> ba.getAuthor().getName())
508-
.collect(Collectors.joining(", "));
509-
510-
String author = authorInfo.isEmpty() ? "저자 미상" : authorInfo;
511-
512-
// GPT를 통한 키워드 분석
513-
Map<String, String> keywords = gptService.analyzeBookKeywords(
514-
book.getTitle(),
515-
author,
516-
book.getPublisherName()
517-
);
518-
519-
return BookRecommendationCardResponse.builder()
520-
.bookId(book.getId())
521-
.bookTitle(book.getTitle())
522-
.author(author)
523-
.publisher(book.getPublisherName())
524-
.thumbnailUrl(book.getThumbnailUrl())
525-
.recommendationSourceField("popular_ranking")
526-
.recommendationSourceValue("최신 인기 도서")
527-
// 인기 도서 기반(서재/온보딩 없음): 작가, 장르, 분위기
528-
.keyword1(author) // 작가
529-
.keyword2(keywords.get("genre") != null ? keywords.get("genre") : "장르 미상") // 장르
530-
.keyword3(keywords.get("mood")) // 분위기
531-
// 하위 호환성
532-
.moodKeyword(keywords.get("mood"))
533-
.styleKeyword(keywords.get("style"))
534-
.immersionKeyword(keywords.get("immersion"))
535-
.build();
536-
})
505+
// 작가 섹션 (0-1번 책)
506+
List<BookRecommendationCardResponse> authorBooks = popularBooks.stream()
507+
.limit(2)
508+
.map(book -> buildPopularBookCard(book, "작가"))
509+
.toList();
510+
511+
// 장르 섹션 (2-3번 책)
512+
List<BookRecommendationCardResponse> genreBooks = popularBooks.stream()
513+
.skip(2)
514+
.limit(2)
515+
.map(book -> buildPopularBookCard(book, "장르"))
537516
.toList();
538517

539-
// 3개 섹션으로 분리 (각 섹션당 2개씩)
540-
List<BookRecommendationCardResponse> authorBooks = allRecommendations.stream().limit(2).toList();
541-
List<BookRecommendationCardResponse> genreBooks = allRecommendations.stream().skip(2).limit(2).toList();
542-
List<BookRecommendationCardResponse> moodBooks = allRecommendations.stream().skip(4).limit(2).toList();
518+
// 분위기 섹션 (4-5번 책)
519+
List<BookRecommendationCardResponse> moodBooks = popularBooks.stream()
520+
.skip(4)
521+
.limit(2)
522+
.map(book -> buildPopularBookCard(book, "분위기"))
523+
.toList();
543524

544-
log.info("인기 도서 기반 추천 완료 - count: {}", allRecommendations.size());
525+
log.info("인기 도서 기반 추천 완료 - count: {}", popularBooks.size());
545526

546527
return OnboardingBasedRecommendationResponse.builder()
547528
.authorSection(OnboardingBasedRecommendationResponse.RecommendationSection.builder()
@@ -567,13 +548,52 @@ private OnboardingBasedRecommendationResponse generatePopularBooksRecommendation
567548
}
568549
}
569550

551+
/**
552+
* 인기 도서 카드 생성 헬퍼 메서드
553+
*/
554+
private BookRecommendationCardResponse buildPopularBookCard(Books book, String title) {
555+
String authorInfo = book.getBookAuthors().stream()
556+
.filter(ba -> ba.getRole() == com.example.booklog.domain.library.books.entity.AuthorRole.AUTHOR)
557+
.map(ba -> ba.getAuthor().getName())
558+
.collect(Collectors.joining(", "));
559+
560+
String author = authorInfo.isEmpty() ? "저자 미상" : authorInfo;
561+
562+
// GPT를 통한 키워드 분석
563+
Map<String, String> keywords = gptService.analyzeBookKeywords(
564+
book.getTitle(),
565+
author,
566+
book.getPublisherName()
567+
);
568+
569+
return BookRecommendationCardResponse.builder()
570+
.bookId(book.getId())
571+
.title(title)
572+
.bookTitle(book.getTitle())
573+
.author(author)
574+
.publisher(book.getPublisherName())
575+
.thumbnailUrl(book.getThumbnailUrl())
576+
.recommendationSourceField("popular_ranking")
577+
.recommendationSourceValue("최신 인기 도서")
578+
// 인기 도서 기반(서재/온보딩 없음): 작가, 장르, 분위기
579+
.keyword1(author) // 작가
580+
.keyword2(keywords.get("genre") != null ? keywords.get("genre") : "장르 미상") // 장르
581+
.keyword3(keywords.get("mood")) // 분위기
582+
// 하위 호환성
583+
.moodKeyword(keywords.get("mood"))
584+
.styleKeyword(keywords.get("style"))
585+
.immersionKeyword(keywords.get("immersion"))
586+
.build();
587+
}
588+
570589
/**
571590
* 온보딩 기반 개별 추천 카드 생성
572591
*/
573592
private BookRecommendationCardResponse generateOnboardingBasedCard(
574593
RecommendationCriteria criteria,
575594
String recentSearches,
576-
UserReadingProfile profile) {
595+
UserReadingProfile profile,
596+
String title) {
577597

578598
log.debug("추천 카드 생성 - field: {}, value: {}",
579599
criteria.getFieldName(), criteria.getFieldValue());
@@ -617,6 +637,7 @@ private BookRecommendationCardResponse generateOnboardingBasedCard(
617637
// 5. 추천 카드 DTO 생성
618638
return BookRecommendationCardResponse.builder()
619639
.bookId(bookId)
640+
.title(title)
620641
.bookTitle(book.getTitle())
621642
.author(book.getAuthors() != null && !book.getAuthors().isEmpty()
622643
? String.join(", ", book.getAuthors()) : "저자 미상")

0 commit comments

Comments
 (0)