| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | import 'dart:io'; | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  | import 'dart:ui'; | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | import 'package:dio/dio.dart'; | 
					
						
							| 
									
										
										
										
											2025-08-21 16:44:24 +08:00
										 |  |  |  | import 'package:qhd_prevention/customWidget/toast_util.dart'; | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  | import 'package:qhd_prevention/tools/tools.dart'; | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | /// 全局接口异常
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | class ApiException implements Exception { | 
					
						
							|  |  |  |  |   final String result; | 
					
						
							|  |  |  |  |   final String message; | 
					
						
							|  |  |  |  |   ApiException(this.result, this.message); | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |   @override | 
					
						
							|  |  |  |  |   String toString() => 'ApiException($result): $message'; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// HTTP 方法枚举
 | 
					
						
							|  |  |  |  | enum Method { get, post, put, delete } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | /// HTTP 管理器 单例
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  | class HttpManager { | 
					
						
							|  |  |  |  |   HttpManager._internal() { | 
					
						
							|  |  |  |  |     _dio = Dio(BaseOptions( | 
					
						
							| 
									
										
										
										
											2025-09-05 09:16:54 +08:00
										 |  |  |  |       connectTimeout: const Duration(milliseconds: 20000), | 
					
						
							|  |  |  |  |       receiveTimeout: const Duration(milliseconds: 20000), | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |       headers: { | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |         'Content-Type': Headers.formUrlEncodedContentType, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |       }, | 
					
						
							|  |  |  |  |     )); | 
					
						
							|  |  |  |  |     _initInterceptors(); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   static final HttpManager _instance = HttpManager._internal(); | 
					
						
							|  |  |  |  |   factory HttpManager() => _instance; | 
					
						
							|  |  |  |  |   late final Dio _dio; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  |   // 添加401处理回调
 | 
					
						
							|  |  |  |  |   static VoidCallback? onUnauthorized; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |   void _initInterceptors() { | 
					
						
							|  |  |  |  |     _dio.interceptors | 
					
						
							|  |  |  |  |       ..add(LogInterceptor(request: true, responseBody: true, error: true)) | 
					
						
							|  |  |  |  |       ..add(InterceptorsWrapper(onError: (err, handler) { | 
					
						
							| 
									
										
										
										
											2025-07-22 13:34:34 +08:00
										 |  |  |  |         // TODO 暂不处理
 | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  |         // 捕获401错误
 | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |         if (err.response?.statusCode == 401) { | 
					
						
							|  |  |  |  |           // 触发全局登出回调
 | 
					
						
							|  |  |  |  |           onUnauthorized?.call(); | 
					
						
							|  |  |  |  |           // 创建自定义异常
 | 
					
						
							|  |  |  |  |           final apiException = ApiException( | 
					
						
							|  |  |  |  |               '提示', | 
					
						
							|  |  |  |  |               '您的账号已在其他设备登录,已自动下线' | 
					
						
							|  |  |  |  |           ); | 
					
						
							|  |  |  |  |           // 直接抛出业务异常,跳过后续错误处理
 | 
					
						
							|  |  |  |  |           return handler.reject( | 
					
						
							|  |  |  |  |             DioException( | 
					
						
							|  |  |  |  |               requestOptions: err.requestOptions, | 
					
						
							|  |  |  |  |               error: apiException, | 
					
						
							|  |  |  |  |               response: err.response, | 
					
						
							|  |  |  |  |               type: DioExceptionType.badResponse, | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |           ); | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |         handler.next(err); | 
					
						
							|  |  |  |  |       })); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |   /// 通用请求方法,返回完整后台 JSON
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |   Future<Map<String, dynamic>> request( | 
					
						
							|  |  |  |  |       String baseUrl, | 
					
						
							|  |  |  |  |       String path, { | 
					
						
							|  |  |  |  |         Method method = Method.post, | 
					
						
							|  |  |  |  |         Map<String, dynamic>? data, | 
					
						
							|  |  |  |  |         Map<String, dynamic>? params, | 
					
						
							|  |  |  |  |         CancelToken? cancelToken, | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |         bool withToken = false, //  新增可选参数,默认 false
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |       }) async { | 
					
						
							|  |  |  |  |     Response resp; | 
					
						
							|  |  |  |  |     final url = baseUrl + path; | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     // 动态 headers
 | 
					
						
							|  |  |  |  |     final headers = { | 
					
						
							|  |  |  |  |       'Content-Type': Headers.formUrlEncodedContentType, | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  |     if (withToken) { | 
					
						
							|  |  |  |  |       final token = SessionService.instance.studyToken; | 
					
						
							|  |  |  |  |       if (token != null && token.isNotEmpty) { | 
					
						
							|  |  |  |  |         headers['Token'] = token; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     final options = Options( | 
					
						
							|  |  |  |  |       method: method.name.toUpperCase(), | 
					
						
							|  |  |  |  |       contentType: Headers.formUrlEncodedContentType, | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |       headers: headers, | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     try { | 
					
						
							|  |  |  |  |       switch (method) { | 
					
						
							|  |  |  |  |         case Method.get: | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |           resp = await _dio.get(url, | 
					
						
							|  |  |  |  |               queryParameters: {...?params, ...?data}, | 
					
						
							|  |  |  |  |               cancelToken: cancelToken, | 
					
						
							|  |  |  |  |               options: options); | 
					
						
							|  |  |  |  |           break; | 
					
						
							|  |  |  |  |         case Method.post: | 
					
						
							|  |  |  |  |           resp = await _dio.post(url, | 
					
						
							|  |  |  |  |               data: data, | 
					
						
							|  |  |  |  |               queryParameters: params, | 
					
						
							|  |  |  |  |               cancelToken: cancelToken, | 
					
						
							|  |  |  |  |               options: options); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |           break; | 
					
						
							|  |  |  |  |         case Method.put: | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |           resp = await _dio.put(url, | 
					
						
							|  |  |  |  |               data: data, | 
					
						
							|  |  |  |  |               queryParameters: params, | 
					
						
							|  |  |  |  |               cancelToken: cancelToken, | 
					
						
							|  |  |  |  |               options: options); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |           break; | 
					
						
							|  |  |  |  |         case Method.delete: | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |           resp = await _dio.delete(url, | 
					
						
							|  |  |  |  |               queryParameters: params, | 
					
						
							|  |  |  |  |               cancelToken: cancelToken, | 
					
						
							|  |  |  |  |               options: options); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |           break; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |     } on DioException catch (e) { | 
					
						
							| 
									
										
										
										
											2025-08-29 09:52:48 +08:00
										 |  |  |  |       if (e.error is ApiException) throw e.error as ApiException; | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |       throw ApiException('network_error', e.message ?? e.toString()); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     final json = resp.data is Map<String, dynamic> | 
					
						
							|  |  |  |  |         ? resp.data as Map<String, dynamic> | 
					
						
							|  |  |  |  |         : <String, dynamic>{}; | 
					
						
							|  |  |  |  |     return json; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | /// 上传图片扩展
 | 
					
						
							|  |  |  |  | extension HttpManagerUpload on HttpManager { | 
					
						
							|  |  |  |  |   Future<Map<String, dynamic>> uploadFaceImage({ | 
					
						
							|  |  |  |  |     required String baseUrl, | 
					
						
							|  |  |  |  |     required String path, | 
					
						
							|  |  |  |  |     required Map<String, dynamic> fromData, | 
					
						
							|  |  |  |  |     CancelToken? cancelToken, | 
					
						
							|  |  |  |  |   }) async { | 
					
						
							|  |  |  |  |     final form = FormData.fromMap(fromData); | 
					
						
							|  |  |  |  |     try { | 
					
						
							|  |  |  |  |       final resp = await _dio.post( | 
					
						
							|  |  |  |  |         baseUrl + path, | 
					
						
							|  |  |  |  |         data: form, | 
					
						
							|  |  |  |  |         cancelToken: cancelToken, | 
					
						
							|  |  |  |  |         options: Options( | 
					
						
							|  |  |  |  |           method: Method.post.name.toUpperCase(), | 
					
						
							|  |  |  |  |           contentType: 'multipart/form-data', | 
					
						
							|  |  |  |  |         ), | 
					
						
							|  |  |  |  |       ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       final json = resp.data is Map<String, dynamic> | 
					
						
							|  |  |  |  |           ? resp.data as Map<String, dynamic> | 
					
						
							|  |  |  |  |           : <String, dynamic>{}; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       return json; | 
					
						
							|  |  |  |  |     } on DioException catch (e) { | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  |       // 如果已经是ApiException类型(401转换的)
 | 
					
						
							|  |  |  |  |       if (e.error is ApiException) { | 
					
						
							|  |  |  |  |         throw e.error as ApiException; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |       throw ApiException('network_error', e.message ?? e.toString()); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  | } |