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继续上传。失败之前的文件数据只要不刷新页面是会继续保留的,所以这功能问题不大。 今日的分享完毕,下次在来~~

相关推荐
还是大剑师兰特15 分钟前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_7482361124 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61736 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489438 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js