JavaScript實現大檔案分片上傳處理
阿新 • • 發佈:2021-08-04
很多時候我們在處理檔案上傳時,如視訊檔案,小則幾十M,大則 1G+,以一般的HTTP請求傳送資料的方式的話,會遇到的問題:
1、檔案過大,超出服務端的請求大小限制;
2、請求時間過長,請求超時;
3、傳輸中斷,必須重新上傳導致前功盡棄
這些問題很影響使用者的體驗感,所以下面介紹一種基於原生進行檔案分片處理上傳的方案,具體實現過程如下:
1、通過dom獲取檔案物件,並且對檔案進行MD5加密(檔案內容+檔案標題形式),採用SparkMD5進行檔案加密;
2、進行分片設定,檔案File基於Blob,繼承了Blob的功能,可以把File當成Blob的子類,利於Blob的slice方法進行檔案分片處理,並且依次進行上傳
1. 上傳檔案頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>檔案上傳</title> <script src="https://cdn.boot.com/axios/0.18.0/axios.min."></script> <script src="https://code..com/jquery-3.4.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.js"></script> <style> /* 自定義進度條樣式 */ .precent input[type=range] { -webkit-appearance: none; /*清除系統預設樣式*/ width: 7.8rem; /* background: -webkit-linear-gradient(#ddd,#ddd) no-repeat,#ddd; */ /*設定左邊顏色為#61bd12,右邊顏色為#ddd*/ background-size: 75% 100%; /*設定左右寬度比例*/ height: 0.6rem; /*橫條的高度*/ borhttp://www.cppcns.comder-radius: 0.4rem; border: 1px solid #ddd; box-shadow: 0 0 10px rgba(0,.125) inset ; } /*拖動塊的樣式*/ .precent input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; /*清除系統預設樣式*/ height: .9rem; /*拖動塊高度*/ width: .9rem; /*拖動塊寬度*/ background: #fff; /*拖動塊背景*/ border-radius: 50%; /*外觀設定為圓形*/ border: solid 1px #ddd; /*設定邊框*/ } </style> </head> <body> <h1>大檔案分片上傳測試</h1> <div> <input id="file" type="file" name="avatar" /> <div style="padding: 10px 0;"> <input id="submitBtn" type="button" value="提交" /> <input id="pauseBtn" type="button" value="暫停" /> </div> <div class="precent"> <input type="range" value="0" /><span id="precentVal">0%</span> </div> </div> <script type="text/script" src="./js/index.js"></script> </body> </html>
2. 大檔案分片上傳處理
$(document).ready(() => { const submitBtn = $('#submitBtn'); //提交按鈕 const precentDom = $(".precent input")[0]; // 進度條 const precentVal = $("#precentVal"); // 進度條值對應dom const pauseBtn = $('#pauseBtn'); // 暫停按鈕 // 每個chunk的大小,設定為1兆 const chunkSize = 1 * 1024 * 1024; // 獲取slice方法,做相容處理 const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; // 對檔案進行MD5加密(檔案內容+檔案標題形式) const hashFile = (file) => { return new Promise((resolve,reject) => { const chunks = Math.ceil(file.size / chunkSize); let currentChunk = 0; const spark = new SparkMD5.ArrayBuffer(); 客棧 const fileReader = new FileReader(); function loadNext() { const start = currentChunk * chunkSize; const end = start + chunkSize >= file.size ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file,start,end)); } fileReader.onload = e => { spark.append(e.target.result); // Append array buffer currentChunk += 1; if (currentChunk < chunks) { loadNext(); } else { console.log('finished loading'); const result = spark.end(); // 通過內容和檔名稱進行md5加密 const sparkMd5 = new SparkMD5(); sparkMd5.append(result); sparkMd5.append(file.name); const hexHash = sparkMd5.end(); resolve(hexHash); } }; fileReader.onerror = () => { console.warn('檔案讀取失敗!'); }; loadNext(); }).catch(err => { console.log(err); }); } // 提交 submitBtn.on('click',async () => { var pauseStatus = false; var nowUploadNums = 0 // 1.讀取檔案 const fileDom = $('#file')[0]; const files = fileDom.files; const file = files[0]; if (!file) { alert('沒有獲取檔案'); return; } // 2.設定分片引數屬性、獲取檔案MD5值 const hash = await hashFile(file); //檔案 hash const blockCount = Math.ceil(file.size / chunkSize); // 分片總數 const axiosPromiseArray = []; // axiosPromise陣列 // 檔案上傳 const uploadFile = () => { const start = nowUploadNums * chunkSize; const end = Math.min(file.size,start + chunkSize); // 構建表單 const form = new FormData(); // blobSlice.call(file,end)方法是用於進行檔案分片 form.append('file',blobSlice.call(file,end)); form.append('index',nowUploadNums); form.append('hash',hash); // ajax提交 分片,此時 content-type 為 multipart/form-data const axiosOptions = { onUploadProgress: e => { nowUploadNums++; // 判斷分片是否上傳完成 if (nowUploadNums < blockCount) { setPrecent(nowUploadNums,blockCount); uploadFile(nowUploadNums) } else { // 4.所有分片上傳後,請求合併分片檔案 axios.all(axiosPromiseArray).then(() => { setPrecent(blockCount,blockCount); // 全部上傳完成 axios.post('/file/merge_chunks',{ name: file.name,total: blockCount,hash }).then(res => { console.log(res.data,file); pauseStatus = false; alert('上傳成功'); }).catch(err => { console.log(err); }); }); } },}; // 加入到 Promise 陣列中 if (!pauseStatus) { axiosPromiseArray.push(axios.post('/file/upload',form,axiosOptions)); } } // 設定進度條 function setPrecent(now,total) { var prencentValue = ((now / total) * 100).toFixed(2) precentDom.value = prencentValue precentVal.text(prencentValue + '%') precentDom.style.cssText = `background:-webkit-linear-gradient(top,#059CFA,#059CFA) 0% 0% / ${prencentValue}% 100% no-repeat` } // 暫停 pauseBtn.on('click',(e) => { pauseStatus = !pauseStatus; e.currentTarget.value = pauseStatus ? '開始' : '暫停' if (!pauseStatus) { uploadFile(nowUploadNums) } }) uploadFile(); }); })
3. 檔案上傳和合並分片檔案介面(node)
const Router = require('koa-router');
const multer = require('koa-multer');
const fs = require('fs-extra');
const path = require('path');
const router = new Router();
const { mkdirsSync } = require('../utils/dir');
const uploadPath = path.join(__dirname,'upload');
const chunkUploadPath = path.join(uploadPath,'temp');
const upload = multer({ dest: chunkUploadPath });
// 檔案上傳介面
router.post('/file/upload',upload.single('file'),async (ctx,next) => {
const { index,hash } = ctx.req.body;
const chunksPath = path.join(chunkUpl程式設計客棧oadPath,hash,'/');
if(!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);
fs.renameSync(ctx.req.file.path,chunksPath + hash + '-' + index);
ctx.status = 200;
ctx.res.end('Success');
})
// 合併分片檔案介面
router.post('/file/merge_chunks',next) => {
const { name,total,hash } = ctx.request.body;
const chunksPath = path.join(chunkUploadPath,'/');
const filePath = path.join(uploadPath,name);
// 讀取所有的chunks
const chunks = fs.readdirSync(chunksPath);
// 建立儲存檔案
fs.writeFileSync(filePath,'');
if(chunks.length !== total || chunks.length === 0) {
ctx.status = 200;
ctx.res.end('切片檔案數量不符合');
return;
}
for (let i = 0; i < total; i++) {
// 追加寫入到檔案中
fs.appendFileSync(filePath,fs.readFileSync(chunksPath + hash + '-' +i));
// 刪除本次使用的chunk
fs.unlinkSync(chunksPath + hash + '-' +i);
}
fs.rmdirSync(chunksPath);
// 檔案合併成功,可以把檔案資訊進行入庫。
ctx.status = 200;
程式設計客棧 ctx.res.end('Success');
})
以上就是檔案分片上傳的基本過程,過程中加入了上傳進度條、暫停和開始上傳操作,見詳細程式碼
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。