node实现文件下载不得不说的那些事儿

这几天一直在做远程文件下载的事,现在总算有了解决,特来记录一下踩过的坑和想揍自己的心

需求

  1. 应用场景是这样的,底层逻辑数据请求接口是由Java写的,也就是说原始文件存在Java服务端,返回时有加密措施
  2. 由于工作需要,前端获取数据操作需要node服务器做中间转发
  3. Java接口使用post方式来请求下载
  4. 前端点击下载后浏览器启用内置下载器进行下载,并能看到进度如下图所示
    《node实现文件下载不得不说的那些事儿》

先说总结,下附过程

前端GET下载和POST下载的对比

  1. 一般情况下,如果是网盘应用或者不涉及多文件下载的场景(如本例中node作为文件服务器,可以直接与前端交互时),完全可以通过拼接GET请求url进行模拟点击下载,系统开销还小,响应快。如果像本例中这样的场景会遇到这样一个问题,详见链接
  2. 当请求参数过长或为了安全,就需要用到POST下载。

最终采用的方案

  • 前端通过模拟表单提交POST请求
  • node端通过piperesponseAresponseB串联起来,如responseA.pipe(responseB)
  • Done

最开始的思路

最开始没搞清楚怎么用POST请求下载且前端该怎样接收和处理,关键字node 前端下载搜到的绝大多数都是用GET链接下载,加上刚刚接触node没有很好理解流的概念,因此一根筋的想如何通过POST请求转换成GET请求下载,于是自作主张采用了笨办法,走上了一条差点没回来的路:

  1. 前端点击下载,发送post请求Anode
  2. node获取参数向Java端发送post请求B把文件先下载到node本地(Java返回的记为responseA)并用responseB返回前端文件地址文件名
  3. 前端获取到responseB后拼接成get请求模拟a标签点击去下载node中的文件
  4. 下载完成后再将node端对应文件删除。

写到这里自己都忍不住想锤自己,给自己挖坑不说,这样来回请求下载,流量double,真的是败家。

涉及的知识点

  • angular前端访问node跨域设置

    在前端项目根目录下新建
    proxy.conf.json文件,配置接口转发

    {
        "/api": {
            "target" : "http://localhost:3000"//server端port
        }
    }

    保存后,配置
    package.json文件里
    start命令如下,保存后重新运行就好

    "start": "ng serve --proxy-config proxy.conf.json",
  • node如何发送get/post请求
  • stream、buffer的概念:文章一 文章二
  • 前端GET下载的三种方式

    1. 直接将拼接好的GET请求url赋值给a标签,模拟点击
    2. 先获取数据流存进blob对象,a.href = window.URL.createObjectURL(blob)

      每次调用
      createObjectUR的时候,一个新的URL对象就被创建了.即使你已经为同一个文件创建过一个URL. 如果你不再需要这个对象,要释放它,需要使用
      URL.revokeObjectURL()方法. 当页面被关闭,浏览器会自动释放它,但是为了最佳性能和内存使用,当确保不再用得到它的时候,就应该释放它.

    3. 新建一个隐藏的iframesrc设置为如上一步的url即可
  • 前端如何接收文件流并下载

    原生
    xhr请求写法

    var xhr = new XMLHttpRequest();    
        xhr.open("get", url, true);
        xhr.responseType = "blob";
        xhr.onload = function() {
            if (this.status == 200) {
                var blob = this.response;
                var img = document.createElement("img");
                img.onload = function(e) {
                  window.URL.revokeObjectURL(img.src); 
                };
                img.src = window.URL.createObjectURL(blob);
           $("#imgcontainer").html(img);
     } } xhr.send();

    axios请求写法

    axios.post("/api/download_reports",msgArr,{
        responseType:'blob',
        onDownloadProgress (a){
            //监听下载进度
            if(a.lengthComputable){
                let percent = (a.loaded*100/a.total).toFixed(2)
                console.log(percent)
                $('#percent').html(tempLoaded)
            }
        }
    })
    .then(response => {
        console.log(response)
        if(response.status == 200){
            const blob = new Blob([response.data],{type: 'application/octet-stream'});
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = baseName+'.zip';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
        }
    })
    .catch(error => {
        console.log(error)
    })
  • 前端如何获取下载进度,并进一步完成进度条设置

    axios.post('/喵',postData, {
        onUploadProgress (a){
        //上传进度同理
            console.log(a)
        },
        onDownloadProgress (a){
        //控制台输出后,可以发现我们能够通过a.loaded*100/a.total来获得下载进度
        //但需注意的是如果node端的responseB没有设置'Content-Length'即二进制流size的话
        //axios.post此时获取到的下载进度事件对象a里lengthComputable为false,进而a.total=0
        //进而无法获取百分比进度
            console.log(a)
        }
    })
    
  • 前端POST下载的两种方式

    这个没有什么好说的,唯一可能要注意的就是表单里input传参的时候,如果参数比较多,可以用JSON.stringify()转换,只向后端发送一个字符串就好

以上就是自己对node实现文件前端下载的一些理解,如有不妥欢迎交流指正~

    原文作者:shaoyuLee
    原文地址: https://segmentfault.com/a/1190000017462391
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