Vue.js+express利用切片實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳
斷點(diǎn)續(xù)傳
在文件上傳期間因?yàn)橐恍┦虑橹袛?比如網(wǎng)絡(luò)中斷,服務(wù)器出錯(cuò),客戶端奔潰),但是在下次上傳同一個(gè)文件可以從上一次上傳的位置繼續(xù)上傳,以節(jié)省上傳時(shí)間。
實(shí)現(xiàn)思路
- 將文件上傳分為多個(gè)切片
- 上傳切片
- 合并切片
- 文件切片驗(yàn)證
- 狀態(tài)的切換
1.文件如何在前端分切片
我們需要為文件生成唯一fileHash
,目的是為了每次上傳判斷此文件是否之前存在過(guò)斷點(diǎn),如果hash相同同時(shí)存在斷點(diǎn),那么就需要斷點(diǎn)續(xù)傳,如果上傳成功過(guò)就直接重新上傳即可。
使用Blob
對(duì)象中的slice
函數(shù)可以進(jìn)行對(duì)文件進(jìn)行切片處理??梢詫⑽募袨樽远x的大小和數(shù)量.
創(chuàng)建一個(gè)createChunl
函數(shù)
const createFileChunk = async (file, size = SIZE) => { const fileChunkList = []; let spark = new SparkMd5.ArrayBuffer(); let readerComplete = false; const reader = new FileReader(); for (let cur = 0; cur < file.size; cur += size) { let data = { file: file.slice(cur, cur + size) }; fileChunkList.push(data); reader.readAsArrayBuffer(data.file); await new Promise((resolve) => { reader.onload = (e) => { spark.append(e.target.result); resolve(); }; }); } fileHash.value = spark.end(); return fileChunkList; };
spark-md5
是一個(gè)高效的md5加密算法,詳情請(qǐng)看文檔spark-md5,通過(guò)spark-md5
將文件內(nèi)容加密為一個(gè)hash值,用來(lái)作為文件上傳的唯一標(biāo)識(shí)。
創(chuàng)建一個(gè)upload
函數(shù)
const handleUpload = async () => { if (!uploadFile.value) return; loading.value = true; const fileChunkList = await createFileChunk(uploadFile.value); let vertifyRes = await request({ url: "/vertify", method: "post", data: { fileHash: fileHash.value, filename: fileHash.value + "." + getSuffix(uploadFile.value.name), }, }); if (!vertifyRes.data.shouldUpload) { alert(vertifyRes.msg); uploadPercentage.value = 100; loading.value = false; return; } data.value = fileChunkList.map(({ file }, index) => ({ chunk: file, hashPrefix: fileHash.value, suffix: getSuffix(uploadFile.value.name), hash: fileHash.value + "-" + index, index: index, percentage: 0, })); await uploadChunk(vertifyRes.data.uploadList); loading.value = false; };
上傳文件切片,首先需要驗(yàn)證hash值是否在服務(wù)端已經(jīng)存在如果存在實(shí)際上相同的文件已經(jīng)存在了,當(dāng)效驗(yàn)未通過(guò)時(shí)說(shuō)明不需要再上傳了,只有當(dāng)效驗(yàn)通過(guò)是才可以上傳文件,驗(yàn)證接口的響應(yīng)數(shù)據(jù)中會(huì)帶有切片的hash值,為了是過(guò)濾掉已上傳的切片提高上傳效率。
剩下的前端只需要過(guò)濾hash的切片,將剩下的切片上傳即可
// 上傳切片 const uploadChunk = async (uploadList = []) => { const requestList = data.value .filter(({ hash }) => !uploadList.includes(hash)) .map(({ chunk, hash, index, hashPrefix }) => { const formData = new FormData(); formData.append("chunk", chunk); formData.append("hash", hash); formData.append("filename", uploadFile.value.name); formData.append("index", index); formData.append("hashPrefix", hashPrefix); return { formData, index }; }) .map(async ({ formData, index }) => { return request({ url: "/upload_chunk", data: formData, onUploadProgress: onPregress.bind(null, data.value[index]), cancelToken: new axios.CancelToken((c) => { aborts.value.push(c); }), headers: { "Content-Type": "multipart/form-data", }, method: "post", }); }); showStopAndResume.value = true; await Promise.all(requestList); // 合并切片 await mergeRequest(); aborts.value = []; };
這里的mergeRequest
是當(dāng)所有的切片上傳成功后,要告訴后端進(jìn)行切片合并。
const mergeRequest = async () => { let res = await request({ url: "/merge_chunk", headers: { "Content-Type": "application/json", }, method: "post", data: { originName: uploadFile.value.name, filename: fileHash.value + "." + getSuffix(uploadFile.value.name), size: SIZE, }, }); };
這樣前端一些核心的代碼基本完成了。
2.文件如何在后端實(shí)現(xiàn)切片上傳
后端暫且使用express
實(shí)現(xiàn)
根據(jù)前端的要求有以下幾個(gè)接口需要實(shí)現(xiàn)
- /upload_chunk
- /merge_chunk
- /vertify
實(shí)現(xiàn)思路
1./upload_chunk
利用fs
相關(guān)api將上傳的緩存chunk資源移動(dòng)到對(duì)應(yīng)目錄中
const multipart = new multiparty.Form({ maxFieldsSize: 200 * 1024 * 1024, }); multipart.parse(req, async (err, fields, files) => { if (err) { res.send({ code: 500, msg: "服務(wù)器錯(cuò)誤", }); return; } const [chunk] = files.chunk; const [hash] = fields.hash; const [hashPrefix] = fields.hashPrefix; const chunkDir = path.resolve(UPLOAD_DIR, hashPrefix); if (!fse.existsSync(chunkDir)) { await fse.mkdirs(chunkDir); } await fse.move(chunk.path, `${chunkDir}/${hash}`); res.send({ code: 200, msg: "上傳成功", }); });
2./merge_chunk
將上傳的chunk
按照hash-index
序號(hào)進(jìn)行順序合并,用到比較核心的api就是fs.createReadStream()
// 合并切片 const mergeFileChunk = async (filePath, filename, size) => { const chunkDir = path.resolve( UPLOAD_DIR, filename.slice(0, filename.lastIndexOf(".")) ); const chunkPaths = await fse.readdir(chunkDir); // 根據(jù)切片下標(biāo)進(jìn)行排序 // 否則直接讀取目錄的獲取的順序可能會(huì)錯(cuò)亂 chunkPaths.sort((a, b) => a.split("-")[1] - b.split("-")[1]); let pipeP = chunkPaths.map((chunkPath, index) => pipeStream( path.resolve(chunkDir, chunkPath), fse.createWriteStream(filePath, { start: index * size, end: (index + 1) * size, }) ) ); await Promise.all(pipeP); fse.rmdirSync(chunkDir); // 合并后刪除保存的切片目錄 }; const pipeStream = (path, writeStream) => { return new Promise((resolve) => { const readStream = fse.createReadStream(path); readStream.on("end", () => { fse.unlinkSync(path); resolve(); }); readStream.pipe(writeStream); }); }; app.post("/merge_chunk", async (req, res) => { // size是每一個(gè)chunk的size const { filename, size } = req.body; const filePath = path.resolve(UPLOAD_DIR, `${filename}`); await mergeFileChunk(filePath, filename, size); res.send({ code: 200, msg: "file merged success", }); });
創(chuàng)建chunk
切片路徑的讀入流,然后將讀入流寫入新的地址,當(dāng)所有切片全部寫入完成,完整的文件就合并成功了。
3./vertify
這個(gè)api比較簡(jiǎn)單,用來(lái)查看當(dāng)前文件是否存在服務(wù)器資源目錄中,存在返回shouldUpload
為false,不存在就需要獲取此文件上傳的切片數(shù)組用于過(guò)濾前端不需要上傳的切片
const createUploadedList = async (fileHash) => fse.existsSync(path.resolve(UPLOAD_DIR, fileHash)) ? await fse.readdir(path.resolve(UPLOAD_DIR, fileHash)) : []; app.post("/vertify", async (req, res) => { const { filename, fileHash } = req.body; const filePath = path.resolve(UPLOAD_DIR, filename); if (fse.existsSync(filePath)) { res.send({ code: 200, data: { shouldUpload: false, }, msg: "文件已上傳成功", }); return; } res.send({ code: 200, data: { shouldUpload: true, uploadList: await createUploadedList(fileHash), }, msg: "請(qǐng)求成功", }); });
到此,大文件上傳基本工作就已經(jīng)完成了。其中最核心的思想就是切片思想,將大的文件進(jìn)行唯一標(biāo)識(shí)+切片上傳,服務(wù)器進(jìn)行先緩存切片在進(jìn)行合并操作。保證了斷點(diǎn)可續(xù)傳,大文件穩(wěn)定上傳。
到此這篇關(guān)于Vue.js+express利用切片實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳的文章就介紹到這了,更多相關(guān)Vue express 大文件斷點(diǎn)續(xù)傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手寫?Vue3?響應(yīng)式系統(tǒng)(核心就一個(gè)數(shù)據(jù)結(jié)構(gòu))
這篇文章主要介紹了手寫?Vue3?響應(yīng)式系統(tǒng)(核心就一個(gè)數(shù)據(jù)結(jié)構(gòu)),響應(yīng)式就是被觀察的數(shù)據(jù)變化的時(shí)候做一系列聯(lián)動(dòng)處理。就像一個(gè)社會(huì)熱點(diǎn)事件,當(dāng)它有消息更新的時(shí)候,各方媒體都會(huì)跟進(jìn)做相關(guān)報(bào)道。這里社會(huì)熱點(diǎn)事件就是被觀察的目標(biāo)2022-06-06修改el-form-item中的label里面的字體邊距或者大小問(wèn)題
這篇文章主要介紹了修改el-form-item中的label里面的字體邊距或者大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10vue如何搭建多頁(yè)面多系統(tǒng)應(yīng)用
這篇文章主要為大家詳細(xì)介紹了vue搭建多頁(yè)面多系統(tǒng)應(yīng)用的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Vue項(xiàng)目通過(guò)vue-i18n實(shí)現(xiàn)國(guó)際化方案(推薦)
這篇文章主要介紹了Vue項(xiàng)目通過(guò)vue-i18n實(shí)現(xiàn)國(guó)際化方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12