MinIO零基础入门实战教程
适配Java开发,覆盖概念讲解、Docker安装、Web控制台操作、Java SDK开发、权限配置
一、介绍
MinIO 是高性能、分布式的开源对象存储系统,100%兼容 Amazon S3 API。
核心价值:你可以在本地服务器、私有云、Docker、K8s中,快速搭建一套和阿里云OSS、腾讯云COS功能一致的私有存储服务,专门存储图片、视频、大文件、AI数据等非结构化数据。
二、概念扫盲
2.1 什么是对象存储?
对象存储是海量非结构化数据的专用存储方案,不适合存数据库,专门解决大文件存储问题。
非结构化数据(对象存储核心场景)
图片、视频、音频、日志、备份文件、AI训练数据集、模型权重、安装包等大文件。
对象存储三大组成
-
数据本体:文件本身(图片/视频等)
-
元数据:文件属性(大小、类型、创建时间、自定义标签)
-
唯一ID:全局唯一标识,用于精准定位文件
核心特点
✅ 无限扩容、✅ 超高读写性能、✅ 低成本、✅ 海量文件存储、✅ HTTP/HTTPS API访问
2.2 什么是 S3 API?
S3 = Amazon Simple Storage Service(亚马逊对象存储服务),全球对象存储的行业标准接口。
核心意义
所有云厂商的对象存储都兼容S3 API:阿里云OSS、腾讯云COS、华为云OBS、MinIO
一句话总结:学会MinIO = 学会云厂商对象存储,代码零修改直接迁移。
三、MinIO 核心优势
-
极致高性能:Go语言开发,支持GB级读写,适配AI训练、视频存储等高并发场景
-
全兼容S3 API:所有S3 SDK/工具直接复用,Java/Python/Go无缝对接
-
部署极简:单二进制文件,无依赖,Windows/Linux/Docker一键启动
-
弹性扩展:单节点 → 分布式集群,加节点即可线性提升容量/性能
-
高可用:多副本、纠删码,硬盘/节点损坏数据不丢失
-
企业级安全:TLS加密、IAM权限控制、对象锁定、文件加密
四、MinIO 安装
4.1 版本两大坑
坑1:许可证变更
-
新版本(2023.04后):AGPL v3,仅部署使用无商业限制,修改源码需开源
-
旧版本(2023.03前):Apache 2.0,完全无开源约束
-
✅ 学习/中小企业:直接用新版本,无任何风险
坑2:控制台移除
2025.04后新版本Docker镜像默认移除Web控制台,新手无法可视化操作!
✅ 解决方案:安装指定稳定版(带控制台),本教程统一使用:
minio/minio:RELEASE.2025-04-08T15-41-24Z
4.2 Docker 一键安装(推荐,全平台通用)
前置条件
服务器/本地已安装 Docker、Docker Compose
步骤1:拉取指定版本镜像
docker pull minio/minio:RELEASE.2025-04-08T15-41-24Z
步骤2:创建数据挂载目录
mkdir -p /opt/minio/data
步骤3:启动MinIO(单节点开发版)
单节点模式:无扩容/纠删码,仅用于开发、测试、学习
docker run -d \
--name minio \
-p 9000:9000 \
-p 9001:9001 \
-v /opt/minio/data:/data \
-e MINIO_ROOT_USER="admin" \
-e MINIO_ROOT_PASSWORD="minioadmin123" \
minio/minio:RELEASE.2025-04-08T15-41-24Z \
server /data --console-address ":9001"
端口说明
-
9000:MinIO API端口(Java SDK/程序连接用) -
9001:Web控制台端口(浏览器可视化操作)
账号密码
-
管理员账号:
admin -
管理员密码:minioadmin123(密码必须≥8位)
4.3 访问Web控制台
-
浏览器访问:
http://你的服务器IP:9001如: -
输入账号密码登录
-
核心功能:创建桶、上传文件、配置权限、查看监控

最新版lastest是没有web操作的,只能够创建桶,其他的就需要使用mc来操作,不太友好。
五、MinIO 核心概念
5.1 核心名词
-
Bucket(存储桶):相当于文件夹,用于分类存储文件,全局唯一命名
-
Object(对象):存储桶里的文件/数据
-
Access Policy(访问策略):控制桶的读写权限
5.2 控制台基础操作
1.创建桶:Buckets → Create Bucket → 输入桶名(小写、无特殊字符)

配置的介绍
Bucket Name(桶名称)
-
说明:存储桶的唯一标识,全局唯一、不可重复。
-
命名规则:仅支持小写字母、数字、短横线
-、点.,长度 3~63 字符,不能以特殊字符开头 / 结尾。 -
示例:
demo、user-avatar、app-log-2025
Versioning(版本控制)
-
OFF(默认):上传同名文件会直接覆盖旧版本,删除文件会永久删除,无法恢复,占用存储空间最小。
-
ON:同名文件上传不覆盖,自动生成多版本快照;删除文件仅标记删除,历史版本完整保留,可随时回滚恢复。
-
用途:当你误删或覆盖文件时,可以随时恢复到之前的版本,适合需要数据回溯的场景,比如重要文档、配置文件、备份数据。
-
注意:开启后会占用更多存储空间,建议仅重要桶开启,普通静态资源桶建议关闭。
Object Locking(对象锁定)
-
功能:一次性开启、永久生效的强数据保护机制,用于禁止文件被篡改、提前删除,满足行业合规要求。
-
OFF(默认):文件可正常修改、删除,无强制保护。
-
ON:桶内文件支持锁定保护,仅能在创建桶时开启,创建后无法关闭。
-
用途:满足金融、医疗等行业的合规性需求,比如法律法规要求数据不可篡改的强合规场景,防止被篡改或删除。
-
作用:配合 Retention 保留策略,实现文件不可删、不可改的强制保护。
Quota(存储配额)
-
功能:为单个桶设置最大存储容量上限,限制桶的资源占用,防止单个桶占满整个集群存储。
-
OFF(默认):桶无容量限制,可无限存储文件(仅受服务器 / 集群物理硬盘限制)。
-
ON:自定义桶最大容量,支持单位:
TiB、GiB、MiB,达到上限后禁止继续上传文件。 -
用途:多租户、多业务分桶隔离,防止日志、备份文件无限堆积占用磁盘,按部门 / 项目分配存储资源。
Retention(保留策略)
-
功能:对象锁定的配套规则,强制设置文件的最低保留时长,保留期内文件禁止删除 / 修改。
-
启用条件:必须先开启 Object Locking + Versioning 才能配置。
-
开启后,它会强制为 Bucket 里的所有对象设置一个 “不能被删除或修改” 的保留期。
-
它和 Object Locking 是配套的,后者是基础开关,前者是具体的保留规则。
Mode(保留模式)
-
Compliance(合规模式-最严格):任何人(包括 MinIO 管理员)都不能提前删除或修改对象,直到保留期结束。它主要用于满足金融、医疗等行业的强合规需求,比如需要强制保留审计日志。
-
Governance(治理模式-灵活合规):普通用户无法删除对象,但拥有特殊权限的管理员可以在必要时提前删除,适合需要兼顾合规性和灵活性的场景。
Validity(保留时长)
-
定义:文件的强制保护周期,单位支持
days(天) -
规则:文件上传后,在该时长内受锁定保护,到期自动解锁
-
示例:
180 days= 文件 180 天内不可删除、不可修改
2.上传文件:进入桶 → Upload → 选择本地文件

3.删除文件/桶:选中文件/桶 → Delete(桶必须为空才能删除)

六、桶配置(Bucket Configuration)
在 MinIO 控制台进入存储桶详情页后,可在 Summary(总览) 页面完成桶的访问权限、版本控制、对象锁定、存储配额、数据保留等全量配置。

