轻量级 Dart HTTP 网络库,基于 Dio 封装,专为 Flutter 应用设计。

7 阅读3分钟

HttpUtils

轻量级 Dart HTTP 网络库,基于 Dio 封装,专为 Flutter 应用设计。

组件简介

HttpUtils 是一个开箱即用的网络请求工具,目标是让开发者只关心业务逻辑,不必处理底层网络细节。

核心能力

能力说明
🎯 类型安全泛型 + 自动转换器,请求直接返回强类型对象
🔄 智能认证401 自动刷新 Token 并重试,并发安全
📦 多实例一个项目对接多个后端,实例间完全隔离
📤 文件操作单文件/多文件上传、文件下载 + 进度回调
🛡️ 统一错误处理超时/断网/4xx/5xx/业务错误,全部封装为 ApiResponse

代码地址

├── http_utils.dart     # 核心网络请求工具
└── api_response.dart   # 统一响应模型

依赖

dependencies:
  dio: ^5.0.0

基础使用

只需 3 步即可开始使用:初始化 → 发请求 → 处理结果。

第 1 步:初始化

main.dart 中初始化默认实例:

void main() {
  HttpUtils.defaultInstance(
    baseUrl: 'https://api.example.com',
  );
  runApp(MyApp());
}

第 2 步:发起请求

// GET
final response = await HttpUtils().get('/users');

// POST
final response = await HttpUtils().post('/users', data: {'name': 'John'});

第 3 步:处理结果

所有请求返回统一的 ApiResponse<T>

if (response.isSuccess) {
  print('数据: ${response.data}');
} else {
  print('错误码: ${response.code}');
  print('错误信息: ${response.message}');
}

就是这么简单。 以下是完整的功能文档。


详细功能

目录


1. 请求方法

所有 HTTP 方法
// GET(带查询参数)
await HttpUtils().get('/users', params: {'page': 1, 'size': 20});

// POST(带请求体)
await HttpUtils().post('/users', data: {'name': 'John', 'age': 28});

// PUT
await HttpUtils().put('/users/123', data: {'name': 'Jane'});

// DELETE
await HttpUtils().delete('/users/123');

// PATCH
await HttpUtils().patch('/users/123', data: {'name': 'Updated'});
通用参数

所有请求方法都支持以下可选参数:

参数类型说明
paramsMap<String, dynamic>?URL 查询参数
datadynamic请求体(POST/PUT/PATCH/DELETE)
headersMap<String, dynamic>?自定义请求头
requireAuthbool?是否需要 Token 认证(覆盖实例默认值)
fromJsonT Function(dynamic)?数据转换函数(一次性,不注册)
cancelTokenCancelToken?用于取消请求
表单提交
await HttpUtils().postForm('/submit', formData: {
  'field1': 'value1',
  'field2': 'value2',
});

2. 类型转换器

类型转换器让你的请求直接返回强类型对象,而不是 Map

定义模型
class User {
  final int id;
  final String name;

  User({required this.id, required this.name});

  factory User.fromJson(dynamic json) {
    final map = json as Map<String, dynamic>;
    return User(id: map['id'], name: map['name']);
  }
}
方式一:注册全局转换器(推荐)
// 在 main() 中注册一次
HttpUtils.registerConverter<User>(User.fromJson);

// 之后所有请求自动转换
final response = await HttpUtils().get<User>('/users/1');
User user = response.data!;  // 类型安全 ✅

自动列表支持:注册 User 后,List<User> 自动可用,无需额外注册。

final response = await HttpUtils().get<List<User>>('/users');
List<User> users = response.data!;  // 自动转换列表 ✅
方式二:请求时直传 fromJson

无需提前注册,在请求时直接传入:

final response = await HttpUtils().get<User>(
  '/users/1',
  fromJson: User.fromJson,
);
批量注册
HttpUtils.registerConverters({
  User: User.fromJson,
  Product: Product.fromJson,
  Order: Order.fromJson,
});
管理转换器
HttpUtils.removeConverter<User>();   // 移除指定转换器
HttpUtils.clearConverters();         // 清空所有转换器

3. Token 认证

HttpUtils 内置了完整的 Token 认证体系,包括自动注入、自动刷新、并发安全。

3.1 开启认证
HttpUtils.defaultInstance(
  baseUrl: 'https://api.example.com',
  enableAuth: true,  // 开启全局认证
);
3.2 配置 Token 获取函数
HttpUtils().onGetToken = ([bool refresh = false, HttpUtils? http]) async {
  if (refresh) {
    // 刷新 Token(当收到 401 时自动调用)
    final newToken = await refreshTokenFromServer();
    await saveTokenToLocal(newToken);
    return newToken;
  }
  // 获取本地缓存的 Token(每次请求前自动调用)
  return await getTokenFromLocal();
};
3.3 自定义认证头格式

