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

FastAPI+React+MongoDB架构下二进制存储的产品图片无法显示问题求助

FastAPI+React+MongoDB架构下二进制存储的产品图片无法显示问题求助

我正在做一个个人项目,用FastAPI+React+MongoDB的技术栈,把产品图片以二进制形式存在MongoDB里。用户头像的同款功能已经完美跑通了,但产品图片就是显示不出来,现在遇到了UnicodeDecodeError的问题,实在找不到问题根源,求各位大佬帮忙看看!

技术栈

  • FastAPI(后端)
  • React(前端)
  • MongoDB(数据库)

产品数据模型(FastAPI Pydantic)

from pydantic import BaseModel, Field, Optional
from datetime import date
from typing import List

class Products(BaseModel):
    _id: str = Field(alias="_id")
    productName: str
    productID: str
    photographer: str
    productEvent: str
    productTags: Optional[List[str]] = None
    productPrice: float
    productCurrency: str
    productDate: Optional[date] = None
    productImage: Optional[bytes] = None
    productImageExtension: Optional[str] = None

前端产品上传组件(React)

import React, {useState, useEffect} from 'react';
import axios from 'axios';

function ProductUpload(){
    const [productName, setProductName] = useState('');
    const [productID, setProductID] = useState('');
    const [photographer, setPhotographer] = useState('');
    const [productEvent, setProductEvent] = useState('');
    const [productTags, setProductTags] = useState([]);
    const [productPrice, setProductPrice] = useState(0);
    const [productCurrency, setProductCurrency] = useState('');
    const [productDate, setProductDate] = useState('');
    const [productImage, setProductImage] = useState(null);
    const [error, setError] = useState('');

    const handleSubmit = async (event) => {
        event.preventDefault();
        const formData = new FormData();
        formData.append('productName', productName);
        formData.append('productID', productID);
        formData.append('photographer', photographer);
        formData.append('productEvent', productEvent);
        formData.append('productTags', JSON.stringify(productTags));
        formData.append('productPrice', productPrice);
        formData.append('productCurrency', productCurrency);
        formData.append('productDate', productDate);
        formData.append('productImage', productImage);

        try {
            const response = await axios.post('http://localhost:8000/api/products', formData);
            console.log(response.data);
        } catch (error) {
            console.log(error.response.data);
            setError(error.response.data.detail);
        }
    };

    return(
        <form onSubmit={handleSubmit} style={{alignContents: "center", justifyContent: "center", display: "flex", flexDirection: "column", margin: "auto", width: "100%"}}>
            <label>
                Product Name:
                <input type="text" value={productName} onChange={(event) => setProductName(event.target.value)} />
            </label>
            <label>
                Product ID:
                <input type="text" value={productID} onChange={(event) => setProductID(event.target.value)} />
            </label>
            <label>
                Photographer:
                <input type="text" value={photographer} onChange={(event) => setPhotographer(event.target.value)} />
            </label>
            <label>
                Product Event:
                <input type="text" value={productEvent} onChange={(event) => setProductEvent(event.target.value)} />
            </label>
            <label>
                Product Tags:
                <input type="text" value={productTags} onChange={(event) => setProductTags(event.target.value.split(','))} />
            </label>
            <label>
                Product Price:
                <input type="number" value={productPrice} onChange={(event) => setProductPrice(event.target.value)} />
            </label>
            <label>
                Product Currency:
                <input type="text" value={productCurrency} onChange={(event) => setProductCurrency(event.target.value)} />
            </label>
            <label>
                Product Date:
                <input type="date" value={productDate} onChange={(event) => setProductDate(event.target.value)} />
            </label>
            <label>
                Product Image:
                <input type="file" onChange={(event) => setProductImage(event.target.files[0])} />
            </label>
            <button type="submit">Submit product</button>
        </form>
    )
}

export default ProductUpload;

后端核心代码

main.py 接口部分

