前端分片上传附件
- 分片上传定义:
所谓的分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。分片上传不仅可以避免因网络环境不好导致的一直需要从文件起始位置还是上传的问题,还能使用多线程对不同分块数据进行并发发送,提高发送效率,降低发送时间。
- 主要适用于以下几种场景:
- 网络环境不好:当出现上传失败的时候,可以对失败的 Part 进行独立的重试,而不需要重新上传其他的 Part。
- 断点续传:中途暂停之后,可以从上次上传完成的 Part 的位置继续上传。
- 加速上传:要上传到服务器的本地文件很大的时候,可以并行上传多个 Part 以加快上传。
- 流式上传:可以在需要上传的文件大小还不确定的情况下开始上传。这种场景在视频监控等行业应用中比较常见。
- 文件较大:一般文件比较大时,默认情况下一般都会采用分片上传。
- 分片上传的流程大致如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
- 分片上传规则如下:
- 定义分片规则大小: chunkSize: 5 * 1024 * 1024 // 分多大一片 5M;
- 上传并发数: threads: 3 // 上传并发数, 允许同时最大上传进程数;
- 定义分片上传对象: 定义分片上传对象基础属性包含附件文件名、原始文件大小、原始文件 MD5 值、分片总数、每个分片大小、当前分片大小、当前分片序号等. 定义基础属于便于后续对文件合理分割、分片的合并等业务拓展,当然根据业务场景可以定义拓展属性;
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: '',
},
// 文件数量限制
fileNumLimit: {
type: Number,
default: 1,
},
isLoading: {
type: Boolean,
default: false,
},
fileSizeLimit: {
type: Number,
default: 2 * 1024 * 1024 * 1024, // 2G
},
isReupload: {
// 默认初始上传,为true时是再次上传
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
//监听分块上传过程中的三个时间点
WebUploader.Uploader.unRegister('contractUpload')
WebUploader.Uploader.register(
{
name: 'contractUpload',
'before-send-file': 'beforeSendFile', //整个文件上传前
'before-send': 'beforeSend',
'after-send-file': 'afterSendFile',
},
{
// 所有分块进行上传之前调用此函数
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()
},
// 如果有分块上传,则上传之前调用此函数
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() // 继续上传该分片
}
return deferred.promise()
}
}
)
// 1. 文件上传方法的参数说明
this.uploader = WebUploader.create({
auto: true,
server: this.url,
dnd: '#slice_uploader',
pick: '#slice_uploader',
fileNumLimit: this.fileNumLimit, // 验证文件总数量, 超出则不允许加入队列
fileSingleSizeLimit: this.fileSizeLimit, // 验证文件总大小是否超出限制, 超出则不允许加入队列
chunked: true, // 是否要分片处理大文件上传
chunkRetry: 3, // 如果某个分片由于网络问题出错,允许自动重传多少次
chunkSize: 5 * 1024 * 1024, // 分多大一片 5M
threads: 3, // 上传并发数。允许同时最大上传进程数
multiple: false,
resize: false,
// duplicate: true,
disableGlobalDnd: true,
accept: acceptRules,
formData: {
sliceSize: 5242880,
},
timeout: 60000, //超时时间
})
// 文件入队列之前
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文件,不再进行分片上传
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
}
})
// 文件入队列(.on事件监听不到)
this.uploader.on('fileQueued', (file) => {
this.fileName = file.name
})
// 监听分片上传 可以添加参数
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
)
})
// 当文件上传成功时触发
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 } //进度条
},
uploadError(file) {
this.uploadStatus = '上传失败'
this.isLoading = false //上传失败
},
- 总结:
所谓的分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为 Part)来进行分别上传。