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

React向FastAPI后端传图片报422错误,求解决方法

React + FastAPI 图片上传422错误解决方法

问题场景

用React做前端、FastAPI做后端实现图片上传处理功能时,调用/process-images接口返回422 Unprocessable Entity错误,调整FormData后问题仍未解决。

错误原因

前端用FileReader.readAsDataURL()把图片转成了Base64字符串并添加到FormData中,但FastAPI的UploadFile类型仅接受原始文件对象(multipart/form-data格式的文件流),无法解析Base64字符串,因此触发422错误。

前端代码修正

修改状态存储逻辑:同时保存原始File对象(用于上传)和Base64字符串(用于预览),上传时将原始File对象传入FormData。

修正后的前端代码:

// pages/about.js
import React, { useState, useRef } from "react";
import "../App.css";

const StitchImage = () => {
    // 保存原始文件对象(用于上传)
    const [image1File, setImage1File] = useState(null);
    const [image2File, setImage2File] = useState(null);
    // 保存Base64字符串(用于预览)
    const [image1Preview, setImage1Preview] = useState(null);
    const [image2Preview, setImage2Preview] = useState(null);
    
    const inputRef1 = useRef();
    const inputRef2 = useRef();
    const [stitchedImage, setStitchedImage] = useState(null);

    const handleImage1Change = (e) => {
        const file = e.target.files[0];
        if (file) {
            setImage1File(file);
            // 转Base64用于预览
            const reader = new FileReader();
            reader.onloadend = () => setImage1Preview(reader.result);
            reader.readAsDataURL(file);
        }
    };

    const handleImage2Change = (e) => {
        const file = e.target.files[0];
        if (file) {
            setImage2File(file);
            const reader = new FileReader();
            reader.onloadend = () => setImage2Preview(reader.result);
            reader.readAsDataURL(file);
        }
    };

    const removeImage1 = () => {
        setImage1File(null);
        setImage1Preview(null);
        inputRef1.current?.value = '';
    };

    const removeImage2 = () => {
        setImage2File(null);
        setImage2Preview(null);
        inputRef2.current?.value = '';
    };

    const stitchingImages = async () => {
        if (!image1File || !image2File) {
            window.alert('请先选择两张图片');
            return;
        }
        const formData = new FormData();
        // 注意参数名和后端接口定义一致(后端是img1、img2)
        formData.append('img1', image1File);
        formData.append('img2', image2File);
        
        try {
            const response = await fetch('/process-images', {
                method: 'POST',
                body: formData
            });
            if (!response.ok) throw new Error('图片处理失败');

            const data = await response.json();
            setStitchedImage(data.stitchedImage);
            window.alert('图片处理成功');
        } catch (error) {
            let message = error.message;
            if (error.response) message = await error.response.text();
            else if (error.request) message = '未收到服务器响应';
            window.alert(message);
        }
    };

    return (
        <div className="image-uploader">
            <div className="image-container">
                <h2>图片1</h2>
                <input type="file" accept="image/jpg, image/jpeg, image/png" onChange={handleImage1Change} ref={inputRef1}/>
                {image1Preview && (
                    <div>
                        <img src={image1Preview} alt="预览" className="preview-img" />
                        <br/>
                        <button onClick={removeImage1}>移除图片1</button>
                    </div>
                )}
            </div>
            <div className="image-container">
                <h2>图片2</h2>
                <input type="file" accept="image/jpg, image/jpeg, image/png" onChange={handleImage2Change} ref={inputRef2}/>
                {image2Preview && (
                    <div>
                        <img src={image2Preview} alt="预览" className="preview-img" />
                        <br/>
                        <button onClick={removeImage2}>移除图片2</button>
                    </div>
                )}
            </div>
        
            <button onClick={stitchingImages} className="stitch-button">拼接图片</button>
            {stitchedImage && <img src={stitchedImage} alt="拼接结果" />}
        </div>
    );
};

export default StitchImage;

前端关键修改点

  • 新增image1File、image2File状态存储原始文件对象
  • 预览用单独的image1Preview、image2Preview状态
  • FormData中添加的是原始File对象,且参数名与后端接口定义完全匹配

后端代码优化(可选)

原后端代码存在文件名重名覆盖风险,可改为使用临时文件存储:

import shutil
import tempfile
from fastapi import FastAPI, File, HTTPException, UploadFile
import os
from pydantic import ValidationError

app = FastAPI()

def validate_file_type(filename: str):
    allowed_extensions = ('.jpg', '.jpeg', '.png')
    ext = os.path.splitext(filename)[1]
    if ext.lower() not in allowed_extensions:
        raise HTTPException(status_code=422, detail="仅支持JPEG和PNG格式图片")

@app.post("/process-images")
async def process_images(img1: UploadFile=File(...), img2: UploadFile=File(...)):
    tmp1_path = tmp2_path = None
    try:
        validate_file_type(img1.filename)
        validate_file_type(img2.filename)

        # 用临时文件保存,避免文件名冲突
        with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(img1.filename)[1]) as tmp1:
            shutil.copyfileobj(img1.file, tmp1)
            tmp1_path = tmp1.name
        
        with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(img2.filename)[1]) as tmp2:
            shutil.copyfileobj(img2.file, tmp2)
            tmp2_path = tmp2.name
        
        # 在这里执行图片拼接逻辑,比如调用第三方工具
        # ...
        
        # 示例:返回拼接后的图片Base64(根据实际需求调整)
        # with open(stitched_path, "rb") as f:
        #     stitched_base64 = base64.b64encode(f.read()).decode("utf-8")
        # return {"stitchedImage": f"data:image/png;base64,{stitched_base64}"}
        
        return {"message": "图片处理成功"}
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"服务器内部错误:{str(e)}")
    finally:
        # 清理临时文件(可选)
        if tmp1_path: os.unlink(tmp1_path)
        if tmp2_path: os.unlink(tmp2_path)

验证步骤

  1. 重启前后端服务
  2. 在前端页面选择两张符合格式的图片
  3. 点击「拼接图片」按钮,接口应返回200状态码,后端成功接收并处理图片

内容的提问来源于stack exchange,提问作者tnguyen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:08:20