Angular 12 多超大文件打包ZIP下载及进度展示正确实现问询
原有方案问题根因
- 内存占用过高:所有文件二进制内容全部暂存在浏览器内存中,11GB级别的数据远超普通浏览器内存分配上限,直接导致页面卡顿甚至崩溃
- 进度统计逻辑缺陷:只有所有文件全部拉取完成后才会触发JSZip的打包进度回调,拉取文件的数分钟到数十分钟阶段完全没有进度反馈,用户看到的进度一直为0%
- 打包逻辑不合理:JSZip本身设计不支持超大文件流式处理,必须全量加载所有文件后才开始打包,天生不适用于GB级别的文件打包场景
方案1:服务端生成ZIP(优先推荐,生产环境唯一可用方案)
该方案完全规避前端内存占用问题,进度展示准确,稳定性最高:
- 前端将需要打包的文件ID/URL列表提交给后端接口
- 后端直接拉取所有资源,边拉取边写入ZIP输出流,不需要在服务端暂存全量ZIP文件
- 前端直接接收ZIP流走浏览器原生下载逻辑,自动展示下载进度,全程不占用前端内存
- 如果你的资源存储在阿里云OSS、腾讯云COS等云存储服务中,可以直接调用云厂商提供的批量打包ZIP接口,无需自己实现打包逻辑,性能更高、成本更低
方案2:前端流式打包下载(仅适用于服务端无法支持的小体量场景)
如果确实没有服务端支持的条件,使用流式打包方案替代JSZip,边拉取边打包边下载,无需全量加载文件到内存,同时可统计全链路进度:
依赖安装
选择对大文件支持更好的@zip.js/zip.js替代JSZip,配合流式下载工具:
npm install @zip.js/zip.js streamsaver
核心代码改造
DownloadService实现
import { ZipWriter, BlobReader, configure } from '@zip.js/zip.js'; import streamSaver from 'streamsaver'; // 配置zip.js使用Web Worker执行打包逻辑,避免阻塞主线程 configure({ useWebWorkers: true, // 注意:将node_modules/@zip.js/zip.js/dist/z-worker.js放到你的Angular项目assets目录下,配置对应路径 workerScripts: { deflate: ['/assets/z-worker.js'] } }); @Injectable({ providedIn: 'root' }) export class DownloadService { async downloadAll( resourceList: {url: string, filename: string, size: number}[], progressCallback?: (percent: number) => void ) { // 计算所有文件总大小,用于进度统计 const totalSize = resourceList.reduce((sum, item) => sum + item.size, 0); let downloadedSize = 0; // 创建下载流,直接向用户输出ZIP文件,无需暂存全量数据 const writeStream = streamSaver.createWriteStream('resources.zip'); const zipWriter = new ZipWriter(writeStream); for (const resource of resourceList) { const response = await fetch(resource.url); if (!response.ok) throw new Error(`文件拉取失败:${resource.url}`); const reader = response.body.getReader(); // 自定义流统计单文件下载进度 const progressStream = new ReadableStream({ async start(controller) { while (true) { const { done, value } = await reader.read(); if (done) break; downloadedSize += value.length; const percent = Math.min(99.9, (downloadedSize / totalSize) * 100); progressCallback?.(percent); controller.enqueue(value); } controller.close(); } }); // 将文件流直接写入ZIP,无需加载全量文件到内存 const fileBlob = await new Response(progressStream).blob(); await zipWriter.add(resource.filename, new BlobReader(fileBlob)); } // 完成ZIP打包,关闭流 await zipWriter.close(); progressCallback?.(100); } }
组件调用改造
downloadAll() { // 注意:需要提前在资源元数据中存储每个文件的size字段,否则无法计算准确进度 const resourceList = this.PROJECT.resources.map(u => ({ url: u.link, filename: u.link.split('/').pop(), size: u.size })); this.downloadService.downloadAll(resourceList, (percent) => { console.log(`下载进度:${percent.toFixed(1)}%`); if (percent >= 100) { console.log('Zip Downloaded'); } }); }
前端方案注意事项
- 仅建议总大小不超过4GB的场景使用,超过4GB依然会有浏览器内存限制问题,必须走服务端方案
- 需要资源存储服务配置CORS规则,且返回
content-length响应头,否则无法正常拉取文件和统计进度 - Web Worker配置必须正确,否则打包逻辑依然会阻塞主线程导致页面卡顿
内容的提问来源于stack exchange,提问作者Sergio Mendez
相关产品推荐
相关产品推荐

