大文件上传和下载解决方案
前言
前端处理 “大” 一直是一个痛点和难点,比如大文件、大数据量。虽然浏览器硬件有限,但是聪明的工程师总是能够最大化利用浏览器的能力和特性,优雅的解决一个个极端问题,满足用户的多样化需求。
断点上传
对于大文件,如果我们直接上传,用户网速够慢的话,可能需要等上几天几夜才能上传完成,这样的用户体验可能导致用户直接放弃,那么有没有一种方式能够更好的上传大文件呢?
首先我们可以想到一些浏览器常见的优化套路:
- 多线并行处理
- 缓存结果
- 按需使用
有了优化思路,那么看看浏览器支持能力:
- HTTP 1.x,浏览器可以并行处理请求,比如 Chrome 可以并行处理 6 个请求。HTTP 2.x,理论上可以无限制并行处理请求。
- 浏览器支持 WebWorker单独子线程来处理一些耗时任务。
- HTTP 没有状态,所以我们只能将状态缓存到服务器。
浏览器也提供了支持能力,那么我们怎么把一个文件并发上传,又如何做缓存呢?
文件切割和唯一标识
我们知道,计算机底层数据都是由 0 和 1 的二进制数据构成,文件也不例外,那么我们可以按照字节数将大文件切割成一个个小文件块,然后并行上传。但是切割之后的文件块是无法标识的,所以我们需要为文件确定一个唯一标识,我们常见会使用文件名来标识文件,但是文件名是可修改的,这样的标识是非常不可靠的,所以我们会基于文件内容来做一个标识,也就是计算文件的 md5 值,这样只要文件内容不修改,文件的 md5 值就不会变化。
我们通过文件大小和固定文件块大小来计算需要上传的文件块数量和每个块对应字节范围。然后使用 spark-md5
库来计算文件的 md5 值,如果注意的是,如果文件较大,计算 md5 时间可能较长,所以需要利用 WebWorker
来计算。
文件并行上传和缓存实现
通过对文件切块和 md5 值计算,我们可以并行的上传文件块,并缓存上传的文件块。
这里我们通过一个接口来保持上传的状态。使用File.prototype.slice
来进行文件切割,并通过Promise.allSettled
来实现并发上传。
合并文件,文件上传校验
当服务器文件块和本地切割块一致时,则通知服务器进行文件合并。
至此,断点续传已经完成了。
断点下载
对于大文件上传,上面那节我们给了解法,那么对于大文件下载,我们应该怎么做呢?
其实原理也是一样的:利用浏览器请求并发能力和缓存能力。
获取文件信息
首先我们需要获取文件的总大小,从而进行分块下载。
我们使用 head 请求来获取文件的大小和文件名称,从而进行分块下载。
分块下载和重试机制
我们利用请求头 range
来进行分块下载,并添加重试机制。
我们通过一个递归函数,每次上传检测下载进度,从而完成下载重试。
文件合并和下载
在获取到所有文件数据之后,我们需要对文件进行合并,并下载。
我们获取到的文件数据是ArrayBuffer
类型,这个数据是不能直接操作的,所以我们需要使用类型数组来操作它,这里我们使用Unit8Array
类型数组来合并文件数据,最后通过生成BlobUrl
来进行文件下载。
总结
断点上传和断点下载都是利用常见的优化套路:并行计算和缓存。充分发挥浏览器特性能力,达到更佳的效果。其实大数据渲染也是相似套路,比如懒加载、分片渲染、虚拟列表等等,使用的是按需加载、异步渲染、按需渲染的套路来达到大数据的渲染效果。