# SpringBoot集成MinIO企业级实战教程
本文从零开始,基于IDEA 2023 Ultimate版搭建企业级SpringBoot-MinIO项目,涵盖MinIO配置、核心API封装、接口开发、异常处理等全流程,代码符合企业级规范,而非简单Demo。
# 1. 环境准备
# 1.1 开发环境清单
确保你的开发环境已满足以下要求:
| 工具/框架 | 版本号 |
|---|---|
| IDEA | 2023 Ultimate |
| JDK | 17 |
| Maven | 3.9.4 |
| SpringBoot | 3.5.10 |
| MinIO服务端 | RELEASE.2025-09-07T16-13-09Z |
| MinIO客户端SDK | 8.5.9(适配服务端版本) |
# 1.2 MinIO服务确认
你的MinIO服务信息:
- 服务地址:
192.168.31.112 - API端口:
9000(MinIO客户端连接用) - 控制台端口:
9001(Web管理端) - 用户名:
heproot - 密码:
hep123321
验证方式:打开浏览器访问 http://192.168.31.112:9001,输入上述账号密码,能正常登录控制台即表示服务可用。
# 2. 新建SpringBoot项目
# 2.1 创建项目

# 2.2 项目结构
初始化后,手动整理项目结构如下(后续代码按此结构编写):
SpringBoot-MinIO
├── src
│ ├── main
│ │ ├── java
│ │ │ └── cc
│ │ │ └── hep
│ │ │ ├── SpringBootMinIoApplication.java // 启动类
│ │ │ ├── config // 配置类
│ │ │ │ └── MinioConfig.java
│ │ │ ├── constant // 常量类
│ │ │ │ └── MinioConstant.java
│ │ │ ├── controller // 控制层
│ │ │ │ └── MinioController.java
│ │ │ ├── exception // 异常处理
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── MinioBusinessException.java
│ │ │ ├── service // 服务层
│ │ │ │ ├── MinioService.java
│ │ │ │ └── impl
│ │ │ │ └── MinioServiceImpl.java
│ │ │ ├── util // 工具类
│ │ │ │ └── MinioUtil.java
│ │ │ └── vo // 返回对象/入参
│ │ │ ├── Result.java
│ │ │ └── ResultCode.java
│ │ └── resources
│ │ └── application.yml // 配置文件
└── pom.xml // 依赖文件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 3. 引入核心依赖
修改pom.xml文件,添加MinIO客户端依赖及其他企业级必备依赖,确保版本适配SpringBoot 3.5.10:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cc.hep</groupId>
<artifactId>springboot-minio</artifactId>
<name>SpringBoot-MinIO</name>
<version>0.0.1-SNAPSHOT</version>
<description>SpringBoot 3.5.10 集成 MinIO 企业级实战项目</description>
<properties>
<java.version>17</java.version>
<!-- MinIO客户端版本,适配服务端RELEASE.2025-09-07T16-13-09Z -->
<minio.version>8.5.9</minio.version>
</properties>
<dependencies>
<!-- Spring Web核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MinIO Java客户端 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- 工具类:简化文件/字符串处理 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.28</version>
</dependency>
<!-- 校验依赖:参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- lombok:简化实体类编写(可选,企业级常用) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 4. MinIO配置
在resources目录下创建/修改application.yml,配置MinIO连接信息(企业级规范:区分环境,此处简化为默认配置):
server:
port: 8080 # 项目启动端口
spring:
servlet:
multipart:
max-file-size: 100MB # 单个文件最大大小
max-request-size: 500MB # 单次请求最大文件大小
# MinIO配置
minio:
endpoint: http://192.168.31.112:9000 # MinIO API地址(9000端口)
access-key: heproot # 用户名
secret-key: hep123321 # 密码
bucket-name: hep-default # 默认桶名(项目启动后自动创建)
url-prefix: http://192.168.31.112:9000/ # 文件访问前缀
connect-timeout: 60000 # 连接超时时间(毫秒)
read-timeout: 60000 # 读取超时时间(毫秒)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5. MinIO配置类
在cc.hep.config包下创建MinioConfig,用于创建MinIO客户端Bean,读取配置文件参数:
package cc.hep.config;
import io.minio.MinioClient;
import okhttp3.OkHttpClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
/**
* MinIO配置类
* 读取application.yml中的minio配置,创建MinioClient Bean
*/
@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {
/** MinIO API地址 */
private String endpoint;
/** 访问密钥(用户名) */
private String accessKey;
/** 秘密密钥(密码) */
private String secretKey;
/** 默认桶名 */
private String bucketName;
/** 文件访问前缀 */
private String urlPrefix;
/** 连接超时时间(毫秒) */
private int connectTimeout;
/** 读取超时时间(毫秒) */
private int readTimeout;
/**
* 创建MinioClient Bean,全局唯一
*/
@Bean
public MinioClient minioClient() {
// 创建自定义的OkHttpClient来配置超时时间
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(Duration.ofMillis(connectTimeout))
.readTimeout(Duration.ofMillis(readTimeout))
.build();
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.httpClient(httpClient)
.build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 6. 企业级基础封装
# 6.1 统一返回结果
在cc.hep.common包下创建统一返回对象,规范接口返回格式:
package cc.hep.common;
import lombok.Getter;
/**
* 统一返回码枚举
*/
@Getter
public enum ResultCode {
SUCCESS(200, "操作成功"),
FAIL(500, "操作失败"),
PARAM_ERROR(400, "参数错误"),
BUCKET_NOT_EXIST(404, "桶不存在"),
FILE_NOT_EXIST(404, "文件不存在"),
MINIO_CONNECT_ERROR(503, "MinIO连接失败");
private final int code;
private final String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cc.hep.common;
import lombok.Data;
/**
* 统一返回结果
* @param <T> 数据类型
*/
@Data
public class Result<T> {
/** 状态码 */
private int code;
/** 提示信息 */
private String msg;
/** 响应数据 */
private T data;
/** 成功返回(无数据) */
public static <T> Result<T> success() {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), null);
}
/** 成功返回(带数据) */
public static <T> Result<T> success(T data) {
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
}
/** 失败返回 */
public static <T> Result<T> fail() {
return new Result<>(ResultCode.FAIL.getCode(), ResultCode.FAIL.getMsg(), null);
}
/** 失败返回(自定义消息) */
public static <T> Result<T> fail(String msg) {
return new Result<>(ResultCode.FAIL.getCode(), msg, null);
}
/** 失败返回(自定义码+消息) */
public static <T> Result<T> fail(ResultCode resultCode) {
return new Result<>(resultCode.getCode(), resultCode.getMsg(), null);
}
/** 失败返回(自定义码+自定义消息) */
public static <T> Result<T> fail(int code, String msg, T data) {
return new Result<>(code, msg, data);
}
public Result(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 6.2 自定义异常
在cc.hep.exception包下创建自定义异常,用于处理MinIO相关业务异常:
package cc.hep.exception;
import cc.hep.common.ResultCode;
import lombok.Getter;
/**
* MinIO业务自定义异常
*/
@Getter
public class MinioBusinessException extends RuntimeException {
private final ResultCode resultCode;
public MinioBusinessException(ResultCode resultCode) {
super(resultCode.getMsg());
this.resultCode = resultCode;
}
public MinioBusinessException(String msg) {
super(msg);
this.resultCode = ResultCode.FAIL;
}
public MinioBusinessException(ResultCode resultCode, String msg) {
super(msg);
this.resultCode = resultCode;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 6.3 全局异常处理
在cc.hep.exception包下创建全局异常处理器,统一捕获并处理异常:
package cc.hep.exception;
import cc.hep.common.Result;
import cc.hep.common.ResultCode;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
/**
* 全局异常处理器
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理MinIO自定义业务异常
*/
@ExceptionHandler(MinioBusinessException.class)
public Result<?> handleMinioBusinessException(MinioBusinessException e) {
log.error("MinIO业务异常:{}", e.getMessage(), e);
return Result.fail(e.getResultCode());
}
/**
* 处理MinIO原生异常
*/
@ExceptionHandler(MinioException.class)
public Result<?> handleMinioException(MinioException e) {
log.error("MinIO原生异常:{}", e.getMessage(), e);
return Result.fail(ResultCode.MINIO_CONNECT_ERROR);
}
/**
* 处理参数校验异常
*/
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException e) {
log.error("参数校验异常:{}", e.getMessage(), e);
String msg = e.getBindingResult().getFieldError() != null ?
e.getBindingResult().getFieldError().getDefaultMessage() : "参数错误";
return Result.fail(ResultCode.PARAM_ERROR.getCode(), msg, null);
}
/**
* 处理文件大小超限异常
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error("文件大小超限:{}", e.getMessage(), e);
return Result.fail("文件大小超过限制,请上传100MB以内的文件");
}
/**
* 处理通用异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
log.error("系统异常:{}", e.getMessage(), e);
return Result.fail("系统内部错误,请稍后重试");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 7. MinIO核心工具类
在cc.hep.utils包下创建MinioUtil,封装MinIO核心操作(企业级常用方法,带完整异常处理):
package cc.hep.utils;
import cc.hep.config.MinioConfig;
import cc.hep.exception.MinioBusinessException;
import cc.hep.common.ResultCode;
import cn.hutool.core.lang.UUID;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* MinIO核心工具类
* 封装MinIO的上传、下载、删除、桶管理等核心操作
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class MinioUtil {
private final MinioClient minioClient;
private final MinioConfig minioConfig;
/**
* 创建桶(不存在则创建)
* @param bucketName 桶名
*/
public void createBucket(String bucketName) {
try {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
log.info("创建桶成功:{}", bucketName);
}
} catch (Exception e) {
log.error("创建桶失败:{}", bucketName, e);
throw new MinioBusinessException("创建桶失败:" + e.getMessage());
}
}
/**
* 检查桶是否存在
* @param bucketName 桶名
* @return true-存在,false-不存在
*/
public boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("检查桶是否存在失败:{}", bucketName, e);
throw new MinioBusinessException("检查桶是否存在失败:" + e.getMessage());
}
}
/**
* 上传文件(默认桶)
* @param file 上传的文件
* @return 文件访问地址
*/
public String uploadFile(MultipartFile file) {
return uploadFile(file, minioConfig.getBucketName());
}
/**
* 上传文件(指定桶)
* @param file 上传的文件
* @param bucketName 桶名
* @return 文件访问地址
*/
public String uploadFile(MultipartFile file, String bucketName) {
// 1. 校验参数
if (file == null || file.isEmpty()) {
throw new MinioBusinessException(ResultCode.PARAM_ERROR);
}
// 2. 确保桶存在
createBucket(bucketName);
// 3. 生成唯一文件名(避免重复)
String originalFilename = file.getOriginalFilename();
String fileExtension = "";
if (originalFilename != null && originalFilename.contains(".")) {
fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
String fileName = UUID.randomUUID().toString() + fileExtension;
// 4. 上传文件
try (InputStream inputStream = file.getInputStream()) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
log.info("文件上传成功:{}", fileName);
// 5. 返回文件访问地址
return minioConfig.getUrlPrefix() + bucketName + "/" + fileName;
} catch (Exception e) {
log.error("文件上传失败:{}", fileName, e);
throw new MinioBusinessException("文件上传失败:" + e.getMessage());
}
}
/**
* 下载文件
* @param bucketName 桶名
* @param fileName 文件名
* @param response 响应对象(用于输出文件)
*/
public void downloadFile(String bucketName, String fileName, HttpServletResponse response) {
// 1. 检查桶是否存在
if (!bucketExists(bucketName)) {
throw new MinioBusinessException(ResultCode.BUCKET_NOT_EXIST);
}
// 2. 下载文件
try (InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build()
)) {
// 3. 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
// 4. 写入响应流
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) > 0) {
response.getOutputStream().write(buffer, 0, len);
}
response.getOutputStream().flush();
log.info("文件下载成功:{}", fileName);
} catch (Exception e) {
log.error("文件下载失败:{}", fileName, e);
throw new MinioBusinessException(ResultCode.FILE_NOT_EXIST);
}
}
/**
* 删除文件
* @param bucketName 桶名
* @param fileName 文件名
*/
public void deleteFile(String bucketName, String fileName) {
// 1. 检查桶是否存在
if (!bucketExists(bucketName)) {
throw new MinioBusinessException(ResultCode.BUCKET_NOT_EXIST);
}
// 2. 删除文件
try {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build()
);
log.info("文件删除成功:{}", fileName);
} catch (Exception e) {
log.error("文件删除失败:{}", fileName, e);
throw new MinioBusinessException("文件删除失败:" + e.getMessage());
}
}
/**
* 获取文件预览地址(带过期时间)
* @param bucketName 桶名
* @param fileName 文件名
* @param expires 过期时间(秒)
* @return 预览地址
*/
public String getPreviewUrl(String bucketName, String fileName, int expires) {
if (!bucketExists(bucketName)) {
throw new MinioBusinessException(ResultCode.BUCKET_NOT_EXIST);
}
try {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(fileName)
.method(Method.GET)
.expiry(expires, TimeUnit.SECONDS)
.build()
);
} catch (Exception e) {
log.error("获取文件预览地址失败:{}", fileName, e);
throw new MinioBusinessException("获取文件预览地址失败:" + e.getMessage());
}
}
/**
* 列出桶内所有文件
* @param bucketName 桶名
* @return 文件列表
*/
public List<Item> listFiles(String bucketName) {
if (!bucketExists(bucketName)) {
throw new MinioBusinessException(ResultCode.BUCKET_NOT_EXIST);
}
try {
List<Item> items = new ArrayList<>();
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true)
.build()
);
for (Result<Item> result : results) {
items.add(result.get());
}
return items;
} catch (Exception e) {
log.error("列出桶内文件失败:{}", bucketName, e);
throw new MinioBusinessException("列出桶内文件失败:" + e.getMessage());
}
}
/**
* 列出所有桶
* @return 桶列表
*/
public List<Bucket> listBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
log.error("列出所有桶失败", e);
throw new MinioBusinessException("列出所有桶失败:" + e.getMessage());
}
}
/**
* 上传字节数组文件
* @param bytes 字节数组
* @param fileName 文件名
* @param bucketName 桶名
* @return 文件访问地址
*/
public String uploadBytes(byte[] bytes, String fileName, String bucketName) {
createBucket(bucketName);
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(inputStream, bytes.length, -1)
.build()
);
log.info("字节数组文件上传成功:{}", fileName);
return minioConfig.getUrlPrefix() + bucketName + "/" + fileName;
} catch (Exception e) {
log.error("字节数组文件上传失败:{}", fileName, e);
throw new MinioBusinessException("字节数组文件上传失败:" + e.getMessage());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# 8. 服务层封装
# 8.1 服务接口
在cc.hep.service包下创建MinioService,定义业务接口:
package cc.hep.service;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* MinIO服务接口
*/
public interface MinioService {
/**
* 创建桶
* @param bucketName 桶名
*/
void createBucket(String bucketName);
/**
* 上传文件(默认桶)
* @param file 文件
* @return 文件访问地址
*/
String uploadFile(MultipartFile file);
/**
* 上传文件(指定桶)
* @param file 文件
* @param bucketName 桶名
* @return 文件访问地址
*/
String uploadFile(MultipartFile file, String bucketName);
/**
* 下载文件
* @param bucketName 桶名
* @param fileName 文件名
* @param response 响应对象
*/
void downloadFile(String bucketName, String fileName, HttpServletResponse response);
/**
* 删除文件
* @param bucketName 桶名
* @param fileName 文件名
*/
void deleteFile(String bucketName, String fileName);
/**
* 获取文件预览地址
* @param bucketName 桶名
* @param fileName 文件名
* @param expires 过期时间(秒)
* @return 预览地址
*/
String getPreviewUrl(String bucketName, String fileName, int expires);
/**
* 列出桶内所有文件
* @param bucketName 桶名
* @return 文件列表
*/
List<Item> listFiles(String bucketName);
/**
* 列出所有桶
* @return 桶列表
*/
List<Bucket> listBuckets();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 8.2 服务实现类
在cc.hep.service.impl包下创建MinioServiceImpl,实现接口:
package cc.hep.service.impl;
import cc.hep.service.MinioService;
import cc.hep.utils.MinioUtil;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* MinIO服务实现类
*/
@Service
@RequiredArgsConstructor
public class MinioServiceImpl implements MinioService {
private final MinioUtil minioUtil;
@Override
public void createBucket(String bucketName) {
minioUtil.createBucket(bucketName);
}
@Override
public String uploadFile(MultipartFile file) {
return minioUtil.uploadFile(file);
}
@Override
public String uploadFile(MultipartFile file, String bucketName) {
return minioUtil.uploadFile(file, bucketName);
}
@Override
public void downloadFile(String bucketName, String fileName, HttpServletResponse response) {
minioUtil.downloadFile(bucketName, fileName, response);
}
@Override
public void deleteFile(String bucketName, String fileName) {
minioUtil.deleteFile(bucketName, fileName);
}
@Override
public String getPreviewUrl(String bucketName, String fileName, int expires) {
return minioUtil.getPreviewUrl(bucketName, fileName, expires);
}
@Override
public List<Item> listFiles(String bucketName) {
return minioUtil.listFiles(bucketName);
}
@Override
public List<Bucket> listBuckets() {
return minioUtil.listBuckets();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 9. 控制层
在cc.hep.controller包下创建MinioController,提供RESTful接口:
package cc.hep.controller;
import cc.hep.service.MinioService;
import cc.hep.common.Result;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* MinIO控制器
* 提供文件上传、下载、删除等REST接口
*/
@RestController
@RequestMapping("/minio")
@RequiredArgsConstructor
@Validated
public class MinioController {
private final MinioService minioService;
/**
* 创建桶
* @param bucketName 桶名
* @return 响应结果
*/
@PostMapping("/bucket/{bucketName}")
public Result<?> createBucket(@PathVariable String bucketName) {
minioService.createBucket(bucketName);
return Result.success("创建桶成功:" + bucketName);
}
/**
* 上传文件(默认桶)
* @param file 文件
* @return 文件访问地址
*/
@PostMapping("/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
String url = minioService.uploadFile(file);
return Result.success(url);
}
/**
* 上传文件(指定桶)
* @param file 文件
* @param bucketName 桶名
* @return 文件访问地址
*/
@PostMapping("/upload/{bucketName}")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file, @PathVariable String bucketName) {
String url = minioService.uploadFile(file, bucketName);
return Result.success(url);
}
/**
* 下载文件
* @param bucketName 桶名
* @param fileName 文件名
* @param response 响应对象
*/
@GetMapping("/download/{bucketName}/{fileName}")
public void downloadFile(@PathVariable String bucketName, @PathVariable String fileName, HttpServletResponse response) {
minioService.downloadFile(bucketName, fileName, response);
}
/**
* 删除文件
* @param bucketName 桶名
* @param fileName 文件名
* @return 响应结果
*/
@DeleteMapping("/delete/{bucketName}/{fileName}")
public Result<?> deleteFile(@PathVariable String bucketName, @PathVariable String fileName) {
minioService.deleteFile(bucketName, fileName);
return Result.success("删除文件成功:" + fileName);
}
/**
* 获取文件预览地址
* @param bucketName 桶名
* @param fileName 文件名
* @param expires 过期时间(秒,默认3600)
* @return 预览地址
*/
@GetMapping("/preview/{bucketName}/{fileName}")
public Result<String> getPreviewUrl(
@PathVariable String bucketName,
@PathVariable String fileName,
@RequestParam(defaultValue = "3600") int expires
) {
String url = minioService.getPreviewUrl(bucketName, fileName, expires);
return Result.success(url);
}
/**
* 列出桶内所有文件
* @param bucketName 桶名
* @return 文件列表
*/
@GetMapping("/files/{bucketName}")
public Result<List<Item>> listFiles(@PathVariable String bucketName) {
List<Item> items = minioService.listFiles(bucketName);
return Result.success(items);
}
/**
* 列出所有桶
* @return 桶列表
*/
@GetMapping("/buckets")
public Result<List<Bucket>> listBuckets() {
List<Bucket> buckets = minioService.listBuckets();
return Result.success(buckets);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 10. 项目启动类
确保SpringBootMinIoApplication.java正确配置(IDEA自动生成,需确认包扫描范围):
package cc.hep;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import cc.hep.config.MinioConfig;
@SpringBootApplication
@EnableConfigurationProperties(MinioConfig.class)
public class SpringBootMinIoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMinIoApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11. 接口测试
# 11.1 启动项目
点击IDEA中SpringBootMinIoApplication的启动按钮,控制台输出===== SpringBoot-MinIO 项目启动成功 =====即表示启动成功。
# 11.2 测试用例
| 接口功能 | 请求方式 | 请求地址 | 参数 | 预期结果 |
|---|---|---|---|---|
| 创建桶 | POST | http://localhost:8080/minio/bucket/hep-test | 路径参数:bucketName=hep-test | 返回“创建桶成功:hep-test” |
| 上传文件(指定桶) | POST | http://localhost:8080/minio/upload/hep-test | form-data:file=xxx.jpg | 返回文件访问地址 |
| 下载文件 | GET | http://localhost:8080/minio/download/hep-test/xxx.jpg | 路径参数:bucketName=hep-test、fileName=xxx.jpg | 浏览器下载文件 |
| 删除文件 | DELETE | http://localhost:8080/minio/delete/hep-test/xxx.jpg | 路径参数:bucketName=hep-test、fileName=xxx.jpg | 返回“删除文件成功:xxx.jpg” |
| 列出所有桶 | GET | http://localhost:8080/minio/buckets | 无 | 返回桶列表(包含hep-test) |

# 12. 企业级优化建议
# 12.1 配置信息加密
生产环境中,MinIO的accessKey/secretKey不能明文存储,可使用jasypt-spring-boot-starter加密配置文件中的敏感信息。
# 12.2 分片上传/断点续传
针对大文件(如1GB以上),实现分片上传:
- 将文件分割为多个分片(如5MB/片);
- 逐个上传分片,记录已上传分片;
- 所有分片上传完成后,调用MinIO的
composeObject接口合并分片。
# 12.3 桶策略配置
通过MinIO控制台或代码配置桶策略,比如:
- 私有桶:仅授权用户可访问;
- 公共读桶:所有人可读取,但仅授权用户可写入;
- 限制文件类型:仅允许上传jpg、png、pdf等指定类型文件。
# 12.4 日志与监控
- 接入SLF4J+Logback记录MinIO操作日志(包含操作人、操作时间、文件名称、桶名等);
- 接入Prometheus+Grafana监控MinIO服务状态(存储使用率、上传/下载速率、接口响应时间等)。
# 12.5 权限控制
结合Spring Security实现接口权限控制,比如:
- 普通用户仅可上传/下载自己的文件;
- 管理员可管理所有文件和桶。
# 总结
- 本教程基于SpringBoot 3.5.10和MinIO RELEASE.2025-09-07T16-13-09Z,从零搭建了企业级文件存储项目,涵盖配置、工具类、服务层、控制层、异常处理等全流程;
- 核心亮点是封装了通用的MinIO工具类,提供了上传、下载、删除、桶管理等企业级常用方法,并实现了统一返回结果和全局异常处理;
- 生产环境中需结合配置加密、分片上传、权限控制等优化方案,进一步提升项目的安全性和可用性。