PC端大文件上传:分片上传、断点续传、上传进度条

最近写的项目需要支持多个大文件上传、显示上传进度条、所有文件上传状态、上传失败重新上传。前面三个还好实现,但是由于目前后端接口未实现断点续传,所以现在上传失败,点击重新上传是从头开始上传。这个功能还待定,但是对于前端来说实现难度不大。效果图如下:

实现思路

FileReader

FileReader 对象允许我们读取存储在用户计算机上文件的内容,使用 File 或者**Blob** 对象指定要读取的文件或数据。大文件上传,在文件成功读取时 load 之后,我们只要知道这个文件大小是多少,按量分割,顺序上传,每次只上传当前这个区间单位的文件。

问题 复制代码
分片上传有一个很重要的点就是当前上传的分片是否已经上传过?
如何判断当前上传的是第几片?
多个上传如何区别?
上传之前校验文件格式
javascript 复制代码
async beforeUploadFile(file, val) {
      await reportApi.abortMultipartUpload({ fileId: this.currentItem.fileId });
      const ext = file.fileSuffixName || extendForFile(file);
      if (
        [
          "doc",
          "docx",
          "pdf",
          "xls",
          "xlsx",
          "zip",
          "mp4",
          "pptx",
          "ppt",
          "mp3",
        ].some((item) => item === ext)
      ) {
        this.requestName = reportApi.ossFileUploadFile;
        if (file.size / 1024 / 1024 / 1024 > 100) {
          this.$message.error("文件大小超出100M,请重新上传!");
          return false;
        }
      } else if (
        ["png", "bmp", "jpg", "jpeg", "gif", "tif"].some((item) => item === ext)
      ) {
        this.requestName = reportApi.ossFileUploadFile;
        if (file.size / 1024 / 1024 / 1024 > 40) {
          this.$message.error("图片大小超出40M,请重新上传!");
          return false;
        }
      } else {
        this.$message.error("暂不支持文件上传格式,请重新上传!");
        return false;
      }
      this.selectOptions = val;

      console.log(file, "this.$refs.cropper.showModal");
    }
上传文件解析

文件load成功后,需要先初始化文件,获取文件的切片大小、切片数量以及一些文件的唯一信息。虽然支持上传多个文件,但是上传还是按照顺序一个一个的,fileListData在每次初始化都需要记录当前文件的所有信息。上传进度只需要在初始化成功/失败、分片上传成功/失败,上传完成这5种情况下,记录当前上传分片数量。 在初始化成功后,根据当前上传分片数量和总数量大小比对,来判断是否要执行合并操作。

javascript 复制代码
// 获取文件后缀
extendForFile(file){
  var imgName = file.name
  var idx = imgName.lastIndexOf('.')
  var ext = ''
  if (idx !== -1) {
    ext = imgName.substr(idx + 1).toUpperCase()
    ext = ext.toLowerCase()
  }
  return ext
}
javascript 复制代码
 uploadVideo({ file, onSuccess, onError }, prop) {
      console.log(111111, this.fileListData);
      const that = this;
      let fileRederInstance = new FileReader();
      fileRederInstance.readAsBinaryString(file);
      fileRederInstance.addEventListener("load", (e) => {
        let fileBolb = e.target.result;
        const fileMD5 = MD5(fileBolb); // 文件秒传的关键,文件生成的md5是唯一的,如果判断上传的文件和数据库原来上传过的文件md5值相同就可以直接吐出原来的文件地址,达到文件秒传的效果
        // 1、初始化
        const ext = file.fileSuffixName || extendForFile(file);
        this.fileListData.push({
          fileName: file.name,
          fileSize: file.size,
          fileSuffixName: ext,
          fileError: false,
          size: file.size,
          video: file,
          chunkCount: Math.ceil(file.size / this.chunkSize), // 切片数量
          fileMD5,
        });
        reportApi
          .shardingInitiate({
            fileName: file.name || "111",
            md5: fileMD5,
          })
          .then((res) => {
            this.fileListData.forEach((f) => {
              if (f.fileName == file.name) {
                f.size = file.size;
                f.fileId = res.result.fileId;
                f.uploadId = res.result.uploadId;
                f.videoName = res.result.fileName;
                f.fileDirVideoKey = res.result.fileDirVideoKey;
              }
            });
            //初始化文件之后,需要触发父组件,开始记录上传状态和上传进度
            this.onFinish(this.fileListData)
            this.readChunkMD5(0, file.name);
          })
          .catch((error) => {
            console.log(error, "09----");
            this.fileListData.forEach((f) => {
              if (f.fileName == file.name) {
                f.fileHttpUrl = "";
                f.fileUrl = "";
                f.fileError = true;
                f.fileResetUpload = true;
              }
            });
            //初始化文件失败,需要触发父组件,记录上传状态
            this.onFinish(this.fileListData)
          });
      });
    },
