Vue 大文件分片上传是前端文件上传中常见的解决方案之一,用于解决大文件上传时可能遇到的性能和稳定性问题。常见的性能问题包括上传时间过长、上传失败等,而稳定性问题则是在上传过程中可能因为网络原因导致上传失败,需要支持断点续传。
-
什么是文件分片上传?
文件分片上传是指将大文件分成多个较小的文件片段进行上传,上传完成后再将这些片段组合成完整的文件。这样做的好处是文件较小、上传速度较快,并且上传过程中在网络环境不佳时也具有更好的稳定性,一旦上传失败可以从上次失败的位置继续上传,实现断点续传。 -
Vue 大文件分片上传步骤
Vue 大文件分片上传的步骤分为以下几个:
2.1 文件切片
将文件分成大小一致的片段,并生成唯一标识符和上传地址等信息,示例代码如下:
function createChunks (file, chunk_size) {
const chunks = []
let start = 0
let end = 0
let index = 0
while (start < file.size) {
end = start + chunk_size
chunks.push({
index: index,
total: Math.ceil(file.size / chunk_size),
blob: file.slice(start, end),
name: file.name,
uid: `${file.name}_${index}_${file.size}_${Date.now()}`
})
index++
start = end
}
return chunks
}
2.2 上传文件片段
将文件片段通过 AJAX 异步上传到服务器,并在上传完成后触发上传成功回调函数,示例代码如下:
async function uploadChunk ({ url, data, progressCallback }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.open('post', url, true)
xhr.setRequestHeader('Content-Type', 'application/octet-stream')
xhr.upload.onprogress = function (event) {
if (event.total > 0) {
event.percent = (event.loaded / event.total) * 100
}
progressCallback && progressCallback(event)
}
xhr.onload = function () {
if (xhr.status === 200) {
resolve(xhr.responseText)
} else {
reject(new Error('上传失败'))
}
}
xhr.onerror = function (err) {
reject(new Error('上传失败'))
}
xhr.send(data)
})
}
2.3 流程控制
使用 Promise.all 控制上传过程,等待所有文件片段上传完成后再触发上传完成回调函数,示例代码如下:
Promise.all(chunks.map((chunk) => {
return uploadChunk({
url: upload_url,
data: chunk.blob,
progressCallback: updateProgress
})
})).then((res) => {
console.log('文件上传成功')
// 完整的文件上传
checkFile()
}).catch((err) => {
console.error(err.message)
})
2.4 校验文件完整性
将所有文件片段的 md5 值上传到服务器,服务器根据 md5 值验证文件完整性,如果文件完整,则将文件合并;如果文件不完整,则删除已上传的文件片段,示例代码如下:
async function checkFile () {
const md5Promises = chunks.map((chunk) => {
return createFileMd5(chunk.blob)
})
const md5Array = await Promise.all(md5Promises)
const md5 = md5Array.join('')
console.log('文件MD5值为', md5)
// 校验文件是否完整
const res = await checkFileApi({
md5: md5,
size: file.size,
name: file.name
})
if (res.code === 0 && res.data && res.data.uploaded) {
console.log('秒传文件')
// 秒传
} else {
console.log('开始合并文件')
// 合并文件
mergeChunks(md5)
}
}
2.5 合并文件
将已上传的文件片段按照顺序合并成完整的文件,并触发文件上传完成回调函数,示例代码如下:
async function mergeChunks (md5) {
const res = await mergeFileApi({
md5: md5,
name: file.name,
chunks
})
if (res.code === 0) {
console.log('文件上传成功')
// 文件上传成功
} else {
console.error(res.message)
}
}
- 示例说明
下面给出两个示例说明:
3.1 Vue 大文件分片上传组件
<template>
<div>
<input type="file" @change="handleFileChange" />
<el-button type="primary" @click="handleUpload">上传</el-button>
<div class="progress">
<el-progress :percentage="progress" :stroke-width="3"/>
</div>
</div>
</template>
<script>
export default {
data () {
return {
file: null,
chunks: [],
upload_url: 'https://example.com/api/upload',
progress: 0
}
},
methods: {
handleFileChange (e) {
this.file = e.target.files[0]
this.chunks = createChunks(this.file, 2 * 1024 * 1024) // 按2MB切片
},
handleUpload () {
Promise.all(this.chunks.map((chunk) => {
return uploadChunk({
url: this.upload_url,
data: chunk.blob,
progressCallback: this.updateProgress
})
})).then((res) => {
console.log('文件上传成功')
checkFile()
}).catch((err) => {
console.error(err.message)
})
},
async checkFile () {
const md5Promises = this.chunks.map((chunk) => {
return createFileMd5(chunk.blob)
})
const md5Array = await Promise.all(md5Promises)
const md5 = md5Array.join('')
console.log('文件MD5值为', md5)
const res = await checkFileApi({
md5: md5,
size: this.file.size,
name: this.file.name
})
if (res.code === 0 && res.data && res.data.uploaded) {
console.log('秒传文件')
// 秒传
} else {
console.log('开始合并文件')
// 合并文件
mergeChunks(md5)
}
},
async mergeChunks (md5) {
const res = await mergeFileApi({
md5: md5,
name: this.file.name,
chunks: this.chunks
})
if (res.code === 0) {
console.log('文件上传成功')
// 文件上传成功
} else {
console.error(res.message)
}
},
updateProgress (progressEvent) {
this.progress = Math.ceil(progressEvent.percent)
}
}
}
</script>
3.2 上传进度条组件
<template>
<div class="progress-wrapper">
<div class="progress-bar" :style="{ width: percent + '%' }"></div>
</div>
</template>
<script>
export default {
props: {
url: {
type: String,
required: true
},
data: {
type: Object,
required: true
},
fileName: {
type: String,
required: true
},
chunk: {
type: Blob,
required: true
}
},
data () {
return {
percent: 0
}
},
methods: {
uploadChunk () {
const xhr = new XMLHttpRequest()
xhr.open('post', this.url, true)
xhr.setRequestHeader('Content-Type', 'application/octet-stream')
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
this.percent = Math.ceil((e.loaded / e.total) * 100)
}
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText)
} else {
console.log('上传失败')
}
}
}
const formData = new FormData()
formData.append('file', this.chunk, `${this.fileName}_${this.data.index}`)
formData.append('index', String(this.data.index))
formData.append('total', String(this.data.total))
formData.append('name', this.fileName)
formData.append('uid', this.data.uid)
xhr.send(formData)
}
},
mounted () {
this.uploadChunk()
}
}
</script>
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue 大文件分片上传(断点续传、并发上传、秒传) - Python技术站