vue實現(xiàn)大文件切片上傳的示例詳解
大文件為什么要切片上傳
前端上傳文件很大時,會出現(xiàn)各種問題,比如連接超時了,網(wǎng)斷了,都會導(dǎo)致上傳失敗
服務(wù)端限制了單次上傳文件的大小
項目實際場景
客戶端需要上傳一個算法包文件到服務(wù)器,這個算法包實測 3.7G
nginx配置文件 上傳文件大小最大值為100M
,
切片上傳原理
通過file.slice
將大文件chunks
切成許多個大小相等的chunk
將每個chunk
上傳到服務(wù)器
服務(wù)端接收到許多個chunk
后,合并為chunks
第一版
先對文件按指定大小進(jìn)行切片
/** * file: 需要切片的文件 * chunkSize: 每片文件大小,1024*1024=1M */ chunkSlice(file, chunkSize) { const chunks = [], size = file.size, total = Math.ceil(size / chunkSize) for (let i = 0; i < size; i += chunkSize) { chunks.push({ total, blob: file.slice(i, i + chunkSize), }) } return chunks }
處理切片后的文件,后端想要我傳給他一個json對象,所以使用readAsDataURL
讀取文件
這里使用了一個插件spark-md5
來生成每個切片的MD5
async handleFile(chunks) { const res = [] for (const item of chunks) { const { bytes, md5 } = await this.addMark(item.blob) item.blob = bytes item.md5 = md5 res.push(md5) } return res }, // 使用FileReader讀取每一片數(shù)據(jù),并生成MD5編碼 async addMark(chunk) { return new Promise((resolve, reject) => { const reader = new FileReader() const spark = new SparkMD5() reader.readAsDataURL(chunk) reader.onload = function (e) { const bytes = e.target.result spark.append(bytes) const md5 = spark.end() resolve({ bytes, md5 }) } }) },
組裝數(shù)據(jù),包括每一片的排列順序index
,總共切了多少片total
,文件IDfileID
,每一片的md5編碼md5
,每一片數(shù)據(jù)fileData
mergeData(chunks) { const fileId = this.getUUID() const data = [] for (let i = 0; i < chunks.length; i++) { const obj = { fileId, fileData: chunks[i].blob,//每片切片的數(shù)據(jù) fileIndex: i + 1,//每片數(shù)據(jù)索引 fileTotal: chunks[i].total + '', md5: chunks[i].md5, } data.push(obj) } return { data, fileId } },
上傳文件,這里使用并發(fā)上傳文件,提升文件上傳速度
const chunks = chunkSlice(file,1024*1024) this.handleFile(chunks) const data = this.mergeData(chunks) for(let i = 0; i < data.length; i++){ this.uplload(data[i]) }
第一版遇到的問題
文件太大,切片太小,上傳接口的timeout
太短,并發(fā)請求時,全都在pendding
,導(dǎo)致請求出錯
第一版問題解決
對上傳文件接口的timeout
修改,調(diào)整時長,大一點
限制每次并發(fā)的數(shù)量,我用的是500個每次
第二版,切片 + web worker
為什么要使用web worker
在生成文件MD5
編碼時,需要讀文件,是一個I/O
操作,會阻塞頁面,文件太大,導(dǎo)致頁面卡死
將耗時操作轉(zhuǎn)移到worker
線程,主頁面就不會卡住
vue2,使用worker
yarn add worker-loader
vue.config.js 配置
// vue.config.js chainWebpack(config) { config.module.rule('worker') .test(/\.worker\.js$/) .use('worker-loader') .loader('worker-loader') // .options({ inline: 'fallback' })// 這個配置是個坑,不要加 },
新建file.worker.js
// file.worker.js import SparkMD5 from 'spark-md5' const chunkSlice = (file, chunkSize) => { const chunks = [], size = file.size, total = Math.ceil(size / chunkSize) for (let i = 0; i < size; i += chunkSize) { chunks.push({ total, blob: file.slice(i, i + chunkSize), }) } return chunks } const handleFile = async (chunks) => { const res = [] for (const item of chunks) { const { bytes, md5 } = await addMark(item.blob) item.blob = bytes item.md5 = md5 res.push(md5) } return res } const addMark = (chunk) => { return new Promise((resolve, reject) => { const reader = new FileReader() const spark = new SparkMD5() reader.readAsDataURL(chunk) reader.onload = function (e) { const bytes = e.target.result spark.append(bytes) const md5 = spark.end() resolve({ bytes, md5 }) } }) } const mergeData = (chunks, fileName, options) => { const fileId = getUUID() // 這里更好的方式是讀整個文件的 MD5 const data = [] for (let i = 0; i < chunks.length; i++) { const obj = { ...options, suffix: '.tar.gz', fileId, fileName, fileData: chunks[i].blob, fileIndex: i + 1 + '', fileTotal: chunks[i].total + '', md5: chunks[i].md5, } data.push(obj) } return { data, fileId } } const getUUID = () => { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) ) } const dataSlice = (data, step, fileId) => { const total = Math.ceil(data.length / step) let index = 1 for (let i = 0; i < data.length; i += step) { const params = { type: 'workerFile', index, total, fileId, data: data.slice(i, i + step), } self.postMessage(params) index++ } } self.addEventListener('error', (event) => { console.log('worker error', event) }) self.addEventListener('message', async (event) => { // 確保接受的是我想要的消息 if (!event.data.type) return if (event.data.type != 'file') return console.log('worker success', event) const { file, chunkSize } = event.data const chunks = chunkSlice(file, chunkSize) const allMD5 = await handleFile(chunks) console.log(allMD5) // 此處 allMD5 可用來做后續(xù)的斷點續(xù)傳 const { data, fileId } = mergeData(chunks, file.name) // 這里對處理好的數(shù)據(jù)進(jìn)行切片,分片傳遞給主線程,是由于 Web Worker 試圖將大量數(shù)據(jù)復(fù)制到主線程中,會導(dǎo)致內(nèi)存溢出。 dataSlice(data, 100, fileId) })
這個報錯一般是在使用 JavaScript Web Worker 時出現(xiàn)的,通常是由于 Web Worker 試圖將大量數(shù)據(jù)復(fù)制到主線程中,導(dǎo)致內(nèi)存溢出所引起的。
主進(jìn)程使用
// xxx.vue文件 import Worker from '@/utils/worker/file.worker.js' const worker = new Worker() worker.postMessage({ type: 'file', file: this.curFile, chunkSize: 1024 * 1024 }) worker.onerror = (error) => { console.log('main error', error) worker.terminate() } const finalData = [] worker.onmessage = async (event) => { console.log('main success', event) if (event.data.type != 'workerFile') return const fileId = mergeWorkerData(finalData, event.data) if (fileId) { worker.terminate() const status = await stepLoad(finalData, 500) if (!status) { this.$message.error('文件上傳失敗') } else { this.$message.success('文件上傳成功') } } } mergeWorkerData = (res, params) => { res.push(...params.data) return params.index == params.total ? params.fileId : false } const stepLoad = async (data, step) => { const res = [] for (let i = 0; i < data.length; i += step) { res.push(data.slice(i, i + step)) } for (const item of res) { const chunkRes = await Promise.all(item.map((v) => this.$api.upload(v))) if (chunkRes.some((v) => v.httpCode != 0)) { return false } const isEnd = chunkRes.filter((v) => v.finish) if (isEnd.length) { return true } } }
總結(jié)
worker
引入腳本或三方庫可以使用importScript()
,但是我沒弄成功,一使用importScript()
就會報錯,Renference: importScript() xxxxxxxxxxxx
,如果你們弄出來了,或者知道為什么,可以在下面留言
以上就是vue實現(xiàn)大文件切片上傳的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于vue大文件切片上傳的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Vue3實現(xiàn)交互式雷達(dá)圖的代碼實現(xiàn)
雷達(dá)圖是一種可視化數(shù)據(jù)的方式,用于比較多個類別中不同指標(biāo)的相對值,它適用于需要展示多個指標(biāo)之間的關(guān)系和差異的場景,本文給大家介紹了如何用Vue3輕松創(chuàng)建交互式雷達(dá)圖,需要的朋友可以參考下2024-06-06Vue3中SetUp函數(shù)的參數(shù)props、context詳解
我們知道setup函數(shù)是組合API的核心入口函數(shù),下面這篇文章主要給大家介紹了關(guān)于Vue3中SetUp函數(shù)的參數(shù)props、context的相關(guān)資料,需要的朋友可以參考下2021-07-07Vue前端實現(xiàn)導(dǎo)出頁面為word的兩種方法
這篇文章主要介紹了Vue前端實現(xiàn)導(dǎo)出頁面為word的兩種方法,文中通過代碼示例和圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-12-12vue的Virtual Dom實現(xiàn)snabbdom解密
這篇文章主要介紹了vue的Virtual Dom實現(xiàn)- snabbdom解密,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05vue如何將導(dǎo)航欄、頂部欄設(shè)置為公共頁面
這篇文章主要介紹了vue如何將導(dǎo)航欄、頂部欄設(shè)置為公共頁面問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01