最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript利用切片實現(xiàn)大文件斷點續(xù)傳

 更新時間:2022年03月30日 14:47:54   作者:風徹  
斷點續(xù)傳即在文件上傳期間因為一些原因而導致上傳終止時,下次再次上傳同一個文件就從上一次上傳到一半的地方繼續(xù)上傳,以節(jié)省上傳時間。本文將利用切片實現(xiàn)大文件斷點續(xù)傳功能,快來跟隨小編一起學一學吧

最近公司需要用戶上傳比較大的文件,一個文件可能大于1GB,如果出現(xiàn)網(wǎng)絡(luò)波動或者用戶違規(guī)操作導致上傳中斷,那么就必須重新重頭上傳。身為前端,與后端商量后,查看了一些已經(jīng)成熟的的實現(xiàn)方案,最后使用斷點續(xù)傳優(yōu)化上傳的邏輯。

完整代碼-github

什么是斷點續(xù)傳

在文件上傳期間因為一些原因而導致上傳終止(刷新或網(wǎng)絡(luò)中斷)時,下次再次上傳同一個文件就從上一次上傳到一半的地方繼續(xù)上傳,以節(jié)省上傳時間。

實現(xiàn)思路

主體思路是將比較大的文件分成若干個切片。并非一次性將一個文件整體傳輸給服務(wù)器,而是將分割的切片一部分一部分地傳給服務(wù)器。服務(wù)器將已上傳的切片暫存,當所有的切片都上傳成功了,再將切片合并成一整個文件。

這么做就可以利用切片使用斷點續(xù)傳的功能。具體邏輯是如果在上傳切片的途中斷了,那么下次再次上傳同一個文件的時候可以先向服務(wù)器端發(fā)一個請求,獲取已經(jīng)上傳了哪些切片,與整體切片進行比對后,再將剩下未上傳的切片繼續(xù)上傳。

其中后端處理切片和合并的一個細節(jié)點為:將大文件進行切片時,后端需要將每一個切片的文件名為${原文件HASH值}_${切片序號}.${文件后綴}例如'f07ec272dbb0b883eed4b2f415625a90_2.mp4'。并且將服務(wù)器存的切片的臨時文件夾的名字命名為hash值。最后所有的切片上傳完成時,再調(diào)用合并接口,后端將所有臨時文件夾中的切片合并。

需要后端提供的api

以下api需要后端提供開發(fā)

獲取已經(jīng)上傳的切片

  url:/upload_already
  method:GET
  params:
    HASH:文件的HASH名字
  return:application/json
    code:0成功 1失敗,
    codeText:狀態(tài)描述,
    fileList:[...]

此方法用來獲取已上傳文件的所有切片的切片名,例如返回:

{
fileList:['f07ec272dbb0b883eed4b2f415625a90_1.mp4','f07ec272dbb0b883eed4b2f415625a90_2.mp4','f07ec272dbb0b883eed4b2f415625a90_3.mp4']
}

意思為HASH值為'f07ec272dbb0b883eed4b2f415625a9'的文件已經(jīng)上傳了三個切片,接下來只需要從第四個切片開始上傳即可。如果是空數(shù)組,說明此文件第一次上傳。

上傳切片

  url:/upload_chunk
  method:POST
  params:multipart/form-data
    file:切片數(shù)據(jù)
    filename:切片名字「文件生成的HASH_切片編號.后綴」
  return:application/json
    code:0成功 1失敗,
    codeText:狀態(tài)描述,
    originalFilename:文件原始名稱,
    servicePath:文件服務(wù)器地址

上傳file對象格式的切片文件,并且將切片名字已${原文件HASH值}_${切片序號}.${文件后綴}格式傳給后端,后端利用hash值將切片暫存在一個臨時文件夾中,最后所有切片上傳完成,就將切片合并,然后刪除這個臨時文件夾。

合并切片

  url:/upload_merge
  method:POST
  params:application/x-www-form-urlencoded
    HASH:文件的HASH名字
    count:切片數(shù)量
  return:application/json
    code:0成功 1失敗,
    codeText:狀態(tài)描述,
    originalFilename:文件原始名稱,
    servicePath:文件服務(wù)器地址

當所有切片上傳完成(前段自行判斷)之后,調(diào)用合并接口,后端會將切片合并,然后刪除存切片的臨時文件夾。