import io
from datetime import datetime
from fastapi import FastAPI, Form, File, UploadFile, HTTPException
from fastapi.responses import StreamingResponse
from pymongo import Binary
from PIL import Image
from your_module import Products, fetch_all_products, fetch_one_product, fetch_product_image, create_product, update_product, remove_product

app = FastAPI()

@app.get("/api/products")
async def get_products():
    response = await fetch_all_products()
    return response

@app.get("/api/products{productName}", response_model=Products)
async def get_product_by_name(productName):
    response = await fetch_one_product(productName)
    if response:
        return response
    raise HTTPException(404, f"There is no Product item with this title: {productName}")

@app.get("/api/products/image/{productID}")
async def get_product_img(productID: str):
    image_data, image_extension = await fetch_product_image(productID)
    if image_data:
        return StreamingResponse(io.BytesIO(image_data), media_type=f"image/{image_extension}")
    raise HTTPException(404, f"There is no Product with this ID: {productID}")

@app.post("/api/products")
async def post_product(
    productName: str = Form(...),
    productID: str = Form(...),
    photographer: str = Form(...),
    productEvent: str = Form(...),
    productTags: str = Form(...),
    productPrice: float = Form(...),
    productCurrency: str = Form(...),
    productDate: str = Form(...),
    productImage: UploadFile = File(...)
):
    productTagsList = [tag.strip() for tag in productTags.split(',')]
    productDateObj = datetime.strptime(productDate, "%Y-%m-%d").date()
    image_data = await productImage.read()
    img = Image.open(io.BytesIO(image_data))
    original_format = img.format
    productImageExtension = original_format.lower()
    binary_image_data = Binary(image_data)

    product = Products(
        productName=productName,
        productID=productID,
        photographer=photographer,
        productEvent=productEvent,
        productTags=productTagsList,
        productPrice=productPrice,
        productCurrency=productCurrency,
        productDate=productDateObj,
        productImage=binary_image_data,
        productImageExtension=productImageExtension
    )

    response = await create_product(product.dict())
    if response:
        return response
    raise HTTPException(400, "Something went wrong / Bad Request")

@app.put("/api/products{productName}/", response_model=Products)
async def put_product(productName:str, productID:str, photographer:str, productEvent:str, productTags:List[str], productPrice:float,
                      productCurrency: str, productDate: datetime.date, productImage: bytes):
    response = await update_product(productName, productID, photographer, productEvent, productTags,
                                    productPrice, productCurrency, productDate, productImage)
    if response:
        return response
    raise HTTPException(400, "Something went wrong / Bad Request")

@app.delete("/api/products{productName}")
async def delete_product(productName):
    response = await remove_product(productName)
    if response:
        return "Successfully deleted Product Item"
    raise HTTPException(400, "Something went wrong / Bad Request")

database.py 数据库操作部分

import datetime
from pymongo import Binary
from your_module import Products, database

async def fetch_one_product(name):
    document = await database.products.find_one({"productName":name})
    return document

async def fetch_all_products():
    productsL = []
    cursor = database.products.find({})
    async for document in cursor:
        productsL.append(Products(**document))
    return productsL

async def create_product(product: dict):
    product['productDate'] = datetime.datetime.combine(product['productDate'], datetime.datetime.min.time())
    document = product
    result = await database.products.insert_one(document)
    document["_id"] = result.inserted_id
    product_instance = Products(**document)
    return product_instance.to_dict()

async def update_product(productName, productID, photographer, productEvent, productTags, productPrice, productCurrency, productDate, productImage):
    await database.products.update_one({"name":productName},{"$set":{"productName": productName, "productID": productID, "photographer": photographer,
                                                                     "productEvent": productEvent, "productTags": productTags, "productPrice": productPrice,
                                                                     "productCurrency": productCurrency, "productDate": productDate, "productImage": productImage}})
    document = await database.products.find_one({"name":productName})
    return document

