You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Feign Client将InputStream转为MultipartFile并实现流式传输

解决方案核心思路

  1. 从原始Part中获取文件元数据(大小、文件名)——Part本身提供了getSize()和getSubmittedFileName()方法,无需仅保存InputStream,直接拿到这些信息就能解决文件大小缺失的问题。
  2. 自定义MultipartFile实现类,基于InputStream和元数据构建,全程不将文件内容加载到内存。
  3. 配置Feign客户端支持流式multipart上传,确保请求过程中无内存缓存。

步骤1:修正文件元数据存储

先修改FileDto,添加文件大小和文件名字段,读取Part时一并保存:

// 修正后的FileDto
public class FileDto {
    private InputStream fileContent;
    private long fileSize;
    private String fileName;
    private String contentType;

    // getter、setter省略
}

// 读取Part时的代码修改
servletRequest.getParts().forEach(part -> {
    // 跳过body部件,仅处理文件类型的部件
    if (!"body".equals(part.getName())) {
        FileDto fileDto = new FileDto();
        fileDto.setFileContent(part.getInputStream());
        fileDto.setFileSize(part.getSize()); // 直接从Part获取文件大小
        fileDto.setFileName(part.getSubmittedFileName()); // 获取原始文件名
        fileDto.setContentType(part.getContentType()); // 获取文件MIME类型
        // 将fileDto存入业务逻辑
    }
});

步骤2:自定义流式MultipartFile实现

实现MultipartFile接口,完全基于传入的流和元数据,避免内存加载:

import org.springframework.web.multipart.MultipartFile;
import java.io.*;

public class StreamMultipartFile implements MultipartFile {

    private final InputStream inputStream;
    private final long size;
    private final String fileName;
    private final String contentType;

    public StreamMultipartFile(InputStream inputStream, long size, String fileName, String contentType) {
        this.inputStream = inputStream;
        this.size = size;
        this.fileName = fileName;
        this.contentType = contentType;
    }

    @Override
    public String getName() {
        return "file"; // 对应Feign接口中@RequestPart("file")的参数名
    }

    @Override
    public String getOriginalFilename() {
        return fileName;
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public long getSize() {
        return size;
    }

    @Override
    public byte[] getBytes() throws IOException {
        // 禁止此方法调用,避免触发内存加载
        throw new UnsupportedOperationException("getBytes() 不支持流式MultipartFile");
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream;
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        // 可选实现:如需转存到本地文件可启用,本次场景无需
        try (InputStream is = inputStream; OutputStream os = new FileOutputStream(dest)) {
            is.transferTo(os);
        }
    }
}

对应的转换方法实现:

private MultipartFile convertToMultiPartFile(FileDto fileDto) {
    return new StreamMultipartFile(
        fileDto.getFileContent(),
        fileDto.getFileSize(),
        fileDto.getFileName(),
        fileDto.getContentType()
    );
}

步骤3:配置Feign客户端支持流式上传

首先添加依赖(以Apache HttpClient为例):

<!-- pom.xml -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.8.0</version> <!-- 适配你的Spring Cloud版本 -->
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.8.0</version>
</dependency>

然后配置Feign的编码器和HTTP客户端:

import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    public Encoder feignEncoder() {
        // 使用SpringFormEncoder支持multipart请求,确保流式处理
        return new SpringFormEncoder();
    }

    // 配置Apache HttpClient禁用内容缓存,支持流式上传
    @Bean
    public CloseableHttpClient httpClient() {
        return HttpClientBuilder.create()
                .disableContentCompression()
                .disableCookieManagement()
                .build();
    }
}

步骤4:简化Feign接口注解

去掉冗余的headers和produces配置,@RequestPart会自动处理Content-Type:

@FeignClient(name = "feignClient", url = "${api.url}", configuration = FeignConfig.class)
public interface ExternalSystemConnector {

    @PostMapping(value = "/v1/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    void createFile(@RequestPart("body") AdditionalInfoDto requestDto,
                    @RequestPart("file") MultipartFile file);
}

关键注意事项

  • 确保InputStream仅在Feign调用时被读取:Part的输入流只能读取一次,不要在业务逻辑中提前消费流。
  • 禁止调用StreamMultipartFile的getBytes()方法:该方法已被禁用,防止误操作导致文件加载到内存。
  • 必须替换默认HTTP客户端:默认的URLConnection会缓存请求内容到内存,使用Apache HttpClient或OkHttp才能实现真正的流式上传。

内容的提问来源于stack exchange,提问作者Zis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 01:45:04