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

Spring Boot实现外部URL文件打包ZIP流式下载(不加载到内存)

远程大文件流式打包ZIP下载实现方案(Spring Boot场景)

核心逻辑

整个流程完全基于流式传输实现,全程不会将完整文件或者ZIP包加载到内存:从远程服务器拉取文件流的同时,直接写入绑定了HTTP响应的ZIP输出流,边拉边传边打包,用户端可以实时收到下载流,服务端无内存溢出风险。

前置依赖

仅需Spring Boot基础Web依赖即可,无需额外引入第三方包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

完整代码实现

import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
@RequestMapping("/download")
public class ZipDownloadController {

    private final RestTemplate restTemplate = new RestTemplate();
    // 缓冲区大小,可根据实际场景调整,建议4KB~16KB
    private static final int BUFFER_SIZE = 8192;

    @GetMapping("/remote-files-zip")
    public void downloadRemoteFilesAsZip(HttpServletResponse response) throws IOException {
        // 配置要下载的远程文件列表:key为远程文件URL,value为ZIP包内的文件名
        List<Map.Entry<String, String>> remoteFileList = List.of(
                Map.entry("http://remote-server.com/file1.pdf", "项目说明书.pdf"),
                Map.entry("http://remote-server.com/file2.mp4", "演示视频.mp4"),
                Map.entry("http://remote-server.com/file3.zip", "附件包.zip")
        );

        // 配置响应头
        response.setContentType("application/zip");
        String zipFileName = URLEncoder.encode("批量下载包.zip", StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + zipFileName);
        // 禁用缓存,避免流式输出被缓冲
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setHeader("Pragma", "no-cache");

        try (OutputStream os = response.getOutputStream();
             ZipOutputStream zos = new ZipOutputStream(os, StandardCharsets.UTF_8)) {

            byte[] buffer = new byte[BUFFER_SIZE];
            for (Map.Entry<String, String> fileItem : remoteFileList) {
                String remoteUrl = fileItem.getKey();
                String entryName = fileItem.getValue();

                // 流式获取远程文件输入流,不会加载整个文件到内存
                InputStream remoteIs = restTemplate.execute(
                        remoteUrl,
                        HttpMethod.GET,
                        null,
                        clientHttpResponse -> clientHttpResponse.getBody()
                );

                if (remoteIs == null) {
                    // 可根据需求调整异常处理逻辑:跳过当前文件/抛出异常终止下载
                    continue;
                }

                try (remoteIs) {
                    // 创建ZIP条目
                    zos.putNextEntry(new ZipEntry(entryName));
                    int len;
                    // 边读边写,每次只缓冲BUFFER_SIZE大小的字节
                    while ((len = remoteIs.read(buffer)) != -1) {
                        zos.write(buffer, 0, len);
                    }
                    zos.closeEntry();
                    // 立即flush,避免缓冲区堆积
                    zos.flush();
                }
            }
            zos.finish();
        }
    }
}

关键注意事项

  • 禁止使用RestTemplate.getForObject(url, byte[].class)这类会将完整远程文件加载为字节数组的API,必须使用流式请求获取InputStream
  • 不需要将ZIP包先写入本地磁盘或者内存缓冲区,直接将ZipOutputStream绑定到HTTP响应输出流即可
  • 中文文件名必须做UTF-8编码,避免不同浏览器下文件名乱码
  • 所有流都要使用try-with-resources语法自动关闭,避免HTTP连接、IO资源泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 21:24:05