-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
enhancementNew feature or requestNew feature or request
Description
- 책 상세정보 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테이블 활용 - 태그가 없으면 자동 매핑 (후술)
- 실제 DB의
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)
tags필드 추가
-
BookSearchItemResponse.java (search/dto)
tags필드 추가
컨버터 (2개)
-
BookConverter.java
- JSON 파싱 로직 추가:
parseAiTasteComment(),parseTasteAnalysis(),parseTableOfContents() ObjectMapper의존성 주입
- JSON 파싱 로직 추가:
-
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
-
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 - 중복 검색 시 새 레코드 미생성 (중복 방지)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request