Go+Vue.js 如何较好的实现 Web 下载大文件? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Frankcox
V2EX    Go 编程语言

Go+Vue.js 如何较好的实现 Web 下载大文件?

  •  1
     
  •   Frankcox 2022-12-30 10:13:50 +08:00 4095 次点击
    这是一个创建于 1018 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在尝试实现一个功能,从 k8s Container 中导出文件下载。目前已经能够获取到文件流,但是在返回给前端时出现了问题,我尝试直接使用 io.Copy 将文件流 Copy 到 response 的 responseWriter 中,前端使用 fetch 请求下载。但是该模式会有一个问题,就是触发下载请求后,浏览器会一直等待后端返回数据,在文件流加载好之后直接提示下载完成。而不像平常下载那样有下载进度条。这种情况我推测是 response 是完全加载好文件数据流后再一次返回,使用 wireshark 抓包时可能因为一次数据量过大( 500 多 MB )直接卡死。
    不怎么懂前端,我想知道有没有较好的方式能将文件流直接返回给前端实现下载,使用 ws 下载也行。
    32 条回复    2023-02-12 16:45:03 +08:00
    lwch
        1
    lwch  
       2022-12-30 10:19:37 +08:00
    需要先获取文件大小后通过 Content-Length 返回给客户端,客户端才能计算出进度
    lwch
        2
    lwch  
       2022-12-30 10:25:46 +08:00
    你可以尝试使用 http.ServeFile 接口来返回文件内容,这个接口中已正确的处理了 Content-Type 和 Content-Length 字段
    kumoocat
        3
    kumoocat  
       2022-12-30 10:40:13 +08:00
    io.Copy 每次只写入 32KB 的数据,应该就是楼上说的没有返回 Content-Length 导致客户端计算不出进度
    asen001
        4
    asen001  
       2022-12-30 10:40:48 +08:00
    浏览器也有 steam ,可以试试这个库,不过可能会有兼容性问题
    https://github.com/jimmywarting/StreamSaver.js
    Frankcox
        5
    Frankcox  
    OP
       2022-12-30 10:41:30 +08:00
    @lwch 您好,Content-Length 这个我之前确实没有处理,但是我设置 Content-Length 后还是之前的问题,点击后浏览器没反应,大概 5 秒后之前跳出下载完成的提示。至于 http.ServeFile ,我之前使用过,效果和 io.Copy 是一样的,仍然是触发下载后几秒钟没反应,然后直接显示下载文件完成。
    我现在有点怀疑是前端的请求问题,前端我是用的 fetch+创建 a 标签的下载方式,这种用法是不是有问题?
    Frankcox
        6
    Frankcox  
    OP
       2022-12-30 10:46:17 +08:00
    @kumoocat 您好,我使用 http.ServeFile 和手动设置 Content-Length 后还是一样的情况,点击下载后没反应,大概几秒后直接弹出下载完成的提示。
    okakuyang
        7
    okakuyang  
       2022-12-30 10:51:21 +08:00
    fetch 是脚本中的请求怎么能触发浏览器下载?浏览器下载不是 a.click()触发吗?
    qscasdqwezxc
        8
    qscasdqwezxc  
       2022-12-30 10:57:33 +08:00 via Android
    服务器返回 http 206 客户端进度看 onprogress
    Frankcox
        9
    Frankcox  
    OP
       2022-12-30 10:57:34 +08:00
    @okakuyang fetch 请求后,document createElement 一个 display = none 的 a 标签,然后调用 a.click()
    timnottom
        10
    timnottom  
       2022-12-30 11:00:26 +08:00
    为什么会用 fetch 请求?

    应该:

    let a = document.createElement('a')
    a.href= 'download link'
    a.click()

    ps:伪代码
    okakuyang
        11
    okakuyang  
       2022-12-30 11:02:45 +08:00
    @Frankcox 哪你这个 fetch 和 a 标签怎么关联起来?你这步下载就错了吧。
    Frankcox
        12
    Frankcox  
    OP
       2022-12-30 11:02:47 +08:00
    @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);
    Frankcox
        13
    Frankcox  
    OP
       2022-12-30 11:04:25 +08:00
    @okakuyang 代码在 12# 前端这块不是太清楚
    rekulas
        14
    rekulas  
       2022-12-30 11:04:53 +08:00
    大概率 fetch 的问题,前端要么直接跳转下载链接下载要么用 fs 去下载,fetch 那肯定会获取完数据才能处理,容易卡死
    okakuyang
        15
    okakuyang  
       2022-12-30 11:05:43 +08:00
    你这样写肯定是要等脚本下载完成才会弹下载框的。压根没有进度条相关的代码。
    AlphaTr
        16
    AlphaTr  
       2022-12-30 11:06:00 +08:00
    @Frankcox 你这是请求结束后才交给浏览器下载保存,主要的网络耗时都在交给浏览器之前,交给浏览器之后基本没有网络流量;所以会有你说的现象
    rekulas
        17
    rekulas  
       2022-12-30 11:06:27 +08:00
    @Frankcox 你这个相当于前端把数据全部拉下来丢内存再下载 效率可想而知
    monkeyWie
        18
    monkeyWie  
       2022-12-30 11:06:30 +08:00
    createObjectURL 就是这样的,直接用#10 的方法就不会这样了
    timnottom
        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()
    okakuyang
        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 就行了
    Frankcox
        21
    Frankcox  
    OP
       2022-12-30 11:07:35 +08:00
    @rekulas
    @okakuyang
    @AlphaTr
    感谢,请问有这块的相关文档文章吗,我去看看
    timnottom
        22
    timnottom  
       2022-12-30 11:10:22 +08:00
    Frankcox
        23
    Frankcox  
    OP
       2022-12-30 11:11:24 +08:00
    @timnottom 谢谢
    rekulas
        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 的方式,现在有不少新生服务都喜欢这个模式,优点是比较灵活
    Frankcox
        25
    Frankcox  
    OP
       2022-12-30 11:49:54 +08:00
    @rekulas 感谢,我之前用 js 发请求主要是为了 header 添加信息处理鉴权,用 a.href 直接下载的话虽然解决了下载的问题,但会有权限的问题,我再看看这块。
    gogogo2000
        26
    gogogo2000  
       2022-12-30 13:39:19 +08:00
    @Frankcox 添加鉴权的话你需要用 worker 方式拦截浏览器的下载请求,然后在 worker 中把 header 加上去
    weiwoxinyou
        27
    weiwoxinyou  
       2022-12-30 14:30:55 +08:00
    有鉴权需求可以试试成熟的 axios 请求库
    darknoll
        28
    darknoll  
       2022-12-30 15:10:31 +08:00
    @Frankcox 你可以把 token 直接放到 URL 的查询参数里面啊
    hellojukay
        29
    hellojukay  
       2022-12-30 18:23:36 +08:00
    别处理下载流量过程,把下载交给浏览器来干,模拟一个 a 标签点击事件。
    webcape233
        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 用来鉴定权限,临时地址对应下载的资源(怎么对应的方法很多,例如临时地址和对应资源地址存到数据库), 后端返回文件即可。 不知道还有啥方法不,我能找到由浏览器来接管下载的方法就是这样了。
    webcape233
        31
    webcape233  
       2023-02-12 00:19:03 +08:00 via iPhone
    当然,其实也可以省掉前面一步,直接用 a 标签来下载,带上 cookie 进行认证
    Frankcox
        32
    Frankcox  
    OP
       2023-02-12 16:45:03 +08:00
    @webcape233 我用的也是类似这个模式,用户发送请求后,后端不执行下载而是根据信息返回一个临时 token ,前端获取到 token 再进行实际下载请求,我同时将这个实际下载请求从中间件中直接放行了,只是使用 token 进行校验。因为我的这个项目目前是公司内部使用,安全性和性能之类的要求不高,暂时就只使用这种方式了。
    至于其他下载方式的话,如果不想专门为了下载文件引入其他系统,那做到这样我觉得差不多了。如果真有特别强烈的需求,那针对文件存储下载单独写一个服务,再提供一个中间件可能更好一些,不过我觉得是没必要了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2933 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 43ms UTC 14:33 PVG 22:33 LAX 07:33 JFK 10:33
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86