You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Dio和image_picker上传多份MultipartFile时遇422错误排查

Dio多文件上传返回422不可处理内容问题修复

问题场景

通过image_picker选取4张图片,按以下流程上传:

  1. 函数参数定义
required File image1,
required File image2,
required List<File> images, // 包含2张图片的列表
  1. 转换为MultipartFile
var img1 = await MultipartFile.fromFile(image1.path, filename: 'img1.jpg');
var img2 = await MultipartFile.fromFile(image2.path, filename: 'img2.jpg');

List<MultipartFile> listImages = [];
for (var i = 0; i < images.length; i++) {
  var value = await MultipartFile.fromFile(images[i].path, filename: 'image_$i.jpg');
  listImages.add(value);
}
  1. 组装上传数据
/// 上传图片
final imagesUploadResponse = await _dioClient.postRequest(
  'uploads/images',
  parameters: {'lang': 'en'},
  contentType: 'multipart/form-data',
  data: {
    'files': <MultipartFile>[
      img1,
      img2,
      ...listImages,
    ],
    'type': 'member',
  },
);
  1. 创建FormData并发送请求
final FormData formData = FormData.fromMap(data, ListFormat.multiCompatible);
inspect(formData);
final response = await dio.post<Map<String, dynamic>>(
  baseUrl + url,
  queryParameters: parameters,
  data: formData,
  options: _getOption(token, contentType: contentType, boundary: formData.boundary),
);
  1. Options构造代码
Options _getOption(String? token, {String? contentType, String? boundary}) {
return Options(
  headers: {
    'Content-Type':
        contentType == null ? '$contentType;boundary=$boundary' : 'application/json',
    if (token != null) 'Authorization': 'Bearer $token',

    // 可选头
    'Accept': '*/*',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
  },
);
}

执行后返回422不可处理内容错误,Postman测试显示:发送files[]格式的数组正常,发送files格式则报错。

问题根源

核心错误在_getOption的Content-Type判断逻辑完全写反:
当传入contentType: 'multipart/form-data'时,代码会进入分支将Content-Type设置为application/json,但实际请求体是MultipartFormData格式,后端收到的请求头与请求体格式不匹配,无法解析,因此返回422错误。

另外,手动设置Content-Type和boundary存在风险,Dio会自动为FormData请求处理Content-Type及boundary,无需手动配置。

修复方案

方案1:修正Content-Type判断逻辑

将_getOption中的Content-Type代码修正为:

Options _getOption(String? token, {String? contentType, String? boundary}) {
return Options(
  headers: {
    'Content-Type':
        contentType != null ? '$contentType;boundary=$boundary' : 'application/json',
    if (token != null) 'Authorization': 'Bearer $token',

    // 可选头
    'Accept': '*/*',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
  },
);
}

方案2:移除手动设置的Content-Type,让Dio自动处理

更稳妥的方式是删掉手动配置Content-Type的代码,Dio在检测到请求数据是FormData时,会自动设置正确的Content-Type(包含自动生成的boundary):

Options _getOption(String? token) {
return Options(
  headers: {
    if (token != null) 'Authorization': 'Bearer $token',

    // 可选头
    'Accept': '*/*',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
  },
);
}

调用时不再传入contentType和boundary参数:

final response = await dio.post<Map<String, dynamic>>(
  baseUrl + url,
  queryParameters: parameters,
  data: formData,
  options: _getOption(token),
);

确认FormData格式正确性

当前使用FormData.fromMap(data, ListFormat.multiCompatible)是正确的,该方式会将files数组转换为后端期望的files[]格式,与Postman测试通过的请求格式一致。


内容的提问来源于stack exchange,提问作者Quốc Thịnh Trần

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 12:07:08