Skip to content

[FEAT] 책 상세정보 API 리팩토링 #147

@millkk04

Description

@millkk04
  1. 책 상세정보 API 확장 (shortIntro, aiTasteComment, tasteAnalysis, tableOfContents)
  2. description 끊김 현상 해결
  3. 온보딩 기반 도서 추천 API 인증 적용 (이미 구현됨 확인)
  4. 도서 검색 API에 태그값 추가

구현 내용

책 상세정보 API 확장

API: GET /api/v1/books/{bookId}

추가된 필드

필드 타입 설명 예시
shortIntro String 책 간략 소개 (한 문장) "상실, 사랑 그리고 숨어 있는 삶의 질서에 관한 이야기"
aiTasteComment Object AI 취향 코멘트 {"title": "사유가 깊어지는 문장들", "description": "..."}
tasteAnalysis Object 상세 취향 분석 {"mood": {...}, "style": {...}, "immersion": {...}}
tableOfContents Array 목차 정보 ["1장 ...", "2장 ..."]

응답 예시

{
  "bookId": 51,
  "title": "물고기는 존재하지 않는다",
  "description": "상실, 사랑, 그리고 숨어 있는 삶의 질서에 관한 이야기...(전체 텍스트)",
  "shortIntro": "상실, 사랑 그리고 숨어 있는 삶의 질서에 관한 이야기",
  "thumbnailUrl": "...",
  "publisherName": "곰출판",
  "publishedDate": "2021-03-25",
  "isbn": "...",
  "isbn10": "...",
  "isbn13": "...",
  "detailUrl": "...",
  "authors": [
    {
      "authorId": 1,
      "name": "룰루 밀러",
      "role": "AUTHOR",
      "profileImageUrl": null
    }
  ],
  "aiTasteComment": {
    "title": "우울한 감성이 담긴 작품",
    "description": "이 책은 우울한 분위기로 독자를 사로잡습니다."
  },
  "tasteAnalysis": {
    "mood": {
      "title": "#우울한",
      "description": "우울한 분위기가 독자를 깊이 사로잡습니다."
    },
    "style": {
      "title": "#서정적인",
      "description": "서정적인 문체로 이야기를 풀어나갑니다."
    },
    "immersion": {
      "title": "#사색적인",
      "description": "사색적인 몰입감으로 페이지를 넘기게 만듭니다."
    }
  },
  "tableOfContents": []
}

구현 방식

  1. DB 스키마 확장

    • Books 엔티티에 4개 필드 추가 (@Lob, TEXT/LONGTEXT)
    • JPA ddl-auto: update로 자동 마이그레이션
  2. AI 정보 자동 생성

    • BookEnrichmentService: 책 조회 시 AI 정보가 없으면 자동 생성
    • shortIntro: description 첫 문장 추출
    • aiTasteComment: 태그 기반 JSON 생성
    • tasteAnalysis: MOOD/STYLE/IMMERSION 태그 기반 JSON 생성
    • tableOfContents: 빈 배열 (향후 확장 가능)
  3. 태그 기반 분석

    • 실제 DB의 tags, book_tags 테이블 활용
    • 태그가 없으면 자동 매핑 (후술)

description 끊김 현상 해결

문제 원인

  • DB 컬럼 타입: TEXT (최대 64KB) → 긴 설명 저장 시 잘림

해결 방법

  1. 엔티티 수정

    @Lob
    @Column(name = "contents", columnDefinition = "LONGTEXT")
    private String description;
  2. DB 자동 마이그레이션

    • ddl-auto: update 설정으로 자동 적용
    • TEXTLONGTEXT (최대 4GB)
  3. 검증

    • 외부 API에서 truncate 로직 없음 확인
    • DTO 매핑에서 substring 없음 확인
    • JSON 직렬화에서 제한 없음 확인

결과

  • 모든 책의 전체 설명이 완전하게 저장 및 반환됨
  • 기존 데이터 안전하게 유지

온보딩 기반 도서 추천 API 인증 적용

API: GET /api/v1/onboarding/recommendations

확인 결과

이미 인증 구현되어 있음

구현 상태

  1. 컨트롤러

    @SecurityRequirement(name = "Bearer Authentication")
    @GetMapping("/recommendations")
    public ResponseEntity<...> getRecommendations(
        @AuthenticationPrincipal CustomUserDetails userDetails
    ) {
        Long userId = userDetails.getUserId(); // 토큰에서 추출
        ...
    }
  2. Security 설정

    • /api/v1/onboarding/** 경로는 allowUris에 없음 → 인증 필수
    • JWT 필터 적용됨
    • 인증 실패 시 401/403 자동 반환
  3. 테스트 시나리오

    # 인증 없이 호출 → 401 Unauthorized
    curl -X GET http://localhost:8080/api/v1/onboarding/recommendations
    
    # 인증과 함께 호출 → 200 OK
    curl -X GET http://localhost:8080/api/v1/onboarding/recommendations \
      -H "Authorization: Bearer {JWT_TOKEN}"

도서 검색 API에 태그값 추가

API: GET /api/v1/search/books

추가된 필드

{
  "page": 1,
  "size": 10,
  "isEnd": false,
  "totalCount": 25,
  "items": [
    {
      "bookId": 1,
      "title": "클린 코드",
      "thumbnailUrl": "...",
      "publisherName": "인사이트",
      "isbn13": "...",
      "authors": ["로버트 C. 마틴"],
      "translators": ["박재호", "이해영"],
      "publishedDate": "2013-12-24",
      "tags": ["#직설적인", "#높은몰입감", "#유쾌한"]  // 추가
    }
  ]
}

구현 방식

  1. DTO 수정

    • BookSearchItemResponsetags 필드 추가
  2. 서비스 로직

    // BookSearchService.convertToSearchItem()
    List<String> tags = bookTagsRepository.findAllByBookId(book.getId())
        .stream()
        .map(BookTags::getTag)
        .map(tag -> "#" + tag.getName())
        .toList();
  3. 안전성

    • 태그가 없으면 빈 배열 [] 반환
    • 500 에러 발생하지 않음

핵심 기능: 자동 태그 매핑 시스템

  1. 성능 최적화

    • 태그 및 키워드 캐싱 (애플리케이션 시작 시 1회)
    • DB 조회 최소화
  2. 스마트 매칭

    • 가중치 기반: 제목(3점) > 설명(1점)
    • 키워드 맵: 각 태그당 평균 9-10개 키워드
    • 유의어 포함
  3. 안정성

    • DB에 태그 없으면 기본 태그 자동 생성
    • null 안전 처리
    • 실패해도 시스템 정상 작동
  4. 정확성

    • 카테고리별 균형 (MOOD, STYLE, IMMERSION 각 1개)
    • 문맥 고려 (제목 키워드 우선)

키워드 예시

// MOOD (분위기)
"몽환적인" → ["꿈", "환상", "초현실", "신비", "마법", "비현실", "몽환", "판타지", "상상"]
"따뜻한" → ["사랑", "가족", "우정", "희망", "감동", "위로", "행복", "치유", "온기", "정"]
"긴장감있는" → ["스릴", "추리", "미스터리", "범죄", "사건", "긴장", "서스펜스", "공포", "전율", "살인", "수사", "탐정", "비밀"]

// STYLE (문체)
"담백한" → ["간결", "절제", "담담", "간단", "평이", "담백", "소박", "진솔"]
"서정적인" → ["시적", "감성", "서정", "운율", "아름다움", "서정적", "감성적", "시"]

// IMMERSION (몰입도)
"높은몰입감" → ["흥미진진", "빠른전개", "속도감", "긴장", "몰입", "박진감", "전개", "흥미", "재미", "쫄깃", "스릴"]
"사색적인" → ["철학", "사유", "성찰", "고민", "생각", "명상", "사색", "사고", "깊이", "통찰", "인생", "의미"]

동작 흐름

책 상세 조회
  ↓
태그 존재 확인
  ↓ (없으면)
제목 + 설명 텍스트 추출
  ↓
키워드 매칭 점수 계산
  ↓
MOOD 카테고리: 최고 점수 태그 선택
STYLE 카테고리: 최고 점수 태그 선택
IMMERSION 카테고리: 최고 점수 태그 선택
  ↓
DB에 3개 태그 저장
  ↓
AI 정보 생성 (태그 기반)
  ↓
응답 반환

실제 예시

: "살인자의 기억법"
설명: "치매에 걸린 연쇄살인마의 기억과 사건..."

분석 결과:

  • MOOD: "살인", "사건" 키워드 → "긴장감있는" (점수 5점)
  • STYLE: 키워드 미매칭 → "직설적인" (첫 번째 태그)
  • IMMERSION: "긴장" 키워드 → "높은몰입감" (점수 2점)

최종 태그: #긴장감있는, #직설적인, #높은몰입감


DB 저장 메커니즘

책 검색 시 자동 DB 저장

동작 방식

// BookSearchService.searchBooks()
1. DB에서 검색
2. 결과 없음3. bookImportService.searchAndUpsert() 호출4. 카카오 API 호출5. Books 엔티티 생성/업데이트6. booksRepository.save() // ✅ DB INSERT7. Authors, BookAuthors 매핑 저장8. DB에서 재검색하여 반환

저장되는 데이터

테이블 데이터 예시
books 책 기본 정보 제목, 설명, ISBN, 출판사, 썸네일 등
authors 저자/역자 로버트 C. 마틴, 박재호, 이해영
book_authors 책-저자 매핑 book_id=1, author_id=1, role=AUTHOR, display_order=1
book_tags 책-태그 매핑 book_id=1, tag_id=10 (자동 생성)
  • 캐싱 효과: 한 번 검색하면 DB에 영구 보관
  • 외부 API 호출 최소화
  • 중복 저장 방지 (ISBN13/URL 기반 upsert)

변경된 파일 목록

엔티티 (1개)

  1. Books.java
    • description: @Lob, LONGTEXT 적용
    • 4개 필드 추가: shortIntro, aiTasteComment, tasteAnalysis, tableOfContents
    • updateEnrichedInfo() 메서드 추가

DTO (3개)

  1. BookDetailResponse.java

    • 4개 필드 추가
    • 중첩 레코드 추가: AiTasteComment, TasteAnalysis, TasteDetail
  2. BookSearchItemResponse.java (library/books/dto)

    • tags 필드 추가
  3. BookSearchItemResponse.java (search/dto)

    • tags 필드 추가

컨버터 (2개)

  1. BookConverter.java

    • JSON 파싱 로직 추가: parseAiTasteComment(), parseTasteAnalysis(), parseTableOfContents()
    • ObjectMapper 의존성 주입
  2. BookSearchConverter.java

    • toResponse()tags 파라미터 추가

서비스 (4개)

  1. BookQueryServiceImpl.java

    • BookEnrichmentService, BookTagAutoMappingService 주입
    • 책 조회 시 태그 자동 매핑 → AI 정보 생성 순서로 실행
  2. BookEnrichmentService.java (신규)

    • AI 기반 책 정보 확장
    • shortIntro, aiTasteComment, tasteAnalysis, tableOfContents 생성
    • 태그 기반 분석
  3. BookTagAutoMappingService.java (신규)

    • 자동 태그 매핑 시스템
    • 태그/키워드 캐싱
    • 가중치 기반 스마트 매칭
    • 기본 태그 자동 생성
  4. BookSearchService.java

    • BookTagsRepository 주입
    • convertToSearchItem()에 태그 조회 로직 추가

레포지토리 (1개)

  1. BookTagsRepository.java
    • 패키지 위치 수정 (mapping → repository)

테스트 체크리스트

책 상세 조회 API

  • GET /api/v1/books/{bookId} 호출
  • shortIntro 필드 존재 확인
  • aiTasteComment 객체 구조 확인 (title, description)
  • tasteAnalysis 객체 구조 확인 (mood, style, immersion)
  • tableOfContents 배열 존재 확인
  • description 전체 텍스트 반환 (끊김 없음)
  • 태그가 없던 책에 자동으로 태그 매핑됨

도서 검색 API

  • GET /api/v1/search/books?query=클린코드 호출
  • items[].tags 필드 존재 확인
  • 태그가 # 접두사와 함께 반환
  • 태그 없는 책은 빈 배열 [] 반환

온보딩 추천 API

  • 토큰 없이 호출 시 401 Unauthorized
  • 유효한 토큰으로 호출 시 200 OK
  • userId를 쿼리 파라미터가 아닌 토큰에서 추출

DB 저장

  • 새 책 검색 시 books 테이블 INSERT
  • authors 테이블에 저자/역자 INSERT
  • book_authors 매핑 테이블 INSERT
  • 중복 검색 시 새 레코드 미생성 (중복 방지)

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions