Vue는 중단점 재개 및 업로드 진행률 표시줄을 포함하여 대용량 파일의 조각화된 업로드를 구현합니다.

먼저 멀티파트 업로드가 무엇인지 설명합니다.

        부분 업로드는 대용량 파일을 여러 조각으로 나누어 하나씩 전송하는 것입니다. 이는 재업로드의 오버헤드를 줄이는 이점이 있습니다. 예를 들어, 우리가 업로드하는 파일이 큰 파일인 경우 업로드 시간이 상대적으로 길어야 하며 네트워크 불안정의 다양한 요인의 영향과 결합하여 전송이 중단되기 쉽고 사용자는 다른 선택이 없습니다. 하지만 파일 방식으로 다시 업로드해야 하지만 멀티파트 업로드를 사용하여 이 문제를 해결할 수 있습니다. 프래그먼트 업로드 기술을 통해 네트워크 전송이 중단되면 나머지 프래그먼트만 업로드하면 파일을 다시 선택할 수 있습니다. 전체 파일을 재전송할 필요가 없으므로 재전송의 오버헤드가 크게 줄어듭니다.

그러나 적절한 샤드를 어떻게 선택합니까? 따라서 우리는 다음 사항을 고려해야 합니다.

1. 샤드가 작을수록 더 많은 요청이 있어야 하고 오버헤드가 커집니다. 따라서 너무 작게 설정할 수 없습니다.

2. 샤드가 클수록 유연성이 떨어집니다.

3. 서버는 고정된 크기의 수신 버퍼를 갖게 됩니다. 슬라이스의 크기는 바람직하게는 이 값의 정수배입니다.

멀티파트 업로드 단계

1. 먼저 md5 로 파일을 암호화합니다 . md5 암호화를 사용하면 파일을 고유하게 식별할 수 있고 백그라운드에서 파일 무결성 검증을 위해 비교할 수도 있다는 장점이 있습니다.

2. md5 값을 받은 후 서버는 파일이 이미 업로드되었는지 확인하고 이미 업로드된 경우 다시 업로드할 필요가 없습니다.

3. 대용량 파일을 샤딩합니다. 예를 들어 100M 파일의 경우 조각 중 하나가 5M이면 이 파일을 20번 업로드할 수 있습니다.

4. 백그라운드에서 인터페이스를 요청합니다. 인터페이스의 데이터는 업로드한 파일 블록입니다. (참고: 이 요청을 보내는 이유는 무엇입니까? 중단점에서 계속 업로드할 수 있도록 하기 위한 것입니다. 예를 들어 Baidu의 네트워크 디스크를 사용합니다. 그러면 서버는 이전에 업로드한 파일 블록을 기억해야 합니다. 컴퓨터에서 다시 업로드하려면 이전에 업로드한 파일 블록을 건너뛰어야 합니다. 그런 다음 후속 블록을 업로드합니다.)

5. 업로드되지 않은 파일 블록 업로드를 시작합니다. (이것은 모든 조각을 병합하고 요청을 업로드하는 두 번째 요청입니다.)

6. 업로드가 성공하면 서버가 파일을 병합합니다. 드디어 끝났습니다.

할 말이 많지 않습니다. 코딩을 시작하세요.

<template>
  <div>
  <!-- on-preview	点击文件列表中已上传的文件时的钩子 -->
  <!-- http-request	覆盖默认的上传行为,可以自定义上传的实现 -->
  <!-- limit	最大允许上传个数 -->
  <!-- before-upload	上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 -->
  <!-- accept	接受上传的文件类型(thumbnail-mode 模式下此参数无效) -->
  <!-- multiple	是否支持多选文件 -->
  <!-- on-change	文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 -->
  <!-- on-remove	文件列表移除文件时的钩子 -->
  <!-- file-list	上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] -->
  <!-- on-exceed	文件超出个数限制时的钩子 -->
  <!-- auto-upload	是否在选取文件后立即进行上传 -->
  <!-- action	必选参数,上传的地址  例如  action="https://jsonplaceholder.typicode.com/posts/"-->
  <el-upload
    drag
    multiple
    :auto-upload="true"
    :http-request="checkedFile"
    :before-remove="removeFile"
    :limit="10"
    action="/tools/upload_test/"
  >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">
      将文件拖到此处,或
      <em>点击上传</em>
    </div>
  </el-upload>
  <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress>
