Appearance
Flutter Dio 网络请求封装设计文档
本文深入解析一个基于 Dio 的 Flutter 网络请求封装方案,涵盖了基础配置、拦截器设计、错误处理、Token 刷新机制等核心功能。
一、整体架构设计
shell
lib/
├── app/
│ ├── data/
│ │ └── providers/ # API 提供者
│ │ ├── api_constants.dart # 统一管理 API 相关常量
│ │ ├── api_exceptions.dart # 定义网络请求异常类型
│ │ ├── api_interceptors.dart # 实现请求拦截器核心逻辑
│ │ └── api_provider.dart # 提供统一请求接口
二、核心组件详解
1. ApiConstants - 常量管理
dart
class ApiConstants {
// 基础配置
static String get baseUrl => env('API_BASE_URL'); // 环境变量配置
static const Duration defaultTimeout = Duration(milliseconds: 5000);
// 请求头配置
static const String headerTokenKey = 'X-Token';
static const String contentType = 'application/json;charset=UTF-8';
// 请求控制参数
static const String extraIsLoading = 'isLoading'; // 显示加载框
static const String extraIsToast = 'isToast'; // 显示错误提示
static const String extraIsCancelReq = 'isCancelReq'; // 可取消请求
static const String extraIsDeduplication = 'isDeduplication'; // 请求去重
// 错误信息
static const String tokenExpiredCode = '0200002';
static const String tokenRefreshFailed = 'Token刷新失败,请重新登录';
static const String networkTimeoutMessage = '网络连接超时,请检查网络后重试';
// ...其他错误常量
}
2. ApiExceptions - 异常处理
dart
/// HTTP请求异常基类
class HttpException implements Exception {
final String message;
final DioException? dioError;
final dynamic responseData;
HttpException(this.message, {this.dioError, this.responseData});
}
/// 携带响应数据的特殊异常(用于Token刷新后重试)
class ReturnWithResponse implements Exception {
final Response response;
ReturnWithResponse(this.response);
}
3. ApiInterceptors - 拦截器核心
3.1 请求拦截处理
dart
Future<void> onRequest(RequestOptions options) async {
_handleLoading(options); // 处理加载状态
await _attachToken(options); // 附加Token
_checkDuplicateRequest(options); // 请求去重
_setupCancelToken(options); // 设置取消Token
}
3.2 Token 管理
dart
Future<void> _attachToken(RequestOptions options) async {
final AuthModel authModel = await AuthServices.get();
if (authModel.token.isNotEmpty) {
options.headers[ApiConstants.headerTokenKey] = authModel.token;
}
}
3.3 Token 刷新机制
dart
Future<Response> _handleTokenExpired(RequestOptions options) async {
if (isRefreshing) {
// 已有刷新请求时加入等待队列
final completer = Completer<Response>();
waitingCompleters.add(completer);
await completer.future;
return await dio.fetch(options..cancelToken = null);
}
isRefreshing = true;
try {
// 执行Token刷新
AuthModel authModel = await AuthServices.get();
AuthModel newAuthModel = await AuthRepository.refresh(authModel.refreshToken);
// 保存新Token
await AuthServices.set(newAuthModel);
options.headers[ApiConstants.headerTokenKey] = newAuthModel.token;
// 重试原始请求
final newResponse = await dio.fetch(options..cancelToken = null);
// 完成所有等待中的请求
for (final c in waitingCompleters) {
if (!c.isCompleted) c.complete(newResponse);
}
return newResponse;
} finally {
isRefreshing = false;
waitingCompleters.clear();
}
}
4. ApiProvider - 请求封装
4.1 初始化配置
dart
void _initInstance() {
_dio.options = BaseOptions(
baseUrl: ApiConstants.baseUrl,
connectTimeout: ApiConstants.defaultTimeout,
headers: {'Content-Type': ApiConstants.contentType},
);
// 调试模式添加日志
if (kDebugMode) {
_dio.interceptors.add(PrettyDioLogger(/* 配置 */);
}
// 添加拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: _interceptors.onRequest,
onResponse: _interceptors.onResponse,
onError: _interceptors.onError,
));
}
4.2 统一请求方法
dart
static Future<T> request<T>(
String path, {
String method = 'POST',
dynamic data,
bool isLoading = true,
bool isToast = true,
bool isCancelReq = true,
bool isDeduplication = true,
T Function(dynamic)? fromJson,
}) async {
try {
final isGet = method.toUpperCase() == 'GET';
final response = await _instance._dio.request(
path,
data: isGet ? null : data,
queryParameters: isGet ? data : null,
options: Options(
method: method,
extra: {
ApiConstants.extraIsLoading: isLoading,
ApiConstants.extraIsToast: isToast,
ApiConstants.extraIsCancelReq: isCancelReq,
ApiConstants.extraIsDeduplication: isDeduplication,
},
),
);
return _instance.parseResponse<T>(response.data['data'], fromJson);
} on DioException catch (e) {
// 统一错误处理
final message = e.message ?? ApiConstants.defaultErrorMessage;
if (isToast) ToastUtil.show(message);
throw HttpException(message, dioError: e);
}
}
三、关键特性
1. Token 自动管理
- 请求前自动添加 Token 到请求头
- Token 过期时自动刷新
- 刷新期间并发请求排队处理
- 刷新成功后自动更新请求头并重试
2. 请求控制
- 去重机制:防止重复提交
- 取消功能:全局请求取消支持
- 加载状态:自动显示/隐藏加载框
- 错误提示:统一错误消息处理
3. 错误处理体系
dart
String _getErrorMessage(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
return ApiConstants.networkTimeoutMessage;
case DioExceptionType.badResponse:
return _parseServerError(error.response!);
case DioExceptionType.connectionError:
return ApiConstants.networkErrorMessage;
case DioExceptionType.badCertificate:
return ApiConstants.certificateErrorMessage;
default:
return '${ApiConstants.unknownErrorMessage}: ${error.error}';
}
}
4. 响应解析
dart
T parseResponse<T>(dynamic data, T Function(dynamic)? fromJson) {
if (fromJson != null) return fromJson(data);
if (data is T) return data;
throw HttpException('Response type mismatch');
}
四、使用示例
1. 初始化
dart
void main() {
ApiProvider.init(); // 初始化网络请求
runApp(MyApp());
}
2. 发起请求
dart
// GET请求
List<Product> products = await ApiProvider.get<List<Product>>(
'/products',
fromJson: (json) => (json as List).map((e) => Product.fromJson(e)).toList(),
isLoading: true,
);
// POST请求
final result = await ApiProvider.post<String>(
'/orders',
data: order.toJson(),
isToast: true,
);
3. 错误处理
dart
try {
await ApiProvider.post('/submit', data: formData);
} on HttpException catch (e) {
print('请求失败: ${e.message}');
// 自定义错误处理
}
五、最佳实践
环境配置:
- 使用
.env
文件管理不同环境 API 地址 - 通过
env('API_BASE_URL')
动态获取配置
- 使用
请求参数:
- 重要操作启用去重 (
isDeduplication: true
) - 表单提交禁用取消 (
isCancelReq: false
) - 后台请求关闭加载框 (
isLoading: false
)
- 重要操作启用去重 (
Token 管理:
- 使用
AuthServices
统一管理认证状态 - 确保
AuthRepository.refresh()
正确实现
- 使用
错误处理:
- 用户操作使用
isToast: true
自动提示 - 关键错误使用自定义处理逻辑
- 用户操作使用