前端代碼細節(jié)實現(xiàn)

HASH值的獲取方法

  • 使用FileReader對象將選擇的文件對象轉(zhuǎn)為buffer
  • 依據(jù)文件的buffer使用MD5庫生成文件的HASH值。

封裝成函數(shù):

/**
 * 傳入文件對象,返回文件生成的HASH值,后綴,buffer,以HASH值為名的新文件名
 * @param file
 * @returns {Promise<unknown>}
 */
const changeBuffer = file => {
    return new Promise(resolve => {
        let fileReader = new FileReader();
        fileReader.readAsArrayBuffer(file);
        fileReader.onload = ev => {
            let buffer = ev.target.result,
                spark = new SparkMD5.ArrayBuffer(),
                HASH,
                suffix;
            spark.append(buffer);
            HASH = spark.end();
            suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1];
            resolve({
                buffer,
                HASH,
                suffix,
                filename: `${HASH}.${suffix}`
            });
        };
    });
};

切片處理

使用Blob對象中的slice函數(shù)可以進行對文件進行切片處理??梢詫⑽募袨樽远x的大小和數(shù)量。

Blob.slice文檔參考

例如file.slice(0,1024)代表將文件的0-1024字節(jié)數(shù)據(jù)切片,然后返回一個新的對象。

總體html結(jié)構(gòu)

<div class="container">
    <div class="item">
        <h3>大文件上傳</h3>
        <section class="upload_box" id="upload7">
            <input type="file" class="upload_inp">
            <div class="upload_button_box">
                <button class="upload_button select">上傳文件</button>
            </div>
            <div class="upload_progress">
                <div class="value"></div>
            </div>
        </section>
    </div>
</div>

使用axios發(fā)送請求

/*把axios發(fā)送請求的公共信息進行提取*/
//創(chuàng)建一個單獨的實例,不去項目全局的或者其他的axios沖突
let instance = axios.create();
instance.defaults.baseURL = 'http://127.0.0.1:8888';
//默認是multipart/form-data格式
instance.defaults.headers['Content-Type'] = 'multipart/form-data';
instance.defaults.transformRequest = (data, headers) => {
    //兼容x-www-form-urlencoded格式的請求發(fā)送
    const contentType = headers['Content-Type'];
    if (contentType === "application/x-www-form-urlencoded") return Qs.stringify(data);
    return data;
};
//統(tǒng)一結(jié)果的處理
instance.interceptors.response.use(response => {
    return response.data;
},reason=>{
    //統(tǒng)一失敗的處理
    return Promise.reject(reason)
});

整體邏輯和代碼

詳細的邏輯在注釋當中,寫的比較詳細

完整代碼-github

