【上传文件过大进行的切片式上传】
<template>
<div>
<el-button type="primary" @click="clickUpload" :disabled="disabled">选择镜像</el-button>
<input ref="up" type="file" style="display: none">
<div>
<span style="margin-right: 40px">{{ filename }}</span>
<span v-if="status===1"><i class="el-icon-loading"></i>正在上传中,请勿关闭当前窗口</span>
<span v-if="status===2" style="color: #67C23A">文件上传成功</span>
<span v-if="status===3" style="color: #F56C6C">文件已上传过</span>
<span v-if="status===4" style="color: #F56C6C">上传失败</span>
</div>
</div>
</template>
<script>
import axios from 'axios'
import SparkMD5 from 'spark-md5' //需要手动安装
export default {
name: 'Uploader',
//父组件传过来的值
props: {
disabled: {
type: Boolean,
default: true,
},
params: {
type: Object,
default: () => {
return { registry: '',mark: '', useId: '' }
},
},
},
data() {
return {
filename: '',
status: 0,
}
},
mounted() {
let chunkSize = 2 * 1024 * 1024
this.$refs.up.addEventListener('change', async(e) => {
let fileList = e.target.files
let file = fileList[0]
let chunkArr = this.chunk(file, chunkSize)
let fileHash = await this.hash(chunkArr)
let filename = file.name
this.filename = filename
//false:没上传 true:上传过了
let hasUpload = await this.check(fileHash, filename)
if (!hasUpload) {
this.status = 1
let promises = []
for (let i = 0; i < chunkArr.length; i++) {
//将最后的返回结果添加到数组中
let res = await this.upload(fileHash, chunkArr, i, filename)
promises.push(res)
}
Promise.all(promises).then(res => {
this.mergeNotify(fileHash, filename, chunkArr.length)
this.status = 2
// msg.style.color = 'green'
}).catch(err => {
this.status = 4
console.error(err)
})
} else {
//文件上传过了,无需再次上传
this.status = 3
// msg.style.color = 'red'
}
})
},
methods: {
clickUpload() {
this.$refs.up.click()
},
reset() {
this.status = 0
this.filename = ''
this.$refs.up.value = ''
},
/**
*
* @param file 文件File对象
* @param chunkSize 每一个切片的大小
* @return {[]} 返回切片数组
*/
chunk(file, chunkSize) {
let res = []
for (let i = 0; i < file.size; i += chunkSize) {
res.push(file.slice(i, i + chunkSize))
}
return res
},
/**
*
* @param chunks 切片数组
* @return string 返回文件hash
*/
async hash(chunks) {
console.log(chunks)
let sparkMD5 = new SparkMD5.ArrayBuffer()
//存储每个切片加密的任务状态,全部完成后,才会返回最终hash
let promises = []
//将切片数组所有切片转为二进制,并将其合并为一个完整文件
for (let i = 0; i < chunks.length; i++) {
//由于hash加密耗时,所以我们采用异步
let promise = new Promise((resolve, reject) => {
let fileReader = new FileReader()//使用fileReader对象将文件切片转为二进制
fileReader.onload = (e) => {
console.log('add md5')
//添加到SparkMD5中,等所有切片添加完毕后,获取最终哈希
sparkMD5.append(e.target.result)
//每次添加成功后返回一个成功状态
resolve()
}
fileReader.onerror = (e) => {
reject(e.target.error)
}
fileReader.readAsArrayBuffer(chunks[i])
})
//将该promise任务添加到promise数组中
promises.push(promise)
}
//当所有加密任务全都完成后,返回加密后的完整文件hash
return await Promise.all(promises).then(res => {
const md5 = sparkMD5.end()
console.log(md5)
return md5
}).catch(err => {
this.status = 4
console.error('Hash加密出现问题')
})
},
/***
*
* @param hash 文件hash
* @param chunks 切片数组
* @param currentIndex 当前切片索引
* @param filename 文件名
* @return 返回Promise,用于检测当前切片是否上传成功
*/
upload(hash, chunks, currentIndex, filename) {
return new Promise((resolve, reject) => {
let formData = new FormData()
formData.append('hash', hash)
formData.append('chunkIndex', currentIndex)
formData.append('filename', filename)
formData.append('chunkBody', chunks[currentIndex])
axios.post('/file/upload', formData).then(res => {
//出现无法判断是否成功的问题,推荐判断是否成功在Promise.all中判断
resolve('')
}).catch(err => {
reject(err)
})
})
},
/***
* 通知后端接口:可以开始合并任务了
* @param hash 文件hash
* @param filename 文件名
*/
mergeNotify(hash, filename, chunksLen) {
let formData = new FormData()
formData.append('filename', filename)
formData.append('fileHash', hash)
formData.append('totalChunk', chunksLen)
axios.post('/file/merge', formData).then(res => {
})
},
/**
* 检查文件是否上传
* @param hash 文件hash
* @param filename 文件名
* @return {Promise<Boolean>} 返回一个Promise对象
*/
async check(hash, filename) {
const statusCode = {
UPLOAD_SUCCESS: 200,
NOT_UPLOAD: 202,
ALREADY_UPLOAD: 1000,
UPLOAD_FAILED: 1004,
}
let formData = new FormData()
formData.append('filename', filename)
formData.append('fileHash', hash)
formData.append('registry', this.params.registry)
formData.append('mark', this.params.mark)
formData.append('useId', this.params.useId)
let hasUpload = axios.post('/file/check', formData).then(res => {
let result
//判断是否上传过该文件
if (res.data.code === statusCode.NOT_UPLOAD) {
result = false
} else {
result = true
}
//返回promise对象
return Promise.resolve(result)
})
return hasUpload
},
},
}
</script>
<style scoped>
.msg {
font-size: 20px;
font-weight: bold;
}
</style>