Quarkus Minio教程 - 从Minio存储和检索对象
考虑一个需要存储用户生成内容(如图片、视频和文档)的Web应用程序。与使用文件系统或数据库不同,该应用程序可以使用对象存储。对象存储将对象作为单个单元处理,提供每个对象的元数据,并抽象化底层存储(可以是本地或分布式存储)。在这篇博文中,我将解释使用Docker进行Minio的本地设置。我还使用Quarkus作为云原生应用程序的框架,并使用与Quarkus配合出色的Minio SDK。让我们开始吧 :)
对象是二进制数据,有时称为二进制大对象(BLOB)。Blob可以是图像、音频文件、电子表格,甚至是二进制可执行代码。像MinIO这样的对象存储平台提供了专用的工具和功能来存储、检索和搜索blob。——根据Minio
本地设置
Minio,除了其酷炫的名称外,使用Docker在本地运行非常简单。他们还有一个官方游乐场,可将文件和存储桶保留约24小时;或者至少我的在24小时内被删除了。但在本文中,我专注于本地设置。在这个例子中,我使用一个docker-compose文件。我通常更喜欢compose的原因是因为它让我可以在一个文件中拉取多个服务。如果我必须每次都记住每个命令和参数,这就不太容易。这样,所有内容都集中在一个地方。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
minio:
image: minio/minio (1)
ports: (2)
- "9000:9000"
- "9001:9001"
environment: (3)
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
MINIO_BUCKETS: jakarta-bucket # 使用存储桶初始化
volumes: (4)
- ./data:/data
- ./config:/root/.minio
command: server /data --console-address ":9001"
|
- image: minio/minio - 拉取官方的minio镜像,我这里只是拉取最新的。标签也可以在
:
后提供。
- 将端口设置到本地机器。服务器端口9000映射到9000,控制台端口9001映射到9001。这样也可以访问管理仪表板,以防有人想要管理对象,这在开发过程中非常有用。
- 为容器设置环境变量。minio镜像就是这样获取服务器配置的。
- 可以省略卷。在某些不希望数据在容器外部持久化的情况下,这可能是个好主意。在这种情况下,数据是持久化的,因此即使重新启动容器,它仍然会保留您添加的存储桶/对象等。
- 最后是服务器命令,它告诉服务器从
/data
加载,并监听控制台地址。
这一切都很好。但有一件事让我印象深刻。嘿,关于它启动时也应该加载存储桶怎么样?为什么要手动创建它。我项目中的相同名称也应该在这里。这可以使用一个初始化容器来完成。我将此服务称为初始化器,因为这基本上是它的作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 在minio中创建一个存储桶并从本地驱动器上传文件。
initializer:
image: alpine:latest
entrypoint: sh (1)
command: # 复制文档中的文件
- -c (2)
- |
apk add --no-cache curl jq; # 安装必要的工具 (3)
until curl -s http://minio:9000/minio/health/ready >/dev/null; do
echo "Waiting for MinIO to start...";
sleep 2;
done;
echo "MinIO is ready. Installing MinIO client (mc)..."; (4)
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/bin/mc && chmod +x /usr/bin/mc;
echo "Uploading file..."; (5)
mc alias set myminio http://minio:9000 minioadmin minioadmin;
mc mb myminio/jakarta-bucket || true; # 确保存储桶存在
mc cp --recursive /documents/ myminio/jakarta-bucket/; (6)
depends_on:
- minio
volumes:
- ../documents:/documents # 将'documents'目录挂载到容器中
|
- entrypoint是启动时调用的脚本。
-c
表示将在启动时运行的多个命令。
- 检查minio服务是否健康并响应。
- 下载mc - minio客户端。我真的很希望有更简单的方法来做到这一点。但下载客户端可能是进入下一步的唯一途径。
- 为项目创建一个存储桶,即jakarta-bucket。
- 使用本地文档目录(即挂载到容器中的目录)并将文件递归添加到存储桶。
这解决了基础设施部分。现在,每当minio服务启动时,它也会初始化存储桶。
完整的docker-compose.yml文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
services:
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
MINIO_BUCKETS: jakarta-bucket # 使用存储桶初始化
volumes:
- ./data:/data
- ./config:/root/.minio
command: server /data --console-address ":9001"
# 在minio中创建一个存储桶并从本地驱动器上传文件。
initializer:
image: alpine:latest
entrypoint: sh
command: # 复制文档中的文件
- -c
- |
apk add --no-cache curl jq; # 安装必要的工具
until curl -s http://minio:9000/minio/health/ready >/dev/null; do
echo "Waiting for MinIO to start...";
sleep 2;
done;
echo "MinIO is ready. Installing MinIO client (mc)...";
wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/bin/mc && chmod +x /usr/bin/mc;
echo "Uploading file...";
mc alias set myminio http://minio:9000 minioadmin minioadmin;
mc mb myminio/jakarta-bucket || true; # 确保存储桶存在
# 如果希望在初始化期间上传文件,请取消注释。
#mc cp --recursive /documents/ myminio/jakarta-bucket/;
depends_on:
- minio
volumes:
- ../documents:/documents # 将'documents'目录挂载到容器中
|
要在本地启动这个配置:
1
|
docker-compose -f deploy/docker-compose.yml -up
|
一旦compose文件启动并运行。尝试类似docker ps
的命令,您将看到服务正在运行。
现在,您可以通过访问localhost:9000
登录Minio,在这种情况下,凭据将是minioadmin
/minioadmin
。同时,一个存储桶已经被初始化。
如果您不想使用本地Docker设置,Minio团队为初学者提供了一个非常有用的游乐场。登录后,您需要创建一个名为jakarta-bucket
的新存储桶。
基于Minio的Quarkus应用程序
最后转到code.quarkus.io,并生成一个新项目。也可以使用quarkus cli生成一个新项目。无论如何,为了使用minio,添加以下依赖项。Quarkiverse是由社区成员贡献的Quarkus扩展的宇宙,包含许多扩展。其中之一是实验性的minio扩展。但在这种情况下,我只是使用minio提供的依赖项。将以下内容添加到pom.xml文件中。
1
2
3
4
5
6
|
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.17</version>
<type>jar</type>
</dependency>
|
我还在application.properties中添加了以下属性,以便可以在MinioClientProducer中使用minio。
1
2
3
4
5
|
# 远程配置通过
minio.endpoint=https://localhost:9000
minio.access-key=minioadmin
minio.secret-key=minioadmin
minio.bucket-name=jakarta-bucket
|
下一步创建一个MinioClient。我使用一个生产者(producer),以便它可以在我的项目中全局使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
import io.minio.MinioClient;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class MinioClientProducer {
@ConfigProperty(name = "minio.endpoint") //(1)
String endpoint;
@ConfigProperty( name = "minio.access-key")
String accessKey;
@ConfigProperty( name = "minio.secret-key")
String secretKey;
@Produces //(2)
@ApplicationScoped //(3)
public MinioClient getMinioClient() {
return MinioClient.builder() //(4)
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
|
@ConfigProperty
,从application.properties中提取键值对。
@Produces
,生成可在应用程序中使用的MinioClient。
@ApplicationScoped
,表示这只会被生成一次。
- 最后使用凭据和minio端点构建MinioClient。
接下来,我们为HTTP请求创建上传表单。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import jakarta.ws.rs.core.MediaType;
import java.io.InputStream;
public class FileUploadForm {
@RestForm
public InputStream file; //(1)
@RestForm
@PartType(MediaType.TEXT_PLAIN)
public String fileName; //(2)
@RestForm
@PartType(MediaType.TEXT_PLAIN)
public String contentType; //(3)
}
|
- file: 一个InputStream,表示上传文件的内容。
- fileName: 一个String,保存上传文件的名称,指定为纯文本。
- contentType: 一个String,保存上传文件的MIME类型,也指定为纯文本。
这些字段使用@RestForm
和@PartType(MediaType.TEXT_PLAIN)
注解,表明它们应该绑定到HTTP请求中的表单字段。
好的,在这一点上,我们已经成功完成了两件事,一是我们有一个MinioClient,可以在我们应用程序的任何地方使用。这有助于我们保持同步并控制客户端的行为和连接。其次,我们还有一个基本上可以接收文件及其类型的表单。我们没有的是为我们完成工作的主要REST端点。我们也可以创建一个处理所有Minio交互的服务,在一个大型项目中,这绝对是我的选择,即使有助于测试,如果你喜欢测试的话:)。无论如何,为了简单起见,我在这里使用一个REST资源进行展示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.GetObjectArgs;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.io.InputStream;
@Path("/minio")
@ApplicationScoped
public class MinioResource {
@Inject //(1)
MinioClient minioClient;
@ConfigProperty( name = "minio.bucket-name") //(2)
String bucketName;
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA) //(3)
public Response uploadFile(FileUploadForm form) {
try {
minioClient.putObject( //(4)
PutObjectArgs.builder()
.bucket(bucketName)
.object(form.fileName)
.stream(form.file, form.file.available(), -1)
.contentType(form.contentType)
.build()
);
return Response.ok("File uploaded successfully: " + form.fileName).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
}
|
- 注入在我们MinioClientProducer类中生成的MinioClient。
- 我们想要使用的存储桶名称。假设该存储桶在minio中可用。我们在docker-compose.yml文件中通过初始化容器处理了这一点。
- REST端点的意图是它消费一个上传表单作为多部分数据。
- 将接收到的对象放入S3存储桶。
接下来也添加上传代码。如果我们能上传和下载同一个文件来查看它是否真的有效,那对我们的测试将非常有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@GET
@Path("/download/{fileName}")//(1)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response downloadFile(@PathParam("fileName") String fileName) {
try {
// 从MinIO下载文件
InputStream stream = minioClient.getObject( //(2)
GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build()
);
return Response.ok(stream)
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
.build();
} catch (Exception e) {
return Response.status(Response.Status.NOT_FOUND).entity("File not found").build();
}
}
}
|
- 我们希望在请求中传递fileName。
- 从minio存储桶获取对象。
好的,现在让我们测试我们的上传器和下载器。
在documents目录中,我们有一个pdf文档,现在可以使用curl尝试上传,如下所示。
1
2
3
4
|
curl -X POST http://localhost:8080/minio/upload \
-F "file=@documents/jakartaee12.pdf" \
-F "fileName=jakartaee12.pdf" \
-F "contentType=application/pdf"
|
以及以下命令来下载文件:
1
|
curl -X GET http://localhost:8080/minio/download/jakartaee12.pdf -o jakartaee12.pdf
|
此示例的源代码存在于此处
沿途其他一些有趣的事情:
1
2
3
4
5
6
7
8
9
10
11
|
// 检查存储桶是否已存在
minioClient.bucketExists(BucketExistsArgs.builder().bucket("bucketNAME").build());
// 在代码中创建存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket("bucketNAME").build());
// 删除存储桶
minioClient.removeBucket(RemoveBucketArgs.builder().bucket("bucketNAME").build());
// 文件也可以作为对象上传到存储桶中,但对于流,我们仍然希望像在我们的示例中那样使用putObject。
minioClient.uploadObject(UploadObjectArgs.builder().bucket("bucketNAME").object("objectNAME").filename("filePATH").build());
|