Skip to content
索引

大文件上传

需求分析

针对大文件上传,最少做到以下几点

  • 大文件切割,分片上传
  • 如果有部分切片上传失败了,我们希望提醒用户重新上传,并且上传成功不需要上传
  • 最好能有上传的进度提示

什么是 web worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程。

js
var worker = new Worker('work.js');
var worker = new Worker('work.js');

Worker 线程有一些自己的全局属性和方法。

self.name: Worker 的名字。该属性只读,由构造函数指定。 self.onmessage:指定message事件的监听函数。 self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。 self.close():关闭 Worker 线程。 self.postMessage():向产生这个 Worker 线程发送消息。 self.importScripts():加载 JS 脚本。

Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()。 该方法可以同时加载多个脚本

js
importScripts('script1.js');
importScripts('script1.js', 'script2.js');
importScripts('script1.js');
importScripts('script1.js', 'script2.js');

进度条功能

上传进度分两种,一个是每个切片的上传进度,另一个是整个文件的上传进度,而整个文件的上传进度是基于每个切片上传进度计算而来,所以我们先实现切片的上传进度

切片进度条

XMLHttpRequest 原生支持上传进度的监听,只需要监听 upload.onprogress 即可,我们在原来的 request 基础上传入 onProgress 参数,给 XMLHttpRequest 注册监听事件

每一个切片都需要对应一个上传的进度,这个时候应该写一个方法针对切片对象进行进度条的除了,

js
// item是我们的chunk对象
createProgressHandler(item) {
  return (e) => {
    item.percentage = parseInt(String((e.loaded / e.total) * 100));
  };
},
// item是我们的chunk对象
createProgressHandler(item) {
  return (e) => {
    item.percentage = parseInt(String((e.loaded / e.total) * 100));
  };
},

文件的进度条

将每个切片已上传的部分累加,除以整个文件的大小,就能得出当前文件的上传进度,所以这里使用 Vue 计算属性

js
computed: {
  // 针对每一个 chunk的进度 计算出总的上传进度
  uploadPercentage() {
    if (!this.file || !this.chunkList.length) return 0;
    const loaded = this.chunkList.map((item) => item.size * item.percentage).reduce((acc, cur) => acc + cur);
    return parseInt((loaded / this.file.size).toFixed(2));
  },
},
复制代码
computed: {
  // 针对每一个 chunk的进度 计算出总的上传进度
  uploadPercentage() {
    if (!this.file || !this.chunkList.length) return 0;
    const loaded = this.chunkList.map((item) => item.size * item.percentage).reduce((acc, cur) => acc + cur);
    return parseInt((loaded / this.file.size).toFixed(2));
  },
},
复制代码

大文件上传的基本功能就差不多完成了。

文件秒传

这个功能的意思就是说,我们在文件上传之前,去问一下服务器,你有没有这个文件呀,你没有的话我就开始上传,你要是有的话我就偷个懒,用你有的我就不上传了

所以需要实现一个检测接口(verify),去询问服务器有没有这个文件,因为我们之前是计算过文件的 hash的,能保证文件的唯一性。就用这个hash就能唯一的判断这个文件。所以这个接口的思路也很简单,就是判断我们的 target目录下是否存在这个文件

断点续传

断点续传的意思就是我们上传的时候如果文件上传失败了,我们之后在上传一次的时候,只上传我们之前失败的文件,成功的文件我们就跳过

文件切片

选择文件是使用的input输入框,获取到选择的值也很简单。文件切片的核心就是文件对象的slice 方法,类似数组,我们可以调用这个方法获取到文件的某一段

文件唯一值

如何告诉后端,上传的两个文件是不是同一个文件,显然,如果使用文件名作为唯一标识肯定不太好。这个时候我们想到可以使用md5对文件加密获取唯一的hash

生成hash值的方法我们是调用 spark-md5 这个库,在计算hash的时候是非常消耗计算机的CPU的会造成浏览器的卡顿,为了优化体验我们使用 web-workerworker 线程计算 hash

worker 线程中,接受文件切片 fileChunkList,利用 FileReader 读取每个切片的 ArrayBuffer 并不断传入 spark-md5 中,每计算完一个切片通过 postMessage 向主线程发送一个进度事件,全部完成后将最终的 hash 发送给主线程

暂停上传

我们先自己手动实现一个按钮,点击之后就停止当前的上传情况。模拟了上传失败

这个思路肯定就是要改装我们的 request方法,在改装之前我们需要知道 XMLHttpRequest对象是可以自己主动停止当前的网络连接的,不知道的同学补补课

这样我们只需要使用一个公共的数组,每一次发请求的时候都保存我们当前的这个XMLHttpRequest对象,当请求成功之后,我们就移除这个对象,当点击暂停按钮的时候我们就遍历这个数组调用每一个XMLHttpRequestabrot方法就可以取消上传了。

Released under the MIT License.