首页 前端知识 vue element md5实现大文件分片上传、断点续传

vue element md5实现大文件分片上传、断点续传

2024-07-29 00:07:34 前端知识 前端哥 581 519 我要收藏

概述:

一、上传组件部分:使用el-upload,在页面创建一个上传控件。

        UI组件参数:multiple--是否支持多选文件、action--必选参数,上传的地址(因为实际情况下肯定会使用项目组件来调用后端接口,所以此处我给了action="",)、show-file-list--是否显示已上传文件列表、http-request--覆盖默认的上传行为,可以自定义上传的实现(一开始是使用before-upload来做上传文件之前的钩子,但是我们需要拦截action 属性的请求,做自定义文件的上传行为,官网上给出了“返回 false 或者返回 Promise 且被 reject,则停止上传”,但是我在return false后还是会在上传时默认调用一次空请求,再调用我的自定义上传请求接口,没办法只能将before-upload替换为http-request实现自定义上传)、limit--配合multiple使用,最大允许上传个数(这里有个坑要注意一下,上传后你会发现on-exceed的返回参数中,上传文件数是不断累积的,比如你限制最大同时上传10个文件,第一次你上传了7个,第二次你上传了5个,此时会提示你上传了12个,因此第二次被限制住了,所以需要在on-success中清除上传历史记录,防止文件限制莫名其妙的超出)、on-exceed--文件超出个数限制时的钩子、on-success--文件上传成功时的钩子。

<el-upload ref="upload" multiple action="" :show-file-list="false" :http-request="handleFileUpload" :limit="10" :on-exceed="handleExceed" :on-success="handleAvatarSuccess">

              <Button class="button" type="primary" :disabled="disabled">上传</Button>

</el-upload>

 二、JS部分:点击上传后触发自定义的上传方法,接收一个file参数为上传的文件,这里要注意两点,1.当同时上传多个文件时,会多次触发事件,有几个文件就执行几次方法,file参数为单次执行的文件对象而不是全部的上传文件;2.before-upload的返回参数直接就是文件对象,而http-request的返回参数需要file.flie才能到文件对象。

        首先通过file.size获得文件大小,判断当前文件是否需要分片上传,过小的文件直接上传即可,此分支不再多讲。

        npm安装spark-md5

npm install --save spark-md5

         在当前文件引入

import SparkMD5 from "spark-md5";

        为当前文件生成md5值(md5主要是给断点续传和快传用的,就是用来做个标识,具体原理这里就不讲了,只写前端业务实现。)正常是判断文件大小,如果不是很大的情况直接用整个文件生成md5值就行,当判断文件过大时,应该先用file.slice()分割文件,将第一片的md5值拼接最后一片的md5值,防止性能消耗过大。我这里就简单的和后端沟通一下,统一不做区分,直接全部用第一片的拼接最后一片的md5值。

        文件完整性检测

        在正式调用上传接口前先调用文件完整性检测接口,为断点续传用,后端通过md5值结合文件名来检测当前文件是否已经上传过,上传了多少,获取当前文件应该上传的分片的下标。

        上传文件

        file.slice()分割出本次上传的文件,同各种业务参数调用上传接口上传文件。在成功的回调中递归调用上传方法,每次重新分割下一片文件,反复上传直到全部上传完毕。(代码包含进度条部分)

    // 上传(同时选择多个文件时会多次上传该文件)
    async handleFileUpload(item) {
      let file = item.file
      this.disabled = true;
      this.list = [...this.list, file]; //用于展示进度
      this.uploadModal = true; // 展示进度弹窗
      // 文件大于50MB时分片上传
      if(file.size / 1024 / 1024 < 50){
        const formData = new FormData();
        formData.append("file", file);
        formData.append("name", file.name);
        this.$api.xxx(formData).then((res) => {
          if (res.error !== "error") {
            this.list.forEach((item)=>{
              if(item.name === file.name){
                item.percent = 100;
              }
            })
            this.$Message.success(`${file.name}:上传完成`);
          }else {
            this.list.forEach((item)=>{
              if(item.name === file.name){
                item.typeProgress = 1;
              }
            })
          }
        }).finally(() => {
          this.disabled = false;
        });
      }else{
        const size = 10 * 1024 * 1024; // 10MB 每个分片大小
        let current = 0; // 当前分片index(从0开始)
        let total = Math.ceil(file.size / size); // 分片总数
        let startByte = 0;
        let that = this;
        // 通过文件获取对应的md5值
        let dataFileStart = file.slice(0, size); // 第一片文件
        let dataFileEnd = file.slice(size* (total - 1) , file.size) // 最后一片文件
        var spark = new SparkMD5.ArrayBuffer();
        function getMd5(file) {
          return new Promise((resolve) => {
            // 对文件对象的处理
            var fileReader = new FileReader();
            fileReader.readAsArrayBuffer(file);
            // fileReader.onload为异步函数,要放到Promise对象中,等待状态的变更后再返回生成的md5值
            fileReader.onload = function (e) {
              spark.append(e.target.result);
              resolve(spark.end());
            };
          });
        }
        //获取文件二进制数据
        let md5Start = await getMd5(dataFileStart); // 第一片的md5值
        let md5End = await getMd5(dataFileEnd); // 最后一片的md5值
        let md5 = md5Start + md5End;
        // 文件完整性检测
        this.$api.xxx({md5:md5,fileName:file.name}).then((res) => {
          if (res.error !== "error") {
            current = res?.indexOf("0") === -1?0 : res?.indexOf("0"); // 当前服务器应该上传的分片下标
            startByte = size* current
            uploadChunk();
          }
        }).finally(() => {
          this.disabled = false;
        });
        // 编辑上传参数并上传文件
        function uploadChunk() {
          const formData = new FormData();
          const endByte = Math.min(startByte + size, file.size);
          const chunk = file.slice(startByte, endByte); // 当前分片文件
          formData.append("file", chunk);
          formData.append("name", file.name);
          formData.append("current", current);
          formData.append("total", total);
          formData.append("md5", md5);
          that.$api.xxx(formData).then((res) => {
            if (res.error !== "error") {
              that.list.forEach((item)=>{
                if(item.name === file.name){
                  item.percent = Math.floor(((Number(current) + 1) * 100) / total);
                }
              })
              that.list= [...that.list]
              startByte = endByte;
              if (startByte < file.size) {
                current++;
                uploadChunk();
              } else {
                that.$Message.success(`${file.name}:上传完成`);
              }
            }else {
              that.list.forEach((item)=>{
                if(item.name === file.name){
                  item.typeProgress = 1;
                }
              })
            }
          }).finally(() => {
            that.disabled = false;
          });
        }
      }
      return false;
    },
    // 文件超出个数限制时的钩子
    handleExceed(files) {
      this.$Message.warning(`最多同时上传 10 个文件,本次选择了 ${files.length} 个文件`);
    },
    // 文件上传成功时的钩子(清除上传历史记录,防止文件限制超出)
    handleAvatarSuccess(){
      this.$refs.upload.clearFiles();
    }

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14543.html
标签
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!