
一、概念不管怎样简单的需求在量级达到一定层次的时候都会变得 异常复杂文件上传简单文件变大就复杂上传大文件时以下几个变量会影响我们的用户体验服务器处理数据的能力请求超时网络波动上传时间会变长高频次文件上传失败失败后又需要重新上传等为了解决上述问题我们需要对大文件上传单独处理这里涉及到分片上传及断点续传两个概念1.分片上传分片上传就是将所要上传的文件按照一定的大小将整个文件分割成多个数据块Part)来进行分片上传如下图上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件大致流程如下将需要上传的文件按照一定的分割规则分割成相同大小的数据块初始化一个分片上传任务返回本次分片上传唯一标识按照一定的策略串行或并行发送各个分片数据块发送完成后服务端根据判断数据上传是否完整如果完整则进行数据块合成得到原始文件2.断点续传断点续传指的是在下载或上传时将下载或上传任务人为的划分为几个部分 每一个部分采用一个线程进行上传或下载如果碰到网络故障可以从已经上传或下载的部分开始继续上传下载未完成的部分而没有必要从头开始上传下载。用户可以节省时间提高速度 一般实现方式有两种服务器端返回告知从哪开始浏览器端自行处理上传过程中将文件在服务器写为临时文件等全部写完了文件上传完将此临时文件重命名为正式文件即可 如果中途上传中断过下次上传的时候根据当前临时文件大小作为在客户端读取文件的偏移量从此位置继续读取文件数据块上传到服务器从此偏移量继续写入文件即可二、实现思路整体思路比较简单拿到文件保存文件唯一性标识切割文件分段上传每次上传一段根据唯一性标识判断文件上传进度直到文件的全部片段上传完毕下面的内容都是伪代码读取文件内容// 读取文件内容 const input document.querySelector(input); input.addEventListener(change,function(){ var file this.files[0] }) // 可以使用md5实现文件的唯一性 const md5code md5(file) // 然后开始对文件进行分割 var reader new FileReader() reader.readAsArrayBuffer(file) reader.addEventListener(load,function(e){ var slice e.target.result.slice(0,10*1024*1024) }) // h5上传一个一片 const formdata new FormData() formdata.append(0,slice) //这里是有一个坑的部分设备无法获取文件名称和文件类型这个在最后给出解决方案 formdata.append(filename,file.filename) var xhr new XMLHttpRequest() xhr.addEventListener(load,function(){ // xhr.responseText }) xhr.open(POST,) xhr.send(formdata) xhr.addEventListenter(progress,updateProgress) xhr.upload.addEventListener(progress,updateProgress) function updateProgress(event){ if(event.lengthComputable){ //进度条 } } 这里给出常见的图片和视频的文件类型判断 function checkFileType(type, file, back) { /** * type png jpg mp4 ... * file input.change this.files[0] * back callback(boolean) */ var args arguments; if (args.length ! 3) { back(0); } var type args[0]; // type (png|jpg) , png var file args[1]; var back typeof args[2] function ? args[2] : function() {}; if (file.type ) { var imgType [ ff d8 ff, //jpg 89 50 4e, //png 0 0 0 14 66 74 79 70 69 73 6F 6D, //mp4 0 0 0 18 66 74 79 70 33 67 70 35, //mp4 0 0 0 0 66 74 79 70 33 67 70 35, //mp4 0 0 0 0 66 74 79 70 4D 53 4E 56, //mp4 0 0 0 0 66 74 79 70 69 73 6F 6D, //mp4 0 0 0 18 66 74 79 70 6D 70 34 32, //m4v 0 0 0 0 66 74 79 70 6D 70 34 32, //m4v 0 0 0 14 66 74 79 70 71 74 20 20, //mov 0 0 0 0 66 74 79 70 71 74 20 20, //mov 0 0 0 0 6D 6F 6F 76, //mov 4F 67 67 53 0 02, //ogg 1A 45 DF A3, //ogg 52 49 46 46 x x x x 41 56 49 20 //avi (RIFF fileSize fileType LIST) ]; var typeName [ jpg, png, mp4, mp4, mp4, mp4, mp4, m4v, m4v, mov, mov, mov, ogg, ogg, avi, ]; var sliceSize /png|jpg|jpeg/.test(type) ? 3 : 12; var reader new FileReader(); reader.readAsArrayBuffer(file); reader.addEventListener(load, function(e) { var slice e.target.result.slice(0, sliceSize); reader null; if (slice slice.byteLength sliceSize) { var view new Uint8Array(slice); var arr []; view.forEach(function(v) { arr.push(v.toString(16)); }); view null; var idx arr.join( ).indexOf(imgType); if (idx -1) { back(typeName[idx]); } else { arr arr.map(function(v, i) { if (i 3 i 8) { return x; } return v; }); var idx arr.join( ).indexOf(imgType); if (idx -1) { back(typeName[idx]); } else { back(false); } } } else { back(false); } }); } else { var type file.name.match(/\.(\w)$/)[1]; back(type); } } // 调用方法如下 checkFileType((mov|mp4|avi),file,function(fileType){ // fileType mp4, // 如果file的类型不在枚举之列则返回false }); // 上面上传文件的一步可以改成 formdata.append(filename, md5code.fileType);有了切割上传后也就有了文件唯一标识信息断点续传变成了后台的一个小小的逻辑判断。 后端主要做的内容为根据前端传的 md5 值到服务器磁盘查找是否有之前未完成的文件的合并信息也就是未完成的半成品文件切片取到之后根据文件上传的片数返回数据告诉前端开始从第几片上传。如果想要暂停文件的上传可以使用 XMLHttpRequest 的 abort 方法。三、使用场景大文件加速上传当文件大小超过预期大小时使用分片上传可实现并行上传多个 Part以加快上传速度。网络环境较差建议使用分片上传。当出现上传失败的时候仅需重传失败的 Part。流式上传可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见。