Files
url-analyzer/CODE_STRUCTURE.md
2025-11-28 15:58:57 +09:00

35 KiB

Video Analyzer 코드 구조 설명서

개인 학습 및 유지보수를 위한 상세 문서

📋 목차

  1. 프로젝트 개요
  2. 전체 아키텍처
  3. 패키지 구조
  4. 핵심 컴포넌트
  5. 실행 흐름
  6. 플랫폼별 분석 방식
  7. 디자인 패턴
  8. 확장 방법
  9. 주요 상수 관리

프로젝트 개요

목적

다양한 비디오 플랫폼의 URL을 분석하여 재생 가능 여부를 판단하는 라이브러리

주요 기능

  • 9개 플랫폼 지원 (네이버TV, 카카오TV, YouTube, TikTok, Twitter, Vimeo, 웨이보, 직접 URL)
  • DRM 보호 여부 확인
  • 3D 비디오 여부 확인
  • AVPlayer 재생 가능 여부 확인
  • oEmbed API 활용

기술 스택

  • Java 17
  • Gradle
  • Apache HttpClient
  • Jackson (JSON 파싱)
  • SLF4J (로깅)

전체 아키텍처

┌─────────────────────────────────────────────────────────┐
│                    VideoAnalyzer                        │
│                   (Entry Point)                         │
└────────────────┬────────────────────────────────────────┘
                 │
                 ├──────────────────┬──────────────────────┐
                 ▼                  ▼                      ▼
    ┌────────────────────┐  ┌─────────────────┐  ┌──────────────┐
    │ PlatformAnalyzer   │  │ VideoAnalyzer   │  │   Global     │
    │     Factory        │  │    Service      │  │ (Constants)  │
    └────────────────────┘  └─────────────────┘  └──────────────┘
             │
             ├─────────────────────────────────────┐
             │                                     │
             ▼                                     ▼
    ┌─────────────────┐                  ┌──────────────────┐
    │  PlatformAnalyzer                  │ HttpBased        │
    │   (Interface)   │◄─────────────────│ PlatformAnalyzer │
    └─────────────────┘                  │  (Abstract)      │
             ▲                            └──────────────────┘
             │                                     ▲
             │                                     │
    ┌────────┴─────────┬────────┬────────┬────────┴────────┐
    │                  │        │        │                 │
    ▼                  ▼        ▼        ▼                 ▼
┌─────────┐     ┌──────────┐ ┌────┐  ┌────────┐    ┌─────────┐
│ NaverTV │     │ KakaoTV  │ │... │  │ Weibo  │    │ Direct  │
│Analyzer │     │ Analyzer │ │    │  │Analyzer│    │  URL    │
└─────────┘     └──────────┘ └────┘  └────────┘    └─────────┘

아키텍처 특징

  1. 전략 패턴 (Strategy Pattern)

    • PlatformAnalyzer 인터페이스로 플랫폼별 분석 전략 정의
    • 각 플랫폼은 독립적인 분석 로직 구현
  2. 팩토리 패턴 (Factory Pattern)

    • PlatformAnalyzerFactory가 URL에 맞는 분석기 선택
    • 새 플랫폼 추가 시 Factory에만 등록하면 됨
  3. 템플릿 메서드 패턴 (Template Method Pattern)

    • HttpBasedPlatformAnalyzer 추상 클래스가 공통 로직 제공
    • 각 플랫폼은 응답 파싱 부분만 구현

패키지 구조

com.caliverse
│
├── analyzer
│   ├── VideoAnalyzer.java              // 메인 진입점
│   │
│   ├── entity
│   │   └── UrlType.java                // URL 타입 열거형
│   │
│   ├── model
│   │   └── VideoAnalysisResult.java    // 분석 결과 DTO
│   │
│   ├── platform
│   │   ├── PlatformAnalyzer.java       // 플랫폼 분석기 인터페이스
│   │   ├── PlatformAnalyzerFactory.java // 팩토리 클래스
│   │   │
│   │   └── impl
│   │       ├── HttpBasedPlatformAnalyzer.java    // HTTP 기반 추상 클래스
│   │       ├── NaverTvPlatformAnalyzer.java
│   │       ├── KakaoTvPlatformAnalyzer.java
│   │       ├── YouTubePlatformAnalyzer.java
│   │       ├── VimeoPlatformAnalyzer.java
│   │       ├── TikTokPlatformAnalyzer.java
│   │       ├── TwitterPlatformAnalyzer.java
│   │       ├── WeiboPlatformAnalyzer.java
│   │       └── DirectUrlPlatformAnalyzer.java
│   │
│   └── service
│       └── VideoAnalyzerService.java   // 일반 비디오 분석 서비스
│
└── global
    ├── common
    │   ├── CommonConstants.java        // 공통 상수 (도메인, API, 헤더 등)
    │   └── Messages.java               // 메시지 상수 (로그, 에러)
    │
    └── util
        ├── HttpUtils.java              // HTTP 유틸리티
        └── UrlParser.java              // URL 파싱 유틸리티

