大文件上传-切片上传和断点续传

2,399 阅读1分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

一、计算文件MD5

在计算文件MD5之前写过的判断文件类型:文件上传判断文件格式,计算文件的MD5就是为了传值给后端比较文件的准确性和完整性,可以使用spark-md5插件,但是我们需要考虑的是大文件上传,需要把文件切片后再进行MD5计算。

安装语法:

npm install --save spark-md5

async calculateHashWorker() {
	return new Promise(resolve => {
		this.worker = new Worker('./hash.js');
		this.worker.postMessage({chunks: this.chunks});//文件通过hash通过标识,用来区分唯一性
		this.worker.onMessage = e => {
			const {progress,hash} = e.data;
			this.hashProgress = Number(progress.toFixed(2));//展示计算hash进度条
			if(hash) {
				resolve(hash);
			}
		}
	})
},
async uploadFile() {
	this.chunks = this.createFileChunk(this.file);//拿到文件切片后的数组
	const hash  = await this.calculateHashWorker();//计算md5
}

二、切片上传

前端把文件切片成很多块,成为文件切片数组,并且可以固定成每一块的大小,限制大小例如1M这样。等文件上传完成后再合并成一个文件,这样就实现了大文件切片上传功能,如果上传过程中出现网络中断或其他问题,下次继续上传时就可以使用下面的断点续传。

//把文件切片,生成文件块
createFileChunk(file) {
	const chunks = [];
	let cur = 0;
	const CHUNK_SIZE = 1 * 1024 * 1024;//把文件分块指定成1M大小
	while(cur < file.size) {
		chunks.push({
			index: cur,
			file: file.slice(cur,cur + CHUNK_SIZE)
		});
		cur += size;
	}
	return chunks;
}

把文件分块上传,如果上传了一半网络断了或者用户进行浏览器刷新,就不会继续上传,这时候只有一半到了服务器,这时候可以进行断点续传,后面有网络了只传分片剩下了的部分,然后后端进行合并。这里需要考虑的是把每个切片保存唯一性标识,根据唯一性标识判断文件上传进度,直到文件全部上传完成为止。

上传到后端后需要进行文件合并,文件合并按什么顺序呢?这里是后端需要在服务器搭建一个单独临时的文件夹保存所有的切片,切片上传的时候给每个切片加一个索引,这样后端在合并的时候进行索引排序。

断点续传

查询服务器看哪些数据已经传成功标记一下,表示已经上传成功,只需要传剩下的部分。断点续传还需要实现文件覆盖功能,所以如果文件已经有了完整的大文件就要进行删除,上传完成后还要进行文件校验,比较上传前和上传成功后的文件大小。

async uploadChunks(uploadedList = []) {
	const list = this.chunks.filter(chunks => uploadedList.indexOf(chunk.hash) == -1).map(({chunk,hash,index},i) => {
		const form = new FormData();
		form.append('chunk',chunk);
		form.append('hash',hash);
		form.append('filename',this.container.file.name);
		form.append('fileHash',this.container.hash);
		return {form,index,status: Status.wait}
	}).map(({form,index}) => {
		request({
			url: '/upload',
			data: form,
			onProgress: this.createProgresshandler(this.chunks[index]),
			requestList: this.requestList
		})
	})
	await Promise.all(list);
}