国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

這篇具有很好參考價值的文章主要介紹了springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

網(wǎng)上關(guān)于minio分片上傳的資料不太詳細(xì),缺斤少兩,所以我基于他們的代碼做了一些修改,demo能夠正常運行起來,但是偶爾也會發(fā)生一些小bug,不過這些都無傷大雅,最終目的是理解代碼背后的邏輯和流程

流程:

  1. 前端獲取生成文件MD5,發(fā)送至后臺判斷是否有該文件緩存,有信息終止上傳,無則開始進行文件分片? 。這里,我為了簡單方便實現(xiàn)便沒有使用數(shù)據(jù)庫,直接用redis存儲文件信息;
  2. 前端后端返回的結(jié)果進行分片,然后將文件分片的信息傳輸給后端,后端調(diào)用 minio 初始化,返回分片上傳地址和 uploadId;
  3. 前端則根據(jù)獲取的分片上傳地址直接通過axios上傳分片文件,不走后端;
  4. 上傳完成后,前端發(fā)送請求至后端,后端調(diào)用 minio 合并文件;

流程圖:

springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

效果圖

? 1.vue前端

springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

2. minio文件桶

springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)

一.前端vue代碼(代碼較多,我就分開貼)

?項目中使用到的類庫:spark-md5、axios、element-ui;

spark-md5?主要用來計算文件MD5,安裝命令:

npm install spark-md5 --S
npm install axios --S

? ?1.template?

<template>
    <div class="container">

        <h2>上傳示例</h2>

        <h4> 文件上傳任務(wù)數(shù):{{ taskQueueRunningNum }} </h4>

        <el-upload class="upload-demo" ref="upload" :on-remove="handleRemove" :on-change="handleFileChange"
            :file-list="uploadFileList" :show-file-list="false" :auto-upload="false" multiple>
            <template #trigger>
                <el-button type="primary" plain>選擇文件</el-button>
            </template>

            <el-button style="margin-left: 5px;" type="success" @click="handlerPlus"
                :disabled="uploadDisabled">上傳</el-button>

            <el-button type="danger" @click="clearFileHandler">清空</el-button>

            <el-button :type="isPaused ? 'success' : 'danger'" :disabled="taskQueueRunningNum == 0"
                @click="continueOrPauseUpload">{{
                    isPaused ? '繼續(xù)' : '暫停'
                }}</el-button>

        </el-upload>

        <!-- 文件列表 -->
        <div class="file-list-wrapper">

            <el-collapse>
                <el-collapse-item v-for="(item, index) in uploadFileList" :key="index" :name="index">
                    <template #title>

                        <el-row style="width:800px " type="flex" align="middle">

                            <el-col :span="9">
                                <div class="file-name" :title="item.name">{{ item.name }}</div>

                            </el-col>

                            <el-col :span="3">
                                <div class="file-size">{{ transformByte(item.size) || item.size }}</div>
                            </el-col>

                            <el-col :span="6">
                                <el-progress :percentage="item.uploadProgress" />
                            </el-col>

                            <el-col :span="3">
                                <div class="file-size">{{ `${item.uploadSpeed ? item.uploadSpeed : 0} M/s` }}</div>
                            </el-col>

                            <el-col :span="3">
                                <div>
                                    <el-tag v-if="item.status === '等待上傳'" size="default" type="info">等待上傳</el-tag>
                                    <el-tag v-else-if="item.status === '校驗MD5'" size="default" type="warning">校驗MD5</el-tag>
                                    <el-tag v-else-if="item.status === '正在創(chuàng)建序列'" size="default"
                                        type="warning">正在創(chuàng)建序列</el-tag>
                                    <el-tag v-else-if="item.status === '正在上傳'" size="default">正在上傳</el-tag>
                                    <el-tag v-else-if="item.status === '上傳成功'" size="default" type="success">上傳完成</el-tag>
                                    <el-tag v-else size="default" type="danger">上傳錯誤</el-tag>
                                </div>
                            </el-col>

                        </el-row>

                    </template>

                    <div class="file-chunk-list-wrapper">
                        <el-table :data="item.chunkList" max-height="400" style="width: 100%">
                            <el-table-column prop="chunkNumber" label="分片序號" width="180">
                            </el-table-column>
                            <el-table-column prop="progress" label="上傳進度">
                                <template v-slot="{ row }">
                                    <el-progress v-if="!row.status || row.progressStatus === 'normal'"
                                        :percentage="row.progress" />
                                    <el-progress v-else :percentage="row.progress" :status="row.progressStatus"
                                        :text-inside="true" :stroke-width="14" />
                                </template>
                            </el-table-column>
                            <el-table-column prop="status" label="狀態(tài)" width="180">
                            </el-table-column>
                        </el-table>
                    </div>

                </el-collapse-item>
            </el-collapse>
        </div>
    </div>
</template>

2.scirpt

<script>

import { reactive } from 'vue';

import { checkUpload, initUpload, mergeUpload, fileIsExits } from "./upload";

import SparkMD5 from 'spark-md5'

import axios from 'axios'

const chunkSize = 10 * 1024 * 1024

// 用于axios請求的取消
const CancelToken = axios.CancelToken;
let source = CancelToken.source();

const FileStatus = {
    wait: '等待上傳',
    getMd5: '校驗MD5',
    chip: '正在創(chuàng)建序列',
    uploading: '正在上傳',
    success: '上傳成功',
    error: '上傳錯誤'
}

