[轉]文件編碼後再上傳,大文件分片上傳

 

原文:https://jishuin.proginn.com/p/763bfbd373dc

------------------

 

最近遇見一個需要上傳超大大文件的需求,調研了七牛和騰訊雲的切片分段上傳功能,因此在此整理前端大文件上傳相關功能的實現。

在某些業務中,大文件上傳是一個比較重要的交互場景,如上傳入庫比較大的Excel表格數據、上傳影音文件等。如果文件體積比較大,或者網絡條件不好時,上傳的時間會比較長(要傳輸更多的報文,丟包重傳的概率也更大),用戶不能刷新頁面,只能耐心等待請求完成。

 

下面從文件上傳方式入手,整理大文件上傳的思路,並給出了相關實例代碼,由於PHP內置了比較方便的文件拆分和拼接方法,因此服務端代碼使用PHP進行示例編寫。本文相關示例代碼位於github上,主要參考聊聊大文件上傳大文件切割上傳文件上傳的幾種方式首先我們來看看文件上傳的幾種方式。
普通表單上傳使用PHP來展示常規的表單上傳是一個不錯的選擇。首先構建文件上傳的表單,並指定表單的提交內容類型爲enctype="multipart/form-data",表明表單需要上傳二進制數據。

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

form表單上傳大文件時,很容易遇見服務器超時的問題。通過xhr,前端也可以進行異步上傳文件的操作,一般由兩個思路。

 

文件編碼上傳

第一個思路是將文件進行編碼,然後在服務端進行解碼,之前寫過一篇在前端實現圖片壓縮上傳的博客,其主要實現原理就是將圖片轉換成base64進行傳遞

varimgURL = URL.createObjectURL(file);

ctx.drawImage(imgURL, 0, 0);

// 獲取圖片的編碼,然後將圖片當做是一個很長的字符串進行傳遞

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

在服務端需要做的事情也比較簡單,首先解碼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編碼的缺點在於其體積比原圖片更大(因爲Base64將三個字節轉化成四個字節,因此編碼後的文本,會比原文本大出三分之一左右),對於體積很大的文件來說,上傳和解析的時間會明顯增加。更多關於base64的知識,可以參考Base64筆記。除了進行base64編碼,還可以在前端直接讀取文件內容後以二進制格式上傳

// 讀取二進制文件

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

varui8a = newUint8Array(data, 0);

for( vari = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);

}

console.log(ui8a)

}

varreader = newFileReader;

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

}

// 把從input裏讀取的文件內容,放到fileReader的result字段裏

reader.readAsBinaryString(file);

formData異步上傳


FormData對象主要用來組裝一組用 發送請求的鍵/值對,可以更加靈活地發送Ajax請求。可以使用FormData來模擬表單提交。

letfiles = e.target.files // 獲取input的file對象

letformData = newFormData;

formData.append( 'file', file);

axios.post(url, formData);

服務端處理方式與直接form表單請求基本相同。

iframe無刷新頁面

在低版本的瀏覽器(如IE)上,xhr是不支持直接上傳formdata的,因此只能用form來上傳文件,而form提交本身會進行頁面跳轉,這是因爲form表單的target屬性導致的,其取值有

_self,默認值,在相同的窗口中打開響應頁面

_blank,在新窗口打開

_parent,在父窗口打開

_top,在最頂層的窗口打開

framename,在指定名字的iframe中打開


如果需要讓用戶體驗異步上傳文件的感覺,可以通過framename指定iframe來實現。把form的target屬性設置爲一個看不見的iframe,那麼返回的數據就會被這個iframe接受,因此只有該iframe會被刷新,至於返回結果,也可以通過解析這個iframe內的文本來獲取。

 

functionupload{
varnow = + newDate

varid = 'frame'+ now

$( "body").append( `<iframe style="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)

}

})

}

 

大文件上傳

現在來看看在上面提到的幾種上傳方式中實現大文件上傳會遇見的超時問題,表單上傳和iframe無刷新頁面上傳,實際上都是通過form標籤進行上傳文件,這種方式將整個請求完全交給瀏覽器處理,當上傳大文件時,可能會遇見請求超時的情形通過fromData,其實際也是在xhr中封裝一組請求參數,用來模擬表單請求,無法避免大文件上傳超時的問題編碼上傳,我們可以比較靈活地控制上傳的內容大文件上傳最主要的問題就在於:在同一個請求中,要上傳大量的數據,導致整個過程會比較漫長,且失敗後需要重頭開始上傳。試想,如果我們將這個請求拆分成多個請求,每個請求的時間就會縮短,且如果某個請求失敗,只需要重新發送這一次請求即可,無需從頭開始,這樣是否可以解決大文件上傳的問題呢?
綜合上面的問題,看來大文件上傳需要實現下面幾個需求支持拆分上傳請求(即切片)支持斷點續傳支持顯示上傳進度和暫停上傳接下來讓我們依次實現這些功能,看起來最主要的功能應該就是切片了。
文件切片參考:大文件切割上傳編碼方式上傳中,在前端我們只要先獲取文件的二進制內容,然後對其內容進行拆分,最後將每個切片上傳到服務端即可。在Java中,文件FIle對象是Blob對象的子類,Blob對象包含一個重要的方法slice,通過這個方法,我們就可以對二進制文件進行拆分。下面是一個拆分文件的示例,對於up6來說開發者不需要關心拆分的細節,由控件幫助實現,開發者只需要關心業務邏輯即可。控件上傳的時候會爲每一個文件塊數據添加相關的信息,開發者在服務端接收到數據後可以自已進行處理。服務器接收到這些切片後,再將他們拼接起來就可以了,下面是PHP拼接切片的示例代碼對於up6來說,開發人員不需要進行拼接,up6已經提供了示例代碼,已經實現了這個邏輯。保證唯一性,控件會爲每一個文件塊添加信息,如塊索引,塊MD5,文件MD5
斷點續傳up6自帶續傳功能,up6在服務端已經保存了文件的信息,在客戶端也保存了文件的進度信息。在上傳時控件會自動加載文件進度信息,開發者不需要關心這些細節。在文件塊的處理邏輯中只需要根據文件塊索引來識別即可。此時上傳時刷新頁面或者關閉瀏覽器,再次上傳相同文件時,之前已經上傳成功的切片就不會再重新上傳了。服務端實現斷點續傳的邏輯基本相似,只要在getUploadSliceRecord內部調用服務端的查詢接口獲取已上傳切片的記錄即可,因此這裏不再展開。此外斷點續傳還需要考慮切片過期的情況:如果調用了mkfile接口,則磁盤上的切片內容就可以清除掉了,如果客戶端一直不調用mkfile的接口,放任這些切片一直保存在磁盤顯然是不可靠的,一般情況下,切片上傳都有一段時間的有效期,超過該有效期,就會被清除掉。基於上述原因,斷點續傳也必須同步切片過期的實現邏輯。
續傳效果
上傳進度和暫停通過xhr.upload中的progress方法可以實現監控每一個切片上傳進度。上傳暫停的實現也比較簡單,通過xhr.abort可以取消當前未完成上傳切片的上傳,實現上傳暫停的效果,恢復上傳就跟斷點續傳類似,先獲取已上傳的切片列表,然後重新發送未上傳的切片。由於篇幅關係,上傳進度和暫停的功能這裏就先不實現了。實現效果: 
小結目前社區已經存在一些成熟的大文件上傳解決方案,如七牛SDK,騰訊雲SDK等,也許並不需要我們手動去實現一個簡陋的大文件上傳庫,但是瞭解其原理還是十分有必要的。本文首先整理了前端文件上傳的幾種方式,然後討論了大文件上傳的幾種場景,以及大文件上傳需要實現的幾個功能通過Blob對象的slice方法將文件拆分成切片整理了服務端還原文件所需條件和參數,演示了PHP將切片還原成文件通過保存已上傳切片的記錄來實現斷點續傳還留下了一些問題,如:合併文件時避免內存溢出、切片失效策略、上傳進度暫停等功能,並沒有去深入或一一實現,繼續學習吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章