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
相关产品推荐
相关产品推荐

