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

如何在Spring控制器接收含图片的Product实体及React前端实现

解决Spring + React 同时传递实体与图片文件的问题

后端(Spring Boot)调整

原控制器同时使用@RequestBody和@RequestParam会触发HttpMediaTypeNotSupportedException,原因是@RequestBody接收JSON格式,而文件上传需要multipart/form-data类型,两者无法兼容。需修改控制器如下:

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import java.io.IOException;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<?> create(@Valid @ModelAttribute Product product,
                                    BindingResult result,
                                    @RequestParam("imageData") MultipartFile imageData) throws IOException {
        if (result.hasErrors()) {
            return ResponseEntity.badRequest().body(result.getAllErrors());
        }
        // 将图片转为字节数组存入实体
        product.setImage(imageData.getBytes());
        Product savedProduct = productService.save(product);
        return ResponseEntity.ok(savedProduct);
    }
}

关键说明:

  • 移除@RequestBody,改用@ModelAttribute:Spring会自动从multipart/form-data的表单字段中绑定Product实体的属性,要求实体字段名与前端表单字段的name属性一致。
  • 指定consumes = MediaType.MULTIPART_FORM_DATA_VALUE:明确接口接收的媒体类型,避免解析错误。
  • 处理IOException:MultipartFile.getBytes()可能抛出IO异常,需显式处理或向上抛出。

前端(React)调整

前端不能直接发送JSON格式数据,需使用FormData对象包装表单字段和文件,这是文件上传的标准方式:

import { useState } from 'react';

function ProductForm() {
    const [productName, setProductName] = useState('');
    const [price, setPrice] = useState('');
    const [image, setImage] = useState(null);

    const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData();
        // 添加Product实体的字段,name需与后端实体属性名一致
        formData.append('name', productName);
        formData.append('price', price);
        // 添加图片文件,name需与后端@RequestParam的参数名一致
        if (image) {
            formData.append('imageData', image);
        }

        try {
            const res = await fetch('/api/products', {
                method: 'POST',
                body: formData
                // 不要手动设置Content-Type,浏览器会自动生成带正确边界的请求头
            });
            const data = await res.json();
            console.log('产品保存成功:', data);
        } catch (err) {
            console.error('保存失败:', err);
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>产品名称:</label>
                <input
                    type="text"
                    value={productName}
                    onChange={(e) => setProductName(e.target.value)}
                    name="name"
                    required
                />
            </div>
            <div>
                <label>价格:</label>
                <input
                    type="number"
                    value={price}
                    onChange={(e) => setPrice(e.target.value)}
                    name="price"
                    required
                />
            </div>
            <div>
                <label>产品图片:</label>
                <input
                    type="file"
                    onChange={(e) => setImage(e.target.files[0])}
                    name="imageData"
                    accept="image/*"
                    required
                />
            </div>
            <button type="submit">保存产品</button>
        </form>
    );
}

export default ProductForm;

关键说明:

  • 使用FormData封装数据:将实体字段和文件统一加入FormData,确保符合multipart/form-data格式要求。
  • 不手动设置Content-Type:浏览器会自动生成包含边界标识的Content-Type头,避免因边界不匹配导致的解析失败。
  • 表单字段name匹配:输入框的name属性必须与后端Product实体的字段名、文件参数名完全一致。

Postman测试方法

  1. 新建POST请求,填写接口URL(如http://localhost:8080/api/products)。
  2. 切换到Body标签,选择form-data类型。
  3. 添加以下键值对:
    • name:文本类型,输入产品名称。
    • price:文本/数字类型,输入产品价格。
    • imageData:类型选择File,选择本地图片文件。
  4. 发送请求,即可完成实体与图片的同时提交。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 04:55:34