• js文件处理


    文件处理

    上传文件

    获取文件流

    <div>
        上传文件 : <input type="file" name = "file" id = "fileId" /> 
    </div>
    <script>
        function getFile() {
        //js写法        
        var file=document.getElementById('fileId').files[0];//获取文件流
        var fileName =  file.name;//获取文件名
        var fileSize = file.size;// 获取文件大小byte
        //jq写法
        var file = $('#fileId')[0].files[0]; 
        var filePath = $('#fileId').val(); // 文件路径
        var arr = filePath.split("\");
        var fileName = arr[arr.length-1].split('.')[0];
      }
    

    文件上传

    //上传文件
    function uploadFiles(){                                                         
           var formData = new FormData();
           formData.append("file",$("#uploadFile")[0].files[0]);//append()里面的第一个参数file对应permission/upload里面的参数file                          
            $.ajax({
               type:"post",
               async:true,  //这里要设置异步上传,才能成功调用myXhr.upload.addEventListener('progress',function(e){}),progress的回掉函数
               Accept:'text/html;charset=UTF-8',
               data:formData,
               contentType:"multipart/form-data",
               url: uploadUrl,
               processData: false, // 告诉jQuery不要去处理发送的数据
               contentType: false, // 告诉jQuery不要去设置Content-Type请求头
               xhr:function(){                        
                   myXhr = $.ajaxSettings.xhr();
                   // check if upload property exists
                   if(myXhr.upload){ 
                   		myXhr.upload.addEventListener('progress',function(e){                            
                           var loaded = e.loaded; //已经上传大小情况 
                           var total = e.total;   //附件总大小 
                           var percent = Math.floor(100*loaded/total)+"%"; //已经上传的百分比  
                           console.log("已经上传了:"+percent);                 
                           $("#processBar").css("width",percent);                                                                
                       	}, false); // for handling the progress of the upload
                	}
                   return myXhr;
               },                    
               success:function(data){                      
                   console.log("上传成功!!!!");                        
               },
               error:function(){
                   alert("上传失败!");
               }
           });                             
    }
    

    多文件上传

    多文件上传可采用方案如下

    1.单文件上传(多个input标签,多次传输)
    2.同一目录下多文件一次性(一个input标签,一次传输/多次传输)
    

    递归实现

    //html
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <link rel="stylesheet" type="text/css" href="./css/NewFile.css" rel="external nofollow" >
    <script type="text/javascript" src="./js/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="./js/fileMuti.js"></script>
    </head>
    <body>
    <div id="test">
    <input type="file" id="fileMutiply" name="files" multiple="multiple" >
    </div>
    </body>
    </html>
    
    // js
    
    /**
     * 
     */
    var i=0;
    var j=0;
    $(function(){
      $("#fileMutiply").change(function eventStart(){
        var ss =this.files; //获取当前选择的文件对象
         for(var m=0;m<ss.length;m++){ 
         	//循环添加进度条
            efileName = ss[m].name ;
         	if (ss[m].size> 1024 * 1024){
          		sfileSize = (Math.round(ss[m].size /(1024 * 1024))).toString() + 'MB';
          	}
        	else{
          		sfileSize = (Math.round(ss[m].size/1024)).toString() + 'KB';
          	}
         	$("#test").append(
                "<li id="+m+"file><div class='progress'><div id="+m+"barj class='progressbar'></div></div><span class='filename'>"+efileName+"</span><span id="+m+"pps class='progressnum'>"+(sfileSize)+"</span></li>");
             }
         sendAjax();
         function sendAjax() {
         	//采用递归的方式循环发送ajax请求
            if(j>=ss.length)  { 
             	$("#fileMutiply").val("");
                j=0;
              	return; 
            }
            var formData = new FormData();
            formData.append('files', ss[j]); //将该file对象添加到formData对象中
            $.ajax({
                url:'fileUpLoad.action',
                type:'POST',
                cache: false,
                data:{},//需要什么参数,自己配置
                data: formData,//文件以formData形式传入
                processData : false, 
                //必须false才会自动加上正确的Content-Type 
                contentType : false , 
              	/*  beforeSend:beforeSend,//发送请求
                complete:complete,//请求完成   */  
         		xhr: function(){   //监听用于上传显示进度
                	var xhr = $.ajaxSettings.xhr();
                  	if(onprogress && xhr.upload) {
                   		xhr.upload.addEventListener("progress" , onprogress, false);
                   		return xhr;
                	}
                } ,
                success:function(data){
                	$(".filelist").find("#"+j+"file").remove();//移除进度条样式
                   	j++; //递归条件
                  	sendAjax();
                },
                error:function(xhr){
                 alert("上传出错");
                }               
              });
            } 
      })
        function onprogress(evt){
         var loaded = evt.loaded;   //已经上传大小情况 
         var tot = evt.total;   //附件总大小 
         var per = Math.floor(100*loaded/tot); //已经上传的百分比 
         $(".filelist").find("#"+j+"pps").text(per +"%");
         $(".filelist").find("#"+j+"barj").width(per+"%");
         };
    })
    

    一次性

    //html
    <div class="input-group">
        <input type="file" id="attachment" multiple="multiple">
        <span id="progress_bar" style="color: #1AB394;display: table-cell"></span>
    </div>
    <ul id="attachment_list"></ul>
    <button class="btn btn-file">upload file</button>
    
    // js
    ajaxSetup({   //laravel中的request要带这个header参数
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            }
        });
    
    $('.btn-file').click(function(){
            if($('#attachment').val() == '')
                alert('请选择文件再上传');
            else{
                var path = $('#attachment')[0].files;
                var formData = new FormData();
                var names = '';
                /*
                提示:FormData不能写数组,array json都不行,能写简单的key->value键值对。
                键值对中key不能是中文,不然后台读不出来,而且要保证key的唯一性,
                那么我就用文件名path[i].name用md5加密一下好了,当然你也可以用自己喜欢的加密方式。
                因为laravel不能便利地读取所有file,只能用file('key')读取key值的value,
                是的,所以你不知道key值是读不出你要的东西的。因为文件的key是变化的,所以我这里写定一个info字段,
                然后把文件的key写成字符串,然后后台解析字符串,再根据里面的字段获取文件。
                你也可以写其他需要的数据的键值对到FormData里面,一并传到后台,当成一个虚拟form表单用就行了。
                */
                for(var i= 0,name;i<path.length;i++){
                    name = $.md5(path[i].name);
                    formData.append(name, path[i]);
                    names += name + ',';
                }
                formData.append('info',names);
                $.ajax({
                    url: "{{route('upload')}}",
                    type: 'POST',
                    cache: false,
                    data: formData,
                    processData: false,
                    contentType: false,
                    beforeSend: function(){
                        $('#progress_bar').css('color','#1AB394').show();
                    },
                    success: function(result
                    {
                    $('#progress_bar').html(result.info).css('color','black').fadeOut(3000,function(){$(this).html('')});
                    },
                    error: function (result) {
    
                    },
                    xhr: function(){
                        var xhr = $.ajaxSettings.xhr();
                       if(onprogress && xhr.upload) {
                            xhr.upload.addEventListener("progress" , onprogress, false);
                            return xhr;
                       }
                    }
                });
    /*
    小tips:在网上查找遇到一些方法(例如function A()),没有详细介绍,不知道总共完整传多少个参数,
    每个参数长什么样子的,可以写成function A(a,b,c,d,e,…………){//然后写log打印出来},
    这里只有一个event对象参数,所以我写4个形参上去,然后写日志出来,只有第一个参数写出来是一个对象,
    而且里面有什么属性也会写出来,后面3个形参则输出为空,
    那么这时候就能写定 function A(obj){//只有一个参数,自己写个喜欢的形参名}。
    前端后台都能用这个小技巧哦~
    */
    function onprogress(evt){   
            console.log(evt);
            var loaded = evt.loaded;
            var tot = evt.total;
            $('#progress_bar').html(Math.floor(100*loaded/tot)+'%');
        }
    

    下载文件

    参考

    单文件

    a标签

    <a href="/path/to/img" download="name.png">下载图片</a>
    

    抛开浏览器兼容性,还有几点限制:

    1. href 所指向的地址,必须与当前网站同源,否则可能被浏览器拦截。
    2. 它只能只能使用 GET 请求,直接使用的话,会出现页面跳转或弹窗
    3. [其它限制](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a)
    
    • 通过 js 动态创建 <a> 并设置 download 属性

    原理和限制同上,代码如下:(不支持IE)

    function download(url, name) {
        const aLink = document.createElement('a')
        aLink.download = name 
        aLink.href = url 
        aLink.dispatchEvent(new MouseEvent('click', {}))
    }
    

    以导出 canvas 图片为例:

    // html
    <canvas id="canvas"></canvas>
    
    // js
    const canvas = document.getElementById('canvas')
    download(canvas.toDataURL('image/png'), 'name.png')
    

    form

    提交<form>表单,若action指向的是文件,亦可以发起下载,定义属性method="POST"即可发起POST请求,显然是比上面更优的方案。同样会出现页面跳转或弹窗。

    iframe

    页面跳转或弹窗严重影响用户体验,它打断用户正在处理的事情,我们希望下载请求是被静默处理的。<a>标签和<form>都有target属性,可在指定iframe打开链接。既然资源是用来下载的,打开的页面对用户毫意义,那么可以把用来打开文件链接的iframe隐藏,完成下载后销毁,即可实现异步下载。
    更进一步,我们希望我们的下载逻辑是可复用的,不需要写特定的HTML结构,只需要指定URL和对象参数,为文件下载服务的<a><form>iframe等标签在需要时被静默创建不需要时自动销毁,并有一个错误处理机制。

    限制

    - document.execCommand('SaveAs') 中 SaveAs 是个非标准值,主要用来兼容 ie 不支持 <a> 标签 download 属性的场景
    - window.frames["iframeName"].document 受到同源策略的影响,如果图片地址跨域,是无法访问的 <frame> 的属性和方法
    

    示例

    $(function(){
    	//二维码
    	(function(){
    		var img_src = $('.qr_img')[0].src;
    		if(browserIsIe()){//假如是ie浏览器
    			$('.down_qr').on('click',function(){
    				img_src = $('.qr_img')[0].src;
    				DownLoadReportIMG(img_src);
    			});
    		}else{
    			$('.down_qr').attr('download',img_src);
    			$('.down_qr').attr('href',img_src);
     
    			$('.sutmit_btn').on('click',function(){
    				$('.down_qr').attr('download',img_src);
    				$('.down_qr').attr('href',img_src);
    			});
    		}
    		
    	})();
    });
     
    function DownLoadReportIMG(imgPathURL) {
        //如果隐藏IFRAME不存在,则添加
        if (!document.getElementById("IframeReportImg"))
            $('<iframe style="display:none;" id="IframeReportImg" name="IframeReportImg" onload="DoSaveAsIMG();" width="0" height="0" src="about:blank"></iframe>').appendTo("body");
        if (document.all.IframeReportImg.src != imgPathURL) {
            //加载图片
            document.all.IframeReportImg.src = imgPathURL;
        }
        else {
            //图片直接另存为
            DoSaveAsIMG();
        }
    }
    function DoSaveAsIMG() {
        if (document.all.IframeReportImg.src != "about:blank")
            window.frames["IframeReportImg"].document.execCommand("SaveAs");
    }
    //判断是否为ie浏览器
    function browserIsIe() {
        if (!!window.ActiveXObject || "ActiveXObject" in window)
            return true;
        else
            return false;
    }
    

    Blob

    直接使用类似$.ajax等 AJAX 下载文件是不行的。<a><form><iframe>标签发起的请求,响应是直接由浏览器处理,浏览器识别为下载内容时会触发下载功能,而AJAX(XHR)请求是交给 JavaScript 处理,响应只能是字符串,即使是二进制流,依然会转换成字符串传递给回调函数,因此不会触发下载功能,这就是我使用大篇幅介绍如何封装<a><form><iframe>标签进行下载的原因。
    得益于 HTML5 的 XHR2 与 Blob 接口,现在可以处理 Blob 响应,除此之外还需要一些技术支持,当然最终还是通过<a>标签触发下载。

    Blob 是对大数据块的不透明引用或者句柄,在 JavaScript 中,Blob 通常表示二进制数据。在使用 Blob 之前,首先要知道怎么获取 Blob。获取 Blob 有很多途径,这里讨论的是下载,因此只分析从WEB中下载 Blob,而从 WEB 中下载 Blob,需要 XHR2 的支持。

    上面提到的 AJAX 特指使用 XHR。相对 XHR2,老 XHR 存在一些缺点,具体可见阮一峰老师的XMLHttpRequest Level 2 使用指南,这里要说的是老XHR不支持上传和下载二进制数据,喜闻乐见,XHR2 支持了,可以通过设置 XHR 实例responseType = 'blob'接收二进制数据。

    有了上面条件还不够,拿到了 Blob,上面说了最终还是通过<a>标签触发下载,那么必须把有一个把 Blob 转换成”Blob URL“的方法才能供<a>标签打开,它就是 HTML5 新方法window.URL.createObjectURL。注意这不是普通 URL,直接打开 Blob URL 跳转的是乱码而不是触发下载,正确使用 Blob URL 的方式就是<a>标签得支持 HTML5 的新属性download

    假设上面通通被支持,那么我们开始写代码,由于这里讨论的是应用级别的方案,就不使用原生 XHR2 写代码了,原生实现可以移步到上面阮老师的文章。jQuery 的 AJAX 不支持 Blob 响应,这里使用了axios做演示。

    文件格式

    //文件下载
    var blob = new Blob([要保存的文件流], { type: 'application/octet-stream' }),
    //filename,摘取了常用的部分,其实还有其他一些mimetypes = array(
    //    'doc'        => 'application/msword',
    //    'bin'        => 'application/octet-stream',
    //    'exe'        => 'application/octet-stream',
    //    'so'        => 'application/octet-stream',
    //    'dll'        => 'application/octet-stream',
    //    'pdf'        => 'application/pdf',
    //    'ai'        => 'application/postscript',
    //    'xls'        => 'application/vnd.ms-excel',
    //    'ppt'        => 'application/vnd.ms-powerpoint',
    //    'dir'        => 'application/x-director',
    //    'js'        => 'application/x-javascript',
    //    'swf'        => 'application/x-shockwave-flash',
    //    'xhtml'        => 'application/xhtml+xml',
    //    'xht'        => 'application/xhtml+xml',
    //    'zip'        => 'application/zip',
    //    'mid'        => 'audio/midi',////    'midi'        => 'audio/midi',
    //    'mp3'        => 'audio/mpeg',
    //    'rm'        => 'audio/x-pn-realaudio',
    //    'rpm'        => 'audio/x-pn-realaudio-plugin',
    //    'wav'        => 'audio/x-wav',
    //    'bmp'        => 'image/bmp',
    //    'gif'        => 'image/gif',
    //    'jpeg'        => 'image/jpeg',
    //    'jpg'        => 'image/jpeg',
    //    'png'        => 'image/png',
    //    'css'        => 'text/css',
    //    'html'        => 'text/html',
    //    'htm'        => 'text/html',
    //    'txt'        => 'text/plain',
    //    'xsl'        => 'text/xml',
    //    'xml'        => 'text/xml',
    //    'mpeg'        => 'video/mpeg',
    //    'mpg'        => 'video/mpeg',
    //    'avi'        => 'video/x-msvideo',
    //    'movie'        => 'video/x-sgi-movie',
    //);
    fileName = 'filename' + path.substring(path.lastIndexOf("."), path.length);
    downFile(blob, fileName);
    
    //js下载文件流
    function downFile(blob, fileName) {
        // IE浏览器
        if (window.navigator.msSaveOrOpenBlob) {
            navigator.msSaveBlob(blob, fileName);
        } else {
            // 其他浏览器
            var link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = fileName;
            link.click();
            window.URL.revokeObjectURL(link.href);
        }
    }
    

    vue中使用

    <template>
    	<el-button @click="downloadUrl">导出Excel</el-button>        
    </template>
    <script>
    import api from "../../api/api.js";
    import axios from "axios";
    export default {
      data() {
        return {
    		pageSize:30,
    	}
      },
      created() {},
      computed: {},
      mounted() {},
      methods: {
        downloadUrl() {
          // console.log(api.serverUrl);
          let params = {
            pageNum: "1",
            pageSize: this.pageSize,
            companyId: JSON.parse(sessionStorage.getItem("companyId"))
          };
          this.download_accountsDetails_info(params);
        },
        download_accountsDetails_info(params) {
          return new Promise((resolve, reject) => {
            axios
              .get(api.serverUrl + "/order/exportOrder", {
                params: params,
                // 1.首先设置responseType对象格式为 blob:
                responseType: "blob" 
              })
              .then(
                res => {
                  //resolve(res)
                  // 2.获取请求返回的response对象中的blob 设置文件类型,这里以excel为例
                  let blob = new Blob([res.data], {
                    type: "application/vnd.ms-excel"
                  }); 
                  // 3.创建一个临时的url指向blob对象
                  let url = window.URL.createObjectURL(blob); 
    
                  // 4.创建url之后可以模拟对此文件对象的一系列操作,例如:预览、下载
                  let a = document.createElement("a");
                  a.href = url;
                  a.download = "导出表格.xlsx";
                  a.click();
                  // 5.释放这个临时的对象url
                  window.URL.revokeObjectURL(url);
                },
                err => {
                  resolve(err.response);
                }
              )
              .catch(error => {
                reject(error);
              });
          });
        },
    };
    </script>
    <style>
    </style>
    

    批文件

    • iframe

    勾选多个文件点击本地下载,一次性弹出所有下载窗口

    $('body').on('click', "#Download",function(){//点击下载按钮
        let triggerDelay = 100;
        let removeDelay = 1000;
        let url_arr=[];
        //多个file文件选择checkbox
        $('input[name="filePath"]:checked').each(function(){
            url_arr.push($(this).val());//取到下载url
        });
        url_arr.forEach(function(item,index){
            _createIFrame(item, index * triggerDelay, removeDelay);
        })
        function _createIFrame(url, triggerDelay, removeDelay) {
            //动态添加iframe,设置src,然后删除
            setTimeout(function() {
                var frame = $('<iframe style="display: none;" class="multi-download"></iframe>');
                frame.attr('src', url);
                $(document.body).after(frame);
                setTimeout(function() {
                    frame.remove();
                }, removeDelay);
            }, triggerDelay);
        }
      }
    
    • 压缩为单文件
    在页面把要下载文件的ID传给后台,后台用这些ID,找到文件,生成一个压缩包,然后把压缩包返回给浏览器。如果文件比较大,这里可以考虑做成异步。
    

    判断文件下载成功

    参考

    最近在做的一个项目碰到一个问题,就是需要检测什么时候导出成功并根据导出成功进行提示操作。开始是直接使用location.href,但是无法检测到是否下载成功。

    经过查找资料下载文件使用iframe可以做到,但是onload可以检测到页面加载 成功,无法检测到下载加载成功。经过搜索资料发现一个解决,方案。原理如下:

    在点击下载按钮时获得一个时间戳并把时间戳发送给后台,后台响应成功后把发送的时间戳设置为cookie值,前端实时监测cookie值和前端的时间戳是否相等,相等就说明文件下载成功。

    function exportExcelForm(obj){
                var timer,flag=0;
                var _self=obj._self;
                var downloadToken=obj.time,
                        url=obj.url,
                        triggerDelay = 1000;
                _self.classList.add('disabled');
                _self.innerHTML='导出中...';
                timer=setTimeout(function() {
                    function checkToken(){
                        var timerll=setInterval(function(){
                            var sertoken=getCookie(obj.key);
                            if(sertoken&&(sertoken==downloadToken)){
                                clearTimeout( downloadTimer );
                                clearInterval( timerll );
                                frame.remove();
                                _self.classList.remove('disabled');
                                _self.innerHTML=obj.text;
                                clearCookie(obj.key);
                                flag=0;
                            }else if(sertoken){
                                alert(obj.text+'失败');
                                clearTimeout( downloadTimer );
                                clearInterval( timerll );
                                clearCookie(obj.key);
                                _self.classList.remove('disabled');
                                _self.innerHTML=obj.text;
                            }
                        },100);
                    }
                    if(!flag){
                        flag=1;
                        var frame=$('<iframe />').attr('src', url).attr('id','iframe_download_report').hide().appendTo('body');
                        var downloadTimer=setTimeout(checkToken,1000);
                    }
    
                }, triggerDelay);
            }
          
    
        $('#exportNewStock').on('click',function(){
                    var _self=this;
                    var downloadToken=+new Date();
                    var url =maochao+'stock/exportFinalStockExcelAllotNew.xlsx?brandname='+brandName+'&statisticsdate='+time+'&stockoutDay='+ditchCode+'&value='+downloadToken;
                    var obj={
                        _self:_self,
                        time:downloadToken,
                        url:url,
                        text:'导出新仓调拨模板',
                        key:'excel_new'
                    }
                    exportExcelForm(obj);
                });
    

    参考二

    _iframeDownLoad:function(){
                var timer,_this=this,flag=0;
                $(document).on('click','.J_DownLog',function(){
                    var downloadToken=_this._setFormToken(),
                        url=this.href+"&downloadToken="+downloadToken,
                        triggerDelay = 1000,
                        btn=$(this);
    
                    _this._disableLink(btn,"导出中...");
                    clearTimeout(timer);
                    
                    timer=setTimeout(function() {
                        function checkToken(){
                            sertoken=_this._getCookie( "downloadToken" );
                            if(sertoken==downloadToken){
                                    clearTimeout( downloadTimer );
                                    _this._expireCookie( "downloadToken" );
                                    frame.remove();
                                    _this._enableLink(btn,"导出日志");
                                    flag=0;
                            }else{
                                checkToken();
                            }
                        }
                        if(!flag){
                            flag=1;
                            var frame=$('<iframe />').attr('src', url).attr('id','iframe_download_report').hide().appendTo('body'); 
                            var downloadTimer=setTimeout(checkToken,1000);                        
                        }
    
                    }, triggerDelay);
                    return false;
                    
                });
            }
    

    断点续传

    原理参考

    原理

    • 简述原理

    断点续传说白了就是将一个文件按照一定的规则人为的分割成多个小文件,然后客户端每次只上传一个小文件(当然我们也可以利用多线程技术每次上传多个小文件),服务器接收到上传过来的小文件后根据一定的规则来组合这些小文件。如果在上传过程中出现网络中断等意外情况,下次再次上传时可以从已经上传的部分继续上传,而不是重新上传。

    • 详细讲解

    从 HTTP1.1 协议开始就已经支出获取文件的部分内容,断点续传技术就是利用 HTTP1.1 协议的这个特点在 Header里添加两个参数来实现的。这两个参数分别是客户端请求时发送的Range 和服务器返回信息时返回的 Content-Range - RangeRange 用于指定第一个字节和最后一个字节的位置,格式如下:

    Range:(unit=first byte pos)-[last byte pos]
    

    Range 常用的格式有如下几种情况:

    Range:bytes=0-1024 ,表示传输的是从开头到第1024字节的内容;
    Range:bytes=1025-2048 ,表示传输的是从第1025到2048字节范围的内容;Range:bytes=-2000 ,表示传输的是最后2000字节的内容;
    Range:bytes=1024- ,表示传输的是从第1024字节开始到文件结束部分的内容;Range:bytes=0-0,-1 表示传输的是第一个和最后一个字节 ;
    Range:bytes=1024-2048,2049-3096,3097-4096 ,表示传输的是多个字节范围。
    

    Content-Range Content-Range 用于响应带有Range 的请求。服务器会将 Content-Range 添加在响应的头部,格式如下:

    Content-Range:bytes(unit first byte pos)-[last byte pos]/[entity length]
    

    常见的格式内容如下:

    Content-Range:bytes 2048-4096/10240
    

    这里边 2048-4096 表示当前发送的数据范围, 10240 表示文件总大小。

    这里我顺便说一下,如果在客户端请求报文头中,对 Range 填入了错误的范围值,服务器会返回 416 状态码。416 状态码表示服务器无法处理所请求的数据区间,常见的情况是请求的数据区间不在文件范围之内,也就是说,Range 值,从语法上来说是没问题的,但从语义上来说却没有意义。

    注意:当使用断点续传的方式上传下载软件时 HTTP 响应头将会变为:

    HTTP/1.1 206 Partial Content
    

    当然光有 Range 和 Content-Range 还是不够的,我们还要知道服务端是否支持断点续传,只需要从如下两方面判断即可:

    判断服务端是否只 HTTP/1.1 及以上版本,如果是则支持断点续传,如果不是则不支持服务端返回响应的头部是否包含 Access-Ranges ,且参数内容是 bytes 符合以上两个条件即可判定位支持断点续传。

    • 校验

    这里的校验主要针对断点续传下载来说的。当服务器端的文件发生改变时,客户端再次向服务端发送断点续传请求时,数据肯定就会发生错误。这时我们可以利用Last-Modified来标识最后的修改时间,这样就可以判断服务器上的文件是否发生改变。

    Last-Modified具有同样功能的还有if-Modified-Since,它俩的不同点是 Last-Modified由服务器发送给客户端,而if-Modified-Since 是由客户端发出, if-Modified-Since 将先前服务器发送给客户端的Last-Modified 发送给服务器,服务器进行最后修改时间验证后,来告知客户端是否需要重新从服务器端获取新内容。客户端判断是否需要更新,只需要判断服务器返回的状态码即可,206 代表不需要重新获取接着下载就行,200代表需要重新获取。

    但是 Last-Modifiedif-Modified-Since 存在一些问题:

    某些文件只是修改了修改时间而内容却没变,这时我们并不希望客户端重新缓存这些文件;

    某些文件修改频繁,有时一秒要修改十几次,但是 if-Modified-Since 是秒级的,无法判断比秒更小的级别; 部分服务器无法获得精确的修改时间。 要解决上述问题我们就需要用到 Etag ,只需将相关标记(例如文件版本号等)放在引号内即可。

    当使用校验的时候我们不需要手动实现验证,只需要利用 if-Range 结合 Last-Modified 或者 Etage 来判断是否发生改变,如果没有发生改变服务器将向客户端发送剩余的部分,否则发送全部。

    注意:If-Range 必须与 Range 配套使用。缺少其中任意一个另一个都会被忽略。

    • 秒传

    秒传利文件的MD5,首先将文件的MD5发送个服务器,服务器传输过来的MD5判断服务器上是否存在相同类型的文件,如果存在就将文件复制一份,而不是本地上传。这样就是秒传功能。

    • MD5

    秒传涉及到了MD5,那么什么MD5呢?MD5的英文全称是 Message-Digest Algorith 5 ,是计算机广泛使用的算法之一。 MD5会为文件产生唯一的“指纹”,任何改动都会改变文件指纹。它以 512位分组来处理信息,每个分组又被分为16个32位分组,经过处理后输出4个32位分组,最后将输出的4个32位分组进行级联生成128位散列值。

    MD5的具有压缩性、易计算、抗修改、弱抗碰撞和强抗碰撞。下面我们一一来讲解:

    压缩性:任意长度数据,生成的MD5值长度是固定的;

    易计算:可以很方便的从原始数据计算出MD5;

    抗修改:对原始数据的任何修改,都会改变MD5;

    弱抗碰撞和强抗碰撞:很难找到具有相同MD5的数据。

    实现

    实现参考

    • 后端接口提供

    img

    其实原理也很简单说白了,就是我们把本地的大型文件或者视频使用slice进行分割,然后传给后台,同时需要提供给后台当前分割的索引和一共要分成多少份。

    • 前端代码部署

    点击上传按钮方法

    filesVide() {
       // 视频上传
       this.datafile=this.$refs.file.files[0];
          // 上传的文件的大小
          this.size=(this.datafile.size/1024/1024).toFixed(1);
          this.filesize = this.datafile.size;
          this.fileName = this.datafile.name;
          // 判断后缀
          var index=this.datafile.name.lastIndexOf('.');
          var type1=this.datafile.name.slice(index+1);
          // 进行判断是不是mp4格式,如果不是进行提示处理
          if(type1!=='mp4'||this.filesize>1024*1024*500){
            // 提示处理
            this.MP4sp=true;
            this.clearTimers();
            this.$refs.file.value='';
            return;
          }
          // 以下为一些提示处理(可以先不用考虑)
          this.cut=true;
          this.videcut=false;
          this.cutvide=false;
          this.index=0;
          this.baifenbi=0;
          this.suo=true;
          this.start=0;
          this.end=0;
          // 一共多少个片段戳(bytesPerPiece=1024*1024表示每次上传的大小为1m)
          this.total = Math.ceil(this.filesize / this.bytesPerPiece);
          // 时间戳
          this.timestamp=new Date().getTime();    
          // 进行上传的代码片段
          this.pianduanfiles();
        },
    

    进行循环执行的代码

    pianduanfiles(){
          // 用于判断是否已经结束
          if(this.start < this.filesize&&this.suo){
            // 开始的数量加上每次上传的数量(bytesPerPiece=1024*1024)每次上传1m
            this.end = this.start + this.bytesPerPiece;
            // 如果结束时的大小大于文件大小,说明上传完毕
            if(this.end > this.filesize) {
              this.end = this.filesize;
            }
            // 对开始的值和结束的值就行切割,每次循环开始的值和结束的值默认加1m
            this.chunk = this.datafile.slice(this.start,this.end);//切割文件   
            // 方法执行 
            var  params  =  new  FormData();
            params.append('video', this.chunk);
            params.append('roadShowId',  this.information);
            params.append('fileName',  this.fileName);
            params.append('index', this.index);
            params.append('total',  this.total);
            params.append('timestamp',  this.timestamp);
            axios.post(this.HTTP + 'roadshows/video/investor/add', params , {
              headers: {
                'auth-signature': this.token
              }
            }).then(res => {
              // 调用成功后让结束的值等于开始的值,同时判断是否结束续传如果是300继续循环执行该方法,如果200说明结束
              this.start=this.end;
              // 当前索引加一
              this.index++;
              // 上传视频进行百分比编写
              this.baifenbi=Math.round((this.index/this.total)*100)
              if(res.data.code==300){
                console.log(res,'300')
                // setTimeout(()=>{
                  this.pianduanfiles();
                // },100)
              }
              if(res.data.code==200){
                this.cutvide=true;
                this.url=res.data.data.path;
                console.log(res,'200')
                // this.load()  
              }
              if(res.data.code==4005){
                alert('当前房间未开放视频上传功能!')
              }
            }, err => {
              console.log(err);
            })
          }
        },
    
  • 相关阅读:
    nodejs内存溢出解决方法
    mongodb副本集与分片结合
    node加密
    守护进程,互斥锁,IPC,队列,生产者与消费者模型
    并发编程
    粘包问题
    socket编程
    xml模块
    网络编程
    异常处理
  • 原文地址:https://www.cnblogs.com/fhkankan/p/13538933.html
Copyright © 2020-2023  润新知