0. 引言
在实际开发中,我们经常会面临需要存储文档、存储图片等文件存储需求,并且在分布式架构下,文件又需要实现各节点共享,类似于共享文件夹类的需求,在分布式服务器中创建共享文件夹成本较大,甚至当需要跨机房访问时就不满足了,此时我们需要一个第三方的组件,来实现这类对象存储
也就出现了OSS、OBS、minIO、hdfs这类对象存储组件,今天,我们主要来学习免费开源的MinIO组件
1. minio简介
MinIO是一个使用go语言开发的,开源的对象存储组件,能够提供高性能、高可用的数据存储能力,支持分布式部署,提供数据加密、访问控制、版本控制、生命周期管理和事件通知等功能。它还支持高级特性,如分片上传和分片下载,以提高大文件的处理效率。
官方文档:https://www.minio.org.cn/
2. minio安装
minio支持docker安装,压缩包安装,这里我们为了安装方便,采用docker安装,如果使用的是mac,也可以使用brew工具安装,具体可参考官网文档:https://github.com/minio/minio
1、下载镜像
docker pull minio/minio
2、创建数据映射目录
mkdir -p /Library/software/dockerdata/minio/data
3、创建容器
注意这里我安装的版本是RELEASE.2023-01-02T09-40-09Z
docker run -p 9000:9000 -p 9090:9090 \
--name minio \
-e "MINIO_ACCESS_KEY=minioadmin" \
-e "MINIO_SECRET_KEY=minioadmin" \
-v /Library/software/dockerdata/minio/data:/data \
minio/minio server \
/data --console-address ":9090" -address ":9000"
4、登陆 localhost:9090
,输入账号/密码: minioadmin / minioadmin
3. minio管理端介绍
管理端默认通过9090端口进入:http://ip:9090/
其中比较常用的菜单包括:
- Object Browser: 对象管理页面,minio中所有的桶和桶中的文件都可以在这个页面查看,这里的桶 bucket,大家可以简单的理解为文件夹
- Buckets: 桶管理页面,用于管理桶相关的配置,比如桶的访问权限、桶的生命周期(桶中文件保留几天)
- Identity: 权限管理页面,可以创建用户、分组,并设置对应权限等
- Monitoring: 监控页面,监控显示minio的各类健康、状态、日志信息
4. minio客户端使用
1、添加pom依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
2、增加配置文件
minio:
# minio地址
endpoint: http://localhost:9000
# 账户
username: minioadmin
# 密码
password: minioadmin
defaultBucketName: test
3、创建配置类,用于生成MinioClient
/**
* @author benjamin_5
* @Description minio配置类
* @date 2023/8/5
*/
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.username}")
private String username;
@Value("${minio.password}")
private String password;
@Value("${minio.defaultBucketName}")
private String defaultBucketName;
@Bean
public MinioClient minioClient(){
return MinioClient.builder().credentials(username, password).endpoint(endpoint).build();
}
}
4、创建一个返回实体类,方便规范返回信息
@Data
public class MinioReturn {
/**
* 文件地址
*/
private String path;
/**
* 原始文件名
*/
private String inputName;
/**
* 最终文件名
*/
private String outPutName;
}
5、创建MinioTemplate
类,用来书写minio工具类
@Component
public class MinioTemplate {
@Autowired
private MinioClient minioClient;
private static final String SLASH = "/";
@Value("${minio.defaultBucketName}")
private String defaultBucketName;
@Value("${minio.endpoint}")
private String endpoint;
/**
* 创建桶
*
* @param bucketName
* @throws Exception
*/
public void makeBucket(String bucketName) throws Exception {
BucketExistsArgs args = BucketExistsArgs.builder().bucket(bucketName).build();
if (!minioClient.bucketExists(args)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 上传文件
*
* @param file
* @return
* @throws Exception
*/
public MinioReturn putFile(MultipartFile file) throws Exception {
return putFile(file, file.getOriginalFilename(), defaultBucketName);
}
public MinioReturn putFile(MultipartFile file, String fileName, String bucketName) throws Exception {
if (bucketName == null || bucketName.length() == 0) {
bucketName = defaultBucketName;
}
makeBucket(bucketName);
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return new MinioReturn(fileLink(bucketName, fileName), file.getOriginalFilename(), fileName);
}
/**
* 删除文件
*
* @param bucketName
* @param fileName
* @throws Exception
*/
public void removeFile(String bucketName, String fileName) throws Exception {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName == null || bucketName.length() == 0 ? defaultBucketName : bucketName)
.object(fileName)
.build());
}
@SneakyThrows
private String fileLink(String bucketName, String fileName) {
return endpoint.concat(SLASH).concat(bucketName).concat(SLASH).concat(fileName);
}
private String getFileName(String fileName) {
return getFileName(null, fileName);
}
private String getFileName(String prefix, String fileName) {
String fileNamePre = fileName;
String fileType = "";
int index = fileName.lastIndexOf(".");
if (index > 0) {
fileNamePre = fileName.substring(0, index);
fileType = fileName.substring(index);
}
String name = UUID.randomUUID().toString().replace("-", "");
if (!org.springframework.util.StringUtils.isEmpty(fileNamePre)) {
name = fileNamePre + "-" + name + fileType;
}
if (!StringUtils.isEmpty(prefix)) {
name = prefix + "-" + name;
}
return name;
}
}
6、书写控制类,用于测试
@RestController
@RequestMapping("minio")
@AllArgsConstructor
public class MinioController {
private final MinioTemplate minioTemplate;
@PostMapping("/upload")
@ResponseBody
public MinioReturn upload(MultipartFile file) throws Exception {
return minioTemplate.putFile(file);
}
@PostMapping("/remove")
@ResponseBody
public String remove(String fileName, String bucketName) throws Exception{
minioTemplate.removeFile(bucketName, fileName);
return "success";
}
}
7、调用上传接口,如下图所示,调用成功
我们查看minio中,自动创建了桶,并且文件也上传成功了
同样再测试一下删除接口
查看minio中删除成功
5. 应用场景
-
- 静态资源存储
如在官网、门户、首页等网站,我们经常需要加载一些静态资源,如图片、js文件、视频文件等,一些我们可以直接存放到nginx加载,另一方面我们也可以存放到minio中,通过minio进行访问
要通过minio访问的前提是记得把桶权限设置为public
上述代码创建的桶默认是private
的,如果想要通过客户端代码调整桶权限的话,可以通过minioClient.setBucketPolicy
方法,设置完后可以通过返回的地址进行访问,如下所示(如果想要通过外网访问,给对应的内网地址端口做个外网映射即可)
开启权限public会有个安全问题,那就是当你访问桶路径时,会发现会把所有桶下的文件列出来,这样只要再拼接上文件名就能访问所有文件了
要解决这个问题,只需要将权限设置为custom
,然后将"s3:ListBucket"
取消即可
再次访问会发现权限禁止,而加上文件名后是可以正常查看的
当然如果在旧版本中是不支持直接在minio管理页面中直接设置的,你可以通过下载s3browser https://s3browser.com/,连接上minio后,右键在Edit Bucket Policy
中设置
-
- 文件暂存
某些时候,涉及组件或系统间传输文件时,直接通过接口传输效率太慢且及时性不足,或者用户其实收到后不需要马上查看这些文件,这时我们就可以把文件暂存到minio中,发送一个文件地址给客户,客户收到这个地址可以再进行下载。
既然是文件暂存,那么就希望能自动删除,实现自动删除有两种方法,一种是通过书写shell脚本,直接删除指定路径下的文件,minio在服务器上存储的文件就是直接放在文件夹下,没有特殊处理,所以直接删除即可,第二种就是使用minio提供的生命周期管理,在Buckets-Lifecycle中可以设置,同时这个也可以在客户端中创建桶时就设置,通过minioClient.setBucketLifecycle();
方法
-
- 解放带宽压力
某些时候,涉及接口文件传输时,直接传输占用大量带宽,导致并发上不去,而客户可能也不需要马上使用到传输的文件,只是需要马上知道一个状态或者其他信息,这时就可以通过将文件上传到minio, 而发送一个文件地址给客户,这样将文件状态通知和下载分步进行。
这里某的同学可能会想,下载不还是要占用带宽吗,怎么说是节约带宽资源呢,这是因为一般接口调用追求耗时,我们一般会把接口服务的带宽部署为性能更高的,价格自然更贵,而文件下载服务的没有那么高的延迟要求,就单独拉一根性能相对没那么强的,也就更便宜的,这样自然减少了带宽费用
6. 总结
至此,我们针对minio的快速上手就完成了, 文中只是描述了minio最常用的方法,还有更多方法、指令等待大家自己探索学习。
文中演示源码见如下地址:https://gitee.com/wuhanxue/wu_study/tree/master/demo/minio_demo