首页 > 其他分享 >断点续传 上传逻辑及代码

断点续传 上传逻辑及代码

时间:2022-09-20 10:23:47浏览次数:78  
标签:断点续传 hash index 代码 console percentCount const chunkList 上传

整体逻辑如下

       前台引用spark-md5获取文件唯一ID值,即md5值,前台将文件进行分片,通过该值进行后台校验,以此实现断点续传。

       前台计算MD5,前台计算MD5快慢与文件大小相关,成功后方可请求后台,以防无值出现上传文件错乱。

      后台用MD5值进行校验,有则跳过,无则追加,文件分片完全上传后,再请求合并接口将文件进行合并。

相应注释见代码,具体前后台代码如下:

前台:

<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">大小不超过 200M 的视频</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 v-if="videoUrl">
      <video :src="videoUrl" controls />
    </div>
  </div>
</template>
 
<script>
import SparkMD5 from "spark-md5";
import axios from "axios";

export default {
  name: "App3",
  filters: {
    btnTextFilter(val) {
      return val ? "暂停" : "继续";
    }
  },
  data() {
    return {
      percent: 0,
      videoUrl: "",
      upload: true,
      percentCount: 0,
      CurFileName: "",
      baseurllocal: window.localStorage.getItem("rooturl"),
      curPoint: 0,
      calchunk: 0
    };
  },
  methods: {
    ReadFileMd5(fileObj) {
      return new Promise((resolve, reject) => {
        let hash;
        const slicelength = 10;
        const rfmchunkSize = Math.ceil(fileObj.size / slicelength);
        const fileReader = new FileReader();
        const md5 = new SparkMD5();
        let index = 0;
        const loadFile = () => {
          const slice = fileObj.slice(index, index + rfmchunkSize);
          fileReader.readAsBinaryString(slice);
        };
        loadFile();
        fileReader.onload = e => {
          md5.appendBinary(e.target.result);
          if (index < fileObj.size) {
            index += rfmchunkSize;
            loadFile();
          } else {
            hash = md5.end();
            this.hash = hash;
            console.log(hash);
            resolve(hash);
          }
        };
      });
    },
    async handleChange(file) {
      if (!file) return;
      this.percentCount = 0;
      this.percent = 0;
      this.videoUrl = "";
      // 获取文件并转成 ArrayBuffer 对象
      const fileObj = file.raw;
      this.hash = "";
      this.ReadFileMd5(fileObj).then(response => {
        const hash = this.hash;
        // 将文件按固定大小(2M)进行切片,注意此处同时声明了多个常量
        const chunkSize = 10485760, //2097152,
          chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize), // 计算总共多个切片
          suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名

        // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
        let curChunk = 0; // 切片时的初始位置
        for (let i = 0; i < chunkListLength; i++) {
          const item = {
            breakPoint: i.toString(),
            chunk: fileObj.slice(curChunk, curChunk + chunkSize),
            fileName: `${hash}_${i}.${suffix}` // 文件名规则按照 hash_1.jpg 命名
          };
          curChunk += chunkSize;
          chunkList.push(item);
        }
        console.log(chunkList);
        this.chunkList = chunkList;
        this.CurFileName = file.raw.name;
        console.log(this.CurFileName);
        console.log("chunkListLength" + "------" + chunkListLength);
        this.CheckPoint().then(res => {
          if (this.percentCount === 0) {
            this.percentCount = 100 / this.chunkList.length;
          }
          if (this.calchunk > 0) {
            this.percent += this.percentCount * this.calchunk; // 改变进度
            this.reqChuck(this.calchunk);
          } else {
            this.reqChuck(0);
          }
        });
      });
    },
    async reqChuck(curchunk) {
      let vm = this;
      await vm.reqChuckPoint(curchunk);
    },
    async reqChuckPoint(index) {
      this.curPoint = index;
      if (!this.upload) {
        return;
      } else {
        if (index > this.chunkList.length - 1) {
          this.complete();
          // this.percent = 100;
          console.log("循环结束");
          return;
        } else {
          const item = this.chunkList[index];
          const formData = new FormData();
          formData.append("md5", this.hash);
          formData.append("breakPoint", item.breakPoint);
          formData.append("chunk", item.chunk);
          formData.append("fileName", item.fileName);
          let url = this.baseurllocal + "/api/weatherforecast/UploadEfilePoint";
          console.log(url);
          await this.$api.post(url, formData, res => {
            if (res.code === 200) {
              // 成功
              if (this.percentCount === 0) {
                // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                this.percentCount = 100 / this.chunkList.length;
              }
              this.percent += this.percentCount; // 改变进度
              console.log(this.percent);
              // this.chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk,方便断点续传
              index++;
              this.reqChuck(index);
            } else {
              this.upload = !this.upload;
              console.log("code不为200" + index);
            }
          });
        }
      }
    },
    complete() {
      if (!this.upload) return;
      console.log(this.baseurllocal + "/api/weatherforecast/FileMerge");
      this.$api.post(
        this.baseurllocal + "/api/weatherforecast/FileMerge",
        { hash: this.hash, filename: this.CurFileName },
        res => {
          if (res.code === 200) {
            // 请求发送成功
            console.log("合并成功");
            // this.videoUrl = res.data
          }
        }
      );
    },
    // 按下暂停按钮
    handleClickBtn() {
      this.upload = !this.upload;
      if (this.upload) {
        // 如果不暂停则继续上传
        if (this.percent >= 100) {
          this.percentCount = 0;
          this.percent = 0;
        }
        this.CheckPoint().then(res => {
          if (this.percentCount === 0) {
            this.percentCount = 100 / this.chunkList.length;
          }
          console.log(this.calchunk);
          console.log(this.curPoint);
          if (this.calchunk > 0 && this.calchunk >= this.curPoint - 1) {
            this.percent = this.percentCount * (this.curPoint - 1); // 改变进度
            this.reqChuck(this.curPoint - 1);
          } else {
            this.reqChuck(0);
          }
        });
      }
    },
    CheckPoint() {
      this.calchunk = 0;
      return new Promise((resolve, reject) => {
        this.$api.get(
          "api/weatherforecast/CheckPoint",
          { hash: this.hash },
          res => {
            if (res.code == 200) {
              this.calchunk = res.data;
              resolve(res.data);
            } else {
              this.calchunk = 0;
              resolve();
            }
          }
        );
      });
    }
  }
};
</script>
 