export default {
    data() {
        return {
            changeDisabled: false,
            uploadDisabled: false,
            // 上傳并發(fā)數(shù)
            currentFileIndex: 0,

            maxConcurrency: 3,
            uploadIdInfoList: reactive([]),
            uploadFileList: reactive([]),
            isPaused: false, // 暫停true 繼續(xù)false

            taskQueue: null, // 上傳任務(wù)隊列
        }
    },

    computed: {
        taskQueuePaused() {
            return this.taskQueue ? this.taskQueue.isEmpty() : true
        },

        taskQueueRunningNum() {
            return this.taskQueue ? this.taskQueue.isRunning() : 0
        },

    },
    created() {
        window.mydata = this
    },

    methods: {

        async handlerPlus() {
            // 創(chuàng)建一個允許同時執(zhí)行3個任務(wù)的任務(wù)隊列
            this.taskQueue = new TaskQueue(this.maxConcurrency);

            for (let i = 0; i < this.uploadFileList.length; i++) {
                let file = this.uploadFileList[i]
                this.taskQueue.push({
                    name: file.name,
                    task: () => this.handler(file)
                }); // 將任務(wù)加入隊列
            }

        },


        /**
         * 暫停上傳文件
         */
        async continueOrPauseUpload() {

            const self = this;

            // 檢查上傳是否正在進行
            if (self.isPaused) {

                self.isPaused = false

                // 過濾出已暫停上傳的文件
                let pausedFileList = self.uploadFileList.filter(item => item.uploadProgress < 100 && item.chunkList.length > 0);

                console.log("執(zhí)行未完成的文件-->", pausedFileList)

                for (let i = 0; i < pausedFileList.length; i++) {
                    let file = pausedFileList[i]
                    // 將任務(wù)加入隊列
                    self.taskQueue.pushPauseQueue({
                        name: file.name,
                        task: () => self.handler(file)
                    });
                }

                self.taskQueue.resume()

            } else {
                try {

                    self.taskQueue.pause();

                    source.cancel('中斷上傳!');

                    source = CancelToken.source();

                } catch (err) { }

                self.isPaused = true
            }

        },


        /**
         * 開始上傳文件
         */
        handler(currentFile) {
            const self = this;
            const paused = async () => {
                // 如果上傳被暫停,則等待重新開始上傳
                await new Promise((resolve) => {
                    const interval = setInterval(() => {
                        if (!self.isPaused) {
                            clearInterval(interval);
                            resolve();
                        }
                    }, 1000);
                });
            }

            paused()

            // 判斷文件列表是否為空
            if (self.uploadFileList.length === 0) {
                self.$message.error('請先選擇文件');
                return;
            }

            if (!currentFile) {
                self.uploadDisabled = false;
                return;
            }

            self.uploadDisabled = true;

            return new Promise(async (resolve, reject) => {

                try {

                    // 判斷文件是否已經(jīng)進行分片
                    if (currentFile.uploadProgress < 100 && currentFile.chunkList.length > 0) {
                        self.processUpload(currentFile)
                        resolve();
                        return
                    }
                    // 更新上傳標(biāo)簽
                    currentFile.status = FileStatus.getMd5;

                    // 1. 計算文件MD5
                    const md5 = await new Promise((resolveMd5, rejectMd5) => {
                        self.getFileMd5(currentFile.raw, (md5, totalChunks) => {
                            resolveMd5(md5);
                        });
                    });

                    const checkResult = await self.checkFileUploadedByMd5(md5);

                    if (checkResult.code === 1) {
                        self.$message.success(`上傳成功,文件地址:${checkResult.data.url}`);
                        currentFile.status = FileStatus.success;
                        currentFile.uploadProgress = 100;
                        resolve();
                    } else if (checkResult.code === 2) {
                        currentFile.chunkUploadedList = checkResult.data;
                    }

                    // 3. 正在創(chuàng)建分片
                    currentFile.status = FileStatus.chip;

                    const fileChunks = self.createFileChunk(currentFile.raw);

                    const fileName = self.getNewFileName(currentFile);

                    // 獲取文件類型
                    const fileType = self.fileSuffixTypeUtil(currentFile.name);

                    const uploadIdInfoResult = await self.getFileUploadUrls({
                        fileName,
                        fileSize: currentFile.size,
                        chunkSize: chunkSize,
                        partCount: fileChunks.length,
                        fileMd5: md5,
                        contentType: 'application/octet-stream',
                        fileType,
                    });

                    let uploadIdInfo = uploadIdInfoResult.data.data;

                    const uploadUrls = uploadIdInfo.urlList;

                    currentFile.chunkList = fileChunks.map((chunkItem, index) => ({
                        chunkNumber: index + 1,
                        chunk: chunkItem,
                        uploadUrl: uploadUrls[index],
                        progress: 0,
                        status: '—',
                    }));

                    uploadIdInfo.fileName = fileName;
                    uploadIdInfo.fileType = fileType;
                    uploadIdInfo.md5 = md5;

                    currentFile.uploadIdInfo = uploadIdInfo;

                    await this.processUpload(currentFile);

                    resolve();
                } catch (error) {
                    reject(error);
                }
            });
        },


        async processUpload(currentFile) {

            const self = this;

            let tempFileChunks = [];

            currentFile.chunkList.forEach((item) => {
                tempFileChunks.push(item);
            });

            currentFile.status = FileStatus.uploading;

            // 處理分片列表,刪除已上傳的分片
            tempFileChunks = self.processUploadChunkList(tempFileChunks);

            console.log("刪除已上傳的分片-->", tempFileChunks);

            await self.uploadChunkBase(tempFileChunks, currentFile);

            self.mergeFiles(currentFile.uploadIdInfo, currentFile);
        },


        /**
         * 處理即將上傳的分片列表,判斷是否有已上傳的分片,有則從列表中刪除
         */
        processUploadChunkList(chunkList) {
            // 使用 reduce 過濾并生成新的數(shù)組
            return chunkList.reduce((acc, chunkItem) => {
                if (chunkItem.progress < 100) {
                    acc.push(chunkItem);
                }
                return acc;
            }, []);
        },

        /**
         * 上傳分片文件
         * @param {*} chunkList 
         */
        async uploadChunkBase(chunkList, currentFile) {
            const self = this;

            const startTime = Date.now(); // 記錄開始上傳的時間戳

            async function uploadSingleChunk(chunk, currentFile, result, index) {
                try {
                    if (self.isPaused) {
                        await new Promise(resolve => self.resumeCallback = resolve);
                    }
                    await axios.put(chunk.uploadUrl, chunk.chunk.file, {
                        onUploadProgress: self.checkChunkUploadProgress(chunk, currentFile),
                        headers: {
                            'Content-Type': 'application/octet-stream'
                        },
                        cancelToken: source.token,
                    });

                    // 計算上傳所花費的時間
                    const uploadTime = (Date.now() - startTime) / 1000;
                    // 計算平均網(wǎng)速(字節(jié)/秒) chunkSize: 文件分片大小 uploadTime: 時間差
                    currentFile.uploadSpeed = (chunkSize / uploadTime / 1024 / 1024).toFixed(1);

                    result[index] = true;
                    return true;
                } catch (error) {
                    console.log('上傳失敗');
                    //chunkList.push(chunk);
                    return false;
                }
            }

            // 限制請求并發(fā)數(shù)量
            const maxConcurrentRequests = 10;
            const results = new Array(chunkList.length).fill(false);

            const uploadPromises = chunkList.map((chunk, index) => {
                return () => uploadSingleChunk(chunk, currentFile, results, index);
            });


            let i = 0;
            while (i < Math.min(maxConcurrentRequests, uploadPromises.length)) {
                const success = await uploadNextChunk();
                if (success) {
                    i++;
                }
            }

            async function uploadNextChunk() {
                if (uploadPromises.length > 0) {
                    const uploadPromise = uploadPromises.shift();
                    const success = await uploadPromise();
                    if (success) {
                        if (uploadPromises.length > 0) {
                            return uploadNextChunk();
                        } else if (!results.includes(false)) {
                            console.log('所有請求處理完畢');
                        }
                    }
                    return success;
                }
                return false;
            }

            while (self.isPaused) {
                await new Promise(resolve => {
                    self.pauseCallback = () => {
                        resolve();
                        if (!self.isPaused && i < maxConcurrentRequests && i < uploadPromises.length) {
                            void uploadNextChunk();
                            i++;
                        }
                    };
                });
            }
        },


        /**
         * 文件合并
         * @param {*} uploadIdInfo 
         * @param {*} currentFileIndex 
         */
        async mergeFiles(uploadIdInfo, currentFile) {
            const self = this;

            // 判斷是否單文件
            if (uploadIdInfo.uploadId === 'SingleFileUpload') {
                currentFile.status = FileStatus.success;
            } else {
                const fileInfo = {
                    uploadId: uploadIdInfo.uploadId,
                    fileName: uploadIdInfo.fileName,
                    fileMd5: uploadIdInfo.md5,
                    fileType: uploadIdInfo.fileType,
                };

                try {
                    const mergeResult = await new Promise((resolve, reject) => {
                        mergeUpload(fileInfo).then(response => {
                            console.log(response.data);
                            let data = response.data;
                            if (!data.data) {
                                data.msg = FileStatus.error;
                                resolve(data);
                            } else {
                                data.msg = FileStatus.success;
                                resolve(data);
                            }
                        }).catch(error => {
                            reject(error);
                        });
                    });

                    if (!mergeResult.data) {
                        const fileIsExit = await fileIsExits(fileInfo);

                        if (fileIsExit && !fileIsExit.data.code) {
                            currentFile.status = FileStatus.error;
                            self.$message.error(mergeResult.error);
                            return;
                        }
                    }
                    currentFile.uploadSpeed = 0
                    currentFile.status = FileStatus.success;
                    console.log('文件訪問地址:' + mergeResult.data);
                    self.$message.success(`上傳成功,文件地址:${mergeResult.data}`);
                } catch (error) {
                    currentFile.status = FileStatus.error;
                    self.$message.error(error.message);
                }
            }
        },

        /**
         * 清空列表
         */
        clearFileHandler() {
            this.uploadFileList = []
            this.uploadIdInfoList = []
        },

        /**
         * 上傳文件列表
         * @param {*} file 
         * @param {*} fileList 
         */
        handleFileChange(file, fileList) {

            fileList.forEach((item) => {
                // 去除重復(fù)文件
                if (this.uploadFileList.indexOf(item) == -1) {

                    // 初始化自定義屬性
                    item.chunkList = [];
                    item.status = FileStatus.wait;
                    item.progressStatus = 'warning';
                    item.uploadProgress = 0;
                    item.uploadSpeed = 0
                    // 新增文件
                    this.uploadFileList.push(item);
                }
            })

            this.uploadDisabled = false
        },

        /**
         * 移除文件列表
         * @param {*} file 
         * @param {*} fileList 
         */
        handleRemove(file, fileList) {
            this.uploadFileList = []
        },

        getNewFileName(file, md5) {
            return new Date().getTime() + file.name
        },

        /**
        * 分片讀取文件 MD5
        * 取第一個和最后一個切片全部內(nèi)容; 再加上取中間的,分片個數(shù)為偶數(shù)個,取中間的兩個分片; 為奇數(shù)的取中間的一個分片; 來計算 hash
        */
        getFileMd5(file, callback) {
            const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
            const fileReader = new FileReader()
            const totalChunks = Math.ceil(file.size / chunkSize)

            const loadChunk = (start, end) => {
                return new Promise((resolve, reject) => {
                    fileReader.onload = function (e) {
                        try {
                            resolve(e.target.result);
                        } catch (error) {
                            reject(error);
                        }
                    };
                    fileReader.onerror = function () {
                        reject(new Error('讀取Md5失敗,文件讀取錯誤'));
                    };
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                });
            };

            const calculateHash = async () => {
                const spark = new SparkMD5.ArrayBuffer();

                // 取第一個切片內(nèi)容
                const firstChunk = await loadChunk(0, Math.min(chunkSize, file.size));
                spark.append(firstChunk);

                // 取最后一個切片內(nèi)容
                const lastChunkStart = Math.max(0, file.size - chunkSize);
                const lastChunk = await loadChunk(lastChunkStart, file.size);
                spark.append(lastChunk);

                // 取中間的內(nèi)容
                if (totalChunks % 2 === 0) {
                    // 偶數(shù)個分片,取中間的兩個分片
                    const middleChunkIndex = totalChunks / 2;
                    const middleChunk1Start = (middleChunkIndex - 1) * chunkSize;
                    const middleChunk1 = await loadChunk(middleChunk1Start, middleChunk1Start + chunkSize);
                    spark.append(middleChunk1);

                    const middleChunk2Start = middleChunkIndex * chunkSize;
                    const middleChunk2 = await loadChunk(middleChunk2Start, middleChunk2Start + chunkSize);
                    spark.append(middleChunk2);
                } else {
                    // 奇數(shù)個分片,取中間的一個分片
                    const middleChunkIndex = Math.floor(totalChunks / 2);
                    const middleChunkStart = middleChunkIndex * chunkSize;
                    const middleChunk = await loadChunk(middleChunkStart, middleChunkStart + chunkSize);
                    spark.append(middleChunk);
                }

                return spark.end();
            };

            calculateHash()
                .then((hash) => {
                    callback(hash, totalChunks);
                })
                .catch((error) => {
                    console.error('獲取Md5錯誤:', error);
                });
        },



        /**
         * 文件分片
         */
        createFileChunk(file, size = chunkSize) {
            const chunks = Array.from({ length: Math.ceil(file.size / size) }, (_, i) => {
                const start = i * size;
                const end = Math.min(start + size, file.size);
                return { file: file.slice(start, end) };
            });
            return chunks;
        },





        /**
         * 根據(jù)文件信息獲取分片url
         * @param {*} fileParam 
         */
        getFileUploadUrls(fileParam) {
            return initUpload(fileParam)
        },

        /**
         * 檢查文件上傳的md5,判斷是否上傳
         * @param {*} md5 
         */
        async checkFileUploadedByMd5(md5) {
            try {
                const response = await checkUpload(md5)
                console.log(response.data)
                return response.data
            } catch (error) {
                console.error(error)
            } finally {
                // 無論是否發(fā)生異常,都會執(zhí)行
            }
        },

        /**
        * 檢查分片上傳進度
        */
        checkChunkUploadProgress(item, currentFile) {
            return p => {
                item.progress = parseInt(String((p.loaded / p.total) * 100))
                if (item.progress >= 100) {
                    item.status = FileStatus.success
                    item.progressStatus = 'success'
                }
                this.getCurrentFileProgress(currentFile)
            }
        },

        /**
         * 獲取當(dāng)前文件進度
         */
        getCurrentFileProgress(currentFile) {
            //const currentFile = this.uploadFileList[currentFileIndex];
            if (!currentFile || !currentFile.chunkList) {
                return;
            }
            const chunkList = currentFile.chunkList;
            const uploadedSize = chunkList.reduce((acc, cur) => acc + cur.chunk.file.size * cur.progress, 0);
            // 計算方式:已上傳大小 / 文件總大小
            let progress = parseInt((uploadedSize / currentFile.size).toFixed(2));

            currentFile.uploadProgress = progress;
        },



        fileSuffixTypeUtil(filename) {
            const lastDotIndex = filename.lastIndexOf('.');
            if (lastDotIndex === -1) {
                return ''; // 文件名中沒有'.',返回空字符串  
            }
            return filename.slice(lastDotIndex + 1); // 返回'.'后的字符串  
        },

        // 字節(jié)轉(zhuǎn)標(biāo)準(zhǔn)單位
        transformByte(size) {
            const units = ['B', 'K', 'M', 'G', 'T'];
            if (!size) return '0B';
            let index = 0;
            while (size >= 1024 && index < units.length - 1) {
                size /= 1024;
                index++;
            }
            return `${size.toFixed(2)}${units[index]}`;
        },

    },
}


