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>
This commit is contained in:
79
lib/core/network/interceptors/logging_interceptor.dart
Normal file
79
lib/core/network/interceptors/logging_interceptor.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 로깅 인터셉터
|
||||
///
|
||||
/// 네트워크 요청과 응답을 로그로 기록합니다.
|
||||
/// 디버그 모드에서만 활성화됩니다.
|
||||
class LoggingInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
final uri = options.uri;
|
||||
final method = options.method;
|
||||
final headers = options.headers;
|
||||
|
||||
print('═══════════════════════════════════════════════════════════════');
|
||||
print('>>> REQUEST [$method] $uri');
|
||||
print('>>> Headers: $headers');
|
||||
|
||||
if (options.data != null) {
|
||||
print('>>> Body: ${options.data}');
|
||||
}
|
||||
|
||||
if (options.queryParameters.isNotEmpty) {
|
||||
print('>>> Query Parameters: ${options.queryParameters}');
|
||||
}
|
||||
}
|
||||
|
||||
return handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
final statusCode = response.statusCode;
|
||||
final uri = response.requestOptions.uri;
|
||||
|
||||
print('<<< RESPONSE [$statusCode] $uri');
|
||||
|
||||
if (response.headers.map.isNotEmpty) {
|
||||
print('<<< Headers: ${response.headers.map}');
|
||||
}
|
||||
|
||||
// 응답 본문은 너무 길 수 있으므로 처음 500자만 출력
|
||||
final responseData = response.data.toString();
|
||||
if (responseData.length > 500) {
|
||||
print('<<< Body: ${responseData.substring(0, 500)}...(truncated)');
|
||||
} else {
|
||||
print('<<< Body: $responseData');
|
||||
}
|
||||
|
||||
print('═══════════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
return handler.next(response);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
final uri = err.requestOptions.uri;
|
||||
final message = err.message;
|
||||
|
||||
print('═══════════════════════════════════════════════════════════════');
|
||||
print('!!! ERROR $uri');
|
||||
print('!!! Message: $message');
|
||||
|
||||
if (err.response != null) {
|
||||
print('!!! Status Code: ${err.response!.statusCode}');
|
||||
print('!!! Response: ${err.response!.data}');
|
||||
}
|
||||
|
||||
print('!!! Error Type: ${err.type}');
|
||||
print('═══════════════════════════════════════════════════════════════');
|
||||
}
|
||||
|
||||
return handler.next(err);
|
||||
}
|
||||
}
|
||||
97
lib/core/network/interceptors/retry_interceptor.dart
Normal file
97
lib/core/network/interceptors/retry_interceptor.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../network_config.dart';
|
||||
import '../../errors/network_exceptions.dart';
|
||||
|
||||
/// 재시도 인터셉터
|
||||
///
|
||||
/// 네트워크 오류 발생 시 자동으로 재시도합니다.
|
||||
/// 지수 백오프(exponential backoff) 알고리즘을 사용합니다.
|
||||
class RetryInterceptor extends Interceptor {
|
||||
final Dio dio;
|
||||
|
||||
RetryInterceptor({required this.dio});
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
// 재시도 카운트 확인
|
||||
final retryCount = err.requestOptions.extra['retryCount'] ?? 0;
|
||||
|
||||
// 재시도 가능한 오류인지 확인
|
||||
if (_shouldRetry(err) && retryCount < NetworkConfig.maxRetries) {
|
||||
try {
|
||||
// 지수 백오프 계산
|
||||
final delay = _calculateBackoffDelay(retryCount);
|
||||
|
||||
print('RetryInterceptor: 재시도 ${retryCount + 1}/${NetworkConfig.maxRetries} - ${delay}ms 대기');
|
||||
|
||||
// 대기
|
||||
await Future.delayed(Duration(milliseconds: delay));
|
||||
|
||||
// 재시도 카운트 증가
|
||||
err.requestOptions.extra['retryCount'] = retryCount + 1;
|
||||
|
||||
// 재시도 실행
|
||||
final response = await dio.fetch(err.requestOptions);
|
||||
|
||||
return handler.resolve(response);
|
||||
} catch (e) {
|
||||
// 재시도도 실패한 경우
|
||||
if (retryCount + 1 >= NetworkConfig.maxRetries) {
|
||||
return handler.reject(
|
||||
DioException(
|
||||
requestOptions: err.requestOptions,
|
||||
error: MaxRetriesExceededException(originalError: e),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handler.next(err);
|
||||
}
|
||||
|
||||
/// 재시도 가능한 오류인지 판단
|
||||
bool _shouldRetry(DioException err) {
|
||||
// 네이버 관련 요청은 재시도하지 않음
|
||||
final url = err.requestOptions.uri.toString();
|
||||
if (url.contains('naver.com') || url.contains('naver.me')) {
|
||||
print('RetryInterceptor: 네이버 API 요청은 재시도하지 않음 - $url');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 네트워크 연결 오류
|
||||
if (err.type == DioExceptionType.connectionTimeout ||
|
||||
err.type == DioExceptionType.sendTimeout ||
|
||||
err.type == DioExceptionType.receiveTimeout ||
|
||||
err.type == DioExceptionType.connectionError) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 서버 오류 (5xx)
|
||||
final statusCode = err.response?.statusCode;
|
||||
if (statusCode != null && statusCode >= 500 && statusCode < 600) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 429 Too Many Requests는 재시도하지 않음
|
||||
// 재시도하면 더 많은 요청이 발생하여 문제가 악화됨
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 지수 백오프 지연 시간 계산
|
||||
int _calculateBackoffDelay(int retryCount) {
|
||||
final baseDelay = NetworkConfig.retryDelayMillis;
|
||||
final multiplier = NetworkConfig.retryDelayMultiplier;
|
||||
|
||||
// 지수 백오프: delay = baseDelay * (multiplier ^ retryCount)
|
||||
final exponentialDelay = baseDelay * pow(multiplier, retryCount);
|
||||
|
||||
// 지터(jitter) 추가로 동시 재시도 방지
|
||||
final jitter = Random().nextInt(1000);
|
||||
|
||||
return exponentialDelay.toInt() + jitter;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user