주요 변경사항: - CLAUDE.md: 프로젝트 규칙 v2.0으로 업데이트, 아키텍처 명확화 - 불필요한 문서 제거: NEXT_TASKS.md, TEST_PROGRESS.md, test_results 파일들 - 테스트 시스템 개선: 실제 API 테스트 스위트 추가 (15개 새 테스트 파일) - License 관리: DTO 모델 개선, API 응답 처리 최적화 - 에러 처리: Interceptor 로직 강화, 상세 로깅 추가 - Company/User/Warehouse 테스트: 자동화 테스트 안정성 향상 - Phone Utils: 전화번호 포맷팅 로직 개선 - Overview Controller: 대시보드 데이터 로딩 최적화 - Analysis Options: Flutter 린트 규칙 추가 테스트 개선: - company_real_api_test.dart: 실제 API 회사 관리 테스트 - equipment_in/out_real_api_test.dart: 장비 입출고 API 테스트 - license_real_api_test.dart: 라이선스 관리 API 테스트 - user_real_api_test.dart: 사용자 관리 API 테스트 - warehouse_location_real_api_test.dart: 창고 위치 API 테스트 - filter_sort_test.dart: 필터링/정렬 기능 테스트 - pagination_test.dart: 페이지네이션 테스트 - interactive_search_test.dart: 검색 기능 테스트 - overview_dashboard_test.dart: 대시보드 통합 테스트 코드 품질: - 모든 서비스에 에러 처리 강화 - DTO 모델 null safety 개선 - 테스트 커버리지 확대 - 불필요한 로그 파일 제거로 리포지토리 정리 Co-Authored-By: Claude <noreply@anthropic.com>
166 lines
4.8 KiB
Dart
166 lines
4.8 KiB
Dart
import 'dart:async';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../core/config/environment.dart';
|
|
import '../data/datasources/remote/api_client.dart';
|
|
|
|
// 조건부 import - 웹 플랫폼에서만 dart:js 사용
|
|
import 'health_check_service_stub.dart'
|
|
if (dart.library.js) 'health_check_service_web.dart' as platform;
|
|
|
|
/// API 헬스체크 테스트를 위한 서비스
|
|
class HealthCheckService {
|
|
final ApiClient _apiClient;
|
|
Timer? _healthCheckTimer;
|
|
bool _isMonitoring = false;
|
|
|
|
HealthCheckService({ApiClient? apiClient})
|
|
: _apiClient = apiClient ?? ApiClient();
|
|
|
|
/// 헬스체크 API 호출
|
|
Future<Map<String, dynamic>> checkHealth() async {
|
|
try {
|
|
debugPrint('=== 헬스체크 시작 ===');
|
|
debugPrint('API Base URL: ${Environment.apiBaseUrl}');
|
|
debugPrint('Full URL: ${Environment.apiBaseUrl}/health');
|
|
|
|
final response = await _apiClient.get('/health');
|
|
|
|
debugPrint('응답 상태 코드: ${response.statusCode}');
|
|
debugPrint('응답 데이터: ${response.data}');
|
|
|
|
return {
|
|
'success': true,
|
|
'data': response.data,
|
|
'statusCode': response.statusCode,
|
|
};
|
|
} on DioException catch (e) {
|
|
debugPrint('=== DioException 발생 ===');
|
|
debugPrint('에러 타입: ${e.type}');
|
|
debugPrint('에러 메시지: ${e.message}');
|
|
debugPrint('에러 응답: ${e.response?.data}');
|
|
debugPrint('에러 상태 코드: ${e.response?.statusCode}');
|
|
|
|
// CORS 에러인지 확인
|
|
if (e.type == DioExceptionType.connectionError ||
|
|
e.type == DioExceptionType.unknown) {
|
|
debugPrint('⚠️ CORS 또는 네트워크 연결 문제일 가능성이 있습니다.');
|
|
}
|
|
|
|
return {
|
|
'success': false,
|
|
'error': e.message ?? '알 수 없는 에러',
|
|
'errorType': e.type.toString(),
|
|
'statusCode': e.response?.statusCode,
|
|
'responseData': e.response?.data,
|
|
};
|
|
} catch (e) {
|
|
debugPrint('=== 일반 에러 발생 ===');
|
|
debugPrint('에러: $e');
|
|
|
|
return {
|
|
'success': false,
|
|
'error': e.toString(),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// 직접 Dio로 테스트 (인터셉터 없이)
|
|
Future<Map<String, dynamic>> checkHealthDirect() async {
|
|
try {
|
|
debugPrint('=== 직접 Dio 헬스체크 시작 ===');
|
|
|
|
final dio = Dio(BaseOptions(
|
|
baseUrl: Environment.apiBaseUrl,
|
|
connectTimeout: const Duration(seconds: 10),
|
|
receiveTimeout: const Duration(seconds: 10),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
},
|
|
));
|
|
|
|
// 로깅 인터셉터만 추가
|
|
dio.interceptors.add(LogInterceptor(
|
|
requestBody: true,
|
|
responseBody: true,
|
|
requestHeader: true,
|
|
responseHeader: true,
|
|
error: true,
|
|
));
|
|
|
|
final response = await dio.get('/health');
|
|
|
|
return {
|
|
'success': true,
|
|
'data': response.data,
|
|
'statusCode': response.statusCode,
|
|
};
|
|
} catch (e) {
|
|
debugPrint('직접 Dio 에러: $e');
|
|
return {
|
|
'success': false,
|
|
'error': e.toString(),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// 주기적인 헬스체크 시작 (30초마다)
|
|
void startPeriodicHealthCheck() {
|
|
if (_isMonitoring) return;
|
|
|
|
debugPrint('=== 주기적 헬스체크 모니터링 시작 ===');
|
|
_isMonitoring = true;
|
|
|
|
// 즉시 한 번 체크
|
|
_performHealthCheck();
|
|
|
|
// 30초마다 체크
|
|
_healthCheckTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
_performHealthCheck();
|
|
});
|
|
}
|
|
|
|
/// 주기적인 헬스체크 중지
|
|
void stopPeriodicHealthCheck() {
|
|
debugPrint('=== 주기적 헬스체크 모니터링 중지 ===');
|
|
_isMonitoring = false;
|
|
_healthCheckTimer?.cancel();
|
|
_healthCheckTimer = null;
|
|
}
|
|
|
|
/// 헬스체크 수행 및 알림 표시
|
|
Future<void> _performHealthCheck() async {
|
|
final result = await checkHealth();
|
|
|
|
if (!result['success'] || result['data']?['status'] != 'healthy') {
|
|
_showBrowserNotification(result);
|
|
}
|
|
}
|
|
|
|
/// 브라우저 알림 표시
|
|
void _showBrowserNotification(Map<String, dynamic> result) {
|
|
if (!kIsWeb) return;
|
|
|
|
try {
|
|
final status = result['data']?['status'] ?? 'unreachable';
|
|
final message = result['error'] ?? 'Server status: $status';
|
|
|
|
debugPrint('=== 브라우저 알림 표시 ===');
|
|
debugPrint('상태: $status');
|
|
debugPrint('메시지: $message');
|
|
|
|
// 플랫폼별 알림 처리
|
|
platform.showNotification(
|
|
'Server Health Check Alert',
|
|
message,
|
|
status,
|
|
);
|
|
} catch (e) {
|
|
debugPrint('브라우저 알림 표시 실패: $e');
|
|
}
|
|
}
|
|
|
|
/// 모니터링 상태 확인
|
|
bool get isMonitoring => _isMonitoring;
|
|
} |