一、文件存储方案

 在现代应用开发中,文件(图片、视频、文档、日志等)的存储和管理是一个基础需求。常见的文件存储方案有以下几种:

  • 本地文件存储:直接将文件保存在应用服务器的本地磁盘(如 ./uploads/ 目录)。实现简单、无额外依赖、读写速度快,但不易扩展
  • 网络附属存储(NAS / 共享文件系统):使用 NFS、SMB/CIFS 等协议,将文件存储在专用的网络存储设备上,多台应用服务器共享同一目录。解决了多服务器共享文件的问题;容量可扩展;但性能受网络和 NAS 设备限制,仍存在中心化瓶颈,配置和维护相对复杂。
  • 云对象存储(OSS / S3):使用云厂商提供的对象存储服务(如 AWS S3、阿里云 OSS、腾讯云 COS、华为云 OBS 等),通过 HTTP REST API 存取文件。可以无限容量,按量付费,具有高可用、高持久性;但依赖公网或专线,每次读写有网络延迟和费用
  • 自建对象存储(MinIO、Ceph RGW、Swift):在私有云或本地数据中心部署对象存储系统,提供 S3 兼容的 API。数据完全自主可控,安全合规,且无厂商锁定,可部署在任意 Linux/Windows 服务器或 Kubernetes 上,但需要自行运维,有硬件投资和机房环境要求。

二、MinIO 简介

 MinIO 是一个 高性能、Kubernetes 原生、完全兼容 Amazon S3 API 的对象存储服务器。

  • 采用 Go 语言编写,单一二进制文件即可启动,支持 Windows、Linux、macOS 以及 Docker/Kubernetes 环境。
  • MinIO 的设计目标是为云原生工作负载提供轻量、快速、可扩展的对象存储,既可以单机运行(用于开发/测试),也可以部署成分布式集群(用于生产环境,实现数据冗余和高可用)。
  • 由于其 S3 兼容性,任何为 AWS S3 编写的 SDK(Java、Python、Go、Node.js 等)和工具(如 awscli、s3cmd、rclone)都可以无缝对接 MinIO。

1.核心特性

  • S3 兼容:完整实现 Amazon S3 API,包括 Bucket、Object、Multipart Upload、Presigned URL、Bucket Policy 等
  • 高性能:在合适硬件上可达到 每秒数百 GB 的读写吞吐;利用 SIMD 指令集加速纠删码(Erasure Coding)计算
  • 轻量 & 简单:单个二进制文件(约 100MB),无外部依赖(不依赖数据库),配置简单(环境变量或命令行参数)
  • 分布式支持:支持分布式模式(多个磁盘/多台服务器),使用纠删码提供数据冗余和自动修复,无需 RAID
  • 云原生友好:提供 Kubernetes Operator,可轻松在 K8s 上部署;支持 Prometheus 监控指标;与 CSI(容器存储接口)集成
  • 安全:支持 TLS/HTTPS、服务器端加密(SSE-S3、SSE-C、SSE-KMS)、IAM 身份管理、Bucket 策略和临时凭证(STS)
  • 生命周期管理:支持对象过期、转换到冷存储层(需配合其他系统)
  • 事件通知:支持将 Bucket 事件(上传、删除等)发送到 Kafka、AMQP、Redis、Webhook 等目标
  • 多租户:通过多个 MinIO 实例或 Bucket 策略实现租户隔离

2.适用场景

  • 开发与测试环境:在本地或 CI/CD 管道中快速搭建一个 S3 兼容的存储,避免连接真实云服务带来的网络延迟和费用。
  • 私有云/企业内部文件存储:替代传统的文件服务器(NFS/SMB),为内部应用提供统一的对象存储接口,支持海量小文件或大文件的稳定存储。
  • 大数据与 AI 训练:作为数据湖的存储底座,存储日志、Parquet/ORC 格式数据,供 Spark、Presto、TensorFlow 等计算框架直接读取。MinIO 对 Hadoop S3A 有良好支持。
  • 容器化应用的持久化存储:在 Kubernetes 中作为 PVC 后端(通过 CSI 或 S3 兼容的 FUSE 工具),为无状态应用提供持久卷。
  • 备份与归档:使用 rclone、Velero、Dell EMC Networker 等备份工具将数据直接备份到 MinIO,并通过生命周期管理转储至冷存储层。
  • 边缘计算 / IoT:由于单二进制部署极其轻量,可以运行在树莓派、边缘网关等设备上,收集并存储传感器数据,再通过同步功能汇总到中心云。
  • 混合云架构中的缓存层:在本地部署 MinIO 作为高速缓存,通过异步复制将数据最终同步到公有云对象存储,平衡访问延迟和成本。

三、访问控制台与基本操作

下载安装参考连接

