mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 12:06:24 +00:00
- SecurityConfig CORS 설정 제거 및 단순화 - 모든 요청 허용으로 변경 (내부 API 특성 반영) - DevTools 요청 정적 리소스 제외 처리 - AI Service 워크플로우 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
391 lines
13 KiB
Markdown
391 lines
13 KiB
Markdown
# AI Service 전체 메서드 워크플로우
|
|
|
|
## 1. AI 추천 생성 워크플로우 (Kafka 기반 비동기)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant ES as Event Service
|
|
participant Kafka as Kafka Topic
|
|
participant Consumer as AIJobConsumer
|
|
participant ARS as AIRecommendationService
|
|
participant JSS as JobStatusService
|
|
participant TAS as TrendAnalysisService
|
|
participant CS as CacheService
|
|
participant CAC as ClaudeApiClient
|
|
participant Claude as Claude API
|
|
participant Redis as Redis
|
|
|
|
%% 1. Event Service가 Kafka 메시지 발행
|
|
ES->>Kafka: Publish AIJobMessage<br/>(ai-event-generation-job topic)
|
|
|
|
%% 2. Kafka Consumer가 메시지 수신
|
|
Kafka->>Consumer: consume(AIJobMessage)
|
|
Note over Consumer: @KafkaListener<br/>groupId: ai-service-consumers
|
|
|
|
%% 3. AI 추천 생성 시작
|
|
Consumer->>ARS: generateRecommendations(message)
|
|
activate ARS
|
|
|
|
%% 4. Job 상태: PROCESSING (10%)
|
|
ARS->>JSS: updateJobStatus(jobId, PROCESSING, "트렌드 분석 중")
|
|
JSS->>CS: saveJobStatus(jobId, status)
|
|
CS->>Redis: SET ai:job:status:{jobId}
|
|
|
|
%% 5. 트렌드 분석
|
|
ARS->>ARS: analyzeTrend(message)
|
|
ARS->>CS: getTrend(industry, region)
|
|
CS->>Redis: GET ai:trend:{industry}:{region}
|
|
|
|
alt 캐시 HIT
|
|
Redis-->>CS: TrendAnalysis (cached)
|
|
CS-->>ARS: TrendAnalysis
|
|
else 캐시 MISS
|
|
ARS->>TAS: analyzeTrend(industry, region)
|
|
activate TAS
|
|
|
|
%% Circuit Breaker 적용
|
|
TAS->>TAS: circuitBreakerManager.executeWithCircuitBreaker()
|
|
TAS->>CAC: sendMessage(apiKey, version, request)
|
|
CAC->>Claude: POST /v1/messages
|
|
Note over Claude: Model: claude-sonnet-4-5<br/>System: "트렌드 분석 전문가"<br/>Prompt: 업종/지역/계절 트렌드
|
|
Claude-->>CAC: ClaudeResponse
|
|
CAC-->>TAS: ClaudeResponse
|
|
|
|
TAS->>TAS: parseResponse(responseText)
|
|
TAS-->>ARS: TrendAnalysis
|
|
deactivate TAS
|
|
|
|
%% 트렌드 캐싱
|
|
ARS->>CS: saveTrend(industry, region, analysis)
|
|
CS->>Redis: SET ai:trend:{industry}:{region} (TTL: 1시간)
|
|
end
|
|
|
|
%% 6. Job 상태: PROCESSING (50%)
|
|
ARS->>JSS: updateJobStatus(jobId, PROCESSING, "이벤트 추천안 생성 중")
|
|
JSS->>CS: saveJobStatus(jobId, status)
|
|
CS->>Redis: SET ai:job:status:{jobId}
|
|
|
|
%% 7. 이벤트 추천안 생성
|
|
ARS->>ARS: createRecommendations(message, trendAnalysis)
|
|
ARS->>ARS: circuitBreakerManager.executeWithCircuitBreaker()
|
|
ARS->>CAC: sendMessage(apiKey, version, request)
|
|
CAC->>Claude: POST /v1/messages
|
|
Note over Claude: Model: claude-sonnet-4-5<br/>System: "이벤트 기획 전문가"<br/>Prompt: 3가지 추천안 생성
|
|
Claude-->>CAC: ClaudeResponse
|
|
CAC-->>ARS: ClaudeResponse
|
|
|
|
ARS->>ARS: parseRecommendationResponse(responseText)
|
|
|
|
%% 8. Job 상태: PROCESSING (90%)
|
|
ARS->>JSS: updateJobStatus(jobId, PROCESSING, "결과 저장 중")
|
|
JSS->>CS: saveJobStatus(jobId, status)
|
|
CS->>Redis: SET ai:job:status:{jobId}
|
|
|
|
%% 9. 결과 저장
|
|
ARS->>CS: saveRecommendation(eventId, result)
|
|
CS->>Redis: SET ai:recommendation:{eventId} (TTL: 24시간)
|
|
|
|
%% 10. Job 상태: COMPLETED (100%)
|
|
ARS->>JSS: updateJobStatus(jobId, COMPLETED, "AI 추천 완료")
|
|
JSS->>CS: saveJobStatus(jobId, status)
|
|
CS->>Redis: SET ai:job:status:{jobId}
|
|
|
|
deactivate ARS
|
|
|
|
%% 11. Kafka ACK
|
|
Consumer->>Kafka: acknowledgment.acknowledge()
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Job 상태 조회 워크플로우 (동기)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant ES as Event Service
|
|
participant Controller as InternalJobController
|
|
participant JSS as JobStatusService
|
|
participant CS as CacheService
|
|
participant Redis as Redis
|
|
|
|
%% 1. Event Service가 Job 상태 조회
|
|
ES->>Controller: GET /api/v1/ai-service/internal/jobs/{jobId}/status
|
|
|
|
%% 2. Job 상태 조회
|
|
Controller->>JSS: getJobStatus(jobId)
|
|
activate JSS
|
|
|
|
JSS->>CS: getJobStatus(jobId)
|
|
CS->>Redis: GET ai:job:status:{jobId}
|
|
|
|
alt 상태 존재
|
|
Redis-->>CS: JobStatusResponse
|
|
CS-->>JSS: Object (JobStatusResponse)
|
|
JSS->>JSS: objectMapper.convertValue()
|
|
JSS-->>Controller: JobStatusResponse
|
|
Controller-->>ES: 200 OK + JobStatusResponse
|
|
else 상태 없음
|
|
Redis-->>CS: null
|
|
CS-->>JSS: null
|
|
JSS-->>Controller: JobNotFoundException
|
|
Controller-->>ES: 404 Not Found
|
|
end
|
|
|
|
deactivate JSS
|
|
```
|
|
|
|
---
|
|
|
|
## 3. AI 추천 결과 조회 워크플로우 (동기)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant ES as Event Service
|
|
participant Controller as InternalRecommendationController
|
|
participant ARS as AIRecommendationService
|
|
participant CS as CacheService
|
|
participant Redis as Redis
|
|
|
|
%% 1. Event Service가 AI 추천 결과 조회
|
|
ES->>Controller: GET /api/v1/ai-service/internal/recommendations/{eventId}
|
|
|
|
%% 2. 추천 결과 조회
|
|
Controller->>ARS: getRecommendation(eventId)
|
|
activate ARS
|
|
|
|
ARS->>CS: getRecommendation(eventId)
|
|
CS->>Redis: GET ai:recommendation:{eventId}
|
|
|
|
alt 결과 존재
|
|
Redis-->>CS: AIRecommendationResult
|
|
CS-->>ARS: Object (AIRecommendationResult)
|
|
ARS->>ARS: objectMapper.convertValue()
|
|
ARS-->>Controller: AIRecommendationResult
|
|
Controller-->>ES: 200 OK + AIRecommendationResult
|
|
else 결과 없음
|
|
Redis-->>CS: null
|
|
CS-->>ARS: null
|
|
ARS-->>Controller: RecommendationNotFoundException
|
|
Controller-->>ES: 404 Not Found
|
|
end
|
|
|
|
deactivate ARS
|
|
```
|
|
|
|
---
|
|
|
|
## 4. 헬스체크 워크플로우 (동기)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client as Client/Actuator
|
|
participant Controller as HealthController
|
|
participant Redis as Redis
|
|
|
|
%% 1. 헬스체크 요청
|
|
Client->>Controller: GET /api/v1/ai-service/health
|
|
|
|
%% 2. Redis 상태 확인
|
|
Controller->>Controller: checkRedis()
|
|
|
|
alt RedisTemplate 존재
|
|
Controller->>Redis: PING
|
|
alt Redis 정상
|
|
Redis-->>Controller: PONG
|
|
Controller->>Controller: redisStatus = UP
|
|
else Redis 오류
|
|
Redis-->>Controller: Exception
|
|
Controller->>Controller: redisStatus = DOWN
|
|
end
|
|
else RedisTemplate 없음
|
|
Controller->>Controller: redisStatus = UNKNOWN
|
|
end
|
|
|
|
%% 3. 전체 상태 판단
|
|
alt Redis DOWN
|
|
Controller->>Controller: overallStatus = DEGRADED
|
|
else Redis UP/UNKNOWN
|
|
Controller->>Controller: overallStatus = UP
|
|
end
|
|
|
|
%% 4. 응답
|
|
Controller-->>Client: 200 OK + HealthCheckResponse
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 주요 컴포넌트 메서드 목록
|
|
|
|
### 5.1 Controller Layer
|
|
|
|
#### InternalJobController
|
|
| 메서드 | HTTP | 엔드포인트 | 설명 |
|
|
|--------|------|-----------|------|
|
|
| `getJobStatus(jobId)` | GET | `/api/v1/ai-service/internal/jobs/{jobId}/status` | Job 상태 조회 |
|
|
| `createTestJob(jobId)` | GET | `/api/v1/ai-service/internal/jobs/debug/create-test-job/{jobId}` | 테스트 Job 생성 (디버그) |
|
|
|
|
#### InternalRecommendationController
|
|
| 메서드 | HTTP | 엔드포인트 | 설명 |
|
|
|--------|------|-----------|------|
|
|
| `getRecommendation(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/{eventId}` | AI 추천 결과 조회 |
|
|
| `debugRedisKeys()` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-keys` | Redis 모든 키 조회 |
|
|
| `debugRedisKey(key)` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-key/{key}` | Redis 특정 키 조회 |
|
|
| `searchAllDatabases()` | GET | `/api/v1/ai-service/internal/recommendations/debug/search-all-databases` | 전체 DB 검색 |
|
|
| `createTestData(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/debug/create-test-data/{eventId}` | 테스트 데이터 생성 |
|
|
|
|
#### HealthController
|
|
| 메서드 | HTTP | 엔드포인트 | 설명 |
|
|
|--------|------|-----------|------|
|
|
| `healthCheck()` | GET | `/api/v1/ai-service/health` | 서비스 헬스체크 |
|
|
| `checkRedis()` | - | (내부) | Redis 연결 확인 |
|
|
|
|
---
|
|
|
|
### 5.2 Service Layer
|
|
|
|
#### AIRecommendationService
|
|
| 메서드 | 호출자 | 설명 |
|
|
|--------|-------|------|
|
|
| `getRecommendation(eventId)` | Controller | Redis에서 추천 결과 조회 |
|
|
| `generateRecommendations(message)` | AIJobConsumer | AI 추천 생성 (전체 프로세스) |
|
|
| `analyzeTrend(message)` | 내부 | 트렌드 분석 (캐시 확인 포함) |
|
|
| `createRecommendations(message, trendAnalysis)` | 내부 | 이벤트 추천안 생성 |
|
|
| `callClaudeApiForRecommendations(message, trendAnalysis)` | 내부 | Claude API 호출 (추천안) |
|
|
| `buildRecommendationPrompt(message, trendAnalysis)` | 내부 | 추천안 프롬프트 생성 |
|
|
| `parseRecommendationResponse(responseText)` | 내부 | 추천안 응답 파싱 |
|
|
| `parseEventRecommendation(node)` | 내부 | EventRecommendation 파싱 |
|
|
| `parseRange(node)` | 내부 | Range 객체 파싱 |
|
|
| `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 |
|
|
|
|
#### TrendAnalysisService
|
|
| 메서드 | 호출자 | 설명 |
|
|
|--------|-------|------|
|
|
| `analyzeTrend(industry, region)` | AIRecommendationService | 트렌드 분석 수행 |
|
|
| `callClaudeApi(industry, region)` | 내부 | Claude API 호출 (트렌드) |
|
|
| `buildPrompt(industry, region)` | 내부 | 트렌드 분석 프롬프트 생성 |
|
|
| `parseResponse(responseText)` | 내부 | 트렌드 응답 파싱 |
|
|
| `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 |
|
|
| `parseTrendKeywords(arrayNode)` | 내부 | TrendKeyword 리스트 파싱 |
|
|
|
|
#### JobStatusService
|
|
| 메서드 | 호출자 | 설명 |
|
|
|--------|-------|------|
|
|
| `getJobStatus(jobId)` | Controller | Job 상태 조회 |
|
|
| `updateJobStatus(jobId, status, message)` | AIRecommendationService | Job 상태 업데이트 |
|
|
| `calculateProgress(status)` | 내부 | 상태별 진행률 계산 |
|
|
|
|
#### CacheService
|
|
| 메서드 | 호출자 | 설명 |
|
|
|--------|-------|------|
|
|
| `set(key, value, ttlSeconds)` | 내부 | 범용 캐시 저장 |
|
|
| `get(key)` | 내부 | 범용 캐시 조회 |
|
|
| `delete(key)` | 외부 | 캐시 삭제 |
|
|
| `saveJobStatus(jobId, status)` | JobStatusService | Job 상태 저장 |
|
|
| `getJobStatus(jobId)` | JobStatusService | Job 상태 조회 |
|
|
| `saveRecommendation(eventId, recommendation)` | AIRecommendationService | AI 추천 결과 저장 |
|
|
| `getRecommendation(eventId)` | AIRecommendationService | AI 추천 결과 조회 |
|
|
| `saveTrend(industry, region, trend)` | AIRecommendationService | 트렌드 분석 결과 저장 |
|
|
| `getTrend(industry, region)` | AIRecommendationService | 트렌드 분석 결과 조회 |
|
|
|
|
---
|
|
|
|
### 5.3 Consumer Layer
|
|
|
|
#### AIJobConsumer
|
|
| 메서드 | 트리거 | 설명 |
|
|
|--------|-------|------|
|
|
| `consume(message, topic, offset, ack)` | Kafka Message | Kafka 메시지 수신 및 처리 |
|
|
|
|
---
|
|
|
|
### 5.4 Client Layer
|
|
|
|
#### ClaudeApiClient (Feign)
|
|
| 메서드 | 호출자 | 설명 |
|
|
|--------|-------|------|
|
|
| `sendMessage(apiKey, anthropicVersion, request)` | TrendAnalysisService, AIRecommendationService | Claude API 호출 |
|
|
|
|
---
|
|
|
|
## 6. Redis 캐시 키 구조
|
|
|
|
| 키 패턴 | 설명 | TTL |
|
|
|--------|------|-----|
|
|
| `ai:job:status:{jobId}` | Job 상태 정보 | 24시간 (86400초) |
|
|
| `ai:recommendation:{eventId}` | AI 추천 결과 | 24시간 (86400초) |
|
|
| `ai:trend:{industry}:{region}` | 트렌드 분석 결과 | 1시간 (3600초) |
|
|
|
|
---
|
|
|
|
## 7. Claude API 호출 정보
|
|
|
|
### 7.1 트렌드 분석
|
|
- **URL**: `https://api.anthropic.com/v1/messages`
|
|
- **Model**: `claude-sonnet-4-5-20250929`
|
|
- **Max Tokens**: 4096
|
|
- **Temperature**: 0.7
|
|
- **System Prompt**: "당신은 마케팅 트렌드 분석 전문가입니다. 업종별, 지역별 트렌드를 분석하고 인사이트를 제공합니다."
|
|
- **응답 형식**: JSON (industryTrends, regionalTrends, seasonalTrends)
|
|
|
|
### 7.2 이벤트 추천안 생성
|
|
- **URL**: `https://api.anthropic.com/v1/messages`
|
|
- **Model**: `claude-sonnet-4-5-20250929`
|
|
- **Max Tokens**: 4096
|
|
- **Temperature**: 0.7
|
|
- **System Prompt**: "당신은 소상공인을 위한 마케팅 이벤트 기획 전문가입니다. 트렌드 분석을 바탕으로 실행 가능한 이벤트 추천안을 제공합니다."
|
|
- **응답 형식**: JSON (recommendations: 3가지 옵션)
|
|
|
|
---
|
|
|
|
## 8. Circuit Breaker 설정
|
|
|
|
### 적용 대상
|
|
- `claudeApi`: 모든 Claude API 호출
|
|
|
|
### 설정값
|
|
```yaml
|
|
failure-rate-threshold: 50%
|
|
slow-call-duration-threshold: 60초
|
|
sliding-window-size: 10
|
|
minimum-number-of-calls: 5
|
|
wait-duration-in-open-state: 60초
|
|
timeout-duration: 300초 (5분)
|
|
```
|
|
|
|
### Fallback 메서드
|
|
- `AIServiceFallback.getDefaultTrendAnalysis()`: 기본 트렌드 분석
|
|
- `AIServiceFallback.getDefaultRecommendations()`: 기본 추천안
|
|
|
|
---
|
|
|
|
## 9. 에러 처리
|
|
|
|
### Exception 종류
|
|
| Exception | HTTP Code | 발생 조건 |
|
|
|-----------|-----------|---------|
|
|
| `RecommendationNotFoundException` | 404 | Redis에 추천 결과 없음 |
|
|
| `JobNotFoundException` | 404 | Redis에 Job 상태 없음 |
|
|
| `AIServiceException` | 500 | AI 서비스 내부 오류 |
|
|
|
|
### 에러 응답 예시
|
|
```json
|
|
{
|
|
"timestamp": "2025-10-30T15:30:00",
|
|
"status": 404,
|
|
"error": "Not Found",
|
|
"message": "추천 결과를 찾을 수 없습니다: eventId=evt-123",
|
|
"path": "/api/v1/ai-service/internal/recommendations/evt-123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. 로깅 레벨
|
|
|
|
```yaml
|
|
com.kt.ai: DEBUG
|
|
org.springframework.kafka: INFO
|
|
org.springframework.data.redis: INFO
|
|
io.github.resilience4j: DEBUG
|
|
```
|