The front end realizes the function of uploading large files in pieces, resuming uploads at breakpoints, and uploading in seconds vue2 version

Now we need to upload files larger than 1G, so we consider the need for functions such as slicing and resuming uploads, which need to cooperate with the backend friendly. . .

Go directly to the code! ! ! !

Technology stack implementation: vue2 element minio springboot

 <div v-show="uploadLoading" style="width: 100%">
      <div class="progress-block">
        <el-progress
          :text-inside="true"
          :stroke-width="16"
          :percentage="percentage"
        ></el-progress>
      </div>
    </div>

    <el-upload
      :action="uploadUrl"
      :accept="accept"
      :limit="limit"
      :on-exceed="handleExceed"
      :before-upload="beforeUpload"
      :headers="headers"
      :show-file-list="false"
      :http-request="customHttpRequest" //Custom upload method
      :file-list="fileList"
      class="upload-demo"
      :multiple="multiple"
      ref="batchUpload"
      :disabled="disabled"
    />
     <slot name="filePreviewComponent"></slot>
    <draggable
      class="upload-list"
      v-model="fileArr"
      filter=".forbid"
      animation="300"
      @end="onMoveEnd"
    >
       <!-- Customized attachment list -->
      <transition-group>
        <div
          v-for="(item, index) in fileArr"
          :key="index"
          class="upload-list-item"
        >
          <div :title="item.originalName" class="upload-list-item-title">
            <img
              style="margin-right: 5px"
              :src="showTypeTip(item)"
              width="15px"
              height="18px"
              alt=""
            />
            <span>{<!-- -->{<!-- --> item.originalName }}</span>
          </div>
          <div class="upload_options">
            <span class="preview-class" @click="preview(item)">
              <i class="el-icon-view"></i>
              preview</span
            >
          </div>
        </div>
      </transition-group>
    </draggable>
    <script>
    //The upload logic is mixed in with mixins to facilitate the reuse of other accessory components
    import {<!-- --> webUploaderMixin } from "@/components/upload/webUploader.js";
    export default {<!-- -->
        mixins: [webUploaderMixin],
    }
    </script>

webUploaderMixin.js

