天天看點

大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)

 之前仿造uploadify寫了一個HTML5版的檔案上傳插件,沒看過的朋友可以點此先看一下~得到了不少朋友的好評,我自己也用在了項目中,不論是使用者頭像上傳,還是各種媒體檔案的上傳,以及各種個性的業務需求,都能得到滿足。小小開心了一把。

  但無論插件再怎麼靈活,也難以應付所有的需求,比如,你要上傳一個2G的檔案。以現在我們的網速,恐怕再快也得傳半小時。要命的是,如果你在上傳到90%的時候不小心關掉了浏覽器,或者是手一抖摁了F5,完了,一切還得從頭再來。這種使用者體驗簡直太糟糕了。是以,斷點續傳就十分有必要了。什麼是續傳我就不解釋了,用QQ傳檔案這麼多年,大家都見過了。

  這裡要說的是斷點續傳都有哪些技術要點。使用傳統的表單送出檔案或是HTML5的FormData都是将檔案“整塊”送出,服務端取到該檔案後再進行轉移、重命名等操作,是以,無法實時儲存檔案的已上傳部分。而且在http協定下,我們無法保持浏覽器與服務端的長連接配接,不能以檔案流的形式來送出。是以要解決的問題具體來講有以下幾點:

  1. 對上傳的檔案進行分割,每次隻上傳一小片。服務端接收到檔案後追加到原來部分,最後合并成完整的檔案。
  2. 每次上傳檔案片前先擷取已上傳的檔案大小,确定本次應切割的位置
  3. 每次上傳完成後更新已上傳檔案大小的記錄
  4. 辨別用戶端和服務端的檔案,保證不會把A檔案的内容追加到B檔案上

  在參考了張鑫旭大哥的這篇文章後,我将學到的技術應用在了我的插件Huploadify中,成功的添加了斷點續傳功能。在此将技術和插件都分享給大家。

工作原理/技術要點

  首先的首先,要明确,如果我們有一個10M的檔案,每次切割上傳1M,那麼是需要發10次請求來完成的。在http協定下,隻能這麼搞。斷點上傳分三步來完成:

  1. 選擇一個檔案後,擷取該檔案在伺服器上的大小,通過本地存儲或自定義的函數來擷取。
  2. 根據已上傳大小切割檔案,發出n次請求不斷向伺服器送出檔案片,服務端不斷追加檔案内容
  3. 當已上傳檔案大小達到檔案總大小時,上傳結束  

  首先是檔案的分割,HTML5新增了Blob資料類型,并且提供了一個可以分割資料的方法:slice(),其用法和字元串、數組的slice()方法一樣,可以截取一個二進制檔案的一部分。

  其次是檔案片的儲存與追加,我背景用PHP寫的,先用file_get_contents擷取檔案的二進制格式,再用file_put_contents每次将檔案追加,具體的寫法可以參照後面,或者是下載下傳我打包好的檔案。

  接下來我們還需要實時儲存已上傳檔案的大小,以便于下次上傳前進行正确切割。使用HTML5的localStorage是一種方法,将已上傳的大小儲存在本地,下次上傳前先從本地讀取。不過這種方式是很局限的,抛開使用者可能通過各種管家清除掉本地資料不講,假如使用者在A頁面上傳了一個檔案的50%,然後在B頁面想把該檔案上傳到另外一個地方,結果從本地一讀檔案已上傳50%了,直接從51%的位置開始上傳了,顯然是個錯誤。問題就在于本地不能存太多的資訊,通過File API隻能擷取到檔案的原始名稱,無法正确的與伺服器上的檔案正确比對。是以真正在項目中用,還得依靠服務端來儲存這些資料。

  關于如何将資料存在服務端,已經前端如何取資料,我在下面會講到。

  技術要點就上面的那麼多了,其實也沒有多少技術含量哈~來看看我的插件如何使用吧。

續傳功能的使用方法

  檔案的引入就不講了,可參考上一篇關于插件的介紹。關鍵點是新增的幾個配置,先來看一下:

