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)
验证步骤
- 重启前后端服务
- 在前端页面选择两张符合格式的图片
- 点击「拼接图片」按钮,接口应返回200状态码,后端成功接收并处理图片
内容的提问来源于stack exchange,提问作者tnguyen
相关产品推荐
相关产品推荐

