实现大文件分片上传,断点续传
一、要点
1.分片上传
目的:当文件过大,一次性将几百兆甚至几个G的文件直接传给后端,会造成后台服务崩溃,导致上传失败;因此需要将一个大的文件拆分成若干个小的文件,分别传给后端
实现方法:
1.定义分片大小max
2.根据文件大小以及分片大小计算出分片总数 count = Math.ceil(file.size/max)
3.通过file.slice(index*max,(index+1)*max) 将大文件切割成若干个小文件
4.定义chunks用来存储所有的分片,循环将每个分片存入到数组chunks中
此刻,分片完成 ;最后循环chunks,执行后台接口即可(每个分片上传的接口)
2.断点续传
目的:当由于页面刷新或者浏览器关闭等原因中断文件的上传,下次再次重新上传很浪费时间;因此可以利用断点续传接着上次的上传进度上传
实现方法:
1.前端在执行上传分片的接口时,将文件的md5值传给后端
2.在每次发送分片之前,调后端接口查一下当前文件已上传的分片信息
3.如果当前分片已存在,则无需再次发送请求,直接执行成功回调;同时可计算当前上传进度;如已上传的分片数为20,总分片为100,那么进度progress=(20/100)*100%
3.单次仅发送5个分片请求
目的:当分片过多时,一次性循环将所有分片同时发送给后端会造成多并发问题,并且同时发送太多请求会占用较多带宽,最终也会导致文件发送失败;因此建议同一时间内仅发送5个请求
实现方法:
1.循环chunks的前五个
2.如果当前分片已存在,直接执行成功回调,并且直接跳到第i+5个分片
3.如果当前分片不存在,执行请求
4.获取文件的md5值
二、代码实现
//文件分片
const max = 2M //分片大小
const count = Math.ceil(file.size/2*1024*1024)//分片数量
const chunks = [] //分片集合,用来存储每个分片
let index = 0
//将分片添加到chunks中
const md5 = await getMd5(file)
while(index<count){
chunks.push({
file: file.slice(index*max,(index+1)*max),
filename: `${md5}_${index}`
md5 ,
index
})
index++
}
//断点续传
const getAlreadlyChunks = function(md5){
new Promise(resolve=>{
//后台根据当前分片文件的md5值去查所有相等md5值的分片文件信息,并且返回给前端
const alreadyChunks = res
resolve(alreadlyChunks)
})
}
//获取当前已存在的分片
const alreadlyChunks = await getAlreadlyChunks(md5)
for(let i = 0; i < 5; i++){
}
//上传操作
如下展示了两种获取文件MD5的方法
第一种:
方法:通过SparkMD5读取文件MD5值
缺陷:当问价过大时,浏览器读取不了文件的buffer,导致MD5值不能正常获取
第二种
方法:原理是通过分片读取文件,这里直接使用大佬写好的插件即可
插件地址:https://github.com/forsigner/browser-md5-file
缺陷:文件在分片读取时,需要等待;因此最后在前端加一个文件加载中的状态
import SparkMD5 from "spark-md5"; import BMF from "browser-md5-file"; //获取文件MD5值 (第一种) export function getSparkMD5(file) { return new Promise((resolve) => { let fileReader = new FileReader(); fileReader.readAsArrayBuffer(file); fileReader.onload = (e) => { const spark = new SparkMD5.ArrayBuffer() const buffer = e.target.result spark.append(buffer); const md5 = spark.end(); resolve(md5); }; }); } //获取文件MD5值,针对大文件需要分片读取(第二种) export function getBMF(file) { return new Promise((resolve) => { const bmf = new BMF(); bmf.md5( file, (err, md5) => { resolve(md5); }, (progress) => { //进度 // console.log(progress); } ); }); }