ant-design-vue 文件分块、控制并发、重试上传

<template>
  <div>
    <a-upload
      :accept="accept"
      :beforeUpload="beforeUpload"
      :disabled="disabled"
      :fileList="fileList"
      :list-type="listType"
      :multiple="true"
      :remove="handleImageRemove"
      @change="handleChange"
    >
      <a-button :disabled="disabled">
        <a-icon type="upload" />
        {{ text }}
      </a-button>
    </a-upload>
  </div>
</template>
<script>
import md5 from "js-md5";
import {
  getCheckFile,
  getChunkFile,
  getUploadPart,
  getMergeFile,
  getCheckCosFile,
  getChunkCosFile,
  getCosUploadPart,
  getMergeCosFile
} from "@/api/common";
export default {
  name: "ChuckUploadComponent",
  props: {
    isCosType: { type: Boolean, required: false, default: false },
    disabled: { type: Boolean, required: false, default: false },
    count: { type: Number, required: false, default: 1 },
    uploadLists: {
      type: Array,
      required: false,
      default: () => {
        return [];
      }
    },
    listType: { type: String, required: false, default: "text" },
    text: { type: String, required: false, default: "选择文件" },
    accept: {
      type: String,
      required: false,
      default: "*"
    },
    ext: {
      type: Array,
      required: false,
      default: () => {
        return []; // ['image/jpeg', 'image/jpg'];
      }
    }
  },
  data() {
    return {
      fileList: [],
      tempThreads: 5,
      chunkRetry: 4,
      chunkSize: 5 * 1024 * 1024
    };
  },
  created() {},
  watch: {
    uploadLists(value, oldValue) {
      this.fileList = value;
    }
  },
  methods: {
    beforeUpload(file) {
      this.fileList = [...this.fileList, file];
      file.status = "uploading";
      this.checkFile(file);
      return false;
    },
    // 判断文件是否上传过,获取fileId
    checkFile(file) {
      const md5 = this.getFileMD5(file);
      file.md5 = md5;
      const formData = new FormData();
      formData.append("fileMd5", md5);
      formData.append("fileName", file.name);
      if (this.isCosType) {
        formData.append("fileSize", file.size);
      }
      const http = this.isCosType
        ? getCheckCosFile(formData)
        : getCheckFile(formData);
      http.then(async res => {
        if (res.code === 0) {
          if (res.data.status === 1) {
            // 文件存在
            if (res.data.fileId) {
              file.fileId = res.data.fileId;
              this.fileList.forEach(item => {
                if (item.md5 === md5) {
                  item.status = "done";
                  this.$emit("fileId", res.data.fileId);
                  this.$message.success(item.name + " 文件上传成功!");
                }
              });
            } else {
              // console.log(`fileId不存在`)
              this.$message.error(res.msg);
            }
          } else {
            // 文件不存在或不完整,发送该文件
            await this.uploadChunks(file);
          }
        }
      });
    },
    // 根据文件大小,分配上传分片大小
    updateCchunkSize(file) {
      if (file.size > 2000 * 1024 * 1024) {
        this.chunkSize = 1024 * 1024 * 15;
      } else if (file.size > 1000 * 1024 * 1024) {
        this.chunkSize = 1024 * 1024 * 10;
      } else if (file.size > 500 * 1024 * 1024) {
        this.chunkSize = 1024 * 1024 * 8;
      } else {
        this.chunkSize = 2 * 1024 * 1024;
      }
    },
    createFileChunk(file, size) {
      const fileChunkList = [];
      let count = 0;
      while (count < file.size) {
        fileChunkList.push({
          file: file.slice(count, count + size)
        });
        count += size;
      }
      return fileChunkList;
    },
    async uploadChunks(file) {
      this.updateCchunkSize(file);
      const fileChunkList = this.createFileChunk(file, this.chunkSize);
      file.chunkList = fileChunkList.map(({ file }, index) => ({
        index,
        source: file,
        size: file.size
      }));
      var chunkData = file.chunkList;
      return new Promise((resolve, reject) => {
        const requestDataList = chunkData.map(value => {
          const formData = new FormData();
          formData.append("fileMd5", file.md5);
          formData.append("chunk", value.index);
          formData.append("file", value.source);
          return { formData, index: value.index, md5: file.md5, file };
        });
        try {
          const ret = this.sendRequest(requestDataList);
          // console.log('上传结束,参数:', ret, chunkData, file.md5)
          resolve(ret);
        } catch (error) {
          this.$message.error("上传失败,请重试");
          reject("sendRequest 失败", error);
        }
      }).then(async res => {
        if (res == file.md5) {
          // console.log('--- ' + file.name + ' 文件开始合并----')
          await this.mergeRequest(file.md5);
        }
      });
    },
    // 并发,重试请求
    async sendRequest(list) {
      var finished = 0;
      const retryArr = []; // retryArr.length代表请求数,值代表重试次数
      var currentFileInfo;
      const total = list.length;
      // 所有请求都存放这个promise中
      return new Promise((resolve, reject) => {
        const handler = () => {
          if (list.length) {
            const formInfo = list.shift();
            const index = formInfo.index;
            const http = this.isCosType
              ? getChunkCosFile(formInfo.formData)
              : getChunkFile(formInfo.formData);
            http
              .then(res => {
                // if (res.code === 0) {
                //   //分片存在,跳过. 待测试
                //   finished++;
                //   handler();
                // } else {
                const http = this.isCosType
                  ? getCosUploadPart(formInfo.formData)
                  : getUploadPart(formInfo.formData);
                // 分块不存在或不完整,重新发送该分块内容
                http
                  .then(res => {
                    if (res.code === 0) {
                      // const date = new Date();
                      // console.log(formInfo.file.name + 'UploadPart', formInfo, date.getTime(), res)
                      currentFileInfo = formInfo;
                    }
                    return res;
                  })
                  .then(res => {
                    if (res.code === 0) {
                      finished++;
                      handler();
                    } else {
                      this.$message.error(res.msg);
                    }
                  })
                  .catch(e => {
                    if (typeof retryArr[index] !== "number") {
                      retryArr[index] = 1;
                    }
                    if (retryArr[index] >= this.chunkRetry) {
                      return reject("重试失败", retryArr);
                    }
                    // console.log(`${formInfo.file.name}--文件的第 ${index} 个分块,开始进行第 ${retryArr[index]} 次重试`);
                    retryArr[index]++; // 累加
                    this.tempThreads++; // 释放当前占用的通道
                    list.push(formInfo); // 将失败的重新加入队列
                    handler();
                  });
                // }
              })
              .catch(e => {
                this.$message.error("上传失败,请重试");
              });
          }
          if (finished >= total) {
            resolve(currentFileInfo.md5); // 输出当前完成上传的文件信息
          }
        };
        // 控制并发
        for (let i = 0; i < this.tempThreads; i++) {
          handler();
        }
      });
    },
    async mergeRequest(fileMd5) {
      return new Promise((resolve, reject) => {
        const mergeFormData = new FormData();
        mergeFormData.append("fileMd5", fileMd5);
        const http = this.isCosType
          ? getMergeCosFile(mergeFormData)
          : getMergeFile(mergeFormData);
        http
          .then(res => {
            if (res.code === 0) {
              this.fileList.forEach(item => {
                if (item.md5 === fileMd5) {
                  item.status = "done";
                  item.fileId = res.data.fileId;
                  this.$emit("fileId", item.fileId);
                  this.$message.success(item.name + " 文件上传成功!");
                  // console.log(item.name + ' 文件上传成功!')
                  resolve("sucess");
                }
              });
            } else {
              this.$message.error(res.msg);
            }
          })
          .catch(err => {
            // console.log('mergeRequest -> err', err);
            reject("合并失败", err);
          });
      });
    },
    getFileMD5(file) {
      return md5(file.name + file.size + file.lastModifiedDate);
      // return md5(file.name + file.size + file.lastModifiedDate + Math.random(1000) + new Date().getTime());
      // const reader = new FileReader();
      // reader.onload = function (e) {
      //   file.fileMD5 = md5(e.target.result);
      //   console.log(e.target.result);
      // }
      // reader.readAsText(file);
    },
    handleChange(info) {
      this.$set(this, "fileList", info.fileList);
      const status = info.file.status;
      if (status === "done") {
        this.$message.success("上传成功");
      } else if (status === "uploading") {
        // this.$message.info(`${info.file.name} 上传中...`);
      } else if (status === "error") {
        this.$message.error(`${info.file.name} 上传失败`);
      }
    },
    handleImageRemove(res) {
      this.fileList.forEach((item, idx) => {
        if (item.uid === res.uid) {
          this.fileList.splice(idx, 1);
          this.$message.success("删除成功");
        }
      });
      this.$emit("getFileList", this.fileList);
    }
  }
};
</script>
<style>
/* 隐藏预览功能 */
span.ant-upload-list-item-actions a {
  display: none;
}
</style>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容