Quarkus Minio 教程 - 从Minio存储和检索对象
考虑一个需要存储用户生成内容(如图片、视频和文档)的Web应用程序。与将其存储在文件系统或使用数据库不同,Web应用程序可以使用对象存储。对象存储将对象作为单个单元处理,提供每个对象的元数据,并抽象出可以是本地或分布式的底层存储。在这篇博客文章中,我将解释使用Docker的Minio本地设置。我还使用Quarkus作为云原生应用程序的框架选择,并使用与Quarkus配合得非常出色的Minio SDK。让我们开始吧:)
对象是二进制数据,有时称为二进制大对象(BLOB)。Blob可以是图像、音频文件、电子表格,甚至是二进制可执行代码。像MinIO这样的对象存储平台提供了专用的工具和功能来存储、检索和搜索blob。——根据Minio
本地设置
Minio,除了其酷炫的名称外,使用Docker在本地运行它超级简单。他们还有一个官方 playground,大约保留文件和桶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人员有一个playground,对初学者非常有用这里。一旦您登录那里,您将需要创建一个名为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中添加以下属性,以便Minio可以在MinioClientProducer中使用
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
41
|
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桶中获取对象。
好的,现在让我们测试我们的上传器、下载器
在文档目录中,我们有一个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
// 一个文件也可以作为对象上传到桶中,但对于流,我们仍然想使用putObject,如我们的例子。
minioClient.uploadObject
|