// Related dependencies need to be downloaded
import md5 from "@/utils/md5.js"; // Calculate the md5 of the file
import axios from 'axios'
import Queue from 'promise-queue-plus';
import {<!-- --> ref } from 'vue'
// File upload block task queue (used to stop the upload queue of the file when removing the file) key: fileUid value: queue object
import {<!-- --> getTaskInfoUpload, initTaskUpload, preSignUrlUpload, preMergeUpload } from '@/api/webUploader.js';
export const webUploaderMixin = {<!-- -->
  data() {<!-- -->
    return {<!-- -->
      fileUploadChunkQueue: {<!-- -->}
    }
  },
  methods: {<!-- -->
    /**
     * el-upload custom upload method entry
     */
    async customHttpRequest(options) {<!-- -->
      const file = options. file;
      const task = await this.getTaskInfo(file); //First match the md5 of the file, check whether the md5 exists, if it exists, you can add it directly
      if (task) {<!-- -->
        const {<!-- --> finished, path, taskRecord, attach } = task
        const {<!-- --> fileIdentifier: identifier } = taskRecord
        //If it has been uploaded before, add it directly to the attachment list
        if (finished) {<!-- -->
          let attachResponse = {<!-- -->
            code: 200,
            data: attach
          }
          //The attachment has been uploaded before, and the value can be assigned directly in seconds
          this. handleSuccess(attachResponse)
          return path
        } else {<!-- -->
          const errorList = await this. handleUpload(file, taskRecord, options)
          if (errorList. length > 0) {<!-- -->
            this.msgError("File upload error");
            return;
          }
          // const { code, data, msg } =
          let upLoadRes = await preMergeUpload(identifier)
          if (upLoadRes.code === 200) {<!-- -->
            //upload completed
            this. handleSuccess(upLoadRes)
            return path;
          } else {<!-- -->
            this.msgError("File upload error");
          }
        }
      } else {<!-- -->
        this.msgError("File upload error");
      }
    },
    /**
     * Upload logic processing, if the file has been uploaded (completed block merge operation), it will not enter this method
     */
    handleUpload(file, taskRecord, options) {<!-- -->
      let lastUploadedSize = 0; // The total size uploaded at the last resuming upload
      let uploadedSize = 0 // uploaded size
      const totalSize = file.size || 0 // total file size
      let startMs = new Date().getTime(); // time to start uploading
      const {<!-- --> exitPartList, chunkSize, chunkNum, fileIdentifier } = taskRecord

      // Get the average upload speed from the beginning to the present (byte/s)
      const getSpeed = () => {<!-- -->
        // The total size of the upload - the total size of the last upload (resume upload) = the total size of this upload (byte)
        const intervalSize = uploadedSize - lastUploadedSize
        const nowMs = new Date().getTime()
        // time interval (s)
        const intervalTime = (nowMs - startMs) / 1000
        return intervalSize / intervalTime
      }
      const uploadNext = async (partNumber) => {<!-- -->
        const start = new Number(chunkSize) * (partNumber - 1)
        const end = start + new Number(chunkSize)
        const blob = file. slice(start, end)
        const {<!-- --> code, detailMsg, msg } = await preSignUrlUpload({<!-- --> identifier: fileIdentifier, partNumber: partNumber })
        if (code === 200 & amp; & amp; detailMsg) {<!-- -->
          await axios.request({<!-- -->
            url: detailMsg,
            method: 'PUT',
            data: blob,
            headers: {<!-- --> 'Content-Type': 'application/octet-stream' }
          })
          return Promise. resolve({<!-- --> partNumber: partNumber, uploadedSize: blob. size })
        }
        return Promise.reject(`Shard ${<!-- -->partNumber}, failed to get the upload address`)
      }

      /**
       * Update upload progress
       * @param increment The amount of bytes added to the progress of the upload
       */
      const updateProcess = (increment) => {<!-- -->
        increment = new Number(increment)
        const {<!-- --> onProgress } = options
        let factor = 1000; // increase by 1000 byte each time
        let from = 0;
        // Increase the progress bit by bit through the loop
        while (from <= increment) {<!-- -->
          from + = factor
          uploadedSize += factor
          //Compare the percentage with 100 and take the smaller value to update the progress
          const percent = Math.min((100, Number(Math.round(uploadedSize / totalSize * 100))))
          this.percentage = percent ? percent : 0

          onProgress({<!-- --> percent: percent })
        }

        const speed = getSpeed();
        const remainingTime = speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : 'unknown'
        console.log('remaining size:', (totalSize - uploadedSize) / 1024 / 1024, 'mb');
        console.log('current speed:', (speed / 1024 / 1024).toFixed(2), 'mbps');
        console.log('expected completion:', remainingTime);
      }

      return new Promise(resolve => {<!-- -->
        const failArr = [];
        const queue = Queue(5, {<!-- -->
          "retry": 3, //Number of retries
          "retryIsJump": false, //retry now?
          "workReject": function (reason, queue) {<!-- -->
            failArr. push(reason)
          },
          "queueEnd": function (queue) {<!-- -->
            resolve(failArr);
          }
        })
        // console.log("queue:::", queue);
        this.fileUploadChunkQueue[file.uid] = queue
        this. uploadLoading = true
        for (let partNumber = 1; partNumber <= chunkNum; partNumber ++ ) {<!-- -->

          const exitPart = (exitPartList || []).find(exitPart => exitPart.partNumber == partNumber)
          if (exitPart) {<!-- -->
            // The fragments have been uploaded and accumulated into the total amount of uploads. At the same time, record the size of the last breakpoint upload, which is used to calculate the upload speed
            lastUploadedSize + = new Number(exitPart. size)
            updateProcess(exitPart. size)
          } else {<!-- -->
            queue.push(() => uploadNext(partNumber).then(res => {<!-- -->
              // Update the upload progress after the single file upload is complete
              updateProcess(res.uploadedSize)
            }))
          }
        }
        if (queue. getLength() == 0) {<!-- -->
          // All fragments have been uploaded, but have not been merged, return directly to perform the merge operation
          resolve(failArr);
          return;
        }
        queue. start()
      })
    },
    /**
     * Get an upload task, if not, initialize one
     */
    async getTaskInfo(file) {<!-- -->
      let task;

      const identifier = await md5(file);
      const {<!-- --> code, data, msg } = await getTaskInfoUpload(identifier);
      if (code === 200) {<!-- -->
        task = data;
        if (!task) {<!-- -->
          const initTaskData = {<!-- -->
            identifier,
            fileName: file.name,
            totalSize: file. size,
            chunkSize: 10 * 1024 * 1024,
          };
          const {<!-- --> code, data, msg } = await initTaskUpload(initTaskData);
          if (code === 200) {<!-- -->
            task = data;
          } else {<!-- -->
            this.msgError("File upload error");
          }
        }
      } else {<!-- -->
        this.msgError("File upload error");
      }
      return task;
    },
       // upload successfully
    handleSuccess(response, file, fileList) {<!-- -->
      if (response.code === 200) {<!-- -->
        setTimeout(() => {<!-- -->
          this. uploadLoading = false;
        }, 800);
        let item = response.data;
        this.$emit("update:approvalFileList", [...this.fileArr, item]);
        if (this.isAllowReturnSuccessEmit) {<!-- -->
          this.$emit("uploadBatchSuccess", item);
        }
      } else {<!-- -->
        fileList. pop();
      }
      this.loading = false;
    },
  },
};

md5.js

import SparkMD5 from 'spark-md5'
import {<!-- --> Loading } from 'element-ui';
const DEFAULT_SIZE = 20 * 1024 * 1024
const md5 = (file, chunkSize = DEFAULT_SIZE) => {<!-- -->
  return new Promise((resolve, reject) => {<!-- -->
    const startMs = new Date().getTime();
    const loading = Loading. service({<!-- -->
      lock: true,
      text: 'The system is processing, please wait! ',
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.7)'
    });
    let blobSlice =
      File.prototype.slice||
      File.prototype.mozSlice||
      File.prototype.webkitSlice;
    let chunks = Math. ceil(file. size / chunkSize);
    // console.log("file.size::: ", file.size);
    let currentChunk = 0;
    let spark = new SparkMD5.ArrayBuffer(); //Add array buffer.
    let fileReader = new FileReader(); //read the file
    fileReader.onload = function (e) {<!-- -->
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) {<!-- -->
        loadNext();
      } else {<!-- -->
        const md5 = spark.end(); //Complete the calculation of md5 and return the hexadecimal result.
        console.log('The file md5 calculation is over, the total time spent:', (new Date().getTime() - startMs) / 1000, 's')
        loading. close()
        resolve(md5);

      }
    };
    fileReader.onerror = function (e) {<!-- -->
      loading. close()
      reject(e);
    };

    function loadNext() {<!-- -->
      console.log('current part number:', currentChunk, 'total block number:', chunks);
      let start = currentChunk * chunkSize;
      let end = start + chunkSize;
      (end > file. size) & amp; & amp; (end = file. size);
      fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
    }
    loadNext();
  });
}

export default md5

Interface files can refer to the link below

The original blogger implemented the vue3 elementplus minio springboot code. I changed it to the vue2 + element version for reference according to my own needs. There are also back-end codes in it, which can be copied directly

Reference address: https://gitee.com/Gary2016/minio-upload