|

Aimee

Write the Code. Change the World.

批量打包下载怎么设计:流式 ZIP 与异步任务

· 分享镜

批量打包下载怎么设计:流式 ZIP 与异步任务

批量打包下载的典型需求:用户勾选几百个文件,后端打成一个 ZIP 返回。

最直觉的实现是把文件全部读进内存再压缩:200 个文件平均 1MB,就是 200MB 堆内存——并发 10 个用户同时打包,2GB 内存光这一个接口就占满了。


流式 ZIP:边读边写,不在内存积累

response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=files.zip");

try (ZipOutputStream zos = new ZipOutputStream(
        new BufferedOutputStream(response.getOutputStream()))) {

    for (FileItem file : fileList) {
        zos.putNextEntry(new ZipEntry(file.getName()));
        // 从 OSS 流式读取,写入 zip 流
        try (InputStream in = ossClient.getObject(file.getOssKey()).getObjectContent()) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = in.read(buf)) != -1) {
                zos.write(buf, 0, len);  // 每次只占 8KB 缓冲
            }
        }
        zos.closeEntry();
    }
}

整个过程内存里只有 8KB 的读缓冲区,无论多少个文件都不会因为文件总量撑内存。

流式输出的限制:不能设置 Content-Length(因为压缩后大小在写完前不知道),浏览器看不到下载进度条。这在大多数场景可以接受。


同步还是异步

场景方案
文件数量少(< 20 个),总大小 < 50MB同步流式输出
文件数量多或体积大异步任务:后台生成 ZIP,上传 OSS,发下载链接

异步方案的流程:

用户提交打包请求 → 创建 task,返回 taskId
          ↓(Worker 异步执行)
    从 OSS 流式读取每个文件 → 写入 ZipOutputStream → 目标是 OSS 的 PutObject 流
          ↓
    ZIP 文件上传完成,生成带过期时间的下载链接
          ↓
    通知用户(站内信 / App Push)→ 用户点击链接直接从 OSS 下载

Worker 里也要用流式写入,只是目标从 response.getOutputStream() 换成了 OSS 的 multipart upload 接口。


OSS 直链 vs 打包

如果文件本来就在 OSS 上,有时候不需要打包,直接给用户多个 OSS 直链更省事:

  • 200 个文件 → 生成 200 个带签名的临时 URL(有效期 1 小时)
  • 前端用 <a download> 或 JS 批量触发下载

浏览器对同时下载的文件数量有限制(通常 6 个),但逐个排队下载对用户透明,用户体验比等待 ZIP 生成快很多。

打包下载更适合:文件数量极多(几百上千)、用户需要在本地保持目录结构、文件需要二次处理不适合直链暴露。


大 ZIP 的清理

异步生成的 ZIP 文件放在 OSS 上,要设置过期时间(生命周期规则),比如 7 天自动删除,避免存储费用无限累积。下载链接的有效期和 ZIP 的存储期要对齐,否则文件删了链接还有效,用户点了报 404。

评论0

登录后参与评论。

还没有评论,来抢沙发吧。

回到顶部