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

发布于:2024-12-21 ⋅ 阅读:(13) ⋅ 点赞:(0)

在这里插入图片描述

一:写出弹窗样式(父组件)


    <!-- 镜像上传弹窗 -->
    <el-dialog
        :title="$t('image.uploadImage')"
        :visible.sync="dialogVisible"
        @close="clearDialogForm"
        custom-class="dialog-style"
        :close-on-click-modal="false"
        width="900px"
    >
      <el-form
          ref="formValidate"
          :model="form"
          class="add-node-form"
          :rules="ruleValidate"
          label-width="130px"
      >
        <div>
          <div class="subtitle">
            {{ $t('DI.basicSettings') }}
          </div>
          <el-form-item
              label="镜像 tag"
              maxlength="225"
          >
            <el-input
                v-model="form.registry"
                placeholder="请输入镜像 tag"
            />
          </el-form-item>
          <el-form-item label="镜像描述" maxlength="225">
            <el-input v-model="form.mark" placeholder="请输入镜像描述"/>
          </el-form-item>
          //一个上传的组件
          <el-form-item>
            <uploader ref="uploader" :disabled="!this.form.registry" :params="this.form"/>
          </el-form-item>
        </div>
      </el-form>
      <span
          slot="footer"
          class="dialog-footer"
      >
          <el-button @click="dialogVisible=false">
            {{ $t('common.cancel') }}
          </el-button>
        </span>
    </el-dialog>

二:上传接口的传参

      form: {
        registry: '',
        mark: '',
        useId: '',
      },

三:引入的组件

import Uploader from '@/views/ComputingPower/components/Uploader'

  components: {
    Uploader,
  },

四:点击打开弹窗事件


    // 打开弹窗获取用户id    不需要的可以直接省略最后一行
    openImageUpdate() {
      this.activeName = 'second'
      this.dialogVisible = true
      if (this.$refs.uploader) this.$refs.uploader.reset()
      this.form.useId = this.userOptionList.find(e => e.name === 'hadoop').id
    },

五:最后的切片上传的组件内容(子组件)
需要手动安装vue 的 SparkMD5

<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>