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 · 3561 次点击
    这是一个创建于 718 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在尝试实现一个功能,从 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   ·   实用小工具   ·   5179 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 09:40 · PVG 17:40 · LAX 01:40 · JFK 04:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.