Line data Source code
1 : import 'package:dio/dio.dart';
2 : import 'package:cookie_jar/cookie_jar.dart';
3 : import 'package:dio_cookie_manager/dio_cookie_manager.dart';
4 : import 'package:flutter/material.dart';
5 : import 'package:flutter/foundation.dart';
6 : import '../config/api_config.dart';
7 : import '../services/auth_service.dart';
8 : import '../screens/auth/login_screen.dart';
9 : import '../main.dart'; // navigatorKey를 사용하기 위한 import
10 :
11 : class ApiService {
12 9 : static final ApiService _instance = ApiService._internal();
13 6 : factory ApiService() => _instance;
14 :
15 : late Dio _dio;
16 : final CookieJar _cookieJar = CookieJar();
17 : String? _token;
18 :
19 3 : ApiService._internal() {
20 9 : _dio = Dio(BaseOptions(
21 : baseUrl: ApiConfig.baseUrl,
22 : connectTimeout: const Duration(seconds: 10),
23 : receiveTimeout: const Duration(seconds: 10),
24 : contentType: 'application/json',
25 : ));
26 :
27 : // 쿠키 관리자 설정 (웹 환경에서는 사용하지 않음)
28 : if (!kIsWeb) {
29 15 : _dio.interceptors.add(CookieManager(_cookieJar));
30 : }
31 :
32 : // 로그 인터셉터 (디버깅용)
33 12 : _dio.interceptors.add(LogInterceptor(
34 : requestBody: true,
35 : responseBody: true,
36 : ));
37 : }
38 :
39 : // 토큰 설정
40 0 : void setToken(String token) {
41 0 : _token = token;
42 0 : _dio.options.headers['Authorization'] = 'Bearer $token';
43 : }
44 :
45 : // GET 요청
46 0 : Future<dynamic> get(String path, {Map<String, dynamic>? queryParameters}) async {
47 : try {
48 0 : final response = await _dio.get(
49 : path,
50 : queryParameters: queryParameters,
51 : );
52 0 : return response.data;
53 0 : } on DioException catch (e) {
54 0 : _handleError(e);
55 : rethrow;
56 : }
57 : }
58 :
59 : // POST 요청
60 0 : Future<dynamic> post(String path, {dynamic data}) async {
61 : try {
62 0 : final response = await _dio.post(
63 : path,
64 0 : data: data ?? {},
65 : );
66 0 : return response.data;
67 0 : } on DioException catch (e) {
68 0 : _handleError(e);
69 : rethrow;
70 : }
71 : }
72 :
73 : // PUT 요청
74 0 : Future<dynamic> put(String path, {dynamic data}) async {
75 : try {
76 0 : final response = await _dio.put(
77 : path,
78 : data: data,
79 : );
80 0 : return response.data;
81 0 : } on DioException catch (e) {
82 0 : _handleError(e);
83 : rethrow;
84 : }
85 : }
86 :
87 : // DELETE 요청
88 0 : Future<dynamic> delete(String path) async {
89 : try {
90 0 : final response = await _dio.delete(path);
91 0 : return response.data;
92 0 : } on DioException catch (e) {
93 0 : _handleError(e);
94 : rethrow;
95 : }
96 : }
97 :
98 : // 에러 처리
99 0 : void _handleError(DioException e) {
100 0 : if (e.response != null) {
101 0 : print('API 에러: ${e.response?.statusCode} - ${e.response?.data}');
102 :
103 : // 401 오류 (인증 실패) 처리
104 0 : if (e.response?.statusCode == 401) {
105 0 : print('인증 오류: 토큰이 유효하지 않습니다. 로그아웃 처리합니다.');
106 0 : _handleUnauthorized();
107 : }
108 : } else {
109 0 : print('API 요청 에러: ${e.message}');
110 : }
111 : }
112 :
113 : // 인증 오류 처리 (401)
114 0 : void _handleUnauthorized() {
115 : // 다음 프레임에서 실행하여 현재 API 호출 스택이 완료되도록 함
116 0 : Future.microtask(() async {
117 : try {
118 : // AuthService 인스턴스 가져오기
119 0 : final authService = AuthService();
120 :
121 : // 로그아웃 처리
122 0 : await authService.logout();
123 :
124 : // 전역 네비게이터 키를 사용하여 로그인 화면으로 이동
125 0 : if (navigatorKey.currentContext != null) {
126 0 : Navigator.of(navigatorKey.currentContext!).pushAndRemoveUntil(
127 0 : MaterialPageRoute(builder: (context) => const LoginScreen()),
128 0 : (route) => false,
129 : );
130 : }
131 : } catch (e) {
132 0 : print('로그아웃 처리 중 오류 발생: $e');
133 : }
134 : });
135 : }
136 : }
|