大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)
breakPoints:false,//是否開啟斷點續傳
fileSplitSize:1024*1024,//斷點續傳的檔案塊大小,機關Byte,預設1M
getUploadedSize:null,//類型:function,自定義擷取已上傳檔案的大小函數,用于開啟斷點續傳模式,可傳入一個參數file,即目前上傳的檔案對象,需傳回number類型
saveUploadedSize:null,//類型:function,自定義儲存已上傳檔案的大小函數,用于開啟斷點續傳模式,可傳入兩個參數:file:目前上傳的檔案對象,value:已上傳檔案的大小,機關Byte
saveInfoLocal:false,//用于開啟斷點續傳模式,是否使用localStorage存儲已上傳檔案大小      
大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)

  這是插件中的預設配置值。一個續傳功能竟然要配置五個項,真要命!不要着急聽我慢慢道來,這五個并不是要同時出現的,是為了滿足可能出現的複雜業務而準備的。

  breakPoints是開啟斷點續傳的開關,要使用的話設為true,預設是不開啟的。

  fileSplitSize是每次切割的檔案片的大小,預設是1M,可根據實際情況來定。如果你的系統上傳的檔案普遍都在1G以上,可以配置的大一點。

  getUploadedSize是用來自定義擷取已上傳的檔案大小的函數,還記得上面說過的localStorage的局限吧,是以我這裡直接把擷取檔案大小的函數交給你來定義,你可以從session、cookie,從檔案、資料庫或者任何地方取,可以發送一個ajax請求到你想要的位址,傳遞你需要的參數。注意你定義的函數将來會被插件調用,是以一定要傳回一個Number類型的結果。

  saveUploadedSize與getUploadedSize對應,你自己定義如何儲存已上傳檔案的大小,隻要你存的資料你自己能取到就OK。當然前提是你要注意到上面說過的localStorage的局限,確定你的邏輯正确能夠操作到正确的檔案。

  saveInfoLocal是當你使用localStorage儲存資料時需要開啟的一個開關。插件預設提供使用localStorage方式的支援。隻要開啟此選項就可以了。當然,這種情況下你的業務邏輯必須足夠簡單,比如隻是做一個上傳的demo,或者這系統的使用者隻有你一個人,你明白如何避開那些局限的地方。

  掌握了這五個配置的作用,你就可以實作一個足夠靈活的斷點上傳功能了!在我打包好的檔案裡,提供了使用localStorage方式的demo,抱歉我無法将資料庫表都發給你,是以隻能用本地存儲來示範。

在服務端儲存資料

  使用者在使用上傳的時候可能有各種你意想不到的操作,這裡我發揮想象描述一下使用者可能的行為:

  • 同一台機器使用不同帳号登入,上傳同一個檔案
  • 檔案上傳了一部分,然後修改了檔案内容,再次上傳
  • 檔案上傳完成100%,再次上傳該檔案
  • 同一個頁面有多個上傳按鈕,上傳同一個檔案,或在不同頁面上傳同一個檔案

  僅僅上面四條,是不是情況就夠複雜了?再加上你系統還有自己的業務邏輯,是以在服務端儲存已上傳檔案資料是非常有必要的。而且儲存資料和擷取資料的函數都交給你來定義,抱着插件有足夠的靈活性。

  因為涉及到了服務端的技術,無法示範,我将我項目中的真實使用場景在此講解一下,來展示一下如何自已定義方法來實作服務端儲存資料的可靠上傳。我定義的getUploadedSize函數如下:

大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)
getUploadedSize:function(file){
            var data = {
                data : {
                    fileName : file.name,
                    lastModifiedDate : file.lastModifiedDate.getTime()
                }
            };
            var url = 'http://localhost/uploadfile/';
            var uploadedSize = 0;
            $.ajax({
                url : url,
                data : data,
                async : false,
                type : 'POST',
                success : function(returnData){
                    returnData = JSON.parse(returnData);
                    uploadedSize = returnData.uploadedSize;
                }
            });
            return uploadedSize;
        }      
大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)

  我向背景的某個位址發送一個請求,傳遞檔案名和檔案的最後修改時間為參數,背景根據這兩個參數來找到與前台所選擇的檔案對應的伺服器上的檔案,将伺服器傳回的檔案大小return出去,來被插件使用。為什麼要傳遞這兩個參數呢?我們在前台無法知道伺服器上的這個檔案的名稱,是以使用原始檔案名作為一個輔助辨別。為了防止使用者在兩次上傳間隔修改了檔案,我們把檔案的最後修改時間也傳給服務端,讓服務端進行比較,若時間不對應則傳回已上傳大小為0,重新上傳此檔案。

  再來看背景都要做哪些工作。資料庫中需要有一張表來記錄每個已檔案的情況,包含的字段大緻有:

