- 책 상세정보 API 확장 (shortIntro, aiTasteComment, tasteAnalysis, tableOfContents)
- description 끊김 현상 해결
- 온보딩 기반 도서 추천 API 인증 적용 (이미 구현됨 확인)
- 도서 검색 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": []
}
구현 방식
-
DB 스키마 확장
Books 엔티티에 4개 필드 추가 (@Lob, TEXT/LONGTEXT)
- JPA
ddl-auto: update로 자동 마이그레이션
-
AI 정보 자동 생성
BookEnrichmentService: 책 조회 시 AI 정보가 없으면 자동 생성
shortIntro: description 첫 문장 추출
aiTasteComment: 태그 기반 JSON 생성
tasteAnalysis: MOOD/STYLE/IMMERSION 태그 기반 JSON 생성
tableOfContents: 빈 배열 (향후 확장 가능)
-
태그 기반 분석
- 실제 DB의
tags, book_tags 테이블 활용
- 태그가 없으면 자동 매핑 (후술)
description 끊김 현상 해결
문제 원인
- DB 컬럼 타입:
TEXT (최대 64KB) → 긴 설명 저장 시 잘림
해결 방법
-
엔티티 수정
@Lob
@Column(name = "contents", columnDefinition = "LONGTEXT")
private String description;
-
DB 자동 마이그레이션
ddl-auto: update 설정으로 자동 적용
TEXT → LONGTEXT (최대 4GB)
-
검증
- 외부 API에서 truncate 로직 없음 확인
- DTO 매핑에서 substring 없음 확인
- JSON 직렬화에서 제한 없음 확인
결과
- 모든 책의 전체 설명이 완전하게 저장 및 반환됨
- 기존 데이터 안전하게 유지
온보딩 기반 도서 추천 API 인증 적용
API: GET /api/v1/onboarding/recommendations
확인 결과
이미 인증 구현되어 있음
구현 상태
-
컨트롤러
@SecurityRequirement(name = "Bearer Authentication")
@GetMapping("/recommendations")
public ResponseEntity<...> getRecommendations(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Long userId = userDetails.getUserId(); // 토큰에서 추출
...
}
-
Security 설정
/api/v1/onboarding/** 경로는 allowUris에 없음 → 인증 필수
- JWT 필터 적용됨
- 인증 실패 시 401/403 자동 반환
-
테스트 시나리오
# 인증 없이 호출 → 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": ["#직설적인", "#높은몰입감", "#유쾌한"] // 추가
}
]
}
구현 방식
-
DTO 수정
BookSearchItemResponse에 tags 필드 추가
-
서비스 로직
// BookSearchService.convertToSearchItem()
List<String> tags = bookTagsRepository.findAllByBookId(book.getId())
.stream()
.map(BookTags::getTag)
.map(tag -> "#" + tag.getName())
.toList();
-
안전성
- 태그가 없으면 빈 배열
[] 반환
- 500 에러 발생하지 않음
핵심 기능: 자동 태그 매핑 시스템
-
성능 최적화
- 태그 및 키워드 캐싱 (애플리케이션 시작 시 1회)
- DB 조회 최소화
-
스마트 매칭
- 가중치 기반: 제목(3점) > 설명(1점)
- 키워드 맵: 각 태그당 평균 9-10개 키워드
- 유의어 포함
-
안정성
- DB에 태그 없으면 기본 태그 자동 생성
- null 안전 처리
- 실패해도 시스템 정상 작동
-
정확성
- 카테고리별 균형 (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 INSERT
↓
7. 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개)
- Books.java
description: @Lob, LONGTEXT 적용
- 4개 필드 추가:
shortIntro, aiTasteComment, tasteAnalysis, tableOfContents
updateEnrichedInfo() 메서드 추가
DTO (3개)
-
BookDetailResponse.java
- 4개 필드 추가
- 중첩 레코드 추가:
AiTasteComment, TasteAnalysis, TasteDetail
-
BookSearchItemResponse.java (library/books/dto)
-
BookSearchItemResponse.java (search/dto)
컨버터 (2개)
-
BookConverter.java
- JSON 파싱 로직 추가:
parseAiTasteComment(), parseTasteAnalysis(), parseTableOfContents()
ObjectMapper 의존성 주입
-
BookSearchConverter.java
toResponse()에 tags 파라미터 추가
서비스 (4개)
-
BookQueryServiceImpl.java
BookEnrichmentService, BookTagAutoMappingService 주입
- 책 조회 시 태그 자동 매핑 → AI 정보 생성 순서로 실행
-
BookEnrichmentService.java (신규)
- AI 기반 책 정보 확장
- shortIntro, aiTasteComment, tasteAnalysis, tableOfContents 생성
- 태그 기반 분석
-
BookTagAutoMappingService.java (신규)
- 자동 태그 매핑 시스템
- 태그/키워드 캐싱
- 가중치 기반 스마트 매칭
- 기본 태그 자동 생성
-
BookSearchService.java
BookTagsRepository 주입
convertToSearchItem()에 태그 조회 로직 추가
레포지토리 (1개)
- BookTagsRepository.java
- 패키지 위치 수정 (mapping → repository)
테스트 체크리스트
책 상세 조회 API
도서 검색 API
온보딩 추천 API
DB 저장
구현 내용
책 상세정보 API 확장
API:
GET /api/v1/books/{bookId}추가된 필드
shortIntroaiTasteComment{"title": "사유가 깊어지는 문장들", "description": "..."}tasteAnalysis{"mood": {...}, "style": {...}, "immersion": {...}}tableOfContents["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": [] }구현 방식
DB 스키마 확장
Books엔티티에 4개 필드 추가 (@Lob,TEXT/LONGTEXT)ddl-auto: update로 자동 마이그레이션AI 정보 자동 생성
BookEnrichmentService: 책 조회 시 AI 정보가 없으면 자동 생성shortIntro: description 첫 문장 추출aiTasteComment: 태그 기반 JSON 생성tasteAnalysis: MOOD/STYLE/IMMERSION 태그 기반 JSON 생성tableOfContents: 빈 배열 (향후 확장 가능)태그 기반 분석
tags,book_tags테이블 활용description 끊김 현상 해결
문제 원인
TEXT(최대 64KB) → 긴 설명 저장 시 잘림해결 방법
엔티티 수정
DB 자동 마이그레이션
ddl-auto: update설정으로 자동 적용TEXT→LONGTEXT(최대 4GB)검증
결과
온보딩 기반 도서 추천 API 인증 적용
API:
GET /api/v1/onboarding/recommendations확인 결과
이미 인증 구현되어 있음
구현 상태
컨트롤러
Security 설정
/api/v1/onboarding/**경로는allowUris에 없음 → 인증 필수테스트 시나리오
도서 검색 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": ["#직설적인", "#높은몰입감", "#유쾌한"] // 추가 } ] }구현 방식
DTO 수정
BookSearchItemResponse에tags필드 추가서비스 로직
안전성
[]반환핵심 기능: 자동 태그 매핑 시스템
성능 최적화
스마트 매칭
안정성
정확성
키워드 예시
동작 흐름
실제 예시
책: "살인자의 기억법"
설명: "치매에 걸린 연쇄살인마의 기억과 사건..."
분석 결과:
최종 태그:
#긴장감있는,#직설적인,#높은몰입감DB 저장 메커니즘
책 검색 시 자동 DB 저장
동작 방식
저장되는 데이터
booksauthorsbook_authorsbook_tags변경된 파일 목록
엔티티 (1개)
description:@Lob,LONGTEXT적용shortIntro,aiTasteComment,tasteAnalysis,tableOfContentsupdateEnrichedInfo()메서드 추가DTO (3개)
BookDetailResponse.java
AiTasteComment,TasteAnalysis,TasteDetailBookSearchItemResponse.java (library/books/dto)
tags필드 추가BookSearchItemResponse.java (search/dto)
tags필드 추가컨버터 (2개)
BookConverter.java
parseAiTasteComment(),parseTasteAnalysis(),parseTableOfContents()ObjectMapper의존성 주입BookSearchConverter.java
toResponse()에tags파라미터 추가서비스 (4개)
BookQueryServiceImpl.java
BookEnrichmentService,BookTagAutoMappingService주입BookEnrichmentService.java (신규)
BookTagAutoMappingService.java (신규)
BookSearchService.java
BookTagsRepository주입convertToSearchItem()에 태그 조회 로직 추가레포지토리 (1개)
테스트 체크리스트
책 상세 조회 API
GET /api/v1/books/{bookId}호출shortIntro필드 존재 확인aiTasteComment객체 구조 확인 (title, description)tasteAnalysis객체 구조 확인 (mood, style, immersion)tableOfContents배열 존재 확인도서 검색 API
GET /api/v1/search/books?query=클린코드호출items[].tags필드 존재 확인#접두사와 함께 반환[]반환온보딩 추천 API
DB 저장
books테이블 INSERTauthors테이블에 저자/역자 INSERTbook_authors매핑 테이블 INSERT