Flutter使用Dio上传多图到FastAPI时返回422 Unprocessable Entity错误
Flutter上传多图+表单到FastAPI报422错误修复
问题表现
- 接口在Swagger UI(
localhost:8000/docs)、Postman、curl调用下全部正常,仅Flutter端调用返回422 Unprocessable Entity - 报错明确提示两个校验失败:
price字段值不是合法float类型files字段为必填项但缺失
- 已尝试手动添加multipart请求头、固定Content-Type,问题仍存在
相关代码
原Flutter端上传逻辑(存在问题)
_uploadImage() async { for (int i = 0; i < imageFileList!.length; i++) { var path = imageFileList![i].path; _images!.add(await MultipartFile.fromFile(path, filename: path.split('/').last, contentType: MediaType("image", "jpg"))); var formData = FormData.fromMap( { "name": productNameController, "price": productPriceController, "description": productDescriptionController, "image": _images, }, ); var response = await DioClient.dio.post( "http://10.0.2.2:8000/products/addProductFD", data: formData, options: Options(contentType: 'multipart/form-data')); debugPrint(response.toString()); } }
FastAPI后端接口定义
@router.post('/addProductFD', status_code=status.HTTP_201_CREATED) async def create( name: str = Form(...), price: float = Form(...), description: str = Form(...), files: List[UploadFile] = File(...), db: Session = Depends(get_db), ): fileList = [] for file in files: try: FILEPATH = "./static/product_images/" pimage_name = FILEPATH + imghex(file.filename) contents = await file.read() with open(pimage_name, 'wb') as f: f.write(contents) except Exception: return {"message": "There was an error uploading the file(s)"} finally: await file.close() fileList.append("localhost:8000" + pimage_name[1:]) file_urls = ",".join(fileList) new_item = Product( name=name, price=price, description=description, imgs_url=[file_urls], ) db.add(new_item) db.commit() db.refresh(new_item) return new_item
正常调用参考
curl调用示例:
curl -X 'POST' \ 'http://127.0.0.1:8000/products/addProductFD' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'name=iPhone 44' \ -F 'price=1800' \ -F 'description=Brand New with a fresh box.' \ -F 'files=@iphone1.jpg;type=image/jpeg' \ -F 'files=@iphone1ss.jpg;type=image/jpeg'
接口正常返回:
{ "name": "iPhone 44", "price": 1800, "is_active": true, "imgs_url": [ "localhost:8000/static/product_images/0337698056d37da14fae.jpg,localhost:8000/static/product_images/db2f6998aa87a611c89e.jpg" ], "id": 4, "description": "Brand New with a fresh box.", "owner_id": null }
根因定位
代码里一共4个直接触发报错的问题:
- 表单字段传值错误:直接把
TextEditingController控制器对象作为字段值传给后端,没有取控制器存储的实际输入文本(需要用.text属性获取),price字段也没有做类型转换,后端收到的是控制器实例的字符串,自然无法解析为float类型。 - 字段名不匹配:后端定义的文件接收字段名是
files,Flutter端写的字段名是image,后端找不到对应字段直接报缺失。 - 请求逻辑位置错误:把构造FormData、发送请求的逻辑写在了遍历图片的for循环内部,选N张图就会重复发N次请求,且前N-1次请求携带的图片列表都是不完整的。
- 冗余请求头导致解析失败:Dio发送FormData时会自动生成带
boundary分隔符的multipart/form-data请求头,手动固定写死contentType: 'multipart/form-data'会丢失关键的boundary参数,直接导致后端无法解析表单内容。
修复方案
把原_uploadImage方法替换为以下实现即可:
_uploadImage() async { // 清空历史缓存,避免重复提交之前选的图 _images!.clear(); // 先遍历处理完全部选中的图片,不要在循环里发请求 for (int i = 0; i < imageFileList!.length; i++) { var path = imageFileList![i].path; // 按实际文件后缀匹配媒体类型,不要硬编码jpg String fileExt = path.split('.').last.toLowerCase(); _images!.add(await MultipartFile.fromFile( path, filename: path.split('/').last, contentType: MediaType("image", fileExt == 'png' ? 'png' : 'jpeg') )); } // 所有文件处理完成后一次性构造表单 var formData = FormData.fromMap( { // 取控制器的text属性获取实际输入值,price转为double类型 "name": productNameController.text.trim(), "price": double.parse(productPriceController.text.trim()), "description": productDescriptionController.text.trim(), // 字段名和后端保持一致,用files "files": _images, }, ); // 发送请求,不要手动指定contentType,让Dio自动生成带boundary的合法请求头 var response = await DioClient.dio.post( "http://10.0.2.2:8000/products/addProductFD", data: formData ); debugPrint(response.toString()); }
额外优化建议
- 提交前加表单校验:判断名称、价格、描述是否为空,价格输入是否为合法数字,是否选中了至少1张图片,避免运行时报错。
- 如果项目里配置了全局Dio拦截器,确认拦截器没有对FormData类型的请求做额外的JSON序列化处理,避免破坏表单结构。
内容的提问来源于stack exchange,提问作者pythonGo
相关产品推荐
相关产品推荐

