一文帶你徹底搞懂JS大文件分片上傳的實(shí)現(xiàn)
學(xué)習(xí)AbortController
AbortController 接口表示一個(gè)控制器對(duì)象,允許你根據(jù)需要中止一個(gè)或多個(gè) Web 請(qǐng)求。
可以通過這個(gè)來實(shí)現(xiàn)取消或者中斷請(qǐng)求的功能。
axios.abort();底層實(shí)現(xiàn)就是這個(gè)。
認(rèn)識(shí)了
fse = require('fs-extra')
概述
實(shí)現(xiàn)文件分片上傳的原理就是通過將文件的ArrayBlob形式,通過file(blob)的方法slice,來實(shí)現(xiàn)將文件拆成幾個(gè)部分,然后排上順序傳到服務(wù)端,最后傳完了調(diào)用服務(wù)端的merge合并接口,服務(wù)端將文件合并。
服務(wù)端實(shí)現(xiàn)原理,上傳前創(chuàng)建文件夾,命名注意了,可以參考給到的代碼。然后將獲得的文件通過fse的寫入方法,寫入到我們創(chuàng)建的文件夾中,并且有相應(yīng)的排序。最后我們通過合并方法將文件合并到一個(gè)文件。
詳細(xì)學(xué)習(xí)
Client端
getChunkListAndFileMd5函數(shù)
這個(gè)函數(shù)用來創(chuàng)建分片數(shù)組,以及生成Hash簽名。
創(chuàng)建分片列表數(shù)組,我們使用的方法是file.prototype.slice當(dāng)然這里我們?yōu)榱私鉀Q兼容性問題,我們使用的代碼是:
export function getBlobSlice() { return (File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice); }
創(chuàng)建分片:getBlobSlice.call(file,start,end)。這里start、end分別是起始位置和終點(diǎn)位置,我們確認(rèn)好分片大小就可以計(jì)算start和end。
好,那么我們開始了,我們定義size是 5M 代碼如下
const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
我們定義一個(gè)對(duì)象保存默認(rèn)配置:
const DEFAULT_OPTIONS = { chunkSize: DEFAULT_CHUNK_SIZE, };
我們來定義分片的編號(hào),初始值肯定是 0。我們定義chunkSize來保存我們上邊定義的常量分片大小。
let currentChunk = 0; const chunkSize = this.fileUploaderClientOptions.chunkSize;
接下來我們需要計(jì)算需要多少個(gè)分片,很簡(jiǎn)單:文件總的大小/每個(gè)分片的大小,最終結(jié)果可能是個(gè)小數(shù),但是為了保證分片的完整肯定是向上取整。
const chunks = Math.ceil(file.size / chunkSize);
定義一個(gè)函數(shù)來加載分片,代碼如下:
function loadNextChunk() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; const chunk = blobSlice.call(file, start, end); chunkList.push(chunk); fileReader.readAsArrayBuffer(chunk); }
拆解這個(gè)函數(shù):
1.計(jì)算start位置,這個(gè)不難理解。
2.計(jì)算end位置,這里需要判斷一下,主要是針對(duì)兩種情況:
文件尺寸很小,小于分片大小,直接取文件的尺寸。
最后一個(gè)切片,大小可能沒有切片尺寸大,我們直接取文件大小的位置。
3.調(diào)用我們上邊說的函數(shù)來實(shí)現(xiàn)分片,并且獲取當(dāng)前的分片。
4.將當(dāng)前的分片push到我們的數(shù)組中。
5.調(diào)用fileReader.readAsArrayBuffer方法來讀取分片。(其實(shí)當(dāng)前的分片是一個(gè)blob實(shí)例,我們通過這個(gè)方法可以讀取到里邊的內(nèi)容)。
接下來我們來看看fileReader是怎么回事。
認(rèn)識(shí)FileReader
不懂fileReader的可以先看看文檔,了解下里邊的方法。
FileReader 對(duì)象允許 Web 應(yīng)用程序異步讀取存儲(chǔ)在用戶計(jì)算機(jī)上的文件(或原始數(shù)據(jù)緩沖區(qū))的內(nèi)容,使用 File 或 Blob 對(duì)象指定要讀取的文件或數(shù)據(jù)。
其中 File 對(duì)象可以是
- 來自用戶在一個(gè) 元素上選擇文件后返回的FileList對(duì)象
- 也可以來自拖放操作生成的 DataTransfer對(duì)象,
- 還可以是來自在一個(gè)HTMLCanvasElement上執(zhí)行mozGetAsFile()方法后返回結(jié)果。
FileReader可以在Web Worker中使用。 (重要,可以很大成都解決性能問題)
這里因?yàn)橛玫搅薋ileReader的方法,所以重點(diǎn)講講這個(gè)對(duì)象的方法。
- FileReader.abort(); 中止讀取操作。
- FileReader.readAsArrayBuffer(); 開始讀取指定的 Blob中的內(nèi)容,一旦完成,result 屬性中保存的將是被讀取文件的 ArrayBuffer數(shù)據(jù)對(duì)象。
- FileReader.readAsDataURL(); 開始讀取指定的Blob中的內(nèi)容。一旦完成,result屬性中將包含一個(gè)data: URL 格式的 Base64 字符串以表示所讀取文件的內(nèi)容。
- FileReader.readAsText();開始讀取指定的Blob中的內(nèi)容。一旦完成,result屬性中將包含一個(gè)字符串以表示所讀取的文件內(nèi)容。
我們目前需要讀取分片的內(nèi)容,并且需要ArrayBuffer的格式,所以我們創(chuàng)建一個(gè)FileReader對(duì)象實(shí)例,并且使用readAsArrayBuffer方法來讀取文件,通過onload事件來監(jiān)聽,獲取最終結(jié)果。代碼如下:
const fileReader = new FileReader(); fileReader.onload = function (e) { // 我們讀取到的結(jié)果是 e.target.result } fileReader.onerror = function (e) { // 這里表示讀取失敗 }
上邊loadNextChunk函數(shù)中調(diào)用了這個(gè)方法。
fileReader.readAsArrayBuffer(chunk);
使用md5來實(shí)現(xiàn)簽名
用到了 spark-md5 這個(gè)三方庫(kù)。主要使用的方法是生成md5的編碼。
npm install --save spark-md5
這個(gè)就是一個(gè)處理分片的一個(gè)庫(kù)。給出了方法:
const spark = new SparkMD5.ArrayBuffer();
將分片 v 添加到對(duì)象中
spark.append(v)
最終結(jié)果返回
const result = spark.end()
uploadFile函數(shù)
這個(gè)函數(shù)用來上傳文件的函數(shù)。
首先我們要獲取上邊我們函數(shù)得到的值 md5、chunkList。
const { md5, chunkList } = yield this.getChunkListAndFileMd5(file);
上傳文件前,需要調(diào)用接口來初始化文件分片上傳,這里我們其實(shí)就是調(diào)用初始化文件分片上傳的接口requestOptions.initFilePartUploadFunc。
yield requestOptions.initFilePartUploadFunc();
這個(gè)接口在后端的實(shí)現(xiàn)其實(shí)就是創(chuàng)建好一個(gè)文件夾用來保存分片,這里就不多說了,之后在學(xué)習(xí)Server代碼的時(shí)候我們細(xì)講。
接下來就是開始上傳我們的分片了,上傳方法requestOptions.uploadPartFileFunc:
for (let index = 0; index < chunkList.length; index++) { try { yield requestOptions.uploadPartFileFunc(chunkList[index], index); } catch (e) { console.warn(`${index} part upload failed`); retryList.push(index); } }
注意了:我們不能保證全部順利上傳,如果中間出現(xiàn)問題中斷了上傳等問題,我們?nèi)绾翁幚恚?/p>
這里我們使用retryTimes來獲取需要重新上傳的列表。
for (let retry = 0; retry < requestOptions.retryTimes; retry++) { if (retryList.length > 0) { console.log(`retry start, times: ${retry}`); for (let a = 0; a < retryList.length; a++) { const blobIndex = retryList[a]; try { yield requestOptions.uploadPartFileFunc(chunkList[blobIndex], blobIndex); retryList.splice(a, 1); } catch (e) { console.warn(`${blobIndex} part retry upload failed, times: ${retry}`); } } } }
最后我們調(diào)用上傳結(jié)束的接口requestOptions.finishFilePartUploadFunc(md5),其實(shí)這個(gè)接口主要是通知服務(wù)端,分片都上傳完了,服務(wù)端可以進(jìn)行文件合并了,最終將分片合并成一個(gè)文件。
if (retryList.length === 0) { return yield requestOptions.finishFilePartUploadFunc(md5); } else { throw Error(`upload failed, some chunks upload failed: ${JSON.stringify(retryList)}`); }
至此,客戶端的操作完畢!
以上就是一文帶你徹底搞懂JS大文件分片上傳的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于JS大文件分片上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javascript 實(shí)現(xiàn)放大鏡效果實(shí)例詳解
這篇文章主要介紹了Javascript 實(shí)現(xiàn)放大鏡效果實(shí)例詳解的相關(guān)資料,這里附有實(shí)現(xiàn)實(shí)例代碼,具有參考價(jià)值,需要的朋友可以參考下2016-12-12

關(guān)于js中removeEventListener取消事件監(jiān)聽的坑

重學(xué) JS:為啥 await 不能用在 forEach 中詳解

解決微信授權(quán)成功后點(diǎn)擊按返回鍵出現(xiàn)空白頁(yè)和報(bào)錯(cuò)的問題

DOM操作原生js 的bug,使用jQuery 可以消除的解決方法

jquery實(shí)現(xiàn)的圖片點(diǎn)擊滾動(dòng)效果