1.Web端基础操作

 可以登录 Web 控制台创建存储桶(Bucket),注意创建桶名时必须介于 3 到 63 个字符之间;桶名称只能由小写字母、数字、句点 (.) 和连字符 (-) 组成;桶名称必须以字母或数字开头和结尾;且名称不得采用 IP 地址格式,否则会报错。
在这里插入图片描述
 可以上传、下载、删除文件
在这里插入图片描述

2.共享访问链接

 ①可以选中文件生成共享访问链接,但是这种方式生成的链接,有效期最长只能设置到12小时,无法延长。

在这里插入图片描述
在这里插入图片描述
 ②可以使用 mc 命令行客户端生成永久共享链接(推荐),下载 mc 并配置连接别名

mc alias set myminio http://127.0.0.1:9000 YOUR_USERNAME YOUR_PASSWORD

 将整个存储桶设为公开只读,即任何人都可以列出并下载桶内的所有文件(即永久共享)。

mc anonymous set download myminio/my-bucket-name

 ③也可以通过SDK编程实现永久访问配置,MinIO 所有官方 SDK 都兼容 AWS S3 的 Bucket Policy。以下以 Java 和 Python 为例,演示如何将存储桶设置为公开只读(永久共享)。

 Java代码如下

import io.minio.*;
import io.minio.messages.*;

public class MakeBucketPublic {
    public static void main(String[] args) throws Exception {
        MinioClient minioClient = MinioClient.builder()
                .endpoint("http://127.0.0.1:9000")
                .credentials("YOUR_USERNAME", "YOUR_PASSWORD")
                .build();

        // 设置 bucket 策略为 "public-read"(允许所有人下载)
        String policy = "{\n" +
                "  \"Version\": \"2012-10-17\",\n" +
                "  \"Statement\": [\n" +
                "    {\n" +
                "      \"Effect\": \"Allow\",\n" +
                "      \"Principal\": \"*\",\n" +
                "      \"Action\": [\"s3:GetObject\"],\n" +
                "      \"Resource\": [\"arn:aws:s3:::my-bucket-name/*\"]\n" +
                "    }\n" +
                "  ]\n" +
                "}";
        minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                .bucket("my-bucket-name")
                .config(policy)
                .build());

        System.out.println("Bucket is now public. Permanent URL: http://127.0.0.1:9000/my-bucket-name/your-file.jpg");
    }
}

 Python 示例如下

from minio import Minio

client = Minio(
    "127.0.0.1:9000",
    access_key="YOUR_USERNAME",
    secret_key="YOUR_PASSWORD",
    secure=False
)

policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": ["s3:GetObject"],
            "Resource": ["arn:aws:s3:::my-bucket-name/*"]
        }
    ]
}

client.set_bucket_policy("my-bucket-name", json.dumps(policy))
print("永久共享链接格式:http://127.0.0.1:9000/my-bucket-name/文件名")

 ④如果你不希望将整个桶公开,但又需要生成一个有效期很长(例如 1 年) 的分享链接,可以通过 SDK 生成自定义有效期的预签名 URL。不过请注意,MinIO 官方建议最长不超过 7 天,但通过代码可以设置更大的值(如 365 天)。示例(Java):

String url = minioClient.getPresignedObjectUrl(
    GetPresignedObjectUrlArgs.builder()
        .bucket("my-bucket-name")
        .object("my-file.pdf")
        .method(Method.GET)
        .expiry(3600 * 24 * 365)  // 365 天,单位秒
        .build()
);
System.out.println("Long-term URL: " + url);

四、客户端工具(mc)简介

 MinIO 官方命令行客户端 mc(MinIO Client)提供了类似 UNIX 命令(如 ls、cp、mb 等),用于管理对象存储和兼容 S3 的服务。与直接操作文件系统不同,mc 通过 S3 API 与服务端交互,确保元数据一致性。

1.mc 的安装与配置

  • Windows:下载 mc.exe,放入任意目录。
  • macOS:brew install minio/stable/mc
  • Linux
    wget https://dl.min.io/client/mc/release/linux-amd64/mc
    chmod +x mc
    sudo mv mc /usr/local/bin/
    
  • Docker:docker pull minio/mc

配置别名(Alias)

mc alias set myminio http://127.0.0.1:9000 YOUR_ACCESS_KEY YOUR_SECRET_KEY

注意端口必须为 API 端口(默认 9000),不是 Web 控制台端口。

2.常用 mc 命令(ls、mb、cp、rm、policy)

命令 说明 示例
ls 列出存储桶或对象 mc ls myminio
mc ls myminio/mybucket
mb 创建存储桶 mc mb myminio/new-bucket
cp 复制(上传/下载) 上传:mc cp ./file myminio/mybucket/
下载:mc cp myminio/mybucket/file ./
rm 删除对象 mc rm myminio/mybucket/file.txt
mc rm --force myminio/mybucket/
policy/anonymous 设置桶的匿名访问策略 mc anonymous set download myminio/mybucket(公开只读)

