Spring Boot 整合 Minio 實現高效文件存儲解決方案(本地和線上)

文章目錄

  • 前言
  • 一、配置
    • 1.配置文件:application.yml
    • 2.配置類:MinioProperties
    • 3.工具類:MinioUtil
      • 3.1 初始化方法
      • 3.2 核心功能
      • 3.3 關鍵技術點
  • 二、使用示例
    • 1.控制器類:FileController
    • 2.服務類
    • 3.效果展示
  • 總結


前言

  • Minio 是一個高性能的分布式對象存儲系統,專為云原生應用而設計
  • 作為 Amazon S3 的兼容替代品,它提供了簡單易用的 API,支持海量非結構化數據存儲
  • 在微服務架構中,文件存儲是常見需求,而 Minio 以其輕量級、高可用和易部署的特點成為理想選擇

一、配置

1.配置文件:application.yml

vehicle:minio:url: http://localhost:9000 # 連接地址,如果是線上的將:localhost->ipusername: minio # 登錄用戶名password: 12345678 # 登錄密碼bucketName: vehicle # 存儲文件的桶的名字
  • url:Minio 服務器地址,線上環境替換為實際 IP 或域
  • username/password:Minio 控制臺登錄憑證
  • bucketName:文件存儲桶名稱,類似文件夾概念
  • HTTPS 注意:若配置域名訪問,URL 需寫為 https://your.domain.name:9090

2.配置類:MinioProperties

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@Data
@ConfigurationProperties(prefix = "vehicle.minio")
public class MinioProperties {private String url;private String username;private String password;private String bucketName;
}
  • @ConfigurationProperties:將配置文件中的屬性綁定到類字段
  • @Component:使該類成為 Spring 管理的 Bean
  • 提供 Minio 連接所需的所有配置參數

3.工具類:MinioUtil

