一文带你彻底搞懂JS大文件分片上传的实现
作者:落叶飘
学习AbortController
AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。
可以通过这个来实现取消或者中断请求的功能。
axios.abort();底层实现就是这个。
认识了
fse = require('fs-extra')
概述
实现文件分片上传的原理就是通过将文件的ArrayBlob形式,通过file(blob)的方法slice,来实现将文件拆成几个部分,然后排上顺序传到服务端,最后传完了调用服务端的merge合并接口,服务端将文件合并。
服务端实现原理,上传前创建文件夹,命名注意了,可以参考给到的代码。然后将获得的文件通过fse的写入方法,写入到我们创建的文件夹中,并且有相应的排序。最后我们通过合并方法将文件合并到一个文件。
详细学习
Client端
getChunkListAndFileMd5函数
这个函数用来创建分片数组,以及生成Hash签名。
创建分片列表数组,我们使用的方法是file.prototype.slice当然这里我们为了解决兼容性问题,我们使用的代码是:
export function getBlobSlice() { return (File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice); }
创建分片:getBlobSlice.call(file,start,end)。这里start、end分别是起始位置和终点位置,我们确认好分片大小就可以计算start和end。
好,那么我们开始了,我们定义size是 5M 代码如下
const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
我们定义一个对象保存默认配置:
const DEFAULT_OPTIONS = { chunkSize: DEFAULT_CHUNK_SIZE, };
我们来定义分片的编号,初始值肯定是 0。我们定义chunkSize来保存我们上边定义的常量分片大小。
let currentChunk = 0; const chunkSize = this.fileUploaderClientOptions.chunkSize;
接下来我们需要计算需要多少个分片,很简单:文件总的大小/每个分片的大小,最终结果可能是个小数,但是为了保证分片的完整肯定是向上取整。
const chunks = Math.ceil(file.size / chunkSize);
定义一个函数来加载分片,代码如下:
function loadNextChunk() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; const chunk = blobSlice.call(file, start, end); chunkList.push(chunk); fileReader.readAsArrayBuffer(chunk); }
拆解这个函数:
1.计算start位置,这个不难理解。
2.计算end位置,这里需要判断一下,主要是针对两种情况:
文件尺寸很小,小于分片大小,直接取文件的尺寸。
最后一个切片,大小可能没有切片尺寸大,我们直接取文件大小的位置。
3.调用我们上边说的函数来实现分片,并且获取当前的分片。
4.将当前的分片push到我们的数组中。
5.调用fileReader.readAsArrayBuffer方法来读取分片。(其实当前的分片是一个blob实例,我们通过这个方法可以读取到里边的内容)。
接下来我们来看看fileReader是怎么回事。
认识FileReader
不懂fileReader的可以先看看文档,了解下里边的方法。
FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
其中 File 对象可以是
- 来自用户在一个 元素上选择文件后返回的FileList对象
- 也可以来自拖放操作生成的 DataTransfer对象,
- 还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。
FileReader可以在Web Worker中使用。 (重要,可以很大成都解决性能问题)
这里因为用到了FileReader的方法,所以重点讲讲这个对象的方法。
- FileReader.abort(); 中止读取操作。
- FileReader.readAsArrayBuffer(); 开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer数据对象。
- FileReader.readAsDataURL(); 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL 格式的 Base64 字符串以表示所读取文件的内容。
- FileReader.readAsText();开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。
我们目前需要读取分片的内容,并且需要ArrayBuffer的格式,所以我们创建一个FileReader对象实例,并且使用readAsArrayBuffer方法来读取文件,通过onload事件来监听,获取最终结果。代码如下:
const fileReader = new FileReader(); fileReader.onload = function (e) { // 我们读取到的结果是 e.target.result } fileReader.onerror = function (e) { // 这里表示读取失败 }
上边loadNextChunk函数中调用了这个方法。
fileReader.readAsArrayBuffer(chunk);
使用md5来实现签名
用到了 spark-md5 这个三方库。主要使用的方法是生成md5的编码。
npm install --save spark-md5
这个就是一个处理分片的一个库。给出了方法:
const spark = new SparkMD5.ArrayBuffer();
将分片 v 添加到对象中
spark.append(v)
最终结果返回
const result = spark.end()
uploadFile函数
这个函数用来上传文件的函数。
首先我们要获取上边我们函数得到的值 md5、chunkList。
const { md5, chunkList } = yield this.getChunkListAndFileMd5(file);
上传文件前,需要调用接口来初始化文件分片上传,这里我们其实就是调用初始化文件分片上传的接口requestOptions.initFilePartUploadFunc。
yield requestOptions.initFilePartUploadFunc();
这个接口在后端的实现其实就是创建好一个文件夹用来保存分片,这里就不多说了,之后在学习Server代码的时候我们细讲。
接下来就是开始上传我们的分片了,上传方法requestOptions.uploadPartFileFunc:
for (let index = 0; index < chunkList.length; index++) { try { yield requestOptions.uploadPartFileFunc(chunkList[index], index); } catch (e) { console.warn(`${index} part upload failed`); retryList.push(index); } }
注意了:我们不能保证全部顺利上传,如果中间出现问题中断了上传等问题,我们如何处理?
这里我们使用retryTimes来获取需要重新上传的列表。
for (let retry = 0; retry < requestOptions.retryTimes; retry++) { if (retryList.length > 0) { console.log(`retry start, times: ${retry}`); for (let a = 0; a < retryList.length; a++) { const blobIndex = retryList[a]; try { yield requestOptions.uploadPartFileFunc(chunkList[blobIndex], blobIndex); retryList.splice(a, 1); } catch (e) { console.warn(`${blobIndex} part retry upload failed, times: ${retry}`); } } } }
最后我们调用上传结束的接口requestOptions.finishFilePartUploadFunc(md5),其实这个接口主要是通知服务端,分片都上传完了,服务端可以进行文件合并了,最终将分片合并成一个文件。
if (retryList.length === 0) { return yield requestOptions.finishFilePartUploadFunc(md5); } else { throw Error(`upload failed, some chunks upload failed: ${JSON.stringify(retryList)}`); }
至此,客户端的操作完毕!
以上就是一文带你彻底搞懂JS大文件分片上传的实现的详细内容,更多关于JS大文件分片上传的资料请关注脚本之家其它相关文章!