如何实现大文件的分片上传,断点续传,还有通常说的秒传

分片上传

为什么需要分片上传

如果文件体积比较大,或者网络条件不好时,上传的时间会比较长(要传输更多的报文,丢包重传的概率也更大),用户不能刷新页面,只能耐心等待请求完成。

分片上传的核心思想

  • 利用H5提供的原生File对象,由于File对象是特殊类型的Blob, File接口也继承了Blob接口的属性,分片上传的核心思想就是利用File继承Blob接口的Blob.slice方法
  • Blob.slice方法可以将我们的文件切分为多个单个的切片,分片上传的思想就是利用slice APi将文件分割成多个切片,然后利用浏览器多进程的特性进行并发上传

具体实现

  1. 利用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
}
  1. 上传
chunks.forEach(chunk=>{
  let fd = new FormData();
  fd.append("file", chunk);
  post(‘/bigFile‘, fd)
})
  1. 为了后端能正确的拼接文件,我们需要为每一个文件提供一个唯一的标识符,以及标记每一个切片的顺序

标识符一般通过以下两种方式获取

  • 根据文件名、文件长度等基本信息进行拼接,为了避免多个用户上传相同的文件,可以再额外拼接用户信息如uid等保证唯一性
  • 根据文件的二进制内容计算文件的hash,这样只要文件内容不一样,则标识也会不一样,缺点在于计算量比较大.

断点续传

即使将大文件拆分成切片上传,我们仍需等待所有切片上传完毕,在等待过程中,可能发生一系列导致部分切片上传失败的情形,如网络故障、页面关闭等。由于切片未全部上传,因此无法通知服务端合成文件。这种情况下可以通过断点续传来进行处理。

主要思路

  • 在切片上传成功后,保存已上传的切片信息
  • 当下次传输相同文件时,遍历切片列表,只选择未上传的切片进行上传
  • 所有文件分片上传完毕,调用接口通知服务端进行合并

对于切片信息的保存一般采用以下两种方式

  1. 可以通过locaStorage等方式保存在前端浏览器中,这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失
  2. 服务端本身知道哪些切片已经上传,因此可以由服务端额外提供一个根据文件context查询已上传切片的接口,在上传文件前调用该文件的历史上传记录

秒传

在上传切片前将文件的基本信息发送至服务端进行验证,判断该文件是否需要重新上传,如果已经上传就返回上传结果,实现秒传

验证方法

  1. 文件名
  2. 文件最后修改的时间(File文件对象的一个属性 lastModified)
  3. 文件hash值

如何实现大文件的分片上传,断点续传,还有通常说的秒传

上一篇:【SpringBoot DB系列】Jooq批量写入采坑记录


下一篇:《MySQL慢查询优化》之数据库表结构优化