핵심 컴포넌트

1. VideoAnalyzer (메인 클래스)

역할: 외부 API로 사용되는 진입점

주요 메서드:

public VideoAnalysisResult analyzeUrl(String url)
public String[] getSupportedPlatforms()

동작:

  1. PlatformAnalyzerFactory에서 URL에 맞는 분석기 찾기
  2. 플랫폼 분석기가 있으면 해당 분석기 사용
  3. 없으면 VideoAnalyzerService로 일반 비디오 분석

사용 예시:

VideoAnalyzer analyzer = new VideoAnalyzer();
VideoAnalysisResult result = analyzer.analyzeUrl("https://tv.naver.com/v/84373511");

if (result.isSuccess()) {
    System.out.println("재생 가능: " + result.getReason());
} else {
    System.out.println("재생 불가: " + result.getReason());
}

2. PlatformAnalyzer (인터페이스)

역할: 모든 플랫폼 분석기가 구현해야 하는 계약 정의

메서드:

boolean canHandle(String url)       // 이 플랫폼 URL인지 확인
String getPlatformName()            // 플랫폼 이름 반환
VideoAnalysisResult analyze(String url)  // URL 분석 수행

구현 원칙:

  • canHandle(): 빠르게 판단 가능해야 함 (정규식, 도메인 체크)
  • analyze(): 실제 HTTP 요청 및 분석 수행
  • Thread-safe 해야 함 (상태를 갖지 않음)

3. PlatformAnalyzerFactory (팩토리)

역할: URL에 맞는 분석기 선택 및 생성

등록 순서 (중요!):

analyzers.add(new NaverTvPlatformAnalyzer());
analyzers.add(new KakaoTvPlatformAnalyzer());
analyzers.add(new YouTubePlatformAnalyzer());
analyzers.add(new VimeoPlatformAnalyzer());
analyzers.add(new TikTokPlatformAnalyzer());
analyzers.add(new TwitterPlatformAnalyzer());
analyzers.add(new WeiboPlatformAnalyzer());
analyzers.add(new DirectUrlPlatformAnalyzer());  // 가장 마지막 (fallback)

왜 순서가 중요한가?

  • DirectUrlPlatformAnalyzer는 모든 비디오 확장자를 처리
  • 마지막에 배치하여 다른 플랫폼 우선 확인

동작 방식:

public PlatformAnalyzer findAnalyzer(String url) {
    for (PlatformAnalyzer analyzer : analyzers) {
        if (analyzer.canHandle(url)) {
            return analyzer;  // 첫 번째로 매칭되는 분석기 반환
        }
    }
    return null;
}

4. HttpBasedPlatformAnalyzer (추상 클래스)

역할: HTTP 기반 플랫폼의 공통 로직 제공 (Template Method Pattern)

템플릿 메서드:

@Override
public final VideoAnalysisResult analyze(String url) {
    try {
        // 1. API URL 구성
        String endpoint = getApiEndpoint();     // 추상 메서드

        // 2. HTTP 요청
        String response = fetchApiResponse(endpoint, url);

        // 3. 응답 파싱 (각 플랫폼에서 구현)
        VideoAnalysisResult result = parseResponse(response, url);  // 추상 메서드

        // 4. 로깅 및 결과 반환
        return result;
    } catch (Exception e) {
        // 공통 예외 처리
    }
}

하위 클래스가 구현해야 할 메서드:

protected abstract String getApiEndpoint();           // API URL
protected abstract String getPlatformDomain();        // 도메인
protected abstract VideoAnalysisResult parseResponse(...);  // 응답 파싱

제공하는 유틸리티:

protected String buildApiUrl(String endpoint, String videoUrl)  // API URL 생성
protected String getStringValue(JsonNode node, String fieldName) // JSON 파싱
protected Integer getIntValue(JsonNode node, String fieldName)   // JSON 파싱

5. VideoAnalysisResult (결과 DTO)

역할: 분석 결과를 담는 불변 객체

구조:

public class VideoAnalysisResult {
    private final boolean success;      // 성공 여부
    private final String reason;        // 상세 이유

    public static VideoAnalysisResult success() { ... }
    public static VideoAnalysisResult failure(String reason) { ... }
}

사용 패턴:

// 성공
return VideoAnalysisResult.success();

// 실패
return VideoAnalysisResult.failure("DRM이 적용된 콘텐츠입니다");

