node大文件上傳如何做斷點(diǎn)續(xù)傳?

[圖片上傳失敗...(image-887dc4-1651990424785)]

一、是什么

不管怎樣簡單的需求凌那,在量級達(dá)到一定層次時,都會變得異常復(fù)雜

文件上傳簡單,文件變大就復(fù)雜

上傳大文件時囱井,以下幾個變量會影響我們的用戶體驗(yàn)

  • 服務(wù)器處理數(shù)據(jù)的能力
  • 請求超時
  • 網(wǎng)絡(luò)波動

上傳時間會變長庞呕,高頻次文件上傳失敗地啰,失敗后又需要重新上傳等等

為了解決上述問題亏吝,我們需要對大文件上傳單獨(dú)處理

這里涉及到分片上傳及斷點(diǎn)續(xù)傳兩個概念

分片上傳

分片上傳,就是將所要上傳的文件止喷,按照一定的大小弹谁,將整個文件分隔成多個數(shù)據(jù)塊(Part)來進(jìn)行分片上傳

如下圖

[圖片上傳失敗...(image-3d449b-1651990424785)]

上傳完之后再由服務(wù)端對所有上傳的文件進(jìn)行匯總整合成原始的文件

大致流程如下:

  1. 將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊;
  2. 初始化一個分片上傳任務(wù)向图,返回本次分片上傳唯一標(biāo)識榄攀;
  3. 按照一定的策略(串行或并行)發(fā)送各個分片數(shù)據(jù)塊;
  4. 發(fā)送完成后贞瞒,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整乒融,則進(jìn)行數(shù)據(jù)塊合成得到原始文件

斷點(diǎn)續(xù)傳

斷點(diǎn)續(xù)傳指的是在下載或上傳時赞季,將下載或上傳任務(wù)人為的劃分為幾個部分

每一個部分采用一個線程進(jìn)行上傳或下載晃财,如果碰到網(wǎng)絡(luò)故障断盛,可以從已經(jīng)上傳或下載的部分開始繼續(xù)上傳下載未完成的部分,而沒有必要從頭開始上傳下載命迈。用戶可以節(jié)省時間,提高速度

一般實(shí)現(xiàn)方式有兩種:

  • 服務(wù)器端返回征椒,告知從哪開始
  • 瀏覽器端自行處理

上傳過程中將文件在服務(wù)器寫為臨時文件,等全部寫完了(文件上傳完)蒙秒,將此臨時文件重命名為正式文件即可

如果中途上傳中斷過晕讲,下次上傳的時候根據(jù)當(dāng)前臨時文件大小瓢省,作為在客戶端讀取文件的偏移量疑枯,從此位置繼續(xù)讀取文件數(shù)據(jù)塊,上傳到服務(wù)器從此偏移量繼續(xù)寫入文件即可

二、實(shí)現(xiàn)思路

整體思路比較簡單液兽,拿到文件,保存文件唯一性標(biāo)識柑晒,切割文件,分段上傳涌庭,每次上傳一段,根據(jù)唯一性標(biāo)識判斷文件上傳進(jìn)度猛拴,直到文件的全部片段上傳完畢

[圖片上傳失敗...(image-6aad61-1651990424785)]

下面的內(nèi)容都是偽代碼

讀取文件內(nèi)容:

const input = document.querySelector('input');
input.addEventListener('change', function() {
    var file = this.files[0];
});

可以使用md5實(shí)現(xiàn)文件的唯一性

const md5code = md5(file);

然后開始對文件進(jìn)行分割

var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.addEventListener("load", function(e) {
    //每10M切割一段,這里只做一個切割演示麻蹋,實(shí)際切割需要循環(huán)切割扮授,
    var slice = e.target.result.slice(0, 10*1024*1024);
});

h5上傳一個(一片)

const formdata = new FormData();
formdata.append('0', slice);
//這里是有一個坑的堪侯,部分設(shè)備無法獲取文件名稱芽死,和文件類型关贵,這個在最后給出解決方案
formdata.append('filename', file.filename);
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
    //xhr.responseText
});
xhr.open('POST', '');
xhr.send(formdata);
xhr.addEventListener('progress', updateProgress);
xhr.upload.addEventListener('progress', updateProgress);

function updateProgress(event) {
    if (event.lengthComputable) {
        //進(jìn)度條
    }
}

這里給出常見的圖片和視頻的文件類型判斷

