当前位置: 首页 > 后端技术 > Node.js

部分上传和断点续传方案

时间:2023-04-03 23:02:11 Node.js

上传文件基本上是每个网站应用都会有的功能。对于网络存储应用,上传功能的需求更为迫切。现在市面上成熟的上传插件,比如WebUploader,都“太大”了,不适合手机上传;再加上程序员的“道德”,我当然还是更喜欢自己造轮子。于是花了一天半的时间,MoUploader应运而生。为什么叫MoUploader?Mo说Mobile(其实是因为我的昵称moyu)首先要明确实现原理。上传这个东西不仅是一件只有前端才能完成的好事,而且需要前后端统一数据格式,才能实现断点续传。经过。(所以,这篇文章适合全栈工程师,至少如果你想成为全栈工程师的话)还有,为什么需要分片,不分片能不能实现断点续传?分片是为了充分利用网络带宽,加快上传速度;不分片也可以实现断点续传。详见深入剖析HTML5文件上传组件。分段上传和断点续传之间没有直接关系。好吧,让我们进入正题。断点续传的前提是服务器需要记录一个文件的上传进度,那么判断是否是同一个文件的依据是什么?可以通过文件内容获取md5码。如果文件太大,获取md5码是一个很长的过程,所以对于大文件,只能计算某段数据,加上服务器对cookie用户信息的判断,得到一个相对唯一的key。在前端页面,文件需要按照一定的大小进行切分,一次请求只发送这一小块数据,所以我们可以同时发起多个请求。但是同时请求的连接数不能太多,服务器负载太大。对于文件切片操作,H5有一个非常强大的FileAPI,使用File对象的slice方法可以直接获取Blob对象。至于同时传输数据的连接数的控制逻辑,就需要花点脑筋思考了。前端已经成功将数据传输给服务器,服务器只需要根据数据中给出的起始字节位置和读取的文件分片数据将文件写入文件即可。更多内容请查看源码!MoUploader功能实现文件结构file-upload/├──bower_components/#bower包├──db.js#数据操作接口├──demo.html├──md5.json#数据├──mouploader.js#源码├──README.md└──server.js#demo.html服务,建立在3000端口1个目录,8个文件。(打印文件目录树用的是自己写的print-dir)导入脚本的使用方法,amd/cmd/...,使用MoUploaderinput.onchange=function(e){varself=this;varmoUploader=MoUploader({files:this.files,uploadUrl:'/upload',request:false,onBeforeUpload:function(index){if(index>=0){self.files[index].progress=appendUploading(self.files[index],index)}},onOverAllProgress:函数(index,loaded,total){console.log(loaded/total)//setProgress(loaded/total,self.files[index].progress)},onLoad:function(index,chunkIndex,chunksNum){console.log('onLoad',this,arguments)},onAbort:函数(index,chunkIndex,chunksNum){console.log('onAbort',this,arguments)},onError:function(index,chunkIndex,chunksNum){console.log('onError',this,arguments)},onContinue:function(file,md5,index){returnnewPromise(function(reslove,reject){varxhr=newXMLHttpRequest()xhr.open('GET','/getFile?md5='+md5,true);xhr.send(null);xhr.addEventListener('readystatechange',function(){if(xhr.readyState===4&&xhr.status===200){varjson=JSON.parse(xhr.responseText);log(json)reslove(json.pos)}})})}})//暂停或继续上传//如果index<0,将为所有文件运行//moUploader.pause(index);//moUploader.continue(index);}配置选项vardefault_ops={//chunkSize:bytechunkSize:(1<<20)*5,//Number:requestNumber.//数组:文件请求。//Boolean:打开或关闭Slice,如果为false,chunkSize不起作用。request:3,files:[],uploadUrl:'/',//功能:获取上传位置。//参数:文件、md5、索引。//需要返回一个promise对象,它将返回上传的pos。onContinue:null,//如果为false,md5将由文件名设置。md5:true,//md5Size:切片文件0-md5Size用于计算md5md5Size:(1<<20)*50,//在上传之前调用。//参数:文件索引或-1(将开始上传)onBeforeUpload:null,//函数:上传进度监听器。//*只听一个请求。*//参数:index、chunkIndex、chunksNum、loaded、total。onProgress:null,//功能:整体上传进度监听。//参数:索引、加载、总计onOverAllProgress:null,//函数:当一个请求结束时调用。//arguments:index,chunkIndex,chunksNumonLoad:null,//函数:当一个请求被中止时调用。//arguments:index,chunkIndex,chunksNumonAbort:null,//function:当一个请求发生错误时调用。//arguments:index,chunkIndex,chunksNumonError:null}服务器数据处理(Node.js)数据分段写入文件functionwriteBuffer(bf,path,pos){varfd=fs.openSync(path,'a+');fs.writeSync(fd,bf,0,bf.length,Number(pos)||0)console.log(`writebuffer,pos:${pos},path:${path},length:${bf.length}`)}functionstore(param,chunks){param.chunks=param.chunks||1参数块=参数块||0varp=path.join('./upload',param.name)varbf=Buffer.concat(chunks);varjson=db.get(param.md5);if(json){json.pos=parseInt(json.pos!=null?json.pos:0)json.size=parseInt(json.size!=null?json.size:0)}if(!json||(json.pos+json.size)<=param.pos){//新数据pos大于数据库,更新数据param.size=bf.lengthdb.set(param.md5,参数)db.save();writeBuffer(bf,p,param.pos||0)}}varmultiparty=require('multiparty')varform=newmultiparty.Form({autoFields:true,autoFiles:false,});form.on('part',(part)=>{form.on('aborted',()=>{//意外退出或暂停会保存数据console.log('aborted');store(param,chunks)})varchunks=[]part.on('data',(data)=>{if(part.filename){chunks.push(data)}}).on('end',()=>{console.log('end')store(param,chunks)})});form.on('field',(name,value)=>{param[name]=value;});