We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂
文件上传简单,文件变大就复杂
上传大文件时,以下几个变量会影响我们的用户体验
上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等
为了解决上述问题,我们需要对大文件上传单独处理
这里涉及到分片上传及断点续传两个概念
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
如下图
上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
大致流程如下:
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
一般实现方式有两种:
上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可
如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可
整体思路比较简单,拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕
下面的内容都是伪代码
读取文件内容:
const input = document.querySelector('input'); input.addEventListener('change', function() { var file = this.files[0]; });
可以使用md5实现文件的唯一性
md5
const md5code = md5(file);
然后开始对文件进行分割
var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.addEventListener("load", function(e) { //每10M切割一段,这里只做一个切割演示,实际切割需要循环切割, 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.addEventListener('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)(52 49 46 46,DC 6C 57 09,41 56 49 20,4C 49 53 54) ]; 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) { 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 方法
XMLHttpRequest
abort
当前的伪代码,只是提供一个简单的思路,想要把事情做到极致,我们还需要考虑到更多场景,比如
人生又何尝不是如此,极致的人生体验有无限可能,越是后面才发现越是精彩 _
The text was updated successfully, but these errors were encountered:
其实对于大文件上传,我们采用的正常的方法为,多线程处理切片和加密,同时采用bff中间层来存储,后端服务器只保存切片的引用地址,并且最后的切片并不真实的进行合并,也就是说,只有在用的时候,后端会把切片进行临时的组装,但并不是真正的组装,这样当其他人传输相同的切片的时候,服务器可以根据哈希值很好的识别是否已经存在相同的文件了,那么进行分片上传的时候可以参考两种解决方案, 1 先进行分片,得到分片信息,计算整体哈希,然后在进行上传,这样用户的感知就是,进度条为0(卡好久等待分片和哈希计算完成)----->百分比或者一点点增加 2 边分片,边上传,也就是我假设每次上传都是一个全新的文件,那么我计算一个切片上传一个切片,那么如果服务器存在,当前切片就不会发起二次请求带过去内容(上传应该分两次请求过去,第一次只发送哈希,让客户端判断是否存在当前文件的切片,告诉客户端是否需要发送当前切片,如果需要,在进行二次发送将数据带过去)后续再补上完整文件的哈希, 那么用户的感知就是0--->慢慢增加,如果当前文件已经存在则加载完毕,如果只存在一部分,则快速加载到那一部分对应的位置,这种情况用户的体验感明显好于第一种 那么我们前端其实最重要的就是进行多线程切片和并发控制 因为计算哈希是一个比较耗时cpu密集型的任务,所以开始多线程帮助我们进行处理,再将处理结果汇总到主线程,然后通过并发控制进行上传(同一个服务器浏览器限制tcp最多同时进行六条连接) 同时也需要根后端约定好相互的协议
Sorry, something went wrong.
No branches or pull requests
一、是什么
不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂
文件上传简单,文件变大就复杂
上传大文件时,以下几个变量会影响我们的用户体验
上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等
为了解决上述问题,我们需要对大文件上传单独处理
这里涉及到分片上传及断点续传两个概念
分片上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
如下图
上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
大致流程如下:
断点续传
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
一般实现方式有两种:
上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可
如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可
二、实现思路
整体思路比较简单,拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕
下面的内容都是伪代码
读取文件内容:
可以使用
md5
实现文件的唯一性然后开始对文件进行分割
h5上传一个(一片)
这里给出常见的图片和视频的文件类型判断
调用方法如下
上面上传文件的一步,可以改成:
有了切割上传后,也就有了文件唯一标识信息,断点续传变成了后台的一个小小的逻辑判断
后端主要做的内容为:根据前端传给后台的
md5
值,到服务器磁盘查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片),取到之后根据上传切片的数量,返回数据告诉前端开始从第几节上传如果想要暂停切片的上传,可以使用
XMLHttpRequest
的abort
方法三、使用场景
小结
当前的伪代码,只是提供一个简单的思路,想要把事情做到极致,我们还需要考虑到更多场景,比如
人生又何尝不是如此,极致的人生体验有无限可能,越是后面才发现越是精彩
_参考文献
The text was updated successfully, but these errors were encountered: