Vue は、ブレークポイントの再開とアップロードの進行状況バーを含む、大きなファイルの断片化されたアップロードを実装します

最初にマルチパート アップロードとは何かを説明します

        部分アップロードとは、大きなファイルをいくつかに分割して、1つずつ送信することです。これには、再アップロードのオーバーヘッドを削減できるという利点があります。たとえば、アップロードするファイルが大きなファイルの場合、アップロード時間は比較的長く、ネットワークの不安定性のさまざまな要因の影響と相まって、送信が中断されやすく、ユーザーには他に選択肢がありません。ファイルの方法で再アップロードしますが、マルチパートアップロードを使用してこの問題を解決できます。フラグメントアップロード技術により、ネットワーク伝送が中断された場合、残りのフラグメントをアップロードしてファイルを再選択するだけで済みます。ファイル全体を再送信する必要がないため、再送信のオーバーヘッドが大幅に削減されます。

しかし、適切なシャードをどのように選択すればよいでしょうか? したがって、次のことを考慮する必要があります。

1. シャードが小さいほど、必要なリクエストが多くなり、オーバーヘッドが大きくなります。したがって、あまり小さく設定することはできません。

2. シャードが大きくなるほど、柔軟性が低下します。

3. サーバーには固定サイズの受信バッファがあります。スライスのサイズは、この値の整数倍であることが好ましい。

マルチパート アップロードの手順

1. 最初にmd5でファイルを暗号化しますmd5 暗号化を使用する利点は、ファイルを一意に識別できることと、バックグラウンドでファイルの整合性検証のために比較できることです。

2. md5 値を取得した後、サーバーはファイルがアップロード済みかどうかを確認します. アップロード済みの場合は、再度アップロードする必要はありません.

3. 大きなファイルを分割します。たとえば、100M ファイルの場合、フラグメントの 1 つが 5M の場合、このファイルは 20 回でアップロードできます。

4. バックグラウンドからインターフェイスをリクエストします。インターフェイス内のデータは、アップロードしたファイル ブロックです。(注: なぜこのリクエストを送信する必要があるのですか?ブレークポイントでアップロードを続行できるようにするためです。たとえば、Baidu のネットワーク ディスクを使用しますよね? サーバーは、以前にアップロードしたファイル ブロックを記憶している必要があります。再アップロードするためにコンピュータ上でアップロードした場合、以前にアップロードしたファイル ブロックをスキップし、その後のブロックをアップロードする必要があります)。

5. アップロードされていないファイル ブロックのアップロードを開始します。(これは 2 番目のリクエストで、すべてのフラグメントをマージしてリクエストをアップロードします)。

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
おすすめ