fix: API 응답 파싱 오류 수정 및 에러 처리 개선

주요 변경사항:
- 창고 관리 API 응답 구조와 DTO 불일치 수정
  - WarehouseLocationDto에 code, manager_phone 필드 추가
  - RemoteDataSource에서 API 응답을 DTO 구조에 맞게 변환
- 회사 관리 API 응답 파싱 오류 수정
  - CompanyResponse의 필수 필드를 nullable로 변경
  - PaginatedResponse 구조 매핑 로직 개선
- 에러 처리 및 로깅 개선
  - Service Layer에 상세 에러 로깅 추가
  - Controller에서 에러 타입별 처리
- 새로운 유틸리티 추가
  - ResponseInterceptor: API 응답 정규화
  - DebugLogger: 디버깅 도구
  - HealthCheckService: 서버 상태 확인
- 문서화
  - API 통합 테스트 가이드
  - 에러 분석 보고서
  - 리팩토링 계획서
This commit is contained in:
JiWoong Sul
2025-07-31 19:15:39 +09:00
parent ad2c699ff7
commit f08b7fec79
89 changed files with 10521 additions and 892 deletions

View File

@@ -4,6 +4,7 @@ import '../../../core/config/environment.dart';
import 'interceptors/auth_interceptor.dart';
import 'interceptors/error_interceptor.dart';
import 'interceptors/logging_interceptor.dart';
import 'interceptors/response_interceptor.dart';
/// API 클라이언트 클래스
class ApiClient {
@@ -18,15 +19,20 @@ class ApiClient {
ApiClient._internal() {
try {
print('[ApiClient] 초기화 시작');
_dio = Dio(_baseOptions);
print('[ApiClient] Dio 인스턴스 생성 완료');
print('[ApiClient] Base URL: ${_dio.options.baseUrl}');
print('[ApiClient] Connect Timeout: ${_dio.options.connectTimeout}');
print('[ApiClient] Receive Timeout: ${_dio.options.receiveTimeout}');
_setupInterceptors();
} catch (e) {
print('Error while creating ApiClient');
print('Stack trace:');
print(StackTrace.current);
print('[ApiClient] 인터셉터 설정 완료');
} catch (e, stackTrace) {
print('[ApiClient] ⚠️ 에러 발생: $e');
print('[ApiClient] Stack trace: $stackTrace');
// 기본값으로 초기화
_dio = Dio(BaseOptions(
baseUrl: 'http://localhost:8080/api/v1',
baseUrl: 'https://superport.naturebridgeai.com/api/v1',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
@@ -35,6 +41,7 @@ class ApiClient {
},
));
_setupInterceptors();
print('[ApiClient] 기본값으로 초기화 완료');
}
}
@@ -59,7 +66,7 @@ class ApiClient {
} catch (e) {
// Environment가 초기화되지 않은 경우 기본값 사용
return BaseOptions(
baseUrl: 'http://localhost:8080/api/v1',
baseUrl: 'https://superport.naturebridgeai.com/api/v1',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
@@ -77,13 +84,7 @@ class ApiClient {
void _setupInterceptors() {
_dio.interceptors.clear();
// 인증 인터셉터
_dio.interceptors.add(AuthInterceptor());
// 에러 처리 인터셉터
_dio.interceptors.add(ErrorInterceptor());
// 로깅 인터셉터 (개발 환경에서만)
// 로깅 인터셉터 (개발 환경에서만) - 가장 먼저 추가하여 모든 요청/응답을 로깅
try {
if (Environment.enableLogging && kDebugMode) {
_dio.interceptors.add(LoggingInterceptor());
@@ -94,6 +95,15 @@ class ApiClient {
_dio.interceptors.add(LoggingInterceptor());
}
}
// 인증 인터셉터 - 요청에 토큰 추가 및 401 처리
_dio.interceptors.add(AuthInterceptor(_dio));
// 응답 정규화 인터셉터 - 성공 응답을 일관된 형식으로 변환
_dio.interceptors.add(ResponseInterceptor());
// 에러 처리 인터셉터 - 마지막에 추가하여 모든 에러를 캐치
_dio.interceptors.add(ErrorInterceptor());
}
/// 토큰 업데이트
@@ -133,6 +143,9 @@ class ApiClient {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) {
print('[ApiClient] POST 요청 시작: $path');
print('[ApiClient] 요청 데이터: $data');
return _dio.post<T>(
path,
data: data,
@@ -141,7 +154,18 @@ class ApiClient {
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
);
).then((response) {
print('[ApiClient] POST 응답 수신: ${response.statusCode}');
return response;
}).catchError((error) {
print('[ApiClient] POST 에러 발생: $error');
if (error is DioException) {
print('[ApiClient] DioException 타입: ${error.type}');
print('[ApiClient] DioException 메시지: ${error.message}');
print('[ApiClient] DioException 에러: ${error.error}');
}
throw error;
});
}
/// PUT 요청