Skip to content

Commit 06cf88f

Browse files
committed
Merge upstream main into feat/be/57
2 parents 42e00d1 + 0713d8a commit 06cf88f

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

docs/README.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# 한국 여행 가이드 백엔드 (포트폴리오 요약)
2+
3+
여행자를 위한 AI 기반 맞춤 가이드를 목표로 한 백엔드 프로젝트입니다. Kotlin + Spring Boot를 중심으로 도메인 주도 설계(DDD), Spring AI, OAuth 인증, Redis 캐시, WebSocket 실시간 채팅을 결합해 MVP를 완성했습니다. 이 문서는 후보자 관점에서 시스템 전반을 빠르게 이해하도록 구성한 하이라이트 버전입니다.
4+
5+
---
6+
7+
## 1. Product Vision & User Journey
8+
- **타깃 사용자**: 한국 여행을 준비하는 게스트와 현지 가이드, 그리고 AI 여행 도우미를 통해 기본 안내를 받고 싶은 사용자.
9+
- **핵심 플로우**
10+
1. Google/Kakao/Naver OAuth → 최초 로그인 시 역할(게스트/가이드) 선택.
11+
2. AI 여행 챗봇에게 날씨/관광지 정보를 요청하거나 투어를 추천받음.
12+
3. 가이드-게스트 1:1 채팅방을 개설하고 WebSocket으로 대화.
13+
4. AI 세션 및 가이드에 대한 평가를 남겨 품질을 축적.
14+
- **UX 목표**: 실시간 현지 연결 + 신뢰할 수 있는 정보(공공 데이터 + 날씨 API) + 지속적인 개선을 위한 평가 데이터 확보.
15+
16+
---
17+
## 2. System Snapshot
18+
- **언어/런타임**: Kotlin 1.9.25, Java 21
19+
- **프레임워크**: Spring Boot 3.4.1, Spring Data JPA, Spring Security, Spring Web/WebFlux
20+
- **AI 스택**: Spring AI 1.1.0-M2, OpenRouter Chat Completions, JDBC ChatMemory(대화 50턴 보존)
21+
- **데이터 저장소**: PostgreSQL (prod) / H2 (dev), Redis (캐시 & 토큰 블랙리스트)
22+
- **인프라 구성**: Dev profile는 H2 + DevTools + 전체 허용 CORS, Prod profile는 JWT 필터 + OAuth2 로그인
23+
- **문서화/도구**: SpringDoc OpenAPI, ktlint, Actuator, BuildConfig(정적 데이터 코드 생성)
24+
25+
```text
26+
src/main/kotlin/com/back/koreaTravelGuide
27+
├── common/ # 공통 설정, 보안, 예외, 로깅
28+
├── domain/
29+
│ ├── auth/ # OAuth, JWT, 역할 선택, 토큰 재발급
30+
│ ├── user/ # 프로필 CRUD, 역할 관리
31+
│ ├── ai/
32+
│ │ ├── aiChat/ # Spring AI + 도구 + 세션/메시지 저장
33+
│ │ ├── tour/ # 한국관광공사 TourAPI 연동 + 캐시
34+
│ │ └── weather/ # 기상청 중기예보 + 스케줄 캐시 갱신
35+
│ ├── userChat/ # 게스트-가이드 WebSocket 채팅 & REST
36+
│ └── rate/ # 가이드/AI 평가 및 통계
37+
└── resources/
38+
├── application*.yml
39+
├── prompts.yml, area-codes.yml, region-codes.yml
40+
└── org/springframework/ai/chat/memory/... (JDBC 스키마)
41+
```
42+
43+
---
44+
## 3. Core Domains & What They Deliver
45+
46+
### 3.1 Auth & Identity
47+
- Google/Kakao/Naver OAuth2 로그인 → `CustomOAuth2UserService`가 공급자별 프로필을 통일.
48+
- 최초 로그인 사용자는 `ROLE_PENDING``/api/auth/role`에서 게스트/가이드 선택 후 Access Token 발급.
49+
- Refresh Token은 **HttpOnly Secure Cookie + Redis 저장**으로 관리, `AuthService.logout()`은 Access Token을 블랙리스트에 등록.
50+
- Dev profile은 H2 + 토큰 필터 비활성화로 프런트 개발 속도 확보, Prod profile은 JWT 필터·OAuth 성공 핸들러·세션 stateless 모드 적용.
51+
52+
### 3.2 AI Travel Assistant (`domain.ai.aiChat`)
53+
- Spring AI `ChatClient` + JDBC ChatMemory → 세션별 50턴 대화 히스토리 유지, 재접속 시 맥락 이어받기.
54+
- `TourTool`, `WeatherTool`을 기본 Tool로 주입해 LLM이 공공 데이터 API를 직접 호출.
55+
- 첫 사용자 메시지 이후 `aiUpdateSessionTitle()`이 자동 요약 제목 생성, 오류 시 `BuildConfig.AI_ERROR_FALLBACK`으로 graceful degrade.
56+
- 메시지 저장소는 `AiChatMessageRepository` (JPA)로 구성, 세션 생성/삭제/메시지 조회 API 지원.
57+
58+
### 3.3 Public Data Integrations
59+
- **Tour API**: 한국관광공사 OpenAPI 호출 (`TourApiClient`), 주요 API 3종(areaBased, locationBased, detailCommon)을 지원하고 `@Cacheable` + Redis Serializer로 응답 캐시.
60+
- **Weather API**: 기상청 중기예보/기온/강수 데이터를 RestTemplate 기반으로 호출, DTO 파서로 정제, 12시간 TTL 캐시 및 `@Scheduled` 캐시 무효화.
61+
- BuildConfig 플러그인이 `area-codes.yml`, `region-codes.yml`, `prompts.yml` 내용을 상수로 노출해 Tool 설명에 바로 활용 가능.
62+
63+
### 3.4 Guest ↔ Guide Chat (`domain.userChat`)
64+
- REST + STOMP WebSocket 하이브리드 구조. `/api/userchat/rooms`로 채팅방 CRUD, `/ws/userchat` 엔드포인트로 실시간 메시지 전달.
65+
- STOMP CONNECT 단계에서 `UserChatStompAuthChannelInterceptor`가 JWT를 검증하고 `Principal`을 주입 → 명시적 인증 강제.
66+
- 메시지 API는 커서 기반 페이징(최신/after)과 STOMP 브로드캐스트를 모두 제공, 채팅방 마지막 메시지 시각을 업데이트해 리스트 정렬.
67+
68+
### 3.5 Rating & Reputation (`domain.rate`)
69+
- 게스트는 가이드를, 사용자 본인은 AI 세션을 평가. `RateService`가 중복 평가 시 수정(Update), 최초면 Insert.
70+
- 가이드 전용 대시보드 API(`/api/rate/guides/my`)는 평균/총 건수/리스트를 묶어서 반환. 관리자용 API는 AI 세션 평가 전체 조회.
71+
72+
### 3.6 User Profiles
73+
- `/api/users/me`에서 닉네임·프로필 이미지 업데이트 지원.
74+
- 삭제 시 연관 데이터 정리는 JPA cascade로 처리, NoSuchElementException/IllegalStateException을 `GlobalExceptionHandler`가 표준 응답으로 래핑.
75+
76+
---
77+
## 4. Architecture & Infrastructure Notes
78+
- **DDD 패키지 구성**: 도메인 수준의 `entity/repository/service/controller/dto` 분리 + 공통 계층(`common/*`)으로 횡단 관심사 관리.
79+
- **Persistence**: 표준 JPA + Kotlin data class, 세션/메시지/평가 엔티티는 soft constraint를 service 계층에서 검증.
80+
- **Caching 전략**
81+
- Redis 캐시 5종 (투어 2, 투어 상세 1, 날씨 2) → Serializer를 DTO별로 분리해 타입 안정성 확보.
82+
- Weather cache는 12시간마다 `@Scheduled`로 비움, Tour cache는 TTL 12시간.
83+
- **Token 관리는 Redis**: Refresh Token은 `refreshToken:{userId}` 키로 저장, Access Token은 로그아웃 시 value=logout으로 블랙리스트 처리.
84+
- **WebSocket 보안**: CONNECT 프레임에서 Authorization 헤더 필수, 실패 시 `AuthenticationCredentialsNotFoundException` 던짐.
85+
- **빌드 파이프라인**: `com.github.gmazzo.buildconfig`로 정적 YAML → Kotlin 상수 생성, ktlint로 브랜치 진입 전 스타일 체크.
86+
- **Dev Experience**: `DevConfig`가 서버 부팅 시 Swagger/H2/Actuator URL, 필수 환경변수 상태를 콘솔에 안내.
87+
88+
---
89+
## 5. API Surface (대표 엔드포인트)
90+
| 도메인 | HTTP | 경로 | 설명 |
91+
|--------|------|------|------|
92+
| Auth | `POST` | `/api/auth/role` | 최초 로그인 사용자의 역할 선택 + Access Token 발급 |
93+
| Auth | `POST` | `/api/auth/refresh` | Refresh Cookie 기반 Access Token 재발급 |
94+
| User | `GET` | `/api/users/me` | 내 프로필 조회/수정/탈퇴 |
95+
| AI Chat | `POST` | `/api/aichat/sessions/{id}/messages` | 사용자 메시지 저장 + Spring AI 응답 생성 |
96+
| Tour | `POST` | `/api/aichat/sessions` | AI 채팅방 생성 (초기 제목 자동 생성) |
97+
| UserChat | `POST` | `/api/userchat/rooms/start` | 게스트-가이드 1:1 채팅방 생성 (중복 시 기존 방 재사용) |
98+
| Rate | `PUT` | `/api/rate/guides/{id}` | 가이드 평가 생성/수정 |
99+
100+
> 전체 스펙은 `docs/api-specification.yaml`과 Swagger UI (`/swagger-ui.html`)에서 확인할 수 있습니다.
101+
102+
---
103+
## 6. Local Setup & Developer Workflow
104+
1. `.env.example` 복사 후 OpenRouter/Weather/Tour API 키, OAuth 클라이언트 ID를 채움.
105+
2. (선택) `docker run -d -p 6379:6379 redis:alpine`으로 Redis 실행.
106+
3. `./setup-git-hooks.sh` 또는 `setup-git-hooks.bat`로 ktlint 프리훅 설치.
107+
4. `./gradlew bootRun` → Dev profile이 기본, H2 + Swagger + STOMP endpoint가 즉시 활성화.
108+
5. `./gradlew ktlintCheck` / `ktlintFormat`으로 스타일 점검, `./gradlew build`로 통합 빌드.
109+
6. Prod profile 배포 시 `SPRING_PROFILES_ACTIVE=prod` 설정 → JWT 필터 활성화, session stateless, OAuth 로그인 성공 시 refresh 쿠키 발급.
110+
111+
## 7. Observability & Quality
112+
- `logging.level.com.back=DEBUG`, Hibernate SQL/바인딩 로그까지 노출해 API 호출→DB 쿼리 흐름 디버깅.
113+
- `Actuator` 기본 엔드포인트(health/info/metrics/env/beans)를 노출해 인프라 상태 확인.
114+
- `DevConfig` 콘솔 배너로 개발 URL/환경변수/Redis 지침 안내.
115+
- 향후 과제: 통합 테스트 케이스 보강, 메트릭 기반 알림, Redis 캐시 히트율 모니터링.
116+
117+
---
118+
## 8. Next Steps & Opportunities
119+
- **AI 경험 고도화**: 여행 추천 결과를 세션 메시지에 요약/카테고리화, 사용자 행동 기반 프롬프트 튜닝.
120+
- **데이터 품질**: 관광/날씨 API 장애 대비 Circuit Breaker + Failover 데이터소스 도입, 캐시 미스 모니터링.
121+
- **채팅 UX**: 메시지 영구 삭제/복원, 타이핑 인디케이터, 읽음 처리.
122+
- **운영 편의**: Admin 전용 대시보드(평가/세션 로그), Redis 클러스터 환경 검증, Kubernetes 헬스체크 스크립트 추가.
123+
124+
## 9. Reference Docs
125+
- [프로젝트 구조](project-structure.md)
126+
- [ERD](erd-diagram.md)
127+
- [개발 규칙](DEVELOPMENT_RULES.md)
128+
- [Redis 가이드](REDIS_GUIDE.md)
129+
- [API 스펙](api-specification.yaml)
130+
131+
---
132+
133+
> 문의 및 협업 제안: `[email protected]`

