日子一天天的过去,帮她带早餐成为了一天中最快乐的事情了,我和她关系也近了许多,交谈起来也不会再面红耳赤了。一天中午她突然发消息说为了感谢我每天帮她带早餐要请我吃午饭,当时心情只能说开心到炸裂……她带我来到了食堂的二楼吃麻辣香锅,和她走在一起,路人会时不时的瞥向我们,我不知道是因为她的漂亮,还是好奇她居然和我这么一个人走在一起,我很不喜欢这种目光。
那是我第一次吃麻辣香锅,也是第一次和她在食堂吃饭,感觉麻辣香锅应该是大学最好吃的东西了,就是价格贵了点,我只吃了一点点,好的东西都要留给她,回到寝室后,室友纷纷调侃了我,我也天真的以为我爱情正在路上了,于是我又准备在她生日的表白了,这次我不会怂了……
终于等到了她生日的那天,我早早的写好了对她的祝福,十二点一过就发给她了,可等了好久都没等到她的回复,我想她应该是睡着了…..本来计划下午把蛋糕送给她,然后约她一起吃个晚饭,联系她的时候,她早已和朋友出去庆生了,独留我对着蛋糕发呆,这种等待是非常煎熬的事情,只能打打英雄联盟来让时间过得快点,5杀我也提不起半点开心….
她说她快到了,我赶紧放下手上的游戏去她楼下等她,等了好一会才看到她,我把蛋糕小心翼翼的递给她,说了一堆祝福的话,在她快上楼的时候,我终于鼓足了勇气说了,我喜欢你,你能做我女朋友吗?
大文件上传前言
为了方便大家阅读和理解,我将以单个大文件上传为例,先简单描述下思路。
antd的上传组件有一个上传前的钩子,里面是可以拿到file信息,上传前将file切片,然后包装成一个一个的请求,放到一个数组,上传的的时候将数组的请求执行就可以了,执行完后发送一个合并请求,我没有用Promise.all去执行,而是2个2个的递归执行。
对大文件先通过slice进行切片
核心是利用 Blob.prototype.slice
方法
createFileChunk
接收两个参数
dataSource:所上传的File大文件,size:每个分片大小
//切片
createFileChunk = (dataSource, size = 5 * 1024 * 1024) => {
const fileChunkList = [];//因为只有一个文件,数组只有1项
let cur = 0;
let index = 0;//每个分片给一个索引,最后后端合并按序合并分片
let obj: IFileChunksList = {
name:dataSource.name,
progressArr: [], //记录每一个分片的上传进度
errChunkFile: [],//上传失败的文件
keys: [],//将每个分片包装成一个http请求
};
let arr = [];
while (cur < dataSource.size) {
arr.push(this.createHttp({ hash:dataSource.name+'_'+index, file: dataSource.slice(cur, cur + size)}));
index += 1;
cur += size;
}
obj.keys = arr;
fileChunkList.push(obj);
this.setState({fileChunkList})
};
复制代码
hash
由文件名和序号组成,后端合并的时候需要按顺序合并。
this.createHttp
方法分析
简单的做了参数处理,this.request里面才是真是ajax请求
onProgress:监听ajax进度并实时记录下来
createHttp = (data) => {
const { hash, file } = data;
const formData = new FormData();
formData.append('chunk', file);
formData.append('hash', hash);
return () =>
this.request({
url: this.props.action,
data: formData,
onProgress: this.createProgressHandler(data, hash),
});
};
复制代码
为每个分片创建一个http请求
this.request
方法通过promise和ajax包装
url:分片上传接口。data:分片参数。onProgress:监听此分片上传进度。
requestList:所有正在上传的分片请求集合。(断点续传用的)
也可以在此方法里面设置token认证
//创建ajax
request = ({
url,
method = 'post',
data,
onProgress = (e: any) => e,
requestList = this.state.requestList,
}: IAjax) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = onProgress;
xhr.open(method, url, true);
xhr.setRequestHeader('Authorization', this.props.token);
xhr.send(data);
xhr.onload = (e: any) => {
// 将请求成功的 xhr 从列表中删除
const { requestList } = this.state;
if (requestList) {
const xhrIndex = requestList.findIndex((item) => item.xhr === xhr);
requestList.splice(xhrIndex, 1);
this.setState({ requestList });
}
resolve({
data: e.target.response,
});
};
xhr.onerror = (e) => {
reject(e);
// throw new Error('fail');
};
requestList.push({ xhr, hash:data.get('hash')});
this.setState({
requestList,
});
});
};
复制代码
记录分片进度方法
this.createProgressHandler(data, hash)
在上面createHttp方法调用
createProgressHandler = (item1, hash) => {
return (e: any) => {
let { fileChunkList } = this.state;
let index=hash.split("_")[1]
fileChunkList[0].progressArr[index]=e.load
this.setState({
fileChunkList,
});
};
};
复制代码
调用开始上传的方法
this.upFile(this,state.fileChunkList[0])(true)
参数true是保证2个请求一发
// 开始上传
upFile = ( item) => {
let fileArr=item.keys
let init = 0;
let loopFun = (initValue) => {
fileArr[initValue]()
.then((res) => {
if (JSON.parse(res.data).statusCode === 200) {
init++;
if (init < fileArr.length) {
//继续传下一个分片
loop();
} else if (init === fileArr.length && !item.errChunk.length && fileArr.length !== 1) {
//分片传完,合并分片
this.mergeChunk(item);
}
}
})
.catch((err) => {
//捕获上传失败的分片存起来
let arrChunk = item.errChunkFile.concat(fileArr[initValue]);
init++;
item.errChunkFile = arrChunk;
this.setState({
fileChunkList: [...item],
});
if (init < fileArr.length) {
loop();
}
});
};
let loop = (initFlag) => {
loopFun(init);
if (initFlag) {
loopFun(++init);
}
};
return loop;
};
复制代码
合并分片的方法我就不写了,就调用一个接口即可。
假如存在上传失败的分片,会被记录在fileChunkList[0].errChunkFile.对这个失败的数组做一个上传就可以了。
断点续传 暂停
this.state.requestList是当前正在请求的分片集合。暂停就是把请求abort,
upFileCancel = (itemCurrent: IFileChunksList) => {
this.state.requestList.forEach((item) => {
item.xhr.abort();
});
};
复制代码
续传,可以获取已经上传成功的,然后把未上传的重新上传即可。
总结
我只写了前端的大致实现思想,后端只需提供单个分片上传的接口,合并分片的接口。我的hash用文件名+索引,用spark-md5
对文件内容生成一个hash才是最合适的。
单个大文件上传感觉其实并不复杂,知道它的大致思想再去扩展多文件排队上传,断点续传,记录每个文件的进度条、总进度条甚至每个分片的进度条,还要考虑暂停的时候,由于onProgress是实时监听进度条的,当分片上传了百分之80,取消后变为0,进度条回退的情况….
大家对大文件有疑问的,可以评论,最后麻烦点个赞。搬砖不易
看故事学习es6原理的:juejin.cn/post/702429…
作者:舔狗的泪
链接:https://juejin.cn/post/7030987126724362276
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。