前端分片上傳附件
- 分片上傳定義:
所謂的分片上傳抱完,就是將所要上傳的文件,按照一定的大小刃泡,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為 Part)來進(jìn)行分別上傳巧娱,上傳完之后再由服務(wù)端對(duì)所有上傳的文件進(jìn)行匯總整合成原始的文件。分片上傳不僅可以避免因網(wǎng)絡(luò)環(huán)境不好導(dǎo)致的一直需要從文件起始位置還是上傳的問題烘贴,還能使用多線程對(duì)不同分塊數(shù)據(jù)進(jìn)行并發(fā)發(fā)送禁添,提高發(fā)送效率,降低發(fā)送時(shí)間桨踪。
- 主要適用于以下幾種場(chǎng)景:
- 網(wǎng)絡(luò)環(huán)境不好:當(dāng)出現(xiàn)上傳失敗的時(shí)候老翘,可以對(duì)失敗的 Part 進(jìn)行獨(dú)立的重試,而不需要重新上傳其他的 Part。
- 斷點(diǎn)續(xù)傳:中途暫停之后酪捡,可以從上次上傳完成的 Part 的位置繼續(xù)上傳叁征。
- 加速上傳:要上傳到服務(wù)器的本地文件很大的時(shí)候,可以并行上傳多個(gè) Part 以加快上傳逛薇。
- 流式上傳:可以在需要上傳的文件大小還不確定的情況下開始上傳捺疼。這種場(chǎng)景在視頻監(jiān)控等行業(yè)應(yīng)用中比較常見。
- 文件較大:一般文件比較大時(shí)永罚,默認(rèn)情況下一般都會(huì)采用分片上傳啤呼。
- 分片上傳的流程大致如下:
- 將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊呢袱;
- 初始化一個(gè)分片上傳任務(wù)官扣,返回本次分片上傳唯一標(biāo)識(shí);
- 按照一定的策略(串行或并行)發(fā)送各個(gè)分片數(shù)據(jù)塊羞福;
- 發(fā)送完成后惕蹄,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整治专,則進(jìn)行數(shù)據(jù)塊合成得到原始文件
- 分片上傳規(guī)則如下:
- 定義分片規(guī)則大小: chunkSize: 5 * 1024 * 1024 // 分多大一片 5M;
- 上傳并發(fā)數(shù): threads: 3 // 上傳并發(fā)數(shù), 允許同時(shí)最大上傳進(jìn)程數(shù);
- 定義分片上傳對(duì)象: 定義分片上傳對(duì)象基礎(chǔ)屬性包含附件文件名卖陵、原始文件大小、原始文件 MD5 值张峰、分片總數(shù)泪蔫、每個(gè)分片大小、當(dāng)前分片大小喘批、當(dāng)前分片序號(hào)等. 定義基礎(chǔ)屬于便于后續(xù)對(duì)文件合理分割撩荣、分片的合并等業(yè)務(wù)拓展,當(dāng)然根據(jù)業(yè)務(wù)場(chǎng)景可以定義拓展屬性;
5.前端分片上傳使用方法:
下載 import Webuploader from 'webuploader';
fileSliceUpload.vue文件:
<template>
<!-- 模型 - 分片上傳 -->
<div id="slice_uploader" class="slice-uploader">
<div>上傳附件</div>
</div>
</template>
<script>
import WebUploader from 'webuploader'
import { sliceDetail } from '@/api/manager.js'
export default {
name: 'FileSliceUpload',
props: {
// 分片上傳url
url: {
type: String,
default: '',
},
// 文件數(shù)量限制
fileNumLimit: {
type: Number,
default: 1,
},
isLoading: {
type: Boolean,
default: false,
},
fileSizeLimit: {
type: Number,
default: 2 * 1024 * 1024 * 1024, // 2G
},
isReupload: {
// 默認(rèn)初始上傳饶深,為true時(shí)是再次上傳
type: Boolean,
default: false,
},
},
data() {
return {
uploader: null,
fileMd5: '',
fileName: '',
entireUpload: false,
uploadMsg: {
totalChunks: 0,
identifier: [],
},
fileId: '',
response: null,
chunkList: [],
chunkTotal: 0,
successNum: 0,
currentFile: null,
}
},
mounted() {
this.$nextTick(() => {
this.initUploader()
})
},
methods: {
initUploader() {
let that = this
let acceptRules = [
{
mimeTypes: '.zip,.jar',
},
]
const _this = this
//監(jiān)聽分塊上傳過程中的三個(gè)時(shí)間點(diǎn)
WebUploader.Uploader.unRegister('contractUpload')
WebUploader.Uploader.register(
{
name: 'contractUpload',
'before-send-file': 'beforeSendFile', //整個(gè)文件上傳前
'before-send': 'beforeSend',
'after-send-file': 'afterSendFile',
},
{
// 所有分塊進(jìn)行上傳之前調(diào)用此函數(shù)
beforeSendFile(file) {
var deferred = WebUploader.Deferred()
_this.fileMd5 = ''
_this.uploader.md5File(file).then((val) => {
_this.fileMd5 = val
_this.fileId = ''
_this.chunkList = []
sliceDetail({
md5: _this.fileMd5,
})
.then((res) => {
if (res.data.chunkList) {
_this.chunkList = res.data.chunkList
_this.fileId = res.data.fileId
}
deferred.resolve()
})
.catch((err) => {
this.$message.error(err.errmsg)
console.log(err, 'err')
})
})
// }
return deferred.promise()
},
// 如果有分塊上傳餐曹,則上傳之前調(diào)用此函數(shù)
beforeSend(block) {
_this.chunkTotal = block.chunks
var deferred = WebUploader.Deferred()
// _this.chunkList = _this.chunkList.slice(0, 5)
// 跳過如果存在則跳過
if (block.chunks === _this.chunkList.length) {
const file = {}
const response = {
data: {
fileId: _this.fileId,
},
}
that.$emit('uploadSuccess', file, response)
that.$emit('uploadProgress', block.file.name, 1)
deferred.reject() // 跳過該分片
_this.uploader.removeFile(block.file)
_this.uploader.reset()
return deferred.promise()
}
// 如果里面有上傳過的分片
else if (
_this.chunkList.includes(block.chunk) &&
block.chunks !== _this.chunkList.length
) {
// this.owner.skipFile(block.file)
_this.successNum++
deferred.reject() // 跳過該分片
} else {
deferred.resolve() // 繼續(xù)上傳該分片
}
return deferred.promise()
}
}
)
// 1. 文件上傳方法的參數(shù)說明
this.uploader = WebUploader.create({
auto: true,
server: this.url,
dnd: '#slice_uploader',
pick: '#slice_uploader',
fileNumLimit: this.fileNumLimit, // 驗(yàn)證文件總數(shù)量, 超出則不允許加入隊(duì)列
fileSingleSizeLimit: this.fileSizeLimit, // 驗(yàn)證文件總大小是否超出限制, 超出則不允許加入隊(duì)列
chunked: true, // 是否要分片處理大文件上傳
chunkRetry: 3, // 如果某個(gè)分片由于網(wǎng)絡(luò)問題出錯(cuò),允許自動(dòng)重傳多少次
chunkSize: 5 * 1024 * 1024, // 分多大一片 5M
threads: 3, // 上傳并發(fā)數(shù)粥喜。允許同時(shí)最大上傳進(jìn)程數(shù)
multiple: false,
resize: false,
// duplicate: true,
disableGlobalDnd: true,
accept: acceptRules,
formData: {
sliceSize: 5242880,
},
timeout: 60000, //超時(shí)時(shí)間
})
// 文件入隊(duì)列之前
this.uploader.on('beforeFileQueued', (file) => {
that.uploadMsg.totalChunks = 0
that.$emit('setLoadingStatus', true)
that.$emit('uploadProgress', file.name, 0)
const zipTypes = [
'application/x-zip',
'application/zip',
'application/x-zip-compressed',
]
// 如果不是zip文件凸主,不再進(jìn)行分片上傳
if (
file.ext !== 'zip' &&
!zipTypes.includes(file.type) &&
file.ext !== 'jar'
) {
this.entireUpload = true
this.uploader.options.chunked = false
} else {
this.entireUpload = false
this.uploader.options.chunked = true
}
})
// 文件入隊(duì)列(.on事件監(jiān)聽不到)
this.uploader.on('fileQueued', (file) => {
this.fileName = file.name
})
// 監(jiān)聽分片上傳 可以添加參數(shù)
this.uploader.on('uploadBeforeSend', (block, data, headers) => {
data.md5 = this.fileMd5
})
// 處理分片接收
this.uploader.on('uploadAccept', (file, res) => {
if (res.errno !== 0) {
that.$emit('uploadError', file)
this.$message.error(res.errmsg)
this.uploader.stop()
this.uploader.reset()
this.successNum = 0
} else {
this.successNum++
}
that.$emit(
'uploadProgress',
file.file.name,
this.successNum / this.chunkTotal
)
})
// 當(dāng)文件上傳成功時(shí)觸發(fā)
this.uploader.on('uploadSuccess', (file, response) => {
that.$emit('uploadSuccess', file, response)
this.successNum = 0
this.uploader.removeFile(file)
})
// 上傳失敗方法
this.uploader.on('uploadError', (file, reason) => {
that.$emit('uploadError', file, reason)
})
},
},
beforeDestroy() {
this.uploader.reset()
},
}
</script>
使用
<file-slice-upload
url="/xxx/xxx/api/sliceList"
:isLoading="isLoading"
@setLoadingStatus="setLoadingStatus"
@mergeCompleted="uploadSuccess"
@uploadSuccess="uploadSuccess"
@uploadProgress="uploadProgress"
@uploadError="uploadError"
>
setLoadingStatus(val) {
this.isLoading = val
},
uploadSuccess(val, data) {
if (data && data.data && data.data.fileId) {
this.sliceFileId = data.data.fileId || ''
}
this.uploadStatus = '上傳成功'
this.isLoading = false
//上傳成功
},
uploadProgress(filename, percent) {
// this.isLoading = false
this.uploadData = { filename, percent } //進(jìn)度條
},
uploadError(file) {
this.uploadStatus = '上傳失敗'
this.isLoading = false //上傳失敗
},
- 總結(jié):
所謂的分片上傳,就是將所要上傳的文件额湘,按照一定的大小,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為 Part)來進(jìn)行分別上傳旁舰。