javascript 复制代码
getChunkInfo(currentChunk, curFileName) {
  const cur = this.fileListData[0];
  console.log(cur);
  let start = currentChunk * this.chunkSize; // 起始位置
  let end = Math.min(cur.size, start + this.chunkSize); // 结束位置
  let chunk = cur.video.slice(start, end); // 切片内容
  console.log(start, end, chunk);
  console.log("0." + start, "start222");
  // let that = this;
  // let storeAs = this.videoName;
  // that.$message.loading(`${storeAs}正在上传,请稍后...${start}%`,0);
  return { start, end, chunk };
},
javascript 复制代码
readChunkMD5(num, curFileName) {
  const { chunk } = this.getChunkInfo(num, curFileName);
  const cur = this.fileListData.find((f) => f.fileName == curFileName);
  const curFileId = this.fileListData.find(
    (f) => f.fileName == curFileName
  ).fileId;
  console.log("readChunkMD5:", num)
  this.onProgress({
    currentChunk: num,
    chunks: cur.chunkCount,
    curFileId,
  })
  // 如果当前上传的文件小于总数量就执行上传操作,如果大于当前数量走合并操作
  if (num < cur.chunkCount) {
    let fetchForm = new FormData();
    fetchForm.append("chunk", num + 1); // 当前分片数
    fetchForm.append("chunks", cur.chunkCount);
    fetchForm.append("file", chunk); // 当前分片文件内容
    fetchForm.append("md5", cur.fileMD5);
    fetchForm.append("objectName", cur.videoName);
    fetchForm.append("uploadId", cur.uploadId);
    fetchForm.append("curPartSize", chunk.size);
    fetchForm.append("fileDirVideoKey", cur.fileDirVideoKey);
    fetchForm.append("fileId", cur.fileId);
    fetchForm.append("fileSize", cur.size);
    reportApi
      .shardingFile(fetchForm)
      .then((res) => {
        this.loadProgress++;
        num = num + 1;
        this.readChunkMD5(num, curFileName);
        this.onFinish(this.fileListData)
      })
      .catch((error) => {
        console.log(error, "09----");
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = "";
            f.fileUrl = "";
            f.fileError = true;
            f.fileResetUpload = true;
          }
        });
        //分片上传成功后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData);
        this.$forceUpdate();
      });
  } else {
    reportApi
      .complete({
        uploadId: cur.uploadId,
        objectName: cur.videoName,
        fileDirVideoKey: cur.fileDirVideoKey,
        fileSize: cur.size,
      })
      .then((res) => {
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = res.result.fileHttp;
            f.fileUrl = res.result.fileUrl;
            f.fileError = false;
            f.fileResetUpload = false;
            f.uploadTime = moment().format("YYYY-MM-DD HH:mm:ss");
          }
        });
        //上传成功后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData)
        this.$emit("updateRender");
      })
      .catch((error) => {
        console.log(error, "09----");
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = "";
            f.fileUrl = "";
            f.fileError = true;
            f.fileResetUpload = true;
          }
        });
        //分片上传失败后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData)
        this.$emit("updateRender");
      });
  }
}
javascript 复制代码
<a-upload
  :accept="uploadfileType"
  name="file"
  :multiple="false"
  :custom-request="uploadVideo"
  :show-upload-list="false"
  :before-upload="beforeUploadFile"
>
  <slot name="content">
    <div style="display:flex:align-items:center">
      <span>上传失败,</span>
      <span class="link">重新上传</span>
      <img
        :src="undo_blue"
        width="12"
        height="12"
      >
    </div>
  </slot>
</a-upload>
<video
  ref="video"
  v-show="false"
/>
</div>

完整代码也贴上去了,等后端断点续存的接口出来。就可以在上传失败的情况下,根据当前上传到第几片,当前文件的uploadId继续上传。失败之前的文件数据只要不刷新页面是会继续保留的,所以这功能问题不大。 今日的分享完毕,下次在来~~

相关推荐
哎呦你好2 分钟前
HTML 表格与div深度解析区别及常见误区
前端·html
运维@小兵4 分钟前
vue配置子路由,实现点击左侧菜单,内容区域显示不同的内容
前端·javascript·vue.js
koiy.cc1 小时前
记录:echarts实现tooltip的某个数据常显和恢复
前端·echarts
一只专注api接口开发的技术猿1 小时前
企业级电商数据对接:1688 商品详情 API 接口开发与优化实践
大数据·前端·爬虫
GISer_Jing1 小时前
[前端高频]数组转树、数组扁平化、深拷贝、JSON.stringify&JSON.parse等手撕
前端·javascript·json
古拉拉明亮之神1 小时前
Spark处理过程-转换算子
javascript·ajax·spark
Yvonne爱编码2 小时前
CSS- 4.1 浮动(Float)
前端·css·html·github·html5·hbuilder
timeguys2 小时前
【前端】[vue3] [uni-app]使用 vantUI 框架
前端·uni-app
岁岁岁平安2 小时前
Vue3学习(组合式API——Watch侦听器、watchEffect()详解)
前端·javascript·vue.js·学习·watch侦听器·组合式api
码视野2 小时前
基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)
vue.js·spring boot·系统架构