实现大文件分片上传,断点续传

一、要点

    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)
  const isAlready = function(chunk) {
        return (
          md5 === chunk.MD5&&
          alreadChunks.length > 0 &&
          alreadChunks.findIndex((val) => Number(val) - 1 == chunk.index) > -1
        );
      };


  //循环分片集合,将每个分片上传到服务器---单次上传五个
for(let i = 0; i < 5; i++){  
     const chunk = chunks[i];
        if (isAlready(chunk)) { //已存在的分片直接成功,并且轮到第i+5发请求
          complete(i); 
          const next = i + 5;
          uploadFn(next);
        } else { //分片不存在向后台发请求
          uploadFn(i);
        }
}

//上传操作
  const uploadFn = function(index) {
        const chunk = chunks[index];
     //判断当前分片是否存在,如果存在,递归执行上传操作
        if (isAlready(chunk)) {
          complete(index);
          const next = index + 5;
          uploadFn(next); 
        } else {
          const formData = new FormData();
          formData.append("file", chunk.file);
          const query = {
            id, //模板id
            filename: chunk.filename, //文件名称
            md5: chunk.HASH, //当前分片文件hash值
            chunk: chunk.index + 1, //当前分片下标,从1开始
            chunks: count, //分片总数量
          };
          _uploadFunc(formData, query)
            .then((res) => {
              uploadCount++;
              if (uploadCount === 1) {
                context.emit("uploadSuccess"); //第一个分片上传成功,提醒父组件关闭弹窗
              }
              complete(index);
              if (index < count) {
                //如果第1个请求成功后,接着执行第6个;第2个成功后接着执行第7个....
                const next = res.chunk - 1 + 5;
                uploadFn(next);
              }
            })
            .catch(() => {
              context.emit("uploadFail");
            });
        }
      };
 

 如下展示了两种获取文件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);
      }
    );
  });
}

 

posted @ 2021-12-07 15:41  敏秀  阅读(730)  评论(0编辑  收藏  举报