本文详细解析JavaScript中大文件分片上传的完整实现流程。通过AbortController接口实现请求中断,利用File.slice进行分片切割,并结合FileReader异步读取与spark-md5生成文件签名。文章涵盖客户端分片逻辑、重试机制及服务端合并策略,提供从配置到执行的代码示例,帮助开发者掌握高可靠性的大文件上传方案。
掌握AbortController
AbortController接口代表一个控制对象,允许在需要时中止一个或多个Web请求。
利用该接口可实现取消或中断请求的功能。
axios.abort()的底层实现即为此机制。
了解
fse = require('fs-extra')
概述
文件分片上传的原理是将文件转换为ArrayBlob形式,利用file(blob)的slice方法将文件拆分为多个部分,按顺序传输至服务端,传输完成后调用服务端的merge合并接口,由服务端完成文件合并。
服务端实现原理为:上传前创建文件夹(命名可参考提供的代码),随后通过fse的写入方法将获取的文件写入创建的文件夹中,并保持相应排序。最后通过合并方法将文件合并为一个完整文件。
深入学习
客户端
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
npmjs中的spark-md5
github
这是一个处理分片的库。提供了方法:
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大文件分片上传的资料请关注本站其它相关文章!