乐趣区

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

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

应用场景是这样的,底层逻辑数据请求接口是由 Java 写的,也就是说原始文件存在 Java 服务端, 返回时有加密措施
由于工作需要,前端获取数据操作需要 node 服务器做中间转发

Java 接口使用 post 方式来请求下载
前端点击下载后浏览器启用内置下载器进行下载,并能看到进度如下图所示

先说总结,下附过程
前端 GET 下载和 POST 下载的对比

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

当请求参数过长或为了安全,就需要用到 POST 下载。

最终采用的方案

前端通过模拟表单提交 POST 请求

node 端通过 pipe 将 responseA 和 responseB 串联起来,如 responseA.pipe(responseB)

Done

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

前端点击下载,发送 post 请求 A 给 node

由 node 获取参数向 Java 端发送 post 请求 B 把文件先下载到 node 本地 (Java 返回的记为 responseA) 并用 responseB 返回前端文件地址和文件名

前端获取到 responseB 后拼接成 get 请求模拟 a 标签点击去下载 node 中的文件
下载完成后再将 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 下载的三种方式

直接将拼接好的 GET 请求 url 赋值给 a 标签,模拟点击

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

新建一个隐藏的 iframe,src 设置为如上一步的 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 实现文件前端下载的一些理解,如有不妥欢迎交流指正~

退出移动版