import cn.hutool.core.lang.UUID;
import com.fc.properties.MinioProperties;
import io.minio.*;
import io.minio.errors.*;
import lombok.RequiredArgsConstructor;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import io.minio.http.Method;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;/*** 文件操作工具類*/
@RequiredArgsConstructor
@Component
public class MinioUtil {private final MinioProperties minioProperties;//配置類private MinioClient minioClient;//連接客戶端private String bucketName;//桶的名字// 初始化 Minio 客戶端@PostConstructpublic void init() {try {//創建客戶端minioClient = MinioClient.builder().endpoint(minioProperties.getUrl()).credentials(minioProperties.getUsername(), minioProperties.getPassword()).build();bucketName = minioProperties.getBucketName();// 檢查桶是否存在,不存在則創建boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());if (!bucketExists) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}} catch (Exception e) {throw new RuntimeException("Minio 初始化失敗", e);}}/** 上傳文件*/public String uploadFile(MultipartFile file,String extension) {if (file == null || file.isEmpty()) {throw new RuntimeException("上傳文件不能為空");}try {// 生成唯一文件名String uniqueFilename = generateUniqueFilename(extension);// 上傳文件minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(uniqueFilename).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());return "/" + bucketName + "/" + uniqueFilename;} catch (Exception e) {throw new RuntimeException("文件上傳失敗", e);}}/*** 上傳已處理的圖片字節數組到 MinIO** @param imageData 處理后的圖片字節數組* @param extension 文件擴展名(如 ".jpg", ".png")* @param contentType 文件 MIME 類型(如 "image/jpeg", "image/png")* @return MinIO 中的文件路徑(格式:/bucketName/yyyy-MM-dd/uuid.extension)*/public String uploadFileByte(byte[] imageData, String extension, String contentType) {if (imageData == null || imageData.length == 0) {throw new RuntimeException("上傳的圖片數據不能為空");}if (extension == null || extension.isEmpty()) {throw new IllegalArgumentException("文件擴展名不能為空");}if (contentType == null || contentType.isEmpty()) {throw new IllegalArgumentException("文件 MIME 類型不能為空");}try {// 生成唯一文件名String uniqueFilename = generateUniqueFilename(extension);// 上傳到 MinIOminioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(uniqueFilename).stream(new ByteArrayInputStream(imageData), imageData.length, -1).contentType(contentType).build());return "/" + bucketName + "/" + uniqueFilename;} catch (Exception e) {throw new RuntimeException("處理后的圖片上傳失敗", e);}}/*** 上傳本地生成的 Excel 臨時文件到 MinIO* @param localFile  本地臨時文件路徑* @param extension 擴展名* @return MinIO 存儲路徑,格式:/bucketName/yyyy-MM-dd/targetName*/public String uploadLocalExcel(Path localFile, String extension) {if (localFile == null || !Files.exists(localFile)) {throw new RuntimeException("本地文件不存在");}try (InputStream in = Files.newInputStream(localFile)) {String objectKey = generateUniqueFilename(extension); // 保留日期目錄minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectKey).stream(in, Files.size(localFile), -1).contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet").build());return "/" + bucketName + "/" + objectKey;} catch (Exception e) {throw new RuntimeException("Excel 上傳失敗", e);}}/** 根據URL下載文件*/public void downloadFile(HttpServletResponse response, String fileUrl) {if (fileUrl == null || !fileUrl.contains(bucketName + "/")) {throw new IllegalArgumentException("無效的文件URL");}try {// 從URL中提取對象路徑和文件名String objectUrl = fileUrl.split(bucketName + "/")[1];String fileName = objectUrl.substring(objectUrl.lastIndexOf("/") + 1);// 設置響應頭response.setContentType("application/octet-stream");String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");// 下載文件try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectUrl).build());OutputStream outputStream = response.getOutputStream()) {// 用IOUtils.copy高效拷貝(內部緩沖區默認8KB)IOUtils.copy(inputStream, outputStream);}} catch (Exception e) {throw new RuntimeException("文件下載失敗", e);}}/*** 根據 MinIO 路徑生成帶簽名的直鏈* @param objectUrl 已存在的 MinIO 路徑(/bucketName/...)* @param minutes   鏈接有效期(分鐘)* @return 可直接訪問的 HTTPS 下載地址*/public String parseGetUrl(String objectUrl, int minutes) {if (objectUrl == null || !objectUrl.startsWith("/" + bucketName + "/")) {throw new IllegalArgumentException("非法的 objectUrl");}String objectKey = objectUrl.substring(("/" + bucketName + "/").length());try {return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectKey).expiry(minutes, TimeUnit.MINUTES).build());} catch (Exception e) {throw new RuntimeException("生成直鏈失敗", e);}}/** 根據URL刪除文件*/public void deleteFile(String fileUrl) {try {// 從URL中提取對象路徑String objectUrl = fileUrl.split(bucketName + "/")[1];minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectUrl).build());} catch (Exception e) {throw new RuntimeException("文件刪除失敗", e);}}/** 檢查文件是否存在*/public boolean fileExists(String fileUrl) {if (fileUrl == null || !fileUrl.contains(bucketName + "/")) {return false;}try {String objectUrl = fileUrl.split(bucketName + "/")[1];minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectUrl).build());return true;} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |XmlParserException e) {if (e instanceof ErrorResponseException && ((ErrorResponseException) e).errorResponse().code().equals("NoSuchKey")) {return false;}throw new RuntimeException("檢查文件存在失敗", e);}}/*** 生成唯一文件名(帶日期路徑 + UUID)*/private String generateUniqueFilename(String extension) {String dateFormat = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));String uuid = UUID.randomUUID().toString().replace("-", ""); // 去掉 UUID 中的 "-"return dateFormat + "/" + uuid + extension;}
}

3.1 初始化方法

  • 使用 @PostConstruct 在 Bean 初始化后自動執行

  • 創建 MinioClient 客戶端實例

  • 檢查并創建存儲桶(若不存在)

3.2 核心功能