function checkFileType(type, file, back) {
/**
* type png jpg mp4 ...
* file input.change=> this.files[0]
* back callback(boolean)
*/
    var args = arguments;
    if (args.length != 3) {
        back(0);
    }
    var type = args[0]; // type = '(png|jpg)' , 'png'
    var file = args[1];
    var back = typeof args[2] == 'function' ? args[2] : function() {};
    if (file.type == '') {
        // 如果系統(tǒng)無法獲取文件類型亥啦,則讀取二進(jìn)制流,對二進(jìn)制進(jìn)行解析文件類型
        var imgType = [
            'ff d8 ff', //jpg
            '89 50 4e', //png

            '0 0 0 14 66 74 79 70 69 73 6F 6D', //mp4
            '0 0 0 18 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 33 67 70 35', //mp4
            '0 0 0 0 66 74 79 70 4D 53 4E 56', //mp4
            '0 0 0 0 66 74 79 70 69 73 6F 6D', //mp4

            '0 0 0 18 66 74 79 70 6D 70 34 32', //m4v
            '0 0 0 0 66 74 79 70 6D 70 34 32', //m4v

            '0 0 0 14 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 66 74 79 70 71 74 20 20', //mov
            '0 0 0 0 6D 6F 6F 76', //mov

            '4F 67 67 53 0 02', //ogg
            '1A 45 DF A3', //ogg

            '52 49 46 46 x x x x 41 56 49 20', //avi (RIFF fileSize fileType LIST)(52 49 46 46,DC 6C 57 09,41 56 49 20,4C 49 53 54)
        ];
        var typeName = [
            'jpg',
            'png',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'mp4',
            'm4v',
            'm4v',
            'mov',
            'mov',
            'mov',
            'ogg',
            'ogg',
            'avi',
        ];
        var sliceSize = /png|jpg|jpeg/.test(type) ? 3 : 12;
        var reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.addEventListener("load", function(e) {
            var slice = e.target.result.slice(0, sliceSize);
            reader = null;
            if (slice && slice.byteLength == sliceSize) {
                var view = new Uint8Array(slice);
                var arr = [];
                view.forEach(function(v) {
                    arr.push(v.toString(16));
                });
                view = null;
                var idx = arr.join(' ').indexOf(imgType);
                if (idx > -1) {
                    back(typeName[idx]);
                } else {
                    arr = arr.map(function(v) {
                        if (i > 3 && i < 8) {
                            return 'x';
                        }
                        return v;
                    });
                    var idx = arr.join(' ').indexOf(imgType);
                    if (idx > -1) {
                        back(typeName[idx]);
                    } else {
                        back(false);
                    }

                }
            } else {
                back(false);
            }

        });
    } else {
        var type = file.name.match(/\.(\w+)$/)[1];
        back(type);
    }
}

調(diào)用方法如下

checkFileType('(mov|mp4|avi)',file,function(fileType){
    // fileType = mp4,
    // 如果file的類型不在枚舉之列禁悠,則返回false
});

上面上傳文件的一步念祭,可以改成:

formdata.append('filename', md5code+'.'+fileType);

有了切割上傳后,也就有了文件唯一標(biāo)識信息碍侦,斷點(diǎn)續(xù)傳變成了后臺的一個小小的邏輯判斷

后端主要做的內(nèi)容為:根據(jù)前端傳給后臺的md5值,到服務(wù)器磁盤查找是否有之前未完成的文件合并信息(也就是未完成的半成品文件切片)瓷产,取到之后根據(jù)上傳切片的數(shù)量站玄,返回數(shù)據(jù)告訴前端開始從第幾節(jié)上傳

如果想要暫停切片的上傳,可以使用XMLHttpRequestabort方法

三濒旦、使用場景

  • 大文件加速上傳:當(dāng)文件大小超過預(yù)期大小時株旷,使用分片上傳可實(shí)現(xiàn)并行上傳多個 Part, 以加快上傳速度
  • 網(wǎng)絡(luò)環(huán)境較差:建議使用分片上傳尔邓。當(dāng)出現(xiàn)上傳失敗的時候晾剖,僅需重傳失敗的Part
  • 流式上傳:可以在需要上傳的文件大小還不確定的情況下開始上傳。這種場景在視頻監(jiān)控等行業(yè)應(yīng)用中比較常見

小結(jié)

當(dāng)前的偽代碼梯嗽,只是提供一個簡單的思路齿尽,想要把事情做到極致,我們還需要考慮到更多場景灯节,比如

  • 切片上傳失敗怎么辦
  • 上傳過程中刷新頁面怎么辦
  • 如何進(jìn)行并行上傳
  • 切片什么時候按數(shù)量切循头,什么時候按大小切
  • 如何結(jié)合 Web Work 處理大文件上傳
  • 如何實(shí)現(xiàn)秒傳

人生又何嘗不是如此,極致的人生體驗(yàn)有無限可能炎疆,越是后面才發(fā)現(xiàn)越是精彩 _

原文地址

https://www.webxiu.com.cn/post/10000462

參考文獻(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卡骂,一起剝皮案震驚了整個濱河市椿浓,隨后出現(xiàn)的幾起案子宰翅,更是在濱河造成了極大的恐慌,老刑警劉巖成肘,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亿遂,死亡現(xiàn)場離奇詭異浓若,居然都是意外死亡盒使,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門七嫌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苞慢,你說我怎么就攤上這事诵原。” “怎么了挽放?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵绍赛,是天一觀的道長。 經(jīng)常有香客問我辑畦,道長吗蚌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任纯出,我火速辦了婚禮蚯妇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暂筝。我一直安慰自己箩言,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布焕襟。 她就那樣靜靜地躺著陨收,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸵赖。 梳的紋絲不亂的頭發(fā)上务漩,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音它褪,去河邊找鬼饵骨。 笑死,一個胖子當(dāng)著我的面吹牛列赎,可吹牛的內(nèi)容都是我干的宏悦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼包吝,長吁一口氣:“原來是場噩夢啊……” “哼饼煞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诗越,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤砖瞧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嚷狞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體块促,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荣堰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了竭翠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片振坚。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖斋扰,靈堂內(nèi)的尸體忽然破棺而出渡八,到底是詐尸還是另有隱情,我是刑警寧澤传货,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布屎鳍,位于F島的核電站,受9級特大地震影響问裕,放射性物質(zhì)發(fā)生泄漏逮壁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一粮宛、第九天 我趴在偏房一處隱蔽的房頂上張望窥淆。 院中可真熱鬧,春花似錦巍杈、人聲如沸祖乳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眷昆。三九已至,卻和暖如春汁咏,著一層夾襖步出監(jiān)牢的瞬間亚斋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工攘滩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帅刊,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓漂问,卻偏偏與公主長得像赖瞒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚤假,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容