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

相关推荐
zhougl99637 分钟前
html处理Base文件流
linux·前端·html
花花鱼41 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_44 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)3 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法