2828import 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