前端大文件下载(带进度条)
- 1.需求背景
- 2.需求分析:
- 3.实现方式
- 4.实现方式解析
- 1.递归调用接口获取数据流(base64/blob),我这里后端接口返回的是base64;
- 2.后端对文件进行切片处理,接口返回流数据和总切片数以及当前的切片索引,前端进行进度计算,和流数据整合;
- 3.注意,不能对接收的base64数据流直接字符串拼接,否则会报错(Invalid string length),字符串长度超过了 JS 引擎的限制,那么这个长度限制到底是多少呢?根据搜索到的结果,V8 引擎支持的字符串长度为 2 的 29 次方,换算成内存量约为 512 MB;
- 4.解决办法,以数组的方式,进行保存
- 5.循环数组,把每一个切片base64数据转成Blob格式
- 6.调用file-saver的saveAs的api实现下载
- 5.可优化点
- 增加暂停/继续、取消按钮对进度条进行控制,实现断点续传;
1.需求背景
前端页面实现大文件(G级别以上)下载,压缩文件格式(tar包), 显示下载进度
2.需求分析:
如果页面上实现一个点击下载的功能,传统做法:
1.是使用一个 a 标签,然后将该标签的 href 属性地址指向下载文件在服务端的地址,增加download属性(同域);
2. location.href直接赋值(如果该文件能在浏览器上打开,那么就直接打开)
以上两种方法不做过多介绍,本文章主要是通过对文件进行切片传输,以流的形式返回给前端,前端进行整合实现下载
3.实现方式
import {saveAs} from ‘file-saver’;
点击下载的函数(递归实现)
async downloadLog(val: Row) {
const requestHandle = async () => {
const params = {
date,
number: val.packageNum // 当前的切片索引
};
let {context, flag, total} = await this.fetchLogDownloadAction(params);
val.dataStream.push(`${context}`); // 文件内容(base64)
val.progress = ((++val.packageNum / total) * 100).toFixed(0); // 进度的百分比展示
if (flag === 1) {
// 最后一包
val.packageNum = 0;
this.saveFileDownload(val);
} else if (flag === 0) {
this.$nextTick(() => {
requestHandle();
});
}
};
requestHandle();
}
流文件保存本地并下载
async saveFileDownload(val: Row) {
let arrBlob: any[] = [];
val.dataStream.forEach((ele: string) => {
var bstr = atob(ele),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
arrBlob.push(u8arr);
});
let saveFile = new Blob(arrBlob, {type: 'application/x-tar'});
saveAs(saveFile, val.date);
val.dataStream = [];
}
4.实现方式解析
1.递归调用接口获取数据流(base64/blob),我这里后端接口返回的是base64;
2.后端对文件进行切片处理,接口返回流数据和总切片数以及当前的切片索引,前端进行进度计算,和流数据整合;
3.注意,不能对接收的base64数据流直接字符串拼接,否则会报错(Invalid string length),字符串长度超过了 JS 引擎的限制,那么这个长度限制到底是多少呢?根据搜索到的结果,V8 引擎支持的字符串长度为 2 的 29 次方,换算成内存量约为 512 MB;
4.解决办法,以数组的方式,进行保存
5.循环数组,把每一个切片base64数据转成Blob格式
6.调用file-saver的saveAs的api实现下载
5.可优化点
增加暂停/继续、取消按钮对进度条进行控制,实现断点续传;