vue+WebUploader实现大文件上传

责编:menVScode 2020-09-29 10:15 阅读(89)

说说我前端开发的时候用的大文件上传,前端原本项目用的是element自带的el-upload文件上传,确实很方便,element把数据上传成功,失败,上传中等等的监听事件都已经封装好了,文件列表和文件信息也携带在监听方法的参数中,调用然后打印,,一目了然,进行业务逻辑开发效率很高。但问题是,,element的upload没有附带大文件的断点续传功能,上传过程中如果中断那么就比较麻烦,所以需要自己开发。

什么是断点续传?

当用户上传文件过程中如果由于网络、手滑、或者其他骚操作等种种原因突然中断上传,那原本上传一半的文件要怎么处理???下次上传这个文件还得全部重来??这样是很浪费性能和资源的。并且,http协议和Springboot都限制了文件的上传大小,文件太大怎么办??这个时候,大文件的断点续传技术完美解决。

它将一个文件切割成若干部分分开向服务器端上传,每个小的部分我们称为切块,每上传结束一个切块除了保存文件信息,还会在后端保存切块的“”识别码”,用于识别文件上传到哪儿了,等到下次上传时,直接从这个位置开始继续上传,这样大大节省了开销。而且还有一个亮点,如果文件已经上传过,那么可以对后台进行秒传,节省大量时间,用户体验也大大提高。

想要实现大文件断点续传,我们只需要安装一个插件WebUploader,然后在前端js代码中触发监听,配置相关的变量就可以实现断点续传了,灵活性很高。

插件安装方法

插件的底层源码是用JQuery封装的,所以需要安装JQuery

如果是vue项目,npm安装到环境中:

npm install JQuery
npm install WebUploader

然后在vue页面中引入:

import $ from Jquery
import webUploader from WebUploader

然后就可以在项目中触发对应的方法和配置了。

上传原理流程

WebUploader分为三个部分:1.注册三个事件,文件上传前,分片上传前和分片上传后,创建WebUploader实例对象,配置文件块大小,上传地址,文件限制大小等变量。。2.先判断是否上传过该文件,调用接口。如果上传过,进行秒传,没有则进行切块。3.切块后进行分片上传,获取分片的编号,确认分片,4.等全部分片上传完成后向后端请求合并分块,成一个完整的文件。

html效果图


前端代码示例

下面贴上完整前端代码,自行修改请求路径,也可以修改文件的上传配置,可以修改样式,。。。可以在监听方法中写自己想要的功能代码。。。并且在上传中还包含了进度条信息可以看进度。。

<template>
	<div class="uploadWrapper">
	    <div class="btnUpload">
	        <div id="picker" class="form-control-focus">点击选择文件上传</div>
	    </div>
	    <div id="thelist" class="uploader-list">
	
	    </div>
	    <button id="btnSync" type="button" class="btn btn-warning">开始同步</button>
	</div>
</template>

import apis from "@apis";
import $ from "jquery";
import WebUploader from "webuploader";

export default {
  data() {
    return {
      projectContract: {},
    };
  },

  mounted() {
    $("#btnSync").hide();
    //该map,用于给uploader.options.formData表达赋一个动态的键值对.
    var map = {};
    // var testMd5;
    //定义文件的分片大小
    var chunkSize = 0.9 * 1024 * 1024;
    var _self = this;

    //监听分块上存过程中的三个时间点
    WebUploader.Uploader.register(
      {
        "before-send-file": "beforeSendFile", //整个文件上存前,触发方法beforeSendFile
        "before-send": "beforeSend", //每个分片上存前,触发方法beforeSend
        "after-send-file": "afterSendFile", //分片上存完毕后,触发方法afterSendFile
      },
      {
        //时间点1:所有分块进行上存之前触发该方法,即当每个文件开始上传第一个分块前就调用该方法
        beforeSendFile: function (file) {
          console.log("执行时间点1的方法。。。。。");
          //定义一个异步对象
          var deferred = WebUploader.Deferred();
          // setTimeout(() => {
          //   if (!map[file.id]) {
          //     // deferred.reject();
          //     alert("文件解析出错,请刷新后重新上传.");
          //     return deferred.promise();
          //   }
          // }, 1000);

          //显示暂停按键并且隐藏删除按键
          switchButton(file.id);

          //先查看服务器中是否已经有该文件
          $.ajax({
            type: "POST",
            dataType: "json",
            url: url_second_pass,//这是检查是否传过的请求路径
            data: {
              fileSize: file.size,
              fileName: file.name,
              md5Val: map[file.id],
            },
            success: function (data) {
              if (data.status === "1") {
                console.log(
                  "............................................输出返回值:" +
                    data.status
                );
                // deferred.reject();
                uploader.skipFile(file);
                //清除进度条,如果是秒传,那么是不会触发方法uploader.on('uploadComplete'
                fadeOutProgress(file);
                $("#" + file.id)
                  .find("span.state")
                  .text("已经上传");
                // deferred.resolve();
              } else {
                $("#" + file.id)
                  .find("span.state")
                  .text("正在上传...");
                deferred.resolve();
              }
            },
            error: function (data) {
              console.log("秒传错误");
              deferred.reject();
              $("#" + file.id)
                .find("span.state")
                .text("秒传出错...");
              alert("网络错误,请刷新后再上传");
            },
          });

          return deferred.promise();
        },
        //时间点2:如果有分块上传,则每个分块上传之前调用此函数
        //用于文件的续传
        beforeSend: function (block) {
          console.log("执行时间点2的方法。。。");
          var deferred = WebUploader.Deferred();
          $.ajax({
            type: "POST",
            dataType: "json",
            url: url_check_chunk,//这里是检查文件分块的请求路径
            data: {
              chunk: block.chunk,
              //block.file.id,获取该分片对应的文件的id,从而获取该文件的md5值
              md5Val: map[block.file.id],
            },
            success: function (data) {
              console.log("成功切块", data);
              if (data.status === "1") {
                console.log("跳过..");
                //分片存在,跳过
                deferred.reject();
              } else {
                console.log("上传..");
                //分片不存在,那么就上传.
                deferred.resolve();
              }
            },
            error: function (data) {
              console.log("时间点2出错:" + JSON.stringify(data));
              //如果是一般的请求出错,那么也可以尝试上传
              deferred.resolve();
            },
          });
          return deferred.promise();
        },
        //时间点3:一个文件的所有分片上传成功后,调用该方法,让后台合并所有分片
        //该方法的在uploader.on("success")方法前执行。
        afterSendFile: function (file) {
          $("#" + file.id)
            .find("span.state")
            .text("后台正在合并文件...");
          //上传成功后,异步请求后台的servlet,发送的数据有guid(该文件所有分片保存的目录),chunks(该文件一共分了多少片,注意要向上取整),filename(文件名)
          $.ajax({
            type: "POST",
            dataType: "json",
            url: apis.url_merge_chunk,//这里是合并文件的请求路径
            data: {
              guid: uploader.options.formData.guid,
              fileSize: file.size,
              chunks: Math.ceil(file.size / chunkSize),
              fileName: file.name,
              md5Val: map[file.id],
            },
            success: (data) => {
              if (data.status === "success") {
                $("#" + file.id)
                  .find("span.state")
                  .text("已经上传");
              } else {
                $("#" + file.id)
                  .find("span.state")
                  .text("上传失败");
              }
            },
            error: function (data) {
              console.log("合并错误");
              $("#" + file.id)
                .find("span.state")
                .text("合并文件出错...");
            },
          });
          console.log("执行时间点3的方法。。。");
        },
      }
    );

    var uploader = WebUploader.create({
      // swf文件路径
      swf: "webuploader/Uploader.swf",
      // 文件接收服务端。
      server: apis.url_resource_upload,//这里是文件上传路径
      // 选择文件的按钮。可选。
      // 内部根据当前运行是创建,可能是input元素,也可能是flash.
      pick: "#picker",
      compress: null, //图片不压缩
      chunked: true, //分片处理
      chunkSize: chunkSize, //每片5M
      chunkRetry: 3, //由于网络原因出现的故障,最多允许分片自动重转3次
      threads: 8, //上传并发数。允许同时最大上传进程数。
      fileSizeLimit: 12 * 1024 * 1024 * 1024, //12G 验证文件总大小是否超出限制, 超出则不允许加入队列
      fileSingleSizeLimit: 5 * 1024 * 1024 * 1024, //5G 验证单个文件大小是否超出限制, 超出则不允许加入队列
      fileNumLimit: 100,
      //禁用全局拖拽功能
      disableGlobalDnd: true,
      // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
      resize: false,
    });
    // 当有文件被添加进队列的时候触发
    uploader.on("fileQueued", function (file) {
      // file.statusText = "未上传"
      console.log("添加", file);

      $("#thelist").append(
        '<div id="' +
          file.id +
          '" class="item">' +
          '<span class="info">' +
          file.name +
          "</span>" +
          '<span class="state">等待上传...</span>' +
          '<button class="btn btn-info btn-stop" style="display:none;">' +
          "暂停" +
          "</button>" +
          '<a id="del" href="javascript:void(0);" class="btn btn-primary file_btn btnRemoveFile" >' +
          "删除" +
          "</a>" +
          '<div class="progress">' +
          '<div id="' +
          file.id +
          'progress"  class="test-bar" style="width: 0%;" >' +
          "</div>" +
          "</div>" +
          "</div>"
      );
      new WebUploader.Uploader()
        .md5File(file, 0, 10 * 1024 * 1024)
        .progress(function (percentage) {
          $("#" + file.id)
            .find("span.state")
            .text("正在读取文件信息..." + parseInt(percentage * 100) + "%");
        })
        //当文件读取完后,就执行then方法
        .then(function (md5Val) {
          map[file.id] = md5Val;
          $("#" + file.id)
            .find("span.state")
            .text("读取成功,请点击上传!");
          //uploader.options.formData.file.id=md5Val,这种方式中"file.id"只能作为一个字符串
          //下面种方式可以给uploader.options.formData表单动态赋一个键值对
          $.extend(uploader.options.formData, map);
        });
      $("#btnSync").show();
      var obj = "div[id=" + file.id + "]";

      console.log("打印id啊:" + $(obj).attr("id"));
      //给“删除”按键绑定监听事件
      $("#" + file.id + " a").bind("click", function () {
        var fileItem = $(this).parent();
        var fileId = $(fileItem).attr("id");

        if (!map[fileId]) {
          alert("正在解析文件,请稍后再操作...");
          return;
        }
        //  console.log("输出id:" +fileId);
        //$(fileItem).attr("id")意思是,获取到fileItem该标签的id属性的值,true为从队列中移除
        uploader.removeFile(file, true);
        //同时取消文件上传
        uploader.cancelFile(file);
        delete map[fileId];
        var len = $("#thelist").children("div").length;
        //渐变的效果的消失
        $(fileItem).fadeOut(function () {
          $(fileItem).remove();
        });
        //由于上面的remove方法是异步删除,因此当下面获取长度的时候,长度还是不变,因此当长度为1的时候,其实list中就没有文件了
        var len = $("#thelist").children("div").length;
        console.log("打印文件列表长度:" + len);
        if (len === 0 || len === 1) {
          $("#btnSync").hide();
        }
      });
      //给“暂停”按键绑定监听事件
      $("#" + file.id + " button").bind("click", function () {
        console.log("暂停");
        clickStopButton(file);
      });
    });

    //上传过程中,一直会执行该方法
    uploader.on("uploadProgress", function (file, percentage) {
      console.log("当前文件" + file.id + "上传的百分比" + percentage + "\n");
      //因为percentage是百分比(小数来的),因此要显示进度条效果,就先乘100,然后(percentage*100)%作为进度条的宽度百分比,
      // 就可以实现进度条效果
      // $('#' + file.id).children($("#test-bar")).css("width", parseInt(percentage * 100) + "%");
      $("#" + file.id + "progress").css(
        "width",
        parseInt(percentage * 100) + "%"
      );
    });

    /**
     * 文件上传成功后,就在该文件对应的位置上,显示上传成功,file.id,作为上传文件位置标签的id,
     */
    uploader.on("uploadSuccess", (file) => {
      switchButton(file.id);
      console.log("执行上传成功的方法。。");
      $("#" + file.id)
        .find("span.state")
        .text("上传成功。。。");

    });

    uploader.on("uploadError", function (file) {

      switchButton(file.id);
      $("#" + file.id)
        .find("span.state")
        .text("uploadError上传出错...");
    });

    //不管所有分片发送成功或者失败都会执行该方法
    uploader.on("uploadComplete", function (file) {
      console.log("执行上传完成的方法");
      // //上传完成就删除进度条
      fadeOutProgress(file);
    });
    /**
     * 当点击上传文件的时候,就触发该方法
     */
    $("#btnSync").on("click", function () {
      //获取文件列表的长度
      var len = $("#thelist").children("div").length;
      //获取计算出md5的文件数
      var mapSize = Object.keys(map).length;
      //一定要全部文件都计算出md5的值,才能上存
      if (len !== mapSize) {
        alert("文件正在解析,请稍等..");
        return;
      }
      uploader.upload();
    });

    //该方法删除指定文件下的进度条
    function fadeOutProgress(file) {
      $("#" + file.id)
        .find(".progress")
        .fadeOut();
    }

    //指定文件下的,“暂停”键,和“删除”按键切换显示状态,
    function switchButton(fileId) {
      var display = $("#" + fileId + " button").css("display");
      if (display === "none") {
        //暂停键显示
        $("#" + fileId + " button").css("display", "");
        //删除键隐藏
        $("#" + fileId + " a").css("display", "none");
      } else {
        //暂停键隐藏
        $("#" + fileId + " button").css("display", "none");
        //删除键显示
        $("#" + fileId + " a").css("display", "");
      }
    }

    //指定文件下,点击了“暂停”,则按键变为“继续”,反之一样
    function clickStopButton(file) {
      var content = $("#" + file.id + " button").text();
      if (content.trim() === "暂停") {
        //暂停上传
        uploader.stop(true);
        console.log("暂停");
        $("#" + file.id + " button")
          .text("继续")
          .addClass("btn-warning");
        //删除键显示
        $("#" + file.id + " a").css("display", "");
      } else if (content.trim() === "继续") {
        console.log("继续");
        //继续上传
        uploader.upload();
        $("#" + file.id + " button")
          .text("暂停")
          .removeClass("btn-warning");
        //删除键隐藏
        $("#" + file.id + " a").css("display", "none");
      }
    }
  },
  methods: {
   
  },
};
</script>
<style lang="scss" scope>
</style>

来源:https://blog.csdn.net/litiane/article/details/108830046

标签: vue WebUploader
前端交流群: MVC前端网(menvscode.com)-qq交流群:551903636

邮箱快速注册

忘记密码