分片上传
为什么需要分片上传
如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。
分片上传的核心思想
- 利用H5提供的原生
File
对象,由于File
对象是特殊类型的Blob
,File
接口也继承了Blob
接口的属性,分片上传的核心思想就是利用File
继承Blob
接口的Blob.slice
方法 -
Blob.slice
方法可以将我们的文件切分为多个单个的切片,分片上传的思想就是利用slice APi
将文件分割成多个切片,然后利用浏览器多进程的特性进行并发上传
具体实现
- 利用file继承自Blob的slice方法切割文件
function slice(file, piece = 1024 * 1024 * 5) {
let totalSize = file.size; // 文件总大小
let start = 0; // 每次上传的开始字节
let end = start + piece; // 每次上传的结尾字节
let chunks = []
while (start < totalSize) {
// 根据长度截取每次需要上传的数据
// File对象继承自Blob对象,因此包含slice方法
let blob = file.slice(start, end);
chunks.push(blob)
start = end;
end = start + piece;
}
return chunks
}
- 上传
chunks.forEach(chunk=>{
let fd = new FormData();
fd.append("file", chunk);
post(‘/bigFile‘, fd)
})
- 为了后端能正确的拼接文件,我们需要为每一个文件提供一个唯一的标识符,以及标记每一个切片的顺序
标识符一般通过以下两种方式获取
- 根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如uid等保证唯一性
- 根据文件的二进制内容计算文件的hash,这样只要文件内容不一样,则标识也会不一样,缺点在于计算量比较大.
断点续传
即使将大文件拆分成切片上传,我们仍需等待所有切片上传完毕,在等待过程中,可能发生一系列导致部分切片上传失败的情形,如网络故障、页面关闭等。由于切片未全部上传,因此无法通知服务端合成文件。这种情况下可以通过断点续传来进行处理。
主要思路
- 在切片上传成功后,保存已上传的切片信息
- 当下次传输相同文件时,遍历切片列表,只选择未上传的切片进行上传
- 所有文件分片上传完毕,调用接口通知服务端进行合并
对于切片信息的保存一般采用以下两种方式
- 可以通过locaStorage等方式保存在前端浏览器中,这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失
- 服务端本身知道哪些切片已经上传,因此可以由服务端额外提供一个根据文件context查询已上传切片的接口,在上传文件前调用该文件的历史上传记录
秒传
在上传切片前将文件的基本信息发送至服务端进行验证,判断该文件是否需要重新上传,如果已经上传就返回上传结果,实现秒传
验证方法
- 文件名
- 文件最后修改的时间(File文件对象的一个属性 lastModified)
- 文件hash值