前端-文件上傳幾種方式及其核心思想


一、文件上傳幾種方式

  1. form表單上傳
  2. iframe
  3. FormData異步上傳

1、from 表單上傳

首先要知道我們上傳文件時需要修改form表單的 enctype='multipart/form-data'
產生問題:
form表單提交之后會刷新頁面
form表單上傳大文件時,很容易遇見服務器超時

1.1 普通上傳

<form action="http:localhost:8080/uploadFile" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile">
    <input type="submit">
</form>

1.2異步上傳

方案1:base64上傳

通過canvas講圖片裝成base64,然后在服務端進行解碼。
base64會將原本的體積轉成4/3的體積,so會增大請求體加,浪費帶寬,上傳和解析的時間會明顯增加。

<input type="file" id='file'>
<canvas id='canvas'></canvas>
<img src="" id='target-img'>
<script>
    let canvas = document.getElementById("canvas"),
        targetImg = document.getElementById('target-img'),
        file = document.getElementById('file'),
        context = canvas.getContext('2d')

    file.onchange = function() {
        let URL = window.URL || window.webkitURL
        let dataURL = URL.createObjectURL(this.files[0]) // 創建URL對象
        let img = new Image()
        img.crossOrigin = "anonymous" // 只有服務器模式打開, 才有效
        img.src = dataURL
        img.onload = function() {
            URL.revokeObjectURL(this.src) //  img加載完成后,主動釋放URL對象
            canvas.width = img.width
            canvas.height = img.height
            context.drawImage(img, 0, 0, img.width, img.height)
            let dataBase64Url = canvas.toDataURL('img/png')
            targetImg.src = dataBase64Url
        }

    }
</script>
方案2:二進制形式

除了進行base64編碼,還可以在前端直接讀取文件內容后以二進制格式上傳

關鍵api:
參考

  • FileReader:對象允許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩沖區)的內容,使用 File 或 Blob 對象指定要讀取的文件或數據。

    • File:對象可以是來自用戶在一個<input>元素上選擇文件后返回的files對象

    • readAsBinaryString: 方法會讀取指定的 Blob 或 File 對象,當讀取完成的時候,readyState 會變成DONE(已完成),並觸發 loadend (en-US) 事件,同時result 屬性將包含所讀取文件原始二進制格式

  • Blob: 前端的一個專門用於支持文件操作的二進制對象

  • ArrayBuffer:前端的一個通用的二進制緩沖區,類似數組,但在API和特性上卻有諸多不同

  • Buffer:Node.js提供的一個二進制緩沖區,常用來處理I/O操作

  • Uint8Array:類型數組表示的8位無符號整數數組

二進制上傳

文件路徑格式轉二進制

var reader = new FileReader();//①

reader.readAsBinaryString(file);// 把從input里讀取的文件內容,放到fileReader的result字段里
reader.onload = function(){
	 readBinary(this.result) // 讀取result或直接上傳
}
// 讀取二進制文件
function readBinary(text){
    var data = new ArrayBuffer(text.length);//創建一個長度為text.length的二進制緩存區
    var ui8a = new Uint8Array(data, 0);
    for (var i = 0; i < text.length; i++){ 
        ui8a[i] = (text.charCodeAt(i) & 0xff);
    }
    console.log(ui8a)
}

二進制下載
在向后端發起請求時,需要在請求頭中加上

responseType: 'blob'

這樣在返回data中可以得到一個瀏覽器可以解析的blob數據

	const downURL = window.URL.createObjectURL(new Blob([data]));
	 // data 為獲取到的二進制數據
	const listNode = document.createElement("a");
	// 這里注意 : 非同源a標簽的download去命名沒有用
	listNode.download = '合同公允價錯誤文件下載.xlsx';
	listNode.style.display = "none";
	listNode.href = downURL;

2、frame上傳

低版本瀏覽器上,xhr請求不支持formdata上傳,只能form表單上傳。
form表單上傳,出現的問題上文已經提到,會本身進行頁面跳轉,產生原因為target屬性導致
target我們或多或少有些了解,a標簽也有改屬性:
_self:默認值,在相同的窗口中打開響應頁面
_blank:在新窗口打開
_parent:在父窗口打開
_top:在最頂層的窗口打開

實現方案
實現異步上傳的感覺,自理我們就要用到framename去置頂名字的iframe中打開,也就是<iframe name='formtarget'></iframe><form target='formtarget'>,這樣一來返回的數據會被iframe接收,就不會出現刷新問題,而返回的內容可以通過iframe文本拿到。
問題:預覽圖片只有先傳給后台,后台再返回一個線上的地址

<iframe id="iframe1" name="formtarget" style="display: none"></iframe>
<form id="fm1" action="/app04/ajax1/" method="POST" target="formtarget" enctype="multipart/form-data">
    <input type="file" name="k3"/>
    <input type="submit">
