Spring WebFlux RestController上传报错:Could not find first boundary
错误原因分析
@RequestBody注解误用:WebFlux处理
multipart/form-data请求时,不能用@RequestBody注解。@RequestBody会触发框架将整个请求体作为单一数据流解析,而multipart请求需要按边界分割成多个部分解析,这就导致解析器无法找到multipart的边界标识,抛出"Could not find first boundary"错误。RouterFunction的方式之所以正常,是因为它通过BodyExtractors.toMultipartData()明确指定了multipart解析逻辑,和@RequestBody的处理路径完全不同。subscribe方法的错误使用:在Controller方法内直接调用
subscribe()会脱离Spring WebFlux的响应式上下文管理,导致请求处理流程异常。WebFlux依赖返回的Mono/Flux控制请求生命周期(比如背压、错误处理、资源释放),直接subscribe会让处理逻辑在独立线程执行,无法和请求生命周期绑定。
解决办法
1. 移除@RequestBody注解
WebFlux支持直接将Mono<MultiValueMap<String, Part>>或Flux<Part>作为Controller方法的参数,无需添加@RequestBody,框架会自动使用multipart解析器处理请求。
2. 用响应式操作符替代直接subscribe
将subscribe替换为flatMap、doOnNext等响应式操作符,保持链式调用,让处理逻辑纳入WebFlux的上下文管理。
修正后的Controller代码示例
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<Result<MultiFileUploadResponse>> upload(Mono<MultiValueMap<String, Part>> multiValueMapMono) { return multiValueMapMono.flatMap(stringPartMap -> { stringPartMap.forEach((partName, value) -> { if (value instanceof FilePart) { FilePart file = (FilePart) value; DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File name: {}", file.filename()); } if (value instanceof FormFieldPart) { FormFieldPart type = (FormFieldPart) value; DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("type value: {}", type.value()); } }); return Mono.just(Result.success(new MultiFileUploadResponse())); }); } @PostMapping(value = "/upload2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Mono<Result<MultiFileUploadResponse>> upload2(Flux<Part> input) { return input.doOnNext(value -> { if (value instanceof FilePart) { FilePart file = (FilePart) value; DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File name: {}", file.filename()); } if (value instanceof FormFieldPart) { FormFieldPart type = (FormFieldPart) value; DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("type value: {}", type.value()); } }).then(Mono.just(Result.success(new MultiFileUploadResponse()))); }
额外说明
如果需要单独接收某个文件或表单字段,还可以用@RequestPart注解指定名称,示例如下:
@PostMapping("/single-upload") public Mono<Result<Void>> singleUpload(@RequestPart("file") Mono<FilePart> filePart, @RequestPart("type") Mono<String> type) { return filePart.doOnNext(file -> DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("File: {}", file.filename())) .then(type.doOnNext(t -> DefaultLogGenerator.BIZ_SERVICE_LOGGER.info("Type: {}", t))) .then(Mono.just(Result.success())); }
内容的提问来源于stack exchange,提问作者Luo Stone

