java檔案分片上傳,斷點續傳
最近遇見一個需要上傳超大大檔案的需求,調研了七牛和騰訊雲的切片分段上傳功能,因此在此整理前端大檔案上傳相關功能的實現。
在某些業務中,大檔案上傳是一個比較重要的互動場景,如上傳入庫比較大的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將切片還原成檔案
通過儲存已上傳切片的記錄來實現斷點續傳
還留下了一些問題,如:合併檔案時避免記憶體溢位、切片失效策略、上傳進度暫停等功能,並沒有去深入或一一實現,繼續學習吧
後端程式碼邏輯大部分是相同的,目前能夠支援MySQL,Oracle,SQL。在使用前需要配置一下資料庫,可以參考我寫的這篇文章: