大文件文件用户上传优化。那么应该怎么做才能快速上传,即使再次发送失败,也可以从上次中断的地方继续上传呢?下面就为你揭晓答案~温馨提示:最好结合Demo源码阅读。整体思路的第一步是结合项目背景,考察更优化的方案。文件上传失败是个老生常谈的问题。常见的解决方案是将一个大文件切分成多个小文件,请求接口并行上传。所有的请求都响应后,在服务器端合并所有的碎片文件。当分片上传失败时,重新上传时可以判断,只上传上次失败的部分,减少用户等待时间,减轻服务器压力。这是分段上传文件。如何分段上传大文件?流程图如下:分为以下几个步骤来实现:1、文件MD5加密MD5是文件的唯一标识,可以通过文件的MD5来查询文件的上传状态。根据文件的修改时间、文件名、最后修改时间等信息,通过spark-md5生成文件的MD5。需要注意的是,大型文件需要分片读取,将读取的文件内容加入到spark-md5的hash计算中,直到文件读取完毕,最后将最终的hash码返回给回调函数。这里可以根据需要添加文件读取的进度条。实际方法如下://修改时间+文件名称+最后修改时间-->MD5md5File(file){returnnewPromise((resolve,reject)=>{letblobSlice=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSliceletchunkSize=file.size/100letchunks=100letcurrentChunk=0letspark=newSparkMD5.ArrayBuffer()letfileReader=newFileReader()fileReader.onload=function(e){控制台。log('readchunknr',currentChunk+1,'of',chunks)spark.append(e.target.result)//添加数组缓冲区currentChunk++if(currentChunk=file.size?file.size:start+chunkSizefileReader.readAsArrayBuffer(blobSlice.call(file,start,end))}loadNext()})}2、查询文件状态前端得到文件的MD5后,查询是否有背景中名为MD5的文件夹。如果存在,则列出该文件夹下的所有文件,并获取上传的切片列表。如果不存在,则上传切片列表为空.ceil(fileSize/chunkSize)returnnewPromise(async(resolve,reject)=>{constparams={fileName:fileName,fileMd5Value:fileMd5Value,}const{ok,data}=awaitservices.checkFile(params)if(ok){this.hasUploaded=data.chunkList.lengthuploadProgress(file)resolve(data)}else{reject(ok)onError()}})}3.文件分片文件上传优化的核心是文件分片。Blob对象中的slice方法可以切割文件。File对象继承自Blob对象,所以File对象也有slice方法。定义每个分片文件的size变量为chunkSize,通过文件大小FileSize和分片大小chunkSize得到分片chunk的个数,使用for循环和file.slice()方法对文件进行分片,序号为0-n,并比较上传的切片列表,得到所有未上传的切片,推送到请求列表requestList。asynccheckAndUploadChunk(file,fileMd5Value,chunkList){let{chunks,upload}=thisconstrequestList=[]for(leti=0;i复制代码-1//如果已经存在,则无需上传当前区块if(!exit){requestList.push(upload(i,fileMd5Value,file))}}console.log({requestList})constresult=requestList.length>0?awaitPromise.all(requestList).then(result=>{console.log({result})returnresult.every(i=>i.ok)}).catch(err=>{returnerr}):true控制台.log({result})returnresult===true}4.上传切片调用Promise.all并发上传所有切片,并将切片序号、切片文件、文件MD5传给后台。后台收到上传请求后,首先检查名为文件MD5的文件夹是否存在,如果不存在则创建一个文件夹,然后通过fs-extra的rename方法将切片从临时路径移动到切片文件夹,结果如下:当所有分片上传成功后,会通知服务器进行合并。当部分上传失败时,会提示“上传失败”。重新上传时,通过文件的MD5获取文件的上传状态。当服务器已经有该MD5对应的分片时,说明该分片已经上传,无需再次上传。当服务器找不到MD5对应的分片时,说明需要上传分片Slices。用户只需要上传这部分切片就可以上传整个文件。这是恢复的文件上传。//上传chunkupload(i,fileMd5Value,file){const{uploadProgress,chunks}=thisreturnnewPromise((resolve,reject)=>{let{chunkSize}=this//构造一个表单,FormData是HTML5新增的letend=(i+1)*chunkSize>=file.size?file.size:(i+1)*chunkSize让form=newFormData()form.append('data',file.slice(i*chunkSize,end))//file对象的slice方法用于切出文件的一部分form.append('total',chunks)//总切块数form.append('index',i)//.append('fileMd5Value',fileMd5Value)services.uploadLarge(form).then(data=>{if(data.ok){this.hasUploaded++uploadProgress(file)}console.log({data})resolve(data)}).catch(err=>{reject(err)})})}5.上传进度虽然批量上传分片比单个上传大文件要快很多,但是还是有一段时间的加载时间,此时要加上上传进度提示,实时显示文件上传进度。NativeJavascript的XMLHttpRequest提供了一个progress事件,返回文件的上传大小和总大小。项目使用axios封装ajax,可以在config中添加onUploadProgress方法来监控文件上传进度。constconfig={onUploadProgress:progressEvent=>{varcomplete=(progressEvent.loaded/progressEvent.total*100|0)+'%'}}services.uploadChunk(form,config)6.合并部分并上传所有文件部分后分片后,前端主动通知服务端合并。服务端收到这个请求后,主动合并分片,通过文件MD5在服务端的文件上传路径中找到同名文件夹。从上面可以看出,文件分片是按照分片的序号命名的,分片上传接口是异步的,所以不能保证服务器收到的分片一定会按照请求的顺序进行拼接.因此,在合并文件夹中的碎片文件之前,先根据文件名排序,然后通过concat-files将碎片文件合并,得到用户上传的文件。至此,大文件上传完成。节点代码://合并文件exports.merge={validate:{query:{fileName:Joi.string().trim().required().description('文件名'),md5:Joi.string().trim().required().description('文件md5'),size:Joi.string().trim().required().description('文件大小'),},},permission:{roles:['user'],},异步处理程序(ctx){const{fileName,md5,size}=ctx.request.querylet{name,base:filename,ext}=path.parse(fileName)constnewFileName=randomFilename(name,ext)awaitmergeFiles(path.join(uploadDir,md5),uploadDir,newFileName,size).then(async()=>{constfile={key:newFileName,name:filename,mime_type:mime.getType(`${uploadDir}/${newFileName}`),ext,path:`${uploadDir}/${newFileName}`,provider:'oss',size,owner:ctx.state.user.id,}constkey=encodeURIComponent(file.key).replace(/%/g,'').slice(-100)file.url=awaituploadLocalFileToOss(file.path,key)file.url=getFileUrl(file)constf=awaitFile.create(omit(file,'path'))constfiles=[]files.push(f)ctx.body=invokeMap(files,'toJSON')}).catch(()=>{throwBoom.badData('大文件切片合并失败,请稍后再试~')})},}总结本文介绍了一些优化大文件上传的方法,归纳为以下4点:Blob.slice对文件进行切片并发上传多个分片,所有分片上传完毕后,通知服务器合并,实现分片上传大文件;原生XMLHttpRequest的onprogress监听切片上传进度,实时获取文件上传进度;spark-md5根据文件内容计算文件的MD5,得到文件的唯一标识,与文件上传状态绑定;上传切片前通过文件MD5查询已上传切片列表,上传时只上传未上传的切片,实现断点续传。参考Demo源码,快速上手以上功能。希望本文能对您有所帮助,感谢阅读??欢迎关注傲兔实验室博客:aotu.io或关注傲兔实验室公众号(傲兔实验室),不定期推送文章: