最近有个需求,需要实现多个文件批量压缩后批量下载。
我第一反应就是,在服务端进行压缩生成文件,生成新的URL返回前端,打开新窗口下载。
这样的坏处就是当文件过多时:
- 后端实现复杂。
- 服务器资源占用太高,功能模块访问过多或者文件太多导致服务器资源吃紧。
- 前端需要轮训等待后台打包完成,逻辑实现复杂。
- 客户直接关闭浏览器,服务端还在打包,造成浪费资源 ...
于是思考,是否可以在浏览器端进行ZIP压缩?
搜索 浏览器压缩 关键字 出来了一个看名称就知道是啥的插件
「JSZip」官方文档
思路逐渐清晰,开始...
使用Fetch api下载文件
/**
* 使用fetch 下载单个文件
* 返回 对象 包含 文件名称和二进制文件Blob对象
*
*/
const download = (url) => fetch(url).then((res) => ({
name: url.substring(url.lastIndexOf("/") + 1, url.length),
blob: res.blob(),
}));
使用浏览器 Fetch 下载文件,返回「blob对象」以及处理好文件名称。
使用 Promise.all 实现多个文件下载
const downLoadGroup = urls => Promise.all(urls.map(download))
当直接使用 promise.all 时,将会同时进行多个get请求,恰巧使用批量下载的人比较多,那将给服务器带来不小的压力。
把众多请求进行 分组,5个或10个下载分成一组,再进行Promise.all 请求。一组完成之后再进行另外组,将会极大的减少服务器压力。
使用 「bluebirdjs」 扩展了Promise 原型方法,就不用自己手动去做这些操作,这里是使用Promise.map 这个方法进行分组的。
/**
* 使用bluebird 扩展方法 扩展Promise
* 优化GET请求
*/
const downloadGroup = (urls, group_size) => {
return Promise.map(urls, async (url) => await download(url), {
concurrency: group_size,
});
};
使用 JSZip进行文件压缩
/**
* 使用JSZip压缩 文件
* 使用blob模式
* 使用 FileSaver 下载
*/
const generateZIP = async (files) => {
const zip = new JSZip();
files.forEach((item) => {
zip.file(item.name, item.blob, { binary: true });
});
const content = await zip.generateAsync({ type: "blob" });
const currentDate = new Date().getTime();
const fileName = `zipped-${currentDate}.zip`;
return saveAs(content, fileName);
};