【Minio上传】-大文件分片上传、断点续传✍️

一、背景

  • 日常业务中难免出现前端需要向后端传输大型文件的情况,这时单次的请求不能满足传输大文件的需求,就需要用到分片上传

  • 前端:vue2.x+Element组件+axios

二、流程图

三、技术准备

  1. 主要技术

1.el-upload、axious 、 vue2

四、选择需要上传的文件

1.文件类型筛选

  • 本身minio是没有文件类型筛选,文件大小和数量以及多文件这些功能;这些其实是在input上设置实现accept来实现的;文件的file对应的支持方式为下面几种:

 imgAcceptOption: [
        //4图片:png、jpg、bmp、jpeg
        'image/png',
        'image/x-png',
        'image/jpeg',
        'image/jpg',
        'image/pjpeg',
        'image/webp',
        'image/bmp',
      ],
      textAcceptOption: [
        //2文件: txt、json、xml、dat
        'text/plain',
        'application/json ',
        'text/xml',
        'application/xml',
        '.dat',
      ],
      tableAcceptOption: [
        //3表格:xls、xlsx、csv
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'application/vnd.ms-excel',
        'application/msexcel',
        '.xls',
        '.xlsx',
        'text/csv',
      ],
      folderAcceptOption: ['.zip', 'application/zip', '.7z', '.rar'], //1文件夹zip rar

2.自定义上传方式,不采用el-upload自带的上传方式

<el-upload
          ref="uploadRef"
          class="upload-demo"
          :accept="currentAcceptOption.toString()"
          action="/"
          multiple
          :limit="100"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="handleFileChange"
        >
          <div class="add-file-btn">
            <span>添加文件上传</span>
            <i class="upload-icon"></i>
          </div>
        </el-upload>

五、获取预签名

  1. 前端支持批量上传,并将获取的文件进行获取,然后自动请求预签名接口;

  2. 后端生成presigned url(预签名url,里面包含上传到AWS S3所需要的一些认证标识信息)以及每个的partSize和currentNumber给到前端,前端通过这个URL,以及大小将文件分片上传到minio服务上,

  3. 具体切片处理:

     const start = Number(partSize) * (partItem.currentNumber - 1);
     const end = start + Number(partSize);
     const blob = fileList[filesIndex].raw.slice(start, end); //切片后的文件
  4. 循环请求模块的代码,代码上传完成后根据请求的requist的coinfig里面的链接参数中的partNumber来判断当前片段是否成功:

// 判断链接
const queryURLParams = (url) => {
  if (!url) {
    return;
  }
  const askIn = url.indexOf('?');
  let wellIn = url.indexOf('#');
  let askText = '';
  let wellText = '';
  // #不存在
  wellIn === -1 ? (wellIn = url.length) : null;
  // ?存在
  askIn >= 0 ? (askText = url.substring(askIn + 1, wellIn)) : null;
  wellText = url.substring(wellIn + 1);
  const result = {};
  wellText !== '' ? (result['HASH'] = wellText) : null;
  if (askText !== '') {
    const ary = askText.split('&');
    ary.forEach((item) => {
      const aryText = item.split('=');
      result[aryText[0]] = aryText[1];
    });
  }
  return result.partNumber;
};

     5.表格合并

  1.  // 进行中的文件和已经成功的记录进行表格合并去重
        mergeTable(fileList) {
          try {
            this.upList = [];
            fileList.forEach((e) => {
              this.upList.push({
                fileName: e.name,
                fileSize: e.size,
                fileSizeString: this.formatFileSize(e.size),
                fileStatus: e.status,
                uid: e.uid,
                fileType: e.type || this.getBit(e.name),
                percent: e.percent,
                isComplete: -1,
              });
            });
            // 缓存列表
            this.cacheCurrentUploadList = [
              ...this.cacheCurrentUploadList,
              ...fileList,
            ];
            this.tableData = this.unique([...this.upList, ...this.tableData]);
          } catch (e) {
            console.log(e);
          }
        },

    6.axios循环请求预签名

 // 获取预签名接口
    async handleHttpRequest(fileList, paramsDataSetFilesArr) {
      this.$refs.uploadRef.uploadFiles = [];
      const params = {
        dataSetFiles: paramsDataSetFilesArr,
        datasetId: this.currentDatasetId,
      };
      const { code, data, msg } = await preSignUrlFileUploadCheck(params);
      if (code === 200) {
        // 空间状态spaceStatus: 0、剩余空间足够支撑上传 1、剩余空间不支持上传 2、 文件类型校验失败
        if (data.spaceStatus === 0) {
          this.mergeTable(fileList); //当前的表格
          data.fileInfos.forEach((filesItem, filesIndex) => {
            // 文件检查状态checkStatus: 0、需要上传 1、已存在,秒上传 2、失败
            if (filesItem.checkStatus === 1) {
              this.fileUploadCompleteConfirm(filesItem, true);
            } else {
              // 依据预签名列表,分片上传
              const { partSize } = filesItem;
              const bathSuccessPartNumberList = [];
              filesItem.filePartInfos &&
                filesItem.filePartInfos.forEach(async (partItem) => {
                  const start = Number(partSize) * (partItem.currentNumber - 1);
                  const end = start + Number(partSize);
                  const blob = fileList[filesIndex].raw.slice(start, end);
                  await axios
                    .request({
                      url: partItem.fileUrl,
                      method: 'PUT',
                      data: blob,
                      headers: {
                        'Content-Type': 'application/octet-stream',
                      },
                    })
                    .then((response) => {
                      const obj = {};
                      obj[`${queryURLParams(response.config.url)}`] = true;
                      bathSuccessPartNumberList.push(obj);
                      //   上传完成
                      if (
                        bathSuccessPartNumberList.length ===
                        filesItem.filePartInfos.length
                      ) {
                        this.fileUploadCompleteConfirm(filesItem, true);
                      } else {
                        this.tableData.find((item, index) => {
                          if (item.fileName === filesItem.fileName) {
                            this.tableData[index].percent = (
                              (100 / Number(filesItem.filePartInfos.length)) *
                              (index + 1)
                            ).toFixed(0);
                            this.$refs.tableRef.doLayout();
                          }
                        });
                      }
                    })
                    .catch((error) => {
                      console.log(error);
                      //   this.fileUploadCompleteConfirm(filesItem, false);
                    });
                });
            }
          });
        } else if (data.spaceStatus === 1) {
          this.$message.error('剩余空间不支持上传');
        } else if (data.spaceStatus === 2) {
          this.$message.error('文件类型校验失败');
        }
      } else {
        this.$message.error(msg || '服务异常~');
      }
    },

  

六、完成上传合并

 // 文件上传成功确认
    async fileUploadCompleteConfirm(options, isSuccess) {
      try {
        const meargeParams = {
          fileName: options.fileName,
          datasetId: this.currentDatasetId,
          isSuccess: isSuccess,
        };
        const { code, msg } = await fileUploadCompleteConfirm(meargeParams);
        if (code === 200) {
          this.$refs.uploadRef.uploadFiles = [];
          this.tableData.find((item, index) => {
            if (item.fileName === options.fileName) {
              this.tableData[index].isComplete = 1;
              this.$refs.tableRef.doLayout();
            }
          });
          this.queryPageByDatasetId();
          this.$refs.uploadRef.uploadFiles = [];
        } else {
          this.$message.error(msg || '文件上传错误');
        }
      } catch (e) {
        console.log(e);
      }
    },

七、总结

猜你喜欢

转载自blog.csdn.net/weixin_44171297/article/details/128294252