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

项目需要实现下载大文件(最大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点问题,调大后就正常了。
相关推荐
世界哪有真情2 小时前
拿人类意识卡 AI?等于用 bug 验收正式产品
前端·人工智能·后端
Listen·Rain3 小时前
Vue3:setup详解
前端·javascript·vue.js
Patrick_Wilson4 小时前
修好 bug 只是开始:一次由监控需求反向重构日志结构的复盘
前端·监控·数据可视化
kyriewen4 小时前
面试官让我优化一个卡死的表格,我打开 Claude 五秒重写,他放下咖啡问我要不要来当组长
前端·javascript·面试
小粉粉hhh5 小时前
React(二)——dom、组件间通信、useEffect
前端·javascript·react.js
Csvn5 小时前
🤖 AI 生成前端代码的 5 个典型翻车现场及修复指南
前端
IT_陈寒6 小时前
SpringBoot自动装配坑了我一天,原来问题出在这
前端·人工智能·后端
索西引擎6 小时前
【React】key 属性:协调算法中的元素标识机制与最佳实践
前端·javascript·react.js
只会cv的前端攻城狮7 小时前
动态组件架构设计:从配置到事件驱动全链路解析
前端·vue.js
飞天狗7 小时前
Next.js 16 App Router 实战:服务端组件与流式渲染
前端·next.js