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