這幾天一直在做遠(yuǎn)程文件下載的事路鹰,現(xiàn)在總算有了解決矾飞,特來記錄一下踩過的坑和想揍自己的心
需求
- 應(yīng)用場(chǎng)景是這樣的枚冗,底層邏輯數(shù)據(jù)請(qǐng)求接口是由
Java
寫的姓建,也就是說原始文件存在Java
服務(wù)端,返回時(shí)有加密措施 - 由于工作需要诞仓,前端獲取數(shù)據(jù)操作需要
node
服務(wù)器做中間轉(zhuǎn)發(fā) -
Java
接口使用post
方式來請(qǐng)求下載 -
前端點(diǎn)擊下載后瀏覽器啟用內(nèi)置下載器進(jìn)行下載,并能看到進(jìn)度如下圖所示
先說總結(jié)速兔,下附過程
前端GET下載和POST下載的對(duì)比
- 一般情況下墅拭,如果是網(wǎng)盤應(yīng)用或者不涉及多文件下載的場(chǎng)景(如本例中
node
作為文件服務(wù)器,可以直接與前端交互時(shí))涣狗,完全可以通過拼接GET
請(qǐng)求url
進(jìn)行模擬點(diǎn)擊下載谍婉,系統(tǒng)開銷還小舒憾,響應(yīng)快。如果像本例中這樣的場(chǎng)景會(huì)遇到這樣一個(gè)問題穗熬,詳見鏈接 - 當(dāng)請(qǐng)求參數(shù)過長(zhǎng)或?yàn)榱税踩朴兀托枰玫?code>POST下載。
最終采用的方案
- 前端通過
模擬表單
提交POST
請(qǐng)求 -
node
端通過pipe
將responseA
和responseB
串聯(lián)起來唤蔗,如responseA.pipe(responseB)
- Done
最開始的思路
最開始沒搞清楚怎么用POST
請(qǐng)求下載且前端該怎樣接收和處理招拙,關(guān)鍵字node 前端下載
搜到的絕大多數(shù)都是用GET
鏈接下載,加上剛剛接觸node
沒有很好理解流的概念措译,因此一根筋的想如何通過POST
請(qǐng)求轉(zhuǎn)換成GET
請(qǐng)求下載别凤,于是自作主張采用了笨辦法
,走上了一條差點(diǎn)沒回來的路:
- 前端點(diǎn)擊下載领虹,發(fā)送
post請(qǐng)求A
給node
- 由
node
獲取參數(shù)向Java
端發(fā)送post請(qǐng)求B
把文件先下載到node
本地(Java
返回的記為responseA
)并用responseB
返回前端文件地址
和文件名
- 前端獲取到
responseB
后拼接成get請(qǐng)求
模擬a標(biāo)簽
點(diǎn)擊去下載node
中的文件 - 下載完成后再將
node
端對(duì)應(yīng)文件刪除规哪。
寫到這里自己都忍不住想錘自己,給自己挖坑不說塌衰,這樣來回請(qǐng)求下載诉稍,流量double,真的是敗家最疆。
涉及的知識(shí)點(diǎn)
-
angular前端訪問node跨域設(shè)置
- 在前端項(xiàng)目根目錄下新建
proxy.conf.json
文件,配置接口轉(zhuǎn)發(fā)
- 在前端項(xiàng)目根目錄下新建
{
"/api": {
"target" : "http://localhost:3000"http://server端port
}
}
- 保存后杯巨,配置
package.json
文件里start
命令如下,保存后重新運(yùn)行就好
"start": "ng serve --proxy-config proxy.conf.json",
- node如何發(fā)送get/post請(qǐng)求
- stream努酸、buffer的概念:文章一 文章二
-
前端GET下載的三種方式
- 直接將拼接好的
GET
請(qǐng)求url
賦值給a標(biāo)簽
服爷,模擬點(diǎn)擊 - 先獲取數(shù)據(jù)流存進(jìn)
blob
對(duì)象,a.href = window.URL.createObjectURL(blob)
- 直接將拼接好的
每次調(diào)用
createObjectUR
的時(shí)候,一個(gè)新的URL對(duì)象就被創(chuàng)建了.即使你已經(jīng)為同一個(gè)文件創(chuàng)建過一個(gè)URL. 如果你不再需要這個(gè)對(duì)象,要釋放它,需要使用URL.revokeObjectURL()
方法. 當(dāng)頁面被關(guān)閉,瀏覽器會(huì)自動(dòng)釋放它,但是為了最佳性能和內(nèi)存使用,當(dāng)確保不再用得到它的時(shí)候,就應(yīng)該釋放它.
- 新建一個(gè)隱藏的
iframe
获诈,src
設(shè)置為如上一步的url
即可
-
前端如何接收文件流并下載
- 原生
xhr
請(qǐng)求寫法參考
- 原生
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
請(qǐng)求寫法
axios.post("/api/download_reports",msgArr,{
responseType:'blob',
onDownloadProgress (a){
//監(jiān)聽下載進(jìn)度
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)
})
- 前端如何獲取下載進(jìn)度仍源,并進(jìn)一步完成進(jìn)度條設(shè)置
axios.post('/喵',postData, {
onUploadProgress (a){
//上傳進(jìn)度同理
console.log(a)
},
onDownloadProgress (a){
//控制臺(tái)輸出后,可以發(fā)現(xiàn)我們能夠通過a.loaded*100/a.total來獲得下載進(jìn)度
//但需注意的是如果node端的responseB沒有設(shè)置'Content-Length'即二進(jìn)制流size的話
//axios.post此時(shí)獲取到的下載進(jìn)度事件對(duì)象a里lengthComputable為false舔涎,進(jìn)而a.total=0
//進(jìn)而無法獲取百分比進(jìn)度笼踩,詳見上一知識(shí)點(diǎn)
console.log(a)
}
})
這個(gè)沒有什么好說的,唯一可能要注意的就是表單里input傳參的時(shí)候亡嫌,如果參數(shù)比較多嚎于,可以用JSON.stringify()轉(zhuǎn)換,只向后端發(fā)送一個(gè)字符串就好
以上就是自己對(duì)node實(shí)現(xiàn)文件前端下載的一些理解挟冠,如有不妥歡迎交流指正~