项目需要实现下载大文件(最大1GB),并且下载时需要校验发起下载的用户是否有权限(最开始是token放在请求头这样做校验),所以想了以下这些方案。
1.blob生成url
既然要校验身份(token放请求头),就只能自己发起这个请求,用blob接收,然后生成url,再赋值给a标签的href属性,触发click事件,就会下载了。
需要注意响应头content-disposition的设置,设置为"attachment;xxx"才会下载,否则浏览器会先尝试打开/预览文件。或者给a标签设置download属性,值为文件名(如果不知道文件名可以设置download为空字符串,浏览器会尝试从响应头的content-disposition取文件名),这样浏览器也会直接下载而不是尝试打开文件。
但是这样肯定是有问题的:
- 大文件下载时响应很慢。我们是vue项目,http请求用axios发起,请求的then会在所有响应体都接收完成后才执行,意味着需要等到所有流接收完毕才会做下载,用户才会在页面上看到下载进程;
- 浏览器容易崩溃。执行到axios请求的then方法时,需要用blob变量接收,这些变量都是暂存在内存中的,而浏览器一般占用到1-2GB就会崩溃。

所以此方案打咩。
2.使用streamsaver.js
因为文件太大,自然会想到用流式下载的方式,避免占用过多内存。streamsaver.js就是实现前端流式下载的依赖包,可以解决下载大文件问题。 streamsaver原理可见:
图片来自:Http Fetch+StreamSaver.js分片下载大文件 - keitsi - 博客园
在开发时发现有一点小问题:因为项目运行环境无法访问github,而streamSaver的中间人mitm.html文件放在了github上,所以需要下载这个mitm.html文件放到项目public目录下,并修改引用地址。另外sw.js也需单独下载下来放到项目中。
解决后发现本地运行可行,浏览器不会崩溃啦!
但!是!部署后下载失败了! 现象是浏览器会新打开地址栏是xxx/mitm.html的窗口,并在控制台看到浏览器在不断地发起ping请求。(这里忘记截图了,自行想象)

发现是因为streamsaver使用了service worker,这个仅支持https或localhost,而我们部署是http,于是放弃此方案。
下图是支持的情况,如果不支持,在控制台输入ServiceWorker会报undefined错误。

3.使用file system Web API
是可以操作本地文件系统的API,但是研究了一番发现也是只支持https,放弃。
(不过这个没有仔细研究,不知道能否用这套API实现前端下载)
4.先校验再生成一次性下载链接
没辙了,还是从浏览器下载出发,想想咋做校验。最后想了这套方案:
- 用户点击下载后,先发起一个校验权限和身份的请求,如果通过校验,返回一个一次性的下载链接;
- window.open这个链接或者把这个链接给a标签并触发click。
一开始我们使用的是a标签的方式,但是如果请求的http status不是200、content-dispostion是json(就是请求失败了),浏览器还是会把这个错误响应信息下载为文件(我们用的是某个浏览器,不知道其他浏览器是不是聪明点)。
改为window.open方式的话,会新开一个标签页,响应成功了就会开始下载,并自动关闭这个新开的标签页。如果失败了,会把失败信息显示在页面。 不过还是出现了一些问题,因为后端接收到那个一次性下载请求后,需要对文件做很多处理,时间花费巨大,越大的文件越久,进而导致:
- 打开新页面后没有任何提示,需要等待很久才会显示下载;
- 经常失败,越大的文件越容易失败,而且现象居然是显示下载链接已过期,但是浏览器只显示了一个请求,后端查看日志却发现有两个请求。查看nginx错误日志发现了这样的报错: "
connect() failed (111: Connection refused) while connecting to upstream, client: xxxxx, server: xxx, request: xxxx, upstream: xxx, host: xxx
",意思是Nginx 尝试与 upstream(后端)建立 TCP 连接时,被目标机器立即拒绝,这条日志说明 Nginx 遇到了"网络层"错误,并可能触发 Nginx 向后端重试(注意:重试只在 Nginx→后端之间,浏览器仍只看到一个请求/响应)
第一个问题没办法缩短文件处理时间,只能把时间放到校验身份那个请求里。于是改为:
- 如果后端校验权限和身份成功,马上就开始处理文件,处理完成后才返回一次性下载连接,同时前端将这个请求的超时时间由原来的1.5分钟设置为半小时;
- 为了提升用户体验,制作了一个带有loading动画的等待下载页面,在用户点击下载后立马打开一个这个页面,等到响应一次性下载链接后,把这个链接赋值给地址栏,然后再做下载;
- 后端排查发现因为网关超时时间设置过小,才出现上述第2点问题,调大后就正常了。