(function () {
    let upload = document.querySelector('#upload7'),
        upload_inp = upload.querySelector('.upload_inp'),
        upload_button_select = upload.querySelector('.upload_button.select'),
        upload_progress = upload.querySelector('.upload_progress'),
        upload_progress_value = upload_progress.querySelector('.value');

    const checkIsDisable = element => {
        let classList = element.classList;
        return classList.contains('disable') || classList.contains('loading');
    };

    /**
     * 傳入文件對象,返回文件生成的HASH值,后綴,buffer,以HASH值為名的新文件名
     * @param file
     * @returns {Promise<unknown>}
     */
    const changeBuffer = file => {
        return new Promise(resolve => {
            let fileReader = new FileReader();
            fileReader.readAsArrayBuffer(file);
            fileReader.onload = ev => {
                let buffer = ev.target.result,
                    spark = new SparkMD5.ArrayBuffer(),
                    HASH,
                    suffix;
                spark.append(buffer);
                HASH = spark.end();
                suffix = /.([a-zA-Z0-9]+)$/.exec(file.name)[1];
                resolve({
                    buffer,
                    HASH,
                    suffix,
                    filename: `${HASH}.${suffix}`
                });
            };
        });
    };

    upload_inp.addEventListener('change', async function () {
        //get native file object
        let file = upload_inp.files[0];
        if (!file) return;
        //button add loading
        upload_button_select.classList.add('loading');
        //show progress
        upload_progress.style.display = 'block';

        // 獲取文件的HASH
        let already = [],//已經(jīng)上傳過的切片的切片名
            data = null,
            {
                HASH,
                suffix
            } = await changeBuffer(file);//得到原始文件的hash和后綴

        // 獲取已經(jīng)上傳的切片信息
        try {
            data = await instance.get('/upload_already', {
                params: {
                    HASH
                }
            });
            if (+data.code === 0) {
                already = data.fileList;
            }
        } catch (err) {}

        // 實現(xiàn)文件切片處理 「固定數(shù)量 & 固定大小」
        let max = 1024 * 100,//切片大小先設(shè)置100KB
            count = Math.ceil(file.size / max),//得到應(yīng)該上傳的切片
            index = 0,//存放切片數(shù)組的時候遍歷使用
            chunks = [];//用以存放切片值
        if (count > 100) {//如果切片數(shù)量超過100,那么就只切成100個,因為切片太多的話也會影響調(diào)用接口的速度
            max = file.size / 100;
            count = 100;
        }
        while (index < count) {//循環(huán)生成切片
            //index 0 =>  0~max
            //index 1 =>  max~max*2
            //index*max ~(index+1)*max
            chunks.push({
                file: file.slice(index * max, (index + 1) * max),
                filename: `${HASH}_${index+1}.${suffix}`
            });
            index++;
        }

        index = 0;
        const clear = () => {//上傳完成后,將狀態(tài)回歸
            upload_button_select.classList.remove('loading');
            upload_progress.style.display = 'none';
            upload_progress_value.style.width = '0%';
        };

        //每一次上傳一個切片成功的處理[進度管控&切片合并]
        const complate = async () => {
            // 管控進度條:每上傳完一個切片,就將進度條長度增加一點
            index++;
            upload_progress_value.style.width = `${index/count*100}%`;

            if (index < count) return;
            // 當所有切片都上傳成功,就合并切片
            upload_progress_value.style.width = `100%`;
            try {
                //調(diào)用合并切片方法
                data = await instance.post('/upload_merge', {
                    HASH,
                    count
                }, {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded'
                    }
                });
                if (+data.code === 0) {
                    alert(`恭喜您,文件上傳成功,您可以基于 ${data.servicePath} 訪問該文件~~`);
                    clear();
                    return;
                }
                throw data.codeText;
            } catch (err) {
                alert('切片合并失敗,請您稍后再試~~');
                clear();
            }
        };

        // 循環(huán)上傳每一個切片
        chunks.forEach(chunk => {
            // 已經(jīng)上傳的無需在上傳
            //后臺返回的already格式為['HASH_1.png','HASH_2.png'],既已經(jīng)上傳的文件的切片名
            if (already.length > 0 && already.includes(chunk.filename)) {
                //已經(jīng)上傳過了的切片就無需再調(diào)用接口上傳了
                complate();//動進度條或合并所有切片
                return;
            }
            let fm = new FormData;
            fm.append('file', chunk.file);
            fm.append('filename', chunk.filename);
            instance.post('/upload_chunk', fm).then(data => {//使用form data格式上傳切片
                if (+data.code === 0) {
                    complate();////動進度條或合并所有切片
                    return;
                }
                return Promise.reject(data.codeText);
            }).catch(() => {
                alert('當前切片上傳失敗,請您稍后再試~~');
                clear();
            });
        });
    });
    //觸發(fā)原生的上傳文件框
    upload_button_select.addEventListener('click', function () {
        if (checkIsDisable(this)) return;
        upload_inp.click();
    });
})();

實現(xiàn)效果

第一次上傳時,分別調(diào)用/upload_already,/upload_chunk方法獲取已上傳的切片(空數(shù)組),然后進行切片分割,再一個個進行上傳。

當在此時刷新頁面,終端切片的上傳行為時。此時我們看一下后端的臨時數(shù)據(jù)

可以看到一個以hash至命名的臨時文件夾,并且已經(jīng)上傳了24個切片

如果我們再次選擇同樣的文件上傳,進度條會立即到上次已經(jīng)上傳的位置,already接口返回已上傳的24個切片的名字的數(shù)組。

將剩下的切片上傳完成之后,會調(diào)用merge接口。完成上傳

此時后端的臨時文件夾被刪除,合并為一整個文件。上傳結(jié)束

