天天看點

利用HTML5分片上傳超大檔案控件

檔案上傳是最古老的網際網路操作之一,20多年來幾乎沒有怎麼變化,還是操作麻煩、缺乏互動、使用者體驗差。

一、前端代碼

英國程式員Remy Sharp總結了這些新的接口 ,本文在他的基礎之上,讨論在前端采用HTML5的API,對檔案上傳進行漸進式增強:

    * iframe上傳
   * ajax上傳
   * 進度條
   * 檔案預覽
   * 拖放上傳      

1.1 傳統形式

  檔案上傳的傳統形式,是使用表單元素file,參考 http://www.ruanyifeng.com/blog/2012/08/file_upload.html :

  <form id="upload-form" action="upload.php" method="post" enctype="multipart/form-data" >
    <input type="file" id="upload" name="upload" /> <br />
    <input type="submit" value="Upload" />
  </form>      

所有浏覽器都支援上面的代碼,點選上傳按鈕後,網頁"鎖死",使用者隻能等待上傳結束,然後浏覽器重新整理,跳到表單的action屬性指定的網址。

1.2 iframe上傳

  使用者點選submit時,動态插入一個iframe元素

var form = $("#upload-form");

  form.on('submit',function() {

    // 此處動态插入iframe元素

  });

  var seed = Math.floor(Math.random() * 1000);

  var id = "uploader-frame-" + seed;

  var callback = "uploader-cb-" + seed;

  var iframe = $('<iframe id="'+id+'" name="'+id+'" >');

  var url = form.attr('action');

  form.attr('target', id).append(iframe).attr('action', url + '?iframe=' + callback);

1.3 ajax上傳

  HTML5提出了XMLHttpRequest對象的第二版,從此ajax能夠上傳檔案了。這是真正的"異步上傳",是将來的主流。

form.on('submit',function() {

    // 此處進行ajax上傳

  });

 // 檢查是否支援FormData

  if(window.FormData) { 

    var formData = new FormData();

    // 建立一個upload表單項,值為上傳的檔案

    formData.append('upload', document.getElementById('upload').files[0]);

    var xhr = new XMLHttpRequest();

    xhr.open('POST', $(this).attr('action'));

    // 定義上傳完成後的回調函數

    xhr.onload = function () {

      if (xhr.status === 200) {

        console.log('上傳成功');

      } else {

        console.log('出錯了');

      }

    };

    xhr.send(formData);

  }

1.4 進度條

  XMLHttpRequest第二版還定義了一個progress事件,可以用來制作進度條。

//在頁面中放置一個HTML元素progress

<progress id="uploadprogress" min="0" max="100" value="0">0</progress>

//定義進度progress事件的回調函數

 xhr.upload.onprogress = function (event) {

    if (event.lengthComputable) {

      var complete = (event.loaded / event.total * 100 | 0);

      var progress = document.getElementById('uploadprogress');

      progress.value = progress.innerHTML = complete;

    }

  }

二、後端

  Spring 架構中使用類似CommonsMultipartFile對象處理表二進制檔案資訊,細心地會發現在利用架構下封裝的Multiform接口進行檔案上傳時,會先把檔案傳輸至tomcat一個指定的work目錄之下,然後再傳輸到指定的路徑。小檔案上傳這個時間延遲基本上可以忽略,但是在大檔案上傳時,這個上傳的速度就很讓人頭疼,上傳過程中的進度資訊無法通路。

  是以我們有必要從浏覽器請求位元組流中解析Multiform協定,實作不依靠架構内置對象,取得使用者請求的所有資料,同時,使用者上傳的大小不受限制,而且在傳輸過程中,我們可以實時擷取傳輸進度。

參考https://www.cnblogs.com/darkprince/p/5114936.html

2.1 普通Post請求協定及MultiPart協定

  因為一次傳輸的大檔案MultiPart資料包,位元組數可能會很大(1G甚至以上),為了擷取實時進度資訊,以及記憶體開銷控制,我們需要将接收過程分成多段處理,即将資料包分段循環接收(例:每次循環隻接收64K資料,期間即可更新目前的進度資訊)。本次我們采用Spring架構來實作“大檔案傳輸”功能,要點設計結構圖如下:

利用HTML5分片上傳超大檔案控件

2.2 源碼解析

Filter對象:

  用于負責接收MultiPart原始資料的Filter,用以在Spring内置對象之前接收使用者請求。需要在Web.xml中進行配置,Web啟動後,該Filter即啟動,當使用者請求到來時需要判斷該MultiPart資料資訊是否合法,接收并進行解析。

ServletInputStream/BufferedInputStream對象:

  使用以上兩對象,可對本次請求進行按位元組流接收。在此可建立比較小的接收緩沖區,依靠BufferedInputStream的read進行分段循環接收。 

getBoundarySectFromBuf()函數:

  自定義函數,我們需要該函數從分段緩沖區中分析可能包含的多個Form表單資訊,或者部分表單資訊,或者二進制檔案片段資訊。對于表單資訊分析後填充表單資料結構,對于二進制檔案資訊需要寫檔案。該函數需要完成邊接收邊解析邊寫檔案的重要工作。

ProgressInfo對象:

  進度資訊類,描述了一次上傳請求的進度資訊。該對象會用來被用戶端輪詢請求,以獲得目前傳輸大檔案過程中的進度資訊。

FormPart對象及listFormPart集合:

  FormPart對于單個Form表單的描述。listFormPart為本次請求的全部表單描述集合。即供後續代碼調用的全部表單項内容。

Controller層getProgInfo()處理函數:

  該函數将接受來自浏覽器的“獲得進度資訊請求”,并從目前ServletContext公共記憶體區中找到與Progesss ID對應的進度資訊對象ProgressInfo,以XML的形式傳回給浏覽器。該函數會被用戶端輪詢請求。

     詳細代碼可以參考:http://blog.ncmem.com/wordpress/2019/08/12/java實作大檔案上傳/

歡迎入群一起讨論“374992201”

繼續閱讀