실행 흐름

전체 흐름도

[사용자]
   │
   │ analyzeUrl("https://tv.naver.com/v/123")
   ▼
[VideoAnalyzer]
   │
   │ findAnalyzer(url)
   ▼
[PlatformAnalyzerFactory]
   │
   │ 순회: canHandle(url) 체크
   ▼
[NaverTvPlatformAnalyzer] ✓ 매칭!
   │
   │ analyze(url)
   ▼
[HttpBasedPlatformAnalyzer] (Template)
   │
   ├─ getApiEndpoint() → "https://tv.naver.com/oembed"
   ├─ buildApiUrl() → "https://tv.naver.com/oembed?url=..."
   ├─ fetchApiResponse() → HTTP GET 요청
   │
   │ parseResponse(response) ← 하위 클래스 구현
   ▼
[NaverTvPlatformAnalyzer]
   │
   ├─ JSON 파싱
   ├─ 유효성 검증
   ├─ oEmbed 응답 확인
   │  └─ 실패 시 fallback (HTML 파싱)
   │
   ▼
[VideoAnalysisResult]
   │
   │ return result
   ▼
[사용자]

상세 흐름 (네이버TV 예시)

1단계: URL 매칭

// PlatformAnalyzerFactory.findAnalyzer()
URL: "https://tv.naver.com/v/123"

 NaverTvPlatformAnalyzer.canHandle()
   lowerUrl.contains("tv.naver.com") || lowerUrl.contains("naver.me")
    true 반환

 NaverTvPlatformAnalyzer 인스턴스 반환

2단계: 분석 실행

// NaverTvPlatformAnalyzer.analyze() → HttpBasedPlatformAnalyzer.analyze()

1. API URL 생성
   getApiEndpoint()  "https://tv.naver.com/oembed"
   buildApiUrl()  "https://tv.naver.com/oembed?url=https%3A%2F%2Ftv.naver.com%2Fv%2F123&format=json"

2. HTTP 요청
   HttpClient.execute(GET)
   └─ User-Agent: CommonConstants.USER_AGENT_STANDARD
   └─ Accept: CommonConstants.ACCEPT_JSON_HTML

3. 응답 수신
   {
     "type": "video",
     "title": "비디오 제목",
     "provider_name": "NAVERTV",
     "width": 640,
     "height": 360,
     ...
   }

3단계: 응답 파싱

// NaverTvPlatformAnalyzer.parseResponse()

1. JSON 파싱
   JsonNode rootNode = objectMapper.readTree(response)

2. 필드 추출
   type = rootNode.get("type")
   title = rootNode.get("title")
   width = rootNode.get("width")
   height = rootNode.get("height")
   providerName = rootNode.get("provider_name")

3. 검증
   if (providerName != "NAVERTV")  경고 로그
   if (width == 0 || height == 0)  Shorts로 판단, fallback
   if (type == null || (title == null && html == null))  실패

4. 결과 반환
    VideoAnalysisResult.success()

4단계: Fallback 처리 (Shorts인 경우)

// fallbackToVideoIdExtraction()

1. URL에서 videoId 추출
   Matcher matcher = VIDEO_ID_PATTERN.matcher(url)
   videoId = "123"

2. HTML 파싱
   - __NEXT_DATA__ 스크립트 태그 찾기
   - JSON 데이터 추출
   - props.pageProps.clipInfo 경로로 메타데이터 추출

3. 최소 메타데이터 구성
   embedUrl = "https://tv.naver.com/embed/123?autoPlay=true"

    VideoAnalysisResult.success()

플랫폼별 분석 방식

1. 네이버TV (NaverTvPlatformAnalyzer)

지원 URL 형식:

  • VOD: https://tv.naver.com/v/{video_id}
  • Live: https://tv.naver.com/l/{video_id}
  • Shorts: https://tv.naver.com/h/{video_id}
  • 단축 URL: https://naver.me/{short_code}

분석 방식:

  1. oEmbed API 우선 시도

    • Endpoint: https://tv.naver.com/oembed
    • VOD/Live는 정상 응답
    • Shorts는 width=0, height=0으로 응답 (무효)
  2. Fallback: HTML 파싱

    • __NEXT_DATA__ 스크립트 태그 파싱
    • props.pageProps.clipInfo 경로에서 메타데이터 추출
    • videoId로 embed URL 구성
  3. 단축 URL 처리

    • HttpClient가 자동으로 리다이렉트 따라감
    • 최종 URL에서 videoId 추출

특이사항:

  • Shorts는 oEmbed 미지원 → 반드시 fallback 필요
  • 단축 URL은 리다이렉트 후 처리

2. 카카오TV (KakaoTvPlatformAnalyzer)

지원 URL 형식:

  • https://tv.kakao.com/channel/{channel_id}/cliplink/{clip_id}
  • https://tv.kakao.com/v/{video_id}

분석 방식:

  • oEmbed API: https://tv.kakao.com/oembed
  • 표준 oEmbed JSON 응답
  • Provider: "kakaoTV"

3. YouTube (YouTubePlatformAnalyzer)

지원 URL 형식:

  • https://www.youtube.com/watch?v={video_id}
  • https://youtu.be/{video_id}

분석 방식:

  • oEmbed API: https://www.youtube.com/oembed
  • Provider: "YouTube"
  • Version: "1.0"

제한사항:

  • oEmbed는 가능하지만 YouTube 전용 player에서만 재생 가능
  • 직접 비디오 URL 추출은 불가능 (YouTube API 필요)

4. TikTok (TikTokPlatformAnalyzer)

지원 URL 형식:

  • https://www.tiktok.com/@{username}/video/{video_id}
  • https://vm.tiktok.com/{short_code} (단축 URL)

분석 방식:

  • oEmbed API: https://www.tiktok.com/oembed
  • type 검증: 반드시 "video"

5. Twitter/X (TwitterPlatformAnalyzer)

지원 URL 형식:

  • https://twitter.com/{username}/status/{tweet_id}
  • https://x.com/{username}/status/{tweet_id}

분석 방식:

  • oEmbed API: https://publish.twitter.com/oembed
  • type 검증: "rich" 또는 "video"
  • Provider: "Twitter"

6. Vimeo (VimeoPlatformAnalyzer)

지원 URL 형식:

  • https://vimeo.com/{video_id}

분석 방식:

  • oEmbed API: https://vimeo.com/api/oembed.json
  • 표준 oEmbed 응답

7. 웨이보 (WeiboPlatformAnalyzer)

지원 URL 형식:

  • https://weibo.com/tv/show/{video_id}
  • https://video.weibo.com/show?fid={video_id}
  • https://m.weibo.cn/status/{status_id}
  • https://weibo.com/{user_id}/{status_id}

분석 방식 (oEmbed 미지원):

  1. HTML 파싱

    • Open Graph 메타태그에서 비디오 URL 추출
    • og:video, og:title, og:image
  2. $render_data 스크립트 파싱

    • 웨이보의 동적 데이터 스크립트
    • JSON 구조: [{ status: { page_info: { ... } } }]

특수 헤더 요구:

User-Agent: CommonConstants.USER_AGENT_WEIBO  // 웨이보 전용
Accept-Language: "zh-CN,zh;q=0.9,en;q=0.8"
Referer: "https://weibo.com"

특이사항:

  • 웨이보는 User-Agent를 엄격히 확인
  • 공식 API 없음 → HTML 파싱 방식 사용

8. 직접 URL (DirectUrlPlatformAnalyzer)

지원 포맷:

{".mp4", ".m3u8", ".mpd", ".webm", ".mov", ".avi", ".mkv"}

분석 방식:

  1. 확장자 체크

    • URL에서 쿼리 파라미터 제거
    • 확장자가 비디오 포맷인지 확인
  2. HTTP HEAD 요청

    • 접근 가능 여부 확인
    • 상태 코드 체크 (200 성공)
  3. CORS 확인

    • Access-Control-Allow-Origin 헤더 확인
    • CORS 미설정 시 경고 (브라우저 재생 제한 가능성)
  4. DRM 감지

    • 헤더에 "drm", "protection", "encryption" 키워드 포함 시 DRM 판정
  5. 인증 확인

    • URL에 signature=, token=, key= 파라미터 있으면 인증 필요
    • 헤더에 "signature", "token" 있으면 인증 필요

검증 항목:

  • HTTP 접근 가능 여부
  • ⚠️ CORS 제한 (경고만, 실패 아님)
  • DRM 보호 (실패)
  • 인증 필요 (실패)

디자인 패턴

1. Strategy Pattern (전략 패턴)

적용 위치: PlatformAnalyzer 인터페이스

목적: 플랫폼마다 다른 분석 알고리즘을 캡슐화

장점:

  • 새 플랫폼 추가가 쉬움 (OCP 준수)
  • 각 플랫폼이 독립적으로 변경 가능
  • 런타임에 전략 선택 가능

구현:

// 전략 인터페이스
public interface PlatformAnalyzer {
    VideoAnalysisResult analyze(String url);
}

// 구체적 전략들
public class NaverTvPlatformAnalyzer implements PlatformAnalyzer { ... }
public class YouTubePlatformAnalyzer implements PlatformAnalyzer { ... }