</div>
</template>

파일 업로드 시 http-request 메소드를 사용하게 되며, 이 메소드를 정의하면 submit컴포넌트의 메소드를 가로채게 됨( submit이 메소드에서 컴포넌트의 메소드를 호출하지 않도록 주의하여 무한루프 발생) ).이 방법에서는 내가 원하는 것을 할 수 있습니다.

http-request의 수신 콜백 함수는 one을 반환해야 하므로 Promise쓸모없는 Promise를 직접 정의했습니다.

const prom = new Promise((resolve, reject) => {})
prom.abort = () => {}
return prom

중단점에서 재개 가능한 업로드를 구현하려면 백엔드와 협력하는 방법을 결정해야 합니다.

여기에서 내 계획은 모든 프래그먼트를 업로드한 후 쿼리 인터페이스를 요청하고 이 인터페이스에서 프래그먼트가 성공적으로 업로드되지 않은 백엔드가 나에게 반환(일련 번호를 알려줌)하는 것입니다. 그런 다음 성공적으로 업로드되지 않은 조각을 다시 업로드하십시오.

완성된 코드를 직접 붙여넣으면 주석이 다 들어있습니다 이해가 안되면 저에게 직접 연락하셔도 됩니다 블로그에 연락 방법이 있습니다(element-ui, axios, spark-md5에 따라 다름)

<template>
  <div>
  <!-- on-preview	点击文件列表中已上传的文件时的钩子 -->
  <!-- http-request	覆盖默认的上传行为,可以自定义上传的实现 -->
  <!-- limit	最大允许上传个数 -->
  <!-- before-upload	上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 -->
  <!-- accept	接受上传的文件类型(thumbnail-mode 模式下此参数无效) -->
  <!-- multiple	是否支持多选文件 -->
  <!-- on-change	文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 -->
  <!-- on-remove	文件列表移除文件时的钩子 -->
  <!-- file-list	上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] -->
  <!-- on-exceed	文件超出个数限制时的钩子 -->
  <!-- auto-upload	是否在选取文件后立即进行上传 -->
  <!-- action	必选参数,上传的地址  例如  action="https://jsonplaceholder.typicode.com/posts/"-->
  <el-upload
    drag
    multiple
    :auto-upload="true"
    :http-request="checkedFile"
    :before-remove="removeFile"
    :limit="10"
    action="/tools/upload_test/"
  >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">
      将文件拖到此处,或
      <em>点击上传</em>
    </div>
  </el-upload>
  <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress>
</div>
</template>
  <script>
