美文网首页程序员java进阶干货
无插件实现大文件分片上传,断点续传

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

作者: Xiangdong_She | 来源:发表于2017-08-19 17:35 被阅读1623次
    文件上传.gif
    1. 简介:

    本篇文章基于实际项目的开发,将介绍项目中关于大文件分片上传、文件验证、断点续传、手动重试上传等需求的使用场景及实现;

    2. 项目需求
    1. 在一个音视频的添加中,既要有音视频的简介(如音视频内容文字介绍、自定义主题名称等一些基本的信息),又要有音视频所需要的多个文件(就像电视剧,一部电视剧有多集一样)。在数据库中具体表现为一对多的关系,即一个视频对应多个文件。下文就以电视剧为例
    2. 如果一个电视剧中,既有上百兆的,也有几十兆的视频,但是如果在不稳定的一个网络环境中,c传输大文件时,客户想先把小的视频上传了,之后再来继续传未传完的大文件声誉部分,这就需要断点续传的功能;
    3. 电视剧中至少有一集(至少有一个文件),无文件的电视剧基本信息无效;
    3. 需求分析
    1. 确定电视剧基本信息(自定义名称,内容简介、演员简介、播出时间等)及文件的上传方式

      • 基本信息和音视频文件分开上传(因为在原有的数据库表设计中,文件表是关联于基本信息,所以必须要有音视频主键,才能在数据库添加对应的文件信息),取得基本信息主键之后再去上传文件;
    2. 文件断点续传中,如何分片;文件接收方式;服务器端如何判断是哪个文件的分片;如何拼接各个分片;上传过程中发生意外情况(如断网,关闭浏览器),如何处理?

      • 分片方式: 在客户端进行分片;
      • 服务器端接收方式:使用MultipartFile接收文件
      • 服务器端确定是哪个文件的分片: 在客户端按照一定规则(UUID或其他方式)生成唯一名称,在服务器端直接找到与该名称相同的文件片段;
      • 拼接文件分片: 使用NIO的方式,将分片追加到已有分片的后面;
      • 上传中发生意外:
        A. 断网: 该情况类似于暂停上传,上传到文件处于暂停状态,网络恢复,即可点击继续上传按钮,继续上传;
        B. 关闭浏览器: 在关闭时,给用户提示框,询问是否继续保存,若不保存,则根据视频基本信息表的主键的删除脏数据;
        C. 第一个文件在上传时候,被用户取消或者断网,则服务器端未修改基本信息为有效,并且也未标记该文件为有效记录,可以理解为脏数据,但不需要清理这些数据(在查询的时候,不能查出这些无效记录,可以在更新视频基本信息记录的时候,查找这些脏数据,并清理磁盘上及数据表中的记录);
    4. 实现

    根据需求,已经确认了先上传基本信息,后上传文件,基本信息的提交(标题、简介、封面)比较简单,只需要在前端提交数据表所需字段,然后后台返回插入的主键即可,所以基本信息的提交及返回不过多说明,仅仅通过前端页面截图及后端部分代码进行简要说明,实现部分主要讲解分析文件上传部分的代码;
    说明:本篇文章主要涉及到两张表的操作,两张表的数据结构如下:

    DataFile表.png
    microClass表.png
    • 4.1.1 前端提交基本信息页面

    提交页面使用模板+原生html+css实现,每个人的页面、所需参数各不相同,所以在前端代码中,没有多大的参考价值,所以直接使用截图,来表现我需要做的工作、传的参数。

    1-基本信息.png
    • 4.1.2 前端保存基本信息代码

    可以根据自己的业务使用FormData封装传递的参数

    //点击保存按钮后保存数据
            function save_microClass() {
                    //获取form的基本信息
                    var classTitle = $("#classTitle").val(),
                    classDes = $("#classDes").val(),
                    coverFile = document.getElementById("coverFileName").files[0],
                    //构造一个新表单,FormData是HTML5新增的,因为基本信息中存在封面图片,所以需要使用表单提交数据            
                    var form = new FormData();            
                    form.append("title", classTitle); 
                    form.append("desc", classDes); 
                    form.append("coverFile", coverFile);
                    //Ajax提交            
                     $.ajax({                
                        url: "micro/save",               
                        type: "POST",                
                        data: form,                
                        async: true,        //异步                
                        processData: false,  //很重要,告诉jquery不要对form进行处理                
                        contentType: false,  //很重要,指定为false才能形成正确的Content-Type                
                        success: function(data){
                            //在添加音视频基本信息之后,服务器端返回新增音视频的主键,以便之后上传文件时,与文件进行关联
                            if(data.length>0){
                                $("#microClassId").val(data);
                                submitFile();//提交文件
                            }
                        }
                    });
                }
            }
    
    • 4.1.3 基本信息服务器端接收方法

    服务器端需要与前端协同作战,接收参数需要与前端传递过来的参数对应上,以免报出400的错误。

        /**
         * @Description: 新增基本信息时保存form表单
         * @param title
         *            音视频名称
         * @param coverFile
         *            封面对象
         * @param desc
         *            音视频描述字段
         */
        @RequestMapping(value = "/save", method = RequestMethod.POST)
        @ResponseBody
        public Integer create(String title,String desc, MultipartFile coverFile) {
            try {
                 //保存基本信息逻辑,自己实现,然后返回基本信息插入后的主键
                 //request,即HttpServletRequest对象,在项目启动时候就被注入,如下:
                 /**
                  * @Autowired
                  * private HttpServletRequest request;
                 **/
                 Integer videoId = videoService.save(title,desc, coverFile, request);
                 return videoId;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    • 4.2.1 前端上传文件示例
    文件上传.gif
    • 4.2.2 前端实现

    在前端实现中,采用纯JavaScript+html+css来实现按钮的删除、添加、文件的添加、上传时暂停、继续、以及文件的顺序控制;

        <div class="row pb40 pt40 mlr-10 border-top-1">
                            <div class="col-xs-12 col-lg-12">
                                <div  class="col-xs-12 col-lg-12">
                                    <span id="mediaLable">添加视频内容</span><small style="color: #ff5704;margin-left:10px;" id="mediaLimit">视频格式仅支持MP4及AVI,文件大小需小于500M</small>
                                </div>
                                <div id="mediaFiles" class="col-xs-12 col-lg-12 mt20 video-list-wrap">
                                </div>
                                <div class="col-xs-10 col-xs-offset-1 col-lg-10 col-lg-offset-1 mt20 mb10">
                                    <a href="javascript:;" id="addMediaButton" onclick="addFileInput()" class="mybtn btn-add-user2">添加视频</a>
                                </div>
                            </div>
                        </div>
    

    以上代码,就是文件上传部分的html代码,无需关心css样式,但需要注意每个元素的id/name/事件函数;以下就是JavaScript实现文件上传逻辑的代码;

    <script type="text/javascript">      
            //删除一个上传文件文本框
            function delFileInput(that) {
                var delFileDivId = $(that).parent().attr("id");
                //如果是修改页面的删除则将要删除的附件ID保存在隐藏框
                if (delFileDivId.indexOf("modify") == 0) {
                    var deleteFileIds = $("#deleteFileIds").val();
                    if (deleteFileIds.length > 0) {
                        deleteFileIds = deleteFileIds + "," + delFileDivId.split("_").pop();
                        $("#deleteFileIds").val(deleteFileIds);
                    } else {
                        $("#deleteFileIds").val(delFileDivId.split("_").pop());
                    }
                }
                var uploadFileDivs = $("#mediaFiles").children();
                episodeCount = uploadFileDivs.length-1;
                //删除后修改集数
                for(var i=1;i<uploadFileDivs.length;i++){
                    if($("#mediaFiles").children()[i].id==$(that).parent().parent().attr("id")){
                        $(that).parent().parent().remove();
                        for(var j=i;j<uploadFileDivs.length;j++){
                            $($("#mediaFiles").children()[j]).find(".account").text("第"+(j+1)+"集");
                        }
                        break;
                    }
                }
            }
    
            //上传附件文本框计数器
            var inputCount = 0;
            var episodeCount = 0;
            //点击按钮后新增一个上传文件文本框
            function addFileInput() {
                //把新增的每一行都放到一个div中,删除时删除这个父div节点即可
                var rootFileDiv = document.createElement("div");
                rootFileDiv.setAttribute("id", "rootFileDiv" + inputCount);
                if(inputCount==0){
                    rootFileDiv.setAttribute("class", "col-xs-12 col-lg-12 video-list");
                }else{
                    rootFileDiv.setAttribute("class", "col-xs-12 col-lg-12 full video-list mt20");
                }
                $("#mediaFiles").append(rootFileDiv);
                //删除图标
                var delDiv = document.createElement("div");
                delDiv.setAttribute("id", "delFileDiv" + inputCount);
                delDiv.setAttribute("class", "delete");
                if(inputCount>0){
                    var delDivText = "<span>×</span>";
                    delDiv.innerHTML = delDivText;
                }
                //集数
                var countDiv = document.createElement("div");
                countDiv.setAttribute("id", "account");
                countDiv.setAttribute("class", "account");
                var countDivText = "第"+(episodeCount+1)+"集";
                countDiv.innerHTML = countDivText;
                //文件名称显示文本框
                var mediaFileNameDiv = document.createElement("div");
                mediaFileNameDiv.setAttribute("class", "name");
                var mediaFileNameDivInput = "<input type='text' id=mediaFile" + inputCount + "_Name" + " class='form-control' />"; 
                mediaFileNameDiv.innerHTML = mediaFileNameDivInput;
                //文件上传文本框
                var mediaFileDiv = document.createElement("div");
                mediaFileDiv.setAttribute("class", "filebtn");
                var mediaType = $("#type").val();
                var mediaFileInput;
                if(mediaType==0){
                    mediaFileInput = "<a><span>选择文件 ></span><input type='file' accept='.mp4,.avi' name='myfiles' id=mediaFile"+inputCount+"_ class='' onchange='handleFile(this)'/></a>";
                }else{
                    mediaFileInput = "<a><span>选择文件 ></span><input type='file' accept='.mp3' name='myfiles' id=mediaFile"+inputCount+"_ class='' onchange='handleFile(this)'/></a>";
                }
                mediaFileDiv.innerHTML = mediaFileInput;
                //上传进度条
                var progressbarDiv = document.createElement("div");
                progressbarDiv.setAttribute("id", "progressbar"+inputCount);
                progressbarDiv.setAttribute("class", "progress hide");
                var progressbarDivText = '<div class="progress-bar progress-bar-green" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div><div class="percent">0%</div><div class="size">40M/200M</div>';
                progressbarDiv.innerHTML = progressbarDivText;
                //暂停上传或者继续上传按钮
                var uploadStatusButtonDiv = document.createElement("div");
                uploadStatusButtonDiv.setAttribute("id", "uploadStatusButton"+inputCount);
                uploadStatusButtonDiv.setAttribute("class", "result");
                uploadStatusButtonDivButton = '<span class="success hide"><i class="fa fa-check-circle"></i>上传完成</span><span class="stop-continue hide" id="uploadStatusButton" onclick="changeUploadStatus(this)">暂停上传</span>';
                uploadStatusButtonDiv.innerHTML = uploadStatusButtonDivButton;
                $("#rootFileDiv" + inputCount).append(delDiv);
                $("#rootFileDiv" + inputCount).append(countDiv);
                $("#rootFileDiv" + inputCount).append(mediaFileNameDiv);
                $("#rootFileDiv" + inputCount).append(mediaFileDiv);
                $("#rootFileDiv" + inputCount).append(progressbarDiv);
                $("#rootFileDiv" + inputCount).append(uploadStatusButtonDiv);
                inputCount++;
                episodeCount++;
                return rootFileDiv;
            }
            
            //mediaFileArray存放分片数据信息
            var mediaFileArray=new Array();
            //获取上传分片数据基本信息,获取完毕后调用上传方法提交分片
            function submitFile(){
                var fileInputs = $("input[name='myfiles']");
                for(var i=0;i<fileInputs.length;i++){
                    if(fileInputs[i].files[0]!=null){
                        var newName = guid();
                        var name = fileInputs[i].files[0].name, //文件名           
                        size = fileInputs[i].files[0].size, //总大小 
                        type = fileInputs[i].files[0].type, //文件类型
                        shardSize = 10 * 1024 * 1024, // shardSize = 10 * 1024 * 1024, 以10MB为一个分片            
                        shardCount = Math.ceil(size / shardSize); //总片数
                        var shardArray = new Array();
                        shardArray[0]=name;
                        shardArray[1]=size;
                        shardArray[2]=type;
                        shardArray[3]=shardSize;
                        shardArray[4]=shardCount;
                        shardArray[5]=0;//当前上传状态,0-等待上传,1-正在上传,2是上传完成
                        shardArray[6]=0;//已传输最后分片编号
                        shardArray[7]=newName;//服务器存储名称
                        mediaFileArray[i]=shardArray;//将每个file的分片信息放入全局数组
                    }else{
                        //当进入修改页面file为空时
                        var newName = guid();
                        var name = "", //文件名           
                        size = 0, //总大小 
                        type = "", //文件类型
                        shardSize = 10 * 1024 * 1024, // shardSize = 10 * 1024 * 1024, 以10MB为一个分片            
                        shardCount = 0; //总片数
                        var shardArray = new Array();
                        shardArray[0]=name;
                        shardArray[1]=size;
                        shardArray[2]=type;
                        shardArray[3]=shardSize;
                        shardArray[4]=shardCount;
                        shardArray[5]=0;//当前上传状态,0-等待上传,1-正在上传,2是上传完成
                        shardArray[6]=0;//已传输最后分片编号
                        shardArray[7]=newName;//服务器存储名称
                        mediaFileArray[i]=shardArray;//将每个file的分片信息放入全局数组
                    }
                }
                postFile(0,0);//第一个参数是第几个file元素,最后一个参数是从第几个分片开始上传
            }
            
            //附件完成上传计数器
            var endCount=0;
            //控制是否继续往服务端发送分片数据,0-继续提交,1-停止提交
            var flag=0;
            //文件断点续传
            function postFile(fileNum,shardNum){
                 if(flag==0){
                    //计算每一片的起始与结束位置
                    var file = $("input[name='myfiles']")[fileNum].files[0];
                    var oldName;
                    if($("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).val().length>0){
                        oldName = $("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).val()+"#";
                    }else{
                        oldName =  mediaFileArray[fileNum][0]+"#";
                    }
                    //修改时需要oldname拼接fileId
                    var fileNameId = $("#mediaFiles").find(".video-list").eq(fileNum).find(".form-control").eq(0).attr("id");
                    if(fileNameId.indexOf("modify")==0&&fileNameId.length>0&&fileNameId.split("_").length>0){
                        oldName = oldName + fileNameId.split("_")[1];
                    }
                    var start = shardNum * mediaFileArray[fileNum][3],
                    end = Math.min(mediaFileArray[fileNum][1], start + mediaFileArray[fileNum][3]);
                    /* //修改文件为空,但所有文件都已传输完则跳转回view
                    if(file==null&&endCount>=mediaFileArray.length){
                        window.location.href="micro/index?type="+$("#type").val();
                        return;
                    } */
                    //当文件不为空,当前文件所有分片都传输完时控制button显示并跳转回view
                    if(shardNum >= mediaFileArray[fileNum][4]&&file!=null){
                        $("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).addClass("hide");
                        $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).addClass("hide");
                        $("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
                        //如果本文件传完,开始传输下一个文件
                        if(fileNum<mediaFileArray.length-1&&mediaFileArray[fileNum+1][6]==0){
                            postFile(fileNum+1,0);
                        }
                        endCount++;
                        //如果所有的文件都传输完则跳转回view
                        if(endCount>=mediaFileArray.length){
                            var location = (window.location+'').split('/');
                            var basePath = location[0]+'//'+location[2]+'/'+location[3];
                            window.location.href=basePath+"/micro/index?type="+$("#type").val();
                        }
                        return;            
                    }
                    //服务端URL
                    var requestUrl = "micro/uploadSlice";
                    //构造一个表单,FormData是HTML5新增的            
                    var form = new FormData();
                    if(file!=null){
                        form.append("data", file.slice(start,end));  //slice方法用于切出文件的一部分    
                    }        
                    //form.append("lastModified", file.lastModified);  //文件最后修改时间不支持IE
                    form.append("name", mediaFileArray[fileNum][7]); //文件存储名称
                    form.append("fileType", mediaFileArray[fileNum][2]); //文件类型
                    form.append("total", mediaFileArray[fileNum][4]);  //总片数            
                    form.append("index", shardNum + 1);  //当前是第几片
                    form.append("oldName", oldName);  //用户自定义文件名
                    form.append("seq", fileNum);  //文件所处的顺序
                    form.append("microClassId", $("#microClassId").val());  //microClassId
                    //Ajax提交            
                     $.ajax({                
                        url: requestUrl,                
                        type: "POST",                
                        data: form,                
                        async: true,        //异步                
                        processData: false,  //很重要,告诉jquery不要对form进行处理                
                        contentType: false,  //很重要,指定为false才能形成正确的Content-Type                
                        success: function(data){
                            //显示暂停按钮
                            if(start==0&&file!=null){
                                $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).removeClass("hide");
                            }
                            mediaFileArray[fileNum][6]=shardNum+1;
                            if(data!="0"){                        
                                shardNum = data++;
                                console.log("当前分片数:",shardNum);
                                var num = Math.ceil(shardNum*mediaFileArray[fileNum][3]*100 / mediaFileArray[fileNum][1]); //百分比进度
                                //改变进度条进度
                                $("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).removeClass("hide");
                                $("#mediaFiles").find(".video-list").eq(fileNum).find(".progress-bar").eq(0).attr("style","width: "+num+"%;");
                                $("#mediaFiles").find(".video-list").eq(fileNum).find(".percent").eq(0).text(num+"%");
                                $("#mediaFiles").find(".video-list").eq(fileNum).find(".size").eq(0).text(shardNum*10+"/"+Math.ceil(mediaFileArray[fileNum][1]/(1024 * 1024))+"M");
                                //通过button状态来判断是否继续上传
                                var text = $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).text();
                                if(text == "暂停上传") postFile(fileNum,shardNum);                    
                            }else{
                                //当修改文件为空时的处理
                                $("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
                                endCount=endCount+1;
                                if(endCount<mediaFileArray.length){
                                    postFile(fileNum+1,0);
                                }else{
                                    $("#mediaFiles").find(".video-list").eq(fileNum).find(".progress").eq(0).addClass("hide");
                                    $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).addClass("hide");
                                    $("#mediaFiles").find(".video-list").eq(fileNum).find('.success').eq(0).removeClass("hide");
                                    var location = (window.location+'').split('/');
                                    var basePath = location[0]+'//'+location[2]+'/'+location[3];
                                    window.location.href=basePath+"/micro/index?type="+$("#type").val();
                                }
                                
                            }                
                        },
                        error:function(xhr,errorText,errorType){
                            $("#wrong_msg").text("网络错误,上传失败,请检查网络后继续上传!");
                            $("#tips1").modal("show");
                            mediaFileArray[fileNum][6]=shardNum;
                            $("#mediaFiles").find(".video-list").eq(fileNum).find(".stop-continue").eq(0).text("继续上传");
                        }
                    });
                }else{
                    return;
                }
            }
            
            //控制上传状态
            function changeUploadStatus(that){
                if($(that).text()=="暂停上传"){
                    var fileNum = $(that).parents(".video-list").eq(0).index();
                    flag=1;
                    $(that).text("继续上传");
                    mediaFileArray[fileNum][5]=0;
                    if(fileNum<mediaFileArray.length-1 && mediaFileArray[fileNum+1][6]==0){
                        $(that).parents("#mediaFiles").find(".video-list").eq(fileNum+1).find(".stop-continue").eq(0).text("暂停上传");
                        flag=0;
                        postFile(fileNum+1,mediaFileArray[fileNum+1][6]);
                    }
                    return;
                }else{
                    var fileNum = $(that).parents(".video-list").eq(0).index();
                    var shardNum = mediaFileArray[fileNum][6];
                    for(var i=0; i<mediaFileArray.length; i++){
                        if(mediaFileArray[i][5]==1){
                            mediaFileArray[i][5]==0;
                            flag=1;
                            $(that).parents("#mediaFiles").find(".video-list").eq(i).find(".stop-continue").eq(0).text("继续上传");
                        }
                    }
                    flag=0;
                    mediaFileArray[fileNum][5]=1;
                    $(that).text("暂停上传");
                    postFile(fileNum,shardNum);
                    return;
                }
            }
            
            //用于生成uuid
            function S4() {
                return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
            }
            function guid() {
                return (S4()+S4()+S4()+S4()+S4()+S4()+S4()+S4());
            }
        </script>
    

    以上代码为前端控制文件上传所需的代码,之后介绍后台部分的实现

    • 4.3.3 后台实现

    后台实现,主要是接收文件的分片,利用前端传入唯一的新文件名称,使用NIO的方式,将分片进行合并。


    Controller实现

    /**
         * @Description: 上传文件分片
         * 
         * @param data
         *            分片文件
         * @param fileType
         *            文件类型 video/avi audio/mp3
         * @param name
         *            文件名称(newName),由Client生成,即NewName 如
         *            MICRO_CLASS_1502179979829.mp4
         * @param total
         *            分片总数
         * @param index
         *            当前分片数
         * @param microClassId
         *            文件关联的MicroClass主键,通过先保存基本信息取得并返回(DataFile中的OwerID)
         * @param oldName
         *            由用户自己输入的节目名称,需要与文件对应起来,如 实现两个100年奋斗目标
         * @param seq
         *            文件的顺序,如第一集对应1,第二集对应2....
         * 
         * @return index 当前合并到文件的分片数
         * 
         * @throws Exception
         */
        @RequestMapping("/uploadSlice")
        @ResponseBody
        public Integer uploadSlice(MultipartFile data, String fileType, String name, Integer total, Integer index,
                Integer microClassId, String oldName, int seq) throws Exception {
            int countFile = 0;// 记录一次保存中上传的文件数目
    
            /**
             * oldName:使用输入框中的字符串与fileId拼接,形如D1#D2 如:
             * 实现两个100年奋斗目标#36,其中“实现两个100年奋斗目标”在数据库中存为OldName,36表示数据库中fileData主键
             * 
             * D1: 可以为空,表示用户将Client获取到的文件名称删除,并且未输入任何字符串,可以为空; D2:
             * 可以为空,表示新增的文件,不为空,则表示在修改页面,传回的fileID;
             **/
            String[] str=oldName.split("#");
            String oldNameFiled=null;
            String fileDataId=null;
            System.out.println(str);
            switch (str.length) {
            case 0:
                break;
            case 1:
                oldNameFiled = oldName.split("#")[0];
                break;
            case 2:
                oldNameFiled = oldName.split("#")[0];
                fileDataId = oldName.split("#")[1];
                break;
            default:
                break;
            }
            
            if (total == 0) {
                // 表示在修改页面,用户只可能修改了oldName,但是未修改文件
                if (fileDataId != null && !fileDataId.equals("")) {
                    if (oldNameFiled != null && !oldNameFiled.equals("")) {
                        dataFileService.updateOldNameById(fileDataId, oldNameFiled);//
                    }
                }
            } else {
                if (index <= total) {//说明是有分片
                    String dirType = fileType.split("/")[0];// 文件类型,用于创建不同的目录,如(video/audio)
                    String fileExt = "." + fileType.split("/")[1];// 文件扩展名,如.mp3/.mp4/.avi
                    System.out.println(data.getSize() + "----" + name + "-----" + total + "----" + index);
                    // 追加分片到已有的分片上,返回保存文件的路径,如/fileDate/video/2017/08/09
                    String savePath = FileUtil.randomWrite(request, data.getBytes(), name, dirType, fileExt);
                    if (index == 1 && savePath != null) {// 说明是新的文件的第一个分片,在数据库中创建相应的记录,并且状态为无效,等到全部上传完毕之后在修改为有效
                        dataFile = new DataFile();
                        dataFile.setOldName(oldNameFiled);
                        dataFile.setFileUrl(savePath);
                        dataFile.setNewName(name + fileExt);
                        dataFile.setOwerId(microClassId);
                        dataFile.setSeq(seq);
                        dataFile.setStatus(1);
                        dataFileService.saveDataFile(dataFile);
                    }
                    if (index == total) {// 说明已经成功上传一个文件
                        // 根据文件名称和OwerId来更新文件记录,把记录的状态修改为0(有效)
                        dataFileService.updateByNewNameAndOwerId(name+fileExt, microClassId);
                        countFile++;
                        if (countFile == 1) {// 说明已经上传成功一个文件,则吧MicroClass的状态改为0(有效);
                            microClassService.updateMicroClass(microClassId);// 根据microClassId来修改status
                        }
                        LOGGER.info("已上传 " + countFile + " 个文件");
                    }
                    return index++;
                } else {
                    return 0;
                }
            }
            return 0;
        }
    

    FileUtil中randomWrite方法实现

        /**
         * @Description: 分片文件追加
         * @param request
         * @param sliceFile  分片文件
         * @param name   文件名称
         * @param dirType  文件夹类型 如video/audio
         * @param fileExt  文件扩展名 如.mp4/.avi  ./mp3
         * @return
         */
        public static String randomWrite(HttpServletRequest request, byte[] sliceFile, String name, String dirType,String fileExt) {
            try {
                /** 以读写的方式建立一个RandomAccessFile对象 **/
                 //获取相对路径/home/gzxiaoi/apache-tomcat-8.0.45/webapps
                String realPath=getRealPath(request); 
                //拼接文件保存路径 /fileDate/video/2017/08/09  如果没有该文件夹,则创建
                String savePath=getSavePath(realPath,dirType);          
                String realName = name;
                String saveFile =realPath+ savePath + realName+fileExt;
                RandomAccessFile raf = new RandomAccessFile(saveFile, "rw");
                // 将记录指针移动到文件最后
                raf.seek(raf.length());
                raf.write(sliceFile);
                return savePath;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * @Description: 取得tomcat中的webapps目录 如: /home/software/apache-tomcat-8.0.45/webapps
         * @param request
         * @return
         */
        public static String getRealPath(HttpServletRequest request) {
            String realPath = request.getSession().getServletContext().getRealPath(File.separator);
            realPath = realPath.substring(0, realPath.length() - 1);
            int aString = realPath.lastIndexOf(File.separator);
            realPath = realPath.substring(0, aString);
            return realPath;
        }
    
    /**
         * @Description: 获取文件保存的路径,如果没有该目录,则创建
         * @param realPath 相对路径 ,如   /home/software/apache-tomcat-8.0.45/webapps
         * @param fileType  文件类型 如: images/video/audio用于拼接文件保存路径,区分音视频
         * @return
         */
        public static String getSavePath(String realPath, String fileType) {
            SimpleDateFormat year = new SimpleDateFormat("yyyy");
            SimpleDateFormat m = new SimpleDateFormat("MM");
            SimpleDateFormat d = new SimpleDateFormat("dd");
            Date date = new Date();
            String sp=File.separator + "fileDate" + File.separator +fileType + File.separator + year.format(date) + File.separator
                    + m.format(date) + File.separator + d.format(date) + File.separator;
            String savePath = realPath+ sp;
            File folder = new File(savePath);
            if (!folder.exists()) {
                folder.mkdirs();
            }
            return sp;
        }
    
    5. 总结

    本篇文章主要从实际项目出发,介绍了文件上传中所常见的一些情况,以及具体的实现。在断点续传中,需要注意的关键点:

    • 浏览器端(前端)需要获取文件的大小,去计算总分片数,并且需要校验文件的合法性,在上传过程中,需要及时的获取到当前传输文件的当前分片数,以更新下一次需要传输文件的范围大小;
    • 服务器端,需要根据前端传入的参数,去确定分片所属的源文件,并追加;(或者根据一定规则,创建临时目录,将属于同一文件的分片,放在同一目录,在将所有分片全部上传完毕之后<即当前分片数=总分片数>,在合并所有的分片);

    相关文章

      网友评论

        本文标题:无插件实现大文件分片上传,断点续传

        本文链接:https://www.haomeiwen.com/subject/wngtdxtx.html