class TaskQueue {
    constructor(concurrency) {
        this.concurrency = concurrency; // 同時執(zhí)行的任務(wù)數(shù)量限制
        this.running = 0; // 當(dāng)前正在執(zhí)行的任務(wù)數(shù)量
        this.queue = []; // 任務(wù)隊列
        this.paused = false; // 是否已暫停任務(wù)執(zhí)行
        this.pauseQueue = []; // 暫停隊列
    }

    push(task) {
        this.queue.push(task); // 將任務(wù)加入隊列
        this.next(); // 嘗試執(zhí)行下一個任務(wù)
    }

    pushPauseQueue(task) {
        this.pauseQueue.push(task); // 將任務(wù)加入暫停隊列
    }

    async next() {
        while (this.running < this.concurrency && (this.queue.length > 0 || this.pauseQueue.length > 0)) {
            if (!this.paused) { // 判斷是否已暫停任務(wù)執(zhí)行
                const taskObj = this.pauseQueue.length > 0 ? this.pauseQueue.shift() : this.queue.shift(); // 優(yōu)先執(zhí)行暫停隊列中的任務(wù)
                const { name, task } = taskObj; // 獲取任務(wù) id 和任務(wù)本身
                this.running++; // 增加正在執(zhí)行的任務(wù)數(shù)量
                try {
                    console.log('正在執(zhí)行隊列任務(wù)', name);
                    await task(); // 等待任務(wù)執(zhí)行完成
                } catch (error) {
                    console.error(error);
                }
                this.running--; // 減少正在執(zhí)行的任務(wù)數(shù)量
            } else {
                break; // 若已暫停任務(wù)執(zhí)行,則退出循環(huán)
            }
        }
    }

    pause() {
        this.paused = true; // 暫停任務(wù)執(zhí)行
        this.running = 0;
    }

    resume() {
        this.paused = false; // 繼續(xù)任務(wù)執(zhí)行
        this.next(); // 嘗試執(zhí)行下一個任務(wù)
    }

    setConcurrency(concurrency) {
        this.concurrency = concurrency; // 設(shè)置同時執(zhí)行的任務(wù)數(shù)量限制
    }

    isPaused() {
        return this.paused; // 返回是否已暫停任務(wù)執(zhí)行
    }

    isEmpty() {
        return this.queue.length === 0 && this.pauseQueue.length === 0; // 判斷任務(wù)隊列和暫停隊列是否都為空
    }

    isRunning() {
        return this.running; // 返回同時執(zhí)行的任務(wù)數(shù)量
    }
}

</script>

3.css

<style scoped>
.container {
    width: 800px;
    margin: 0 auto;
}

.file-list-wrapper {
    margin-top: 20px;
}

h2 {
    text-align: center;
}

.file-info-item {
    margin: 0 10px;
}

.upload-file-item {
    display: flex;
}

.file-progress {
    display: flex;
    align-items: center;
}

.file-progress-value {
    width: 150px;
}

