uploadToUrlResponse<T> method

Future<Response<T>> uploadToUrlResponse<T>({
  1. required String uploadUrl,
  2. required dynamic file,
  3. String method = 'PUT',
  4. Map<String, String>? headers,
  5. void onProgress(
    1. int sent,
    2. int total
    )?,
  6. CancelToken? cancelToken,
})

直接上传文件到外部 URL(OSS 直传)

适用于后端返回预签名上传 URL 的场景,直接上传到 OSS(阿里云、腾讯云等) 返回 Response

uploadUrl 完整的上传 URL(包含签名参数) file 文件对象(File、String 路径或 Uint8List 字节数组) method HTTP 方法,默认为 'PUT'(OSS 通常使用 PUT) headers 自定义请求头(OSS 通常需要签名头,如 'Authorization', 'Content-Type' 等) onProgress 上传进度回调 (已上传字节数, 总字节数) cancelToken 取消令牌

示例(链式调用):

final result = await http
  .send(...)
  .extractModel<FileUploadResult>(FileUploadResult.fromConfigJson)
  .thenWith((uploadResult) => http.uploadToUrlResponse(
    uploadUrl: uploadResult.uploadUrl,
    file: file,
    method: 'PUT',
    headers: uploadResult.contentType != null
        ? {'Content-Type': uploadResult.contentType!}
        : null,
  ));

示例(单独使用):

final response = await http.uploadToUrlResponse(
  uploadUrl: uploadUrl,
  file: File('/path/to/image.jpg'),
  method: 'PUT',
  headers: {'Content-Type': 'image/jpeg'},
  onProgress: (sent, total) {
    print('上传进度: ${(sent / total * 100).toStringAsFixed(1)}%');
  },
);

if (response.isSuccess) {
  print('上传成功');
}

Implementation

Future<Response<T>> uploadToUrlResponse<T>({
  required String uploadUrl,
  required dynamic file,
  String method = 'PUT',
  Map<String, String>? headers,
  void Function(int sent, int total)? onProgress,
  dio_package.CancelToken? cancelToken,
}) async {
  try {
    final dioResponse = await _uploadToUrlInternal(
      uploadUrl: uploadUrl,
      file: file,
      method: method,
      headers: headers,
      onProgress: onProgress,
      cancelToken: cancelToken,
    );

    // 将 dio_package.Response 转换为 Response<T>
    // OSS 直传成功通常返回 200 或 204
    final isSuccess = dioResponse.statusCode != null &&
        (dioResponse.statusCode == 200 || dioResponse.statusCode == 204);

    final response = ApiResponse<T>(
      code: dioResponse.statusCode ?? -1,
      message: isSuccess ? '上传成功' : '上传失败',
      data: dioResponse.data as T?,
      isSuccess: isSuccess,
    );

    // 如果上传失败,自动处理错误(触发错误提示)
    if (!isSuccess) {
      final config = HttpUtilSafeCall._config;
      final errorMessage = response.errorMessage ?? '上传失败,请稍后重试!';
      // 调用 handleError(保持接口兼容性,实际错误处理由 HttpConfig.onFailure 统一处理)
      response.handleError();
      // 延迟调用全局的 onFailure,确保链式调用的 onFailure 可以先执行
      // 优先级:链式调用的 onFailure > 全局的 onFailure(如果用户使用了链式调用的 onFailure,就不调用全局的 onFailure)
      if (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) {
    // 处理错误
    final config = HttpUtilSafeCall._config;
    final errorMessage = config?.networkErrorKey ?? '上传失败,请稍后重试!';

    if (config?.onFailure != null) {
      config!.onFailure!(
          null, null, errorMessage); // 上传异常没有 httpStatusCode 和 errorCode
    }

    return ApiResponse<T>(
      code: -1,
      message: errorMessage,
      data: null,
      isSuccess: false,
    );
  }
}