目录
概述
浏览器文件操作限制
前端多文件分片上传的原理和实现
预览
概述
Amazon S3 提供了一个简单 Web 服务接口,可用于随时在 Web 上的任何位置存储和检索任何数量的数据。此服务让所有开发人员都能访问同一个具备高扩展性、可靠性、安全性和快速价廉的数据存储基础设施, Amazon 用它来运行其全球的网站网络。此服务旨在为开发人员带来最大化的规模效益。
本文主要针对兼容aws-s3接口的第三方存储服务,在不使用官方sdk的情况下直接使用Restful接口进行存储桶多文件分片上传,主要包含浏览器端的多文件分片上传逻辑的Javascript代码实现。
浏览器文件操作限制
- HTML5新特性
input[type=file]
支持调用浏览器文件访问窗口来获取文件数据,实际上JS代码使用此特性访问本地文件系统后拿到的是一个指向文件的引用地址,且如果页面刷新了那么这个地址不可复用,JS代码并没有实际操作文件本身。前端上传数据时根据这个指向文件的地址把文件的一小块分片数据载入到内存并通过Ajax请求发送到中间件进行处理。 - 浏览器JS代码没有文件系统操作权限,不能任意存储和读取文件,因此不支持刷新浏览器后上传进度断点恢复,刷新之后断点恢复的前提是能拿到文件数据,但是JS代码没权限访问之前拿到的文件引用地址,并且存储之前上传过的文件分片数据这一做法也不合理。
- 相对于文件上传,文件下载则完全不可控,由于文件操作权限,所以整个下载文件操作都是由浏览器自带的的下载任务管理器控制的,没有浏览器接口能拿到这些下载任务进度,所以下载任务进度也是不能获取的。
前端多文件分片上传的原理和实现
完整Github源码
使用了React16/Webpack4/Mobx状态管理库
- 支持批量文件分割并行上传
- 多文件操作:暂停/恢复/终止/续传/重传
- 自定义上传任务数目、单个分片大小
运行流程图
主要流程
- cacheFile
前端通过input组件拿到所有文件地址并缓存起来。
1 | /** |
- registry
根据上一步拿到的文件地址数组创建多个Mobx observable对象跟踪每个上传对象的基本识别信息,包括文件名、文件大小、类型、分片信息(分片大小和总分片数)、上传状态信息:uninitial(未初始化)/pending(准备)/uploading(上传中)/pause(暂停)/error(错误)/break(上传完成)、上传开始时间、上传完成时间,为了便于访问这些Mobx observable对象,建立一个weakMap存储file对象和observable对象的弱映射关系。
1 | /** |
- startTasks
获取文件队列中可用于上传的文件对象,根据文件状态对其做初始化或切割文件上传的操作,同时实时修改对应的Mobx observable上传对象的元数据标识,包括当前上传文件的分片索引(单个文件上传进度=分片索引/总分片数目)、已上传完成的分片etag信息(由服务器返回,可用于完成分片上传时校验已上传的所有分片数据是否匹配)、当前上传对象4的上传状态(uninitial/pending/uploading/pause/eror/break)、当前上传对象的上传速度(速度=单个分片大小/单个分片上传所用时间)。
1 | /** |
- refreshTasks
根据当前设置的并行上传任务数目和正在上传的任务数目及时从文件预备上传队列提取文件放入上传可调用文件队列。
1 | /* 刷新任务列表 */ |
- upload & update
根据当前文件对象的上传分片索引对文件进行切割并更新索引,然后把切割下来的数据通过Ajax请求发送给中间件处理,中间件发送到后台后返回得到的当前分片的etag信息,前端拿到etag信息并存储到当前上传对象分片etag信息数组里面。
1 | /** |
- complete
当最后一个分片上传请求完成返回后,我们就拿到了服务端返回的这个文件的所有分片etag信息,前端需要校验当前上传对象etag数组的长度是否匹配,数组内每个etag元素的索引和etag值是否匹配,校验完成后发送最后一个请求到后端进行校验和组装分片,最终完成一个文件的分片上传过程。
1 | /** |
其它操作
暂停文件上传
将上传对象的状态从uploading置为pause,然后把该对象对应的文件从可调用上传文件队列移除。开始暂停的上传任务
将上传对象的状态从pause置为pending,然后把该对象对应的文件放入可调用上传文件队列,等待下一次刷新文件上传任务队列。续传上传错误的任务
将上传对象的状态从error置为pending,然后把该对象对应的文件放入可调用上传文件队列,保持文件的已上传分片索引记录,等待下一次刷新文件上传任务队列,直接调用上传函数进行切割并上传。重传上传错误的任务
将上传对象的状态从error置为pending,然后把该对象对应的文件放入可调用上传文件队列,并将文件已上传分片索引记录置为初始状态,等待下一次刷新文件上传任务队列,从文件初始位置重新开始切割文件并上传。
一些关键代码
- 一个分片上传完成后将后台返回的etag信息更新到本地的上传对象属性,并判断此文件是否上传完成。
1 | /** |
- 在Node.js中间件使用ak/sk预签名算法调用 s3 restful 原生接口
之前预研的时候尝试根据aws s3-version4签名文档里面请求预签名算法在使用Node.js中间件进行实现,结果很容易出现签名的signature不一致报错的情况,所以最后在Node.js中间件采用了一个npm库aws4,用里面的签名方法对前端传过来的ak/sk
进行url预签名,这里给出中间件Request
方法的编写逻辑:
1 |
|
- 在web端使用ak/sk预签名算法调用 s3 restful 原生接口
如果项目需要从web端直连后端s3服务调用接口的话,上面的签名方法就不能用了,其实很多时候直连可以带来更好的性能,比如文件上传/下载等等,不用在中间件做文件转存,其他的接口调用直连的话也不用中间层做request转发了。这里推荐一个能够进行s3请求预签名的axios插件aws4-axios,用法如下:
1 | import axios from "axios"; |