前端实现有校验的大文件下载方案对比

项目需要实现下载大文件(最大1GB),并且下载时需要校验发起下载的用户是否有权限(最开始是token放在请求头这样做校验),所以想了以下这些方案。

1.blob生成url

既然要校验身份(token放请求头),就只能自己发起这个请求,用blob接收,然后生成url,再赋值给a标签的href属性,触发click事件,就会下载了。

需要注意响应头content-disposition的设置,设置为"attachment;xxx"才会下载,否则浏览器会先尝试打开/预览文件。或者给a标签设置download属性,值为文件名(如果不知道文件名可以设置download为空字符串,浏览器会尝试从响应头的content-disposition取文件名),这样浏览器也会直接下载而不是尝试打开文件。

但是这样肯定是有问题的:

  1. 大文件下载时响应很慢。我们是vue项目,http请求用axios发起,请求的then会在所有响应体都接收完成后才执行,意味着需要等到所有流接收完毕才会做下载,用户才会在页面上看到下载进程;
  2. 浏览器容易崩溃。执行到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.先校验再生成一次性下载链接

没辙了,还是从浏览器下载出发,想想咋做校验。最后想了这套方案:

  1. 用户点击下载后,先发起一个校验权限和身份的请求,如果通过校验,返回一个一次性的下载链接;
  2. window.open这个链接或者把这个链接给a标签并触发click。

一开始我们使用的是a标签的方式,但是如果请求的http status不是200、content-dispostion是json(就是请求失败了),浏览器还是会把这个错误响应信息下载为文件(我们用的是某个浏览器,不知道其他浏览器是不是聪明点)。

改为window.open方式的话,会新开一个标签页,响应成功了就会开始下载,并自动关闭这个新开的标签页。如果失败了,会把失败信息显示在页面。 不过还是出现了一些问题,因为后端接收到那个一次性下载请求后,需要对文件做很多处理,时间花费巨大,越大的文件越久,进而导致:

  1. 打开新页面后没有任何提示,需要等待很久才会显示下载;
  2. 经常失败,越大的文件越容易失败,而且现象居然是显示下载链接已过期,但是浏览器只显示了一个请求,后端查看日志却发现有两个请求。查看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. 如果后端校验权限和身份成功,马上就开始处理文件,处理完成后才返回一次性下载连接,同时前端将这个请求的超时时间由原来的1.5分钟设置为半小时;
  2. 为了提升用户体验,制作了一个带有loading动画的等待下载页面,在用户点击下载后立马打开一个这个页面,等到响应一次性下载链接后,把这个链接赋值给地址栏,然后再做下载;
  3. 后端排查发现因为网关超时时间设置过小,才出现上述第2点问题,调大后就正常了。
相关推荐
逾明10 分钟前
Electron自定义菜单栏及Mac最大化无效的问题解决
前端·electron
辰九九14 分钟前
Uncaught URIError: URI malformed 报错如何解决?
前端·javascript·浏览器
月亮慢慢圆14 分钟前
Echarts的基本使用(待更新)
前端
芜青28 分钟前
实现文字在块元素中水平/垂直居中详解
前端·css·css3
useCallback31 分钟前
Elpis全栈项目总结
前端
小高00738 分钟前
React useMemo 深度指南:原理、误区、实战与 2025 最佳实践
前端·javascript·react.js
LuckySusu1 小时前
【js篇】深入理解类数组对象及其转换为数组的多种方法
前端·javascript
LuckySusu1 小时前
【js篇】数组遍历的方法大全:前端开发中的高效迭代
前端·javascript
LuckySusu1 小时前
【js篇】for...in与 for...of 的区别:前端开发中的迭代器选择
前端·javascript
mon_star°1 小时前
有趣的 npm 库 · json-server
前端·npm·json