import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart' hide Response; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:problem_check_system/data/repositories/auth_repository.dart'; import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; // DioProvider 是一个 GetxService,确保它在应用生命周期内是单例的。 // 它负责初始化和配置 Dio 实例,并添加所有拦截器。 class DioProvider extends GetxService { static const String _baseUrl = 'https://xh.anxincloud.cn'; late final Dio _dio; @override Future onInit() async { super.onInit(); _initDio(); } // 初始化 Dio 并配置基础选项。 void _initDio() { _dio = Dio( BaseOptions( baseUrl: _baseUrl, connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), sendTimeout: const Duration(seconds: 30), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ), ); // 添加拦截器。拦截器的顺序非常关键: // 1. 认证拦截器 (AuthInterceptor): 负责添加 token 和处理 401 错误,优先级最高。 // 2. 错误拦截器 (ErrorInterceptor): 处理通用错误,作为所有其他拦截器之后的最终捕获。 // 3. 日志拦截器 (LoggerInterceptor): 在调试模式下打印详细日志,方便开发。 _dio.interceptors.addAll(_getInterceptors()); } List _getInterceptors() { return [ _getAuthInterceptor(), _getErrorInterceptor(), if (kDebugMode) _getLoggerInterceptor(), ]; } /// 认证拦截器:处理请求头中的 token 添加和 401 错误重试。 Interceptor _getAuthInterceptor() { return InterceptorsWrapper( // 在请求发送前执行 onRequest: (options, handler) async { try { // 尝试获取 AuthRepository 并添加 token 到请求头。 final authRepository = Get.find(); final token = authRepository.getToken(); if (token != null && token.isNotEmpty) { options.headers['Authorization'] = 'Bearer $token'; } } catch (e) { // 如果 AuthRepository 尚未初始化(例如在登录或注册时),则跳过添加认证头。 debugPrint('AuthRepository 未找到。跳过认证头。'); } return handler.next(options); }, // 在接收到错误时执行 onError: (error, handler) async { // 专门处理 401 Unauthorized 错误。 if (error.response?.statusCode == 401) { try { final authRepository = Get.find(); // 尝试刷新 token。 await authRepository.refreshToken(); // 如果刷新成功,更新请求头并重试原始请求。 final newOptions = Options( method: error.requestOptions.method, headers: error.requestOptions.headers ..['Authorization'] = 'Bearer ${authRepository.getToken()}', ); final response = await _dio.request( error.requestOptions.path, data: error.requestOptions.data, queryParameters: error.requestOptions.queryParameters, options: newOptions, ); // 使用 handler.resolve() 返回重试的结果,阻止错误继续传递。 return handler.resolve(response); } on Exception catch (e) { debugPrint('刷新 token 失败: $e'); // 如果刷新 token 失败,清除认证数据并跳转到登录页。 try { final authController = Get.find(); await authController.logout(); } catch (e) { // 如果 AuthController 找不到,作为备用方案手动清除数据并导航。 final authRepository = Get.find(); authRepository.clearAuthData(); if (Get.currentRoute != '/login') { Get.offAllNamed('/login'); } } // 传播原始错误,让下一个拦截器(通用错误拦截器)处理。 return handler.next(error); } } // 对于所有其他非 401 的错误,将错误传递给下一个拦截器。 return handler.next(error); }, ); } /// 日志拦截器:在调试模式下打印详细的请求和响应日志。 Interceptor _getLoggerInterceptor() { return PrettyDioLogger( requestHeader: true, requestBody: true, responseHeader: true, responseBody: true, error: true, compact: false, maxWidth: 90, ); } /// 错误拦截器:处理通用的网络和服务器端错误,并显示 Snackbar 提示。 Interceptor _getErrorInterceptor() { return InterceptorsWrapper( onError: (error, handler) { // 处理网络连接超时或未知网络错误。 if (error.type == DioExceptionType.connectionTimeout || error.type == DioExceptionType.receiveTimeout || error.type == DioExceptionType.sendTimeout) { Get.snackbar('网络超时', '请检查网络连接后重试'); } else if (error.type == DioExceptionType.unknown) { Get.snackbar('网络异常', '请检查网络连接后重试'); } // 这部分代码只会在 AuthInterceptor 没有处理的错误(即非 401)时执行。 if (error.response != null) { final message = _handleStatusCode(error.response!); Get.snackbar('请求错误', message); } return handler.next(error); }, ); } /// 辅助方法:根据 HTTP 状态码返回用户友好的错误信息。 String _handleStatusCode(Response response) { switch (response.statusCode) { case 400: return response.data?['detail'] ?? '请求参数错误'; case 401: return response.data?['detail'] ?? '未授权,请重新登录'; // 注意:对于 401 错误,这行代码理想情况下不会被执行。 case 403: return response.data?['detail'] ?? '访问被拒绝'; case 404: return response.data?['detail'] ?? '请求资源不存在'; case 422: final errors = response.data?['errors']; if (errors != null && errors is Map && errors.isNotEmpty) { return errors.values.first?.first?.toString() ?? '数据验证失败'; } return response.data?['detail'] ?? '数据验证失败'; case 500: return response.data?['detail'] ?? '服务器内部错误'; case 502: return response.data?['detail'] ?? '网关错误'; case 503: return response.data?['detail'] ?? '服务不可用'; default: return response.data?['detail'] ?? '网络异常(${response.statusCode})'; } } void clear() { _dio.interceptors.clear(); } /// 新增的请求方法 /// 发送 GET 请求 Future get( String path, { Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, }) async { return await _dio.get( path, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onReceiveProgress: onReceiveProgress, ); } /// 发送 POST 请求 Future post( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) async { return await _dio.post( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); } /// 发送 PUT 请求 Future put( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) async { return await _dio.put( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); } /// 发送 DELETE 请求 Future delete( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, }) async { return await _dio.delete( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, ); } }