uploadFiles<T> method

Future<Response<T>> uploadFiles<T>({
  1. required String path,
  2. required List<UploadFile> files,
  3. Map<String, dynamic>? additionalData,
  4. Map<String, dynamic>? queryParameters,
  5. void onProgress(
    1. int sent,
    2. int total
    )?,
  6. CancelToken? cancelToken,
  7. Map<String, String>? headers,
})

上传多个文件

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>();
  }
}