Skip to content

Link-Trip/BackEnd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LinkTrip Backend

유튜브 여행 영상을 AI로 분석하여 일정표를 자동 생성하고, 인기 여행 영상/크리에이터를 수집하여 제공하는 플랫폼입니다.

Tech Stack

분류 기술
Language Kotlin 1.9.25, JDK 21
Framework Spring Boot 3.4, Spring Batch, Spring Security
ORM / Query JPA, QueryDSL
Database MySQL
Cache Caffeine Cache
Infra AWS EC2, Docker
External API YouTube Data v3, Gemini 2.5 Flash, Google Places API, Discord Webhook

모듈 구조

linktrip-bootstrap                 ← Spring Boot 진입점, DI 조립
│
├── linktrip-input-http            ← REST API Controller
├── linktrip-input-batch           ← Spring Batch Job & Scheduler
│
├── linktrip-application           ← 핵심 비즈니스 로직 (Port & Service)
│   ├── domain/                    ← 도메인 서비스
│   ├── port/input/                ← UseCase 인터페이스
│   └── port/output/               ← Output Port 인터페이스
│
├── linktrip-output-http           ← 외부 API 어댑터 (YouTube, Gemini, Places, Discord)
├── linktrip-output-cache          ← Caffeine 캐시 데코레이터 어댑터
├── linktrip-output-persistence    ← MySQL JPA + QueryDSL 어댑터
└── linktrip-common                ← 공통 예외, 이벤트, 설정

아키텍처

의존성 방향

                         ┌──────────────────────┐
                         │      bootstrap       │
                         │  (모든 모듈 참조,      │
                         │   DI 조립 전용)       │
                         └──────────┬───────────┘
                                    │
         ┌──────────────────────────┼──────────────────────────┐
         ▼                          ▼                          ▼
 ┌──────────────┐          ┌──────────────┐          ┌──────────────┐
 │  input-http  │          │ input-batch  │          │    common    │
 └──────┬───────┘          └──────┬───────┘          └──────────────┘
        │                         │
        │  UseCase Port           │  UseCase Port
        ▼                         ▼
 ┌─────────────────────────────────────────────────────────────┐
 │                       application                           │
 │                                                             │
 │  Domain Service ──→ Output Port Interface                   │
 │                                                             │
 └─────────────────────────────┬───────────────────────────────┘
                               │
              ┌────────────────┼────────────────┐
              ▲                ▲                ▲
     ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
     │ output-cache │ │  output-     │ │  output-http │
     │  (Caffeine)  │ │  persistence │ │  (YouTube,   │
     │              │ │  (MySQL/JPA) │ │   Gemini,    │
     │  @Primary    │ │  @Qualifier  │ │   Places,    │
     └──────┬───────┘ └──────────────┘ │   Discord)   │
            │               ↑          └──────────────┘
            │   delegate    │
            └───────────────┘
             Port 인터페이스 타입으로 위임

모든 모듈은 application의 Port 인터페이스에만 의존합니다. output-cacheoutput-persistence는 서로의 존재를 모릅니다.

캐시-영속성 분리 (데코레이터 패턴)

Application (Port 인터페이스 정의)
      ↑                    ↑
      │                    │
output-cache            output-persistence
(@Primary)              (@Qualifier)
CachingAdapter          PersistenceAdapter
      │                    ↑
      └── delegate ────────┘
  • output-cache는 동일한 Port 인터페이스를 구현하며, DB 어댑터를 delegate로 래핑
  • @Primary로 캐시 어댑터가 우선 주입, 내부에서 @Qualifier로 DB 어댑터 참조
  • 캐시 제거/교체 시 application 코드 수정 불필요

캐싱 전략

Caffeine Cache (TTL 7시간, 최대 100건)

배치 주기(6시간) < TTL(7시간) → 배치 간 캐시 미스 방지
배치 저장 시 @CacheEvict → 전체 무효화 → 다음 요청 시 DB 재조회 (Cache-Aside)

영상 분석 파이프라인

POST /video/analyze { youtubeUrl }
  │
  ▼
