You need to enable JavaScript to run this app. FeignClient传输MultipartFile时接收端生成空文件、大小不匹配异常
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题根因

  1. 缺少Multipart表单对应的Feign编码器:默认的Spring Cloud Feign使用Jackson编码器处理请求体,不会正确识别MultiValueMap里的ByteArrayResource类型的二进制内容,会将二进制流按照普通字符串序列化处理,遇到文本类控制字符(如\0)时就会截断内容,这也是纯文本文件可正常上传、二进制文件截断的核心原因。
  2. 参数注解与位置错误:Feign方法中给MultiValueMap加了@RequestBody注解,会让编码器将整个Map按照JSON格式序列化,根本不会触发multipart/form-data的编码逻辑;同时将本应是表单参数的filename,通过URL query拼接、@PathVariable声明的方式传递,和接收端用@RequestParam声明的表单参数逻辑不匹配,会造成参数错位。
  3. 依赖缺失:未引入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.03 23:45:08