likes
comments
collection
share

有关大文件上传和下载相关知识的整合

作者站长头像
站长
· 阅读数 12

一.前期准备

  做大文件上传和下载,是需要一定的知识储备的。其中综合了不少领域的知识点,很综合的一项技能,可以对技能做一次很好的整合。写一篇这样的文章,也很考验文字水平,希望这一篇文章之后,能够提升自己的理解程度,有一个比较深刻的印象。  说到文件,那肯定少不了前端中的文件(File)、二进制(Blob)、文件读取(FileReader)。大文件上传,一次性上传肯定是不现实的,需要对文件进行分片,然后后端获取后进行整合,那么,Blob.prototype.slice或者File.prototype.slice也是切片时必不可少的。  由于前端会将资源分块,然后单独发送请求,也就是说,原来 1 个文件对应 1 个上传请求,现在可能会变成 1 个文件对应 n 个上传请求(HTTP2的多路复用),所以前端可以基于 Promise.all将这多个接口整合,上传完成在发送一个合并的请求,通知服务端进行合并。合并时可通过 nodejs 中的读写流(readStream/writeStream),将所有切片的流通过管道(pipe)输入最终文件的流中。  而在发送请求资源时,前端会定好每个文件对应的序号(spark-md5),并将当前分块、序号以及文件 hash 等信息一起发送给服务端(由于计算内容的hash需要时间,还需要考虑 WebWorker),服务端在进行合并时,通过序号进行依次合并即可。  而一旦服务端某个上传请求失败,会返回当前分块失败的信息,其中会包含文件名称、文件 hash、分块大小以及分块序号等,前端拿到这些信息后可以进行重传,同时考虑此时是否需要将 Promise.all 替换为 Promise.allSettled 更方便。

二.相关知识点串讲

1.Blob,File以及FileReader

⑴. Blob:  Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。  Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了 blob 的功能并将其扩展以支持用户系统上的文件。①要从其它非 blob 对象和数据构造一个 Blob,需使用 Blob() 构造函数

let blob = new Blob( array, options );

②常用实例属性:

属性/方法名称读写描述
Blob.prototype.size只读对象中所包含数据的大小(字节)
Blob.prototype.type只读对象所包含数据的 MIME 类型
Blob.prototype.arrayBuffer()——返回一个 Promise 对象,包含 blob 中的数据,并在 ArrayBuffer 中以二进制数据的形式呈现,出现于FileReader.readAsArrayBuffer()返回的对象。
Blob.prototype.slice()——用于创建一个包含源 Blob的指定字节范围内的数据的新 Blob 对象,通常用于文件的切片。

⑵. File:  通常情况下, File 对象是来自用户在一个 <input> 元素上选择文件后返回的 FileList 对象,也可以是来自由拖放操作生成的 DataTransfer 对象,或者来自 HTMLCanvasElement 上的 mozGetAsFile() API。  File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中, Blob的方法都能被File使用。①File() 构造器创建新的 File 对象实例。

let myFile = new File(bits, name[, options]);

②常用实例属性:

属性/方法名称读写描述
File.name只读返回当前 File 对象所引用文件的名字。
File.size只读返回文件的大小。
File.type只读返回文件的 多用途互联网邮件扩展类型(MIME Type)
File.slice([start[, end[, contentType]]])——返回一个新的 Blob 对象,它包含有源 Blob 对象中指定范围内的数据。如File.slice(currentIndex, currentIndex + size)

⑶. FileReader:  FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 FileBlob 对象指定要读取的文件或数据。  其中 File 对象可以是来自用户在一个 <input> 元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer 对象,还可以是来自在一个 HTMLCanvasElement 上执行 mozGetAsFile() 方法后返回结果。①使用 FileReader() 构造器去创建一个新的 FileReader。

let reader = new FileReader();

②常用实例属性:

属性/方法名称读写描述
FileReader.readAsArrayBuffer()——开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象,用于大型数据,几百MB甚至几GB。
FileReader.readAsText()——开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容,可用于小型数据,数百KB,因为大文件时会一下子把目标文件加载至内存,导致内存超出上限。
FileReader.abort()——中止读取操作。在返回时,readyState属性为DONE。

⑷.URL:

属性/方法名称读写描述
URL.createObjectURL()——创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。用于大文件下载。
URL.revokeObjectURL()——用来释放一个之前已经存在的、通过调用 URL.createObjectURL() 创建的 URL 对象。

2.HTTP2的多路复用

  在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)流(stream)。帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。HTTP2 采用二进制数据帧传输,取代了 HTTP1.x 的文本格式,二进制格式解析更高效。多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一个 TCP 连接并发完成。同一 Tcp 中可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

三.大文件上传

  实现大文件上传,需要前后端合作,以下主要讲前端方面需要实现的内容,后端简述思路为主,前端使用vue,后端为node。  前端部分理一下大概思路,首先需要解决的一次性上传大文件导致线程挂掉或者太慢的问题,解决方法就是对文件进行分片处理,并发上传。然后再实现断点续传,文件秒传,暂停上传,恢复上传等功能。期间需要考虑对性能的优化。

1.前端部分

先贴上基础的html代码:

<template>
   <div>
    <input type="file" @change="handleFileChange" />
    <el-button @click="handleUpload">upload</el-button>
  </div>
</template>
<script>
export default {
  data: () => ({
    container: {
      file: null
    }
  }),
  methods: {
     handleFileChange(e) {
      const [file] = e.target.files;
      if (!file) return;
      Object.assign(this.$data, this.$options.data());
      this.container.file = file;
    },
    async handleUpload() {}
  }
};
</script>

⑴如何做分片处理再合并发送?

设定好分片大小,有默认值,也可以参数传入。通过input的change事件获取file文件,再通过file.slice切割分片,通过spark-md5每个分片的内容转换为对应hash值,每次发送分片时携带对应hash值+对应分片下标作为唯一标识,使用promise.all发送ajax请求,成功之后再发送合并切片请求给后台,后台则开始处理合并操作。