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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 11:21:18