┌────────────────────────────────────────────────┐
│             VideoAnalyzeService                │
│                                                │
│  URL 정규화 → 중복 확인 → Task 생성 (PENDING)     │
│  → 요청 대기열 등록 (memberId)                   │
│  → Event 발행 → 202 Accepted 즉시 반환           │
└─────────────────────┬──────────────────────────┘
                      │ @Async + @TransactionalEventListener
                      ▼
┌────────────────────────────────────────────────┐
│          VideoAnalyzeEventListener             │
│                                                │
│  Gemini 2.5 Flash 영상 분석                     │
│       │                                        │
│  유효한 여행 영상?                               │
│   ├── No → INVALID                             │
│   ▼ Yes                                        │
│  일정 저장 (EAT / ATTRACTION / SHOPPING / etc.) │
│       │                                        │
│  대기열 조회 → 각 요청자별 TripPlan 생성           │
│       │                                        │
│  Google Places API 좌표 매핑 (병렬)              │
│       │                                        │
│  완료 알림 발송                                  │
└────────────────────────────────────────────────┘
                      │
                      ▼
GET /video/{id}/schedule → 일정표 + 장소 좌표 반환

동시 요청 처리

같은 영상을 여러 사용자가 요청하는 경우:
분석은 1회만 수행하고, 각 요청자의 여행 계획은 개별 생성

User A: POST /analyze (영상 X) → 새 분석 시작, 대기열 등록, Event 발행
User B: POST /analyze (영상 X) → PENDING 확인 → 대기열 등록 (Event 발행 안 함)
...분석 완료...
EventListener → 대기열 조회 → [User A, User B] → TripPlan 각각 생성
분석 완료 후 요청하는 경우:

User C: POST /analyze (영상 X) → COMPLETED 확인 → TripPlan 즉시 생성 (대기열 불필요)
경쟁 조건 보완:

분석 완료 커밋과 대기열 조회 사이에 등록된 요청이 누락될 수 있음
→ GET /schedule 조회 시 TripPlan이 없으면 즉시 생성 (Lazy 보완)
→ EXISTS 쿼리는 (member_id, video_analysis_task_id) 커버링 인덱스로 처리

상태 전이

PENDING ──→ COMPLETED (분석 성공)
   ├──→ INVALID   (여행 영상 아님)
   └──→ FAILED    (AI 분석 오류) ──→ 재요청 시 PENDING 복원

인기 여행 영상

YouTubeCollectJob
  │
  ├── 24개 키워드 중 5개 랜덤 선택
  ├── YouTube Search API → 키워드당 최대 10개 영상
  ├── DB 중복 체크 (videoId)
  ├── YouTube Videos API → 조회수, 좋아요, 영상 길이
  ├── 메타데이터 태깅 (region, country, city, theme)
  └── DB 저장 + @CacheEvict

인기 크리에이터

YouTubeChannelCollectJob
  │
  ├── 6개 키워드로 채널 검색
  ├── 필터링 (구독자 10만+, 방송사 제외, 중복 제거 → 최대 50개)
  ├── 채널별 최신 여행 영상 최대 3개 수집
  └── DB Upsert + @CacheEvict

장소 보강 재시도 (주기적)

PlaceEnrichRetryJob
  │
  ├── 대상: placeId IS NULL, category ≠ TRANSPORTATION, searchCount < 10
  └── Google Places API 재검색 → 성공 시 좌표 저장 / 실패 시 count++

API

Method Path 설명
POST /api/auth/login 디바이스 시리얼 기반 로그인 (신규 시 자동 가입)
POST /api/video/analyze 영상 분석 요청 (비동기, 202)
GET /api/video/{id}/schedule 분석 결과 일정표 + 장소 좌표
GET /api/video/discover/category 인기 여행 영상 (국가/지역 필터)
GET /api/video/discover/theme 테마별 여행 영상 (커서 페이징, 40건)
GET /api/video/discover/channels 인기 크리에이터 + 최신 영상 (랜덤 10개)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors