17370845950

如何在 Spring Boot 中通过单个请求同时上传文件与 JSON 数据

spring boot 默认不支持直接混合使用 @requestbody(json)和 @requestpart(文件)处理 multipart/form-data 请求,正确做法是将 multipartfile 字段嵌入 dto,并统一使用 @requestpart 接收整个表单数据。

在 Spring Boot REST API 开发中,常需在一个 HTTP 请求中同时提交结构化 JSON 数据(如元信息、配置项)和二进制文件(如图片、PDF)。但若按常规方式在 @PostMapping 中同时声明 @RequestBody 和 @RequestPart,会触发 HTTP 415 Unsupported Media Type 错误——这是因为 @RequestBody 依赖 HttpMessageConverter 解析纯 JSON(application/json),而文件上传必须使用 multipart/form-data,二者媒体类型冲突,Spring 无法自动桥接。

✅ 正确解决方案:将 MultipartFile 作为字段嵌入 DTO,并全程使用 @RequestPart 接收

首先,修改你的数据传输对象(DTO),使其兼容 multipart 表单:

public class PortfolioFileSaveRequestDto {
    private String fileName;
    private String description;
    private String category;
    // ⚠️ 关键:直接声明 MultipartFile 字段(非 String 或 byte[])
    private MultipartFile file; // 注意:此处不是 final,且需提供 getter/setter

    // 构造函数、getter、setter 省略...
}

然后,调整 Controller 方法,移除 @RequestBody,改用 @RequestPart 绑定整个 DTO,并确保 consumes 仅指定 multipart/form-data:

@PostMapping(value = "/file/new", 
        consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity> createFile(
        HttpServletRequest servletRequest,
        @RequestPart("request") @Valid PortfolioFileSaveRequestDto request,
        @RequestPart("file") MultipartFile file) { // 可选:显式指定 name,与前端字段名一致

    List tokenInfo = jwtProvider.authorizeJwt(servletRequest.getHeader(AUTHORIZATION));
    // 验证逻辑(如 tokenInfo 是否为空)...

    Portfolio portfolio = portfolioService.saveFile(
            request, 
            user.getUsername(), 
            profile, 
            request.getFile() // ✅ 从 DTO 中获取文件
    );

    return ResponseEntity.status(HttpStatus.CREATED)
            .body(DefaultResponseDto.success(portfolio));
}

? 前端请求示例(JavaScript Fetch):

const formData = new FormData();
formData.append('request', new Blob([JSON.stringify({
    fileName: "report.pdf",
    description: "Q3 financial summary",
    category: "FINANCE"
})], { type: 'application/json' }));
formData.append('file', document.getElementById('fileInput').files[0]);

fetch('/api/file/new', {
    method: 'POST',
    body: formData,
    // ❌ 不要设置 Content-Type!浏览器会自动设置为 multipart/form-data 并携带 boundary
});

⚠️ 注意事项:

  • Spring Boot 2.2+ 默认启用 StandardServletMultipartResolver,请确保 spring.servlet.multipart.* 配置合理(如 max-file-size, max-request-size);
  • @RequestPart("request") 中的 "request" 必须与前端 FormData.append('request', ...) 的 key 严格一致;
  • 若 DTO 中 MultipartFile 字段名为 file,则 @RequestPart("file") 可省略(Spring 会按字段名匹配),但显式声明更健壮;
  • 不要尝试用 @RequestBody + @RequestPart 混合注解——Spring 不支持该组合,这是 415 错误的根本原因。

总结:Spring 的 @RequestPart 能原生解析 multipart 表单中的任意部分(包括 JSON 字符串和文件),关键在于将 JSON 数据“序列化为字符串 Blob”后以表单字段形式提交,并由 DTO 承载结构化语义。这种方式既保持接口原子性,又完全符合 HTTP 规范与 Spring 的设计哲学。