FeignClient传输MultipartFile时接收端生成空文件、大小不匹配异常
Feign跨服务传输MultipartFile附件二进制内容截断/落盘为空问题排查
问题现象
- 发送端本地校验时,待传输的附件文件数据完整、字节大小无缺失
- 接收端最终落盘的文件实际大小远小于源文件,甚至生成0字节空文件
- 纯文本类型文件可正常完成上传,仅非文本类二进制附件出现传输异常
- 传输过程中可观测到接收端获取到的文件字节数组长度,明显小于发送端实际计算的待传输字节长度
涉及的代码上下文
1. 接收端接口定义
@PostMapping(value = "/posts/{postId}/comments/{commentId}/attachments") @JsonView(CaseJsonView.ClientWithComments.class) public ResponseEntity<?> createCommentAttachment(@PathVariable final String postId, @PathVariable final String commentId, @RequestParam("payload") final MultipartFile multipartFile, @RequestParam final String filename, final HttpServletRequest request) throws JsonProcessingException { try { FileUtils.writeByteArrayToFile(new File("C:\\CODE\\data\\" + filename), multipartFile.getBytes()); } catch (IOException e) { e.printStackTrace(); } }
2. 对应封装的Feign客户端定义
@FeignClient(name = "post-service") public interface CommentClient { @RequestMapping(method = RequestMethod.POST, value = "/posts/{postId}/comments/{id}/attachments?filename={filename}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) void storeCommentAttachmentPayload(@PathVariable("postId") String postId, @PathVariable("id") String id, @RequestBody MultiValueMap<String, Object> map, @PathVariable("filename") String filename); }
3. Feign客户端实际调用逻辑
public void sendAttachment() { // 从其他附件服务拉取待传输的资源 final Resource resource = attachmentClient.getAttachmentPayloadById(attachementId); final MultipartFile multipartFile = new InMemoryMultipartFile(filename, filename, mimeType, IOUtils.toByteArray(resource.getInputStream())); final MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); // 封装二进制流为ByteArrayResource final ByteArrayResource byteArrayResource = new ByteArrayResource(multipartFile.getBytes()) { @Override public String getFilename() { return filename == null ? "" : filename; } }; map.add("payload", byteArrayResource); commentService.storeCommentAttachmentPayload(postId, commentId, map, filename); }
问题根因
- 缺少Multipart表单对应的Feign编码器:默认的Spring Cloud Feign使用Jackson编码器处理请求体,不会正确识别
MultiValueMap里的ByteArrayResource类型的二进制内容,会将二进制流按照普通字符串序列化处理,遇到文本类控制字符(如\0)时就会截断内容,这也是纯文本文件可正常上传、二进制文件截断的核心原因。 - 参数注解与位置错误:Feign方法中给
MultiValueMap加了@RequestBody注解,会让编码器将整个Map按照JSON格式序列化,根本不会触发multipart/form-data的编码逻辑;同时将本应是表单参数的filename,通过URL query拼接、@PathVariable声明的方式传递,和接收端用@RequestParam声明的表单参数逻辑不匹配,会造成参数错位。 - 依赖缺失:未引入
feign-form-spring组件时,没有对应的编码器能正确处理multipart请求的边界划分、二进制流透传,会将二进制字节做转义压缩,直接造成内容丢失。
修复方案
- 引入与Spring Cloud版本匹配的Form编码器依赖:
<dependency> <groupId>io.github.openfeign.form</groupId> <artifactId>feign-form-spring</artifactId> <version>3.8.0</version> </dependency>
- 配置自定义的Feign Multipart编码器,替换默认的Jackson编码器:
@Configuration public class FeignMultipartConfig { @Bean public Encoder feignFormEncoder() { return new SpringFormEncoder(new SpringEncoder(FeignClientsConfiguration.feignLoggerFactory())); } }
- 修正Feign客户端的方法定义:
- 移除
MultiValueMap上的@RequestBody注解,multipart表单请求不需要额外声明RequestBody - 移除URL上拼接的
filename参数占位符,去掉filename的@PathVariable声明
修正后的客户端定义:
@FeignClient(name = "post-service", configuration = FeignMultipartConfig.class) public interface CommentClient { @RequestMapping(method = RequestMethod.POST, value = "/posts/{postId}/comments/{id}/attachments", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) void storeCommentAttachmentPayload(@PathVariable("postId") String postId, @PathVariable("id") String id, MultiValueMap<String, Object> map); }
- 修正调用端的参数封装逻辑:将
filename作为普通表单字段加到MultiValueMap中,和接收端的@RequestParam参数对齐,不要单独传参:
public void sendAttachment() { final Resource resource = attachmentClient.getAttachmentPayloadById(attachementId); final byte[] fileBytes = IOUtils.toByteArray(resource.getInputStream()); final MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); // 封装二进制流资源 final ByteArrayResource byteArrayResource = new ByteArrayResource(fileBytes) { @Override public String getFilename() { return filename; } }; map.add("payload", byteArrayResource); // 将filename作为表单参数放入Map map.add("filename", filename); commentService.storeCommentAttachmentPayload(postId, commentId, map); }
- 排查链路中间件的配置:如果服务间调用经过网关(Spring Cloud Gateway/Nginx),需要确认对应路由的最大请求大小、最大文件大小配置匹配待传输的附件尺寸,避免网关层直接截断请求流。
内容的提问来源于stack exchange,提问作者Brijesh Wani
相关产品推荐
相关产品推荐