import axios from "axios";
import SparkMD5 from "spark-md5";
export default {
  data() {
    return {
      maxSize: 5 * 1024 * 1024 * 1024, // 上传最大文件限制  最小单位是b
      multiUploadSize: 100 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传)  100mb
      eachSize: 100 * 1024 * 1024, // 每块文件大小   100mb
      requestCancelQueue: [], // 请求方法队列(调用取消上传
      url: "/tools/upload_test/",
       //上传进度
      progress: 0,
      showProgress: false,
      // 每上传一块的进度
      eachProgress: 0,
      // 总共有多少块。断点续传使用
      chunksKeep:0,
      // 切割后的文件数组
      fileChunksKeep:[],
      // 这个文件,断点续传
      fileKeep:null
    };
  },
  mounted() {
    
  },
  methods: {
    async checkedFile(options) {
      console.log(options);
      const {
        maxSize,
        multiUploadSize,
        getSize,
        splitUpload,
        singleUpload
      } = this; // 解构赋值
      const { file, onProgress, onSuccess, onError } = options; // 解构赋值
      if (file.size > maxSize) {
        return this.$message({
          message: `您选择的文件大于${getSize(maxSize)}`,
          type: "error"
        });
      }
      this.fileKeep = file
      const uploadFunc =
        file.size > multiUploadSize ? splitUpload : singleUpload; // 选择上传方式
      try {
        await uploadFunc(file, onProgress);
        this.$message({
          message: "上传成功",
          type: "success"
        });
        this.showProgress = false;
        this.progress = 0;
        onSuccess();
      } catch (e) {
        console.error(e);
        this.$message({
          message: e.message,
          type: "error"
        });
        this.showProgress = false;
        this.progress = 0;
        onError();
      }
      const prom = new Promise((resolve, reject) => {}); // 上传后返回一个promise
      prom.abort = () => {};
      return prom;
    },
    // 格式化文件大小显示文字
    getSize(size) {
      return size > 1024
        ? size / 1024 > 1024
          ? size / (1024 * 1024) > 1024
            ? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB"
            : (size / (1024 * 1024)).toFixed(2) + "MB"
          : (size / 1024).toFixed(2) + "KB"
        : size.toFixed(2) + "B";
    },
    // 单文件直接上传
   async singleUpload(file, onProgress) {
      await this.postFile(
        { file, uid: file.uid, fileName: file.fileName ,chunk:0},
        onProgress
      );
      var spark = new SparkMD5.ArrayBuffer();
      spark.append(file);
      var md5 = spark.end();
      console.log(md5);
          const isValidate = await this.validateFile({
            fileName: file.name,
            uid: file.uid,
            md5:md5,
            chunks:1
          });
    },
    // 大文件分块上传
    splitUpload(file, onProgress) {
      console.log('file11')
      console.log(file)
      return new Promise(async (resolve, reject) => {
        try {
          const { eachSize } = this;
          const chunks = Math.ceil(file.size / eachSize);
          this.chunksKeep = chunks
          const fileChunks = await this.splitFile(file, eachSize, chunks);
          this.fileChunksKeep = fileChunks
          console.log('fileChunks,文件数组切割后')
          console.log(fileChunks)
          //判断每上传一个文件,进度条涨多少,保留两位小数
          
          this.eachProgress = parseInt(Math.floor(100 / chunks * 100) / 100);

          this.showProgress = true;
          let currentChunk = 0;
          for (let i = 0; i < fileChunks.length; i++) {
            // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
            console.log(currentChunk, i);
            // 此时需要判断进度条

            if (Number(currentChunk) === i) {
              // 每块上传完后则返回需要提交的下一块的index
               await this.postFile(
                {
                  chunked: true,
                  chunk: i,
                  chunks,
                  eachSize,
                  fileName: file.name,
                  fullSize: file.size,
                  uid: file.uid,
                  file: fileChunks[i]
                },
                onProgress
              );
              currentChunk++

              // 上传完一块后,进度条增加
              this.progress += this.eachProgress;
              // 不能超过100
              this.progress = this.progress > 100 ? 100 : this.progress;
            }
          }
          var spark = new SparkMD5.ArrayBuffer();
          spark.append(file);
          var md5 = spark.end();
          console.log(md5);
          const isValidate = await this.validateFile({
            chunks: fileChunks.length,
            // chunk: fileChunks.length,
            fileName: file.name,
            uid: file.uid,
            md5:md5,
            // task_id:file.uid
          });
          // if (!isValidate) {
          //   throw new Error("文件校验异常");
          // }
          resolve();
        } catch (e) {
          reject(e);
        }
      });
    },
    againSplitUpload(file, array) {
      console.log('file,array')
      console.log(file)
      console.log(array)
      return new Promise(async (resolve, reject) => {
        try {
          const { eachSize , fileKeep } = this;
          const chunks = this.chunksKeep
          const fileChunks = this.fileChunksKeep
          this.showProgress = true;
          // let currentChunk = 0;
          for (let i = 0; i < array.length; i++) {
            // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
            // console.log(currentChunk, i);
            // 此时需要判断进度条
              // 每块上传完后则返回需要提交的下一块的index
               await this.postFile(
                {
                  chunked: true,
                  chunk: array[i],
                  chunks,
                  fileName: file.fileName,
                  fullSize: fileKeep.size,
                  uid: file.uid,
                  file: fileChunks[array[i]]
                },
              );
              // currentChunk++

              // 上传完一块后,进度条增加
              // this.progress += this.eachProgress;
              // 不能超过100
              this.progress = this.progress > 100 ? 100 : this.progress;
          }
          var spark = new SparkMD5.ArrayBuffer();
          spark.append(fileKeep);
          var md5 = spark.end();
          console.log(md5);
          const isValidate = await this.validateFile({
            chunks: fileChunks.length,
            // chunk: fileChunks.length,
            fileName: file.fileName,
            uid: file.uid,
            md5:md5,
            // task_id:file.uid
          });
          // if (!isValidate) {
          //   throw new Error("文件校验异常");
          // }
          resolve();
        } catch (e) {
          reject(e);
        }
      });
    },
    // 文件分块,利用Array.prototype.slice方法
    splitFile(file, eachSize, chunks) {
      return new Promise((resolve, reject) => {
        try {
          setTimeout(() => {
            const fileChunk = [];
            for (let chunk = 0; chunks > 0; chunks--) {
              fileChunk.push(file.slice(chunk, chunk + eachSize));
              chunk += eachSize;
            }
            resolve(fileChunk);
          }, 0);
        } catch (e) {
          console.error(e);
          reject(new Error("文件切块发生错误"));
        }
      });
    },
    removeFile(file) {
      this.requestCancelQueue[file.uid]();
      delete this.requestCancelQueue[file.uid];
      return true;
    },
    // 提交文件方法,将参数转换为FormData, 然后通过axios发起请求
    postFile(param, onProgress) {
      console.log(param);
      const formData = new FormData();
      // for (let p in param) {
        // formData.append(p, param[p]);
      // }
      formData.append('file', param.file)  //  改了
      formData.append('uid',param.uid)
      formData.append('chunk',param.chunk)
      const { requestCancelQueue } = this;
      const config = {
        cancelToken: new axios.CancelToken(function executor(cancel) {
          if (requestCancelQueue[param.uid]) {
            requestCancelQueue[param.uid]();
            delete requestCancelQueue[param.uid];
          }
          requestCancelQueue[param.uid] = cancel;
        }),
        onUploadProgress: e => {
          if (param.chunked) {
            e.percent = Number(
              (
                ((param.chunk * (param.eachSize - 1) + e.loaded) /
                  param.fullSize) *
                100
              ).toFixed(2)
            );
          } else {
            e.percent = Number(((e.loaded / e.total) * 100).toFixed(2));
          }
          onProgress(e);
        }
      };
      // return axios.post('/api/v1/tools/upload_test/', formData, config).then(rs => rs.data)
      return this.$http({
        url: "/tools/upload_test/",
        method: "POST",
        
        data: formData
        // config
      }).then(rs => rs.data);
    },
    // 文件校验方法
    validateFile(file) {
      // return axios.post('/api/v1/tools/upload_test/', file).then(rs => rs.data)
      console.log(2)
      console.log(file)
    return  this.$http({
        url: "/tools/upload_test/upload_success/",
        method: "POST",
        data: file
      }).then(res => {
        if(res && res.status == 1){
            this.againSplitUpload(file,res.data.error_file)
          return true
        }
      });
    }
  }
};
</script>
<style scoped>
.progress{
  /* 在当前页面居中 */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* 宽度 */
}
</style>

