2019-08-15
5 min read

大文件上传

前端设计思路

前端大文件上传网上的大部分文章已经给出了解决方案,核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,文件的 slice 方法可以返回原文件的某个切片

预先定义好单个切片大小,将文件切分为一个个切片,然后借助 http 的可并发性,同时上传多个切片。这样从原本传一个大文件,变成了并发传多个小的文件切片,可以大大减少上传时间

另外由于是并发,传输到服务端的顺序可能会发生变化,因此我们还需要给每个切片记录顺序

服务端设计思路

服务端负责接受前端传输的切片,并在接收到所有切片后合并所有切片

这里又引伸出两个问题

  • 何时合并切片,即切片什么时候传输完成
  • 如何合并切片

第一个问题需要前端配合,前端在每个切片中都携带切片最大数量的信息,当服务端接受到这个数量的切片时自动合并。或者也可以额外发一个请求,主动通知服务端进行切片的合并

第二个问题,具体如何合并切片呢?这里可以使用 Nodejs 的 读写流(readStream/writeStream),将所有切片的流传输到最终文件的流里

实现

前端实现

<template></template>

服务端实现

断点续传

断点续传的原理在于前端/服务端需要记住已上传的切片,这样下次上传就可以跳过之前已上传的部分,有两种方案实现记忆的功能

  • 前端使用 localStorage 存储切片信息 hash
  • 服务端保存已上传的切片 hash,前端每次上传前向服务端获取已上传的切片

第一种是前端的解决方案,第二种是服务端,而前端方案有一个缺陷,如果换了个浏览器就失去了记忆的效果,所以这里选后者

生成 hash

利用一个第三方库 spark-md5, 它可以根据文件内容计算出文件的hash值;

考虑到如果上传一个超大文件,读取文件计算hash是非常耗时的,会引起页面UI阻塞,导致页面假死,所以利用web-worker在worker线程计算hash,这样用户可以继续在主界面进行交互

总结

大文件上传

  • 前端上传文件时使用Blob.prototype.slice方法,将文件切片,最后发送一个合并请求
  • 服务端接收切片并存储,收到合并请求后使用流将切片合并到最终文件
  • 利用XMLHttpRequest的upload.onprogress对切片上传进度监听
  • 使用vue的计算属性根据每个切片进度算出整个文件的上传进度

断点续传

  • 利用spark-md5根据文件内容算出hash
  • 通过hash可以判断出服务端是否已经上传该文件,从而直接提示用户上传成功
  • 通过 XMLHttpRequest 的 abort 方法暂停切片的上传
  • 上传前服务端返回已经上传的切片名,前端跳过这些切片的上传