.file-name {
    width: 300px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.file-size {
    width: 100px;
}
</style>

4.upload.js

import request from '@/utils/request'



//上傳信息
export function uploadFileInfo(data){
    return request({
        url:'upload/multipart/uploadFileInfo',
        method:'post',
        data
    })
}

// 上傳校驗
export function checkUpload(MD5) {
    return request({
        url: `upload/multipart/check?md5=${MD5}`,
        method: 'get',
    })
};


// 初始化上傳
export function initUpload(data) {
    return request({
        url: `upload/multipart/init`,
        method: 'post',
        data
    })
};


// 文件合并
export function mergeUpload(data) {
    return request({
        url: `upload/multipart/merge`,
        method: 'post',
        data
    })
};


//判斷文件是否存在
export function fileIsExits(data) {
    return request({
        url: `upload/multipart/fileIsExits`,
        method: 'post',
        data
    })
};
 
 

5.request.js

import axios from 'axios'

// 創(chuàng)建 axios 實例
const service = axios.create({
  baseURL: "/api", // 環(huán)境的不同,對應(yīng)不同的baseURL
  // transformRequest: [function(data) {
  //   return Qs.stringify(data)
  // }],
  //timeout: 5000 // 請求超時時間
})
 
 
//request請求攔截
service.interceptors.request.use(
  config => {
    // var token=getToken()
    // if (token) {
    //     config.headers.token = token // 讓每個請求攜帶自定義token 請根據(jù)實際情況自行修改
    //   }
    return config;
},
  error => {
    // do something with request error
    return Promise.reject(error)
  }
)
 
 
//響應(yīng)攔截
service.interceptors.response.use(
  response => {
    const res = response
    return res
  },
  error => {
    
    //這里還可以根據(jù)實際情況增加一些功能
    return Promise.reject(error)
  }
)
 
export default service
 

二.后端代碼

后端使用的是springboot ,使用之前要啟動minio,redis,否則文件上傳會出現(xiàn)異常。這里我都是使用windows版的

1.controller,文件上傳接口

package com.xy.fileservice.controller;


import com.xy.fileservice.entity.FileUploadInfo;
import com.xy.fileservice.service.UploadService;
import com.xy.fileservice.util.MinioUtils;
import com.xy.fileservice.util.ResponseResult;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;


/**
 * minio上傳流程
 *
 * 1.檢查數(shù)據(jù)庫中是否存在上傳文件
 *
 * 2.根據(jù)文件信息初始化,獲取分片預(yù)簽名url地址,前端根據(jù)url地址上傳文件
 *
 * 3.上傳完成后,將分片上傳的文件進行合并
 *
 * 4.保存文件信息到數(shù)據(jù)庫
 */
@Slf4j
@RestController
@RequestMapping("/upload")
public class FileMinioController {

    @Resource
    private UploadService uploadService;

    @Resource
    private MinioUtils minioUtils;

    /**
     * 校驗文件是否存在
     *
     * @param md5 String
     * @return ResponseResult<Object>
     */
    @GetMapping("/multipart/check")
    public ResponseResult checkFileUploadedByMd5(@RequestParam("md5") String md5) {
        log.info("REST: 通過查詢 <{}> 文件是否存在、是否進行斷點續(xù)傳", md5);
        return uploadService.getByFileMd5(md5);
    }

    /**
     * 分片初始化
     *
     * @param fileUploadInfo 文件信息
     * @return ResponseResult<Object>
     */
    @PostMapping("/multipart/init")
    public ResponseResult initMultiPartUpload(@RequestBody FileUploadInfo fileUploadInfo) {
        log.info("REST: 通過 <{}> 初始化上傳任務(wù)", fileUploadInfo);
        return uploadService.initMultiPartUpload(fileUploadInfo);
    }

    /**
     * 完成上傳
     *
     * @param fileUploadInfo  文件信息
     * @return ResponseResult<Object>
     */
    @PostMapping("/multipart/merge")
    public ResponseResult completeMultiPartUpload(@RequestBody FileUploadInfo fileUploadInfo) {
        log.info("REST: 通過 <{}> 合并上傳任務(wù)", fileUploadInfo);
        return uploadService.mergeMultipartUpload(fileUploadInfo);
    }


    @PostMapping("/multipart/fileIsExits")
    public ResponseResult fileIsExits(@RequestBody FileUploadInfo fileUploadInfo) {
        log.info("REST: 通過 <{}> 判斷文件是否存在", fileUploadInfo);
        return uploadService.fileIsExits(fileUploadInfo);
    }


    @RequestMapping("/createBucket")
    public void createBucket(@RequestParam("bucketName")String bucketName){
        String bucket = minioUtils.createBucket(bucketName);
    }




}

2.UploadService

package com.xy.fileservice.service;



import com.xy.fileservice.entity.FileUploadInfo;
import com.xy.fileservice.util.ResponseResult;
import org.springframework.web.multipart.MultipartFile;

public interface UploadService {
    /**
     * 分片上傳初始化
     *
     * @param fileUploadInfo
     * @return Map<String, Object>
     */
    ResponseResult<Object> initMultiPartUpload(FileUploadInfo fileUploadInfo);

    /**
     * 完成分片上傳
     *
     * @param  fileUploadInfo
     * @return boolean
     */
    ResponseResult<Object> mergeMultipartUpload(FileUploadInfo fileUploadInfo);

    /**
     *  通過 sha256 獲取已上傳的數(shù)據(jù)
     * @param sha256 String
     * @return Mono<Map<String, Object>>
     */
    ResponseResult<Object> getByFileMd5(String sha256);

    /**
     *  獲取文件地址
     * @param bucketName
     * @param fileName
     *
     */
    String getFilePath(String bucketName, String fileName);


    /**
     * 單文件上傳
     * @param file
     * @param bucketName
     * @return
     */
    String upload(MultipartFile file, String bucketName);


    /**
     * 判斷文件是否存在
     * @param fileUploadInfo
     * @return
     */
    ResponseResult fileIsExits(FileUploadInfo fileUploadInfo);

}

3.UploadServiceImpl

package com.xy.fileservice.service.impl;


import com.alibaba.fastjson.JSONObject;
import com.xy.fileservice.entity.FileUploadInfo;
import com.xy.fileservice.service.UploadService;
import com.xy.fileservice.util.MinioUtils;
import com.xy.fileservice.util.RedisRepo;
import com.xy.fileservice.util.ResponseResult;
import com.xy.fileservice.util.ResultCode;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.Objects;

import static com.xy.fileservice.util.ResultCode.ACCESS_PARAMETER_INVALID;

@Slf4j
@Service
public class UploadServiceImpl implements UploadService {


    @Resource
    private MinioUtils fileService;

    @Resource
    private RedisRepo redisRepo;


    /**
     * 通過 md5 獲取已上傳的數(shù)據(jù)(斷點續(xù)傳)
     *
     * @param md5 String
     * @return Mono<Map < String, Object>>
     */
    @Override
    public ResponseResult<Object> getByFileMd5(String md5) {

        if (StringUtils.hasText(md5)) {
            log.error("查詢文件是否存在、入?yún)o效");
            return ResponseResult.error(ACCESS_PARAMETER_INVALID);
        }

        log.info("tip message: 通過 <{}> 查詢數(shù)據(jù)是否存在", md5);

        // 獲取文件名稱和id
        String value = redisRepo.get(md5);

        FileUploadInfo fileUploadInfo = null;

        if (StringUtils.hasText(value)) {
            fileUploadInfo = JSONObject.parseObject(value, FileUploadInfo.class);
        }

        if (Objects.isNull(fileUploadInfo)) {
            // 返回數(shù)據(jù)不存在
            log.error("error message: 文件數(shù)據(jù)不存在");
            return ResponseResult.error(ResultCode.FOUND);
        }

        // 獲取桶名稱
        String bucketName = fileService.getBucketName(fileUploadInfo.getFileType());

        return fileService.getByFileMd5(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), bucketName);
    }


    /**
     * 文件分片上傳
     *
     * @param fileUploadInfo
     * @return Mono<Map < String, Object>>
     */
    @Override
    public ResponseResult<Object> initMultiPartUpload(FileUploadInfo fileUploadInfo) {

        log.info("tip message: 通過 <{}> 開始初始化<分片上傳>任務(wù)", fileUploadInfo);

        // 獲取文件桶名
        String bucketName = fileService.getBucketName(fileUploadInfo.getFileType());

        // 單文件上傳可拆分,可直接上傳完成
        if (fileUploadInfo.getPartCount() == 1) {

            log.info("tip message: 當(dāng)前分片數(shù)量 <{}> 進行單文件上傳", fileUploadInfo.getPartCount());

            // 獲取文件分片上傳的url
            return fileService.getUploadObjectUrl(fileUploadInfo.getFileName(), bucketName);

        }else {

            log.info("tip message: 當(dāng)前分片數(shù)量 <{}> 進行分片上傳", fileUploadInfo.getPartCount());

            // 獲取文件分片上傳的url
            return fileService.initMultiPartUpload(fileUploadInfo, fileUploadInfo.getFileName(), fileUploadInfo.getPartCount(), fileUploadInfo.getContentType(), bucketName);
        }

    }

    /**
     * 文件合并
     *
     * @param
     * @return boolean
     */
    @Override
    public ResponseResult mergeMultipartUpload(FileUploadInfo fileUploadInfo) {

        log.info("tip message: 通過 <{}> 開始合并<分片上傳>任務(wù)", fileUploadInfo);

        // 獲取桶名稱
        String bucketName = fileService.getBucketName(fileUploadInfo.getFileType());

        // 獲取合并結(jié)果
        boolean result = fileService.mergeMultipartUpload(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), bucketName);

        //獲取上傳文件地址
        if(result){
            String filePath = fileService.getFilePath(fileUploadInfo.getFileType().toLowerCase(), fileUploadInfo.getFileName());
            return ResponseResult.success(filePath);
        }

        log.error("error message: 文件合并異常");

        return  ResponseResult.error();
    }




    @Override
    public String getFilePath(String bucketName, String fileName) {
        return fileService.getFilePath(bucketName, fileName);
    }


    @Override
    public String upload(MultipartFile file, String bucketName) {
        fileService.upload(file, bucketName);
        return getFilePath(bucketName, file.getName());
    }

    public ResponseResult fileIsExits(FileUploadInfo fileUploadInfo){
        boolean b = fileService.doesObjectExist(fileUploadInfo.getFileType(), fileUploadInfo.getFileName());

        if(b){
            return ResponseResult.success();
        }

        return  ResponseResult.error();
    }


}

