2025 docker 安装 minio 操作实践、java实践
摘要 2025年MinIO社区版Docker安装实践显示,最新版本移除了Web UI的用户管理功能,仅保留对象浏览功能。文章详细介绍了通过Docker部署单节点MinIO的步骤:拉取镜像、创建数据目录、启动容器及验证。针对UI功能限制,重点讲解了使用MinIO Client(mc)进行用户和策略管理的替代方案,包括创建用户、定义策略和权限分配。最后提供了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、安装对应依赖
<!--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 默认没有任何权限)
更多推荐
所有评论(0)