// 컨텍스트
public class VideoAnalyzer {
    public VideoAnalysisResult analyzeUrl(String url) {
        PlatformAnalyzer analyzer = platformFactory.findAnalyzer(url);
        return analyzer.analyze(url);  // 전략 실행
    }
}

2. Factory Pattern (팩토리 패턴)

적용 위치: PlatformAnalyzerFactory

목적: URL에 맞는 분석기 생성 로직 캡슐화

장점:

  • 객체 생성 로직을 한 곳에 집중
  • 클라이언트는 구체 클래스를 몰라도 됨
  • 새 플랫폼 추가 시 Factory만 수정

구현:

public class PlatformAnalyzerFactory {
    private final List<PlatformAnalyzer> analyzers;

    public PlatformAnalyzerFactory() {
        // 팩토리에서 모든 분석기 생성 및 등록
        analyzers.add(new NaverTvPlatformAnalyzer());
        analyzers.add(new KakaoTvPlatformAnalyzer());
        // ...
    }

    public PlatformAnalyzer findAnalyzer(String url) {
        // URL에 맞는 분석기 찾기
        for (PlatformAnalyzer analyzer : analyzers) {
            if (analyzer.canHandle(url)) {
                return analyzer;
            }
        }
        return null;
    }
}

3. Template Method Pattern (템플릿 메서드 패턴)

적용 위치: HttpBasedPlatformAnalyzer 추상 클래스

목적: HTTP 기반 플랫폼의 공통 알고리즘 정의, 세부 단계는 하위 클래스에서 구현

장점:

  • 코드 중복 제거
  • 공통 로직 변경 시 한 곳만 수정
  • 하위 클래스는 차이점만 구현

구현:

public abstract class HttpBasedPlatformAnalyzer implements PlatformAnalyzer {

    // 템플릿 메서드 (알고리즘 골격)
    @Override
    public VideoAnalysisResult analyze(String url) {
        try {
            logger.debug(Messages.LOG_ANALYSIS_START, getPlatformName(), url);

            // 1. API URL 가져오기 (하위 클래스 구현)
            String endpoint = getApiEndpoint();

            // 2. HTTP 요청 (공통 로직)
            String response = fetchApiResponse(endpoint, url);

            // 3. 응답 파싱 (하위 클래스 구현)
            VideoAnalysisResult result = parseResponse(response, url);

            // 4. 로깅 (공통 로직)
            if (result.isSuccess()) {
                logger.debug(Messages.LOG_VALIDATION_SUCCESS, getPlatformName());
            }

            return result;
        } catch (Exception e) {
            // 공통 예외 처리
        }
    }

    // 추상 메서드들 (하위 클래스가 구현)
    protected abstract String getApiEndpoint();
    protected abstract String getPlatformDomain();
    protected abstract VideoAnalysisResult parseResponse(String response, String originalUrl);

    // 공통 메서드들
    private String fetchApiResponse(String endpoint, String videoUrl) { ... }
    protected String getStringValue(JsonNode node, String fieldName) { ... }
}

하위 클래스 예시:

public class KakaoTvPlatformAnalyzer extends HttpBasedPlatformAnalyzer {

    @Override
    protected String getApiEndpoint() {
        return CommonConstants.API_ENDPOINT_KAKAO_TV;
    }

    @Override
    protected String getPlatformDomain() {
        return CommonConstants.DOMAIN_KAKAO_TV;
    }

    @Override
    protected VideoAnalysisResult parseResponse(String response, String originalUrl) {
        // 카카오TV 특화 파싱 로직
        JsonNode rootNode = objectMapper.readTree(response);
        // ...
    }
}

4. Immutable Object Pattern (불변 객체 패턴)

적용 위치: VideoAnalysisResult

목적: 결과 객체의 안전한 공유 및 Thread-safety 보장

장점:

  • Thread-safe
  • 예측 가능한 동작
  • 캐싱 가능

구현:

public class VideoAnalysisResult {
    private final boolean success;  // final 필드
    private final String reason;    // final 필드

    // private 생성자
    private VideoAnalysisResult(boolean success, String reason) {
        this.success = success;
        this.reason = reason;
    }

    // 정적 팩토리 메서드
    public static VideoAnalysisResult success() {
        return new VideoAnalysisResult(true, Messages.SUCCESS_ALL_CHECKS_PASSED);
    }

    public static VideoAnalysisResult failure(String reason) {
        return new VideoAnalysisResult(false, reason);
    }

    // Getter만 제공 (Setter 없음)
    public boolean isSuccess() { return success; }
    public String getReason() { return reason; }
}

확장 방법

새 플랫폼 추가하기

Step 1: Analyzer 클래스 생성

oEmbed를 지원하는 플랫폼의 경우:

