Spring Webflux接收MultipartFile报415 UnsupportedMediaType异常排查
你遇到的核心问题是在Spring Webflux控制器中误用了Servlet环境的MultipartFile类型,Webflux作为非阻塞的响应式框架,需要使用对应的响应式文件上传类型来处理请求,这才导致参数解析阶段就抛出415错误,控制器方法根本没机会执行。
下面是具体的修复步骤:
1. 替换控制器参数类型为Webflux支持的FilePart
将原来的MultipartFile换成org.springframework.http.codec.multipart.FilePart,这是Webflux专门用于单文件上传的响应式类型:
import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RestController public class UploadController { private static final Logger log = LoggerFactory.getLogger(UploadController.class); private final StorageService storageService; // 构造注入StorageService public UploadController(StorageService storageService) { this.storageService = storageService; } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<Void> save(@RequestPart("file") FilePart file) { log.info("Storing a new file. Received by Controller"); // 调整storageService的store方法适配FilePart return this.storageService.store(file); } }
如果需要处理多文件上传,可以改用Flux<FilePart>:
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<Void> save(@RequestPart("files") Flux<FilePart> files) { log.info("Storing multiple files. Received by Controller"); return files.flatMap(this.storageService::store).then(); }
2. 调整存储服务适配FilePart
FilePart提供了响应式的文件传输方法,你需要修改storageService的store方法来支持它,比如写入本地磁盘的示例:
import org.springframework.http.codec.multipart.FilePart; import reactor.core.publisher.Mono; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.springframework.stereotype.Service; @Service public class StorageService { private final Path storageLocation = Paths.get("your-target-storage-directory"); public Mono<Void> store(FilePart file) { // 确保存储目录存在 try { Files.createDirectories(storageLocation); } catch (Exception e) { return Mono.error(new RuntimeException("Failed to create storage directory", e)); } // 使用响应式的transferTo方法写入文件 Path targetPath = storageLocation.resolve(file.filename()); return file.transferTo(targetPath); } }
3. 检查前端Angular请求的正确性
确保你的Angular代码不要手动设置Content-Type为multipart/form-data——浏览器会自动处理这个头部并添加正确的boundary参数,手动设置反而会导致格式错误:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class UploadService { constructor(private http: HttpClient) {} uploadFile(file: File): Observable<void> { const formData = new FormData(); formData.append('file', file); // 这里的字段名要和控制器@RequestPart的name完全一致 return this.http.post<void>('/your-upload-endpoint', formData, { // 不要添加这行:'Content-Type': 'multipart/form-data' }); } }
错误原因解释
MultipartFile是基于Servlet API的类型,属于Spring MVC(传统阻塞式Web框架)的范畴。而Spring Webflux是基于Reactive Streams的非阻塞框架,它使用FilePart这类响应式类型来处理文件上传,这样才能和整个非阻塞模型兼容。当你在Webflux控制器中使用MultipartFile时,框架找不到合适的消息阅读器来解析请求,因此抛出了415 Unsupported Media Type异常。
另外,你使用的spring-webflux-5.0.4.RELEASE版本比较老旧,建议升级到较新的稳定版(比如5.3.x或更高),可以避免一些已知的文件上传相关bug。
内容的提问来源于stack exchange,提问作者mep

