2025 docker 安装 MinIO 操作实践

最近使用docker 安装MinIO时,发现界面不一样了,最新版的 MinIO 社区版中缺少用户设置,这是因为 MinIO 近期对其社区版的用户界面 (UI) 功能进行了重大调整。过去,MinIO 的 Web 控制台提供了账户和策略管理、配置设置等功能。然而,在最新的社区版中,这些功能已被移除,现在其 Web 界面主要作为一个对象浏览器。

主要调整:
UI 功能的限制: 社区版的 Web UI 不再支持用户和策略管理、桶管理、配置管理等。
转向命令行: 你需要使用 MinIO Client (mc) 工具来执行这些管理操作,例如添加用户、设置权限等。
商业版区分: MinIO 的商业版本(AIStor)提供了更完整的 Web UI 管理功能以及其他企业级特性和支持。

具体实践流程( 部署单节点 MinIO)

准备工作
1、拉取MinIO镜像

docker pull minio

2、在你的宿主机上创建一个目录,用于存储 MinIO 的数据

mkdir -p ~/home/minio/data

3、启动 MinIO 容器

docker run \
  -p 9000:9000 \
  -p 9090:9090 \
  --name minio \
  -v ~/home/minio/data:/data \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  minio/minio server /data --console-address ":9090"

4、验证 MinIO 运行
打开浏览器,访问 http://localhost:9090。你将看到 MinIO 的登录界面。输入你在启动命令中设置的用户名 (minioadmin) 和密码 (minioadmin) 即可登录 MinIO Console。

效果如下:
在这里插入图片描述

使用 MinIO Client (mc) 管理 MinIO
1、Linux 系统下载mc

wget https://dl.min.io/client/mc/release/linux-amd64/mc

下载完成后,你需要给下载下来的 mc 文件添加执行权限

chmod +x mc

将 mc 移动到 PATH 路径中 (可选但推荐)

sudo mv mc /usr/local/bin/

验证安装

mc --version

2、将 MinIO 服务器添加到 mc 的配置中

mc alias set myminio http://localhost:9000 minioadmin minioadmin

(这里的myminio 是你http://localhost:9000的别名,后续在执行像 mc admin user add myminio newuser newpassword 这样的操作时,myminio 就会自动被解析成您之前配置的那个完整的 MinIO 服务器地址。这大大简化了命令行操作。)

3、通过命令行来操作bucket

创建一个桶 (Bucket)

mc mb myminio/mybucket

上传文件到桶

mc cp /path/to/your/local/file.txt myminio/mybucket/

列出桶中的文件

mc ls myminio/mybucket

注意:关于bucket的操作直接在Web UI 中进行

4、用户策略管理(重要,在 Web UI 中已不可用

创建新用户

mc admin user add myminio newuser newpassword

创建策略:假设你想创建一个只读策略,允许访问 mybucket,需要创建一个名为 readonly-policy.json 的文件

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::mybucket",
        "arn:aws:s3:::mybucket/*"
      ]
    }
  ]
}

上传并应用策略:

mc admin policy add myminio myreadonlypolicy readonly-policy.json

为用户附加策略:

mc admin policy set myminio myreadonlypolicy user=newuser

java中使用MinIO

1、安装对应依赖

<!--hutool工具-->
 <dependency>
       <groupId>cn.hutool</groupId>
       <artifactId>hutool-all</artifactId>
       <version>5.8.37</version>
</dependency>

<!--MinIO-->
 <dependency>
       <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>8.5.17</version>
</dependency>

2、配置yaml文件

minio:
  url: http://localhost:9000
  accessKey: minioadmin
  secretKey: minioadmin

3、创建对应工具类

package com.szq.detection_system_back.utils;

import cn.hutool.core.io.FileUtil;
import io.minio.*;
import io.minio.messages.Item;
import io.minio.http.Method; // 导入 Method 类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; // 导入 TimeUnit
import java.util.logging.Logger;

/**
 * MinIO 工具类,用于文件上传、下载、删除、列表展示和获取文件URL。
 */
@Component
public class MinioUtil {

    private static final Logger logger = Logger.getLogger(MinioUtil.class.getName());

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

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

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

    private MinioClient minioClient;

    /**
     * 初始化 MinioClient
     */
    private void initMinioClient() {
        if (minioClient == null) {
            minioClient = MinioClient.builder()
                    .endpoint(minioUrl)
                    .credentials(accessKey, secretKey)
                    .build();
        }
    }