4.MinioUtils

package com.xy.fileservice.util;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.HashMultimap;
import com.xy.fileservice.config.CustomMinioClient;
import com.xy.fileservice.entity.FileUploadInfo;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.xy.fileservice.util.ResultCode.DATA_NOT_EXISTS;
import static com.xy.fileservice.util.ResultCode.UPLOAD_FILE_FAILED;


@Slf4j
@Component
public class MinioUtils {

    @Value(value = "${minio.endpoint}")
    private String endpoint;
    @Value(value = "${minio.accesskey}")
    private String accesskey;
    @Value(value = "${minio.secretkey}")
    private String secretkey;

    @Resource
    private RedisRepo redisRepo;

    private CustomMinioClient customMinioClient;


    /**
     * 用spring的自動注入會注入失敗
     */
    @PostConstruct
    public void init() {
        MinioClient minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accesskey, secretkey)
                .build();
        customMinioClient = new CustomMinioClient(minioClient);
    }


    /**
     * 單文件簽名上傳
     *
     * @param objectName 文件全路徑名稱
     * @param bucketName 桶名稱
     * @return /
     */
    public ResponseResult<Object> getUploadObjectUrl(String objectName, String bucketName) {

        log.info("tip message: 通過 <{}-{}> 開始單文件上傳<minio>", objectName, bucketName);

        try {
            String url = getPresidedObjectUrl(bucketName, objectName);

            Map<String, Object> resMap = new HashMap<>();

            resMap.put("uploadId", "SingleFileUpload");

            resMap.put("urlList", Collections.singletonList(url));

            return ResponseResult.success(resMap);

        } catch (Exception e) {

            log.error("error message: 初始化分片上傳失敗、原因:", e);

            // 返回 文件上傳失敗
            return ResponseResult.error(UPLOAD_FILE_FAILED);
        }

    }



    /**
     * 文件分片上傳
     *
     * @param fileUploadInfo
     * @param objectName     文件全路徑名稱
     * @param partCount      分片數(shù)量
     * @param contentType    類型,如果類型使用默認(rèn)流會導(dǎo)致無法預(yù)覽
     * @param bucketName     桶名稱
     * @return Mono<Map < String, Object>>
     */

    public ResponseResult<Object> initMultiPartUpload(FileUploadInfo fileUploadInfo, String objectName, int partCount, String contentType, String bucketName) {
        log.info("tip message: 通過 <{}-{}-{}-{}> 開始初始化<分片上傳>數(shù)據(jù)", objectName, partCount, contentType, bucketName);

        try {
            String uploadId = getUploadId(bucketName, objectName, contentType);

            fileUploadInfo.setUploadId(uploadId);

            //redis保存文件信息
            redisRepo.saveTimeout(fileUploadInfo.getFileMd5(), JSONObject.toJSONString(fileUploadInfo), 30, TimeUnit.MINUTES);

            List<String> partList = getPartUploadUrls(uploadId, partCount, bucketName, objectName);

            Map<String, Object> resMap = new HashMap<>();

            resMap.put("uploadId", uploadId);

            resMap.put("urlList", partList);

            log.info("tip message: 文件初始化<分片上傳>、成功");

            return ResponseResult.success(resMap);

        } catch (Exception e) {

            log.error("error message: 初始化分片上傳失敗、原因:", e);

            // 返回 文件上傳失敗
            return ResponseResult.error(UPLOAD_FILE_FAILED);
        }
    }
    

    /**
     * 分片上傳完后合并
     *
     * @param objectName 文件全路徑名稱
     * @param uploadId   返回的uploadId
     * @param bucketName 桶名稱
     * @return boolean
     */
    public boolean mergeMultipartUpload(String objectName, String uploadId, String bucketName) {
        try {
            log.info("tip message: 通過 <{}-{}-{}> 合并<分片上傳>數(shù)據(jù)", objectName, uploadId, bucketName);
            //目前僅做了最大1000分片
            Part[] parts = new Part[1000];
            // 查詢上傳后的分片數(shù)據(jù)
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            // 合并分片
            customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);

        } catch (Exception e) {
            log.error("error message: 合并失敗、原因:", e);
            return false;
        }
        return true;
    }

    /**
     * 通過 sha256 獲取上傳中的分片信息
     *
     * @param objectName 文件全路徑名稱
     * @param uploadId   返回的uploadId
     * @param bucketName 桶名稱
     * @return Mono<Map < String, Object>>
     */
    public ResponseResult<Object> getByFileMd5(String objectName, String uploadId, String bucketName) {
        log.info("通過 <{}-{}-{}> 查詢<minio>上傳分片數(shù)據(jù)", objectName, uploadId, bucketName);
        try {
            // 查詢上傳后的分片數(shù)據(jù)
            ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            List<Integer> collect = partResult.result().partList().stream().map(Part::partNumber).collect(Collectors.toList());
            return ResponseResult.uploading(collect);
        } catch (Exception e) {
            log.error("error message: 查詢上傳后的分片信息失敗、原因:", e);
            return ResponseResult.error(DATA_NOT_EXISTS);
        }
    }

    /**
     * 獲取文件下載地址
     *
     * @param bucketName 桶名稱
     * @param fileName   文件名
     * @return
     */
    public String getFilePath(String bucketName, String fileName) {
        return StrUtil.format("{}/{}/{}", endpoint, bucketName, fileName);//文件訪問路徑
    }

    /**
     * 創(chuàng)建一個桶
     *
     * @return
     */
    public String createBucket(String bucketName) {
        try {
            BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();

            //如果桶存在
            if (customMinioClient.bucketExists(bucketExistsArgs)) {
                return bucketName;
            }

            // 如果不存在則創(chuàng)建新文件桶
            MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();

            customMinioClient.makeBucket(makeBucketArgs);

            return bucketName;

        } catch (Exception e) {
            log.error("創(chuàng)建桶失?。簕}", e.getMessage());
            throw new RuntimeException(e);
        }
    }


    /**
     * 根據(jù)文件類型獲取minio桶名稱
     *
     * @param fileType
     * @return
     */
    public String getBucketName(String fileType) {
        try {
            if (StringUtils.isNotEmpty(fileType)) {

                //判斷桶是否存在
                String bucketName = createBucket(fileType.toLowerCase());

                if (StringUtils.isNotEmpty(bucketName)) {

                    return bucketName;
                } else {

                    return fileType;
                }
            }
        } catch (Exception e) {
            log.error("Error reading bucket name ");
        }
        return fileType;
    }


    /**
     *  單文件獲取上傳url
     * @param bucketName
     * @param objectName
     * @return
     * @throws ServerException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidResponseException
     * @throws XmlParserException
     * @throws InternalException
     */
    private String getPresidedObjectUrl(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return customMinioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(1, TimeUnit.DAYS)
                        .build());
    }


    /**
     * 獲取合并id
     * @param bucketName
     * @param objectName
     * @param contentType
     * @return
     * @throws ServerException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws XmlParserException
     * @throws InvalidResponseException
     * @throws InternalException
     */
    private String getUploadId(String bucketName, String objectName, String contentType) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {

        if (CharSequenceUtil.isBlank(contentType)) {
            contentType = "application/octet-stream";
        }

        HashMultimap<String, String> headers = HashMultimap.create();

        headers.put("Content-Type", contentType);

        return customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null);
    }

    /**
     * 獲取文件分片urls
     * @param uploadId
     * @param partCount
     * @param bucketName
     * @param objectName
     * @return
     * @throws ServerException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws InvalidResponseException
     * @throws XmlParserException
     * @throws InternalException
     */
    private List<String> getPartUploadUrls(String uploadId, int partCount, String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        List<String> partList = new ArrayList<>();

        for (int i = 1; i <= partCount; i++) {

            Map<String, String> reqParams = new HashMap<>();

            reqParams.put("uploadId", uploadId);

            reqParams.put("partNumber", String.valueOf(i));

            String uploadUrl = customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.DAYS)
                            .extraQueryParams(reqParams)
                            .build());

            partList.add(uploadUrl);
        }
        return partList;
    }

    /**
     * 判斷文件是否存在
     *
     * @param bucketName 存儲桶
     * @param objectName 對象
     * @return true:存在
     */

    public  boolean doesObjectExist(String bucketName, String objectName) {
        boolean exist = true;
        try {
            customMinioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }


    /**
     * 文件上傳
     *
     * @param file 文件
     * @return Boolean
     */
    public String upload(MultipartFile file, String bucketName) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            throw new RuntimeException();
        }
        String objectName = file.getName();
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名稱相同會覆蓋
            customMinioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(objectName).method(Method.GET).build();
        String url = null;
        try {
            url = customMinioClient.getPresignedObjectUrl(build);
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        }
        return url;
    }

}