到此這篇關(guān)于JavaScript利用切片實現(xiàn)大文件斷點續(xù)傳的文章就介紹到這了,更多相關(guān)JavaScript文件斷點續(xù)傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 手機端點擊圖片放大特效PhotoSwipe.js插件實現(xiàn)

    手機端點擊圖片放大特效PhotoSwipe.js插件實現(xiàn)

    這篇文章主要為大家詳細介紹了手機端點擊圖片放大特效PhotoSwipe.js插件實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 淺談javascript:兩種注釋,聲明變量,定義函數(shù)

    淺談javascript:兩種注釋,聲明變量,定義函數(shù)

    下面小編就為大家?guī)硪黄獪\談javascript:兩種注釋,聲明變量,定義函數(shù)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-10-10
  • 詳解uniapp如何處理圖片加載過程中的錯誤

    詳解uniapp如何處理圖片加載過程中的錯誤

    這篇文章給大家詳細介紹了uniapp如何處理圖片加載過程中的錯誤,文章通過代碼示例介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2023-10-10
  • 前端碎片localStorage的清除方法

    前端碎片localStorage的清除方法

    這篇文章主要介紹了前端localStorage清除方法的相關(guān)資料,localStorage是前端存儲數(shù)據(jù)的工具,數(shù)據(jù)不會因為頁面刷新而消失,但測試后需要清除數(shù)據(jù),清除localStorage的方法包括手動清除、編程方式清除、使用瀏覽器提供的清除功能和使用第三方庫,需要的朋友可以參考下
    2024-11-11
  • Javascript實現(xiàn)視頻文件播放功能(示例詳解)

    Javascript實現(xiàn)視頻文件播放功能(示例詳解)

    這篇文章主要介紹了Javascript實現(xiàn)視頻文件播放功能,使用CSS完成相應(yīng)的布局樣式,利用JavaScript函數(shù)來監(jiān)聽進度條,然后使用鼠標點擊按鈕實現(xiàn)對視頻的播放,需要的朋友可以參考下
    2023-10-10
  • JS實現(xiàn)圖片元素轉(zhuǎn)BASE64編碼的簡單示例

    JS實現(xiàn)圖片元素轉(zhuǎn)BASE64編碼的簡單示例

    在Web開發(fā)中,我們經(jīng)常需要將圖片轉(zhuǎn)換為Base64格式,以便在不依賴外部資源的情況下直接在HTML中使用,在這篇文章中,我將向您展示如何使用JavaScript將圖片元素轉(zhuǎn)BASE64編碼,需要的朋友可以參考下
    2023-12-12
  • 詳解JavaScript數(shù)據(jù)類型和判斷方法

    詳解JavaScript數(shù)據(jù)類型和判斷方法

    這篇文章主要介紹了JavaScript數(shù)據(jù)類型和判斷方法的相關(guān)資料,幫助大家更好的理解和學習JavaScript,感興趣的朋友可以了解下
    2020-09-09
  • 一文搞懂JSON(JavaScript Object Notation)

    一文搞懂JSON(JavaScript Object Notation)

    Json 有兩種基本的結(jié)構(gòu),即 Json對象 和 Json 數(shù)組。通過 Json 對象和 Json 數(shù)組這兩種結(jié)構(gòu)的組合可以表示各種復雜的結(jié)構(gòu),今天通過本文給大家介紹JavaScript Object Notation的基本知識,感興趣的朋友一起看看吧
    2021-10-10
  • 一文詳解JS中的Map、Set、WeakMap和WeakSet

    一文詳解JS中的Map、Set、WeakMap和WeakSet

    在JavaScript中,Map、Set、WeakMap和WeakSet是四個不同的數(shù)據(jù)結(jié)構(gòu),它們都有不同的特點和用途,本文詳細介紹了Map、Set、WeakMap和WeakSet的用法及區(qū)別,需要的朋友可以參考下
    2023-04-04

最新評論

永福县| 黄冈市| 东辽县| 双鸭山市| 台安县| 灵川县| 玛纳斯县| 江口县| 彭泽县| 台中县| 高唐县| 通州区| 商水县| 临沧市| 大安市| 灵璧县| 广东省| 南充市| 江北区| 洪洞县| 道真| 徐州市| 富阳市| 东乌珠穆沁旗| 海门市| 吉林省| 环江| 临潭县| 隆化县| 东乌珠穆沁旗| 西乌珠穆沁旗| 尚义县| 莎车县| 上林县| 乐陵市| 修武县| 穆棱市| 工布江达县| 乌兰察布市| 鄯善县| 浦江县|