</form>
<script>
file.onchange = function() {
    let iframe = document.getElementById('iframe1')
    iframe.addEventListener("load", function() {
        var content = this.contents().
        var data = JSON.parse(content)

    })
}
</script>

3、FormData異步上傳

利用FormData模擬表單數據,通過ajax進行提交,可以更加靈活地發送Ajax請求。可以使用FormData來模擬表單提交。

let files = e.target.files // 獲取input的file對象
let formData = new FormData();
formData.append('file', file);
axios.post(url, formData);

二、大文件上傳

在同一個請求中,要上傳大量的數據,導致整個過程會比較漫長,且失敗后需要重頭開始上傳

大文件上傳我們需要考慮三個方面:

  • 切片:拆分上傳請求
  • 斷點續傳
  • 顯示上傳進度和暫停上傳

1、切片

識別切片來源
保證切片拼接順序

  • 我們一般采用編碼的方式進行上傳,獲取文件對應的二進制內容。
  • 計算出內容的總大小,根據文件大小切成對應的分片。
  • 上傳時標識出當前文件,告訴后端上傳到了第幾個(可以用時間戳形式)。
    • 不加表示的話后端在追加切片時,無法識別切片順序
    • 接口異常的情況下無法正確拼接

實現
根據文件名、文件長度等基本信息進行拼接,為了避免多個用戶上傳相同的文件,可以再額外拼接用戶信息如uid等保證唯一性
根據文件的二進制內容計算文件的hash,這樣只要文件內容不一樣,則標識也會不一樣,缺點在於計算量比較大.
將文件拆分成piece大小的分塊,然后每次請求只需要上傳這一個部分的分塊即可

 let file = document.querySelector("[name=file]").files[0];
const LENGTH = 1024 * 1024 * 0.1;
let chunks = sliceFile(file, LENGTH); // 首先拆分切片
chunks.forEach((chunk,index) => {

    let fd = new FormData();
    fd.append("file", chunk);
    // 傳遞context
    fd.append("context", file.name + file.length);
    // 傳遞切片索引值
    fd.append("chunk", index + 1);

    upload(fd)    
})
  function sliceFile(file, piece = 1024 * 1024 * 5) {
        let totalSize = file.size; // 文件總大小

        let start = 0; // 每次上傳的開始字節
        let end = start + piece; // 每次上傳的結尾字節
        let chunks = []

        while (start < totalSize) {
            // 根據長度截取每次需要上傳的數據
            // File對象繼承自Blob對象,因此包含slice方法
            let blob = file.slice(start, end);
            chunks.push(blob)
            start = end;

            end = start + piece;
        }
        return chunks
    }

請求

/**
 * 文件上傳  
 * @param {} params
 */
export function upload (params) {
  const data = new FormData();
  data.append('file', params.file);
  data.append('type', params.type);
  return $axios({
    method: 'post',
    url: "/api/Files/upload",
    data: data,
    headers: {
      'Content-Type': 'multipart/form-data',
    }
  })
}

2、斷點續傳

我們在上傳或者下載文件的時候,如果已經進行了一部分,這時候網絡故障、頁面關閉的情況下,不需要從頭開始操作,而是從指定位置繼續進行操作,這種處理方式就是所說的“斷點續傳”

斷點:的由來是在下載過程中,將一個下載文件分成了多個部分,同時進行多個部分一起的下載,當某個時間點,任務被暫停了,此時下載暫停的位置就是斷點了。
續傳:一個任務從暫停到開始時,會從上一次任務暫停處開始(可以每次傳輸成功后加一個表示為告訴前端傳輸進度)。

實現思路:

  • 保存已上傳的切片信息
  • 選擇未上傳的切片進行上傳
  • 全部上傳成功后后端進行文件合並

實現方案:

  1. 本地存儲:我們可以利用localstorage,cookie等方式存儲在瀏覽器內,這種情況下我們不依賴於后端,直接本地讀取就行。清理了本地文件,會導致上傳記錄丟失。
  2. 其實服務器知道我們已經傳輸到了哪些切片,那些進度,我們通過接口去傳輸為上傳的切片即可。

3、上傳進度和暫停

進度:我們可以利用xhr.upload.onprogress = Function方法做進度的監聽

xhr.upload.onprogress = function(e) {
    if (e.lengthComputable) {
        var percent = Math.floor( e.loaded / e.total * 100);//進度計算
        if(percent == 100){
           
        }else{
           
        }
    }
};

暫停:如果該請求已被發出,XMLHttpRequest.abort() 方法將終止該請求,實現上傳暫停的效果。
繼續:和斷點繼傳類似,先獲取傳輸的列表,然后重新發送未上傳的切片。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM