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:
- 添加第二个参数:
- Key:
file - 类型选择
File - 点击
Select Files选择要上传的图片文件
- Key:
- 检查请求头,确保没有手动设置
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
相关产品推荐
相关产品推荐