package com.caliverse.analyzer.platform.impl;

import com.caliverse.global.common.CommonConstants;
import com.caliverse.global.common.Messages;

public class NewPlatformAnalyzer extends HttpBasedPlatformAnalyzer {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected String getApiEndpoint() {
        return CommonConstants.API_ENDPOINT_NEW_PLATFORM;  // 1. 상수 추가 필요
    }

    @Override
    protected String getPlatformDomain() {
        return CommonConstants.DOMAIN_NEW_PLATFORM;  // 2. 상수 추가 필요
    }

    @Override
    public String getPlatformName() {
        return CommonConstants.PLATFORM_NAME_NEW_PLATFORM;  // 3. 상수 추가 필요
    }

    @Override
    protected VideoAnalysisResult parseResponse(String response, String originalUrl) {
        try {
            JsonNode rootNode = objectMapper.readTree(response);

            // 플랫폼별 필드 추출
            String type = getStringValue(rootNode, "type");
            String title = getStringValue(rootNode, "title");

            // 유효성 검증
            if (type == null || title == null) {
                return VideoAnalysisResult.failure(Messages.NEW_PLATFORM_INVALID);  // 4. 메시지 추가 필요
            }

            logger.debug(Messages.LOG_OEMBED_VALIDATION_SUCCESS, getPlatformName(), title);
            return VideoAnalysisResult.success();

        } catch (Exception e) {
            throw new IOException(Messages.format(Messages.NEW_PLATFORM_PARSING_FAILED, e.getMessage()));
        }
    }
}

oEmbed를 지원하지 않는 플랫폼의 경우:

public class NewPlatformAnalyzer implements PlatformAnalyzer {

    @Override
    public boolean canHandle(String url) {
        return url != null && url.toLowerCase().contains("newplatform.com");
    }

    @Override
    public String getPlatformName() {
        return CommonConstants.PLATFORM_NAME_NEW_PLATFORM;
    }

    @Override
    public VideoAnalysisResult analyze(String url) {
        try {
            // 커스텀 분석 로직 (HTML 파싱, API 호출 등)
            String htmlContent = fetchHtmlContent(url);
            return parseHtmlMetadata(htmlContent);
        } catch (Exception e) {
            return VideoAnalysisResult.failure(Messages.format(Messages.NEW_PLATFORM_ERROR, e.getMessage()));
        }
    }
}

Step 2: CommonConstants에 상수 추가

// CommonConstants.java에 추가

// 플랫폼 이름
public static final String PLATFORM_NAME_NEW_PLATFORM = "새플랫폼";

// 도메인
public static final String DOMAIN_NEW_PLATFORM = "newplatform.com";

// API 엔드포인트
public static final String API_ENDPOINT_NEW_PLATFORM = "https://newplatform.com/api/oembed";

// Provider (필요시)
public static final String PROVIDER_NEW_PLATFORM = "NewPlatform";

Step 3: Messages에 메시지 추가

// Messages.java에 추가

// 에러 메시지
public static final String NEW_PLATFORM_INVALID = "새플랫폼 응답이 유효하지 않습니다";
public static final String NEW_PLATFORM_PARSING_FAILED = "새플랫폼 응답 파싱 실패: %s";
public static final String NEW_PLATFORM_ERROR = "새플랫폼 분석 중 오류 발생: %s";

Step 4: Factory에 등록

// PlatformAnalyzerFactory.java

public PlatformAnalyzerFactory() {
    this.analyzers = new ArrayList<>();

    analyzers.add(new NaverTvPlatformAnalyzer());
    analyzers.add(new KakaoTvPlatformAnalyzer());
    // ... 기존 플랫폼들

    analyzers.add(new NewPlatformAnalyzer());  // ← 추가

    analyzers.add(new DirectUrlPlatformAnalyzer());  // 항상 마지막
}

Step 5: 테스트 작성

// NewPlatformAnalyzerTest.java

@Test
void testNewPlatformUrl() {
    NewPlatformAnalyzer analyzer = new NewPlatformAnalyzer();

    // canHandle 테스트
    assertTrue(analyzer.canHandle("https://newplatform.com/video/123"));
    assertFalse(analyzer.canHandle("https://other.com/video/123"));

    // analyze 테스트
    VideoAnalysisResult result = analyzer.analyze("https://newplatform.com/video/123");
    assertTrue(result.isSuccess());
}

기존 플랫폼 수정하기

1. 로직 변경

  • 각 Analyzer 클래스에서 직접 수정
  • 공통 로직은 HttpBasedPlatformAnalyzer에서 수정

2. 상수 변경

  • CommonConstants.java 또는 Messages.java에서 수정
  • 변경하면 모든 사용처에 자동 반영

