<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>

          用騷操作解決Spring Boot上傳大文件的問(wèn)題

          共 3462字,需瀏覽 7分鐘

           ·

          2021-01-11 17:43


          點(diǎn)擊上方?泥瓦匠 關(guān)注我!

          最近遇見(jiàn)一個(gè)需要上傳超大大文件的需求,調(diào)研了七牛和騰訊云的切片分段上傳功能,因此在此整理前端大文件上傳相關(guān)功能的實(shí)現(xiàn)。

          在某些業(yè)務(wù)中,大文件上傳是一個(gè)比較重要的交互場(chǎng)景,如上傳入庫(kù)比較大的Excel表格數(shù)據(jù)、上傳影音文件等。如果文件體積比較大,或者網(wǎng)絡(luò)條件不好時(shí),上傳的時(shí)間會(huì)比較長(zhǎng)(要傳輸更多的報(bào)文,丟包重傳的概率也更大),用戶不能刷新頁(yè)面,只能耐心等待請(qǐng)求完成。


          下面從文件上傳方式入手,整理大文件上傳的思路,并給出了相關(guān)實(shí)例代碼,由于PHP內(nèi)置了比較方便的文件拆分和拼接方法,因此服務(wù)端代碼使用PHP進(jìn)行示例編寫(xiě)。
          本文相關(guān)示例代碼位于github上,主要參考
          聊聊大文件上傳
          大文件切割上傳
          文件上傳的幾種方式
          首先我們來(lái)看看文件上傳的幾種方式。

          普通表單上傳
          使用PHP來(lái)展示常規(guī)的表單上傳是一個(gè)不錯(cuò)的選擇。首先構(gòu)建文件上傳的表單,并指定表單的提交內(nèi)容類(lèi)型為enctype="multipart/form-data",表明表單需要上傳二進(jìn)制數(shù)據(jù)。

          然后編寫(xiě)index.php上傳文件接收代碼,使用move_uploaded_file方法即可(php大法好…)

          form表單上傳大文件時(shí),很容易遇見(jiàn)服務(wù)器超時(shí)的問(wèn)題。通過(guò)xhr,前端也可以進(jìn)行異步上傳文件的操作,一般由兩個(gè)思路。


          文件編碼上傳

          第一個(gè)思路是將文件進(jìn)行編碼,然后在服務(wù)端進(jìn)行解碼,之前寫(xiě)過(guò)一篇在前端實(shí)現(xiàn)圖片壓縮上傳的博客,其主要實(shí)現(xiàn)原理就是將圖片轉(zhuǎn)換成base64進(jìn)行傳遞

          varimgURL?=?URL.createObjectURL(file);

          ctx.drawImage(imgURL,?0,?0);

          //?獲取圖片的編碼,然后將圖片當(dāng)做是一個(gè)很長(zhǎng)的字符串進(jìn)行傳遞

          vardata=?canvas.toDataURL(?"image/jpeg",?0.5);

          在服務(wù)端需要做的事情也比較簡(jiǎn)單,首先解碼base64,然后保存圖片即可

          $imgData?=?$_REQUEST[?'imgData'];

          $base64?=?explode(?',',?$imgData)[?1];

          $img?=?base64_decode($base64);

          $url?=?'./test.jpg';

          if(file_put_contents($url,?$img))?{
          exit(json_encode(?array(

          url?=>?$url

          )));

          }


          base64編碼的缺點(diǎn)在于其體積比原圖片更大(因?yàn)锽ase64將三個(gè)字節(jié)轉(zhuǎn)化成四個(gè)字節(jié),因此編碼后的文本,會(huì)比原文本大出三分之一左右),對(duì)于體積很大的文件來(lái)說(shuō),上傳和解析的時(shí)間會(huì)明顯增加。
          更多關(guān)于base64的知識(shí),可以參考Base64筆記。
          除了進(jìn)行base64編碼,還可以在前端直接讀取文件內(nèi)容后以二進(jìn)制格式上傳
          //?讀取二進(jìn)制文件

          functionreadBinary(text){
          vardata?=?newArrayBuffer(text.length);

          varui8a?=?newUint8Array(data,?0);

          for(?vari?=?0;?i?ui8a[i]?=?(text.charCodeAt(i)?&?0xff);

          }

          console.log(ui8a)

          }

          varreader?=?newFileReader;

          reader.?=?function{
          readBinary(?this.result)?//?讀取result或直接上傳

          }

          //?把從input里讀取的文件內(nèi)容,放到fileReader的result字段里

          reader.readAsBinaryString(file);

          formData異步上傳

          FormData對(duì)象主要用來(lái)組裝一組用 發(fā)送請(qǐng)求的鍵/值對(duì),可以更加靈活地發(fā)送Ajax請(qǐng)求。可以使用FormData來(lái)模擬表單提交。
          letfiles?=?e.target.files?//?獲取input的file對(duì)象

          letformData?=?newFormData;

          formData.append(?'file',?file);

          axios.post(url,?formData);

          服務(wù)端處理方式與直接form表單請(qǐng)求基本相同。

          iframe無(wú)刷新頁(yè)面

          在低版本的瀏覽器(如IE)上,xhr是不支持直接上傳formdata的,因此只能用form來(lái)上傳文件,而form提交本身會(huì)進(jìn)行頁(yè)面跳轉(zhuǎn),這是因?yàn)閒orm表單的target屬性導(dǎo)致的,其取值有

          _self,默認(rèn)值,在相同的窗口中打開(kāi)響應(yīng)頁(yè)面

          _blank,在新窗口打開(kāi)

          _parent,在父窗口打開(kāi)

          _top,在最頂層的窗口打開(kāi)

          framename,在指定名字的iframe中打開(kāi)

          如果需要讓用戶體驗(yàn)異步上傳文件的感覺(jué),可以通過(guò)framename指定iframe來(lái)實(shí)現(xiàn)。把form的target屬性設(shè)置為一個(gè)看不見(jiàn)的iframe,那么返回的數(shù)據(jù)就會(huì)被這個(gè)iframe接受,因此只有該iframe會(huì)被刷新,至于返回結(jié)果,也可以通過(guò)解析這個(gè)iframe內(nèi)的文本來(lái)獲取。


          functionupload{
          varnow?=?+?newDate

          varid?=?'frame'+?now

          $(?"body").append(?`"display:none;"?name="${id}"?id="${id}"?/>`);

          var$form?=?$(?"#myForm")

          $form.attr({
          "action":?'/index.php',

          "method":?"post",

          "enctype":?"multipart/form-data",

          "encoding":?"multipart/form-data",

          "target":?id

          }).submit

          $(?"#"+id).on(?"load",?function{
          varcontent?=?$(?this).contents.find(?"body").text

          try{
          vardata?=?JSON.parse(content)

          }?catch(e){
          console.log(e)

          }

          })

          }


          大文件上傳

          現(xiàn)在來(lái)看看在上面提到的幾種上傳方式中實(shí)現(xiàn)大文件上傳會(huì)遇見(jiàn)的超時(shí)問(wèn)題,
          表單上傳和iframe無(wú)刷新頁(yè)面上傳,實(shí)際上都是通過(guò)form標(biāo)簽進(jìn)行上傳文件,這種方式將整個(gè)請(qǐng)求完全交給瀏覽器處理,當(dāng)上傳大文件時(shí),可能會(huì)遇見(jiàn)請(qǐng)求超時(shí)的情形
          通過(guò)fromData,其實(shí)際也是在xhr中封裝一組請(qǐng)求參數(shù),用來(lái)模擬表單請(qǐng)求,無(wú)法避免大文件上傳超時(shí)的問(wèn)題
          編碼上傳,我們可以比較靈活地控制上傳的內(nèi)容
          大文件上傳最主要的問(wèn)題就在于:在同一個(gè)請(qǐng)求中,要上傳大量的數(shù)據(jù),導(dǎo)致整個(gè)過(guò)程會(huì)比較漫長(zhǎng),且失敗后需要重頭開(kāi)始上傳。試想,如果我們將這個(gè)請(qǐng)求拆分成多個(gè)請(qǐng)求,每個(gè)請(qǐng)求的時(shí)間就會(huì)縮短,且如果某個(gè)請(qǐng)求失敗,只需要重新發(fā)送這一次請(qǐng)求即可,無(wú)需從頭開(kāi)始,這樣是否可以解決大文件上傳的問(wèn)題呢?

          綜合上面的問(wèn)題,看來(lái)大文件上傳需要實(shí)現(xiàn)下面幾個(gè)需求
          支持拆分上傳請(qǐng)求(即切片)
          支持?jǐn)帱c(diǎn)續(xù)傳
          支持顯示上傳進(jìn)度和暫停上傳
          接下來(lái)讓我們依次實(shí)現(xiàn)這些功能,看起來(lái)最主要的功能應(yīng)該就是切片了。

          文件切片
          參考:大文件切割上傳
          編碼方式上傳中,在前端我們只要先獲取文件的二進(jìn)制內(nèi)容,然后對(duì)其內(nèi)容進(jìn)行拆分,最后將每個(gè)切片上傳到服務(wù)端即可。
          在Java中,文件FIle對(duì)象是Blob對(duì)象的子類(lèi),Blob對(duì)象包含一個(gè)重要的方法slice,通過(guò)這個(gè)方法,我們就可以對(duì)二進(jìn)制文件進(jìn)行拆分。
          下面是一個(gè)拆分文件的示例,對(duì)于up6來(lái)說(shuō)開(kāi)發(fā)者不需要關(guān)心拆分的細(xì)節(jié),由控件幫助實(shí)現(xiàn),開(kāi)發(fā)者只需要關(guān)心業(yè)務(wù)邏輯即可。
          控件上傳的時(shí)候會(huì)為每一個(gè)文件塊數(shù)據(jù)添加相關(guān)的信息,開(kāi)發(fā)者在服務(wù)端接收到數(shù)據(jù)后可以自已進(jìn)行處理。
          服務(wù)器接收到這些切片后,再將他們拼接起來(lái)就可以了,下面是PHP拼接切片的示例代碼
          對(duì)于up6來(lái)說(shuō),開(kāi)發(fā)人員不需要進(jìn)行拼接,up6已經(jīng)提供了示例代碼,已經(jīng)實(shí)現(xiàn)了這個(gè)邏輯。
          保證唯一性,控件會(huì)為每一個(gè)文件塊添加信息,如塊索引,塊MD5,文件MD5

          斷點(diǎn)續(xù)傳
          up6自帶續(xù)傳功能,up6在服務(wù)端已經(jīng)保存了文件的信息,在客戶端也保存了文件的進(jìn)度信息。在上傳時(shí)控件會(huì)自動(dòng)加載文件進(jìn)度信息,開(kāi)發(fā)者不需要關(guān)心這些細(xì)節(jié)。在文件塊的處理邏輯中只需要根據(jù)文件塊索引來(lái)識(shí)別即可。
          此時(shí)上傳時(shí)刷新頁(yè)面或者關(guān)閉瀏覽器,再次上傳相同文件時(shí),之前已經(jīng)上傳成功的切片就不會(huì)再重新上傳了。
          服務(wù)端實(shí)現(xiàn)斷點(diǎn)續(xù)傳的邏輯基本相似,只要在getUploadSliceRecord內(nèi)部調(diào)用服務(wù)端的查詢接口獲取已上傳切片的記錄即可,因此這里不再展開(kāi)。
          此外斷點(diǎn)續(xù)傳還需要考慮切片過(guò)期的情況:如果調(diào)用了mkfile接口,則磁盤(pán)上的切片內(nèi)容就可以清除掉了,如果客戶端一直不調(diào)用mkfile的接口,放任這些切片一直保存在磁盤(pán)顯然是不可靠的,一般情況下,切片上傳都有一段時(shí)間的有效期,超過(guò)該有效期,就會(huì)被清除掉。基于上述原因,斷點(diǎn)續(xù)傳也必須同步切片過(guò)期的實(shí)現(xiàn)邏輯。

          續(xù)傳效果

          上傳進(jìn)度和暫停
          通過(guò)xhr.upload中的progress方法可以實(shí)現(xiàn)監(jiān)控每一個(gè)切片上傳進(jìn)度。
          上傳暫停的實(shí)現(xiàn)也比較簡(jiǎn)單,通過(guò)xhr.abort可以取消當(dāng)前未完成上傳切片的上傳,實(shí)現(xiàn)上傳暫停的效果,恢復(fù)上傳就跟斷點(diǎn)續(xù)傳類(lèi)似,先獲取已上傳的切片列表,然后重新發(fā)送未上傳的切片。
          由于篇幅關(guān)系,上傳進(jìn)度和暫停的功能這里就先不實(shí)現(xiàn)了。
          實(shí)現(xiàn)效果:
          ?

          小結(jié)
          目前社區(qū)已經(jīng)存在一些成熟的大文件上傳解決方案,如七牛SDK,騰訊云SDK等,也許并不需要我們手動(dòng)去實(shí)現(xiàn)一個(gè)簡(jiǎn)陋的大文件上傳庫(kù),但是了解其原理還是十分有必要的。
          本文首先整理了前端文件上傳的幾種方式,然后討論了大文件上傳的幾種場(chǎng)景,以及大文件上傳需要實(shí)現(xiàn)的幾個(gè)功能
          通過(guò)Blob對(duì)象的slice方法將文件拆分成切片
          整理了服務(wù)端還原文件所需條件和參數(shù),演示了PHP將切片還原成文件
          通過(guò)保存已上傳切片的記錄來(lái)實(shí)現(xiàn)斷點(diǎn)續(xù)傳
          還留下了一些問(wèn)題,如:合并文件時(shí)避免內(nèi)存溢出、切片失效策略、上傳進(jìn)度暫停等功能,并沒(méi)有去深入或一一實(shí)現(xiàn),繼續(xù)學(xué)習(xí)吧

          往期推薦

          MyBatis的動(dòng)態(tài)代理實(shí)現(xiàn)細(xì)節(jié)

          如何用 SpringBoot 實(shí)現(xiàn) MySQL 的讀寫(xiě)分離?

          面試經(jīng)歷:如何從 100 億 URL 中找出相同的 URL?

          用 Arthas 定位 Spring Boot 接口的超時(shí)問(wèn)題,讓?xiě)?yīng)用起飛~

          下方二維碼關(guān)注我

          技術(shù)草根堅(jiān)持分享?編程,算法,架構(gòu)

          朋友助力下!點(diǎn)個(gè)在看
          瀏覽 113
          點(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>
                  爱爱视频在线看 | 性爱9999 | 成人亚洲视频在线观看 | 最新偷拍网址 | 黄色国产片区免费 |