字段 描述
client_filename 檔案在用戶端的原始名稱
server_filename 檔案在伺服器上重命名後的名稱
last_modified_date 檔案的最後修改時間,時間戳
status 檔案的狀态,已完成、未完成
uploaded_size 已上傳檔案的大小

  根據client_filename和last_modified_date,再加上系統中的其他關聯資訊,可以定位到本次上傳的檔案在服務端的大小,然後傳回給用戶端。當然這是我自己的用法,你也可以根據自己的需求靈活設計。總之最終的目的就是要找到前台選擇的檔案在伺服器上真正對應的檔案,并将已上傳大小正确傳回。

  另外需注意的一點,就是在續傳的第二步,不斷送出檔案片的過程中,也需要服務端準确定位到相應的檔案,不能把A的資料追加到B上。采用的方式也是送出fileName和lastModifyDate兩個參數(已寫在插件内部,可服務端直接擷取),服務端找到對應的檔案進行追加。

  另外再啰嗦一句,背景擷取檔案的時候需要取成二進制的,而我們送出是使用FormData來送出的,是以PHP代碼需要這麼寫:

file_put_contents('uploads/'.$filename,file_get_contents($_FILES["file"]["tmp_name"]),FILE_APPEND);      

  如果上面的說明還是不夠清楚,就需要你自己來探索一下了,畢竟考慮到插件可能應用在複雜的系統中,很多工作還是需要你來做的。或者你也可以給我留言,我很樂意為你解答疑惑。

該版本的其他改動

  從1.0到2.0,Huploadify又新加了很多東西,不過隻是新加,使用方式跟之前的沒有變化。例如上面的斷點續傳功能,你如果不想使用,隻需設定breakPoints為false即可,插件仍按照以前的方式工作。除了斷點續傳這個大頭,插件還做了如下改動:

  1. 增加了onSelect回調函數,在選擇了檔案之後觸發,用法與uploadify官網的一緻
  2. 删除掉正在上傳的檔案,中斷發送請求
  3. 完善了input file元件的accept屬性支援,浏覽時隻顯示運作的檔案格式,就是這個東東:
大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)

  4. 對外開放了方法調用接口,upload、stop、cancel、disable、ennable。我在demo中有示範。使用方法如下:

大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)
var up = $('#upload').Huploadify({
    auto:false,
    fileTypeExts:'*.jpg;*.png;*.exe;*.mp3;*.mp4;*.zip;*.doc;*.docx;*.ppt;*.pptx;*.xls;*.xlsx;*.pdf',
    multi:true
});

up.upload(1);//開始上傳檔案,接收一個參數,表示上傳第幾個檔案,可傳入*上傳隊列中的所有檔案
up.stop();//暫停上傳隊列中的所有檔案,不接收參數。用于開啟了斷點需傳
up.cancel(1);//删除隊列中的某個檔案,接收一個參數,表示删除第幾個檔案,可傳入*删除隊列中的所有檔案
up.disable();//使選擇檔案按鈕失效,不接收參數
up.ennable();//使選擇檔案按鈕生效,不接收參數      
大檔案上傳解決方案-支援斷點續傳的檔案上傳插件(轉)

  5. 修改其他已知bug

結束

  插件剛剛完成,與我們的後端程式員調試完成了斷點續傳功能暫未發現問題,歡迎大家在使用的時候給我提任何問題。老實來講這個功能使用起來還是挺費解的,為了最大程度的保證靈活做成這樣,大家可以與我多多交流~

  我在demo中使用了本地存儲來做已上傳檔案大小的儲存,下載下傳壓縮包後可看一下效果。上傳一個比較大的視訊檔案,上傳到中間關閉浏覽器,再次打開浏覽器上傳同一個檔案,會看到從上次斷掉的地方繼續上傳。源碼奉上:

  戳這裡下載下傳:http://files.cnblogs.com/lvdabao/Huploadify-V2.0.zip

本文轉自豹哥部落格:https://www.cnblogs.com/lvdabao/p/3498370.html