默认格式为 {'Authorization': token},可自定义:

HttpUtils().onGenerateAuthToken = (token) {
  return {
    'Authorization': 'Bearer $token',
    'X-Custom-Header': 'value',
  };
};
3.4 认证失败回调

当 Token 刷新失败时触发:

HttpUtils.onAuthFailed = () {
  // 跳转登录页
  Navigator.pushReplacementNamed(context, '/login');
};
3.5 请求级别控制

每个请求可以单独控制是否需要认证,覆盖实例级配置:

// 不启用认证的实例,此次请求临时要求认证
await HttpUtils().get('/profile', requireAuth: true);

// 启用认证的实例,此次请求跳过认证
await HttpUtils().get('/public/data', requireAuth: false);
3.6 自动刷新机制

当请求返回 401 时,HttpUtils 会自动执行以下流程:

请求返回 401
    ↓
调用 onGetToken(refresh: true) 刷新 Token
    ↓
┌───────────────────────────────┐
│  刷新成功                      │  刷新失败
│  ↓                            │  ↓
│  用新 Token 重试原请求          │  触发 onAuthFailed
│  同时重试队列中其他等待的请求    │  清空等待队列
└───────────────────────────────┘

并发安全:多个请求同时收到 401 时,仅触发一次刷新,其余请求排队等待。

防死循环:内置最多重试 2 次限制。

3.7 完整示例
void main() {
  // 初始化
  HttpUtils.defaultInstance(
    baseUrl: 'https://api.example.com',
    enableAuth: true,
  );

  // Token 获取/刷新
  HttpUtils().onGetToken = ([bool refresh = false, HttpUtils? http]) async {
    final prefs = await SharedPreferences.getInstance();
    if (refresh) {
      final refreshToken = prefs.getString('refresh_token');
      final response = await HttpUtils().post(
        '/auth/refresh',
        data: {'refreshToken': refreshToken},
        requireAuth: false,  // 刷新请求本身不需要认证
      );
      final newToken = response.data['token'];
      await prefs.setString('token', newToken);
      return newToken;
    }
    return prefs.getString('token');
  };

  // 认证失败处理
  HttpUtils.onAuthFailed = () {
    Get.offAllNamed('/login');
  };

  runApp(MyApp());
}

4. 多实例管理

适用于一个项目需要对接多个后端服务的场景。每个实例拥有独立的 BaseUrl、Token 状态和拦截器。

创建命名实例
// 主应用 API
HttpUtils.named(
  name: 'app',
  baseUrl: 'https://app-api.example.com',
  enableAuth: true,
);

// 支付 API(独立的认证体系)
HttpUtils.named(
  name: 'payment',
  baseUrl: 'https://pay-api.example.com',
  enableAuth: true,
);

// 第三方 API(无需认证)
HttpUtils.named(
  name: 'third_party',
  baseUrl: 'https://api.third-party.com',
  enableAuth: false,
);
使用命名实例
final appApi = HttpUtils.getInstance('app')!;
final payApi = HttpUtils.getInstance('payment')!;

await appApi.get('/user/profile');
await payApi.post('/orders', data: orderData);
为不同实例配置独立的 Token
final payApi = HttpUtils.getInstance('payment')!;

// 支付实例独立的 Token 获取逻辑
payApi.onGetToken = ([bool refresh = false, HttpUtils? http]) async {
  // ... 支付系统的 Token 逻辑
};

// 支付实例独立的认证头格式
payApi.onGenerateAuthToken = (token) {
  return {'Authorization': 'Bearer $token', 'X-Payment-App': 'MyApp'};
};
实例管理
HttpUtils.removeInstance('third_party');  // 移除指定实例
HttpUtils.clearAllInstances();           // 清除所有实例(含默认实例)
HttpUtils.clearPendingRequests();        // 清理所有实例的待重试队列

5. 文件上传与下载

单文件上传
final response = await HttpUtils().uploadFile(
  '/upload',
  filePath: '/path/to/image.jpg',
  fieldName: 'avatar',            // 默认 'file'
  data: {'userId': '123'},        // 附加表单字段
  onSendProgress: (sent, total) {
    print('上传进度: ${(sent / total * 100).toStringAsFixed(1)}%');
  },
);
多文件上传
final response = await HttpUtils().uploadFiles(
  '/upload/batch',
  files: {
    'photo1': '/path/to/photo1.jpg',
    'photo2': '/path/to/photo2.jpg',
  },
  data: {'albumId': '456'},
);
文件下载
final response = await HttpUtils().downloadFile(
  'https://example.com/file.pdf',
  '/local/path/file.pdf',
  onReceiveProgress: (received, total) {
    if (total > 0) {
      print('下载进度: ${(received / total * 100).toStringAsFixed(1)}%');
    }
  },
);

