Skip to content

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}');
  // 自定义错误处理
}

五、最佳实践

  1. 环境配置

    • 使用 .env 文件管理不同环境 API 地址
    • 通过 env('API_BASE_URL') 动态获取配置
  2. 请求参数

    • 重要操作启用去重 (isDeduplication: true)
    • 表单提交禁用取消 (isCancelReq: false)
    • 后台请求关闭加载框 (isLoading: false)
  3. Token 管理

    • 使用 AuthServices 统一管理认证状态
    • 确保 AuthRepository.refresh() 正确实现
  4. 错误处理

    • 用户操作使用 isToast: true 自动提示
    • 关键错误使用自定义处理逻辑