当前位置: 首页 > 科技观察

前端实现断点续传的文件传输

时间:2023-03-15 09:52:52 科技观察

我听说文件的可恢复传输很长时间了。前端还可以实现文件的断点续传。较旧的浏览器没有很高的支持。本文以简单的断点续传(前端文件提交+后端PHP文件接收)为例。为了理解大致的实现过程,我们先以图片为例。来看看***外观1.一些知识准备断点续传。既然有断点,就应该有一个文件分割的过程,一段一段的传输。以前不能分割文件,但是随着HTML5新特性的引入,类似于普通字符串和数组的分割,我们可以使用slice方法来分割文件。因此,断点续传最基本的实现是:前端通过FileList对象获取对应的文件,将大文件按照指定的切分方式切段,然后逐段传给后端,以及后端按顺序逐段传输文件。文件被拼接。而我们需要在提交前修改FileList对象。在上一篇文章中,我们知道了这种提交的一些注意事项。因为不能直接改变FileList对象,所以不能直接通过表单的.submit()方法上传提交。它需要结合FormData对象生成一个新的数据,并通过Ajax上传。2、实现过程本例实现了文件恢复上传的基本功能,但是手动“暂停上传”操作没有成功实现。您可以在上传过程中刷新页面,模拟上传中断,体验“续传”,可能还有一些其他的小bug,但基本逻辑大致相同。1.前端实现先选中文件,列出选中的文件列表信息,然后可以自定义上传操作(1)所以首先设置页面DOM结构文件名文件类型文件大小上传进度CSS样式抛出outbody{font-family:Arial;}form{margin:50pxauto;width:600px;}input[type="button"]{cursor:pointer;}table{display:none;margin-top:15px;border:1pxsolid#ddd;边境公司llapse:collapse;}tableth{color:#666;}tabletd,tableth{padding:5px;border:1pxsolid#ddd;text-align:center;font-size:14px;}(2)接下来是JS的实现分析通过FileList对象,我们可以获得文件的一些信息。大小是文件的大小。文件的划分和分段就靠这个了。这里的大小是字节数。所以在界面显示文件大小的时候,可以这样换算//计算文件大小size=file.size>1024?file.size/1024>1024?file.size/(1024*1024)>1024?(file.size/(1024*1024*1024)).toFixed(2)+'GB':(file.size/(1024*1024)).toFixed(2)+'MB':(file.size/1024).toFixed(2)+'KB':(file.size).toFixed(2)+'B';选择文件后,显示文件信息,替换模板中的数据//更新文件信息列表uploadItem.push(uploadItemTpl.replace(/{{fileName}}/g,file.name).replace('{{fileType}}',file.type||file.name.match(/\.\w+$/)+'file').replace('{{fileSize}}',size).replace('{{progress}}',progress).replace('{{totalSize}}',file.size).replace('{{uploadVal}}',uploadVal));但是在显示文件信息的时候,这个文件可能是之前上传过的。为了断点续传,需要在界面上判断并提示,通过查询本地是否有对应的数据(这里的方法是本地记录100%上传时,直接重新上传而不是继续上传)//初始通过本地记录判断文件是否曾经上传过percent=window.localStorage.getItem(file.name+'_p');if(percent&&percent!=='100.0'){progress='uploaded'+percent+'%';uploadVal='continuetoupload';}显示文件信息列表,点击开始上传,即可上传相应的文件。上传文件时,需要对文件进行拆分例如这里配置的每个segment为1024B,totalchunkssegment(用于判断是否是最后一个segment),第一个chunksegment,当前上传的百分比等。需要提到的是暂停上传的操作。事实上,我还没有实现它。出来了,不能无奈的停顿……接下来就是切分过程//设置切分的起止点varblobFrom=chunk*eachSize,//切分开始blobTo=(chunk+1)*eachSize>totalSize?totalSize:(chunk+1)*eachSize,//段结束percent=(100*blobTo/totalSize).toFixed(1),//上传百分比timeout=5000,//超时fd=newFormData($('#myForm')[0]);fd.append('theFile',findTheFile(fileName).slice(blobFrom,blobTo));//分段文件fd.append('fileName',fileName);//文件名fd.append('totalSize',totalSize);//文件总大小fd.append('isLastChunk',isLastChunk);//是否是最后一段fd.append('isFirstUpload',times==='first'?1:0);//是否是第一段(第一次上传)//检查上传之前是否已经上传chunk=window.localStorage.getItem(fileName+'_chunk')||0;chunk=parseInt(chunk,10);文件应该支持覆盖上传,所以如果现在上传文件,需要重新设置数据支持覆盖(或者后端会直接追加blob数据)//如果第一次上传是最后一个片段,也就是文件已上传,则重新覆盖上传if(times==='first'&&isLastChunk===1){window.localStorage.setItem(fileName+'_chunk',0);chunk=0;isLastChunk=0;}这个时间其实是一个参数,因为上一段传输完之后还需要传输下一段,所以这里的方法就是在回调中继续调用这个upload操作之后是真正的文件上传操作,使用Ajax上传,因为使用了FormData对象,所以不要忘记在$.ajax({}中添加这个配置processData:false来上传一段,并通过返回结果判断上传是否完成,是否继续上传success:function(rs){rs=JSON.parse(rs);//上传成功if(rs.status===200){//记录上传成功的百分比uploadedwindow.localStorage.setItem(fileName+'_p',percent);//上传完成if(chunk===(chunks-1)){$progress.text(msg['done']);$this.val('uploaded').prop('disabled',true).css('cursor','not-allowed');if(!$('#upload-list').find('.upload-item-btn:not(:disabled)').length){$('#upload-all-btn').val('uploaded').prop('disabled',true).css('游标','不允许');}}else{//记录已经上传的片段window.localStorage.setItem(fileName+'_chunk',++chunk);$progress.text(msg['in']+percent+'%');//设置像这样可以暂停,但是动态设置点击后不能暂停..//if(chunk==10){//isPaused=1;//}console.log(isPaused);if(!isPaused){开始上传();}}}//上传失败,上传失败有很多种情况,具体设置elseif(rs.status===500){$progress.text(msg['failed']);}},error:function(){$progress.text(msg['失败']);}继续上传下一个片段时,进行递归操作,上传下一个片段以抓图。这就是完整的JS逻辑。代码有点注释,应该不难理解哈哈//全部上传操作$(document).on('click','#upload-all-btn',function(){//没有选择文件if(!$('#myFile').val()){$('#myFile').focus();}//模拟点击其他可上传文件else{$('#upload-list.upload-item-btn').each(function(){$(this).click();});}});//选择文件-显示文件信息$('#myFile').change(function(e){varfile,uploadItem=[],uploadItemTpl=$('#file-upload-tpl').html(),size,percent,progress='未上传',uploadVal='上传开始';for(vari=0,j=this.files.length;i1024?file.size/1024>1024?file.size/(1024*1024)>1024?(file.size/(1024*1024*1024)).toFixed(2)+'GB':(file.size/(1024*1024)).toFixed(2)+'MB':(file.size/1024).toFixed(2)+'KB':(file.size).toFixed(2)+'B';//最初通过本地记录判断文件是否上传过percent=window.localStorage.getItem(file.name+'_p');if(percent&&percent!=='100.0'){progress='uploaded'+percent+'%';uploadVal='继续上传';}//更新文件信息列表uploadItem.push(uploadItemTpl.replace(/{{fileName}}/g,file.name).replace('{{fileType}}',file.type||file.name.match(/\.\w+$/)+'文件').replace('{{fileSize}}',size).replace('{{progress}}',progress).replace('{{totalSize}}',file.size).replace('{{uploadVal}}',uploadVal));}$('#upload-list').children('tbody').html(uploadItem.join('')).end().show();});/***上传文件时,提取对应的匹配文件项*@param{String}fileName需要匹配文件名*@return{FileList}匹配文件项*/functionfindTheFile(fileName){varfiles=$('#myFile')[0].files,theFile;for(vari=0,j=files.length;itotalSize?totalSize:(chunk+1)*eachSize,//段结束percent=(100*blobTo/totalSize).toFixed(1),//上传百分比timeout=5000,//超时fd=newFormData($('#myForm')[0]);fd.append('theFile',findTheFile(fileName).slice(blob来源,blobTo));//分段文件fd.append('fileName',fileName);//文件名fd.append('totalSize',totalSize);//文件总大小fd.append('isLastChunk',isLastChunk);//是否是最后一段fd.append('isFirstUpload',times==='first'?1:0);//是否是第一段(***upload)//上传$.ajax({type:'post',url:'/fileTest.php',data:fd,processData:false,contentType:false,timeout:timeout,success:function(rs){rs=JSON.parse(rs);//上传successfulif(rs.status===200){//记录上传的百分比window.localStorage.setItem(fileName+'_p',percent);//上传成功if(chunk===(chunks-1)){$progress.text(msg['done']);$this.val('alreadyuploaded').prop('disabled',true).css('cursor','not-allowed');if(!$('#upload-list').find('.upload-item-btn:not(:disabled)').length){$('#upload-all-btn').val('已上传').prop('disabled',true).css('cursor','not-allowed');}}else{//记录已经上传的片段window.localStorage.setItem(fileName+'_chunk',++chunk);$progress.text(msg['in']+percent+'%');//这个设置可以暂停,但是动态设置点击后不能暂停..//if(chunk==10){//isPaused=1;//}console.log(isPaused);if(!isPaused){startUpload();}}}//上传失败,上传失败的情况很多,根据实际情况而定设置elseif(rs.status===500){$progress.text(msg['failed']);}},error:function(){$progress.text(msg['failed']);}});}});2。后端实现这里的后端实现比较简单,主要依赖于file_put_contents和file_get_contents这两个方法。注意通过FormData对象上传的文件对象在PHP中也是通过$_FILES从全局对象中获取的,并且为了避免上传后文件出现汉字乱码,使用iconv继续上传支持文件覆盖,所以if($isFirstUpload=='1'&&file_exists('upload/'.$fileName)&&filesize('upload/'.$fileName)==$totalSize){unlink('upload/'.$fileName);}使用以上两种方式添加文件信息,别忘了加上参数FILE_APPEND~//继续添加文件数据if(!file_put_contents('upload/'.$fileName,file_get_contents($_FILES['theFile']['tmp_name']),FILE_APPEND)){$status=501;}else{//当正在上传***分片,检查文件是否完整(大小一致)if($isLastChunk==='1'){if(filesize('upload/'.$fileName)==$totalSize){$status=200;}否则{$status=502;}}else{$status=200;}}一般传输后需要校验文件,这里简单校验一下文件大小是否一致。根据不同的实际需要,有不同的错误处理方式。在这里,我们不会处理完整的PHP部分0){$status=500;}else{//这里是一般的文件上传操作//if(!move_uploaded_file($_FILES['theFile']['tmp_name'],'upload/'.$_FILES['theFile']['name'])){//$status=501;//}else{//$status=200;//}//下面是文件恢复操作//如果第一次上传时文件已经存在,则删除文件重新上传if($isFirstUpload=='1'&&file_exists('upload/'.$fileName)&&filesize('upload/'.$fileName)==$totalSize){unlink('upload/'.$fileName);}//否则继续追加文件数据if(!file_put_contents('upload/'.$fileName,file_get_contents($_FILES['theFile']['tmp_name']),FILE_APPEND)){$status=501;}else{//上传***分片时检查文件是否完整(大小一致)if($isLastChunk==='1'){if(文件大小('上传/'.$fileName)==$totalSize){$status=200;}else{$status=502;}}else{$status=200;}}}echojson_encode(array('status'=>$status,'totalSize'=>filesize('upload/'.$fileName),'isLastChunk'=>$isLastChunk));?>先到这里~