Files
lunchpick/doc/03_architecture/naver_url_processing_architecture.md
JiWoong Sul 85fde36157 feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다.

주요 기능:
- 네이버 지도 연동 맛집 추가
- 랜덤 메뉴 추천 시스템
- 날씨 기반 거리 조정
- 방문 기록 관리
- Bluetooth 맛집 공유
- 다크모드 지원

기술 스택:
- Flutter 3.8.1+
- Riverpod 상태 관리
- Hive 로컬 DB
- Clean Architecture

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 19:03:28 +09:00

400 lines
9.7 KiB
Markdown

# 네이버 단축 URL 처리 아키텍처 설계
## 1. 개요
### 1.1 목적
네이버 단축 URL(naver.me)을 처리하여 식당 정보를 추출하고, 네이버 로컬 API를 통해 상세 정보를 보강하는 시스템을 설계합니다.
### 1.2 핵심 요구사항
- 네이버 단축 URL 리다이렉션 처리
- HTML 스크래핑을 통한 기본 정보 추출
- 네이버 로컬 API를 통한 상세 정보 검색
- 기존 Clean Architecture 패턴 유지
- 사이드이펙트 방지 및 테스트 가능성 확보
## 2. 아키텍처 구조
### 2.1 계층 구조
```
Presentation Layer
Domain Layer (Use Cases)
Data Layer
├── Repository Implementation
├── Data Sources
│ ├── Remote
│ │ ├── NaverMapParser (기존)
│ │ ├── NaverLocalApiClient (신규)
│ │ └── NaverUrlProcessor (신규)
│ └── Local
│ └── Hive Database
└── Models/DTOs
```
### 2.2 주요 컴포넌트
#### 2.2.1 Data Layer - Remote Data Sources
**NaverUrlProcessor (신규)**
```dart
// lib/data/datasources/remote/naver_url_processor.dart
class NaverUrlProcessor {
final NaverMapParser _mapParser;
final NaverLocalApiClient _apiClient;
// 단축 URL 처리 파이프라인
Future<Restaurant> processNaverUrl(String url);
// URL 유효성 검증
bool isValidNaverUrl(String url);
// 단축 URL → 실제 URL 변환
Future<String> resolveShortUrl(String shortUrl);
}
```
**NaverLocalApiClient (신규)**
```dart
// lib/data/datasources/remote/naver_local_api_client.dart
class NaverLocalApiClient {
final Dio _dio;
// 네이버 로컬 API 검색
Future<List<NaverLocalSearchResult>> searchLocal({
required String query,
String? category,
int display = 5,
});
// 상세 정보 조회 (Place ID 기반)
Future<NaverPlaceDetail?> getPlaceDetail(String placeId);
}
```
**NaverMapParser (기존 확장)**
- 기존 HTML 스크래핑 기능 유지
- 새로운 메서드 추가:
- `extractSearchQuery()`: HTML에서 검색 가능한 키워드 추출
- `extractPlaceMetadata()`: 메타 정보 추출 강화
#### 2.2.2 Data Layer - Models
**NaverLocalSearchResult (신규)**
```dart
// lib/data/models/naver_local_search_result.dart
class NaverLocalSearchResult {
final String title;
final String link;
final String category;
final String description;
final String telephone;
final String address;
final String roadAddress;
final int mapx; // 경도 * 10,000,000
final int mapy; // 위도 * 10,000,000
}
```
**NaverPlaceDetail (신규)**
```dart
// lib/data/models/naver_place_detail.dart
class NaverPlaceDetail {
final String id;
final String name;
final String category;
final Map<String, dynamic> businessHours;
final List<String> menuItems;
final String? homePage;
final List<String> images;
}
```
#### 2.2.3 Repository Layer
**RestaurantRepositoryImpl (확장)**
```dart
// 기존 메서드 확장
@override
Future<Restaurant> addRestaurantFromUrl(String url) async {
try {
// NaverUrlProcessor 사용
final processor = NaverUrlProcessor(
mapParser: _naverMapParser,
apiClient: _naverLocalApiClient,
);
final restaurant = await processor.processNaverUrl(url);
// 중복 체크 및 저장 로직 (기존 유지)
// ...
} catch (e) {
// 에러 처리
}
}
```
### 2.3 처리 흐름
```mermaid
sequenceDiagram
participant User
participant UI
participant Repository
participant UrlProcessor
participant MapParser
participant ApiClient
participant Hive
User->>UI: 네이버 단축 URL 입력
UI->>Repository: addRestaurantFromUrl(url)
Repository->>UrlProcessor: processNaverUrl(url)
UrlProcessor->>UrlProcessor: isValidNaverUrl(url)
UrlProcessor->>MapParser: resolveShortUrl(url)
MapParser-->>UrlProcessor: 실제 URL
UrlProcessor->>MapParser: parseRestaurantFromUrl(url)
MapParser-->>UrlProcessor: 기본 정보 (HTML 스크래핑)
UrlProcessor->>MapParser: extractSearchQuery()
MapParser-->>UrlProcessor: 검색 키워드
UrlProcessor->>ApiClient: searchLocal(query)
ApiClient-->>UrlProcessor: 검색 결과 리스트
UrlProcessor->>UrlProcessor: 매칭 및 병합
UrlProcessor-->>Repository: 완성된 Restaurant 객체
Repository->>Hive: 중복 체크
Repository->>Hive: 저장
Repository-->>UI: 결과 반환
UI-->>User: 성공/실패 표시
```
## 3. 상세 설계
### 3.1 URL 처리 파이프라인
1. **URL 유효성 검증**
- 네이버 도메인 확인 (naver.com, naver.me)
- URL 형식 검증
2. **단축 URL 리다이렉션**
- HTTP HEAD/GET 요청으로 실제 URL 획득
- 웹 환경에서는 CORS 프록시 사용
3. **HTML 스크래핑 (기존 NaverMapParser)**
- 기본 정보 추출: 이름, 주소, 카테고리
- Place ID 추출 시도
4. **네이버 로컬 API 검색**
- 추출된 이름과 주소로 검색
- 결과 매칭 알고리즘 적용
5. **정보 병합**
- HTML 스크래핑 데이터 + API 데이터 병합
- 우선순위: API 데이터 > 스크래핑 데이터
### 3.2 에러 처리 전략
```dart
// 계층별 예외 정의
abstract class NaverException implements Exception {
final String message;
NaverException(this.message);
}
class NaverUrlException extends NaverException {
NaverUrlException(String message) : super(message);
}
class NaverApiException extends NaverException {
final int? statusCode;
NaverApiException(String message, {this.statusCode}) : super(message);
}
class NaverParseException extends NaverException {
NaverParseException(String message) : super(message);
}
```
### 3.3 매칭 알고리즘
```dart
class RestaurantMatcher {
// 스크래핑 데이터와 API 결과 매칭
static NaverLocalSearchResult? findBestMatch(
Restaurant scrapedData,
List<NaverLocalSearchResult> apiResults,
) {
// 1. 이름 유사도 계산 (Levenshtein distance)
// 2. 주소 유사도 계산
// 3. 카테고리 일치 여부
// 4. 거리 계산 (좌표 기반)
// 5. 종합 점수로 최적 매칭 선택
}
}
```
## 4. 테스트 전략
### 4.1 단위 테스트
```dart
// test/data/datasources/remote/naver_url_processor_test.dart
- URL
- URL
-
// test/data/datasources/remote/naver_local_api_client_test.dart
- API /
-
-
```
### 4.2 통합 테스트
```dart
// test/integration/naver_url_processing_test.dart
-
- URL로 E2E
-
```
### 4.3 모킹 전략
```dart
// Mock 객체 사용
class MockNaverMapParser extends Mock implements NaverMapParser {}
class MockNaverLocalApiClient extends Mock implements NaverLocalApiClient {}
class MockHttpClient extends Mock implements Client {}
```
## 5. 설정 및 환경 변수
### 5.1 API 키 관리
```dart
// lib/core/constants/api_keys.dart
class ApiKeys {
static const String naverClientId = String.fromEnvironment('NAVER_CLIENT_ID');
static const String naverClientSecret = String.fromEnvironment('NAVER_CLIENT_SECRET');
static bool areKeysConfigured() {
return naverClientId.isNotEmpty && naverClientSecret.isNotEmpty;
}
}
```
### 5.2 환경별 설정
```dart
// lib/core/config/environment.dart
abstract class Environment {
static const bool isProduction = bool.fromEnvironment('dart.vm.product');
static String get corsProxyUrl {
return isProduction
? 'https://api.allorigins.win/get?url='
: 'http://localhost:8080/proxy?url=';
}
}
```
## 6. 성능 최적화
### 6.1 캐싱 전략
```dart
class CacheManager {
// URL 리다이렉션 캐시 (TTL: 1시간)
final Map<String, CachedUrl> _urlCache = {};
// API 검색 결과 캐시 (TTL: 30분)
final Map<String, CachedSearchResult> _searchCache = {};
}
```
### 6.2 동시성 제어
```dart
class RateLimiter {
// 네이버 API 호출 제한 (초당 10회)
static const int maxRequestsPerSecond = 10;
// 동시 요청 수 제한
static const int maxConcurrentRequests = 3;
}
```
## 7. 보안 고려사항
### 7.1 API 키 보호
- 환경 변수 사용
- 클라이언트 사이드에서 직접 노출 방지
- ProGuard/R8 난독화 적용
### 7.2 입력 검증
- URL 인젝션 방지
- XSS 방지를 위한 HTML 이스케이핑
- SQL 인젝션 방지 (Hive는 NoSQL이므로 해당 없음)
## 8. 모니터링 및 로깅
### 8.1 로깅 전략
```dart
class NaverUrlLogger {
static void logUrlProcessing(String url, ProcessingStep step, {dynamic data}) {
// 구조화된 로그 기록
// - 타임스탬프
// - 처리 단계
// - 성공/실패 여부
// - 소요 시간
}
}
```
### 8.2 에러 추적
```dart
class ErrorReporter {
static void reportError(Exception error, StackTrace stackTrace, {
Map<String, dynamic>? extra,
}) {
// Crashlytics 또는 Sentry로 에러 전송
}
}
```
## 9. 향후 확장 고려사항
### 9.1 다른 플랫폼 지원
- 카카오맵 URL 처리
- 구글맵 URL 처리
- 배달앱 공유 링크 처리
### 9.2 기능 확장
- 메뉴 정보 수집
- 리뷰 데이터 수집
- 영업시간 실시간 업데이트
### 9.3 성능 개선
- 백그라운드 프리페칭
- 예측 기반 캐싱
- CDN 활용
## 10. 마이그레이션 계획
### 10.1 단계별 적용
1. NaverLocalApiClient 구현 및 테스트
2. NaverUrlProcessor 구현
3. 기존 addRestaurantFromUrl 메서드 리팩토링
4. UI 업데이트 (로딩 상태, 에러 처리)
5. 프로덕션 배포
### 10.2 하위 호환성
- 기존 NaverMapParser는 그대로 유지
- 새로운 기능은 옵트인 방식으로 제공
- 점진적 마이그레이션 지원