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'});
通用参数
所有请求方法都支持以下可选参数:
| 参数 | 类型 | 说明 |
|---|---|---|
params | Map<String, dynamic>? | URL 查询参数 |
data | dynamic | 请求体(POST/PUT/PATCH/DELETE) |
headers | Map<String, dynamic>? | 自定义请求头 |
requireAuth | bool? | 是否需要 Token 认证(覆盖实例默认值) |
fromJson | T Function(dynamic)? | 数据转换函数(一次性,不注册) |
cancelToken | CancelToken? | 用于取消请求 |
表单提交
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
}
错误码对照表
| 错误码 | 含义 | 触发场景 |
|---|---|---|
-100 | BaseUrl 未设置 | 未调用 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": {...}}
数据自动从 data → list → info 字段中提取。
取消请求
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