if (response.isSuccess) {
  print('下载完成!');
}

6. 错误处理

HttpUtils 将所有错误统一封装为 ApiResponse,你永远不需要 try-catch

响应模型
class ApiResponse<T> {
  final bool success;     // 是否成功
  final int code;         // 状态码
  final String message;   // 消息
  final T? data;          // 数据

  bool get isSuccess;     // success == true
  bool get isError;       // success == false
  bool get hasData;       // data != null
}
错误码对照表
错误码含义触发场景
-100BaseUrl 未设置未调用 defaultInstance() 就发起请求
-2数据解析错误fromJson 转换失败
-1网络/未知错误网络断开、未分类的异常
401未授权Token 过期(自动触发刷新)
403无权限服务器拒绝访问
404资源不存在接口路径错误
500服务器错误后端异常
5001+业务错误码后端返回的自定义错误
错误消息自动翻译

HttpUtils 为常见错误提供了中文提示:

类型自动消息
超时"连接超时,请检查网络后重试"
断网"网络连接失败,请检查网络设置"
取消"请求已取消"
404"请求的资源不存在"
500"服务器内部错误,请稍后重试"
403"没有访问权限"
支持的后端响应格式

HttpUtils 自动适配多种常见 JSON 格式:

// 格式 1:success 字段
{"success": true, "code": 200, "message": "成功", "data": {...}}

// 格式 2:status 字段
{"status": 200, "msg": "OK", "list": [...]}

// 格式 3:code 字段 + info
{"code": 200, "message": "Success", "info": {...}}

数据自动从 datalistinfo 字段中提取。

取消请求
final cancelToken = CancelToken();

// 发起请求
final future = HttpUtils().get('/data', cancelToken: cancelToken);

// 取消请求
cancelToken.cancel('用户取消了操作');

final response = await future;
// response.isError == true, response.message == "请求已取消"

7. 高级配置

初始化参数
HttpUtils.defaultInstance(
  baseUrl: 'https://api.example.com',   // 必填:接口基础地址
  enableAuth: true,                     // 是否启用 Token 认证(默认 false)
  connectTimeout: 15000,                // 连接超时(毫秒,默认 15000)
  receiveTimeout: 15000,                // 接收超时(毫秒,默认 15000)
  sendTimeout: 15000,                   // 发送超时(毫秒,默认 15000)
  enableLogging: true,                  // 是否打印请求/响应日志(默认 false)
  interceptors: [MyInterceptor()],      // 自定义拦截器列表
);

HttpUtils.named() 支持完全相同的参数,额外需要一个 name

动态修改 BaseUrl
HttpUtils().setBaseUrl('https://new-api.example.com');
print(HttpUtils().baseUrl);  // https://new-api.example.com
自定义拦截器
class LoggingInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('→ ${options.method} ${options.uri}');
    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('← ${response.statusCode} ${response.requestOptions.uri}');
    handler.next(response);
  }
}

HttpUtils.defaultInstance(
  baseUrl: '...',
  interceptors: [LoggingInterceptor()],
);
直接访问 Dio 实例

用于高级自定义或测试:

HttpUtils().dio.options.headers['X-Custom'] = 'value';
环境切换
abstract class AppConfig {
  static const String baseUrl = String.fromEnvironment(
    'API_URL',
    defaultValue: 'https://api.example.com',
  );
}

void main() {
  HttpUtils.defaultInstance(baseUrl: AppConfig.baseUrl);
  runApp(MyApp());
}

运行时指定:

flutter run --dart-define=API_URL=https://test-api.example.com

8. 最佳实践

Repository 模式

将网络请求封装在 Repository 中,隔离业务逻辑:

class UserRepository {
  Future<List<User>> getUsers() async {
    final response = await HttpUtils().get<List<User>>('/users');
    if (response.isSuccess) {
      return response.data ?? [];
    }
    throw Exception(response.message);
  }

  Future<User> createUser(String name) async {
    final response = await HttpUtils().post<User>(
      '/users',
      data: {'name': name},
      fromJson: User.fromJson,
    );
    if (response.isSuccess) {
      return response.data!;
    }
    throw Exception(response.message);
  }
}
统一错误处理工具
Future<T?> safeRequest<T>(
  Future<ApiResponse<T>> Function() request,
) async {
  final response = await request();
  if (response.isSuccess) {
    return response.data;
  } else {
    showToast(response.message);
    return null;
  }
}

// 使用
final users = await safeRequest(
  () => HttpUtils().get<List<User>>('/users'),
);
退出登录时清理
void logout() {
  HttpUtils.clearPendingRequests();  // 清理待重试的请求
  // ... 其他清理逻辑
}

许可证

MIT License