1
lwch 2022-12-30 10:19:37 +08:00
需要先获取文件大小后通过 Content-Length 返回给客户端,客户端才能计算出进度
|
2
lwch 2022-12-30 10:25:46 +08:00
你可以尝试使用 http.ServeFile 接口来返回文件内容,这个接口中已正确的处理了 Content-Type 和 Content-Length 字段
|
3
kumoocat 2022-12-30 10:40:13 +08:00
io.Copy 每次只写入 32KB 的数据,应该就是楼上说的没有返回 Content-Length 导致客户端计算不出进度
|
4
asen001 2022-12-30 10:40:48 +08:00
浏览器也有 steam ,可以试试这个库,不过可能会有兼容性问题
https://github.com/jimmywarting/StreamSaver.js |
5
Frankcox OP @lwch 您好,Content-Length 这个我之前确实没有处理,但是我设置 Content-Length 后还是之前的问题,点击后浏览器没反应,大概 5 秒后之前跳出下载完成的提示。至于 http.ServeFile ,我之前使用过,效果和 io.Copy 是一样的,仍然是触发下载后几秒钟没反应,然后直接显示下载文件完成。
我现在有点怀疑是前端的请求问题,前端我是用的 fetch+创建 a 标签的下载方式,这种用法是不是有问题? |
6
Frankcox OP @kumoocat 您好,我使用 http.ServeFile 和手动设置 Content-Length 后还是一样的情况,点击下载后没反应,大概几秒后直接弹出下载完成的提示。
|
7
okakuyang 2022-12-30 10:51:21 +08:00
fetch 是脚本中的请求怎么能触发浏览器下载?浏览器下载不是 a.click()触发吗?
|
8
qscasdqwezxc 2022-12-30 10:57:33 +08:00 via Android
服务器返回 http 206 客户端进度看 onprogress
|
9
Frankcox OP @okakuyang fetch 请求后,document createElement 一个 display = none 的 a 标签,然后调用 a.click()
|
10
timnottom 2022-12-30 11:00:26 +08:00
为什么会用 fetch 请求?
应该: let a = document.createElement('a') a.href= 'download link' a.click() ps:伪代码 |
12
Frankcox OP @timnottom
fetch(url,{ method: 'get', responseType: 'blob' }).then(res => res.blob()).then(data => { const downloadURL = window.URL.createObjectURL(data); const a = document.createElement('a'); a.style.display = 'none'; a.href = downloadURL; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(downloadURL); |
14
rekulas 2022-12-30 11:04:53 +08:00
大概率 fetch 的问题,前端要么直接跳转下载链接下载要么用 fs 去下载,fetch 那肯定会获取完数据才能处理,容易卡死
|
15
okakuyang 2022-12-30 11:05:43 +08:00
你这样写肯定是要等脚本下载完成才会弹下载框的。压根没有进度条相关的代码。
|
16
AlphaTr 2022-12-30 11:06:00 +08:00
@Frankcox 你这是请求结束后才交给浏览器下载保存,主要的网络耗时都在交给浏览器之前,交给浏览器之后基本没有网络流量;所以会有你说的现象
|
18
monkeyWie 2022-12-30 11:06:30 +08:00
createObjectURL 就是这样的,直接用#10 的方法就不会这样了
|
19
timnottom 2022-12-30 11:06:44 +08:00
@Frankcox 为什么不直接
let a = document.createElement('a') a.href= url # 这个 url 是你上面,fetch 的 url # 为什么会在通过什么 blob 转换来转换去的? a.click() |
20
okakuyang 2022-12-30 11:06:54 +08:00
@Frankcox
a.href = downloadURL; a.download = filename; document.body.appendChild(a); a.click(); 这里 downloadURL 直接用你 fetch 里的 url 就行了 |
22
timnottom 2022-12-30 11:10:22 +08:00
|
24
rekulas 2022-12-30 11:39:52 +08:00
https://blogs.360.net/post/%E7%94%A8-filesystem-api-%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD%E5%99%A8.html
如果非要用 js 下载可以通过 filesystem 的方式,现在有不少新生服务都喜欢这个模式,优点是比较灵活 |
25
Frankcox OP @rekulas 感谢,我之前用 js 发请求主要是为了 header 添加信息处理鉴权,用 a.href 直接下载的话虽然解决了下载的问题,但会有权限的问题,我再看看这块。
|
26
gogogo2000 2022-12-30 13:39:19 +08:00
@Frankcox 添加鉴权的话你需要用 worker 方式拦截浏览器的下载请求,然后在 worker 中把 header 加上去
|
27
weiwoxinyou 2022-12-30 14:30:55 +08:00
有鉴权需求可以试试成熟的 axios 请求库
|
29
hellojukay 2022-12-30 18:23:36 +08:00
别处理下载流量过程,把下载交给浏览器来干,模拟一个 a 标签点击事件。
|
30
webcape233 2023-02-12 00:14:07 +08:00 via iPhone
@Frankcox 你好,我最近也遇到类似问题,似乎通过 xhr 模式(例如用 axios 或 fetach ),无论怎么写,总是要等待 response 获取完毕才能继续处理,如果使用 blob 模式,大文件就会有很大问题,res 过程中刷新页面就中断了,浏览器自身下载管理也无法接管下载,等 blob 构建完成实际上再弹出的浏览器下载框就快完成下载动作了。 现在策略是 acios 去请求下载的临时地址,这样后台能鉴权,后台认可后返回下载地址和地址和一个专用的下载 key ,key 存 cookie 中,再构造一个 a 标签(加上 download 属性),href 写为临时地址( rudownload/hdjkkdnf )然后点击 a 标签,这样后台监听 download 这个路由,就能取到临时地址和 cookie ,cokkie 用来鉴定权限,临时地址对应下载的资源(怎么对应的方法很多,例如临时地址和对应资源地址存到数据库), 后端返回文件即可。 不知道还有啥方法不,我能找到由浏览器来接管下载的方法就是这样了。
|
31
webcape233 2023-02-12 00:19:03 +08:00 via iPhone
当然,其实也可以省掉前面一步,直接用 a 标签来下载,带上 cookie 进行认证
|
32
Frankcox OP @webcape233 我用的也是类似这个模式,用户发送请求后,后端不执行下载而是根据信息返回一个临时 token ,前端获取到 token 再进行实际下载请求,我同时将这个实际下载请求从中间件中直接放行了,只是使用 token 进行校验。因为我的这个项目目前是公司内部使用,安全性和性能之类的要求不高,暂时就只使用这种方式了。
至于其他下载方式的话,如果不想专门为了下载文件引入其他系统,那做到这样我觉得差不多了。如果真有特别强烈的需求,那针对文件存储下载单独写一个服务,再提供一个中间件可能更好一些,不过我觉得是没必要了。 |