MinIo在Ubantu和Java中的整合
1.MinIo在Ubantu中的部署
首先准备好一台已经安装好Ubantu系统的服务器
MinIO是一个开源的对象存储服务器,兼容Amazon S3,性能卓越,适合存储非结构化数据,例如照片、视频、日志文件、备份和容器镜像等。
1:更新系统
首先更新你的系统包:
sudo apt update sudo apt upgrade -y
2:下载和安装MinIO
从MinIO官网下载最新的稳定版本二进制文件:
wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio sudo mv minio /usr/local/bin/
3:配置MinIO
-
创建MinIO用户: 出于安全考虑,建议以非root用户运行MinIO。
sudo useradd -r minio-user -s /sbin/nologin
2.创建目录: 创建MinIO数据和配置文件目录:
sudo mkdir /usr/local/share/minio sudo mkdir /etc/minio sudo chown -R minio-user:minio-user /usr/local/share/minio sudo chown -R minio-user:minio-user /etc/minio
3.设置环境变量: 创建一个文件来存储MinIO环境变量:
sudo nano /etc/default/minio
将以下内容添加到文件中,用你自己的访问密钥和密钥替换YOUR_ACCESS_KEY
和YOUR_SECRET_KEY
:
MINIO_VOLUMES="/usr/local/share/minio/" MINIO_OPTS="--address :9000 --console-address :9090" MINIO_ACCESS_KEY="YOUR_ACCESS_KEY" MINIO_SECRET_KEY="YOUR_SECRET_KEY"
-
9000
: MinIO服务端口,即外部访问端口。 -
9090
: MinIO控制台端口,即内部访问端口。 -
YOUR_ACCESS_KEY
和YOUR_SECRET_KEY
: 你的MinIO访问密钥和密钥。 -
/usr/local/share/minio/
: MinIO数据目录。
4.创建systemd服务文件: 创建一个systemd服务文件来管理MinIO服务:
sudo nano /etc/systemd/system/minio.service
添加以下内容:
[Unit] Description=MinIO Documentation=https://docs.min.io Wants=network-online.target After=network-online.target [Service] User=minio-user Group=minio-user EnvironmentFile=/etc/default/minio ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES Restart=always LimitNOFILE=65536 [Install] WantedBy=multi-user.target
启动MinIO:
sudo systemctl daemon-reload sudo systemctl start minio sudo systemctl enable minio
访问MinIO管理页面
管理页面的访问地址为:http://yourIp:9001
API调用的 IP:http://yourIp:9000
注意:
ip
需要根据实际情况做出修改
2.MinIO快速入门
2.1 MinIO核心概念
下面介绍MinIO中的几个核心概念,这些概念在所有的对象存储服务中也都是通用的。
-
对象(Object)
对象是实际的数据单元,例如我们上传的一个图片。
-
存储桶(Bucket)
存储桶是用于组织对象的命名空间,类似于文件夹。每个存储桶可以包含多个对象。
-
端点(Endpoint)
端点是MinIO服务器的网络地址,用于访问存储桶和对象,例如
http://192.168.10.101:9000
注意:
9000
为MinIO的API的默认端口,前边配置的9001
以为管理页面端口。 -
Access Key 和 Secret Key
Access Key是用于标识和验证访问者身份的唯一标识符,相当于用户名。
Secret Key是与Access Key关联的密码,用于验证访问者的身份。
2.2 MinIO管理页面操作
-
登录
管理页面的地址为http://192.168.10.101:9001,登录的用户名和密码为部署时在
EnvironmentFile
文件中配置的如下参数MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin
-
创建存储桶
-
上传图片
-
找到目标桶
-
上传图片
-
-
访问图片
-
图片URL
由于MinIO提供了HTTP访问功能,所以可以通过浏览器直接访问对象。对象URL为MinIO的
Endpoint
+对象的存储路径
,例如下图中的图片对象的URL为http:192.168.10.101:9000/test/公寓-外观.jpg。 -
访问权限
不出意外的话,使用浏览器访问上述URL,会得到如下响应,很显然是没有访问权限。
<Error> <Code>AccessDenied</Code> <Message>Access Denied.</Message> <Key>A.jpg</Key> <BucketName>test</BucketName> <Resource>/test/A.jpg</Resource> <RequestId>177BC92022FC5684</RequestId> <HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId> </Error>
若想继续访问图片,需要修改图片所在桶的访问权限,如下图所示
如上图所示,可选的访问权限共有三个选项,分别是
Private
、Public
和Custom
,具体说明如下-
Private
只允许桶的所有者对该桶进行读写。
-
Public
允许所有人对该桶进行读写。
-
Custom
自定义访问权限。
若想将权限设置为只允许所有者写,但允许所有人读,就需要自定义访问权限。自定义访问权限,需要使用一个规定格式的JSON字符串进行描述,具体格式可参考官方文档。
例如以下JSON字符串表达的含义是:允许(
Allow
)所有人(*
)读取(s3:GetObject
)指定桶(test
)的所有内容。{ "Statement" : [ { "Action" : "s3:GetObject", "Effect" : "Allow", "Principal" : "*", "Resource" : "arn:aws:s3:::test/*" } ], "Version" : "2012-10-17" }
将
test
桶访问权限设置为Custom
,并添加上述内容 -
-
重新访问http:192.168.10.101:9000/test/A.jpg,观察是否正常。
-
2.3 MinIO Java SDK
MinIO提供了多种语言的SDK供开发者使用,本项目需要用到Java SDK,下面通过一个简单案例熟悉一下其基本用法,具体内容可参考官方文档。
-
创建一个Maven项目
-
引入如下依赖
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.3</version> </dependency>
-
编写如下内容
public class App { public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException { try { //构造MinIO Client MinioClient minioClient = MinioClient.builder() .endpoint("http://192.168.10.101:9000") .credentials("minioadmin", "minioadmin") .build(); //创建hello-minio桶 boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket("hello-minio").build()); if (!found) { //创建hello-minio桶 minioClient.makeBucket(MakeBucketArgs.builder().bucket("hello-minio").build()); //设置hello-minio桶的访问权限 String policy = """ { "Statement" : [ { "Action" : "s3:GetObject", "Effect" : "Allow", "Principal" : "*", "Resource" : "arn:aws:s3:::hello-minio/*" } ], "Version" : "2012-10-17" }"""; minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket("hello-minio").config(policy).build()); } else { System.out.println("Bucket 'hello-minio' already exists."); } //上传图片 minioClient.uploadObject( UploadObjectArgs.builder() .bucket("hello-minio") .object("A.jpg") .filename("D:\\workspace\\hello-minio\\src\\main\\resources\\A.jpg") .build()); System.out.println("上传成功"); } catch (MinioException e) { System.out.println("Error occurred: " + e); } } }
-
运行测试
运行上述代码,然后查看MinIO管理页面,观察是否上传成功。
3.MioIo在实际项目中的整合应用
3.1图片上传流程
下图展示了新增房间或公寓时,上传图片的流程。
可以看出图片上传接口接收的是图片文件,返回的Minio对象的URL。
3.2 图片上传接口开发
下面为该接口的具体实现
-
配置Minio Client
-
引入Minio Maven依赖
在common模块的
pom.xml
文件增加如下内容:<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> </dependency>
-
配置Minio相关参数
在
application.yml
中配置Minio的endpoint
、accessKey
、secretKey
、bucketName
等参数minio: endpoint: http://<hostname>:<port> access-key: <access-key> secret-key: <secret-key> bucket-name: <bucket-name>
注意:上述
<hostname>
、<port>
等信息需根据实际情况进行修改。 -
在common模块中创建
com.atguigu.lease.common.minio.MinioProperties
,内容如下@ConfigurationProperties(prefix = "minio") @Data public class MinioProperties { private String endpoint; private String accessKey; private String secretKey; private String bucketName; }
-
在common模块中创建
com.atguigu.lease.common.minio.MinioConfiguration
,内容如下@Configuration @EnableConfigurationProperties(MinioProperties.class) public class MinioConfiguration { @Autowired private MinioProperties properties; @Bean public MinioClient minioClient() { return MinioClient.builder().endpoint(properties.getEndpoint()).credentials(properties.getAccessKey(), properties.getSecretKey()).build(); } }
-
-
开发图片上传接口
-
编写Controller层逻辑
在
FileUploadController
中增加如下内容@Tag(name = "文件管理") @RequestMapping("/admin/file") @RestController public class FileUploadController { @Autowired private FileService service; @Operation(summary = "上传文件") @PostMapping("upload") public Result<String> upload(@RequestParam MultipartFile file) { String url = service.upload(file); return Result.ok(url); } }
说明:
MultipartFile
是Spring框架中用于处理文件上传的类,它包含了上传文件的信息(如文件名、文件内容等)。 -
编写Service层逻辑
-
在
FileService
中增加如下内容String upload(MultipartFile file);
-
在
FileServiceImpl
中增加如下内容@Autowired private MinioProperties properties; @Autowired private MinioClient client; @Override public String upload(MultipartFile file) { try { boolean bucketExists = client.bucketExists(BucketExistsArgs.builder().bucket(properties.getBucketName()).build()); if (!bucketExists) { client.makeBucket(MakeBucketArgs.builder().bucket(properties.getBucketName()).build()); client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(properties.getBucketName()).config(createBucketPolicyConfig(properties.getBucketName())).build()); } String filename = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/" + UUID.randomUUID() + "-" + file.getOriginalFilename(); client.putObject(PutObjectArgs.builder(). bucket(properties.getBucketName()). object(filename). stream(file.getInputStream(), file.getSize(), -1). contentType(file.getContentType()).build()); return String.join("/", properties.getEndpoint(), properties.getBucketName(), filename); } catch (Exception e) { e.printStackTrace(); } return null; } private String createBucketPolicyConfig(String bucketName) { return """ { "Statement" : [ { "Action" : "s3:GetObject", "Effect" : "Allow", "Principal" : "*", "Resource" : "arn:aws:s3:::%s/*" } ], "Version" : "2012-10-17" } """.formatted(bucketName); }
注意:
上述
createBucketPolicyConfig
方法的作用是生成用于描述指定bucket访问权限的JSON字符串。最终生成的字符串格式如下,其表示,允许(Allow
)所有人(*
)获取(s3:GetObject
)指定桶(<bucket-name>
)的内容。{ "Statement" : [ { "Action" : "s3:GetObject", "Effect" : "Allow", "Principal" : "*", "Resource" : "arn:aws:s3:::<bucket-name>/*" } ], "Version" : "2012-10-17" }
由于公寓、房间的图片为公开信息,所以将其设置为所有人可访问。
3.3异常处理
-
问题说明
上述代码只是对
MinioClient
方法抛出的各种异常进行了捕获,然后打印了异常信息,目前这种处理逻辑,无论Minio是否发生异常,前端在上传文件时,总是会受到成功的响应信息。可按照以下步骤进行操作,查看具体现象关闭虚拟机中的Minio服务
systemctl stop minio
启动项目,并上传文件,观察接收的响应信息
-
问题解决思路
为保证前端能够接收到正常的错误提示信息,应该将Service方法的异常抛出到Controller方法中,然后在Controller方法中对异常进行捕获并处理。具体操作如下
Service层代码
@Override public String upload(MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException{ boolean bucketExists = minioClient.bucketExists( BucketExistsArgs.builder() .bucket(properties.getBucketName()) .build()); if (!bucketExists) { minioClient.makeBucket( MakeBucketArgs.builder() .bucket(properties.getBucketName()) .build()); minioClient.setBucketPolicy( SetBucketPolicyArgs.builder() .bucket(properties.getBucketName()) .config(createBucketPolicyConfig(properties.getBucketName())) .build()); } String filename = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/" + UUID.randomUUID() + "-" + file.getOriginalFilename(); minioClient.putObject( PutObjectArgs.builder() .bucket(properties.getBucketName()) .stream(file.getInputStream(), file.getSize(), -1) .object(filename) .contentType(file.getContentType()) .build()); return String.join("/",properties.getEndpoint(),properties.getBucketName(),filename); }
Controller层代码
public Result<String> upload(@RequestParam MultipartFile file) { try { String url = service.upload(file); return Result.ok(url); } catch (Exception e) { e.printStackTrace(); return Result.fail(); } }
-
全局异常处理
按照上述写法,所有的Controller层方法均需要增加
try-catch
逻辑,使用Spring MVC提供的全局异常处理功能,可以将所有处理异常的逻辑集中起来,进而统一处理所有异常,使代码更容易维护。具体用法如下,详细信息可参考官方文档:
在common模块中创建
com.atguigu.lease.common.exception.GlobalExceptionHandler
类,内容如下@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public Result error(Exception e){ e.printStackTrace(); return Result.fail(); } }
上述代码中的关键注解的作用如下
@ControllerAdvice
用于声明处理全局Controller方法异常的类@ExceptionHandler
用于声明处理异常的方法,value
属性用于声明该方法处理的异常类型@ResponseBody
表示将方法的返回值作为HTTP的响应体注意:
全局异常处理功能由SpringMVC提供,因此需要在common模块的
pom.xml
中引入如下依赖<!--spring-web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
修改Controller层代码
由于前文的
GlobalExceptionHandler
会处理所有Controller方法抛出的异常,因此Controller层就无序关注异常的处理逻辑了,因此Controller层代码可做出如下调整。public Result<String> upload(@RequestParam MultipartFile file) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { String url = service.upload(file); return Result.ok(url); }
-
-
-