方法名功能描述參數說明返回值
uploadFile()上傳MultipartFile文件文件對象,擴展名文件路徑
uploadFileByte()上傳字節數組字節數據,擴展名,MIME類型文件路徑
uploadLocalExcel()上傳本地Excel文件文件路徑,擴展名文件路徑
downloadFile()下載文件到響應流HTTP響應對象,文件URL
parseGetUrl()生成帶簽名直鏈文件路徑,有效期(分鐘)直鏈URL
deleteFile()刪除文件文件URL
fileExists()檢查文件是否存在文件URL布爾值

3.3 關鍵技術點

  • 唯一文件名生成:日期目錄/UUID.擴展名 格式避免重名

  • 大文件流式傳輸:避免內存溢出

  • 響應頭編碼處理:解決中文文件名亂碼問題

  • 異常統一處理:Minio 異常轉換為運行時異常

  • 預簽名URL:生成臨時訪問鏈接

二、使用示例

1.控制器類:FileController

import com.fc.result.Result;
import com.fc.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Api(tags = "文件")
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileController {private final FileService fileService;@ApiOperation("圖片上傳")@PostMapping("/image")public Result<String> imageUpload(MultipartFile file) throws IOException {String url = fileService.imageUpload(file);return Result.success(url);}@ApiOperation("圖片下載")@GetMapping("/image")public void imageDownLoad(HttpServletResponse response, String url) throws IOException {fileService.imageDownload(response, url);}@ApiOperation("圖片刪除")@DeleteMapping("/image")public Result<Void> imageDelete(String url) {fileService.imageDelete(url);return Result.success();}}

2.服務類

FileService

import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public interface FileService {String imageUpload(MultipartFile file) throws IOException;void imageDownload(HttpServletResponse response, String url) throws IOException;void imageDelete(String url);
}

FileServiceImpl

import com.fc.exception.FileException;
import com.fc.service.FileService;
import com.fc.utils.ImageUtil;
import com.fc.utils.MinioUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {private final MinioUtil minioUtil;@Overridepublic String imageUpload(MultipartFile file) throws IOException {byte[] bytes = ImageUtil.compressImage(file, "JPEG");return minioUtil.uploadFileByte(bytes, ".jpeg", "image/jpeg");}@Overridepublic void imageDownload(HttpServletResponse response, String url) throws IOException {minioUtil.downloadFile(response, url);}@Overridepublic void imageDelete(String url) {if (!minioUtil.fileExists(url)) {throw new FileException("文件不存在");}minioUtil.deleteFile(url);}
}

3.效果展示

利用Apifox測試下三個接口

圖片上傳

在這里插入圖片描述
在這里插入圖片描述

圖片下載
圖片下載
刪除圖片
在這里插入圖片描述
在這里插入圖片描述

總結

本文通過 “配置 - 工具 - 業務” 三層架構,實現了 Spring Boot 與 MinIO 的集成,核心優勢如下:

  • 易用性:通過配置綁定和工具類封裝,簡化 MinIO 操作,開發者無需關注底層 API 細節。
  • 靈活性:支持多種文件類型(表單文件、字節流、本地文件),滿足不同場景需求(如圖片壓縮、Excel 生成)。
  • 可擴展性:可基于此框架擴展功能,如添加文件權限控制(通過 MinIO 的 Policy)、文件分片上傳(大文件處理)、定期清理過期文件等。

MinIO 作為輕量級對象存儲方案,非常適合中小項目替代本地存儲或云廠商 OSS(降低成本)。實際應用中需注意:生產環境需配置 MinIO 集群確保高可用;敏感文件需通過預簽名 URL 控制訪問權限;定期備份桶數據以防丟失。通過本文的方案,開發者可快速搭建穩定、可擴展的文件存儲服務,為應用提供可靠的非結構化數據管理能力。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917919.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917919.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917919.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Unity3D實例-功能-鏡頭】第三人稱視覺-鏡頭優化

