【上传文件过大进行的切片式上传】

时间:2024-12-21 10:47:31
<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>