这几天一直在做远程文件下载的事,现在总算有了解决,特来记录一下踩过的坑和想揍自己的心
需求
- 应用场景是这样的,底层逻辑数据请求接口是由
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){
//监听下载进度
let percent = (a.loaded*100/a.total).toFixed(2)
console.log(percent)
$('#percent').html(percent)
}
})
.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)
}
})
这个没有什么好说的,唯一可能要注意的就是表单里input传参的时候,如果参数比较多,可以用JSON.stringify()转换,只向后端发送一个字符串就好
以上就是自己对node实现文件前端下载的一些理解,如有不妥欢迎交流指正~