5.CustomMinioClient

package com.xy.config;

import com.google.common.collect.Multimap;
import io.minio.CreateMultipartUploadResponse;
import io.minio.ListPartsResponse;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.errors.*;
import io.minio.messages.Part;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


public class CustomMinioClient extends MinioClient {

    /**
     * 繼承父類
     * @param client
     */
    public CustomMinioClient(MinioClient client) {
        super(client);
    }


    /**
     * 初始化分片上傳、獲取 uploadId
     *
     * @param bucket           String  存儲桶名稱
     * @param region           String
     * @param object           String   文件名稱
     * @param headers          Multimap<String, String> 請求頭
     * @param extraQueryParams Multimap<String, String>
     * @return String
     */
    public String initMultiPartUpload(String bucket, String region, String object, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
        CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);
        return response.result().uploadId();
    }

    /**
     * 合并分片
     *
     * @param bucketName       String   桶名稱
     * @param region           String
     * @param objectName       String   文件名稱
     * @param uploadId         String   上傳的 uploadId
     * @param parts            Part[]   分片集合
     * @param extraHeaders     Multimap<String, String>
     * @param extraQueryParams Multimap<String, String>
     * @return ObjectWriteResponse
     */
    public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
        return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }

    /**
     * 查詢當(dāng)前上傳后的分片信息
     *
     * @param bucketName       String   桶名稱
     * @param region           String
     * @param objectName       String   文件名稱
     * @param maxParts         Integer  分片數(shù)量
     * @param partNumberMarker Integer  分片起始值
     * @param uploadId         String   上傳的 uploadId
     * @param extraHeaders     Multimap<String, String>
     * @param extraQueryParams Multimap<String, String>
     * @return ListPartsResponse
     */
    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }


}


6.CorsConfig

package com.xy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 全局跨域處理
 * @author CV
 */

@Configuration
public class CorsConfig implements WebMvcConfigurer {

        private CorsConfiguration buildConfig() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedMethod("*");
            corsConfiguration.setMaxAge(3600L);
            corsConfiguration.setAllowCredentials(true);
            return corsConfiguration;
        }

        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", buildConfig());
            return new CorsFilter(source);
        }
    }

接下來是返回信息工具類

7.ResponseResult

package com.xy.util;

import lombok.Data;

@Data
public class ResponseResult<T> {
    private int code;
    private String enMessage;
    private String zhMessage;
    private T data;

    public ResponseResult() {
    }

    public ResponseResult(int code, String enMessage, String zhMessage) {
        this.code = code;
        this.enMessage = enMessage;
        this.zhMessage = zhMessage;
    }


    /**
     * 成功
     */
    public static <T> ResponseResult<T> success() {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setCode(ResultCode.SUCCESS.getCode());
        result.setEnMessage(ResultCode.SUCCESS.getEnMessage());
        result.setZhMessage(ResultCode.SUCCESS.getZhMessage());
        return result;
    }


    /**
     * 成功
     */
    public static <T> ResponseResult<T> success(T data) {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setCode(ResultCode.SUCCESS.getCode());
        result.setEnMessage(ResultCode.SUCCESS.getEnMessage());
        result.setZhMessage(ResultCode.SUCCESS.getZhMessage());
        result.setData(data);
        return result;
    }


    /**
     * 失敗
     */
    public static  <T> ResponseResult <T> error() {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setCode(ResultCode.FAIL.getCode());
        result.setEnMessage(ResultCode.FAIL.getEnMessage());
        result.setZhMessage(ResultCode.FAIL.getZhMessage());
        return result;
    }

    /**
     * 失敗
     */
    public static <T> ResponseResult<T> error(T data) {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setCode(ResultCode.FAIL.getCode());
        result.setEnMessage(ResultCode.FAIL.getEnMessage());
        result.setZhMessage(ResultCode.FAIL.getZhMessage());
        result.setData(data);
        return result;
    }


    /**
     *
     * @param data 數(shù)據(jù)
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> uploading(T data) {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setCode(ResultCode.UPLOADING.getCode());
        result.setEnMessage(ResultCode.UPLOADING.getEnMessage());
        result.setZhMessage(ResultCode.UPLOADING.getZhMessage());
        result.setData(data);
        return result;
    }

    /**
     * 成功
     */
    public static <T> ResponseResult<T> success(int code, String enMessage, String zhMessage) {
        return new ResponseResult(code, enMessage, zhMessage);
    }


