适配Java开发,覆盖概念讲解、Docker安装、Web控制台操作、Java SDK开发、权限配置

一、介绍

MinIO 是高性能、分布式的开源对象存储系统100%兼容 Amazon S3 API

核心价值:你可以在本地服务器、私有云、Docker、K8s中,快速搭建一套和阿里云OSS、腾讯云COS功能一致的私有存储服务,专门存储图片、视频、大文件、AI数据等非结构化数据。


二、概念扫盲

2.1 什么是对象存储?

对象存储是海量非结构化数据的专用存储方案,不适合存数据库,专门解决大文件存储问题。

非结构化数据(对象存储核心场景)

图片、视频、音频、日志、备份文件、AI训练数据集、模型权重、安装包等大文件。

对象存储三大组成

  1. 数据本体:文件本身(图片/视频等)

  2. 元数据:文件属性(大小、类型、创建时间、自定义标签)

  3. 唯一ID:全局唯一标识,用于精准定位文件

核心特点

✅ 无限扩容、✅ 超高读写性能、✅ 低成本、✅ 海量文件存储、✅ HTTP/HTTPS API访问


2.2 什么是 S3 API?

S3 = Amazon Simple Storage Service(亚马逊对象存储服务),全球对象存储的行业标准接口

核心意义

所有云厂商的对象存储都兼容S3 API:阿里云OSS、腾讯云COS、华为云OBS、MinIO

一句话总结:学会MinIO = 学会云厂商对象存储,代码零修改直接迁移。


三、MinIO 核心优势

  1. 极致高性能:Go语言开发,支持GB级读写,适配AI训练、视频存储等高并发场景

  2. 全兼容S3 API:所有S3 SDK/工具直接复用,Java/Python/Go无缝对接

  3. 部署极简:单二进制文件,无依赖,Windows/Linux/Docker一键启动

  4. 弹性扩展:单节点 → 分布式集群,加节点即可线性提升容量/性能

  5. 高可用:多副本、纠删码,硬盘/节点损坏数据不丢失

  6. 企业级安全: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控制台

  1. 浏览器访问:http://你的服务器IP:9001 如:

  2. 输入账号密码登录

  3. 核心功能:创建桶、上传文件、配置权限、查看监控

最新版lastest是没有web操作的,只能够创建桶,其他的就需要使用mc来操作,不太友好。


五、MinIO 核心概念

5.1 核心名词

  1. Bucket(存储桶):相当于文件夹,用于分类存储文件,全局唯一命名

  2. Object(对象):存储桶里的文件/数据

  3. Access Policy(访问策略):控制桶的读写权限

5.2 控制台基础操作

1.创建桶:Buckets → Create Bucket → 输入桶名(小写、无特殊字符)

配置的介绍

Bucket Name(桶名称)

  • 说明:存储桶的唯一标识,全局唯一、不可重复

  • 命名规则:仅支持小写字母、数字、短横线 -、点 .,长度 3~63 字符,不能以特殊字符开头 / 结尾。

  • 示例demouser-avatarapp-log-2025

Versioning(版本控制)

  • OFF(默认):上传同名文件会直接覆盖旧版本,删除文件会永久删除无法恢复,占用存储空间最小。

  • ON:同名文件上传不覆盖,自动生成多版本快照;删除文件仅标记删除,历史版本完整保留,可随时回滚恢复

  • 用途:当你误删或覆盖文件时,可以随时恢复到之前的版本,适合需要数据回溯的场景,比如重要文档、配置文件、备份数据。

  • 注意:开启后会占用更多存储空间,建议仅重要桶开启,普通静态资源桶建议关闭。

Object Locking(对象锁定)

  • 功能:一次性开启、永久生效的强数据保护机制,用于禁止文件被篡改、提前删除,满足行业合规要求。

  • OFF(默认):文件可正常修改、删除,无强制保护。

  • ON:桶内文件支持锁定保护仅能在创建桶时开启,创建后无法关闭

  • 用途:满足金融、医疗等行业的合规性需求,比如法律法规要求数据不可篡改的强合规场景,防止被篡改或删除。

  • 作用:配合 Retention 保留策略,实现文件不可删、不可改的强制保护。

Quota(存储配额)

  • 功能:为单个桶设置最大存储容量上限,限制桶的资源占用,防止单个桶占满整个集群存储。

  • OFF(默认):桶无容量限制,可无限存储文件(仅受服务器 / 集群物理硬盘限制)。

  • ON:自定义桶最大容量,支持单位:TiBGiBMiB,达到上限后禁止继续上传文件

  • 用途:多租户、多业务分桶隔离,防止日志、备份文件无限堆积占用磁盘,按部门 / 项目分配存储资源。

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 限制、目录限制 ⭐⭐⭐⭐ 生产标准、公开读 + 私有写

配置方式

  1. 点击 Access Policy 右侧编辑图标

  2. 下拉选择 Private / Public / Custom

  3. 选择 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兼容策略标准版本

EffectAllow(允许)/ 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 里的 IpAddressaws:Referers3: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&lt;SnowballObject&gt; 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&gt; 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.避坑指南

结合实际开发经验,总结以下常见坑点,避免踩坑浪费时间:

  1. contentType 必须显式设置:若不设置,MinIO 会默认使用 application/octet-stream,导致浏览器直接下载图片、PDF 等文件,无法预览。

  2. 桶名称规范:桶名称只能包含小写字母、数字、连字符(-),不能包含大写字母、特殊字符,否则创建桶失败。

  3. 删除桶前必须清空文件:桶内有文件、版本记录时,无法删除桶,需先批量删除所有文件和版本。

  4. 分布式部署注意事项:多节点部署时,所有节点的磁盘路径顺序必须一致,否则集群无法组建;推荐使用 host 网络模式,避免端口映射问题。

  5. 临时签名 URL 有效期:有效期不宜过长(建议 1~24 小时),避免 URL 泄露导致文件被非法访问;有效期过短会影响用户体验。

  6. 流上传注意关闭流:使用 InputStream 上传后,需手动关闭流(或使用 try-with-resources 自动关闭),避免资源泄露。

十、总结与扩展

本文从 MinIO Java SDK 入门到实战,讲解了环境搭建、核心 API 用法、Spring Boot 整合、避坑指南,覆盖了开发中 90% 的场景(文件上传、下载、预览、管理、权限控制)。

MinIO 还有更多高级特性,如:对象锁定(防篡改、防删除)、服务端加密、跨桶复制、日志管理等,后续可根据业务需求深入学习。

建议:

  • 使用分布式部署,保障高可用;

  • 开启版本控制和对象锁定,防止数据丢失;

  • 定期备份数据,避免极端情况导致数据不可用;

  • 使用 HTTPS 加密传输(TLS 部署),保障数据安全。

如果觉得本文对你有帮助,欢迎点赞、收藏、转发,关注我,学习更多 Java 后端实战技巧!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