2025 docker 安装 minio 操作实践、java实践_minio社区版功能限制
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、安装对应依赖
cn.hutool hutool-all 5.8.37 io.minio minio 8.5.17
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; // 导入 TimeUnitimport java.util.logging.Logger;/** * MinIO 工具类,用于文件上传、下载、删除、列表展示和获取文件URL。 */@Componentpublic 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 默认没有任何权限)