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

Spring Boot API报415 Unsupported Media Type错误排查求助

商品注册API 415 Unsupported Media Type 问题解决

问题描述

开发商品注册API,通过POST请求同时传递JSON格式的商品信息和图片文件到Amazon S3,在Postman测试时返回415错误,控制台提示Content-Type 'application/json' is not supported。当前Postman采用raw方式传JSON,form-data方式传文件。

错误信息

接口返回的415错误:

{
    "status": 415,
    "error": "Unsupported Media Type"
}

控制台输出:

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/json' is not supported]

控制器代码

package com.api.business_manager_api.Controllers;

import com.api.business_manager_api.Dtos.ProductDto;
import com.api.business_manager_api.Mappers.ProductMapper;
import com.api.business_manager_api.Models.CategoryModel;
import com.api.business_manager_api.Models.ProductModel;
import com.api.business_manager_api.Services.CategoryService;
import com.api.business_manager_api.Services.ProductService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/product")
public class ProductController {
    final ProductService productService;
    final CategoryService categoryService;
    final ProductMapper productMapper;
    final S3Client s3Client;

    private final String BUCKET_NAME = "awstockproducts" + System.currentTimeMillis();

    public ProductController(ProductService productService, ProductMapper productMapper, CategoryService categoryService, S3Client s3Client) {
        this.productService = productService;
        this.productMapper = productMapper;
        this.categoryService = categoryService;
        this.s3Client = s3Client;
    }
    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<Object> saveProduct (@RequestPart("productDto") @Valid ProductDto productDto, @RequestPart("file")MultipartFile file) {
        try {
            if (productService.existsByProduct(productDto.getProduct())) {
                return ResponseEntity.status(HttpStatus.CONFLICT).body("Product already exists!");
            }
            ProductModel productModel = productMapper.toProductModel(productDto);
            CategoryModel categoryModel = categoryService.findById(productDto.getProductCategory().getCategory_id())
                    .orElseThrow(() -> new RuntimeException("Category not found"));
            productModel.setProductCategory(categoryModel);

            String fileName = "/products/images/" + UUID.randomUUID().toString() + "-" + file.getOriginalFilename();

            s3Client.putObject(PutObjectRequest
                            .builder()
                            .bucket(BUCKET_NAME)
                            .key(fileName)
                            .build(),
                        software.amazon.awssdk.core.sync.RequestBody.fromString("Testing java sdk"));
            return ResponseEntity.status(HttpStatus.CREATED).body(productService.save(productModel));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("Cannot create product. Check if the fields sent in your request are correct.");
        }
    }

    @GetMapping
    public ResponseEntity<List<ProductModel>> getAllProducts() {
        return ResponseEntity.status(HttpStatus.OK).body(productService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<Object> getOneProduct(@PathVariable(value = "id")UUID id) {
        Optional<ProductModel> productModelOptional = productService.findById(id);
        if (!productModelOptional.isPresent()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found");
        }
        return ResponseEntity.status(HttpStatus.OK).body(productModelOptional.get());
    }

    @PutMapping("/{id}")
    public ResponseEntity<Object> updateProduct(@PathVariable(value = "id") UUID id,
                                                @RequestBody @Valid ProductDto productDto) {
        Optional<ProductModel> productModelOptional = productService.findById(id);
        if (!productModelOptional.isPresent()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found!");
        }
        ProductModel productModel = productModelOptional.get();
        productModel.setProduct(productDto.getProduct());
        productModel.setDescription(productDto.getDescription());
        productModel.setPrice(productDto.getPrice());
        productModel.setExtraPrice(productDto.getExtraPrice());
        productModel.setStock(productDto.getStock());
        productModel.setProductCategory(productDto.getProductCategory());

        return ResponseEntity.status(HttpStatus.OK).body(productService.save(productModel));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Object> deleteProduct(@PathVariable(value = "id") UUID id) {
        Optional<ProductModel> productModelOptional = productService.findById(id);
        if (!productModelOptional.isPresent()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product not found!");
        }
        productService.delete(productModelOptional.get());
        return ResponseEntity.status(HttpStatus.OK).body("Product deleted succesfully!");
    }
}

问题原因

控制器的@PostMapping指定了consumes = MediaType.MULTIPART_FORM_DATA_VALUE,说明接口仅接受multipart/form-data类型的请求。但你在Postman中同时使用raw(Content-Type为application/json)和form-data,导致请求的Content-Type冲突,Spring无法正确解析请求结构。此外,@RequestPart需要在multipart请求中为每个部分指定正确的Content-Type才能完成反序列化。

解决方法

1. Postman正确配置

  • 新建POST请求,URL填写接口地址/product
  • 切换到Body标签,选择form-data类型
  • 添加第一个参数:
    • Key:productDto
    • 类型选择Text(不要选File)
    • Value框中输入JSON格式的商品信息
    • 展开该条目的隐藏选项,设置Content-Type为application/json
  • 添加第二个参数:
    • Key:file
    • 类型选择File
    • 点击Select Files选择要上传的图片文件
  • 检查请求头,确保没有手动设置Content-Type,Postman会自动生成multipart/form-data; boundary=xxx的请求头,手动设置会导致冲突。

2. 代码优化建议

  • 在@RequestPart上显式指定Content-Type,确保Spring正确解析JSON:
    @RequestPart(value = "productDto", required = true, contentType = MediaType.APPLICATION_JSON_VALUE) @Valid ProductDto productDto
    
  • 修正S3上传代码,实际上传文件内容而非测试字符串:
    s3Client.putObject(PutObjectRequest
                    .builder()
                    .bucket(BUCKET_NAME)
                    .key(fileName)
                    .contentType(file.getContentType())
                    .build(),
                software.amazon.awssdk.core.sync.RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
    
  • 修正BUCKET_NAME的定义,当前代码每次控制器实例化都会生成新桶名,建议改为固定值或从配置文件读取:
    private final String BUCKET_NAME = "awstockproducts"; // 固定桶名
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 20:27:17