src/main/kotlin/com/back/koreaTravelGuide/domain/user/service/GuideService.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ class GuideService(
5555
// String -> Region enum 변환 추가
5656
@Transactional(readOnly = true)
5757
fun findGuidesByRegion(region: String): List<GuideResponse> {
58-
val regionEnum = Region.valueOf(region.uppercase())
58+
// String을 Region enum으로 변환 (한글 displayName 또는 영문 enum name 둘 다 지원)
59+
val regionEnum =
60+
Region.entries.find {
61+
it.displayName.equals(region, ignoreCase = true) ||
62+
it.name.equals(region, ignoreCase = true)
63+
} ?: return emptyList()
64+
5965
val guides = userRepository.findByRoleAndLocation(UserRole.GUIDE, regionEnum)
6066
return guides.map { GuideResponse.from(it) }
6167
}

src/main/resources/prompts.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ prompts:
3535
- 조회된 관광정보를 사용자에게 친근하게 추천하세요.
3636
- 추천할 때는 장소 이름, 주소, 특징을 포함하여 3~5개 정도 제시하세요.
3737
- 이미지가 있는 경우(firstimage 필드), 반드시 마크다운 형식으로 포함하세요: ![장소의 title](firstimage URL)
38+
- 관광정보를 제공한 후, 해당 지역에서 활동하는 가이드를 자연스럽게 제안하세요.
39+
- 예: "이 지역에서 활동하는 여행 가이드를 찾아드릴까요?", "현지 가이드와 함께하면 더 깊이 있는 여행이 가능해요. 가이드 정보를 알아볼까요?"
40+
41+
4-1단계: 지역 가이드 검색
42+
- 사용자가 가이드 정보를 요청하면 findGuidesByRegion(region)을 사용하세요.
43+
- region 파라미터에는 사용자가 이전에 조회한 지역명(예: '서울', '부산', '강남구')을 사용하세요.
44+
- 검색된 가이드 목록을 친근하게 소개하세요.
45+
- 각 가이드의 이름, 활동 지역, 전문 분야 등을 포함하여 제시하세요.
46+
- 가이드가 없는 경우, "죄송합니다. 해당 지역에서 활동하는 가이드를 찾을 수 없네요. 다른 지역을 추천해드릴까요?" 라고 안내하세요.
3847
3948
5단계: 위치 기반 주변 검색 (특정 장소 주변)
4049
- 사용자가 이전에 조회한 장소 주변의 다른 정보를 요청하면 getLocationBasedTourInfo()를 사용하세요.
@@ -52,6 +61,7 @@ prompts:
5261
- 특정 지역 날씨 요청 → getRegionalWeatherDetails(location)를 바로 사용하되, REGION_CODES_DESCRIPTION에서 해당 지역 코드를 찾아 사용
5362
- 특정 구/군 관광정보 요청 → getAreaBasedTourInfo(contentTypeId, areaAndSigunguCode)를 바로 사용하되,
5463
CONTENT_TYPE_CODES_DESCRIPTION에서 타입 코드를 찾고, AREA_CODES_DESCRIPTION에서 지역 코드를 찾아 하이픈을 쉼표로 변환
64+
- 특정 지역 가이드 요청 → findGuidesByRegion(region)을 바로 사용하여 해당 지역의 가이드 목록 제공
5565
- 특정 장소 주변 검색 → 먼저 getAreaBasedTourInfo()로 해당 장소를 찾아 mapX, mapY를 얻은 후 getLocationBasedTourInfo() 사용
5666
- 특정 장소 상세 정보 요청 → 먼저 getAreaBasedTourInfo()로 검색 후 contentId를 얻어 getTourDetailInfo() 사용
5767

0 commit comments

Comments
 (0)