코드 업데이트 위의 코드를 md5로 암호화한 후 백엔드에서 암호화한 MD5 값과 다름 다음 암호화는 동일 

<template>
  <div :class="showProgress == true ? 'loading' : ''">
    <!-- on-preview	点击文件列表中已上传的文件时的钩子 -->
    <!-- http-request	覆盖默认的上传行为,可以自定义上传的实现 -->
    <!-- limit	最大允许上传个数 -->
    <!-- before-upload	上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 -->
    <!-- accept	接受上传的文件类型(thumbnail-mode 模式下此参数无效) -->
    <!-- multiple	是否支持多选文件 -->
    <!-- on-change	文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 -->
    <!-- on-remove	文件列表移除文件时的钩子 -->
    <!-- file-list	上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] -->
    <!-- on-exceed	文件超出个数限制时的钩子 -->
    <!-- auto-upload	是否在选取文件后立即进行上传 -->
    <!-- action	必选参数,上传的地址  例如  action="https://jsonplaceholder.typicode.com/posts/"-->
    <el-upload drag multiple :auto-upload="true" :http-request="checkedFile" :before-remove="removeFile" :limit="10"
      action="/tools/upload_chunk/" :disabled="showProgress">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        将文件拖到此处,或
        <em>点击上传</em>
      </div>
    </el-upload>
    <!-- 正在上传的弹窗 -->
    <el-dialog title="正在上传" :visible.sync="showProgress" width="50%">
      <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress>
    </el-dialog>
    <!-- <el-progress type="circle" :percentage="progress" class="progress" v-if="showProgress"></el-progress> -->
  </div>
