最近在做大文件上传的需求,所以就有了这一系列前端文件上传功能的记录。
首先要实现的需求是文件上传切片的功能。
具体功能点
- 计算文件MD5值,用于后端检验文件和秒传功能的实现
- 对文件进行切片,分块上传
实现
计算文件MD5值
先使用FileReader
进行文件读取。
FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File
或 Blob
对象指定要读取的文件或数据。
代码
const handleMD5 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsBinaryString(file);
fileReader.onload = (event) => {
...
};
});
};
使用 spark-md5
库来根据文件内容计算出文件的 MD5 值,MD5值是用来传给后端用于秒传功能和分块上传的标识。
安装
npm install --save spark-md5
代码
const handleMD5 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsBinaryString(file);
fileReader.onload = (event) => {
if (event.target) {
const md5 = SparkMD5.hashBinary(event.target.result);
return resolve(md5);
}
return reject();
};
});
};
这里有个注意的点,用的是SparkMD5.hashBinary
这个方法,不是官网最开始 SparkMD5.hash
的方法,SparkMD5.hashBinary
是直接对二进制计算MD5值,SparkMD5.hash
是对字符串计算MD5值。
文件秒传
文件秒传,就是后端已经存在了上传的文件,所以当再次上传时会直接提示上传成功,其实就是伪上传啦~
实现
在文件上传前,先计算出文件的 hash,先把 hash 发送给后端进行验证,如果后端找到 hash 相同的文件,则直接返回上传成功的信息即可。
代码
// 预上传
const getPreRequest = async (md5: string, file: any, uploadId?: string) => {
const res = await bucketAPI.postNameFsUpload(detailId, `${pagePros.path}${file.name}`, {
md5,
size: file.size,
uploadId,
});
if (res.code === 200) {
return res.data;
}
return undefined;
};
文件切片
设置一个切片的文件大小值size
,这里使用兆作为单位。然后进行切片。
代码
const createFileChunk = (file: FileList, uploadId, size?: number) => {
const SIZE = (size || configSize) * 1024 * 1024;
const fileChunkList = [] as any[];
for (let cur = 0, index = 0; cur < file.size; cur += SIZE) {
fileChunkList.push({ file: file.slice(cur, cur + SIZE), position: cur, index });
index += 1;
}
return fileChunkList;
};
切片文件上传
这里用一组数组来储存整一个完整需要上传的文件请求。
构建发送请求列表,代码
// 构建发送请求列表
const createRequestList = (fileList: any[], name: string, uid: string, uploadId: string) => {
// 构建发送请求列表
const requestList = fileList.map((item, index) => {
return {
request: bucketAPI.putNameFsUpload,
props: [
detailId,
`${pagePros.path}${name}`,
formatFormData({
blockSeq: item.index,
file: item.file,
position: item.position,
uploadId,
}),
// 其他设置参数
{
// 取消请求设置
...
// 计算上传进度
...
},
],
};
});
return requestList;
};
然后发送请求就好了。可以是并发也可以是一个一个发送。因为后续需求还有一个文件同时上传个数的限制和断点续传的功能,为了方便控制,用的是一个一个发送,所以写了一个迭代函数来执行。并发的话可以直接用promise.all
代码
const loopArray = (index: number, list: any[], uid: string) => {
// 发送请求
bucketAPI
.putNameFsUpload(...list[index].props)
.then(async (res) => {
if (res.code === 200 && res.data.done) {
if (index < list.length - 1) {
// 继续发送请求
loopArray(index + 1, list, uid);
}
if (index === list.length - 1) {
// 整一个文件分块传送完毕
...
}
} else {
// 文件某一块上传失败
...
}
})
.catch((err) => {
// 文件上传失败
...
});
};