    /**
     * 检查桶是否存在,如果不存在则创建
     *
     * @param bucketName 桶名称
     * @throws Exception 如果操作失败
     */
    public void createBucketIfNotExists(String bucketName) throws Exception {
        initMinioClient();
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!found) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            logger.info("桶 '" + bucketName + "' 创建成功.");
        } else {
            logger.info("桶 '" + bucketName + "' 已存在.");
        }
    }

    /**
     * 上传单个文件
     *
     * @param bucketName    桶名称
     * @param objectName    对象名称(MinIO中存储的文件名)
     * @param localFilePath 本地文件路径
     * @throws Exception 如果操作失败
     */
    public void uploadFile(String bucketName, String objectName, String localFilePath) throws Exception {
        initMinioClient();
        createBucketIfNotExists(bucketName); // 确保桶存在

        try (FileInputStream fis = new FileInputStream(localFilePath)) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(fis, fis.available(), -1)
                            .build());
            logger.info("文件 '" + objectName + "' 成功上传到桶 '" + bucketName + "'.");
        }
    }

    /**
     * 下载文件
     *
     * @param bucketName      桶名称
     * @param objectName      对象名称(MinIO中存储的文件名)
     * @param downloadDirectory 下载目录
     * @param desiredLocalFileName 下载到本地的文件名(包含扩展名)
     * @return 下载后的完整文件路径
     * @throws Exception 如果操作失败
     */
    public String downloadFile(String bucketName, String objectName, String downloadDirectory, String desiredLocalFileName) throws Exception {
        initMinioClient();
        FileUtil.mkdir(downloadDirectory); // 确保下载目录存在
        String downloadFilePath = Paths.get(downloadDirectory, desiredLocalFileName).toString();

        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
             FileOutputStream fos = new FileOutputStream(downloadFilePath)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = stream.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            logger.info("文件 '" + objectName + "' 成功下载到: " + downloadFilePath);
            return downloadFilePath;
        }
    }

    /**
     * 列出桶中的所有对象
     *
     * @param bucketName 桶名称
     * @return 对象名称列表
     * @throws Exception 如果操作失败
     */
    public List<String> listObjects(String bucketName) throws Exception {
        initMinioClient();
        List<String> objectNames = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        logger.info("列出桶 '" + bucketName + "' 中的对象:");
        for (Result<Item> result : results) {
            Item item = result.get();
            objectNames.add(item.objectName());
            logger.info("  - " + item.objectName() + " (大小: " + item.size() + " bytes)");
        }
        return objectNames;
    }

    /**
     * 删除对象
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @throws Exception 如果操作失败
     */
    public void removeObject(String bucketName, String objectName) throws Exception {
        initMinioClient();
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
        logger.info("对象 '" + objectName + "' 成功从桶 '" + bucketName + "' 中删除.");
    }

    /**
     * 删除桶
     *
     * @param bucketName 桶名称
     * @throws Exception 如果操作失败
     */
    public void removeBucket(String bucketName) throws Exception {
        initMinioClient();
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        logger.info("桶 '" + bucketName + "' 已删除.");
    }


    /**
     * 上传整个文件夹到 MinIO 的指定桶。
     * 会递归遍历本地文件夹中的所有文件和子文件夹。
     * MinIO 中的对象名称会保留原始文件的相对路径。
     *
     * @param bucketName 目标桶名称
     * @param localFolderPath 要上传的本地根文件夹的路径
     * @param objectPrefix 在 MinIO 中存储的根前缀 (可选,例如 "user_data/")。如果为空,文件将直接上传到桶的根目录。
     * @throws Exception 如果上传过程中发生错误
     */
    public void uploadFolder(String bucketName, String localFolderPath, String objectPrefix) throws Exception {
        initMinioClient();
        createBucketIfNotExists(bucketName); // 确保桶存在

        File baseFolder = new File(localFolderPath);
        if (!baseFolder.isDirectory()) {
            throw new IllegalArgumentException("提供的路径不是一个目录: " + localFolderPath);
        }

        // 确保前缀以斜杠结尾,如果提供了的话
        if (objectPrefix != null && !objectPrefix.isEmpty() && !objectPrefix.endsWith("/")) {
            objectPrefix += "/";
        } else if (objectPrefix == null) {
            objectPrefix = ""; // 如果没有提供前缀,则为空字符串
        }

        logger.info("开始上传文件夹 '" + localFolderPath + "' 到桶 '" + bucketName + "',MinIO 前缀为 '" + objectPrefix + "'.");
        // 开始递归上传
        uploadDirectoryRecursive(bucketName, baseFolder, objectPrefix);
        logger.info("文件夹 '" + localFolderPath + "' 上传完成。");
    }

    /**
     * 递归上传目录中的文件和子目录。
     *
     * @param bucketName 目标桶名称
     * @param directory 当前要处理的本地目录
     * @param currentObjectPrefix 在 MinIO 中的当前对象前缀
     * @throws Exception 如果上传过程中发生错误
     */
    private void uploadDirectoryRecursive(String bucketName, File directory, String currentObjectPrefix) throws Exception {
        File[] files = directory.listFiles();
        if (files == null) {
            logger.warning("目录为空或不可读: " + directory.getAbsolutePath());
            return;
        }

        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是子目录,递归调用
                uploadDirectoryRecursive(bucketName, file, currentObjectPrefix + file.getName() + "/");
            } else {
                // 如果是文件,上传到 MinIO
                String objectName = currentObjectPrefix + file.getName();
                uploadFileInternal(bucketName, file, objectName); // 调用内部方法,避免重复检查桶
            }
        }
    }

    /**
     * 内部方法:上传单个文件到 MinIO,不进行桶存在性检查。
     * 主要供递归上传文件夹时调用。
     *
     * @param bucketName 桶名称
     * @param file 本地文件对象
     * @param objectName 在 MinIO 中的对象名称
     * @throws Exception 如果上传过程中发生错误
     */
    private void uploadFileInternal(String bucketName, File file, String objectName) throws Exception {
        try (FileInputStream fis = new FileInputStream(file)) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(fis, file.length(), -1)
                            .build());
            logger.info("  - 上传文件: " + file.getAbsolutePath() + " -> MinIO: " + bucketName + "/" + objectName);
        } catch (IOException e) {
            logger.severe("上传文件失败: " + file.getAbsolutePath() + ", 错误: " + e.getMessage());
            throw e; // 重新抛出异常以便调用方处理
        }
    }

    /**
     * 获取 MinIO 文件的预签名 URL。
     * 这个 URL 具有时效性,可用于临时访问私有文件。
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称(MinIO中存储的文件名)
     * @param expirySeconds URL 的有效期(秒),默认 MinIO SDK 是 7 天 (604800 秒)。
     * @return 预签名 URL 字符串
     * @throws Exception 如果获取失败
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, int expirySeconds) throws Exception {
        initMinioClient();
        // 校验桶是否存在,但通常获取预签名URL时,文件应该已经存在
        // 如果文件不存在,生成的URL访问时会是404,但URL本身可以生成
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            throw new IllegalArgumentException("桶 '" + bucketName + "' 不存在.");
        }

        String url = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET) // 指定为 GET 请求,用于下载或直接访问
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expirySeconds, TimeUnit.SECONDS) // 设置过期时间,单位为秒
                        .build());
        logger.info("对象 '" + objectName + "' 的预签名URL已生成,有效期 " + expirySeconds + " 秒: " + url);
        return url;
    }

    /**
     * 获取 MinIO 文件的公共 URL。
     * 注意:这要求 MinIO 桶或对象设置为公共可读。
     * 如果没有设置公共策略,该 URL 可能无法直接访问。
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称(MinIO中存储的文件名)
     * @return 文件的公共 URL 字符串
     */
    public String getPublicObjectUrl(String bucketName, String objectName) {
        // 公共URL通常就是 MinIO URL + 桶名 + 对象名
        // 例如:http://8.222.179.199:9000/detect-beetle/打印清单.jpg
        String publicUrl = minioUrl + "/" + bucketName + "/" + objectName;
        logger.info("对象 '" + objectName + "' 的公共URL: " + publicUrl);
        return publicUrl;
    }
}

4、controller 中进行测试

@RestController
@RequestMapping("/api")
public class DetectionController {

    private final MinioUtil minioUtil;

    @Autowired
    public DetectionController(MinioUtil minioUtil) {
        this.minioUtil = minioUtil;
    }

    @PostMapping("test/minio")
    public String testMinio(@RequestParam("bucketName") String bucketName, @RequestParam("objectName") String objectName,
                    @RequestParam("expirySeconds") int expirySeconds) throws Exception {

        return minioUtil.getPresignedObjectUrl(bucketName, objectName,expirySeconds);
    }
}

测试:
在这里插入图片描述
这里成功返回了对应内容的url

其他方法,可自行测试。

总结

本篇文章主要是针对2025 MinIO更新后的操作实践,重点是进行常用操作的流程熟悉,需要注意的是代码中使用的是初始管理员凭证,如果需要对程序进行权限限制的话,可以使用拥有对应权限的账户(mc admin user add myminio newuser newpassword 默认没有任何权限)

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