6.1 桶核心配置总览(界面说明)
进入桶 → Summary 页面,可查看与编辑以下核心配置:
1. Access Policy:桶访问权限(私有/公开/自定义)
2. Versioning:文件版本控制
3. Object Locking:对象锁定(防篡改、防删除)
4. Quota:存储容量配额
5. Retention:数据保留策略(合规锁定)
6. Encryption:服务端加密
7. Replication:跨桶复制
6.2 Access Policy(访问策略)
MinIO 提供三种桶访问模式,控制谁可以访问桶内文件。
| 策略 | 权限说明 | 安全性 | 适用场景 |
| Private(私有) | 仅认证用户可访问,匿名用户完全禁止 | ⭐⭐⭐⭐⭐ | 默认配置、业务数据、敏感文件 |
| Public(公开) | 匿名用户全读写,可上传 / 删除 / 修改文件 | ⭐ | 仅测试,生产禁止使用 |
| Custom(自定义) | 细粒度 JSON 策略,支持只读、IP 限制、目录限制 | ⭐⭐⭐⭐ | 生产标准、公开读 + 私有写 |
配置方式
-
点击 Access Policy 右侧编辑图标
-
下拉选择
Private / Public / Custom -
选择
Custom可粘贴 JSON 策略实现精细化权限
6.3 生产推荐:自定义只读策略(Custom)
功能
匿名用户只能下载 / 查看文件,不能上传、删除、修改;管理员不受限制。
配置 JSON(直接复制)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::your-bucket-name/public/*"],
"Condition": {
"IpAddress": {
"aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8", "123.45.67.89/32"]
},
"StringLike": {
"aws:Referer": [
"https://your-website.com/*",
"https://www.your-website.com/*"
],
"s3:prefix": ["*.jpg", "*.png", "*.mp4"]
},
"DateGreaterThan": {"aws:CurrentTime": "2026-05-01T00:00:00Z"},
"DateLessThan": {"aws:CurrentTime": "2026-06-01T00:00:00Z"}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::your-bucket-name"],
"Condition": {
"IpAddress": {
"aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8", "123.45.67.89/32"]
},
"StringLike": {
"s3:prefix": ["public/*"]
}
}
}
]
}
参数:
Version:固定写死 2012-10-17,AWS S3兼容策略标准版本
Effect:Allow(允许)/ Deny(拒绝)
Principal:* 代表所有用户(包括匿名)
Action:允许的操作(s3:GetObject 下载,s3:ListBucket 列出文件)
Resource:生效资源(桶本身 + 桶内所有文件,your-bucket-name 需替换)
Condition: 其他条件配置
策略逐块详细说明
1.全局基础配置(不可修改)
| 字段 | 说明 |
| Version | 固定值 2012-10-17,AWS S3 标准策略版本,不可修改 |
| Statement | 权限规则数组,可包含多条独立规则 |
2.第一条规则:文件下载限制(核心)
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::your-bucket-name/public/*"],
"Condition": { ... }
}
| 字段 | 说明 |
| Effect | Allow = 允许操作;若需黑名单可用 Deny |
| Principal | * = 所有用户(含匿名用户) |
| Action | s3:GetObject = 仅允许下载文件,不允许上传 / 删除 |
| Resource | 必须替换:仅允许访问 your-bucket-name 桶下 public/ 目录的文件 |
3.Condition 进阶条件块(全配置说明)
所有限制均写在此块内,可按需删除不需要的条件。
① IP 白名单限制
"IpAddress": {
"aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8", "123.45.67.89/32"]
}
-
作用:仅允许指定 IP 段访问,其他 IP 直接拒绝
-
格式:支持 CIDR 格式
-
192.168.1.0/24= 192.168.1.1~192.168.1.255 整个内网段 -
123.45.67.89/32= 仅允许单个固定公网 IP
-
-
按需修改:删除此块则不限制 IP
② 防盗链限制(仅允许指定域名访问)
"StringLike": {
"aws:Referer": [
"https://your-website.com/*",
"https://www.your-website.com/*"
]
}
-
作用:防止文件被其他网站盗用,仅允许自己的域名加载资源
-
按需修改:替换为你的实际域名,删除此块则不限制防盗链
③ 文件后缀限制(仅允许访问图片 / 视频)
"StringLike": {
"s3:prefix": ["*.jpg", "*.png", "*.mp4"]
}
-
作用:仅允许下载
.jpg、.png、.mp4后缀的文件,其他文件拒绝 -
按需修改:增删后缀,删除此块则不限制文件类型
④ 访问时间限制(仅活动期间可访问)
"DateGreaterThan": {"aws:CurrentTime": "2026-05-01T00:00:00Z"},
"DateLessThan": {"aws:CurrentTime": "2026-06-01T00:00:00Z"}
-
作用:仅在 2026 年 5 月 1 日~6 月 1 日(UTC 时间) 允许访问,其他时间拒绝
-
格式:ISO 8601 标准 UTC 时间
-
按需修改:调整时间范围,删除此块则不限制访问时间
4.第二条规则:文件列表限制(配套)
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::your-bucket-name"],
"Condition": {
"IpAddress": {
"aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8", "123.45.67.89/32"]
},
"StringLike": {
"s3:prefix": ["public/*"]
}
}
}
| 字段 | 说明 |
| Action | s3:ListBucket = 仅允许列出文件 |
| Resource | 必须替换:仅允许列出 your-bucket-name 桶 |
| Condition | 与下载规则保持一致:仅允许指定 IP、仅能看到 public/ 目录的文件 |
策略生效效果总结
✅ 满足所有条件的用户
-
仅能访问
public/目录 -
仅能下载
.jpg、.png、.mp4文件 -
仅能从
your-website.com访问 -
仅能在 2026.05.01~06.01 访问
-
仅能从指定 IP 访问
❌ 不满足任一条件的用户
-
完全拒绝访问(403 Forbidden)
✅ 管理员(持有密钥)
-
不受策略限制,拥有完整读写删权限
如何按需修改
1.仅保留 IP 限制(其他全删)
删除 Condition 里的 StringLike(防盗链 + 后缀)、Date*(时间),仅留 IpAddress
2.仅保留目录限制(其他全删)
删除 Condition 里的 IpAddress、aws:Referer、s3:prefix(后缀)、Date*,仅留目录相关
3.完全无限制只读(基础版)
删除整个 Condition 块,仅保留基础 Allow + GetObject + ListBucket
4.必改项
-
所有
your-bucket-name替换为你的桶名 -
IP 段、域名、时间、后缀替换为你的实际业务值
七、Java SDK 集成开发
7.1 环境依赖(Maven)
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.6.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>3.6.0</version>
</dependency>
这里的spring-boot是3.5.7,在使用minio-5.x版本时出现JUnit的相关报错,因此此处用的8.6.0版本。
7.2 客户端初始化工具类
import io.minio.MinioClient;
public class MinioUtil {
// MinIO服务地址
private static final String ENDPOINT = "http://127.0.0.1:9000";
// 管理员账号
private static final String ACCESS_KEY = "admin";
// 管理员密码
private static final String SECRET_KEY = "minioadmin123";
// 全局客户端单例
public static MinioClient getMinioClient() {
return MinioClient.builder()
.endpoint(ENDPOINT)
.credentials(ACCESS_KEY, SECRET_KEY)
.build();
}
}
正常开发将其注册为一个Bean即可。
7.3 核心API实战
官网API : https://min-io.cn/docs/minio/linux/developers/java/API.html
MinIO Java SDK 的 API 主要分为 5 大类:桶操作、文件上传、文件下载、文件管理、高级特性(版本控制、权限配置等),下面逐一讲解实战用法,所有代码均可直接复制运行。
1.客户端初始化(核心入口)
所有 MinIO 操作都需要通过 MinioClient 实例完成,建议封装成工具类,避免重复代码:
import io.minio.MinioClient;
import io.minio.errors.MinioException;
/**
* MinIO 客户端工具类(单例模式)
*/
public class MinioClientUtil {
// 私有构造器,防止实例化
private MinioClientUtil() {}
// 静态内部类,懒加载客户端实例
private static class MinioClientHolder {
private static final MinioClient INSTANCE = MinioClient.builder()
.endpoint("http://服务器IP:9000") // MinIO API 地址
.credentials("admin", "12345678") // 账号密码
.build();
}
// 获取客户端实例
public static MinioClient getInstance() {
return MinioClientHolder.INSTANCE;
}
// 测试客户端连接(可选)
public static void testConnection() {
try {
// 调用 listBuckets() 测试连接是否正常
MinioClientUtil.getInstance().listBuckets();
System.out.println("MinIO 客户端连接成功!");
} catch (MinioException e) {
System.err.println(" MinIO 客户端连接失败:" + e.getMessage());
throw new RuntimeException("MinIO 连接异常", e);
} catch (Exception e) {
System.err.println("未知异常:" + e.getMessage());
}
}
}
2. 桶操作(Bucket):基础必备
桶是 MinIO 存储文件的容器,所有文件都必须放在桶内,常用操作包括:判断桶是否存在、创建桶、删除桶、查询桶列表。
2.1判断桶是否存在
上传文件前,通常需要先判断桶是否存在,避免报错:
import io.minio.BucketExistsArgs;
import io.minio.MinioClient;
/**
* 桶操作工具类
*/
public class MinioBucketUtil {
private static final MinioClient CLIENT = MinioClientUtil.getInstance();
/**
* 判断桶是否存在
* @param bucketName 桶名称
* @return true:存在,false:不存在
*/
public static boolean bucketExists(String bucketName) {
try {
return CLIENT.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketName)
.build()
);
} catch (Exception e) {
System.err.println("判断桶是否存在失败:" + e.getMessage());
return false;
}
}
}
2.2创建桶
项目启动时自动创建桶,无需手动在控制台操作:
import io.minio.MakeBucketArgs;
/**
* 创建桶(若桶不存在则创建)
* @param bucketName 桶名称
*/
public static void createBucket(String bucketName) {
try {
if (!bucketExists(bucketName)) {
CLIENT.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketName)
.build()
);
System.out.println("桶 " + bucketName + " 创建成功");
} else {
System.out.println("桶 " + bucketName + " 已存在");
}
} catch (Exception e) {
System.err.println("创建桶失败:" + e.getMessage());
throw new RuntimeException("创建桶异常", e);
}
}
2.3查询所有桶
用于后台管理系统,展示当前账号下所有桶:
import io.minio.Bucket;
import java.util.List;
/**
* 查询所有桶
* @return 桶列表
*/
public static List<Bucket> listBuckets() {
try {
return CLIENT.listBuckets();
} catch (Exception e) {
System.err.println("❌ 查询桶列表失败:" + e.getMessage());
throw new RuntimeException("查询桶列表异常", e);
}
}
2.4删除桶
注意:删除桶前,必须确保桶内无文件、无版本记录,否则删除失败:
import io.minio.RemoveBucketArgs;
/**
* 删除桶(桶必须为空)
* @param bucketName 桶名称
*/
public static void deleteBucket(String bucketName) {
try {
if (bucketExists(bucketName)) {
CLIENT.removeBucket(
RemoveBucketArgs.builder()
.bucket(bucketName)
.build()
);
System.out.println("✅ 桶 " + bucketName + " 删除成功");
} else {
System.out.println("ℹ️ 桶 " + bucketName + " 不存在");
}
} catch (Exception e) {
System.err.println("❌ 删除桶失败:" + e.getMessage());
throw new RuntimeException("删除桶异常", e);
}
}
2.5文件上传(Object):最常用场景
MinIO 提供 3 种核心上传方式,覆盖本地文件、流数据、批量小文件,满足不同业务场景。
本地文件上传(推荐)
使用 uploadObject 方法,直接传入本地文件路径,最简单、最常用:
import io.minio.UploadObjectArgs;
import java.util.UUID;
/**
* 本地文件上传
* @param bucketName 桶名称
* @param localFilePath 本地文件路径(如:D:/avatar.png)
* @param objectName 桶内文件路径(如:user/avatar.png)
* @param contentType 文件类型(如:image/png)
* @return 桶内文件路径
*/
public static String uploadLocalFile(String bucketName, String localFilePath, String objectName, String contentType) {
try {
// 生成唯一文件名(可选,防止重名覆盖)
String uniqueFileName = UUID.randomUUID() + "_" + objectName;
CLIENT.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketName)
.object(uniqueFileName) // 桶内存储路径
.filename(localFilePath) // 本地文件路径
.contentType(contentType) // 明确文件类型,影响浏览器预览/下载
.build()
);
System.out.println("✅ 本地文件上传成功,桶内路径:" + uniqueFileName);
return uniqueFileName;
} catch (Exception e) {
System.err.println("❌ 本地文件上传失败:" + e.getMessage());
throw new RuntimeException("文件上传异常", e);
}
}
流上传(核心)
使用 putObject 方法,支持 InputStream 流上传,适合内存数据、网络流、动态生成的文件(如验证码、Excel):
import io.minio.PutObjectArgs;
import java.io.InputStream;
import java.util.UUID;
/**
* 流上传(InputStream)
* @param bucketName 桶名称
* @param inputStream 文件流
* @param fileSize 文件大小(字节),未知填 -1
* @param objectName 桶内文件路径
* @param contentType 文件类型
* @return 桶内文件路径
*/
public static String uploadStream(String bucketName, InputStream inputStream, long fileSize, String objectName, String contentType) {
try {
String uniqueFileName = UUID.randomUUID() + "_" + objectName;
CLIENT.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(uniqueFileName)
.stream(inputStream, fileSize, -1) // fileSize=-1 表示未知大小
.contentType(contentType)
.build()
);
System.out.println("✅ 流上传成功,桶内路径:" + uniqueFileName);
return uniqueFileName;
} catch (Exception e) {
System.err.println("❌ 流上传失败:" + e.getMessage());
throw new RuntimeException("流上传异常", e);
}
}
批量上传小文件
使用 uploadSnowballObjects 方法,将多个小文件打包上传,减少网络请求,提高效率(适合批量日志、图片集):
import io.minio.UploadSnowballObjectsArgs;
import io.minio.messages.SnowballObject;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 批量上传小文件
* @param bucketName 桶名称
* @param objects 小文件列表(SnowballObject)
*/
public static void batchUpload(String bucketName, List<SnowballObject> objects) {
try {
CLIENT.uploadSnowballObjects(
UploadSnowballObjectsArgs.builder()
.bucket(bucketName)
.objects(objects)
.build()
);
System.out.println("✅ 批量上传成功,共上传 " + objects.size() + " 个文件");
} catch (Exception e) {
System.err.println("❌ 批量上传失败:" + e.getMessage());
throw new RuntimeException("批量上传异常", e);
}
}
// 示例:构建批量文件列表
public static void testBatchUpload() {
List<SnowballObject> objects = new ArrayList<>();
// 添加第一个文件
String content1 = "批量文件1";
objects.add(new SnowballObject(
"batch/file1.txt",
new ByteArrayInputStream(content1.getBytes(StandardCharsets.UTF_8)),
content1.length(),
null
));
// 添加第二个文件
String content2 = "批量文件2";
objects.add(new SnowballObject(
"batch/file2.txt",
new ByteArrayInputStream(content2.getBytes(StandardCharsets.UTF_8)),
content2.length(),
null
));
// 执行批量上传
MinioFileUtil.batchUpload("test-bucket", objects);
}
2.6 文件下载与访问:前端预览/后端下载
文件上传后,常用操作包括:下载到本地、获取文件流(在线预览)、生成临时访问链接(私有桶分享)。
下载文件到本地
使用 downloadObject 方法,将桶内文件下载到本地指定路径:
import io.minio.DownloadObjectArgs;
/**
* 下载文件到本地
* @param bucketName 桶名称
* @param objectName 桶内文件路径
* @param localSavePath 本地保存路径(如:D:/save.png)
*/
public static void downloadToLocal(String bucketName, String objectName, String localSavePath) {
try {
CLIENT.downloadObject(
DownloadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(localSavePath)
.build()
);
System.out.println("✅ 文件下载成功,保存路径:" + localSavePath);
} catch (Exception e) {
System.err.println("❌ 文件下载失败:" + e.getMessage());
throw new RuntimeException("文件下载异常", e);
}
}
获取文件流(在线预览)
使用 getObject 方法,获取文件 InputStream,用于前端在线预览、读取文件内容(不落地):
import io.minio.GetObjectArgs;
import java.io.InputStream;
/**
* 获取文件流(用于在线预览、读取内容)
* @param bucketName 桶名称
* @param objectName 桶内文件路径
* @return 文件输入流
*/
public static InputStream getObjectStream(String bucketName, String objectName) {
try {
return CLIENT.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
System.err.println("❌ 获取文件流失败:" + e.getMessage());
throw new RuntimeException("获取文件流异常", e);
}
}
示例:Spring Boot 接口实现图片在线预览:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@RestController
public class FilePreviewController {
@GetMapping("/preview/{objectName}")
public void preview(@PathVariable String objectName, HttpServletResponse response) throws IOException {
// 获取文件流
InputStream inputStream = MinioFileUtil.getObjectStream("test-bucket", objectName);
// 设置响应头(根据文件类型调整)
response.setContentType("image/png");
// 输出流到前端
try (OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}
}
生成临时签名 URL(私有桶必备)
若桶为私有(默认),直接访问文件 URL 会被拒绝,此时需要生成临时签名 URL,设置有效期,用于前端预览、文件分享:
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import java.util.concurrent.TimeUnit;
/**
* 生成临时签名 URL(私有桶文件访问)
* @param bucketName 桶名称
* @param objectName 桶内文件路径
* @param expiry 有效期(单位:小时)
* @return 临时访问 URL
*/
public static String getPresignedUrl(String bucketName, String objectName, int expiry) {
try {
return CLIENT.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET) // 访问方式:GET(下载/预览)
.bucket(bucketName)
.object(objectName)
.expiry(expiry, TimeUnit.HOURS) // 有效期 2 小时
.build()
);
} catch (Exception e) {
System.err.println("❌ 生成临时签名 URL 失败:" + e.getMessage());
throw new RuntimeException("生成临时 URL 异常", e);
}
}
2.7文件管理:列表、查询、删除
日常开发中,需要对桶内文件进行管理,包括列出文件、查询文件信息、删除文件等。
列出桶内文件(分页、前缀过滤)
使用 listObjects 方法,支持前缀过滤(如只列出 images/ 目录下的文件)、分页查询:
import io.minio.ListObjectsArgs;
import io.minio.Result;
import io.minio.messages.Item;
import java.util.ArrayList;
import java.util.List;
/**
* 列出桶内文件(支持前缀过滤)
* @param bucketName 桶名称
* @param prefix 前缀(如:images/,只列出该目录下的文件)
* @return 文件列表(Item 包含文件名、大小、修改时间等信息)
*/
public static List<Item> listObjects(String bucketName, String prefix) {
List<Item> itemList = new ArrayList<>();
try {
// 分页查询(默认每页 1000 条,可通过 maxKeys 调整)
Iterable<Result<Item>> results = CLIENT.listObjects(
ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(prefix)
.maxKeys(100) // 每页最多 100 条
.build()
);
// 遍历结果
for (Result<Item> result : results) {
itemList.add(result.get());
}
} catch (Exception e) {
System.err.println("❌ 列出文件失败:" + e.getMessage());
throw new RuntimeException("列出文件异常", e);
}
return itemList;
}
查询文件元数据
使用 statObject 方法,获取文件的元数据(大小、类型、修改时间、ETag 等),可用于判断文件是否存在:
import io.minio.StatObjectArgs;
import io.minio.messages.StatObjectResponse;
/**
* 查询文件元数据
* @param bucketName 桶名称
* @param objectName 桶内文件路径
* @return 文件元数据
*/
public static StatObjectResponse getObjectMetadata(String bucketName, String objectName) {
try {
return CLIENT.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
System.err.println("❌ 查询文件元数据失败:" + e.getMessage());
throw new RuntimeException("查询文件元数据异常", e);
}
}
删除文件(单个/批量)
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.messages.DeleteError;
import java.util.List;
/**
* 删除单个文件
* @param bucketName 桶名称
* @param objectName 桶内文件路径
*/
public static void deleteObject(String bucketName, String objectName) {
try {
CLIENT.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
System.out.println("✅ 文件 " + objectName + " 删除成功");
} catch (Exception e) {
System.err.println("❌ 删除文件失败:" + e.getMessage());
throw new RuntimeException("删除文件异常", e);
}
}
/**
* 批量删除文件
* @param bucketName 桶名称
* @param objectNames 桶内文件路径列表
*/
public static void batchDeleteObject(String bucketName, List<String> objectNames) {
try {
// 构建删除对象列表
Iterable<Result<DeleteError>> results = CLIENT.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(objectNames.stream().map(objectName ->
new RemoveObjectsArgs.RemoveObject(objectName)
).toList())
.build()
);
// 遍历结果,处理删除失败的文件
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
System.err.println("❌ 文件 " + error.objectName() + " 删除失败:" + error.message());
}
System.out.println("✅ 批量删除完成");
} catch (Exception e) {
System.err.println("❌ 批量删除文件失败:" + e.getMessage());
throw new RuntimeException("批量删除文件异常", e);
}
}
2.8高级特性:权限配置、版本控制(生产必备)
针对生产环境,MinIO 提供权限控制、版本控制等高级特性,保障数据安全。
桶权限配置(私有/只读/公开)
通过 setBucketPolicy 方法设置桶权限,常用场景:私有桶(默认)、只读桶(匿名可预览,不可删改)、公开桶(不推荐生产):
import io.minio.DeleteBucketPolicyArgs;
import io.minio.SetBucketPolicyArgs;
/**
* 1. 设置桶为私有(删除策略即可,默认就是私有)
*/
public static void setBucketPrivate(String bucketName) {
try {
CLIENT.deleteBucketPolicy(
DeleteBucketPolicyArgs.builder()
.bucket(bucketName)
.build()
);
System.out.println("✅ 桶 " + bucketName + " 已设置为私有");
} catch (Exception e) {
System.err.println("❌ 设置私有桶失败:" + e.getMessage());
}
}
/**
* 2. 设置桶为只读(生产推荐:匿名可预览、下载,不可上传、删除)
*/
public static void setBucketReadOnly(String bucketName) {
// 只读策略 JSON
String policy = """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:ListBucket", "s3:GetObject"],
"Resource": [
"arn:aws:s3:::%s",
"arn:aws:s3:::%s/*"
]
}
]
}
""".formatted(bucketName, bucketName);
try {
CLIENT.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(policy)
.build()
);
System.out.println("✅ 桶 " + bucketName + " 已设置为只读");
} catch (Exception e) {
System.err.println("❌ 设置只读桶失败:" + e.getMessage());
}
}
版本控制(防止误删)
开启版本控制后,删除文件不会直接删除,而是生成删除标记,可随时恢复历史版本:
import io.minio.SetBucketVersioningArgs;
import io.minio.messages.BucketVersioningConfiguration;
/**
* 开启桶版本控制
* @param bucketName 桶名称
*/
public static void enableBucketVersioning(String bucketName) {
try {
CLIENT.setBucketVersioning(
SetBucketVersioningArgs.builder()
.bucket(bucketName)
.config(new BucketVersioningConfiguration(
BucketVersioningConfiguration.Status.ENABLED, null
))
.build()
);
System.out.println("✅ 桶 " + bucketName + " 版本控制已开启");
} catch (Exception e) {
System.err.println("❌ 开启版本控制失败:" + e.getMessage());
}
}
八、全局异常处理
try {
// MinIO业务代码
} catch (ErrorResponseException e) {
System.out.println("服务端错误:" + e.getMessage());
} catch (IOException e) {
System.out.println("IO流错误:" + e.getMessage());
} catch (Exception e) {
System.out.println("系统异常:" + e.getMessage());
}
九、实战整合:Spring Boot + MinIO示例
结合前面的工具类,实现一个完整的 Spring Boot 文件上传/下载/预览接口,可直接复制到项目中使用。
1. 配置文件(application.yml)
# MinIO 配置
minio:
endpoint: http://服务器IP:9000
access-key: admin
secret-key: 12345678
bucket-name: test-bucket # 默认桶名称
# 服务器端口
server:
port: 8080
2 .配置类(读取配置,初始化客户端)
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
// 初始化 MinioClient,替代之前的工具类单例
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
3.接口层
import io.minio.MinioClient;
import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/file")
public class FileController {
@Autowired
private MinioClient minioClient;
@Autowired
private MinioConfig minioConfig;
// 1. 文件上传(本地文件/前端上传)
@PostMapping("/upload")
public Map<String, Object> upload(@RequestParam("file") MultipartFile file) {
try {
// 调用工具类上传(流上传)
String objectName = MinioFileUtil.uploadStream(
minioConfig.getBucketName(),
file.getInputStream(),
file.getSize(),
file.getOriginalFilename(),
file.getContentType()
);
// 生成临时预览链接
String previewUrl = MinioFileUtil.getPresignedUrl(
minioConfig.getBucketName(),
objectName,
2
);
return Map.of("code", 200, "msg", "上传成功", "objectName", objectName, "previewUrl", previewUrl);
} catch (Exception e) {
return Map.of("code", 500, "msg", "上传失败:" + e.getMessage());
}
}
// 2. 文件预览
@GetMapping("/preview/{objectName}")
public void preview(@PathVariable String objectName, HttpServletResponse response) throws IOException {
InputStream inputStream = MinioFileUtil.getObjectStream(minioConfig.getBucketName(), objectName);
// 动态设置响应类型(根据文件后缀)
response.setContentType(MinioFileUtil.getContentType(objectName));
try (OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}
// 3. 文件下载
@GetMapping("/download/{objectName}")
public void download(@PathVariable String objectName, HttpServletResponse response) throws IOException {
InputStream inputStream = MinioFileUtil.getObjectStream(minioConfig.getBucketName(), objectName);
// 设置响应头,触发浏览器下载
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + objectName);
try (OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}
// 4. 列出桶内文件
@GetMapping("/list")
public Map<String, Object> listFiles(@RequestParam(required = false) String prefix) {
try {
List<Item> itemList = MinioFileUtil.listObjects(minioConfig.getBucketName(), prefix);
return Map.of("code", 200, "msg", "查询成功", "data", itemList);
} catch (Exception e) {
return Map.of("code", 500, "msg", "查询失败:" + e.getMessage());
}
}
// 5. 删除文件
@DeleteMapping("/delete/{objectName}")
public Map<String, Object> deleteFile(@PathVariable String objectName) {
try {
MinioFileUtil.deleteObject(minioConfig.getBucketName(), objectName);
return Map.of("code", 200, "msg", "删除成功");
} catch (Exception e) {
return Map.of("code", 500, "msg", "删除失败:" + e.getMessage());
}
}
}
4.避坑指南
结合实际开发经验,总结以下常见坑点,避免踩坑浪费时间:
-
contentType 必须显式设置:若不设置,MinIO 会默认使用 application/octet-stream,导致浏览器直接下载图片、PDF 等文件,无法预览。
-
桶名称规范:桶名称只能包含小写字母、数字、连字符(-),不能包含大写字母、特殊字符,否则创建桶失败。
-
删除桶前必须清空文件:桶内有文件、版本记录时,无法删除桶,需先批量删除所有文件和版本。
-
分布式部署注意事项:多节点部署时,所有节点的磁盘路径顺序必须一致,否则集群无法组建;推荐使用 host 网络模式,避免端口映射问题。
-
临时签名 URL 有效期:有效期不宜过长(建议 1~24 小时),避免 URL 泄露导致文件被非法访问;有效期过短会影响用户体验。
-
流上传注意关闭流:使用 InputStream 上传后,需手动关闭流(或使用 try-with-resources 自动关闭),避免资源泄露。
十、总结与扩展
本文从 MinIO Java SDK 入门到实战,讲解了环境搭建、核心 API 用法、Spring Boot 整合、避坑指南,覆盖了开发中 90% 的场景(文件上传、下载、预览、管理、权限控制)。
MinIO 还有更多高级特性,如:对象锁定(防篡改、防删除)、服务端加密、跨桶复制、日志管理等,后续可根据业务需求深入学习。
建议:
-
使用分布式部署,保障高可用;
-
开启版本控制和对象锁定,防止数据丢失;
-
定期备份数据,避免极端情况导致数据不可用;
-
使用 HTTPS 加密传输(TLS 部署),保障数据安全。
如果觉得本文对你有帮助,欢迎点赞、收藏、转发,关注我,学习更多 Java 后端实战技巧!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)