<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何實(shí)現(xiàn)文件下載

          共 13546字,需瀏覽 28分鐘

           ·

          2020-09-05 09:21

          大家好我是秋風(fēng),今天帶來(lái)的主題是關(guān)于文件下載,在我之前曾經(jīng)發(fā)過(guò)一篇文件上傳的文章(一文了解文件上傳全過(guò)程(1.8w字深度解析,進(jìn)階必備),反響還不錯(cuò),時(shí)隔多日,由于最近有研究一些媒體相關(guān)的工作,因此打算對(duì)下載做一個(gè)整理,因此他的兄弟篇誕生了,帶你領(lǐng)略文件下載的奧秘。本文會(huì)花費(fèi)你較長(zhǎng)的時(shí)間閱讀,建議先收藏/點(diǎn)贊,然后查看你感興趣的部分,平時(shí)也可以充當(dāng)當(dāng)做字典的效果來(lái)查詢。

          :) 不整不知道,一整,居然整出這么多情況,我只是想簡(jiǎn)單地做個(gè)頁(yè)面仔。

          前言

          一圖覽全文,可以先看看大綱適不適合自己,如果你喜歡則繼續(xù)往下閱讀。

          00e5bd6e1f022dad738b5ef18558be5a.webp一文了解文件下載

          這一節(jié)呢,主要介紹一些前置知識(shí),對(duì)一些基礎(chǔ)知識(shí)的介紹,如果你覺(jué)得你是這個(gè)。??????,你可以跳過(guò)前言。

          21013b5492e544a76e33a64f35f56ae9.webp和榮耀王者說(shuō)你嘛呢?_榮耀_王者表情

          前端的文件下載主要是通過(guò)??,再加上?download屬性,有了它們讓我們的下載變得簡(jiǎn)單。

          download此屬性指示瀏覽器下載 URL 而不是導(dǎo)航到它,因此將提示用戶將其保存為本地文件。如果屬性有一個(gè)值,那么此值將在下載保存過(guò)程中作為預(yù)填充的文件名(如果用戶需要,仍然可以更改文件名)。此屬性對(duì)允許的值沒(méi)有限制,但是?/?和?\?會(huì)被轉(zhuǎn)換為下劃線。大多數(shù)文件系統(tǒng)限制了文件名中的標(biāo)點(diǎn)符號(hào),故此,瀏覽器將相應(yīng)地調(diào)整建議的文件名。( 摘自 https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/a)

          注意:

          因此下載 url 主要有三種方式。(本文大部分以 blob 的方式進(jìn)行演示)

          ee0ce667d3c4bc04a2e08f5db64249dc.webpimage-20200830153314861

          兼容性

          可以看到它的兼容性也非常的可觀(https://www.caniuse.com/#search=download)

          f78de17863eddcd2e0849ca61ae267c3.webpimage-20200817232216749

          為了避免很多代碼的重復(fù)性,因?yàn)槲页殡x出了幾個(gè)公共函數(shù)。(該部分可跳過(guò),名字都比較可讀,之后若是遇到不明白則可以在這里尋找)

          export?function?downloadDirect(url)?{
          ????const?aTag?=?document.createElement('a');
          ????aTag.download?=?url.split('/').pop();
          ????aTag.href?=?url;
          ????aTag.click()
          }
          export?function?downloadByContent(content,?filename,?type)?{
          ????const?aTag?=?document.createElement('a');
          ????aTag.download?=?filename;
          ????const?blob?=?new?Blob([content],?{?type?});
          ????const?blobUrl?=?URL.createObjectURL(blob);
          ????aTag.href?=?blobUrl;
          ????aTag.click();
          ????URL.revokeObjectURL(blob);
          }
          export?function?downloadByDataURL(content,?filename,?type)?{
          ????const?aTag?=?document.createElement('a');
          ????aTag.download?=?filename;
          ????const?dataUrl?=?`data:${type};base64,${window.btoa(unescape(encodeURIComponent(content)))}`;
          ????aTag.href?=?dataUrl;
          ????aTag.click();
          }
          export?function?downloadByBlob(blob,?filename)?{
          ????const?aTag?=?document.createElement('a');
          ????aTag.download?=?filename;
          ????const?blobUrl?=?URL.createObjectURL(blob);
          ????aTag.href?=?blobUrl;
          ????aTag.click();
          ????URL.revokeObjectURL(blob);
          }
          export?function?base64ToBlob(base64,?type)?{
          ????const?byteCharacters?=?atob(base64);
          ????const?byteNumbers?=?new?Array(byteCharacters.length);
          ????for?(let?i?=?0;?i?????????byteNumbers[i]?=?byteCharacters.charCodeAt(i);
          ????}
          ????const?buffer?=?Uint8Array.from(byteNumbers);
          ????const?blob?=?new?Blob([buffer],?{?type?});
          ????return?blob;
          }

          ?????????????????????????????

          (手動(dòng)給不看以上內(nèi)容的大佬畫(huà)分割線)

          ??

          所有示例Github地址: ?https://github.com/hua1995116/node-demo/tree/master/file-download

          在線Demo: https://qiufeng.blue/demo/file-download/index.html

          0f65c5da5c3b8d13a47b30ef0b4008fc.webp前端文件下載

          后端

          本文后端所有示例均以 koa / 原生 js 實(shí)現(xiàn)。

          后端返回文件流

          這種情況非常簡(jiǎn)單,我們只需要直接將后端返回的文件流以新的窗口打開(kāi),即可直接下載了。

          //?前端代碼
          <button?id="oBtnDownload">點(diǎn)擊下載button>
          <script>
          oBtnDownload.onclick?=?function(){
          ????window.open('http://localhost:8888/api/download?filename=1597375650384.jpg',?'_blank')
          }
          script>
          //?后端代碼
          router.get('/api/download',?async?(ctx)?=>?{
          ????const?{?filename?}?=?ctx.query;
          ????const?fStats?=?fs.statSync(path.join(__dirname,?'./static/',?filename));
          ????ctx.set({
          ????????'Content-Type':?'application/octet-stream',
          ????????'Content-Disposition':?`attachment;?filename=${filename}`,
          ????????'Content-Length':?fStats.size
          ????});
          ????ctx.body?=?fs.readFileSync(path.join(__dirname,?'./static/',?filename));
          })

          能夠讓瀏覽器自動(dòng)下載文件,主要有兩種情況:

          一種為使用了Content-Disposition屬性。

          我們來(lái)看看該字段的描述。

          在常規(guī)的HTTP應(yīng)答中,Content-Disposition?響應(yīng)頭指示回復(fù)的內(nèi)容該以何種形式展示,是以內(nèi)聯(lián)的形式(即網(wǎng)頁(yè)或者頁(yè)面的一部分),還是以附件的形式下載并保存到本地 ? --- 來(lái)源 MDN(https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition)

          再來(lái)看看它的語(yǔ)法

          Content-Disposition:?inline
          Content-Disposition:?attachment
          Content-Disposition:?attachment;?filename="filename.jpg"

          很簡(jiǎn)單,只要設(shè)置成最后一種形態(tài)我就能成功讓文件從后端進(jìn)行下載了。

          另一種為瀏覽器無(wú)法識(shí)別的類型

          例如輸入 http://localhost:8888/static/demo.sh,瀏覽器無(wú)法識(shí)別該類型,就會(huì)自動(dòng)下載。

          不知道小伙伴們有沒(méi)有遇到過(guò)這樣的一個(gè)情況,我們輸入一個(gè)正確的靜態(tài) js 地址,沒(méi)有配置Content-Disposition,但是卻會(huì)被意外的下載。

          例如像以下的情況。

          ce33a0c7b01b6527cc68479454d5b661.webp2020-08-30-17.01.5218e46deb3e4cb432264e7e5f4c5bfad4.webp006r3PQBjw1fav4dsikh6j308c0g5gm1

          這很可能是由于你的?nginx?少了這一行配置.

          include?mime.types;

          導(dǎo)致默認(rèn)走了?application/octet-stream,瀏覽器無(wú)法識(shí)別就下載了文件。

          后端返回靜態(tài)站點(diǎn)地址

          通過(guò)靜態(tài)站點(diǎn)下載,這里要分為兩種情況,一種為可能該服務(wù)自帶靜態(tài)目錄,即為同源情況,第二種情況為適用了第三方靜態(tài)存儲(chǔ)平臺(tái),例如阿里云、騰訊云之類的進(jìn)行托管,即非同源(當(dāng)然也有些平臺(tái)直接會(huì)返回)。

          同源

          同源情況下是非常簡(jiǎn)單,先上代碼,直接調(diào)用一下函數(shù)就能輕松實(shí)現(xiàn)下載。

          import?{downloadDirect}?from?'../js/utils.js';
          axios.get('http://localhost:8888/api/downloadUrl').then(res?=>?{
          ????????if(res.data.code?===?0)?{
          ????????????downloadDirect(res.data.data.url);
          ????????}
          })

          非同源

          我們也可以從 MDN 上看到,雖然 download 限制了非同源的情況,但是!!但是!!但是可以使用?blob:?URL 和?data:?URL ,因此我們只要將文件內(nèi)容進(jìn)行下載轉(zhuǎn)化成?blob?就可以了。

          整個(gè)過(guò)程如下

          e4d1d80d6193b7c8848b1f18e93540d7.webpimage-20200830174735143
          <button?id="oBtnDownload">點(diǎn)擊下載button>
          ????<script?type="module">
          ????????import?{downloadByBlob}?from?'../js/utils.js';
          ????????function?download(url)?{
          ????????????axios({
          ????????????????method:?'get',
          ????????????????url,
          ????????????????responseType:?'blob'
          ????????????}).then(res?=>?{
          ????????????????downloadByBlob(res.data,?url.split('/').pop());
          ????????????})?
          ????????}
          ????????oBtnDownload.onclick?=?function(){
          ???????????axios.get('http://localhost:8888/api/downloadUrl').then(res?=>?{
          ????????????????if(res.data.code?===?0)?{
          ????????????????????download(res.data.data.url);
          ????????????????}
          ????????????})
          ????????}
          ????
          script>

          現(xiàn)在非同源的也可以愉快地下載啦。

          后端返回字符串(base64)

          有時(shí)候我們也會(huì)遇到一些新手后端返回字符串的情況,這種情況很少見(jiàn),但是來(lái)了我們也不慌,順便可以向后端小哥秀一波操作,不管啥數(shù)據(jù),咱都能給你下載下來(lái)。

          ps: 前提是安全無(wú)污染的資源 :) ?, 正經(jīng)文章的招牌閃閃發(fā)光。

          這種情況下,我需要模擬下后端小哥的騷操作,因此有后端代碼。

          29b4354e0bad6440544d6112855bc1db.webp994b6f2egy1fgryfevtpvj208c08cmxd

          核心過(guò)程

          4d3aca148a6f2b28bc23d5858d3d1180.webpimage-20200830174752476
          //?node?端
          router.get('/api/base64',?async?(ctx)?=>?{
          ????const?{?filename?}?=?ctx.query;
          ????const?content?=?fs.readFileSync(path.join(__dirname,?'./static/',?filename));
          ????const?fStats?=?fs.statSync(path.join(__dirname,?'./static/',?filename));
          ????console.log(fStats);
          ????ctx.body?=?{
          ????????code:?0,
          ????????data:?{
          ????????????base64:?content.toString('base64'),
          ????????????filename,
          ????????????type:?mime.getType(filename)
          ????????}
          ????}
          })
          //?前端
          <button?id="oBtnDownload">點(diǎn)擊下載button>
          <script?type="module">
          import?{base64ToBlob,?downloadByBlob}?from?'../js/utils.js';
          function?download({?base64,?filename,?type?})?{
          ????const?blob?=?base64ToBlob(blob,?type);
          ????downloadByBlob(blob,?filename);
          }
          oBtnDownload.onclick?=?function(){
          ????axios.get('http://localhost:8888/api/base64?filename=1597375650384.jpg').then(res?=>?{
          ????????if(res.data.code?===?0)?{
          ????????????download(res.data.data);
          ????????}
          ????})
          }
          script>

          思路其實(shí)還是利用了我們上面說(shuō)的??標(biāo)簽。但是在這個(gè)步驟前,多了一個(gè)步驟就是,需要將我們的?base64?字符串轉(zhuǎn)化為二進(jìn)制流,這個(gè)東西,在我的前一篇文件上傳中也常常提到,畢竟文件就是以二進(jìn)制流的形式存在。不過(guò)也很簡(jiǎn)單,js 擁有內(nèi)置函數(shù)?atob。極大地提高了我們轉(zhuǎn)換的效率。

          純前端

          上面介紹借助后端來(lái)完成文件下載的相關(guān)方法,接下來(lái)我們來(lái)介紹介紹純前端來(lái)完成文件下載的一些方法。

          方法一: ?blob:?URL

          023843919f318aa885ea3e29bebb6e4b.webpimage-20200831230800538

          方法二:?data:?URL

          8a19607f3000b9c0de4f4b13f2f48a93.webpimage-20200831230810963

          由于 data:URL 會(huì)有長(zhǎng)度的限制,因此下面的所有例子都會(huì)采用 blob 的方式來(lái)進(jìn)行演示。

          json/text

          下載text和json非常的簡(jiǎn)單,可以直接構(gòu)造一個(gè) Blob。

          Blob(blobParts[,?options])
          返回一個(gè)新創(chuàng)建的 Blob 對(duì)象,其內(nèi)容由參數(shù)中給定的數(shù)組串聯(lián)組成。
          //?html
          <textarea?name=""?id="text"?cols="30"?rows="10">textarea>
          <button?id="textBtn">下載文本button>
          <p>p>
          <textarea?name=""?id="json"?cols="30"?rows="10"?disabled>
          {
          ????"name":?"秋風(fēng)的筆記"
          }
          textarea>
          <button?id="jsonBtn">下載JSONbutton>
          //js
          import?{downloadByContent,?downloadByDataURL}?from?'../js/utils.js';
          textBtn.onclick?=?()?=>?{
          ????????const?value?=?text.value;
          ????????downloadByContent(value,?'hello.txt',?'text/plain');
          ????//?downloadByDataURL(value,?'hello.txt',?'text/plain');
          }
          jsonBtn.onclick?=?()?=>?{
          ????????const?value?=?json.value;
          ????????downloadByContent(value,?'hello.json',?'application/json');
          ?????//?downloadByDataURL(value,?'hello.json',?'application/json');
          }

          效果圖

          a2a3cd35c67c96af7a7af61b2d55ac29.webp2020-08-30-17.53.32

          注釋代碼為 data:URL 的展示部分,由于是第一個(gè)例子,因此我講展示代碼,后面都省略了,但是你也可以通過(guò)調(diào)用?downloadByDataURL?方法,找不到該方法的定義請(qǐng)滑到文章開(kāi)頭哦~

          excel

          excel 可以說(shuō)是我們部分前端打交道很深的一個(gè)場(chǎng)景,什么數(shù)據(jù)中臺(tái),天天需要導(dǎo)出各種報(bào)表。以前都是前端請(qǐng)求后端,來(lái)獲取一個(gè) excel 文件地址。現(xiàn)在讓我們來(lái)展示下純前端是如何實(shí)現(xiàn)下載excel。

          簡(jiǎn)單excel

          表格長(zhǎng)這個(gè)模樣,比較簡(jiǎn)陋的形式

          dced5bb4dd309b63a7b3da54007a9b8f.webpimage-20200829170347728
          const?template?=?'
          ????????????+'xmlns:x="urn:schemas-microsoft-com:office:excel"?'
          ????????????+'xmlns="http://www.w3.org/TR/REC-html40">'
          ????????????+''
          ????????????+''
          ????????????+'{table}<\/body>'
          ????????????+'<\/html>';
          ????const?context?=?template.replace('{table}',?document.getElementById('excel').innerHTML);
          ????downloadByContent(context,?'qiufengblue.xls',?'application/vnd.ms-excel');

          但是編寫(xiě)并不復(fù)雜,依舊是和我們之前一樣,通過(guò)構(gòu)造出?excel?的格式,轉(zhuǎn)化成 blob 來(lái)進(jìn)行下載。

          最終導(dǎo)出的效果

          ba6a0fa7dd777e666be2611bc17fab6a.webpimage-20200829170625763

          element-ui 導(dǎo)出表格

          沒(méi)錯(cuò),這個(gè)就是?element-ui?官方table?的例子。

          607ef2e36da9799fea7712bb6108ab4b.webpimage-20200829170543891

          導(dǎo)出效果如下,可以說(shuō)非常完美。

          66b5ff5680c1f4a92588b8d371aed5c7.webpimage-20200829170912128

          這里我們用到了一個(gè)插件 https://github.com/SheetJS/sheetjs

          使用起來(lái)非常簡(jiǎn)單。

          <template>
          ??????<el-table?id="ele"?border?:data="tableData"?style="width:?100%">
          ????????<el-table-column?prop="date"?label="日期"?width="180">
          ????????el-table-column>
          ????????<el-table-column?prop="name"?label="姓名"?width="180">
          ????????el-table-column>
          ????????<el-table-column?prop="address"?label="地址">
          ????????el-table-column>
          ??????el-table>
          ??????<button?@click="exportExcel">導(dǎo)出excelbutton>
          template>
          <script>
          ...
          methods:?{
          ??exportExcel()?{
          ?????let?wb?=?XLSX.utils.table_to_book(document.getElementById('ele'));
          ?????XLSX.writeFile(wb,?'qiufeng.blue.xlsx');
          ?}
          }
          ...
          script>

          852da8a0d6d568367d6908f7ff1f4ecf.webp完美表情

          word

          講完了?excel我們?cè)賮?lái)講講?word?這可是 office 三劍客另外一大利器。這里我們依舊是利用上述的 blob 的方法進(jìn)行下載。

          簡(jiǎn)單示例

          b9cf817a42154814a7c397064a3315c9.webp2020-08-29-20.13.25

          代碼展示

          exportWord.onclick?=?()?=>?{
          ????const?template?=?'
          ????????????+'xmlns:x="urn:schemas-microsoft-com:office:word"?'
          ????????????+'xmlns="http://www.w3.org/TR/REC-html40">'
          ????????????+''
          ????????????+''
          ????????????+'{table}<\/body>'
          ????????????+'<\/html>';
          ????const?context?=?template.replace('{table}',?document.getElementById('word').innerHTML);
          ????downloadByContent(context,?'qiufeng.blue.doc',?'application/msword');
          }

          效果展示

          6c3fa50da036e8e94bd40cb13c458dbc.webpimage-20200830164208184

          使用?docx.js插件

          如果你想有更高級(jí)的用法,可以使用?docx.js這個(gè)庫(kù)。當(dāng)然用上述方法也是可以高級(jí)定制的。

          代碼

          <button?type="button"?onclick="generate()">下載wordbutton>

          ????<script>
          ????????async?function?generate()?{
          ????????????const?res?=?await?axios({
          ????????????????method:?'get',
          ????????????????url:?'http://localhost:8888/static/1597375650384.jpg',
          ????????????????responseType:?'blob'
          ????????????})
          ????????????const?doc?=?new?docx.Document();
          ????????????const?image1?=?docx.Media.addImage(doc,?res.data,?300,?400)
          ????????????doc.addSection({
          ????????????????properties:?{},
          ????????????????children:?[
          ????????????????????new?docx.Paragraph({
          ????????????????????????children:?[
          ????????????????????????????new?docx.TextRun("歡迎關(guān)注[秋風(fēng)的筆記](méi)公眾號(hào)").break(),
          ????????????????????????????new?docx.TextRun("").break(),
          ????????????????????????????new?docx.TextRun("定期發(fā)送優(yōu)質(zhì)文章").break(),
          ????????????????????????????new?docx.TextRun("").break(),
          ????????????????????????????new?docx.TextRun("美團(tuán)點(diǎn)評(píng)2020校招-內(nèi)推").break(),
          ????????????????????????],
          ????????????????????}),
          ????????????????????new?docx.Paragraph(image1),
          ????????????????],
          ????????????});?

          ????????????docx.Packer.toBlob(doc).then(blob?=>?{
          ????????????????console.log(blob);
          ????????????????saveAs(blob,?"qiufeng.blue.docx");
          ????????????????console.log("Document?created?successfully");
          ????????????});
          ????????}
          ????
          script>

          效果(沒(méi)有打廣告...隨便找了張圖,強(qiáng)行不承認(rèn)系列)

          6bdce49b1ebab52c2a4225b83a6d138f.webp9150e4e5ly1fl8qavz6quj20hs0hsjvl467a88b75f393edcba0646768d260c0e.webp2020-08-30-18.32.09

          zip下載

          前端壓縮還是非常有用的,在一定的場(chǎng)景下,可以節(jié)省流量。而這個(gè)場(chǎng)景比較使用于,例如前端打包圖片下載、前端打包下載圖標(biāo)。

          一開(kāi)始我以為我 https://tinypng.com/ 就是用了這個(gè),結(jié)果我發(fā)現(xiàn)我錯(cuò)了...仔細(xì)一想,因?yàn)樗鼔嚎s好的圖片是存在后端的,如果使用前端打包的話,反而要去請(qǐng)求所有壓縮的圖片從而來(lái)獲取圖片流。如果用后端壓縮話,可以有效節(jié)省流量。嗯。。。失敗例子告終。

          后來(lái)又以為https://www.iconfont.cn/打包下載圖標(biāo)的時(shí)候,使用了這個(gè)方案....發(fā)現(xiàn)....我又錯(cuò)了...但是我們分析一下.

          f86efe69ed3bc2b295bcbd4225cdbd99.webpimage-20200829204540440

          它官網(wǎng)都是 svg 渲染的圖標(biāo),對(duì)于 svg 下載的時(shí)候,完全可以使用前端打包下載。但是,它還支持 font 以及 jpg 格式,所以為了統(tǒng)一,采用了后端下載,能夠理解。那我們就來(lái)實(shí)現(xiàn)這個(gè)它未完成的功能,當(dāng)然我們還需要用到一個(gè)插件,就是 jszip。

          這里我從以上找了兩個(gè) svg 的圖標(biāo)。

          66b5ff5680c1f4a92588b8d371aed5c7.webpimage-20200829204937044

          實(shí)現(xiàn)代碼

          download.onclick?=?()?=>?{
          ????????const?zip?=?new?JSZip();
          ????????const?svgList?=?[{
          ????????????id:?'demo1',
          ????????},?{
          ????????????id:?'demo2',
          ????????}]
          ????????svgList.map(item?=>?{
          ????????????zip.file(item.id?+?'.svg',?document.getElementById(item.id).outerHTML);
          ????????})
          ????????zip.generateAsync({?
          ????????????type:?'blob'
          ????????}).then(function(content)?{
          ????????????//?下載的文件名
          ????????????var?filename?=?'svg'?+?'.zip';
          ????????????//?創(chuàng)建隱藏的可下載鏈接
          ????????????var?eleLink?=?document.createElement('a');
          ????????????eleLink.download?=?filename;
          ????????????//?下載內(nèi)容轉(zhuǎn)變成blob地址
          ????????????eleLink.href?=?URL.createObjectURL(content);
          ????????????//?觸發(fā)點(diǎn)擊
          ????????????eleLink.click();
          ????????????//?然后移除
          ????????});
          ????}
          32e480ca41cdeda019c9a6c74c9ab2ec.webp2020-08-29-20.52.42

          查看文件夾目錄,已經(jīng)將 SVG 打包下載完畢。

          f7e5f90387993038670e43239d43b334.webpimage-20200829205329532

          瀏覽器文件系統(tǒng)(實(shí)驗(yàn)性)

          ca40153cd76d12bf78d6b99ffac51f88.webpimage-20200817234129788

          在我電腦上都有這么一個(gè)瀏覽器,用來(lái)學(xué)習(xí)和調(diào)試?chrome?的最新新特性, 如果你的電腦沒(méi)有,建議你安裝一個(gè)。

          玩這個(gè)特性需要打開(kāi) chrome 的實(shí)驗(yàn)特性?chrome://flags?=>?#native-file-system-api?=>?enable, 因?yàn)閷?shí)驗(yàn)特性都會(huì)伴隨一些安全或者影響原本的渲染的行為,因此我再次強(qiáng)烈建議,下載一個(gè)金絲雀版本的 chrome 來(lái)進(jìn)行玩耍。

          <textarea?name=""?id="textarea"?cols="30"?rows="10">textarea>
          <p><button?id="btn">下載button>p>
          <script>
          ????btn.onclick?=?async?()?=>?{
          ????????const?handler?=?await?window.chooseFileSystemEntries({
          ????????????type:?'save-file',
          ????????????accepts:?[{
          ????????????????description:?'Text?file',
          ????????????????extensions:?['txt'],
          ????????????????mimeTypes:?['text/plain'],
          ????????????}],
          ????????});

          ????????const?writer?=?await?handler.createWritable();
          ????????await?writer.write(textarea.value);
          ????????await?writer.close();
          ????}
          script>

          實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單。卻飛一般的感覺(jué)。

          9534f28b1e7cd4334afe275ec3b7fb6f.webp2020-08-18-00.13.29

          其他場(chǎng)景

          H5文件下載

          一般在 h5 下載比較多的是 pdf 或者是 apk 的下載。

          Android

          在安卓瀏覽器中,瀏覽器直接下載文件。

          ios

          由于ios的限制,無(wú)法進(jìn)行下載,因此,可以使用復(fù)制 url ,來(lái)代替下載。

          import?{downloadDirect}?from?'../js/utils.js';
          const?btn?=?document.querySelector('#download-ios');
          if?(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent))?{
          ????const?clipboard?=?new?ClipboardJS(btn);
          ????clipboard.on('success',?function?()?{
          ????????alert('已復(fù)制鏈接,打開(kāi)瀏覽器粘貼鏈接下載');
          ????});
          ????clipboard.on('error',?function?(e)?{
          ????????alert('系統(tǒng)版本過(guò)低,復(fù)制鏈接失敗');
          ????});
          }?else?{
          ????btn.onclick?=?()?=>?{
          ????????downloadDirect(btn.dataset.clipboardText)
          ????}
          }

          更多

          對(duì)于 apk 等下載包可以使用這個(gè)包(本人暫時(shí)沒(méi)有試驗(yàn),接觸不多,回頭熟悉了再回來(lái)補(bǔ)充。)

          https://github.com/jawidx/web-launch-app

          e4be7e9bfb5c88d407be27174e52e60b.webpimage-20200830145258473

          大文件的分片下載

          最近在開(kāi)發(fā)媒體流相關(guān)的工作的時(shí)候,發(fā)現(xiàn)在加載 mp4 文件的時(shí)候,發(fā)現(xiàn)了一個(gè)比較有意思的現(xiàn)象,視頻流并不需要將整個(gè) mp4 下載完才進(jìn)行播放,并且伴隨了很多狀態(tài)碼為 206 的請(qǐng)求,乍一看有點(diǎn)像流媒體(HLS等)的韻味。

          fa6c05d1a8ec83fc3b90cce332a4895a.webp2020-08-29-21.31.29

          覺(jué)得這個(gè)現(xiàn)象非常的有意思,他能夠分片地加載資源,這對(duì)于體驗(yàn)或者是流量的節(jié)省都是非常大的幫助。最終發(fā)現(xiàn)它帶了一個(gè)名為 Range 的頭。我們來(lái)看看 MDN 的解釋。

          The?Range?是一個(gè)請(qǐng)求首部,告知服務(wù)器返回文件的哪一部分。在一個(gè)?Range?首部中,可以一次性請(qǐng)求多個(gè)部分,服務(wù)器會(huì)以 multipart 文件的形式將其返回。如果服務(wù)器返回的是范圍響應(yīng),需要使用?206?Partial Content?狀態(tài)碼。?摘自 MDN

          語(yǔ)法

          Range:?=-
          Range:?=-
          Range:?=-,?-
          Range:?=-,?-,?-

          Node實(shí)現(xiàn)

          既然我們知道了它的原理,就來(lái)自己實(shí)現(xiàn)一下。

          router.get('/api/rangeFile',?async(ctx)?=>?{
          ????const?{?filename?}?=?ctx.query;
          ????const?{?size?}?=?fs.statSync(path.join(__dirname,?'./static/',?filename));
          ????const?range?=?ctx.headers['range'];
          ????if?(!range)?{
          ????????ctx.set('Accept-Ranges',?'bytes');
          ????????ctx.body?=?fs.readFileSync(path.join(__dirname,?'./static/',?filename));
          ????????return;
          ????}
          ????const?{?start,?end?}?=?getRange(range);
          ????if?(start?>=?size?||?end?>=?size)?{
          ????????ctx.response.status?=?416;
          ????????ctx.set('Content-Range',?`bytes?*/${size}`);
          ????????ctx.body?=?'';
          ????????return;
          ????}
          ????ctx.response.status?=?206;
          ????ctx.set('Accept-Ranges',?'bytes');
          ????ctx.set('Content-Range',?`bytes?${start}-${end???end?:?size?-?1}/${size}`);
          ????ctx.body?=?fs.createReadStream(path.join(__dirname,?'./static/',?filename),?{?start,?end?});
          })

          Nginx實(shí)現(xiàn)

          發(fā)現(xiàn) nginx 不需要寫(xiě)任何代碼就默認(rèn)支持了 range 頭,想著我一定知道它到底是支持,還是加入了什么模塊,或者是我默認(rèn)開(kāi)啟了什么配置,找了半天沒(méi)有找到什么額外的配置。

          bd2b9a4cdfb9016e85e09fbffa84b77d.webp3630px-Nginx_logo-1

          正當(dāng)我準(zhǔn)備放棄的時(shí)候,靈光一現(xiàn),去看看源碼吧,說(shuō)不定會(huì)有發(fā)現(xiàn),去查了 nginx 源碼相關(guān)的內(nèi)容,用了慣用的反推方式,才發(fā)現(xiàn)原來(lái)是max_ranges這個(gè)字段。

          https://github.com/nginx/nginx/blob/release-1.13.6/src/http/modules/ngx_http_range_filter_module.c#L166

          這也怪我一開(kāi)始文檔閱讀不夠仔細(xì),浪費(fèi)了大量的時(shí)間。

          :) 其實(shí)我對(duì) nginx 源碼也不熟悉,這里可以用個(gè)小技巧,直接在源碼庫(kù) 搜索 206 然后 發(fā)現(xiàn)了一個(gè)宏命令

          #define?NGX_HTTP_PARTIAL_CONTENT???????????206

          然后順藤摸瓜,直接找到這個(gè)宏命令NGX_HTTP_PARTIAL_CONTENT用到的地方,這樣一步一步就慢慢能找到我們想要的。

          默認(rèn) nginx 是自動(dòng)開(kāi)啟 range 頭的, 如果不需要配置,則配置?max_range: 0;

          Nginx 配置文檔 http://nginx.org/en/docs/http/ngx_http_core_module.html#max_ranges

          總結(jié)

          我們可以來(lái)總結(jié)一下,其實(shí)全文主要講了(xbb)兩個(gè)核心的知識(shí),一個(gè)是?blob?一個(gè)a?標(biāo)簽,另外還要注意對(duì)于大文件,服務(wù)器的優(yōu)化策略,可以通過(guò)?Range?來(lái)分片加載。

          e23a482802621e4fbbd53598198c59b3.webpimage-20200830181216353

          參考資料

          https://github.com/dolanmiu/docx

          https://github.com/SheetJS/sheetjs

          https://juejin.im/post/6844903763359039501

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲 小说区 图片区 都市 | av天堂手机网 | 欧美精品永久躁夜夜躁 | 国产视频97 | 婷婷爱综合激情 |