淺談文件斷點續傳和WebUploader的基本結合


0、寫在前面的話

上篇博客已是在8月了,期間到底發生了什麼,只有我本身知道,反正就是心情特別糟糕,生活狀態工做狀態學習狀態都十分很差,還有心思進取嗎,No!如今狀態好起來了,生活又充滿了但願 :D 

前兩週在寫視頻管理相關的功能,說是要在原來的項目上進行拓展。結果今天領導給我說客戶那邊還沒定,只作技術上研究就好了,不用寫具體功能代碼(我都寫了好嗎?)因而忽然時間有騰出來,今天整理一下把內容寫一些。

要努力努力,爲了更好的人爲了更好的生活。

一、斷點續傳的兩種方式

1.1 RandomAccessFile

客戶端給一個已經上傳的位置標記,而後服務器端就能夠在指定的位置進行處理。這個斷點位置的讀取,就要用到RandomAccessFile類,該類不一樣於InputStream和OutputStream,它既能夠對文件進行讀也能夠進行寫,兩個重要方法:
  • long getFilePoint():返回文件記錄指針的當前位置,不指定指針的位置默認是0
  • void seek(long pos):設置文件指針偏移,即將文件記錄指針定位到pos位置

至於position位置如何去處理,就看各自的想法了。1)你能夠將位置存在瀏覽器(好比localStorage),下次傳輸的時候前端從殘缺位置切割文件 blob.slice() 只傳輸剩餘的部分,後端直接接收接着寫入服務器便可;2)也能夠前端把文件完整傳輸,同時帶上position參數,由後端經過 RandomAccessFile 在指定位置開始讀取內容便可。

至於客戶端和服務端之間文件的一致性,多使用md5進行校驗。

1.2 分片處理

H5中新增了File API,能夠經過使用 slice() 方法生成只有某段文件內容。這個方法就爲斷點續傳提供了新的方式,就是分片處理,假設一個文件是100M大小,那麼每次傳輸我只須要傳送10M,按序發送10次請求便可。某個分片傳送失敗,那麼從這個分片再繼續發送便可,後端則對分片文件進行合併成完整文件。

其實方式和1.1提到的是相似的,不過每次傳輸的數據單位量更大一些,完整文件交給後端進行合併。

二、WebUploader的分片段點續傳

WebUploader的選項中支持直接開啓分片上傳:
var uploader = WebUploader.Uploader({
    swf: 'path_of_swf/Uploader.swf',

    // 開起分片上傳
    chunked: true,
    // 分片大小,默認5M
    chunkSize: 5242880,
    // 分片出錯後(如網絡緣由等形成出錯)的重傳次數
    chunkRetry: 2,
    // 上傳併發數
    threads: 1
});

開啓分片上傳後,插件會自動分片上傳文件,接下來只須要在配置文件跳過和後端處理便可。官方迴應在分片發送前會有監聽的事件 uploadBeforeSend,在這個方法的callback裏面若是返回的是一個promise,且此promise被reject了,那麼此分片就跳過了。( 實際上該方式在自測和諮詢網友時發現,並無什麼用,即使按照官方說明,分片也沒有跳過,仍然日後端進行了請求發送,同時也附帶有文件
webUploader.on('uploadBeforeSend', function(block, data){
    data.fileMd5 = block.file.wholeMd5;
    var deferred = WebUploader.Deferred();
    var chunk = data.chunk;
    var existChunks = block.file.existChunks;
    //後端返回了已存在分片的數組,這裏判斷要發送的分片是否已存在
    if(existChunks && existChunks.indexOf(chunk) != -1) {
        //console.log("分片存在,已跳過:" + chunk);
        deferred.reject();
    } else {
        deferred.resolve();
    }
    return deferred.promise();
});

分片是否存在的判斷,也有不一樣的方式,一種你能夠每次計算分片的md5值發送給後端,若是服務器已存在則跳過,不然就發送;另外一種就是隻向服務器查詢一次獲取已經存在的分片,而後在瀏覽器端進行比對,但如此須要考慮分片是否併發傳輸,進行相應處理。

我採用的方式是:先對文件進行md5計算,在服務器端建立和md5值同名的文件夾,每次上傳的分片存放在對應文件夾,文件名即分片的序號,好比某文件夾中可能存在文件 0, 1, 2, 3... 前端發送分片前請求後端數據,後端將已經存在的分片名數組返回前端,前端進行跳過處理,同時後端在接收分片也要作是否存在的判斷,已存在的話就再也不進行讀寫操做,直到最後分片到達,則進行分片的按序合併便可。

public boolean uploadChunk() throws ChunkUploadException {
    HttpServletRequest request = ServletActionContext.getRequest();
    //封裝源文件信息
    FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
    //獲取同時上傳的文件其餘屬性
    Map<String, String> params = getVideoParams(request);

    if (params.get("fileMd5") == null || "".equals(params.get("fileMd5"))) {
        throw new ChunkUploadException("文件md5值未傳遞");
    }
    //存放
    File temp = new File(getTempPath(params.get("fileMd5")) + "/" + srcFileInfo.getCurChunk());
    if (!temp.exists()) {
        try {
            VideoUtil.copy(srcFileInfo.getFile(), temp);
        } catch (IOException e) {
            throw new ChunkUploadException("分片上傳失敗: chunkNum" + params.get("chunk"));
        }
    }
    //若是是最後分片
    return !srcFileInfo.isChunked() || srcFileInfo.getCurChunk() == srcFileInfo.getChunkSize() - 1;
}

public String upload() {
    boolean isLastChunk = false;
    try {
        isLastChunk = uploadChunk();
    } catch (ChunkUploadException e) {
        e.printStackTrace();
        AjaxSupport.sendFailText(null, e.getMessage());
        return AJAX_RESULT;
    }

    //不是最後的分片,直接返回成功響應
    if (!isLastChunk) {
        AjaxSupport.sendSuccessText("chunk uploaded", "success");
        return AJAX_RESULT;
    } 
    //最後切片
    else {
        HttpServletRequest request = ServletActionContext.getRequest();
        //封裝源文件信息
        FileInfo srcFileInfo = VideoUtil.getUploadFileInfoByStruts(request, "file");
        //獲取同時上傳的文件其餘屬性
        Map<String, String> params = getVideoParams(request);
        //獲取合併文件的文件名
        String filename = UUID.randomUUID().toString() + "." + srcFileInfo.getFileType();

        //合併文件
        File tempDir = new File(getTempPath(params.get("fileMd5")));
        File[] tempfileArr = tempDir.listFiles();
        File storeFile = new File(getStorePath() + "/" + filename);
        try {
            VideoUtil.merge(tempfileArr, storeFile);
        }
    ...

最後,實際上這種方式斷點續傳仍然存在不少細節沒有考慮,好比多線程,同個瀏覽器兩個tab發送同一文件時如何處理?

三、參考連接

相關文章
相關標籤/搜索