Fork me on GitHub

实习工作记录(一)大文件上传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.等全部分片上传完成后向后端请求合并分块,成一个完整的文件。

可以在监听方法中写自己想要的功能代码。。。并且在上传中还包含了进度条信息可以看进度。。
下面贴上完整前端代码,自行修改请求路径,也可以修改文件的上传配置,可以修改样式,。。。
(script文件需要引入)
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="webuploader.min.js"></script>
    <link href="webuploader.css" type="css/text"/>
    <style type="text/css">
        .test-bar {
            height: 100%;
            background-color: #0b0e9e;
        }
        input{

            height: 0px;
        }

    </style>
</head>
<body>

<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>

<script>


    //项目ip
    function getRootPath(){
        return("http://localhost:53021");

    }

    getRootPath();

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


    //监听分块上存过程中的三个时间点
    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();
                if (!map[file.id]) {
                    deferred.reject();
                    alert("文件解析出错,请刷新后重写上传.");
                    return deferred.promise();
                }
                //显示暂停按键并且隐藏删除按键
                switchButton(file.id);

                //先查看服务器中是否已经有该文件
                $.ajax({
                    type: "POST",
                    dataType: "json",
                    url:  getRootPath()+"/resource/secondPass",
                    // timeout: 3000,
                    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("p.state").text("已经上传");
                            deferred.resolve();
                        } else {
                            $('#' + file.id).find("p.state").text("正在上传...");
                            deferred.resolve();
                        }
                    },
                    error: function (data) {
                        deferred.reject();
                        console.log(JSON.stringify(data));
                        $('#' + file.id).find("p.state").text("上传出错...");
                        alert("网络错误,请刷新后再上传");
                    }
                });

                return deferred.promise();
            },
            //时间点2:如果有分块上传,则每个分块上传之前调用此函数
            //用于文件的续传
            beforeSend: function (block) {
                console.log("执行时间点2的方法。。。");
                var deferred = WebUploader.Deferred();
                $.ajax({
                    type: "POST",
                    dataType: "json",
                    url:  getRootPath()+"/resource/checkChunk",
                    data: {
                        "chunk": block.chunk,
                        //block.file.id,获取该分片对应的文件的id,从而获取该文件的md5值
                        "md5Val": map[block.file.id]
                    },
                    success: function (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('p.state').text("后台正在合并文件...");
                //上传成功后,异步请求后台的servlet,发送的数据有guid(该文件所有分片保存的目录),chunks(该文件一共分了多少片,注意要向上取整),filename(文件名)
                $.ajax({
                    type: "POST",
                    dataType: "json",
                    url:  getRootPath()+"/resource/mergeChunk",
                    data: {
                        "guid": uploader.options.formData.guid,
                        "fileSize": file.size,
                        "chunks": Math.ceil(file.size / chunkSize),
                        "fileName": file.name,
                        "md5Val": map[file.id]
                    },
                    success: function (data) {
                        console.log(JSON.stringify(data))
                        if (data.status === "success") {
                            $('#' + file.id).find('p.state').text('已经上传');
                        } else {
                            $('#' + file.id).find('p.state').text('上传失败');
                        }
                    },
                    error: function (data) {
                        alert(JSON.stringify(data));
                        $('#' + file.id).find("p.state").text("上传出错...");
                    }
                });
                console.log("执行时间点3的方法。。。");

            }
        });


    var uploader = WebUploader.create({

        // swf文件路径
        swf: 'webuploader/Uploader.swf',
        // 文件接收服务端。
        server:  getRootPath()+'/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,
        formData: {
            test: 123
        },
        //禁用全局拖拽功能
        disableGlobalDnd: true,
        // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
        resize: false
    });
    console.log(uploader.upload());
    

    // 当有文件被添加进队列的时候触发
    uploader.on('fileQueued', function (file) {
        alert("123");
        console.log(file);

        $("#thelist").append(
            '<div id="' + file.id + '" class="item">'
            + '<h4 class="info">' + file.name + '</h4>'
            + '<p class="state">等待上传...</p>'
            + '<button class="btn btn-info btn-stop" style="display:none;">' + '暂停' + '</button>'
            + '<a 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%;" >'
            + "<span>"
            + "</span>"
            + '</div>'
            + '</div>'
            + '</div>');
        (new WebUploader.Uploader()).md5File(file, 0, 10 * 1024 * 1024).progress(function (percentage) {
            $('#' + file.id).find('p.state').text("正在读取文件信息..." + parseInt(percentage * 100) + "%");
        })
        //当文件读取完后,就执行then方法
            .then(function (md5Val) {
                map[file.id] = md5Val;
                $('#' + file.id).find("p.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");
            console.log("删除",map);

            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;
            console.log("删除前的文件列表长度:" + len);
            //渐变的效果的消失
            $(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', function (file) {
        switchButton(file.id);
        console.log("执行上传成功的方法。。");
        // $('#' + file.id).find("p.state").text("上传成功。。。");

    });

    uploader.on('uploadError', function (file) {
        switchButton(file.id);
        $('#' + file.id).find('p.state').text('上传出错...');
    });

    //不管所有分片发送成功或者失败都会执行该方法
    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;
        }
        console.log(uploader.upload());

        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");
        }
    }


    // function testInterval() {
    //     var a = 0;
    //     var flag = setInterval(function () {
    //         a = a + 1;
    //         if (a === 5) {
    //             clearInterval(flag);
    //         }
    //         console.log("计时器。。。" + a)
    //     }, 1000);
    //
    //
    // }


</script>
</body>
</html>

 

 
posted @ 2020-09-26 22:01  litianer  阅读(355)  评论(0编辑  收藏  举报