3. API 엔드포인트 변경

// CommonConstants.java에서만 수정
public static final String API_ENDPOINT_NAVER_TV = "https://new-api.naver.com/oembed";
// → 모든 NaverTvPlatformAnalyzer에 자동 반영

주요 상수 관리

CommonConstants.java 구조

1. 플랫폼 정보

// 플랫폼 이름
public static final String PLATFORM_NAME_NAVER_TV = "네이버TV";
public static final String PLATFORM_NAME_KAKAO_TV = "카카오TV";
// ...

// 도메인
public static final String DOMAIN_NAVER_TV = "tv.naver.com";
public static final String DOMAIN_NAVER_SHORT = "naver.me";
// ...

// API 엔드포인트
public static final String API_ENDPOINT_NAVER_TV = "https://tv.naver.com/oembed";
public static final String API_ENDPOINT_KAKAO_TV = "https://tv.kakao.com/oembed";
// ...

// Provider 이름
public static final String PROVIDER_NAVER_TV = "NAVERTV";
public static final String PROVIDER_KAKAO_TV = "kakaoTV";
// ...

2. HTTP 관련

// 헤더 이름
public static final String HEADER_USER_AGENT = "User-Agent";
public static final String HEADER_REFERER = "Referer";
public static final String HEADER_ACCEPT = "Accept";

// User-Agent 값
public static final String USER_AGENT_STANDARD = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
public static final String USER_AGENT_WEIBO = "Mozilla/5.0 ... Chrome/120.0.0.0 Safari/537.36";

// Accept 값
public static final String ACCEPT_ALL = "*/*";
public static final String ACCEPT_JSON_HTML = "application/json, text/html";

// 타임아웃
public static final int HTTP_CONNECTION_TIMEOUT = 5000;  // 5초
public static final int HTTP_SOCKET_TIMEOUT = 5000;      // 5초

3. 비디오 관련

// 확장자
public static final String[] VIDEO_EXTENSIONS = {".mp4", ".m3u8", ".mpd", ".webm", ".mov", ".avi", ".mkv"};

// 컨텐츠 타입
public static final String CONTENT_TYPE_VIDEO = "video";
public static final String CONTENT_TYPE_RICH = "rich";

// 버전
public static final String VERSION_OEMBED_1_0 = "1.0";

Messages.java 구조

1. 일반 메시지

public static final String SUCCESS_ALL_CHECKS_PASSED = "모든 검사 통과";
public static final String ERROR_INVALID_URL = "유효하지 않은 URL";

2. 플랫폼별 에러 메시지

// 네이버TV
public static final String NAVER_TV_OEMBED_INVALID = "네이버TV oEmbed 응답이 유효하지 않습니다";
public static final String NAVER_TV_FALLBACK_FAILED = "네이버TV fallback 처리 실패: %s";

// 카카오TV
public static final String KAKAO_TV_OEMBED_INVALID = "카카오TV oEmbed 응답이 유효하지 않습니다";
public static final String KAKAO_TV_PARSING_FAILED = "카카오TV 응답 파싱 실패: %s";

// ... 각 플랫폼별 메시지

3. 공통 로그 메시지

public static final String LOG_ANALYSIS_START = "%s URL 분석 시작: %s";
public static final String LOG_VALIDATION_SUCCESS = "%s 검증 성공";
public static final String LOG_VALIDATION_FAILED = "%s 검증 실패: %s";
public static final String LOG_OEMBED_VALIDATION_SUCCESS = "%s oEmbed 검증 성공: %s";

4. 포맷 메서드

/**
 * 포맷 문자열을 사용하는 메시지를 생성합니다.
 */
public static String format(String format, Object... args) {
    return String.format(format, args);
}

사용 예시:

// 방법 1: 직접 String.format
String errorMsg = String.format(Messages.NAVER_TV_FALLBACK_FAILED, e.getMessage());

// 방법 2: Messages.format() 사용
String errorMsg = Messages.format(Messages.NAVER_TV_FALLBACK_FAILED, e.getMessage());

상수 사용 원칙

1. 하드코딩 금지

나쁜 예:

logger.debug("네이버TV URL 분석 시작: {}", url);
return VideoAnalysisResult.failure("네이버TV oEmbed 응답이 유효하지 않습니다");

좋은 예:

logger.debug(Messages.LOG_ANALYSIS_START, CommonConstants.PLATFORM_NAME_NAVER_TV, url);
return VideoAnalysisResult.failure(Messages.NAVER_TV_OEMBED_INVALID);

2. 동적 메시지는 포맷 사용

나쁜 예:

String errorMsg = "네이버TV fallback 처리 실패: " + e.getMessage();

좋은 예:

