既然我们知道了切分的碎片有多少片,那么按照已上传的碎片除以总碎片就可以得到进度啦,就顺手算个进度吧。这边感觉好像很复杂的样子,淡定~我只是把界面样式都加进去了~
updateProcess(){ let process=Math.round(this.currentIndex/this.currentSlice.length*100) this.fileProcess.innerHTML=`<span><span></span><b>${process}%</b></span><span>${this.fileSize}</span>` },此外还需注意,文件的单位是字节,这个对于用户来说非常不友好,为了告诉用户文件有多大,我们需要转换一下。这里我是动态的转换,并不是固定一个单位,因为如果一个文件只有几KB,然后我却用G的单位来计算,那么就是满眼的0了。这里可以根据文件大的大小,具体情况具体分析。我这里只给了一个KB和MB的计算。可以自行elseif加条件。
calculateSize(){ let fileSize=this.fileSize/1024; if(fileSize<512){ this.fileSize=Math.round(fileSize)+"KB" } else { this.fileSize=Math.round(fileSize/1024)+"MB" } }, 切分文件逐个上传既然要上传了,那就不得不召唤XMLHttpRequest了。进行AJAX上传文件。上传文件必须要enctype="multipart/form-data",因此还需要请出FormData帮我们创建form表单数据。
先创建一个表单数据吧~,其实我们只需要上传一个file的blob文件就可以了,但是服务器没有这么机智,能够自行给文件加独一无二的标识,所以我们在传文件的时候要加上文件的信息,比如文件名,文件大小,还有文件切分的位置。这个部分就是随意发挥了,看你需要啥就加入啥子段,比如时间啦,用户id啦,巴啦啦~
createFormData(){ let formData = new FormData(); let start=this.currentSlice[this.currentIndex][0] let end=this.currentSlice[this.currentIndex][1] let fileData=this.file.slice(start,end) formData.append("start", start); formData.append("end", end); formData.append("size", this.file.size); formData.append("fileOriName", this.file.name); formData.append("file", fileData); return formData; }终于准备活动做完了,该上传了。这边就是一个标准的XMLHttpRequest的上传模版,有么有很亲切很友好。这边不触及到跨域等那个啥的问题,所以很友好。大家只需在上传成功之后再回调此上传方法。逐个上传。直至最后一个切分。这里为了看出上传的过程,所以我加了一个500ms的延迟,这个仅仅是为了视觉效果,毕竟我只是试了几MB的文件,上传太快了。
createUpload(){ let _=this let formData=this.createFormData() let xhr = new XMLHttpRequest(); xhr.open("post", "/submit", true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4&&parseInt(xhr.status)==200){ _.currentIndex++; if(_.currentIndex<=_.currentSlice.length-1){ setTimeout(()=>{ _.createUpload() },500) }else{ //完成后的处理 } _.updateProcess() } }; xhr.send(formData); } 断点续传Server端的处理方式从上述逻辑来看,这个后端的流程可以分为:
接受文件的数据流,加入Buffer
接受完毕,提取内容
重命名文件名
写入本地
重新从第一步开始获取文件,直至所有切片接受完毕。
接收数据流这估计是整个流程中最简单的部分了,node监听一下,组装一下,搞定!
let buf=[] ctx.req.on("data",(data)=>{ buf.push(data) }); ctx.req.on("end",(data)=>{ if(buf.length>0){ string=Buffer.concat(buf) } }) 提取内容大家还记不记得我们传的是二进制,而且这个二进制除了文本字段,还有文件的二进制。这个时候,我们就需要先提取字段,再将文件和普通文本分开处理。
先拼装分隔符,这边是一个规定,就是content-type中的boundary前面需要加上--。
boundary=ctx.headers["content-type"].split("=")[1] boundary = '--'+boundary上文提到过二进制的分割只能用二进制,因此,我么可以把分隔符变成二进制,然后再分割接收到的内容。
function splitBuffer(buffer,sep) { let arr = []; let pos = 0;//当前位置 let sepPosIndex = -1;//分隔符的位置 let sepPoslen = Buffer.from(sep).length;//分隔符的长度,以便确定下一个开始的位置 do{ sepPosIndex=buffer.indexOf(sep,pos) if(sepPosIndex==-1){ //当sepPosIndex是-1的时候,代表已经到末尾了,那么直接直接一口读完最后的buffer arr.push(buffer.slice(pos)); }else{ arr.push(buffer.slice(pos,sepPosIndex)); } pos = sepPosIndex+sepPoslen }while(-1!==sepPosIndex) return arr }分割完毕之后~就要开始处理啦!把字段都提取出来。这边我们把提取出的内容变成字符串,首先这个是为了判断字段类型,其次如果不是文件,那么可以提取出我们的字段文本,如果是文件类型的,那么就不能任性地toString了,我们需要把二进制的文件内容完美保存下来。
------WebKitFormBoundaryl8ZHdPtwG2eePQ2F Content-Disposition: form-data;; filename="blob" Content-Type: application/octet-streamk 换行*2 乱码 换行*1 ------WebKitFormBoundaryl8ZHdPtwG2eePQ2F--