<style scoped>
.progress-box {
  box-sizing: border-box;
  width: 360px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  padding: 8px 10px;
  background-color: #ecf5ff;
  font-size: 14px;
  border-radius: 4px;
}
</style>

后台net core代码如图

 

 

 

标签:断点续传,hash,index,代码,console,percentCount,const,chunkList,上传
From: https://www.cnblogs.com/Y-o-u/p/16710123.html

相关文章

  • C#反编译DLL查看代码内容
    方法一:使用ildasm.exe查看IL代码在c盘里面找到ildasm.exe并打开。(Win10自带,一般在系统中的地址为:C:\ProgramFiles(x86)\MicrosoftSDKs\Windows\v7.0A\bin\ildasm.exe)......
  • k8s 容器自动重启 错误 代码
    参考文章https://betterprogramming.pub/understanding-docker-container-exit-codes-5ee79a1d58f6ExitCodesCommonexitcodesassociatedwithdockercontainersar......
  • 微信公众号如何上传附件文件
    说到微信公众号,相信大家都非常熟悉。微信公众号是开发者或商家在微信公众平台上申请的应用账号,该账号与QQ账号互通,平台上实现和特定群体的文字、图片、语音、视频的全方位......
  • 代码03
    print("今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问几何?\n")number=int(input("请输入您认为符合条件的数:"))ifnumber%3==2andnumber%5==3andnumber%7==......
  • OSS实现文件上传功能
    心有千斤坠,却无一字言使用对象存储,将文件上传到阿里云分布式文件服务器上。帮助文档:整合spring-cloud:https://github.com/alibaba/aliyun-spring-boot/blob/maste......
  • Python 代码智能感知 —— 类型标注与特殊的注释(献给所有的Python人)
    【原文地址:https://xiaokang2022.blog.csdn.net/article/details/126936985】​一个不会写好的类型标注和注释的Python程序员,是让使用TA的代码的人都痛苦无比的事情…......
  • ArcGIS Pro 二次开发缓冲区代码
    varvalueArray=awaitQueuedTask.Run(()=>{varg=newList<object>(){geometry,};//Createsa8000-meter......
  • 代码之丑-坏味道
    >但能够把代码写得更具可维护性,这是一个程序员从业余迈向职业的第一步。坏味道之Setterpublicvoidapprove(finallongbookId){book.setReviewStatus(Revie......
  • gitee(码云)的注册和代码提交
    概述Github与Gitee是同一类,在云端。区别是Github是国外的,Gitee是国内的。二者的使用需要借助Git。GitHub是全英文并且用户基数多,知名的库也多Gitee全是中文,而且大部分用户......
  • 项目代码在pycharm中可以正常运行,但是linux运行报错No module named 'XX'
    问题产生的原因:pycharm自动将代码的主函数路径加入到运行中去,但是linux不会。解决问题的本质:为项目中文件找到更目录并添加到sys路径中。项目实例   原始的项目......