You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
8.8 KiB
265 lines
8.8 KiB
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 HttpProvider extends GetxService { |
|
static const String _baseUrl = 'https://xh.anxincloud.cn'; |
|
|
|
late final Dio _dio; |
|
|
|
@override |
|
Future<void> 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<Interceptor> _getInterceptors() { |
|
return [ |
|
_getAuthInterceptor(), |
|
_getErrorInterceptor(), |
|
if (kDebugMode) _getLoggerInterceptor(), |
|
]; |
|
} |
|
|
|
/// 认证拦截器:处理请求头中的 token 添加和 401 错误重试。 |
|
Interceptor _getAuthInterceptor() { |
|
return InterceptorsWrapper( |
|
// 在请求发送前执行 |
|
onRequest: (options, handler) async { |
|
try { |
|
// 尝试获取 AuthRepository 并添加 token 到请求头。 |
|
final authRepository = Get.find<AuthRepository>(); |
|
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<AuthRepository>(); |
|
// 尝试刷新 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<AuthController>(); |
|
await authController.logout(); |
|
} catch (e) { |
|
// 如果 AuthController 找不到,作为备用方案手动清除数据并导航。 |
|
final authRepository = Get.find<AuthRepository>(); |
|
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<Response> get( |
|
String path, { |
|
Map<String, dynamic>? queryParameters, |
|
Options? options, |
|
CancelToken? cancelToken, |
|
ProgressCallback? onReceiveProgress, |
|
}) async { |
|
return await _dio.get( |
|
path, |
|
queryParameters: queryParameters, |
|
options: options, |
|
cancelToken: cancelToken, |
|
onReceiveProgress: onReceiveProgress, |
|
); |
|
} |
|
|
|
/// 发送 POST 请求 |
|
Future<Response> post( |
|
String path, { |
|
dynamic data, |
|
Map<String, dynamic>? 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<Response> put( |
|
String path, { |
|
dynamic data, |
|
Map<String, dynamic>? 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<Response> delete( |
|
String path, { |
|
dynamic data, |
|
Map<String, dynamic>? queryParameters, |
|
Options? options, |
|
CancelToken? cancelToken, |
|
}) async { |
|
return await _dio.delete( |
|
path, |
|
data: data, |
|
queryParameters: queryParameters, |
|
options: options, |
|
cancelToken: cancelToken, |
|
); |
|
} |
|
}
|
|
|