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

SpringBoot如何转发Multipart请求至内网微服务实现文件不落地存储

可行方案说明

完全可以实现,核心原则是全链路以流的方式处理上传请求,禁止任何节点触发Multipart请求的自动落地逻辑,两个服务都不需要把上传文件写到本地磁盘。

第一步:基础配置(两个服务必须同步修改)

SpringBoot默认会自动解析multipart/form-data请求,把上传的文件写入本地临时目录,这是文件落地的核心原因,首先要关掉这个自动配置:

spring:
  servlet:
    multipart:
      enabled: false

避坑提醒:配置完后不要在接口里注入MultipartHttpServletRequest、MultipartFile类型的参数,也不要提前调用request.getParameter()系列方法,这些操作都会触发Servlet容器自动解析Multipart请求,导致文件写本地。

第二步:microservice-1(入口服务)请求透传实现

入口层不要解析请求内容,直接把原始请求的输入流原样转发给内网的microservice-2即可,转发时必须保留原请求的Content-Type头(里面带Multipart分隔需要的boundary参数,不能改),可以用RestTemplate、OkHttp、WebClient任意HTTP客户端实现,核心逻辑参考:

@RestController
public class UploadForwardController {
    private final RestTemplate restTemplate = new RestTemplate();
    private final URI targetUri = URI.create("http://microservice-2/internal/upload");

    @PostMapping(value = "/api/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<byte[]> forwardUpload(HttpServletRequest request) throws IOException {
        // 直接复用原请求的输入流,不做全量读取
        InputStreamResource requestBody = new InputStreamResource(request.getInputStream()) {
            @Override
            public long contentLength() throws IOException {
                // 直接取原请求的content-length,避免提前读流计算长度导致全量加载到内存
                return Long.parseLong(request.getHeader(HttpHeaders.CONTENT_LENGTH));
            }
        };

        RequestEntity<InputStreamResource> forwardReq = RequestEntity.post(targetUri)
                .header(HttpHeaders.CONTENT_TYPE, request.getContentType())
                .body(requestBody);

        // 转发请求,把后端服务的响应原样返回给用户
        return restTemplate.exchange(forwardReq, byte[].class);
    }
}

如果microservice-1本身是Spring Cloud Gateway网关,甚至不用写上面的转发代码,只要关掉Multipart自动解析,网关默认就是流式透传,不会落地文件。

第三步:microservice-2(内网服务)流式处理实现

内网服务同样不要用默认的Multipart解析,直接从请求里拿输入流做流式处理即可。如果需要解析Multipart里的普通表单字段、区分多个上传文件,可以用Apache Commons FileUpload的流式解析API,这个API不会把文件写本地,是逐段读取解析的,核心逻辑参考:
首先引入依赖:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

处理逻辑:

@RestController
public class InternalUploadController {
    @PostMapping("/internal/upload")
    public void handleUpload(HttpServletRequest request) throws Exception {
        ServletFileUpload upload = new ServletFileUpload();
        FileItemIterator itemIterator = upload.getItemIterator(request);
        while (itemIterator.hasNext()) {
            FileItemStream item = itemIterator.next();
            try (InputStream stream = item.openStream()) {
                if (item.isFormField()) {
                    // 处理普通表单字段,直接读流拿值
                    String fieldValue = Streams.asString(stream, StandardCharsets.UTF_8);
                    // 业务逻辑处理字段
                } else {
                    // 拿到文件输入流,直接边读边处理,比如直接推送到对象存储、做实时内容校验等
                    // 注意不要把流全量转成byte[]存在内存,保持边读边写的流式处理逻辑
                }
            }
        }
    }
}

额外优化点

  • 两个服务都可以适当调整Servlet容器的连接超时、请求大小限制,适配大文件上传场景
  • 如果需要支持断点续传,可以在流透传的时候透传对应的Range头即可,逻辑和普通HTTP请求透传没有区别
  • 大文件场景下建议用异步HTTP客户端(比如WebClient、OkHttp异步模式)做转发,避免阻塞Servlet线程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 20:45:32