</template>
<script>
import axios from "axios";
import SparkMD5 from "spark-md5";
export default {
  data() {
    return {
      maxSize: 5 * 1024 * 1024 * 1024, // 上传最大文件限制  最小单位是b
      multiUploadSize: 100 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传)  100mb
      eachSize: 100 * 1024 * 1024, // 每块文件大小   100mb
      requestCancelQueue: [], // 请求方法队列(调用取消上传
      url: "/tools/upload_chunk/",
      //上传进度
      progress: 0,
      showProgress: false,
      // 每上传一块的进度
      eachProgress: 0,
      // 总共有多少块。断点续传使用
      chunksKeep: 0,
      // 切割后的文件数组
      fileChunksKeep: [],
      // 这个文件,断点续传
      fileKeep: null,
      // 断点续传,文件md5
      fileMd5Keep: ""
    };
  },
  mounted() { },
  methods: {
    async checkedFile(options) {
      // console.log(options);
      const {
        maxSize,
        multiUploadSize,
        getSize,
        splitUpload,
        singleUpload
      } = this; // 解构赋值
      const { file, onProgress, onSuccess, onError } = options; // 解构赋值
      if (file.size > maxSize) {
        return this.$message({
          message: `您选择的文件大于${getSize(maxSize)}`,
          type: "error"
        });
      }
      this.fileKeep = file;
      const uploadFunc =
        file.size > multiUploadSize ? splitUpload : singleUpload; // 选择上传方式
      try {
        await uploadFunc(file, onProgress);
        onSuccess();
      } catch (e) {
        console.error(e);
        this.$message({
          message: e.message,
          type: "error"
        });
        this.showProgress = false;
        this.progress = 0;
        onError();
      }
      const prom = new Promise((resolve, reject) => { }); // 上传后返回一个promise
      prom.abort = () => { };
      return prom;
    },
    // 格式化文件大小显示文字
    getSize(size) {
      return size > 1024
        ? size / 1024 > 1024
          ? size / (1024 * 1024) > 1024
            ? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB"
            : (size / (1024 * 1024)).toFixed(2) + "MB"
          : (size / 1024).toFixed(2) + "KB"
        : size.toFixed(2) + "B";
    },
    // 单文件直接上传
    async singleUpload(file, onProgress) {
      await this.postFile(
        { file, uid: file.uid, fileName: file.fileName, chunk: 0 },
        onProgress
      );
      // var spark = new SparkMD5.ArrayBuffer();
      // spark.append(file);
      // var md5 = spark.end();
      // console.log(md5);

      const reader = new FileReader();

      reader.readAsArrayBuffer(file);
      let hashMd5 = "";
      console.log(hashMd5);
      const that = this;
      function getHash(cb) {
        console.log("进入单个上传的getHash");
        reader.onload = function (e) {
          console.log("进入单个上传的getHash的函数2");
          console.log(hashMd5);
          console.log(this);
          // console.log(e)
          const hash = SparkMD5.ArrayBuffer.hash(e.target.result);
          // const hash = SparkMD5.ArrayBuffer.hash(file);
          console.log(hash);
          that.hashMd5 = hash;
          console.log(that.hashMd5);
          that.fileMd5Keep = hash;
          cb(hash);
        };
      }
      await getHash(function (hash) {
        console.log(hash);
        console.log(that);
        // 请求接口
        that.validateFile({
          name: file.name,
          uid: file.uid,
          md5: hash,
          chunks: 1,
          filter_type: "user_data_file"
        });
      });
    },
    // getMd5(file, chunkCount) {
    //   const spark = new SparkMD5.ArrayBuffer();
    //   let currentChunk = 0;

    //   const reader = new FileReader();

    //   reader.onload = function(e) {
    //     spark.append(e.target.result);
    //     currentChunk++;

    //     if (currentChunk < chunkCount) {
    //       console.log(currentChunk);
    //       loadNext();
    //     } else {
    //       console.log(spark.end());
    //       // 在这里请求接口
    //       return spark.end();
    //     }
    //   };

    //   function loadNext() {
    //     const start = currentChunk * chunkSize;
    //     const end =
    //       start + chunkSize >= file.size ? file.size : start + chunkSize;
    //     reader.readAsArrayBuffer(file.slice(start, end));
    //   }

    //   loadNext();
    // },
    // 大文件分块上传
    splitUpload(file, onProgress) {
      return new Promise(async (resolve, reject) => {
        try {
          const { eachSize } = this;
          const chunks = Math.ceil(file.size / eachSize);
          this.chunksKeep = chunks;
          const fileChunks = await this.splitFile(file, eachSize, chunks);
          this.fileChunksKeep = fileChunks;
          console.log("fileChunks,文件数组切割后");
          console.log(fileChunks);
          //判断每上传一个文件,进度条涨多少,保留两位小数
          this.eachProgress = parseInt(Math.floor((100 / chunks) * 100) / 100);
          this.showProgress = true;
          let currentChunk = 0;

          for (let i = 0; i < fileChunks.length; i++) {
            // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
            console.log(currentChunk, i);
            // 此时需要判断进度条
            if (Number(currentChunk) === i) {
              // 每块上传完后则返回需要提交的下一块的index
              await this.postFile(
                {
                  chunked: true,
                  chunk: i,
                  chunks,
                  eachSize,
                  fileName: file.name,
                  fullSize: file.size,
                  uid: file.uid,
                  file: fileChunks[i]
                },
                onProgress
              );
              currentChunk++;

              // 上传完一块后,进度条增加
              this.progress += this.eachProgress;
              // 不能超过100
              this.progress = this.progress > 100 ? 100 : this.progress;
            }
          }
          // this.getMd5(file, chunks);
          // var spark = new SparkMD5.ArrayBuffer();
          // spark.append(file);
          // var md5 = spark.end();
          // console.log(md5);
          const spark = new SparkMD5.ArrayBuffer();
          let currentChunkMd5 = 0;
          const that = this;
          const reader = new FileReader();
          reader.onload = async function (e) {
            spark.append(e.target.result);
            currentChunkMd5++;

            if (currentChunkMd5 < chunks) {
              loadNext();
            } else {
              // console.log(spark.end());
              var hashMd5111 = spark.end();
              that.fileMd5Keep = hashMd5111;
              console.log(that);
              console.log(hashMd5111);
              // 在这里请求接口
              await that.validateFile({
                name: file.name,
                uid: file.uid,
                md5: hashMd5111,
                chunks: fileChunks.length,
                filter_type: "git_secret_file"
                // chunk: fileChunks.length,
              });
            }
          };

          async function loadNext() {
            const start = currentChunkMd5 * eachSize;
            const end =
              start + eachSize >= file.size ? file.size : start + eachSize;
            await reader.readAsArrayBuffer(file.slice(start, end));
          }
          this.$message({
            message: "正在进行文件加密校验",
            type: "info"
          });
          await loadNext();
          // let hashMd5 = "";
          // // console.log(hashMd5)
          // const that = this;
          // console.log("进入分片上传的getHash");
          // function getHash(cb) {
          //   reader.onload = function(e) {
          //     console.log("进入分片上传的getHash的函数");
          //     const hash = SparkMD5.ArrayBuffer.hash(e.target.result);
          //     // const hash = SparkMD5.ArrayBuffer.hash(file);
          //     console.log(hash);
          //     that.hashMd5 = hash;
          //     console.log(that.hashMd5);
          //     that.fileMd5Keep = hash;
          //     cb(hash);
          //   };
          //   reader.readAsArrayBuffer(file);
          // }
          // await getHash(function() {
          //   console.log(that);
          //   that.validateFile({
          //     name: file.name,
          //     uid: file.uid,
          //     md5: that.hashMd5,
          //     chunks: fileChunks.length
          //     // chunk: fileChunks.length,
          //   });
          // });
          // 请求接口

          // console.log('fileChunks.length')
          // 请求接口
          // this.validateFile({
          //   fileName: file.name,
          //   uid: file.uid,
          //   md5:md5,
          //   chunks:1
          // });
          resolve();
        } catch (error) {
          reject(error);
        }
      });
    },
    // 断点续传
    againSplitUpload(file, array) {
      console.log("file,array");
      console.log(file);
      console.log(array);
      return new Promise(async (resolve, reject) => {
        try {
          const { eachSize, fileKeep } = this;
          const chunks = this.chunksKeep;
          const fileChunks = this.fileChunksKeep;
          this.showProgress = true;
          // let currentChunk = 0;
          for (let i = 0; i < array.length; i++) {
            // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
            // console.log(currentChunk, i);
            // 此时需要判断进度条
            // 每块上传完后则返回需要提交的下一块的index
            await this.postFile({
              chunked: true,
              chunk: array[i],
              chunks,
              name: file.name,
              fullSize: fileKeep.size,
              uid: file.uid,
              file: fileChunks[array[i]]
            });
            // currentChunk++

            // 上传完一块后,进度条增加
            // this.progress += this.eachProgress;
            // 不能超过100
            this.progress = this.progress > 100 ? 100 : this.progress;
          }
          // var spark = new SparkMD5.ArrayBuffer();
          // spark.append(fileKeep);
          // var md5 = spark.end();
          // console.log(md5);

          var fileMd5KeepTwo = this.fileMd5Keep;

          const isValidate = await this.validateFile({
            chunks: fileChunks.length,
            // chunk: fileChunks.length,
            name: file.name,
            uid: file.uid,
            md5: fileMd5KeepTwo,
            filter_type: "git_secret_file"
            // task_id:file.uid
          });
          // if (!isValidate) {
          //   throw new Error("文件校验异常");
          // }
          // 关闭进度条
          this.showProgress = false;
          // 重置进度条
          this.progress = 0;
          resolve();
        } catch (e) {
          reject(e);
        }
      });
    },
    // 文件分块,利用Array.prototype.slice方法
    splitFile(file, eachSize, chunks) {
      return new Promise((resolve, reject) => {
        try {
          setTimeout(() => {
            const fileChunk = [];
            for (let chunk = 0; chunks > 0; chunks--) {
              fileChunk.push(file.slice(chunk, chunk + eachSize));
              chunk += eachSize;
            }
            resolve(fileChunk);
          }, 0);
        } catch (e) {
          console.error(e);
          reject(new Error("文件切块发生错误"));
        }
      });
    },
    removeFile(file) {
      this.requestCancelQueue[file.uid]();
      delete this.requestCancelQueue[file.uid];
      return true;
    },
    // 提交文件方法,将参数转换为FormData, 然后通过axios发起请求
    postFile(param, onProgress) {
      // console.log(param);
      const formData = new FormData();
      // for (let p in param) {
      // formData.append(p, param[p]);
      // }
      formData.append("file", param.file); //  改了
      formData.append("uid", param.uid);
      formData.append("chunk", param.chunk);
      formData.append("filter_type", "git_secret_file");
      const { requestCancelQueue } = this;
      const config = {
        cancelToken: new axios.CancelToken(function executor(cancel) {
          if (requestCancelQueue[param.uid]) {
            requestCancelQueue[param.uid]();
            delete requestCancelQueue[param.uid];
          }
          requestCancelQueue[param.uid] = cancel;
        }),
        onUploadProgress: e => {
          if (param.chunked) {
            e.percent = Number(
              (
                ((param.chunk * (param.eachSize - 1) + e.loaded) /
                  param.fullSize) *
                100
              ).toFixed(2)
            );
          } else {
            e.percent = Number(((e.loaded / e.total) * 100).toFixed(2));
          }
          onProgress(e);
        }
      };
      // return axios.post('/api/v1/tools/upload_chunk/', formData, config).then(rs => rs.data)
      return this.$http({
        url: "/tools/upload_chunk/",
        method: "POST",

        data: formData
        // config
      }).then(rs => rs.data);
    },
    // 文件校验方法
    validateFile(file) {
      // return axios.post('/api/v1/tools/upload_chunk/', file).then(rs => rs.data)
      return this.$http({
        url: "/tools/upload_chunk/upload_success/",
        method: "POST",
        data: file
      }).then(res => {
        if (res && res.status == 1) {
          this.againSplitUpload(file, res.data.error_file);
          this.$message({
            message: "有文件上传失败,正在重新上传",
            type: "warning"
          });
        } else if (res && res.status == 0) {
          this.$message({
            message: "上传成功",
            type: "success"
          });
          this.showProgress = false;
          this.progress = 0;
        } else if (res && res.status == 40008) {
          this.$message.error(res.message);
          this.showProgress = false;
          this.progress = 0;
        }
      });
    }
  }
};
</script>
<style scoped>
.loading {
  /* 整体页面置灰 */
  /* background: rgba(0, 0, 0, 0.5); */
}

.progress {
  /* 在当前页面居中 */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin-top: 40px;
  /* 宽度 */
}

/deep/ .el-dialog {
  position: relative;
  height: 500px;
}
</style>

추천

출처blog.csdn.net/yjxkq99/article/details/128942133