五、Java 快速集成示例

1.添加 MinIO Java SDK 依赖(Maven/Gradle)

 在项目中引入 MinIO Java SDK 的 Maven 依赖,当前官方推荐的稳定版本为 8.6.0(建议使用最新稳定版)。部分场景下,SDK 内部依赖 HTTP 客户端,如项目中缺少相关依赖,可一并添加 Apache HttpClient5:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.6.0</version>
</dependency>

<!-- 可选:Apache HttpClient5,部分场景可能依赖 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3</version>
</dependency>

 若使用 Gradle

dependencies {
    implementation("io.minio:minio:8.6.0")
}

提示:引入依赖后,MinioClient 对象建议在项目中作为单例使用,不要每次操作都重新创建客户端实例。

2.连接 MinIO 服务器

 所有操作都通过 MinioClient 对象执行,初始化时需传入 MinIO 服务的核心配置:服务地址、访问密钥和秘密密钥。MinioClient 使用建造者模式进行实例化,支持灵活配置 endpoint、region 及 HTTP 客户端设置。

import io.minio.MinioClient;
import io.minio.errors.MinioException;

public class MinioConfig {
    // 配置参数(可根据需要提取到配置文件)
    private static final String MINIO_ENDPOINT = "http://127.0.0.1:9000";
    private static final String MINIO_ACCESS_KEY = "your_access_key";
    private static final String MINIO_SECRET_KEY = "your_secret_key";

