HttpUtils - Flutter HTTP 工具 2.5
一个轻量级、功能强大的 Flutter HTTP 工具包,基于 Dio 实现,提供完整的 RESTful API 支持和 Token 管理功能。
✨ 核心特性
- 🎯 类型安全 - 自动类型转换,支持泛型
- 🔄 智能认证 - 401 自动刷新 Token 并重试请求
- 🚀 开箱即用 - 简洁的 API 设计,快速集成
- 📦 多实例支持 - 轻松管理多个 API 服务
- 🛡️ 并发安全 - Token 刷新机制避免竞态条件
📦 安装
1. 添加依赖
在 pubspec.yaml 中添加:
dependencies:
dio: ^5.0.0
2. 添加到项目
将 http_utils.dart 和 api_response.dart 复制到项目的 utils 目录:
lib/
└── utils/
├── http_utils.dart
└── api_response.dart
🚀 基础使用
初始化
在 main.dart 中初始化默认实例:
void main() {
HttpUtils.defaultInstance(
baseUrl: 'https://api.example.com',
);
runApp(MyApp());
}
发起 GET 请求
final response = await HttpUtils().get('/users');
if (response.isSuccess) {
print('数据: ${response.data}');
} else {
print('错误: ${response.message}');
}
使用类型转换
// 定义模型
class User {
final String id;
final String name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
);
}
}
// 请求时指定转换函数
final response = await HttpUtils().get<User>(
'/users/123',
fromJson: User.fromJson,
);
if (response.isSuccess) {
User user = response.data!; // 类型安全
print('用户名: ${user.name}');
}
📡 支持的请求类型
GET / POST / PUT / DELETE / PATCH
// GET
await HttpUtils().get('/users', params: {'page': 1});
// POST
await HttpUtils().post('/users', data: {'name': 'John'});
// PUT
await HttpUtils().put('/users/123', data: {'name': 'Jane'});
// DELETE
await HttpUtils().delete('/users/123');
// PATCH
await HttpUtils().patch('/users/123', data: {'name': 'Updated'});
FormData 表单提交
// 自动转换为 FormData
await HttpUtils().postForm(
'/upload',
formData: {
'field1': 'value1',
'field2': 'value2',
},
);
文件上传
// 单文件
await HttpUtils().uploadFile(
'/upload',
filePath: '/path/to/image.jpg',
fieldName: 'avatar',
data: {'userId': '123'},
onSendProgress: (sent, total) {
print('进度: ${(sent / total * 100).toStringAsFixed(1)}%');
},
);
// 多文件
await HttpUtils().uploadFiles(
'/upload/batch',
files: {
'photo1': '/path/to/photo1.jpg',
'photo2': '/path/to/photo2.jpg',
},
);
文件下载
final response = await HttpUtils().downloadFile(
'https://example.com/file.pdf',
'/local/path/file.pdf',
onReceiveProgress: (received, total) {
print('下载: ${received}/${total}');
},
);
🔄 类型转换器
注册单个转换器
void main() {
HttpUtils.defaultInstance(baseUrl: 'https://api.example.com');
// 注册 User 类型转换器
HttpUtils.registerConverter<User>(User.fromJson);
runApp(MyApp());
}
注册后,无需每次指定 fromJson:
// 自动使用已注册的转换器
final response = await HttpUtils().get<User>('/users/123');
批量注册
HttpUtils.registerConverters({
User: User.fromJson,
Product: Product.fromJson,
Order: Order.fromJson,
});
List 类型转换
// 注册后自动支持 List
HttpUtils.registerConverter<User>(User.fromJson);
// 获取用户列表
final response = await HttpUtils().get<List<User>>('/users');
if (response.isSuccess) {
List<User> users = response.data!;
}
🔐 Token 认证
开启认证
初始化时启用认证:
HttpUtils.defaultInstance(
baseUrl: 'https://api.example.com',
enableAuth: true, // 开启全局认证
);
配置 Token 生成器
决定如何将 Token 添加到请求头:
// 全局配置
HttpUtils.globalOnGenerateAuthToken = (token) {
return {'Authorization': 'Bearer $token'};
};
// 或实例级配置(优先级更高)
httpElement.onGenerateAuthToken = (token) {
return {
'Authorization': 'Bearer $token',
'X-Custom-Header': 'value',
};
};
配置 Token 获取函数
HttpUtils().onGetToken = ([bool refresh = false]) async {
if (refresh) {
// 刷新 Token
final newToken = await refreshTokenFromServer();
await saveTokenToLocal(newToken);
return newToken;
}
// 获取本地 Token
return await getTokenFromLocal();
};
配置认证失败回调
HttpUtils.onAuthFailed = () {
// Token 刷新失败,跳转登录
navigateToLogin();
};
自动刷新机制
当请求返回 401 时,HttpUtils 会:
- 自动调用
onGetToken(true)刷新 Token - 等待中的请求进入队列
- Token 刷新成功后,自动重试所有失败请求
- Token 刷新失败时,触发
onAuthFailed回调
并发安全:多个 401 响应仅触发一次刷新。
请求级别控制
// 此请求需要认证(覆盖全局配置)
await HttpUtils().get('/profile', requireAuth: true);
// 此请求不需要认证
await HttpUtils().get('/public/data', requireAuth: false);
完整示例
void main() {
// 初始化
HttpUtils.defaultInstance(
baseUrl: 'https://api.example.com',
enableAuth: true,
);
// Token 生成器
HttpUtils.globalOnGenerateAuthToken = (token) {
return {'Authorization': 'Bearer $token'};
};
// Token 获取
HttpUtils().onGetToken = ([refresh = false]) 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());
}
🏢 多实例管理
创建命名实例
适用于需要对接多个后端服务的场景:
// 主应用 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 paymentApi = HttpUtils.getInstance('payment')!;
// 发起请求
await appApi.get('/user/profile');
await paymentApi.post('/orders', data: orderData);
实例管理
// 移除实例
HttpUtils.removeInstance('third-party');
// 清除所有命名实例
HttpUtils.clearAllInstances();
📖 ApiResponse 响应模型
所有请求返回统一的 ApiResponse<T>:
class ApiResponse<T> {
final bool success; // 请求是否成功
final int code; // 状态码
final String message; // 响应消息
final T? data; // 业务数据
}
便捷属性
response.isSuccess // 是否成功
response.isError // 是否失败
response.hasData // 是否有数据
支持的响应格式
HttpUtils 自动适配多种 JSON 格式:
{"success": true, "code": 200, "message": "成功", "data": {...}}
{"status": 200, "msg": "OK", "list": [...]}
{"code": 200, "message": "Success", "info": {...}}
❓ 常见问题
Q: 忘记初始化直接使用 HttpUtils() 怎么办?
会抛出异常并提示:
StateError: HttpUtils 默认实例未初始化!
请先调用 HttpUtils.defaultInstance(baseUrl: "your_url") 进行初始化
解决:在 main() 中添加初始化代码。
Q: 如何切换环境(开发/测试/生产)?
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
Q: 如何动态修改 BaseUrl?
HttpUtils().setBaseUrl('https://new-api.example.com');
// 获取当前 BaseUrl
String url = HttpUtils().baseUrl;
Q: 如何处理错误?
final response = await HttpUtils().get('/users');
if (response.isSuccess) {
// 成功处理
print(response.data);
} else {
// 错误处理
print('错误码: ${response.code}');
print('错误信息: ${response.message}');
}
错误码说明
| 错误码 | 说明 |
|---|---|
| -100 | BaseUrl 未设置 |
| -1 | 业务错误 |
| -2 | 数据解析错误 |
| 401 | 未授权(触发自动刷新) |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
Q: 如何取消请求?
final cancelToken = CancelToken();
// 发起请求
final future = HttpUtils().get('/data', cancelToken: cancelToken);
// 取消
cancelToken.cancel('用户取消');
// 处理
try {
await future;
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
print('请求已取消');
}
}
Q: 如何管理转换器?
// 移除转换器
HttpUtils.removeConverter<User>();
// 清空所有转换器
HttpUtils.clearConverters();
Q: 如何清理待重试的请求?
退出登录时调用:
HttpUtils.clearPendingRequests();
📝 最佳实践
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<void> createUser(User user) async {
final response = await HttpUtils().post(
'/users',
data: user.toJson(),
);
if (!response.isSuccess) {
throw Exception(response.message);
}
}
}
统一错误处理
Future<T?> safeRequest<T>(
Future<ApiResponse<T>> Function() request,
) async {
try {
final response = await request();
if (response.isSuccess) {
return response.data;
} else {
showToast(response.message);
return null;
}
} catch (e) {
showToast('网络请求失败');
return null;
}
}
// 使用
final users = await safeRequest(
() => HttpUtils().get<List<User>>('/users'),
);
📄 许可证
MIT License