React-Admin调用FastAPI上传图片到S3返回422错误求解
问题根因
422报错由两个核心不匹配问题导致:
- 后端接口参数声明错误:FastAPI中同时接收文件上传和普通表单字段时,普通字段必须显式用
Form()声明,否则框架不会从multipart/form-data格式的请求体中解析字段,反而会去URL查询参数中查找对应值,触发字段缺失报错。 - 前端请求格式错误:你当前使用的
ra-data-json-server默认将所有请求序列化为application/json格式发送,既不会将React-Admin的ImageInput组件产出的文件对象转为可上传的二进制格式,也不符合文件上传要求的multipart/form-data编码规范。
修复步骤
1. 修改FastAPI后端代码
首先补充导入Form类型,调整接口参数声明,同时补充基础的异常防范逻辑:
# routers/post.py from fastapi import APIRouter, UploadFile, File, Form import os import uuid import boto3 import psycopg2 router = APIRouter() @router.post("/") def post_product( product_name: str = Form(...), detail: str = Form(...), file: UploadFile = File(...) ): AWS_DEFAULT_REGION = os.getenv("AWS_DEFAULT_REGION") AWS_BUCKET_NAME = os.getenv("AWS_BUCKET_NAME") s3_bucket = AWS_BUCKET_NAME s3_dir = "test-img" region_name = AWS_DEFAULT_REGION s3 = boto3.client("s3", region_name=region_name) # 生成唯一文件名,避免同名文件覆盖、路径注入风险 file_ext = file.filename.split(".")[-1] s3_file_key = f"{s3_dir}/{uuid.uuid4()}.{file_ext}" s3.put_object( Body=file.file, Bucket=s3_bucket, Key=s3_file_key, ContentType=file.content_type # 补充ContentType,S3访问时不会强制触发下载 ) file_url = f"https://{s3_bucket}.s3.{region_name}.amazonaws.com/{s3_file_key}" conn = psycopg2.connect( dbname="postgres", user="postgres", password="Password", host="localhost", port=5432, ) try: with conn.cursor() as cur: cur.execute( "INSERT INTO products (product_name, detail, obj_url) VALUES (%s, %s, %s) RETURNING id", (product_name, detail, file_url), ) conn.commit() finally: conn.close() return { "product_name": product_name, "detail": detail, "obj_url": file_url }
如果还没配置CORS跨域中间件,需要在FastAPI主应用文件中添加如下配置,允许前端本地端口访问:
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # 替换为你的前端实际运行端口 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
2. 修改React-Admin数据提供者逻辑
重写dataProvider的create方法,检测到请求包含文件时自动构造FormData格式发送,不要手动设置Content-Type请求头,浏览器会自动填充正确的分段边界值:
// dataProvider 中create方法替换为如下实现 create: (resource, params) => { // 检测是否包含文件上传字段 const containsFile = Object.values(params.data).some(item => item?.rawFile instanceof File); if (containsFile) { const formData = new FormData(); Object.entries(params.data).forEach(([key, value]) => { if (value?.rawFile instanceof File) { // 文件字段直接塞入原始二进制对象 formData.append(key, value.rawFile); } else { // 普通字段直接拼接,富文本内容不需要额外转义 formData.append(key, value); } }); return httpClient(`${apiUrl}/${resource}`, { method: 'POST', body: formData, }).then(({ json }) => ({ data: { ...params.data, id: json.id, obj_url: json.obj_url }, })); } // 无文件的普通创建请求走原有JSON逻辑 return httpClient(`${apiUrl}/${resource}`, { method: 'POST', body: JSON.stringify(params.data), headers: new Headers({ 'Content-Type': 'application/json' }) }).then(({ json }) => ({ data: { ...params.data, id: json.id }, })); },
你现有ProductCreate组件的字段配置不需要修改,ImageInput的source名file和后端参数名完全匹配,可以直接使用。
验证点
改完后重启后端服务、刷新前端页面重新提交,即可正常上传文件、写入数据库,返回你预期的JSON结构。如果还是报错,可以先打开浏览器开发者工具查看请求头的Content-Type是否为multipart/form-data,请求体中是否包含二进制文件段。
内容的提问来源于stack exchange,提问作者jillb
相关产品推荐
相关产品推荐

