设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 创业者 数据 手机
当前位置: 首页 > 服务器 > 安全 > 正文

大规格文件的上传优化思路详解

发布时间:2020-06-15 18:47 所属栏目:53 来源:站长网
导读:副标题#e# 在开发过程中,收到这样一个问题反馈,在网站上传 100 MB 以上的文件经常失败,重试也要等老半天,这就难为需要上传大规格文件的用户了。那么应该怎么做才能快速上传,就算失败了再次发送也能从上次中断的地方继续上传呢?下文为你揭晓答案~ 温馨

在开发过程中,收到这样一个问题反馈,在网站上传 100 MB 以上的文件经常失败,重试也要等老半天,这就难为需要上传大规格文件的用户了。那么应该怎么做才能快速上传,就算失败了再次发送也能从上次中断的地方继续上传呢?下文为你揭晓答案~

温馨提示:配合 Demo 源码一起阅读效果更佳

整体思路

第一步是结合项目背景,调研比较优化的解决方案。
文件上传失败是老生常谈的问题,常用方案是将一个大文件切片成多个小文件,并行请求接口进行上传,所有请求得到响应后,在服务器端合并所有的分片文件。当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分,减少用户的等待时间,缓解服务器压力。这就是分片上传文件。

大文件上传

那么如何实现大文件分片上传呢?

流程图如下:

分为以下步骤实现:

1. 文件 MD5 加密

MD5 是文件的唯一标识,可以利用文件的 MD5 查询文件的上传状态。

根据文件的修改时间、文件名称、最后修改时间等信息,通过 spark-md5 生成文件的 MD5。需要注意的是,大规格文件需要分片读取文件,将读取的文件内容添加到 spark-md5 的 hash 计算中,直到文件读取完毕,最后返回最终的 hash 码到 callback 回调函数里面。这里可以根据需要添加文件读取的进度条。

实现方法如下:

// 修改时间+文件名称+最后修改时间-->MD5 md5File (file) { return new Promise((resolve, reject) => { let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice let chunkSize = file.size / 100 let chunks = 100 let currentChunk = 0 let spark = new SparkMD5.ArrayBuffer() let fileReader = new FileReader() fileReader.onload = function (e) { console.log('read chunk nr', currentChunk + 1, 'of', chunks) spark.append(e.target.result) // Append array buffer currentChunk++ if (currentChunk < chunks) { loadNext() } else { let cur = +new Date() console.log('finished loading') // alert(spark.end() + '---' + (cur - pre)); // Compute hash let result = spark.end() resolve(result) } } fileReader.onerror = function (err) { console.warn('oops, something went wrong.') reject(err) } function loadNext () { let start = currentChunk * chunkSize let end = start + chunkSize >= file.size ? file.size : start + chunkSize fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)) } loadNext() }) }

2. 查询文件状态

前端得到文件的 MD5 后,从后台查询是否存在名称为 MD5 的文件夹,如果存在,列出文件夹下所有文件,得到已上传的切片列表,如果不存在,则已上传的切片列表为空。

// 校验文件的MD5 checkFileMD5 (file, fileName, fileMd5Value, onError) { const fileSize = file.size const { chunkSize, uploadProgress } = this this.chunks = Math.ceil(fileSize / chunkSize) return new Promise(async (resolve, reject) => { const params = { fileName: fileName, fileMd5Value: fileMd5Value, } const { ok, data } = await services.checkFile(params) if (ok) { this.hasUploaded = data.chunkList.length uploadProgress(file) resolve(data) } else { reject(ok) onError() } }) }

3. 文件分片

文件上传优化的核心就是文件分片,Blob 对象中的 slice 方法可以对文件进行切割,File 对象是继承 Blob 对象的,因此 File 对象也有 slice 方法。

定义每一个分片文件的大小变量为 chunkSize,通过文件大小 FileSize 和分片大小 chunkSize 得到分片数量 chunks,使用 for 循环和 file.slice() 方法对文件进行分片,序号为 0 - n,和已上传的切片列表做比对,得到所有未上传的分片,push 到请求列表 requestList。

async checkAndUploadChunk (file, fileMd5Value, chunkList) { let { chunks, upload } = this const requestList = [] for (let i = 0; i < chunks; i++) { let exit = chunkList.indexOf(i + '') > -1 // 如果已经存在, 则不用再上传当前块 if (!exit) { requestList.push(upload(i, fileMd5Value, file)) } } console.log({ requestList }) const result = requestList.length > 0 ? await Promise.all(requestList) .then(result => { console.log({ result }) return result.every(i => i.ok) }) .catch(err => { return err }) : true console.log({ result }) return result === true }

4. 上传分片

调用 Promise.all 并发上传所有的切片,将切片序号、切片文件、文件 MD5 传给后台。

后台接收到上传请求后,首先查看名称为文件 MD5 的文件夹是否存在,不存在则创建文件夹,然后通过 fs-extra 的 rename 方法,将切片从临时路径移动切片文件夹中,结果如下:

(编辑:ASP站长网)

网友评论
推荐文章
    热点阅读