大文件上传vue+WebUploader

大文件上传vue+WebUploader

说说我实习前端开发的时候用的大文件上传,前端原本项目用的是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.等全部分片上传完成后向后端请求合并分块,成一个完整的文件。

大文件上传vue+WebUploader_第1张图片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(
        '
+ file.id + '" class="item">' + '' + file.name + "" + '等待上传...' + '" + '' + "删除" + "" + '
' + '
+ file.id + 'progress" class="test-bar" style="width: 0%;" >' + "
"
+ "
"
+ "
"
); 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>

你可能感兴趣的:(前端,js,vue)