async def remove_product(name):
    await database.products.delete_one({"name":name})
    return True

async def fetch_one_product_byID(productID):
    document = await database.products.find_one({"productID": productID})
    return document

async def fetch_product_image(productID: str):
    product = await database.products.find_one({"productID": productID}, {"productImage": 1, "productImageExtension": 1})
    if product:
        image_data = bytes(product["productImage"])
        image_extension = product["productImageExtension"]
        return image_data, image_extension
    return None, None

前端产品图片展示组件(React)

import React, { useState, useEffect } from "react";
import axios from "axios";

const ProductImage = ({ productID, size = "normal" }) => {
    const [image, setImage] = useState(null);
    let imageSize = "250px";
    if(size === "small"){
        imageSize = "20px";
    }

    useEffect(() => {
        const fetchImage = async () => {
            try {
                const response = await axios.get(`http://localhost:8000/api/products/image/${productID}`, {
                    responseType: "blob",
                });
                setImage(URL.createObjectURL(response.data));
            } catch (error) {
                console.error(error);
            }
        };
        fetchImage();
    }, [productID]);

    return <img src={image} alt={productID} style={{borderRadius: "50%", width: imageSize, height: imageSize}}/>;
};

export default ProductImage;

遇到的错误

当访问展示产品图片的页面时,后端抛出以下错误:

File "pydantic\json.py", line 45, in pydantic.json.lambda
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte

对比可正常工作的用户图片功能

用户头像的二进制存储和展示功能完全正常,以下是核心代码片段:

后端用户图片相关代码

@app.post("/api/User/image")
async def upload_user_img(email: str = Form(...), image: UploadFile = File(...)):
    res = await user_image_upload(email, image)
    return res

@app.get("/api/User/image/{email}")
async def get_user_img(email: str):
    image_data = await fetch_user_image(email)
    if image_data:
        return StreamingResponse(io.BytesIO(image_data), media_type="image/png")
    raise HTTPException(404, f"There is no User with this email: {email}")

async def user_image_upload(email: str, image: UploadFile = File(...)):
    user_email = email
    user = database.User.find_one({"email": user_email})
    if user:
        image_data = await image.read()
        binary_image_data = Binary(image_data)
        database.User.update_one({"email": user_email}, {"$set": {"image": binary_image_data}})
        return {"message": "Image uploaded successfully"}
    else:
        return {"message": "User not found"}

async def fetch_user_image(email):
    user = await database.User.find_one({"email": email}, {"image": 1})
    return user["image"]

前端用户图片展示组件

import React, { useState, useEffect } from "react";
import axios from "axios";

const UserImage = ({ email, size = "normal" }) => {
    const [image, setImage] = useState(null);
    let imageSize = "250px";
    if(size === "small"){
        imageSize = "20px";
    }

    useEffect(() => {
        const fetchImage = async () => {
            try {
                const response = await axios.get(`http://localhost:8000/api/User/image/${email}`, {
                    responseType: "blob",
                });
                setImage(URL.createObjectURL(response.data));
            } catch (error) {
                console.error(error);
            }
        };
        fetchImage();
    }, [email]);

    return <img src={image} alt={email} style={{borderRadius: "50%", width: imageSize, height: imageSize}}/>;
};

export default UserImage;

用户数据模型

from pydantic import BaseModel, Optional
from datetime import datetime

class User(BaseModel):
    _id: str
    name: Optional[str] = None
    email: str
    phone: Optional[str] = None
    password: str
    active: Optional[bool] = None
    image: Optional[bytes] = None
    dob: Optional[datetime] = None
    weight: Optional[float] = None
    club: Optional[str] = None
    category: Optional[str] = None
    location: Optional[str] = None
    bank_details: Optional[bankDetails] = None
    isAdmin: Optional[bool] = None
    type: Optional[str] = None

备注:内容来源于stack exchange,提问作者sf001

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 10:12:41