| 
									
										
										
										
											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-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( | 
					
						
							|  |  |  |  |       connectTimeout: const Duration(milliseconds: 10000), | 
					
						
							|  |  |  |  |       receiveTimeout: const Duration(milliseconds: 10000), | 
					
						
							|  |  |  |  |       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-07-22 13:34:34 +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, | 
					
						
							|  |  |  |  |       }) async { | 
					
						
							|  |  |  |  |     Response resp; | 
					
						
							|  |  |  |  |     final url = baseUrl + path; | 
					
						
							|  |  |  |  |     final options = Options( | 
					
						
							|  |  |  |  |       method: method.name.toUpperCase(), | 
					
						
							|  |  |  |  |       contentType: Headers.formUrlEncodedContentType, | 
					
						
							|  |  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     try { | 
					
						
							|  |  |  |  |       switch (method) { | 
					
						
							|  |  |  |  |         case Method.get: | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |           resp = await _dio.get( | 
					
						
							|  |  |  |  |             url, | 
					
						
							|  |  |  |  |             queryParameters: params, | 
					
						
							|  |  |  |  |             cancelToken: cancelToken, | 
					
						
							|  |  |  |  |             options: options, | 
					
						
							|  |  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |           break; | 
					
						
							|  |  |  |  |         case Method.put: | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +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-07-17 16:10:46 +08:00
										 |  |  |  |           resp = await _dio.delete( | 
					
						
							|  |  |  |  |             url, | 
					
						
							|  |  |  |  |             queryParameters: params, | 
					
						
							|  |  |  |  |             cancelToken: cancelToken, | 
					
						
							|  |  |  |  |             options: options, | 
					
						
							|  |  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |           break; | 
					
						
							|  |  |  |  |         case Method.post: | 
					
						
							| 
									
										
										
										
											2025-07-18 17:13:38 +08:00
										 |  |  |  |           resp = await _dio.post( | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |             url, | 
					
						
							|  |  |  |  |             data: data, | 
					
						
							|  |  |  |  |             queryParameters: params, | 
					
						
							|  |  |  |  |             cancelToken: cancelToken, | 
					
						
							|  |  |  |  |             options: options, | 
					
						
							|  |  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-17 16:10:46 +08:00
										 |  |  |  |     } 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-15 08:32:50 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // 解析返回 JSON
 | 
					
						
							|  |  |  |  |     final json = resp.data is Map<String, dynamic> | 
					
						
							|  |  |  |  |         ? resp.data as Map<String, dynamic> | 
					
						
							|  |  |  |  |         : <String, dynamic>{}; | 
					
						
							|  |  |  |  |     final result = json['result'] as String?; | 
					
						
							|  |  |  |  |     final msg = json['msg'] as String? ?? json['message'] as String? ?? ''; | 
					
						
							| 
									
										
										
										
											2025-08-06 13:43:22 +08:00
										 |  |  |  |     if (result != 'success') { | 
					
						
							|  |  |  |  |       // 非 success 都抛异常
 | 
					
						
							|  |  |  |  |       throw ApiException(result ?? 'unknown', msg); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-15 08:32:50 +08:00
										 |  |  |  |     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
										 |  |  |  | } |