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.

236 lines
7.3 KiB

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart' hide Response;
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:problem_check_system/app/routes/app_routes.dart';
import 'package:problem_check_system/core/utils/constants/api_endpoints.dart';
import 'package:problem_check_system/data/repositories/auth_repository.dart';
// DioProvider 是一个 GetxService,确保它在应用生命周期内是单例的。
// 它负责初始化和配置 Dio 实例,并添加所有拦截器。
class HttpProvider extends GetxService {
late final Dio _dio;
@override
Future<void> onInit() async {
super.onInit();
_initDio();
}
// 初始化 Dio 并配置基础选项。
void _initDio() {
_dio = Dio(
BaseOptions(
baseUrl: ApiEndpoints.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 [_getErrorInterceptor(), if (kDebugMode) _getLoggerInterceptor()];
}
/// 日志拦截器:在调试模式下打印详细的请求和响应日志。
Interceptor _getLoggerInterceptor() {
return PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
error: true,
compact: false,
maxWidth: 90,
);
}
/// 错误拦截器:处理通用的网络和服务器端错误,并显示 Snackbar 提示。
Interceptor _getErrorInterceptor() {
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 尚未初始化(例如在登录或注册时),则跳过添加认证头。
Get.snackbar(
'认证过期',
'请重新手动登录',
colorText: Colors.white,
backgroundColor: Colors.red,
snackPosition: SnackPosition.TOP,
);
}
return handler.next(options);
},
onError: (error, handler) {
// 处理网络连接超时或未知网络错误。
if (error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout) {
Get.snackbar(
'网络超时',
'请检查网络连接后重试',
colorText: Colors.white,
backgroundColor: Colors.red,
snackPosition: SnackPosition.TOP,
);
} else if (error.type == DioExceptionType.unknown) {
Get.snackbar(
'网络异常',
'请检查网络连接后重试',
colorText: Colors.white,
backgroundColor: Colors.red,
snackPosition: SnackPosition.TOP,
);
}
// 这部分代码只会在 AuthInterceptor 没有处理的错误(即非 401)时执行。
if (error.response != null) {
final message = _handleStatusCode(error.response!);
Get.snackbar(
'请求错误',
message,
colorText: Colors.white,
backgroundColor: Colors.red,
snackPosition: SnackPosition.TOP,
);
}
return handler.next(error);
},
);
}
/// 辅助方法:根据 HTTP 状态码返回用户友好的错误信息。
String _handleStatusCode(Response response) {
switch (response.statusCode) {
case 400:
return response.data?['detail'] ?? '请求参数错误';
case 401:
final authRepository = Get.find<AuthRepository>();
authRepository.clearAuthData();
Get.offAllNamed(AppRoutes.login);
return '未授权,请重新登录';
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,
);
}
}