欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

完整代碼-github

什么是斷點續(xù)傳

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

實現(xiàn)思路

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

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

其中后端處理切片和合并的一個細(xì)節(jié)點為:將大文件進(jìn)行切片時,后端需要將每一個切片的文件名為${原文件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ù)器地址

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

前端代碼細(xì)節(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ù)可以進(jìn)行對文件進(jìn)行切片處理??梢詫⑽募袨樽远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ā)送請求的公共信息進(jìn)行提取*/
//創(chuàng)建一個單獨的實例,不去項目全局的或者其他的axios沖突
let instance = axios.create();
instance.defaults.baseURL = 'http://127.0.0.1:8888';
//默認(rèn)是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)
});

整體邏輯和代碼

詳細(xì)的邏輯在注釋當(dāng)中,寫的比較詳細(xì)

完整代碼-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%';
        };

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

            if (index < count) return;
            // 當(dāng)所有切片都上傳成功,就合并切片
            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();//動進(jìn)度條或合并所有切片
                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();////動進(jìn)度條或合并所有切片
                    return;
                }
                return Promise.reject(data.codeText);
            }).catch(() => {
                alert('當(dāng)前切片上傳失敗,請您稍后再試~~');
                clear();
            });
        });
    });
    //觸發(fā)原生的上傳文件框
    upload_button_select.addEventListener('click', function () {
        if (checkIsDisable(this)) return;
        upload_inp.click();
    });
})();

實現(xiàn)效果

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

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

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

如果我們再次選擇同樣的文件上傳,進(jìn)度條會立即到上次已經(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)文章

  • 詳解JavaScript?中的變量聲明與賦值

    詳解JavaScript?中的變量聲明與賦值

    在?JavaScript?中使用變量或常量之前,必須先進(jìn)行聲明,這篇文章主要介紹了JavaScript中的變量聲明與賦值,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • js獲取當(dāng)前周、上一周、下一周日期

    js獲取當(dāng)前周、上一周、下一周日期

    本文主要介紹了js獲取當(dāng)前周、上一周、下一周日期的實例,具有很好的參考價值。下面跟著小編一起來看下吧
    2017-03-03
  • layui點擊彈框頁面 表單請求的方法

    layui點擊彈框頁面 表單請求的方法

    今天小編就為大家分享一篇layui點擊彈框頁面 表單請求的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • JavaScript?數(shù)組方法filter與reduce

    JavaScript?數(shù)組方法filter與reduce

    這篇文章主要介紹了JavaScript?數(shù)組方法filter與reduce,在ES6新增的數(shù)組方法中,包含了多個遍歷方法,其中包含了用于篩選的filter和reduce
    2022-07-07
  • JS中call apply bind函數(shù)手寫實現(xiàn)demo

    JS中call apply bind函數(shù)手寫實現(xiàn)demo

    這篇文章主要為大家介紹了JS中call apply bind函數(shù)手寫實現(xiàn)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 解決echarts官網(wǎng)打不開訪問失敗的問題

    解決echarts官網(wǎng)打不開訪問失敗的問題

    這篇文章主要介紹了解決echarts官網(wǎng)打不開訪問失敗的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • js select下拉聯(lián)動 更具級聯(lián)性!

    js select下拉聯(lián)動 更具級聯(lián)性!

    這篇文章主要為大家詳細(xì)介紹了js select下拉聯(lián)動的相關(guān)資料,更具級聯(lián)性!文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • TypeScript?Pinia實戰(zhàn)分享(Vuex和Pinia對比梳理總結(jié))

    TypeScript?Pinia實戰(zhàn)分享(Vuex和Pinia對比梳理總結(jié))

    這篇文章主要介紹了TypeScript?Pinia實戰(zhàn)分享(Vuex和Pinia對比梳理總結(jié)),今天我們再來實戰(zhàn)下官方推薦的新的vue狀態(tài)管理工具Pini,感興趣的小伙伴可以參考一下
    2022-06-06
  • JavaScript游戲之優(yōu)化篇

    JavaScript游戲之優(yōu)化篇

    最近回頭看看自己以前寫的游戲代碼,總結(jié)出幾個可以優(yōu)化改進(jìn)的地方,當(dāng)然還有很多地方需要優(yōu)化,還希望大家指出來。
    2010-11-11
  • JS常見面試試題總結(jié)【去重、遍歷、閉包、繼承等】

    JS常見面試試題總結(jié)【去重、遍歷、閉包、繼承等】

    這篇文章主要介紹了JS常見面試試題,總結(jié)分析了javascript去重、遍歷、閉包、繼等相關(guān)算法與操作技巧,需要的朋友可以參考下
    2019-08-08

最新評論