這一篇我們一起來調整一下Cinemachine的第三人稱視覺的鏡頭設置。一般用于ARPG角色扮演游戲的場景中。Unity里頭&#xff0c;這種視角簡直就是標配。來吧&#xff0c;咱們一起研究研究怎么調出這種視角效果&#xff01;目錄&#xff1a;1.調整虛擬攝像機的Y軸2.調整虛擬攝像機的…

二叉樹算法之【中序遍歷】

目錄 LeetCode-94題 LeetCode-94題 給定一個二叉樹的根節點root&#xff0c;返回它的中序遍歷結果。 class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> result new ArrayList<>();order(root, result);return res…

Android14的QS面板的加載解析

/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java QS 面板的創建 getNotificationShadeWindowView()&#xff1a;整個systemui的最頂級的視圖容器&#xff08;super_notification_shade.xml&#xff09;R.id.qs_frame &…

解鎖webpack核心技能(二):配置文件和devtool配置指南

一、配置文件webpack 提供的 cli 支持很多的參數&#xff0c;例如 --mode 。在我們平時的開發過程中&#xff0c;我們要學習很多的功能&#xff0c;這些很多都是可以用參數來完成的。那么后邊就會導致參數越來越多&#xff0c;我們使用命令特別的不方便&#xff0c;所以我們會使…

Gitlab+Jenkins+K8S+Registry 建立 CI/CD 流水線

一、前言 DevOps是一種將開發&#xff08;Development&#xff09;和運維&#xff08;Operations&#xff09;相結合的軟件開發方法論。它通過自動化和持續交付的方式&#xff0c;將軟件開發、測試和部署等環節緊密集成&#xff0c;以提高效率和產品質量。在本篇博客中&#xf…

【Linux】特效爆滿的Vim的配置方法 and make/Makefile原理

一、軟件包管理器 1、Linux下安裝軟件的常見方式&#xff1a; 1&#xff09;源代碼安裝——不推薦。 2&#xff09;rpm包安裝——不推薦。 3&#xff09;包管理器安裝——推薦 2、安裝軟件命令 # Centos$ sudo yum install -y lrzsz# Ubuntu$ sudo apt install -y lrzsz 3、卸…

Spring Boot Actuator 監控功能的簡介及禁用

Spring Boot Actuator: Production-ready Features 1. 添加依賴 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency> </dependencie…

Matlab(1)

一、基本操作1. matlab四則運算規則&#xff1a;先乘除后加減&#xff0c;從左到右2、對數和指數的表示sin(pi^0.5)log(tan(1))exp&#xff08;sin&#xff08;10&#xff09;&#xff09;3、類型&#xff1a;matlab變量默認為double4、who&whos&#xff1a;命令行輸入who&…

Kotlin Android 開發腳手架封裝

Kotlin Android 開發腳手架封裝&#xff08;模塊化版本&#xff09; 我將按照模塊化設計原則&#xff0c;將腳手架拆分為多個文件&#xff0c;每個文件負責特定功能領域&#xff1a; 1. 核心初始化模塊 文件路徑: core/AppScaffold.kt object AppScaffold {lateinit var contex…

Flutter 報錯解析:No TabController for TabBar 的完整解決方案

目錄 Flutter 報錯解析&#xff1a;No TabController for TabBar 的完整解決方案 一、錯誤場景&#xff1a;當 TabBar 失去 "指揮官" 二、為什么 TabBar 必須依賴 Controller&#xff1f; 1. TabBar 與 TabController 的協作關系 2. 狀態管理的核心作用 3. 實戰…

【24】C++實戰篇——【 C++ 外部變量】 C++多個文件共用一個枚舉變量,外部變量 extern,枚舉外部變量 enum

文章目錄1 方法2 外部變量 應用2.1 普通外部全局變量2.2 枚舉外部全局變量 應用2.2.2 枚舉外部變量優化c多個文件中如何共用一個全局變量 c頭文件的使用和多個文件中如何共用一個全局變量 C共享枚舉類型給QML 1 方法 ①頭文件中 聲明外部全局變量&#xff1b; ②在頭文件對…

