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

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)文章

最新評論