一、问题分析
如果将大文件一次性上传,会发生什么?想必都遇到过在一个大文件上传、转发等操作时,由于要上传大量的数据,导致整个上传过程耗时漫长,更有甚者,上传失败,让你重新上传!这个时候,我已经咬牙切齿了。先不说上传时间长久,毕竟上传大文件也没那么容易,要传输更多的报文,丢包也是常有的事,而且在这个时间段万不可以做什么其他会中断上传的操作;其次,前后端交互肯定是有时间限制的,肯定不允许无限制时间上传,大文件又更容易超时而失败....
一、解决方案
既然大文件上传不适合一次性上传,那么将文件分片散上传是不是就能减少性能消耗了。
没错,就是分片上传。分片上传就是将大文件分成一个个小文件(切片),将切片进行上传,等到后端接收到所有切片,再将切片合并成大文件。通过将大文件拆分成多个小文件进行上传,确实就是解决了大文件上传的问题。因为请求时可以并发执行的,这样的话每个请求时间就会缩短,如果某个请求发送失败,也不需要全部重新发送。
二、具体实现1、前端(1)读取文件
准备HTML结构,包括:读取本地文件(类型为)、上传文件按钮、上传进度。
JS实现文件读取:
监听的事件,当选取了本地文件后,打印事件源可得到文件的一些信息:
观察控制台,打印读取的文件信息如下:
(2)创建切片
文件的信息包括文件的名字,文件的大小,文件的类型等信息,接下来可以根据文件的大小来进行切片,例如将文件按照1MB或者2MB等大小进行切片操作:
切片的核心思想是:创建一个空的切片列表数组,将大文件按照每个切片2MB进行切片操作,这里使用的是数组的方法,那么每个切片都应该在2MB大小左右,如上文件的大小是,那么可得到4个切片,分别是[0,2MB]、[2MB,4MB]、[4MB,6MB]、[6MB,8MB]。调用,会返回一个切片列表数组,实际上,有几个切片就相当于有几个请求。
调用创建切片函数:
观察控制台打印的结果:
(3)上传切片
上传切片的个关键的操作:
第一、数据处理。需要将切片的数据进行维护成一个包括该文件,文件名,切片名的对象,所以采用对象来进行整理数据。用以将数据编译成键值对,可用于发送带键数据,通过调用它的方法来添加字段,FormData.append()或者字符串:如果它的字段类型不是 Blob 也不是 File,则会被转换成字符串类。
第二、并发请求。每一个切片都分别作为一个请求,只有当这4个切片都传输给后端了,即四个请求都成功发起,才上传成功,使用保证所有的切片都已经传输给后端。
2、后端(1)接收切片
主要工作:
第一:需要引入中间件,来解析前端传来的对象数据;
第二:通过在根目录创建一个文件夹--,该文件夹将存放另一个文件夹(存放所有的切片)和合并后的文件;
第三:处理跨域问题。通过方法设置所有的请求头和所有的请求源都允许;
第四:解析数据成功后,拿到文件相关信息,并且在文件夹创建一个新的文件夹,用来存放接收到的所有切片;
第五:通过将切片移入文件夹,最后向前端返回上传成功的信息。
通过启动后端服务,可在控制台打印:
(2)合并切片
第一:前端得到后端返回的上传成功信息后,通知后端合并切片:
第二:后端接收到合并的数据,创建新的路由进行合并,合并的关键在于:前端通过请求向后端传递的合并数据是通过将数据转换成字符串,所以后端合并之前,需要进行以下操作:
解析POST请求传递的参数,自定义函数,目的是将每个切片请求传递的数据进行拼接,拼接后的数据仍然是字符串,然后通过将字符串格式的数据转换为JSON对象;
接下来该去合并了,拿到上个步骤解析成功后的数据进行解构,通过获取每个切片所在的路径;
自定义合并函数,只要传入切片路径,切片名字和切片大小,就真的将所有的切片进行合并。在此之前需要将每个切片转换成流对象的形式进行合并,自定义函数,目的是将切片转换成流对象,在这个函数里面创建可读流,读取所有的切片,监听事件,所有的切片读取完毕后,销毁其对应的路径,保证每个切片只被读取一次,不重复读取,最后将汇聚所有切片的可读流汇入可写流;
最后,切片被读取成流对象,可读流被汇入可写流,那么在指定的位置通过创建可写流,同样使用的方法,保证所有切片都被读取,最后调用合并函数进行合并。
还未合并前,文件夹如下图所示:
合并后,文件夹新增了合并后的文件:
领取专属 10元无门槛券
私享最新 技术干货