前言
前端如何實(shí)現(xiàn)下載文件呢?隨著前端技術(shù)的發(fā)展捌刮,越來越多的前端需求中會(huì)出現(xiàn)下載文件這樣的需求。
看著掘金很多人在近期不斷的分享有關(guān)的文章舒岸,我總結(jié)了下自己的經(jīng)驗(yàn)绅作,根據(jù)不同情況,總結(jié)了一篇算是前端文件下載的通識篇蛾派,如果你對這方面完全不懂或者沒有任何方案俄认,那么本文會(huì)給你一個(gè)很不錯(cuò)的啟示。
方案一 :原生提交洪乍,后端返回文件流
這種方式是利用form.submit直接向后端提交,后端返回文件流生成的文件眯杏,后端處理成功后會(huì)直接返回到頁面,瀏覽器會(huì)整理并打開自己的保存下載文件機(jī)制 典尾。
優(yōu)點(diǎn) :沒有兼容問題役拴,傳統(tǒng)方式
缺點(diǎn):拿不到后端處理這個(gè)過程的時(shí)機(jī)糊探,無法根據(jù)回調(diào)函數(shù)做交互以及進(jìn)度提示
// 后端參考代碼
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", HttpUtility.UrlEncode("咨詢記錄導(dǎo)出.xls", Encoding.GetEncoding("UTF-8")));
// 參考代碼
function exportRecord() {
var $form = $("<form>"); //定義一個(gè)form表單
$form.hide().attr({target:'',method:'post','action':'/xxx'});
var $input = $("<input>");
$input.attr({"type":"hidden","name":'req'}).val(req);
$form.append($input).appendTo($("body")).submit().remove();
}
方案 二 :ajax提交钾埂,后端返回在線文件地址
利用ajax或者新生的axios去提交請求,后端會(huì)返回一個(gè)線上的文件地址科平,前端可以通過原生的window.open打開這個(gè)地址就可以實(shí)現(xiàn)下載褥紫;也可以通過a標(biāo)簽設(shè)置href以及download屬性,并自動(dòng)點(diǎn)擊實(shí)現(xiàn)其下載功能瞪慧,關(guān)于其兼容性問題髓考,可以判斷download屬性是否存在來彌補(bǔ)。
優(yōu)點(diǎn) :可以拿到其返回時(shí)機(jī)弃酌,可以做交互
缺點(diǎn) :線上會(huì)存儲(chǔ)大量的中間臨時(shí)文件氨菇,可以用設(shè)置時(shí)限來優(yōu)化。另外涉及用戶隱私的問題妓湘,可以用token等驗(yàn)證機(jī)制實(shí)現(xiàn)查蓉。
// 參考方案
$.ajax({
type: "post",
url: "/xxx",
data: data,
success: function (res) {
tool.loadingend();
if(res.Status){
// window.open或者a標(biāo)簽下載
var isSupportDownload = 'download' in document.createElement('a');
if(isSupportDownload){
var $a = $("<a>") ;
$a.attr({href:res.url,download:'filename'}).hide().appendTo($("body"))[0].click();
}else{
window.open(res.url)
}
}else{
tool.tip(res.Message);
}
}
})
- a標(biāo)簽download屬性
- 方案二 :補(bǔ)充方案 :利用form表單提交下載文件(ajax無法直接處理返回的文件類型),用于解決window.open方案被瀏覽器攔截的情況。
let $form = $("<form>") ;
$form.attr({method:"get",action:res.Message}).hide();
let queryStr = res.Message.split("?")[1];
let queryObj = qs.parse(queryStr) ;
$("body").append($form);
for(let p in queryObj){
let $input =$("<input type='hidden'>") ;
$input.attr({"name":p,value:queryObj[p]}).appendTo($form);
}
$form.submit().remove();
方案三 :前端利用download模塊進(jìn)行下載
支持場景 :
與上面的方案相比榜贴,這個(gè)模塊提供的方案更加完善豌研,而不是局限于某種方案,使用率很高唬党。在源碼中鹃共,我們可以看到在這個(gè)模塊中針對各個(gè)瀏覽器和相應(yīng)的屬性是否支持進(jìn)行了比較全面的兼容。其對應(yīng)的下載文件方案包括了以下幾種驶拱。
- window.open(url)打開某個(gè)文件地址
- iframe的框架中霜浴,設(shè)置src屬性,通過iframe進(jìn)行文件的下載蓝纲,支持文件地址
- 通過form標(biāo)簽阴孟,設(shè)置action的文件地址房铭,然后通過form的提交來完成文件的下載(支持二進(jìn)制)
方案小結(jié):
對于常規(guī)的支持文件地址的下載,兼容性非常好温眉,而對于傳統(tǒng)的文件流性質(zhì)的缸匪,通過form標(biāo)簽也可以進(jìn)行簡單的支持,可以說是非常好的方案了类溢。當(dāng)然如果你需要那么全面的方案凌蔬,大多數(shù)情況用其中一個(gè)就可以了。
方案四 :h5新生方案下載
這個(gè)我覺得張鑫旭大佬介紹的蠻多的闯冷,應(yīng)該上手足夠了砂心,就不多介紹了。除了a標(biāo)簽提供的download屬性蛇耀,多介紹了一種html:blob的方式辩诞。另外針對圖片可以通過base64的方式。
傳送門:h5新方式下載文件
個(gè)人建議:雖然新技術(shù)很好纺涤,但酌情使用译暂,而且這里沒有考慮任何兼容,也沒有談?wù)摰狡渌囊恍┪募愋土么叮热绫砀裢庥溃琾df,大文件拧咳,視頻音頻的下載情況等伯顶。所以不是很建議把這個(gè)當(dāng)做很常規(guī)的方案來考慮。
方案五 :file-saver
模塊地址:https://npm.taobao.org/package/file-saver骆膝,推薦使用祭衩,下載量以及穩(wěn)定性足夠好。支持新出的h5特性的加載方式阅签,也就是方案五的部分掐暮。
github托管地址:https://github.com/eligrey/FileSaver.js
在模塊的介紹中:詳細(xì)說明了瀏覽器支持的情況,以及可以支持的下載范圍愉择,保存為的文件類型劫乱,與其我們?nèi)ビ没A(chǔ)知識踩雷,還是建議大家用成熟的模塊方案去解決需求相關(guān)的問題锥涕。支持不了就退步用傳統(tǒng)的方案解決衷戈,讓后端提供直接的文件地址,要知道后端有更多的成熟的技術(shù)架包层坠,對于前端來說還是萌新不確定的方案殖妇,后端早已經(jīng)有了答案。
說明:我們之前的需求是希望下載一個(gè)表格文件破花,之前的方案是用后端生成文件地址谦趣,然后進(jìn)行下載疲吸,其設(shè)置的返回response content type 為application/vnd.ms-excel (常規(guī)類型application/json)。后面發(fā)現(xiàn)有這個(gè)模塊前鹅,基本使用還是體驗(yàn)蠻好的摘悴,此時(shí)的約定變成了后端根據(jù)查詢的數(shù)據(jù)生成一個(gè)二進(jìn)制的文件流,這樣的好處是如果么有必要的時(shí)候可以減少在阿里云或者其他服務(wù)器暫存很多文件舰绘。
拓展思考下:在大家的公司里有沒有遇到過類似的需求蹂喻,按照我之前的經(jīng)驗(yàn)是本來是想后端返回一個(gè)生成之后的文件地址,但后端的回復(fù)是由于采用了負(fù)載均衡捂寿,這個(gè)地址再去請求時(shí)不一定會(huì)請求到這個(gè)服務(wù)器口四,所以之前的前后端協(xié)調(diào)方案是放到了阿里云,然后通過設(shè)置權(quán)限和時(shí)效來保證文件的臨時(shí)性秦陋,用戶也可以在相似請求時(shí)不用重復(fù)請求數(shù)據(jù)庫蔓彩,重新生成文件,因?yàn)橹貜?fù)的數(shù)據(jù)內(nèi)容會(huì)直接返回已經(jīng)上傳到阿里云的文件地址驳概。
源碼解析:
在其源碼中赤嚼,主要是針對返回的http的resonsetype做了要求,然后針對返回的地址進(jìn)行處理抡句,其中涉及到重要的代碼:
//利用a標(biāo)簽下載
var a = document.createElement('a')
a.href = blob
//觸發(fā)點(diǎn)擊事件
node.dispatchEvent(new MouseEvent('click'))
// reader 進(jìn)行解析
var reader = new FileReader()
var url = reader.result
//得到可解析的地址
_global.URL || _global.webkitURL探膊,
URL.createObjectURL(blob)
//對 cors 跨域是否支持
return xhr.status >= 200 && xhr.status <= 299
filereader的官方介紹:https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
總結(jié)
綜上,無論是偏傳統(tǒng)的方案待榔,還是比較全面兼容的根據(jù)文件地址下載的方式,還是h5新出的webapi的方式都有比較好的認(rèn)識流济,如果你對相關(guān)的知識點(diǎn)或者方案有進(jìn)一步研究的興趣锐锣,建議針對官方api的相關(guān)文章或者已經(jīng)開源出的兩個(gè)模塊進(jìn)行深度的優(yōu)化和研究效率更佳。
覺得還不錯(cuò)绳瘟,給個(gè)贊加關(guān)注吧雕憔,謝謝大家的支持。