String errorMsg = Messages.format(Messages.NAVER_TV_FALLBACK_FAILED, e.getMessage());

3. 플랫폼 정보는 상수 사용

나쁜 예:

private static final String PLATFORM_NAME = "네이버TV";
private static final String API_ENDPOINT = "https://tv.naver.com/oembed";

좋은 예:

@Override
public String getPlatformName() {
    return CommonConstants.PLATFORM_NAME_NAVER_TV;
}

@Override
protected String getApiEndpoint() {
    return CommonConstants.API_ENDPOINT_NAVER_TV;
}

유용한 팁

1. 디버깅 방법

로그 레벨 설정:

# logback.xml
<logger name="com.caliverse.analyzer" level="DEBUG"/>

주요 로그 포인트:

  • LOG_ANALYSIS_START: 분석 시작
  • LOG_API_URL: API 호출 URL
  • LOG_OEMBED_VALIDATION_SUCCESS: 검증 성공
  • LOG_VALIDATION_FAILED: 검증 실패

2. 성능 최적화

HttpClient 재사용:

// ❌ 나쁜 예: 매번 생성
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
    // ...
}

// ✅ 좋은 예: 싱글톤 또는 필드로 재사용
private static final CloseableHttpClient HTTP_CLIENT = HttpClients.createDefault();

타임아웃 설정:

RequestConfig requestConfig = RequestConfig.custom()
    .setConnectTimeout(CommonConstants.HTTP_CONNECTION_TIMEOUT)
    .setSocketTimeout(CommonConstants.HTTP_SOCKET_TIMEOUT)
    .build();

3. 테스트 전략

단위 테스트:

  • 각 Analyzer의 canHandle() 테스트
  • parseResponse() 테스트 (Mock 응답 사용)

통합 테스트:

  • 실제 URL로 analyze() 테스트
  • 네트워크 의존성 있음 (주의)

테스트 URL 예시:

// 네이버TV VOD
"https://tv.naver.com/v/84373511"

// 네이버TV Shorts
"https://tv.naver.com/h/40687083"

// 카카오TV
"https://tv.kakao.com/v/423707209"

// YouTube
"https://www.youtube.com/watch?v=dQw4w9WgXcQ"

문제 해결 가이드

1. oEmbed API 호출 실패

증상: API 호출 시 4xx/5xx 에러

원인:

  • URL 인코딩 문제
  • User-Agent 차단
  • API 엔드포인트 변경

해결:

// User-Agent 확인
request.setHeader(CommonConstants.HEADER_USER_AGENT, CommonConstants.USER_AGENT_STANDARD);

// URL 인코딩 확인
String encodedUrl = URLEncoder.encode(videoUrl, StandardCharsets.UTF_8.toString());

// API URL 로그 확인
logger.debug(Messages.LOG_API_URL, apiUrl);

2. 웨이보 분석 실패

증상: HTML 가져오기 실패 또는 빈 응답

원인: User-Agent 검증 실패

해결:

// 반드시 웨이보 전용 User-Agent 사용
request.setHeader(CommonConstants.HEADER_USER_AGENT, CommonConstants.USER_AGENT_WEIBO);
request.setHeader(CommonConstants.HEADER_ACCEPT_LANGUAGE, CommonConstants.ACCEPT_LANGUAGE_ZH_CN);
request.setHeader(CommonConstants.HEADER_REFERER, CommonConstants.REFERER_WEIBO);

3. 네이버TV Shorts 파싱 실패

증상: width=0, height=0으로 oEmbed 실패

원인: Shorts는 oEmbed 미지원

해결: Fallback 로직 확인

if (width == null || height == null || width == 0 || height == 0) {
    logger.debug(Messages.LOG_NAVER_TV_OEMBED_INVALID_FALLBACK, width, height);
    return fallbackToVideoIdExtraction(originalUrl);  // HTML 파싱으로 처리
}

마무리

이 문서는 Video Analyzer 프로젝트의 코드 구조를 상세히 설명합니다.

핵심 요약

  1. 전략 패턴: 플랫폼마다 다른 분석 로직
  2. 팩토리 패턴: URL에 맞는 분석기 자동 선택
  3. 템플릿 메서드: HTTP 기반 플랫폼의 공통 로직
  4. 상수 중앙 관리: CommonConstants, Messages
  5. 확장 용이: 새 플랫폼 추가 4단계

다음 단계

  • 캐싱 전략 추가 (동일 URL 반복 조회 최적화)
  • 비동기 처리 (CompletableFuture)
  • 배치 분석 지원 (여러 URL 동시 분석)
  • 메트릭 수집 (성공률, 응답 시간)

작성일: 2025-11-12 버전: 1.0.0 작성자: 개인 학습용