vue?大文件分片上傳(斷點續(xù)傳、并發(fā)上傳、秒傳)
對于大文件的處理,無論是用戶端還是服務(wù)端,如果一次性進(jìn)行讀取發(fā)送、接收都是不可取,很容易導(dǎo)致內(nèi)存問題。所以對于大文件上傳,采用切塊分段上傳,從上傳的效率來看,利用多線程并發(fā)上傳能夠達(dá)到最大效率。
本文是基于 springboot + vue 實現(xiàn)的文件上傳,本文主要介紹vue實現(xiàn)文件上傳的步驟及代碼實現(xiàn),服務(wù)端(springboot)的實現(xiàn)步驟及實現(xiàn)請移步本人的另一篇文章:
springboot 大文件上傳、分片上傳、斷點續(xù)傳、秒傳
上傳分步:
本人分析上傳總共分為:
- MD5讀取文件,獲取文件的MD5編碼
- 請求服務(wù)端判斷文件是否上傳,如上傳完成就直接返回文件地址
- 如未上傳,判斷是否是斷點續(xù)傳
- 判斷是并發(fā)上傳還是順序上傳
- 開始分片文件上傳,分片上傳完成后寫入已上傳列表中
- 判斷是否上傳完成
直接上代碼
文件上傳:
import md5 from 'js-md5' //引入MD5加密 import UpApi from '@/api/common.js' import { concurrentExecution } from '@/utils/jnxh' ? /** ?* 文件分片上傳 ?* @params file {File} 文件 ?* @params pieceSize {Number} 分片大小 默認(rèn)3MB ?* @params concurrent {Number} 并發(fā)數(shù)量 默認(rèn)2 ?* @params process {Function} 進(jìn)度回調(diào)函數(shù) ?* @params success {Function} 成功回調(diào)函數(shù) ?* @params error {Function} 失敗回調(diào)函數(shù) ?*/ export const uploadByPieces = ({ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?file, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?pieceSize = 3, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?concurrent = 3, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?success, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?process, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?error ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}) => { ? // 如果文件傳入為空直接 return 返回 ? if (!file || file.length < 1) { ? ? return error('文件不能為空') ? } ? let fileMD5 = '' // 總文件列表 ? const chunkSize = pieceSize * 1024 * 1024 // 1MB一片 ? const chunkCount = Math.ceil(file.size / chunkSize) // 總片數(shù) ? const chunkList = [] // 分片列表 ? let uploaded = [] // 已經(jīng)上傳的 ? let fileType = '' // 文件類型 ? // 獲取md5 ? /*** ? ?* 獲取md5 ? ?**/ ? const readFileMD5 = () => { ? ? // 讀取視頻文件的md5 ? ? fileType = file.name.substring(file.name.lastIndexOf('.') + 1, file.name.length) ? ? console.log('獲取文件的MD5值') ? ? let fileRederInstance = new FileReader() ? ? console.log('file', file) ? ? fileRederInstance.readAsBinaryString(file) ? ? fileRederInstance.addEventListener('load', e => { ? ? ? let fileBolb = e.target.result ? ? ? fileMD5 = md5(fileBolb) ? ? ? var index = file.name.lastIndexOf('.') ? ? ? var tp = file.name.substring(index + 1, file.name.length) ? ? ? let form = new FormData() ? ? ? form.append('filename', file.name) ? ? ? form.append('identifier', fileMD5) ? ? ? form.append('objectType', fileType) ? ? ? form.append('chunkNumber', 1) ? ? ? UpApi.uploadChunk(form).then(res => { ? ? ? ? if (res.skipUpload) { ? ? ? ? ? console.log('文件已被上傳') ? ? ? ? ? success && success(res) ? ? ? ? } else { ? ? ? ? ? // 判斷是否是斷點續(xù)傳 ? ? ? ? ? if (res.uploaded && res.uploaded.length != 0) { ? ? ? ? ? ? uploaded = [].concat(res.uploaded) ? ? ? ? ? } ? ? ? ? ? console.log('已上傳的分片:' + uploaded) ? ? ? ? ? // 判斷是并發(fā)上傳或順序上傳 ? ? ? ? ? if (concurrent == 1 || chunkCount == 1) { ? ? ? ? ? ? console.log('順序上傳') ? ? ? ? ? ? sequentialUplode(0) ? ? ? ? ? } else { ? ? ? ? ? ? console.log('并發(fā)上傳') ? ? ? ? ? ? concurrentUpload() ? ? ? ? ? } ? ? ? ? } ? ? ? }).catch((e) => { ? ? ? ? console.log('文件合并錯誤') ? ? ? ? console.log(e) ? ? ? }) ? ? }) ? } ? /*** ? ?* 獲取每一個分片的詳情 ? ?**/ ? const getChunkInfo = (file, currentChunk, chunkSize) => { ? ? let start = currentChunk * chunkSize ? ? let end = Math.min(file.size, start + chunkSize) ? ? let chunk = file.slice(start, end) ? ? return { ? ? ? start, ? ? ? end, ? ? ? chunk ? ? } ? } ? /*** ? ?* 針對每個文件進(jìn)行chunk處理 ? ?**/ ? const readChunkMD5 = () => { ? ? // 針對單個文件進(jìn)行chunk上傳 ? ? for (var i = 0; i < chunkCount; i++) { ? ? ? const { ? ? ? ? chunk ? ? ? } = getChunkInfo(file, i, chunkSize) ? ? ? ? // 判斷已經(jīng)上傳的分片中是否包含當(dāng)前分片 ? ? ? if (uploaded.indexOf(i + '') == -1) { ? ? ? ? uploadChunk({ ? ? ? ? ? chunk, ? ? ? ? ? currentChunk: i, ? ? ? ? ? chunkCount ? ? ? ? }) ? ? ? } ? ? } ? } ? /*** ? ?* 原始上傳 ? ?**/ ? const uploadChunk = (chunkInfo) => { ? ? var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100) ? ? console.log(sd, '進(jìn)度') ? ? process(sd) ? ? console.log(chunkInfo, '分片大小') ? ? let inde = chunkInfo.currentChunk + 1 ? ? if (uploaded.indexOf(inde + '') > -1) { ? ? ? const { ? ? ? ? chunk ? ? ? } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize) ? ? ? uploadChunk({ ? ? ? ? chunk, ? ? ? ? currentChunk: inde, ? ? ? ? chunkCount ? ? ? }) ? ? } else { ? ? ? var index = file.name.lastIndexOf('.') ? ? ? var tp = file.name.substring(index + 1, file.name.length) ? ? ? // 構(gòu)建上傳文件的formData ? ? ? let fetchForm = new FormData() ? ? ? fetchForm.append('identifier', fileMD5) ? ? ? fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1) ? ? ? fetchForm.append('chunkSize', chunkSize) ? ? ? fetchForm.append('currentChunkSize', chunkInfo.chunk.size) ? ? ? const chunkfile = new File([chunkInfo.chunk], file.name) ? ? ? fetchForm.append('file', chunkfile) ? ? ? // fetchForm.append('file', chunkInfo.chunk) ? ? ? fetchForm.append('filename', file.name) ? ? ? fetchForm.append('relativePath', file.name) ? ? ? fetchForm.append('totalChunks', chunkInfo.chunkCount) ? ? ? fetchForm.append('totalSize', file.size) ? ? ? fetchForm.append('objectType', tp) ? ? ? // 執(zhí)行分片上傳 ? ? ? let config = { ? ? ? ? headers: { ? ? ? ? ? 'Content-Type': 'application/json', ? ? ? ? ? 'Accept': '*/*' ? ? ? ? } ? ? ? } ? ? ? ? UpApi.uploadChunk(fetchForm, config).then(res => { ? ? ? ? ? if (res.code == 200) { ? ? ? ? ? console.log('分片上傳成功') ? ? ? ? ? uploaded.push(chunkInfo.currentChunk + 1) ? ? ? ? ? // 判斷是否全部上傳完 ? ? ? ? ? if (uploaded.length == chunkInfo.chunkCount) { ? ? ? ? ? ? console.log('全部完成') ? ? ? ? ? ? success(res) ? ? ? ? ? ? process(100) ? ? ? ? ? } else { ? ? ? ? ? ? const { ? ? ? ? ? ? ? chunk ? ? ? ? ? ? } = getChunkInfo(file, chunkInfo.currentChunk + 1, chunkSize) ? ? ? ? ? ? uploadChunk({ ? ? ? ? ? ? ? chunk, ? ? ? ? ? ? ? currentChunk: chunkInfo.currentChunk + 1, ? ? ? ? ? ? ? chunkCount ? ? ? ? ? ? }) ? ? ? ? ? } ? ? ? ? ? } else { ? ? ? ? ? console.log(res.msg) ? ? ? ? } ? ? ? ? }).catch((e) => { ? ? ? ? error && error(e) ? ? ? }) ? ? ? // if (chunkInfo.currentChunk < chunkInfo.chunkCount) { ? ? ? // ? setTimeout(() => { ? ? ? // ? ? ? // ? }, 1000) ? ? ? // } ? ? } ? } ? /*** ? ?* 順序上傳 ? ?**/ ? const sequentialUplode = (currentChunk) => { ? ? const { ? ? ? chunk ? ? } = getChunkInfo(file, currentChunk, chunkSize) ? ? let chunkInfo = { ? ? ? chunk, ? ? ? currentChunk, ? ? ? chunkCount ? ? } ? ? var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100) ? ? process(sd) ? ? console.log('當(dāng)前上傳分片:' + currentChunk) ? ? let inde = chunkInfo.currentChunk + 1 ? ? if (uploaded.indexOf(inde + '') > -1) { ? ? ? console.log('分片【' + currentChunk + '】已上傳') ? ? ? sequentialUplode(currentChunk + 1) ? ? } else { ? ? ? let uploadData = createUploadData(chunkInfo) ? ? ? let config = { ? ? ? ? headers: { ? ? ? ? ? 'Content-Type': 'application/json', ? ? ? ? ? 'Accept': '*/*' ? ? ? ? } ? ? ? } ? ? ? // 執(zhí)行分片上傳 ? ? ? UpApi.uploadChunk(uploadData, config).then(res => { ? ? ? ? if (res.code == 200) { ? ? ? ? ? console.log('分片【' + currentChunk + '】上傳成功') ? ? ? ? ? uploaded.push(chunkInfo.currentChunk + 1) ? ? ? ? ? // 判斷是否全部上傳完 ? ? ? ? ? if (uploaded.length == chunkInfo.chunkCount) { ? ? ? ? ? ? console.log('全部完成') ? ? ? ? ? ? success(res) ? ? ? ? ? ? process(100) ? ? ? ? ? } else { ? ? ? ? ? ? sequentialUplode(currentChunk + 1) ? ? ? ? ? } ? ? ? ? ? } else { ? ? ? ? ? console.log(res.msg) ? ? ? ? } ? ? ? ? }).catch((e) => { ? ? ? ? error && error(e) ? ? ? }) ? ? } ? } ? /*** ? ?* 并發(fā)上傳 ? ?**/ ? const concurrentUpload = () => { ? ? for (var i = 0; i < chunkCount; i++) { ? ? ? chunkList.push(Number(i)) ? ? } ? ? console.log('需要上傳的分片列表:' + chunkList) ? ? concurrentExecution(chunkList, concurrent, (curItem) => { ? ? ? return new Promise((resolve, reject) => { ? ? ? ? const { ? ? ? ? ? chunk ? ? ? ? } = getChunkInfo(file, curItem, chunkSize) ? ? ? ? let chunkInfo = { ? ? ? ? ? chunk, ? ? ? ? ? currentChunk: curItem, ? ? ? ? ? chunkCount ? ? ? ? } ? ? ? ? var sd = parseInt((chunkInfo.currentChunk / chunkInfo.chunkCount) * 100) ? ? ? ? process(sd) ? ? ? ? console.log('當(dāng)前上傳分片:' + curItem) ? ? ? ? let inde = chunkInfo.currentChunk + 1 ? ? ? ? if (uploaded.indexOf(inde + '') == -1) { ? ? ? ? ? // 構(gòu)建上傳文件的formData ? ? ? ? ? let uploadData = createUploadData(chunkInfo) ? ? ? ? ? // 請求頭 ? ? ? ? ? let config = { ? ? ? ? ? ? headers: { ? ? ? ? ? ? ? 'Content-Type': 'application/json', ? ? ? ? ? ? ? 'Accept': '*/*' ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? ? ? UpApi.uploadChunk(uploadData, config).then(res => { ? ? ? ? ? ? if (res.code == 200) { ? ? ? ? ? ? ? uploaded.push(chunkInfo.currentChunk + 1) ? ? ? ? ? ? ? console.log('已經(jīng)上傳完成的分片:' + uploaded) ? ? ? ? ? ? ? // 判斷是否全部上傳完 ? ? ? ? ? ? ? if (uploaded.length == chunkInfo.chunkCount) { ? ? ? ? ? ? ? ? success(res) ? ? ? ? ? ? ? ? process(100) ? ? ? ? ? ? ? } ? ? ? ? ? ? ? resolve() ? ? ? ? ? ? } else { ? ? ? ? ? ? ? reject(res) ? ? ? ? ? ? ? console.log(res.msg) ? ? ? ? ? ? } ? ? ? ? ? ? }).catch((e) => { ? ? ? ? ? ? reject(res) ? ? ? ? ? ? error && error(e) ? ? ? ? ? }) ? ? ? ? } else { ? ? ? ? ? console.log('分片【' + chunkInfo.currentChunk + '】已上傳') ? ? ? ? ? resolve() ? ? ? ? } ? ? ? }) ? ? }).then(res => { ? ? ? console.log('finish', res) ? ? }) ? } ? /*** ? ?* 創(chuàng)建文件上傳參數(shù) ? ?**/ ? const createUploadData = (chunkInfo) => { ? ? let fetchForm = new FormData() ? ? fetchForm.append('identifier', fileMD5) ? ? fetchForm.append('chunkNumber', chunkInfo.currentChunk + 1) ? ? fetchForm.append('chunkSize', chunkSize) ? ? fetchForm.append('currentChunkSize', chunkInfo.chunk.size) ? ? const chunkfile = new File([chunkInfo.chunk], file.name) ? ? fetchForm.append('file', chunkfile) ? ? // fetchForm.append('file', chunkInfo.chunk) ? ? fetchForm.append('filename', file.name) ? ? fetchForm.append('relativePath', file.name) ? ? fetchForm.append('totalChunks', chunkInfo.chunkCount) ? ? fetchForm.append('totalSize', file.size) ? ? fetchForm.append('objectType', fileType) ? ? return fetchForm ? } ? readFileMD5() // 開始執(zhí)行代碼 }
并發(fā)控制:
/** * 并發(fā)執(zhí)行 * @params list {Array} - 要迭代的數(shù)組 * @params limit {Number} - 并發(fā)數(shù)量控制數(shù),最好小于3 * @params asyncHandle {Function} - 對`list`的每一個項的處理函數(shù),參數(shù)為當(dāng)前處理項,必須 return 一個Promise來確定是否繼續(xù)進(jìn)行迭代 * @return {Promise} - 返回一個 Promise 值來確認(rèn)所有數(shù)據(jù)是否迭代完成 */ export function concurrentExecution(list, limit, asyncHandle) { // 遞歸執(zhí)行 let recursion = (arr) => { // 執(zhí)行方法 arr.shift() 取出并移除第一個數(shù)據(jù) return asyncHandle(arr.shift()).then(() => { // 數(shù)組還未迭代完,遞歸繼續(xù)進(jìn)行迭代 if (arr.length !== 0) { return recursion(arr) } else { return 'finish' } }) } // 創(chuàng)建新的并發(fā)數(shù)組 let listCopy = [].concat(list) // 正在進(jìn)行的所有并發(fā)異步操作 let asyncList = [] limit = limit > listCopy.length ? listCopy.length : limit console.log(limit) while (limit--) { asyncList.push(recursion(listCopy)) } // 所有并發(fā)異步操作都完成后,本次并發(fā)控制迭代完成 return Promise.all(asyncList) }
到此這篇關(guān)于vue 大文件分片上傳(斷點續(xù)傳、并發(fā)上傳、秒傳)的文章就介紹到這了,更多相關(guān)vue 大文件分片上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue動態(tài)修改網(wǎng)頁標(biāo)題的方法及遇到問題
Vue下有很多的方式去修改網(wǎng)頁標(biāo)題,這里總結(jié)下解決此問題的幾種方案:,需要的朋友可以參考下2019-06-06詳解關(guān)于element el-button使用$attrs的一個注意要點
這篇文章主要介紹了詳解關(guān)于element el-button使用$attrs的一個注意要點,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11vue 查看dist文件里的結(jié)構(gòu)(多種方式)
本文通過三種方式給大家介紹了vue 查看dist文件里的結(jié)構(gòu),非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01