    /**
     * 失敗
     */
    public static <T> ResponseResult<T> error(int code, String enMessage, String zhMessage) {
        return new ResponseResult(code, enMessage, zhMessage);
    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getEnMessage() {
        return enMessage;
    }

    public void setEnMessage(String enMessage) {
        this.enMessage = enMessage;
    }

    public String getZhMessage() {
        return zhMessage;
    }

    public void setZhMessage(String zhMessage) {
        this.zhMessage = zhMessage;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

//    public static ResponseResult<Void> SUCCESS = new ResponseResult<>(200,"成功");
//    public static ResponseResult<Void> INTEVER_ERROR = new ResponseResult<>(500,"服務(wù)器錯誤");
//    public static ResponseResult<Void> NOT_FOUND = new ResponseResult<>(404,"未找到");

}

8.ResultCode

package com.xy.util;



/**
 * http狀態(tài)碼枚舉類
 */
public enum ResultCode {

    SUCCESS(1, "Success", "成功"),
    UPLOADING(2, "Uploading", "上傳中"),
    FAIL(-1, "Err", "失敗"),


    DATABASE_OPERATION_FAILED(504, "數(shù)據(jù)庫操作失敗"),
    CONTINUE(100, "Continue", "請繼續(xù)發(fā)送請求的剩余部分"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols", "協(xié)議切換"),
    PROCESSING(102, "Processing", "請求將繼續(xù)執(zhí)行"),
    CHECKPOINT(103, "Checkpoint", "可以預(yù)加載"),
    OK(200, "OK", "請求已經(jīng)成功處理"),
    CREATED(201, "Created", "請求已經(jīng)成功處理,并創(chuàng)建了資源"),
    ACCEPTED(202, "Accepted", "請求已經(jīng)接受,等待執(zhí)行"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information", "請求已經(jīng)成功處理,但是信息不是原始的"),
    NO_CONTENT(204, "No Content", "請求已經(jīng)成功處理,沒有內(nèi)容需要返回"),
    RESET_CONTENT(205, "Reset Content", "請求已經(jīng)成功處理,請重置視圖"),
    PARTIAL_CONTENT(206, "Partial Content", "部分Get請求已經(jīng)成功處理"),
    MULTI_STATUS(207, "Multi-Status", "請求已經(jīng)成功處理,將返回XML消息體"),
    ALREADY_REPORTED(208, "Already Reported", "請求已經(jīng)成功處理,一個DAV的綁定成員被前一個請求枚舉,并且沒有被再一次包括"),
    IM_USED(226, "IM Used", "請求已經(jīng)成功處理,將響應(yīng)一個或者多個實例"),
    MULTIPLE_CHOICES(300, "Multiple Choices", "提供可供選擇的回饋"),
    MOVED_PERMANENTLY(301, "Moved Permanently", "請求的資源已經(jīng)永久轉(zhuǎn)移"),
    FOUND(302, "Found", "請重新發(fā)送請求"),
    SEE_OTHER(303, "See Other", "請以Get方式請求另一個URI"),
    NOT_MODIFIED(304, "Not Modified", "資源未改變"),
    USE_PROXY(305, "Use Proxy", "請通過Location域中的代理進行訪問"),
    TEMPORARY_REDIRECT(307, "Temporary Redirect", "請求的資源臨時從不同的URI響應(yīng)請求"),
    RESUME_INCOMPLETE(308, "Resume Incomplete", "請求的資源已經(jīng)永久轉(zhuǎn)移"),
    BAD_REQUEST(400, "Bad Request", "請求錯誤,請修正請求"),
    UNAUTHORIZED(401, "Unauthorized", "沒有被授權(quán)或者授權(quán)已經(jīng)失效"),
    PAYMENT_REQUIRED(402, "Payment Required", "預(yù)留狀態(tài)"),
    FORBIDDEN(403, "Forbidden", "請求被理解,但是拒絕執(zhí)行"),
    NOT_FOUND(404, "Not Found", "資源未找到"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed", "請求方法不允許被執(zhí)行"),
    NOT_ACCEPTABLE(406, "Not Acceptable", "請求的資源不滿足請求者要求"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required", "請通過代理進行身份驗證"),
    REQUEST_TIMEOUT(408, "Request Timeout", "請求超時"),
    CONFLICT(409, "Conflict", "請求沖突"),
    GONE(410, "Gone", "請求的資源不可用"),
    LENGTH_REQUIRED(411, "Length Required", "Content-Length未定義"),
    PRECONDITION_FAILED(412, "Precondition Failed", "不滿足請求的先決條件"),
    REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large", "請求發(fā)送的實體太大"),
    REQUEST_URI_TOO_LONG(414, "Request-URI Too Long", "請求的URI超長"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type", "請求發(fā)送的實體類型不受支持"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable", "Range指定的范圍與當(dāng)前資源可用范圍不一致"),
    EXPECTATION_FAILED(417, "Expectation Failed", "請求頭Expect中指定的預(yù)期內(nèi)容無法被服務(wù)器滿足"),
    UNPROCESSABLE_ENTITY(422, "Unprocessable Entity", "請求格式正確,但是由于含有語義錯誤,無法響應(yīng)"),
    LOCKED(423, "Locked", "當(dāng)前資源被鎖定"),
    FAILED_DEPENDENCY(424, "Failed Dependency", "由于之前的請求發(fā)生錯誤,導(dǎo)致當(dāng)前請求失敗"),
    UPGRADE_REQUIRED(426, "Upgrade Required", "客戶端需要切換到TLS1.0"),
    PRECONDITION_REQUIRED(428, "Precondition Required", "請求需要提供前置條件"),
    TOO_MANY_REQUESTS(429, "Too Many Requests", "請求過多"),
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large", "請求頭超大,拒絕請求"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error", "服務(wù)器內(nèi)部錯誤"),
    NOT_IMPLEMENTED(501, "Not Implemented", "服務(wù)器不支持當(dāng)前請求的部分功能"),
    BAD_GATEWAY(502, "Bad Gateway", "響應(yīng)無效"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable", "服務(wù)器維護或者過載,拒絕服務(wù)"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout", "上游服務(wù)器超時"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported", "不支持的HTTP版本"),
    VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates", "服務(wù)器內(nèi)部配置錯誤"),
    INSUFFICIENT_STORAGE(507, "Insufficient Storage", "服務(wù)器無法完成存儲請求所需的內(nèi)容"),
    LOOP_DETECTED(508, "Loop Detected", "服務(wù)器處理請求時發(fā)現(xiàn)死循環(huán)"),
    BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded", "服務(wù)器達到帶寬限制"),
    NOT_EXTENDED(510, "Not Extended", "獲取資源所需的策略沒有被滿足"),
    NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required", "需要進行網(wǎng)絡(luò)授權(quán)"),
    ACCESS_PARAMETER_INVALID(1001,"Invalid access parameter","訪問參數(shù)無效"),
    UPLOAD_FILE_FAILED(1002,"File upload failure","文件上傳失敗"),
    DATA_NOT_EXISTS(1003,"Data does not exist","數(shù)據(jù)不存在"),

    ;



    private int code;
    private String enMessage;
    private String zhMessage;

    ResultCode(int code, String enMessage, String zhMessage) {
        this.code = code;
        this.enMessage = enMessage;
        this.zhMessage = zhMessage;
    }

    ResultCode(int code, String message) {

    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getEnMessage() {
        return enMessage;
    }

    public void setEnMessage(String enMessage) {
        this.enMessage = enMessage;
    }

    public String getZhMessage() {
        return zhMessage;
    }

    public void setZhMessage(String zhMessage) {
        this.zhMessage = zhMessage;
    }
}

9.FileUploadInfo,還有最重要的實體類

package com.xy.entity;

import lombok.Data;
import lombok.experimental.Accessors;


@Data
@Accessors(chain = true)
public class FileUploadInfo {

    //@NotBlank(message = "文件名不能為空")
    private String fileName;

    // @NotNull(message = "文件大小不能為空")
    private Double fileSize;

    // @NotBlank(message = "Content-Type不能為空")
    private String contentType;

    //  @NotNull(message = "分片數(shù)量不能為空")
    private Integer partCount;

    // @NotBlank(message = "uploadId 不能為空")
    private String uploadId;

    // 桶名稱
    //private String bucketName;

    //md5
    private String fileMd5;

    //文件類型
    private String fileType;


    public FileUploadInfo() {
    }


}


10.RedisRepo

package com.xy.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;


@Component
public class RedisRepo {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public String get(String key) {
        BoundValueOperations<String, String> ops = redisTemplate.boundValueOps(key);
        return ops.get();
    }

    public void save(String key,String str){
        BoundValueOperations<String, String> ops = redisTemplate.boundValueOps(key);
        ops.set(str);
    }

    public void saveTimeout(String key, String value, long timeout, TimeUnit unit ){
        redisTemplate.boundValueOps(key).setIfAbsent(value,timeout,unit);
    }

    public void delete(String key){
        redisTemplate.delete(key);
    }

    public long expire(String key){
        return redisTemplate.opsForValue().getOperations().getExpire(key);
    }
}

11.yaml配置

minio:
  endpoint: http://localhost:9000
  accesskey: minioadmin
  secretkey: minioadmin

spring:
  redis:
    host: localhost
    port: 6379

12.pom配置

      <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.2</version>
        </dependency>

13.中間件

為了方便中間件皆采用docker,使用時注意替換自己的本地目錄

redis

地址:localhost:6379

docker run --name redis01 -p 6379:6379 -v D:\Docker-vm\folder\redis\data\redis.conf:/usr/local/etc/redis/redis.conf -d redis:5.0.14

minio

地址:localhost:9000

賬號:minioadmin

密碼:minioadmin

docker run -p 9000:9000 -p 9090:9090 ? --name minio ? ? ?-d --restart=always ? ? ?-e "MINIO_ACCESS_KEY=minioadmin" ? ? ?-e "MINIO_SECRET_KEY=minioadmin" ? ? ?-v D:\Docker-vm\folder\minio\data:/data ? ? ?-v D:\Docker-vm\folder\minio\config:/root/.minio ? ? ?minio/minio server ? ? ?/data --console-address ":9090" -address ":9000"

2023.03.01

本文僅介紹上傳流程的簡單實現(xiàn),很多功能未完善,如文件夾上傳、上傳暫停、停止等功能。代碼有何異?;蛘卟煌暾麣g迎在評論區(qū)留言???????

2023.12.20?

前端升級到了 vite + vue + element plus? 后端升級到了 jdk17 + springboot3.0?

前端升級了框架, 增加了暫停上傳功能, 做了大量的代碼優(yōu)化.?

tip: 暫停上傳功能在文件體積較小的情況下可能會因為文件上傳速度太快出現(xiàn)功能異常

2023.12.23

前端功能優(yōu)化, 計算md5 采用分片抽樣計算,優(yōu)化了上傳邏輯,增加了任務(wù)隊列實現(xiàn)多文件同時上傳,暫停功能也進行優(yōu)化,修復(fù)了功能 . 同時增加了網(wǎng)速顯示功能

tip: 網(wǎng)速顯示不太準(zhǔn)確,參考意義不大,網(wǎng)上沒有較好的網(wǎng)速計算方式,大家如果看到歡迎留言

項目傳送門:?gitee???????

轉(zhuǎn)載請注明出處文章來源地址http://www.zghlxwxcb.cn/news/detail-430563.html

到了這里,關(guān)于springboot整合Minio + vue 實現(xiàn)文件分片上傳(完整代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • springboot整合vue2-uploader文件分片上傳、秒傳、斷點續(xù)傳

    springboot整合vue2-uploader文件分片上傳、秒傳、斷點續(xù)傳

    vue-simple-uploader 是基于 simple-uploader.js 封裝的vue上傳插件。它的優(yōu)點包括且不限于以下幾種: 支持文件、多文件、文件夾上傳;支持拖拽文件、文件夾上傳 可暫停、繼續(xù)上傳 錯誤處理 支持“秒傳”,通過文件判斷服務(wù)端是否已存在從而實現(xiàn)“秒傳” 分片上傳 支持進度、預(yù)估

    2024年02月06日
    瀏覽(35)
  • 【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳

    【SpringBoot整合系列】SpringBoot 實現(xiàn)大文件分片上傳、斷點續(xù)傳及秒傳

    小文件(圖片、文檔、視頻)上傳可以直接使用很多ui框架封裝的上傳組件,或者自己寫一個input 上傳,利用FormData 對象提交文件數(shù)據(jù),后端使用spring提供的MultipartFile進行文件的接收,然后寫入即可。 但是對于比較大的文件,比如上傳2G左右的文件(http上傳),就需要將文件

    2024年04月16日
    瀏覽(37)
  • Spring Boot整合Minio實現(xiàn)上傳憑證、分片上傳、秒傳和斷點續(xù)傳

    Spring Boot整合Minio后,前端的文件上傳有兩種方式: 文件上傳到后端,由后端保存到Minio 這種方式好處是完全由后端集中管理,可以很好的做到、身份驗證、權(quán)限控制、文件與處理等,并且可以做一些額外的業(yè)務(wù)邏輯,比如生成縮略圖、提取元數(shù)據(jù)等。 缺點也很明顯: 延遲時

    2024年02月04日
    瀏覽(24)
  • SpringBoot + minio實現(xiàn)分片上傳、秒傳、續(xù)傳

    SpringBoot + minio實現(xiàn)分片上傳、秒傳、續(xù)傳

    MinIO是一個基于Go實現(xiàn)的高性能、兼容S3協(xié)議的對象存儲。它采用GNU AGPL v3開源協(xié)議,項目地址是https://github.com/minio/minio。 引用官網(wǎng): MinIO是根據(jù)GNU Affero通用公共許可證v3.0發(fā)布的高性能對象存儲。它與Amazon S3云存儲服務(wù)兼容。使用MinIO構(gòu)建用于機器學(xué)習(xí),分析和應(yīng)用程序數(shù)據(jù)工

    2024年02月08日
    瀏覽(23)
  • Minio文件分片上傳實現(xiàn)

    Minio文件分片上傳實現(xiàn)

    資源準(zhǔn)備 MacM1Pro 安裝Parallels19.1.0請參考 https://blog.csdn.net/qq_41594280/article/details/135420241 MacM1Pro Parallels安裝CentOS7.9請參考 https://blog.csdn.net/qq_41594280/article/details/135420461 部署Minio和整合SpringBoot請參考 https://blog.csdn.net/qq_41594280/article/details/135613722 Minio Paralles虛擬機文件百度網(wǎng)盤獲取

    2024年01月21日
    瀏覽(24)
  • Minio大文件分片上傳、斷點續(xù)傳實現(xiàn)

    Minio大文件分片上傳、斷點續(xù)傳實現(xiàn)

    使用minio api實現(xiàn)分片上傳及斷點續(xù)傳功能。 前端準(zhǔn)備:獲取大文件的MD5值,將文件分片,5M為一分片,排好順序,并按順序命名(1,2,3這種后面比較好合并) 在上傳分片階段,前端有上傳進度條 1、檢驗文件MD5值 1.1 redis中查看MD5是否存在 1.2 判斷臨時文件夾是否存在 boolean d

    2024年02月09日
    瀏覽(26)
  • minio&前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳&minio分片上傳實現(xiàn)

    minio&前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳&minio分片上傳實現(xiàn)

    ????????分布式文件系統(tǒng)-minio: 第一章:分布式文件系統(tǒng)介紹與minio介紹與使用(附minio java client 使用) 第二章:minio前后端分離上傳視頻/上傳大文件——前后端分離斷點續(xù)傳minio分片上傳實現(xiàn) 斷點續(xù)傳指的是在下載或上傳時,將下載或上傳任務(wù)(一個文件或一個壓縮包

    2024年02月03日
    瀏覽(44)
  • SpringBoot整合minio,文件的上傳下載,批量獲取

    Minio是GlusterFS創(chuàng)始人之一Anand Babu Periasamy發(fā)布新的開源項目。基于Apache License v2.0開源協(xié)議的對象存儲項目,采用Golang實現(xiàn),客戶端支Java,Python,Javacript, Golang語言。 其設(shè)計的主要目標(biāo)是作為私有云對象存儲的標(biāo)準(zhǔn)方案。主要用于存儲海量的圖片,視頻,文檔等。非常適合于存儲

    2024年02月12日
    瀏覽(30)
  • 【java】java實現(xiàn)大文件的分片上傳與下載(springboot+vue3)

    【java】java實現(xiàn)大文件的分片上傳與下載(springboot+vue3)

    源碼: https://gitee.com/gaode-8/big-file-upload 演示視頻 https://www.bilibili.com/video/BV1CA411f7np/?vd_source=1fe29350b37642fa583f709b9ae44b35 對于超大文件上傳我們可能遇到以下問題 ? 大文件直接上傳,占用過多內(nèi)存,可能導(dǎo)致內(nèi)存溢出甚至系統(tǒng)崩潰 ? 受網(wǎng)絡(luò)環(huán)境影響,可能導(dǎo)致傳輸中斷,只能重

    2024年02月02日
    瀏覽(29)
  • SpringBoot整合Minio(實現(xiàn)上傳與下載)

    SpringBoot整合Minio(實現(xiàn)上傳與下載)

    MinIO 是一款高性能、分布式的對象存儲系統(tǒng) . 它是一款軟件產(chǎn)品, 可以100%的運行在標(biāo)準(zhǔn)硬件。即X86等低成本機器也能夠很好的運行MinIO。 1.導(dǎo)入環(huán)境所需要的依賴 2.application.yml中進行minio的配置 3.MinIoConfig.class 配置類? 此類主要做一些連接Minio實例化對象的配置 ?4.AjaxResult.cl

    2024年02月07日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包