    /**
     * 获取 MinIO 客户端实例(单例模式)
     */
    public static MinioClient getMinioClient() {
        try {
            MinioClient client = MinioClient.builder()
                    .endpoint(MINIO_ENDPOINT)
                    .credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
                    .build();
            // 验证客户端可用性
            client.listBuckets();
            return client;
        } catch (Exception e) {
            throw new RuntimeException("MinIO客户端初始化失败:" + e.getMessage(), e);
        }
    }
}
  • endpoint:MinIO 服务地址(API 端口,如 http://127.0.0.1:9000)
  • accessKey:访问密钥(即登录用户名)
  • secretKey:秘密密钥(即登录密码)

 在 Spring Boot 项目中集成,推荐将连接参数配置到 application.yml 中,通过 @ConfigurationProperties 读取并初始化客户端 Bean,方便后续维护和依赖注入。

# application.yml
minio:
  endpoint: http://127.0.0.1:9000
  access-key: your_access_key
  secret-key: your_secret_key
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 {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.access-key}")
    private String accessKey;

    @Value("${minio.secret-key}")
    private String secretKey;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

3.创建存储桶、上传文件

 MinIO 将存储桶(Bucket)作为顶层容器,所有文件都存放在存储桶中。在上传文件之前,最好先检查目标存储桶是否存在,若不存在则创建。

 判断存储桶是否存在并创建

import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;

public class MinioFileService {
    private final MinioClient minioClient;

    public MinioFileService(MinioClient minioClient) {
        this.minioClient = minioClient;
    }

    /**
     * 确保存储桶存在,若不存在则创建
     */
    public void ensureBucketExists(String bucketName) throws Exception {
        boolean exists = minioClient.bucketExists(
            BucketExistsArgs.builder().bucket(bucketName).build()
        );
        if (!exists) {
            minioClient.makeBucket(
                MakeBucketArgs.builder().bucket(bucketName).build()
            );
        }
    }
}

 上传文件(从本地文件系统)uploadObject 方法支持通过本地文件路径上传文件到 MinIO:

import io.minio.UploadObjectArgs;

/**
 * 上传本地文件到 MinIO
 *
 * @param bucketName 存储桶名称
 * @param objectName 对象名称(即文件在桶中的路径,例如 "images/photo.jpg")
 * @param filePath   本地文件绝对路径
 */
public void uploadFile(String bucketName, String objectName, String filePath) throws Exception {
    ensureBucketExists(bucketName);

    minioClient.uploadObject(
        UploadObjectArgs.builder()
            .bucket(bucketName)
            .object(objectName)
            .filename(filePath)
            .build()
    );
}

提示:objectName 可以包含路径,例如 files/2025/photo.jpg,会在 MinIO 中模拟目录结构(实际上所有对象仍以扁平结构存储,只是按前缀组织)。

 上传文件(从 InputStream):若文件来自其他来源(如用户上传的 MultipartFile),可使用 putObject 方法(Java 中也提供了 putObject 方法用于上传对象):

import io.minio.PutObjectArgs;
import java.io.InputStream;

/**
 * 从输入流上传文件到 MinIO
 */
public void uploadStream(String bucketName, String objectName, InputStream inputStream, 
                         String contentType, long size) throws Exception {
    ensureBucketExists(bucketName);

    minioClient.putObject(
        PutObjectArgs.builder()
            .bucket(bucketName)
            .object(objectName)
            .stream(inputStream, size, -1)
            .contentType(contentType)
            .build()
    );
}

4.获取文件 URL 或下载对象

  • 方式一:获取预签名 URL(带有效期):通过 getPresignedObjectUrl 方法可生成一个带有签名和有效期的临时下载链接,适用于为外部用户提供有时间限制的安全访问。MinIO Web 控制台生成的分享链接有效期最长 12 小时,而通过 SDK 可以自定义过期时间
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import java.util.concurrent.TimeUnit;

/**
 * 获取预签名 URL(适用于私有桶的文件临时访问)
 *
 * @param bucketName 存储桶名称
 * @param objectName 对象名称
 * @param expiry     过期时长(单位:秒)
 * @return 预签名 URL(超期后自动失效)
 */
public String getPresignedUrl(String bucketName, String objectName, int expirySeconds) throws Exception {
    return minioClient.getPresignedObjectUrl(
        GetPresignedObjectUrlArgs.builder()
            .method(Method.GET)
            .bucket(bucketName)
            .object(objectName)
            .expiry(expirySeconds)
            .build()
    );
}

// 使用示例:生成一个 7 天有效的下载链接
String url = getPresignedUrl("my-bucket", "myfile.jpg", (int) TimeUnit.DAYS.toSeconds(7));
  • 方式二:直接下载文件到本地:当需要在后端读取文件内容时(例如 Java 程序需要处理文件数据),直接下载比使用预签名 URL 更合适
import io.minio.DownloadObjectArgs;

/**
 * 从 MinIO 下载文件到本地
 */
public void downloadFile(String bucketName, String objectName, String destinationPath) throws Exception {
    minioClient.downloadObject(
        DownloadObjectArgs.builder()
            .bucket(bucketName)
            .object(objectName)
            .filename(destinationPath)
            .build()
    );
}
  • 方式三:获取文件的 InputStream(浏览器下载):在 Web 应用中,可以将文件内容直接写入 HTTP 响应流,供用户下载或预览
import io.minio.GetObjectArgs;
import jakarta.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 获取文件输入流并返回给客户端
 */
public void downloadToResponse(String bucketName, String objectName, HttpServletResponse response) throws Exception {
    try (InputStream stream = minioClient.getObject(
            GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
         OutputStream os = response.getOutputStream()) {

        // 设置响应头(根据文件类型调整 Content-Type)
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + objectName + "\"");

        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = stream.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
    }
}

说明:getObject 方法返回文件内容的 InputStream,可以灵活地处理(如转换格式、边读边处理等),比直接下载到本地文件更适用于内存处理场景。

 完整示例代码

import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import io.minio.DownloadObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;

public class MinIODemo {
    public static void main(String[] args) throws Exception {
        // 1. 初始化客户端
        MinioClient minioClient = MinioClient.builder()
                .endpoint("http://127.0.0.1:9000")
                .credentials("admin", "your_secure_password")
                .build();

        String bucketName = "my-demo-bucket";
        String objectName = "hello.txt";
        String sourceFile = "C:/temp/hello.txt";
        String destFile = "C:/temp/hello_downloaded.txt";

        // 2. 确保存储桶存在
        boolean found = minioClient.bucketExists(
            BucketExistsArgs.builder().bucket(bucketName).build()
        );
        if (!found) {
            minioClient.makeBucket(
                MakeBucketArgs.builder().bucket(bucketName).build()
            );
            System.out.println("Bucket created: " + bucketName);
        }

        // 3. 上传文件
        minioClient.uploadObject(
            UploadObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .filename(sourceFile)
                .build()
        );
        System.out.println("File uploaded: " + objectName);

        // 4. 生成预签名 URL
        String url = minioClient.getPresignedObjectUrl(
            GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(7 * 24 * 3600)  // 7 days
                .build()
        );
        System.out.println("Presigned URL: " + url);

        // 5. 下载文件到本地
        minioClient.downloadObject(
            DownloadObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .filename(destFile)
                .build()
        );
        System.out.println("File downloaded to: " + destFile);
    }
}

 实际开发建议

  • 配置分离:将 endpoint、accessKey、secretKey 等参数提取到配置文件中,便于不同环境切换(开发/测试/生产),也避免将敏感信息硬编码在代码中。
  • 封装 Service 层:将 MinIO 操作封装成独立的 Service(如 MinioFileService),提供统一的上传、下载、删除等接口,配合 Spring 的 @Service 注解便于业务层调用。
  • 异常处理:MinIO SDK 的调用可能会抛出 MinioException、InvalidKeyException 等异常,建议在封装层统一处理并转换为业务异常。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