<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>
