大文件分片上传vue+springboot

302 阅读2分钟

大文件上传大概是以下几个步骤:

选择文件: 用户选择要上传的文件,可以通过文件选择框或拖放文件到上传区域来实现。

<template>
  <div id="app">
    <!-- 上传组件 -->
    <el-upload action drag :auto-upload="false" :show-file-list="false" :on-change="handleChange">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <div class="el-upload__tip" slot="tip">大小不超过 10G</div>
    </el-upload>

    <!-- 进度显示 -->
    <div class="progress-box">
      <span>上传进度:{{ percent.toFixed() }}%</span>
      <el-button type="primary" size="mini" @click="handleClickBtn">{{ upload | btnTextFilter }}</el-button>
    </div>

    <!-- 展示上传成功的操作 -->
    
  </div>
</template>
  1. 计算切片文件: 将大文件切分成固定大小的切片,通常使用类似于 2MB 大小的切片。
// 获取文件并转成 ArrayBuffer 对象
      const fileObj = file.raw
      let buffer;
      try {
        buffer = await this.fileToBuffer(fileObj)
      } catch (e) {
        console.log(e)
      }

      // 将文件按固定大小(2M)进行切片,注意此处同时声明了多个常量
      const chunkSize = 2097152,
        chunkList = [], // 保存所有切片的数组
        chunkListLength = Math.ceil(fileObj.size / chunkSize), // 计算总共多个切片
        suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1] // 文件后缀名

      
  1. 计算文件 hash并开始切片: 使用类似于 MD5 或 SHA256 等算法计算文件的唯一标识,用于后续校验文件完整性。我用的md5。
// 根据文件内容生成 hash 值
      const spark = new SparkMD5.ArrayBuffer()
      spark.append(buffer)
      const hash = spark.end()

      // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
      let curChunk = 0 // 切片时的初始位置
      for (let i = 0; i < chunkListLength; i++) {
        const item = {
          chunk: fileObj.slice(curChunk, curChunk + chunkSize),
          fileName: `${hash}_${i}.${suffix}` // 文件名规则按照 hash_1.jpg 命名
        }
        curChunk += chunkSize
        chunkList.push(item)
      }
  1. 并发上传切片: 将切片文件并发上传到服务器,可以使用多线程或异步请求来提高上传效率。其中可以从列表中传入索引。

chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk,方便断点续传

// 发送请求
    sendRequest() {
      const totalChunks = this.chunkList.length; // 总切片数量
      const progressPerChunk = 100 / totalChunks; // 每个切片上传所占的进度百分比
      const requestList = [] // 请求集合
      this.chunkList.forEach((item, index) => {
        const fn = () => {
          const formData = new FormData()
          formData.append('chunk', item.chunk)
          formData.append('filename', item.fileName)
          formData.append('index', index);
          return fileApi.postChunks(formData).then(res => {
            if (res.code === 20000) { // 成功
    
              this.percent += progressPerChunk // 改变进度
              this.chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk,方便断点续传
            }
          })
        }
        requestList.push(fn)
      })

      
  1. 合并切片: 在服务器端接收到所有切片后,发送merge请求将切片按顺序合并成完整的文件。
        let i = 0 // 记录发送的请求个数
      // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
      const complete = () => {
        fileApi.merge(this.hash).then(res => {
          if (res.code === 0) { // 请求发送成功
            // this.videoUrl = res.data.path
          }
        })
      }
      const send = async () => {
        if (!this.upload) return
        if (i >= requestList.length) {
          // 发送完毕
          complete()
          return
        }
        await requestList[i]()
        i++
        send()
      }
      send() // 发送请求
    },

小问题:并发请求是异步的,响应的时间不确定那怎么确认进度? 通过计算总切片数量,每次响应后this.percent += progressPerChunk // 改变进度