Linux SELinux 核心概念與管理

Linux SELinux 核心概念與管理一、SELinux 基本概念 SELinux 即安全增強型 Linux&#xff08;Security-Enhanced Linux&#xff09;&#xff0c;由美國國家安全局&#xff08;NSA&#xff09;開發&#xff0c;是一套基于強制訪問控制&#xff08;MAC&#xff09;的安全機制&…

Git 中**未暫存**和**未跟蹤**的區別:

文件狀態分類 Git 中的文件有以下幾種狀態&#xff1a; 工作區文件狀態&#xff1a; ├── 未跟蹤 (Untracked) ├── 已跟蹤 (Tracked)├── 未修改 (Unmodified) ├── 已修改未暫存 (Modified/Unstaged)└── 已暫存 (Staged)1. 未跟蹤 (Untracked) 定義&#xff1a;Gi…

前端1.0

目錄 一、 什么是前端 二、 HTML 1.0 概述 2.0 注釋 三、開發環境的搭建 1.0 插件 2.0 筆記 四、 常見標簽&#xff08;重點&#xff09; 四、案例展示&#xff08;圖片代碼&#xff09; 五、CSS引入 一、 什么是前端 web前端 用來直接給用戶呈現一個一個的網頁 …

Flutter鏡像替換

一、核心鏡像替換&#xff08;針對 Maven 倉庫&#xff09; Flutter 依賴的 Google Maven 倉庫&#xff08;https://maven.google.com 或 https://dl.google.com/dl/android/maven2&#xff09;可替換為國內鏡像&#xff0c;常見的有&#xff1a;阿里云鏡像&#xff08;推薦&am…

MATLAB實現的改進遺傳算法用于有約束優化問題

基于MATLAB實現的改進遺傳算法&#xff08;GA&#xff09;用于有約束優化問題的代碼&#xff0c;包括處理非線性約束。此代碼通過引入懲罰函數和修復機制&#xff0c;有效處理約束條件&#xff0c;提高算法的魯棒性和收斂速度。 1. 定義優化問題 % 定義目標函數 function f ob…

Qt子類化QWidget后,使用setStyleSheet設置樣式無效的解決方案

關鍵代碼&#xff1a; #include <QPainter> #include <QStyleOption>void paintEvent(QPaintEvent *e) {QStyleOption opt;opt.init(this);QPainter p(this);style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);QWidget::paintEvent(e); }定義…

【python中級】關于Flask服務在同一系統里如何只被運行一次

【python中級】關于Flask服務在同一系統里如何只被運行一次 1.背景 2.方案1 2.方案2 1.背景 python Flask實現的一個http服務,打包成應用程序exe后在windows10系統運行; 由于我會不斷的更新這個http服務,我希望運行這個http服務的時候之前的http服務被停掉; 即實現 Pytho…

git配置公鑰/密鑰

遇到 “gitgithub.com: Permission denied (publickey)” 錯誤通常意味著你嘗試通過 SSH 連接到 GitHub 時&#xff0c;SSH 密鑰沒有被正確設置或者 GitHub 無法識別你的公鑰。這里有幾個步驟可以幫助你解決這個問題&#xff1a; 檢查 SSH 密鑰 首先&#xff0c;確保你已經在本…

【機器學習】“回歸“算法模型的三個評估指標:MAE(衡量預測準確性)、MSE(放大大誤差)、R2(說明模型解釋能力)

文章目錄一、MAE、MSE、r概念說明二、MAE&#xff08;平均絕對誤差&#xff09;&#xff1a;用"房價預測"理解誤差測量三、MSE&#xff08;均方誤差&#xff09;&#xff1a;誤差的"放大鏡"1、概念說明2、 sklearn代碼實踐3、流程總結四、R&#xff1a;理解…