uploadFiles<T> method
上传多个文件
path 请求路径
files 文件列表
additionalData 额外的表单数据(除了文件之外的其他字段)
queryParameters URL 查询参数
onProgress 上传进度回调 (已上传字节数, 总字节数)
cancelToken 取消令牌
headers 特定请求的请求头(可选),会与全局请求头合并,如果键相同则覆盖全局请求头
示例:
final response = await http.uploadFiles<String>(
path: '/api/upload/multiple',
files: [
UploadFile(file: File('/path/to/file1.jpg'), fieldName: 'images[]'),
UploadFile(file: File('/path/to/file2.jpg'), fieldName: 'images[]'),
],
additionalData: {'albumId': '456'},
headers: {'X-Upload-Type': 'batch'}, // 特定请求头
onProgress: (sent, total) {
print('上传进度: ${(sent / total * 100).toStringAsFixed(1)}%');
},
);
Implementation
Future<Response<T>> uploadFiles<T>({
required String path,
required List<UploadFile> files,
Map<String, dynamic>? additionalData,
Map<String, dynamic>? queryParameters,
void Function(int sent, int total)? onProgress,
dio_package.CancelToken? cancelToken,
Map<String, String>? headers,
}) async {
// 验证文件列表不为空
if (files.isEmpty) {
throw ArgumentError('文件列表不能为空,请至少提供一个文件');
}
try {
// 构建 FormData
final formData = dio_package.FormData();
// 添加文件
for (final uploadFile in files) {
final multipartFile = await uploadFile.toMultipartFile();
formData.files.add(
MapEntry(uploadFile.fieldName, multipartFile),
);
}
// 添加额外数据
if (additionalData != null) {
additionalData.forEach((key, value) {
formData.fields.add(MapEntry(key, value.toString()));
});
}
// 转换进度回调格式
dio_package.ProgressCallback? dioProgressCallback;
if (onProgress != null) {
dioProgressCallback = (sent, total) {
onProgress(sent, total);
};
}
final config = HttpUtilSafeCall._config;
if (config == null) {
throw StateError('HttpUtil 未配置,请先调用 HttpUtil.configure() 进行配置');
}
// 构建请求选项(responseType.plain 拿原始字符串,再在 isolate 中 decode 避免 UI 卡顿)
final options = dio_package.Options(
headers: headers ?? const {},
responseType: dio_package.ResponseType.plain,
);
// 调用 request 方法以支持进度回调
final rawResponse = await HttpUtil.instance.request<T>(
method: hm.post,
path: path,
data: formData,
queryParameters: queryParameters,
options: options,
onSendProgress: dioProgressCallback,
cancelToken: cancelToken,
);
// 检查 500 错误
if (rawResponse.statusCode == 500) {
return _handleNetworkError<T>();
}
// 在 isolate 中解码 JSON,避免主线程解析大 JSON 导致 UI 卡顿
dynamic responseData = rawResponse.data;
if (responseData is String) {
try {
responseData = await compute<String, dynamic>(_decodeJsonInIsolate, responseData);
} catch (_) {
// 解码失败保持原样,由解析器返回格式错误
}
}
final raw = RawHttpResponse(
statusCode: rawResponse.statusCode,
data: responseData,
path: rawResponse.requestOptions.path,
);
final response = config.responseParser.parse<T>(raw);
// 自动处理错误(如果用户实现了 handleError 方法)
if (!response.isSuccess) {
response.handleError();
// 延迟调用全局的 onFailure,确保链式调用的 onFailure 可以先执行
// 优先级:链式调用的 onFailure > 全局的 onFailure(如果用户使用了链式调用的 onFailure,就不调用全局的 onFailure)
final errorMessage = response.errorMessage;
if (errorMessage != null && config.onFailure != null) {
final httpStatusCode = response.httpStatusCode; // 获取 HTTP 状态码
final errorCode = response.errorCode; // 获取业务错误码
Future.microtask(() {
// 再次检查 errorHandled,如果链式调用的 onFailure 已经处理了,就不调用全局的 onFailure
if (!response.errorHandled) {
config.onFailure!(httpStatusCode, errorCode, errorMessage);
}
});
}
}
return response;
} catch (e) {
// 所有异常都统一处理为网络错误
return _handleNetworkError<T>();
}
}