SpringBoot教程(三十三)| SpringBoot集成MinIO
SpringBoot教程(三十三)| SpringBoot集成MinIO
- 一、MinIO 是什么?
- 二、MinIO 与主流存储方案的对比分析
- 三、MinIO 适用场景界定
- 四、Windows 环境下 MinIO 部署与配置
- 1. 下载 MinIO
- 2. 启动 MinIO
- 3. 汉化操作
- 4. MinIO 核心概念解析
- 5. MinIO 基础操作指南(Web 控制台)
- 五、SpringBoot集成MinIO
- 1. 引入maven
- 2. 配置 MinIO 连接信息
- 3. 创建 MinIO 客户端配置类
- 4. 封装 MinIO 工具类
- 5. 编写测试接口
- 6. 测试验证
一、MinIO 是什么?
MinIO 是一款基于对象存储模型的高性能开源存储方案,采用 Go 语言开发,具备轻量部署、高可用、可扩展等核心特性。
其核心价值在于为用户提供私有化对象存储服务——通过将本地磁盘或服务器存储资源转化为标准化的对象存储服务,实现对图片、视频、文档等非结构化数据的高效管理,同时支持 API 集成、权限管控、数据备份等企业级能力,兼顾个人测试与生产环境部署需求。
二、MinIO 与主流存储方案的对比分析
为明确 MinIO 的适用场景,以下从存储模型、部署方式、核心优势等维度,将其与常见存储方案进行对比:
| 存储方案 | 存储模型 | 部署模式 | 核心优势 | 适用场景 | 局限性 |
|---|---|---|---|---|---|
| MinIO | 对象存储 | 单机/分布式 | 轻量部署、高可用、API 兼容 S3、私有化可控 | 企业私有化文件存储、业务数据备份、开发测试环境 | 需自行维护服务器硬件与部署环境 |
| 本地磁盘存储(Ext4/NTFS) | 文件系统 | 单机本地 | 部署零成本、操作直观 | 个人临时存储、单节点小型工具 | 无共享能力、扩容受限、数据可靠性低 |
| 公有云对象存储(OSS/COS) | 对象存储 | 公有云托管 | 零运维、弹性扩容、按需付费 | 无私有化需求的互联网业务、轻量级应用 | 数据归属第三方、长期使用成本较高 |
| 分布式文件系统(FastDFS) | 文件系统 | 集群部署 | 高并发访问支持、适合大文件存储 | 短视频平台、海量日志存储等超大规模场景 | 部署复杂度高、运维成本高、适配门槛高 |
三、MinIO 适用场景界定
-
私有化部署场景:企业对数据私密性要求较高(如合同文档、内部研发资料),需将数据存储在自有服务器集群,MinIO 可通过分布式部署实现数据本地化管控。
-
业务数据存储场景:电商平台商品图片、社交 APP 用户头像、教育平台课程视频等非结构化数据存储,MinIO 支持高并发读写,适配业务流量波动。
-
开发测试场景:开发者需快速搭建轻量化存储服务用于功能测试,MinIO 单机部署仅需 1 个可执行文件,5 分钟内即可完成环境搭建。
-
数据备份场景:企业核心业务数据需异地备份,MinIO 支持跨节点数据同步与多副本存储,保障数据冗余安全。
四、Windows 环境下 MinIO 部署与配置
1. 下载 MinIO
硬件要求:Windows 7 及以上 64 位系统,建议 CPU 2 核及以上、内存 4GB 及以上,存储盘预留至少 10GB 可用空间(用于存储服务数据)。
软件下载:(推荐方式二)
- 方式一:访问 MinIO 官方下载页(https://min.io/download)(太慢了),
下载核心服务端程序minio.exe;若需命令行管理工具,可同步下载客户端mc.exe(可选)。

- 方式二:访问 MinIO 国内镜像地址(https://www.minio.org.cn/)(速度飞起)
下载核心服务端程序minio.exe;若需命令行管理工具,可同步下载客户端mc.exe(可选)。

2. 启动 MinIO
(1)规划目录
首先在本地磁盘(建议非系统盘,如 D 盘)创建标准化目录结构,用于统一管理程序与数据:
以下是我存放的位置(你们可以根据情况自行更改)
- 主目录:
D:\Develop\Env\MinIO(存放部署相关文件) - 程序目录:
D:\Develop\Env\MinIO\bin(存放minio.exe、mc.exe等可执行文件) - 数据目录:
D:\Develop\Env\MinIO\data(用于存储 MinIO 管理的对象数据,避免临时目录导致数据丢失) - 日志目录:
D:\Develop\Env\MinIO\logs(用于存储服务启动日志,便于问题排查)


(2)创建启动脚本
创建一个txt文件,然后复制下面的命令(脚本中的位置根据你的存放的位置自行修改),在改成bat后缀
@echo off
:: 切换编码为 UTF-8,避免中文乱码
chcp 65001 > nul
echo 正在启动MinIO服务...
:: 切换至程序目录
cd D:\Develop\Env\MinIO\bin
:: 启动服务,指定数据目录、控制台端口
minio.exe server D:\Develop\Env\MinIO\data --console-address ":9001"
pause
然后放到 和 bin 目录同级别

(3)双击启动

访问 http://127.0.0.1:9001,用户名和密码默认都为 minioadmin


3. 汉化操作
目前没有汉化配置。有的话也是和源码相关的(忒麻烦),所以最快的方式就是浏览器翻译


4. MinIO 核心概念解析
-
桶(Bucket):MinIO 的基本存储单元,用于对对象进行分类管理,类似文件系统中的 “文件夹”,但无层级嵌套限制(可通过对象名中的 “/” 模拟层级)。
命名规则:3-63 个字符,仅支持小写字母、数字、横线,且不能以横线开头或结尾。 -
对象(Object):MinIO 中存储的核心数据单元,对应实际的文件(如图片、文档),由 “文件内容 + 元数据” 组成。
元数据包含文件大小、类型、创建时间等基础信息,支持自定义扩展(如给文件添加业务标签)。 -
API 服务端口:默认 9000 端口,用于客户端(如 Spring Boot 应用)通过 SDK 集成 MinIO,实现文件上传、下载等自动化操作。
-
Web 控制台端口:默认 9001 端口,提供可视化管理界面,支持手动创建桶、上传文件、配置权限等操作。
5. MinIO 基础操作指南(Web 控制台)
(1)创建存储桶
登录 Web 控制台后,点击左侧菜单栏 “Create Bucket”。输入桶名称(如 test-123)


你可以看看data下面,就出现了你创建的那个桶了

(2)对象操作—桶下面创建文件夹
前提是你创建文件夹以后,还得再到这个新文件夹里面上传一个资源才行,不然就属于无效



(3)对象操作—桶下面创建文件

你可以看看data下面,就出现了《API命名规范.txt》的这个文件夹

和windows的不一样,windows 就直接是文件了,而它是以文件夹方式的,里面还有一层,这个 xl.meta 才是《API命名规范.txt》的真正的数据

五、SpringBoot集成MinIO
1. 引入maven
我这边是jdk1.8 建议选择 8.5.6
<!-- MinIO 官方 Java SDK -->
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.6</version> <!-- JDK 1.8 推荐版本 -->
</dependency><!-- 用于文件流处理的工具类(可选) -->
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
2. 配置 MinIO 连接信息
在 application.yml 中配置 MinIO 连接参数:
spring:minio:endpoint: http://localhost:9000 # MinIO API 地址access-key: minioadmin # 管理员账号secret-key: minioadmin # 管理员密码bucket-name: test-bucket # 默认操作的桶(提前创建)base-url: http://localhost:9000/test-bucket/ # 公开桶的文件访问基础路径
3. 创建 MinIO 客户端配置类
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MinioConfig {// 从配置文件读取 MinIO 连接参数@Value("${spring.minio.endpoint}")private String endpoint;@Value("${spring.minio.access-key}")private String accessKey;@Value("${spring.minio.secret-key}")private String secretKey;/*** 初始化 MinIO 客户端,交给 Spring 容器管理* @return MinioClient 实例*/@Beanpublic MinioClient minioClient() {return MinioClient.builder().endpoint(endpoint) // 设置 API 地址.credentials(accessKey, secretKey) // 设置账号密码.build();}
}
4. 封装 MinIO 工具类
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;@Component
public class MinioUtil {@Autowiredprivate MinioClient minioClient;@Value("${spring.minio.bucket-name}")private String defaultBucket; // 默认桶名@Value("${spring.minio.base-url}")private String baseUrl; // 公开桶的文件访问基础路径/*** 创建桶(如果桶不存在)* @param bucketName 桶名* @throws Exception 异常*/public void createBucket(String bucketName) throws Exception {// 判断桶是否存在if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {// 不存在则创建桶minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 上传文件(支持 MultipartFile 类型,Spring 接收前端文件的标准类型)* @param file 前端上传的文件* @param bucketName 桶名(可为空,使用默认桶)* @return 文件访问路径* @throws Exception 异常*/public String uploadFile(MultipartFile file, String bucketName) throws Exception {// 若未指定桶名,使用默认桶if (bucketName == null || bucketName.isEmpty()) {bucketName = defaultBucket;}// 确保桶存在createBucket(bucketName);// 生成唯一文件名(前缀+时间戳+原文件名,避免重复)String originalFilename = file.getOriginalFilename();String fileName = "upload/" + System.currentTimeMillis() + "_" + originalFilename;// 上传文件到 MinIOminioClient.putObject(PutObjectArgs.builder().bucket(bucketName) // 桶名.object(fileName) // 存储在 MinIO 中的文件名(可包含路径,如 "img/123.jpg")// 文件流(MultipartFile 的输入流).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()) // 文件类型(如 image/jpeg).build());// 返回文件访问路径(公开桶可直接访问)return baseUrl + fileName;}/*** 下载文件(返回文件输入流,用于前端下载)* @param fileName 文件名(与上传时的 fileName 一致)* @param bucketName 桶名(可为空,使用默认桶)* @return 文件输入流* @throws Exception 异常*/public InputStream downloadFile(String fileName, String bucketName) throws Exception {if (bucketName == null || bucketName.isEmpty()) {bucketName = defaultBucket;}// 获取文件输入流return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());}/*** 删除文件* @param fileName 文件名* @param bucketName 桶名(可为空,使用默认桶)* @throws Exception 异常*/public void deleteFile(String fileName, String bucketName) throws Exception {if (bucketName == null || bucketName.isEmpty()) {bucketName = defaultBucket;}// 删除文件minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());}/*** 获取桶列表* @return 桶列表* @throws Exception 异常*/public List<Bucket> getBucketList() throws Exception {return minioClient.listBuckets();}/*** 获取私有文件的临时访问链接(适用于私有桶,链接有有效期)* @param fileName 文件名* @param bucketName 桶名(可为空,使用默认桶)* @param expireSeconds 链接有效期(秒)* @return 临时访问链接* @throws Exception 异常*/public String getPresignedUrl(String fileName, String bucketName, int expireSeconds) throws Exception {if (bucketName == null || bucketName.isEmpty()) {bucketName = defaultBucket;}// 生成临时访问链接return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET) // 请求方法.bucket(bucketName).object(fileName).expiry(expireSeconds, TimeUnit.SECONDS) // 链接有效期.build());}
}
5. 编写测试接口
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;@RestController
@RequestMapping("/minio")
public class MinioController {@Autowiredprivate MinioUtil minioUtil;/*** 测试上传文件* @param file 前端上传的文件(form-data 格式)* @return 文件访问路径*/@PostMapping("/upload")public String uploadFile(@RequestParam("file") MultipartFile file) {try {// 上传到默认桶,返回文件访问路径return minioUtil.uploadFile(file, null);} catch (Exception e) {return "上传失败:" + e.getMessage();}}/*** 测试下载文件* @param fileName 文件名(与上传时返回的路径中的文件名部分一致)* @return 响应实体(文件字节流)*/@GetMapping("/download")public ResponseEntity<byte[]> downloadFile(@RequestParam("fileName") String fileName) {try {// 获取文件输入流InputStream in = minioUtil.downloadFile(fileName, null);// 转换为字节数组byte[] bytes = IOUtils.toByteArray(in);// 设置响应头(解决中文文件名乱码)HttpHeaders headers = new HttpHeaders();headers.setContentDispositionFormData("attachment", new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);// 返回文件字节流return new ResponseEntity<>(bytes, headers, HttpStatus.OK);} catch (Exception e) {return ResponseEntity.status(500).body(("下载失败:" + e.getMessage()).getBytes());}}/*** 测试删除文件* @param fileName 文件名* @return 操作结果*/@DeleteMapping("/delete")public String deleteFile(@RequestParam("fileName") String fileName) {try {// 删除文件minioUtil.deleteFile(fileName, null);return "删除成功";} catch (Exception e) {return "删除失败:" + e.getMessage();}}/*** 测试获取私有文件的临时访问链接* @param fileName 文件名* @return 临时访问链接(有效期 1 小时)*/@GetMapping("/getUrl")public String getFileUrl(@RequestParam("fileName") String fileName) {try {// 获取临时链接,有效期 3600 秒(1 小时)return minioUtil.getPresignedUrl(fileName, null, 3600);} catch (Exception e) {return "获取链接失败:" + e.getMessage();}}
}
6. 测试验证
- 启动 SpringBoot 项目和 MinIO 服务。
- 使用 Postman 测试接口:
- 上传文件:发送 POST 请求到 http://localhost:8080/minio/upload,参数类型 form-data,键 file,值选择本地文件,返回文件访问路径。
- 下载文件:发送 GET 请求到 http://localhost:8080/minio/download?fileName=上传返回的文件名,浏览器自动下载文件。
- 删除文件:发送 DELETE 请求到 http://localhost:8080/minio/delete?fileName=文件名,返回删除结果。
- 获取临时链接:发送 GET 请求到 http://localhost:8080/minio/getUrl?fileName=文件名,返回有效期 1 小时的临时访问链接。
