Minio介绍
Minlo
是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。 对于中小型企业,如果不选择存储上云,那么 MinIO
是个不错的选择,麻雀虽小,五脏俱全。当然 MinIO
除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3
等。 优点:
-
部署简单:一个single二进制文件即是一切,还可支持各种平台。
-
Minio支持海量存储,可按zone扩展(原zone不受任何影响),支持单个对象最大5TB;
-
兼容Amazon S3接口,充分考虑开发人员的需求和体验;
-
读写性能优异
Minio基础概念
概念名称 | 对应含义解释 |
---|---|
Object | 存储的基本对象;比如文件、图片等等 |
Bucket | 用于存储 Object 的逻辑空间;相互之间互相隔离;类似于系统中的顶层文件夹 |
Drive | 即存储数据的磁盘;所有的对象数据都会存储在 Drive 里面 |
Set | 即一组 Drive 的集合;分布式部署根据集群规模自动划分一个或多个 Set |
Linux安装Minio
1.获取minio安装包
wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20241029160148.0.0-1.x86_64.rpm -O minio.rpm
2.安装minio
rpm -ivh minio.rpm
Minio配置
1.修改minio服务配置文件
注释Type、User、Group
修改后的文件如下:
[Unit]
Description=MinIO
Documentation=https://docs.min.io
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio
[Service]
#Type=notify
WorkingDirectory=/usr/local
#User=minio-user
#Group=minio-user
ProtectProc=invisible
EnvironmentFile=-/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=1048576
MemoryAccounting=no
TasksMax=infinity
TimeoutSec=infinity
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
参数解释:
AssertFileIsExecutable
:可以在此参数声明的配置文件中添加minio服务启动时所需一些参数
ExecStart
:该参数用于配置Minio服务的启动命令,其中$MINIO_OPTS
、$MINIO_VOLUMES
,均引用于EnvironmentFile
中的变量。
Restart
:设置服务在失败时自动重启,这里是always
,意味着总是重启。
2.配置
1.编写EnvironmentFile中指定路径下的配置文件
vim /etc/default/minio
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_VOLUMES=/data
MINIO_OPTS="--console-address :9001"
解释:
-
MINIO_ROOT_USER
:登录后台管理平台的用户名 -
MINIO_ROOT_PASSWORD
:登录后台管理平台的密码 -
MINIO_VOLUMES
:指定数据存储路径,需确保指定的路径是存在的 -
MINIO_OPTS
:指定管理页面的地址的端口号
2.创建minio数据存储路径
mkdir /data
3.防火墙设置开放端口
#开放minio的9000和9001端口
firewall-cmd --zone=public --add-port=9000/tcp --add-port=9001/tcp --permanent
#重新加载防火墙规则
firewall-cmd --reload
#查看开放的端口
firewall-cmd --list-ports
4.启动minio服务
#设置minio服务自启动
systemctl enable minio
#启动minio服务
systemctl start minio
#查看minio服务状态
systemctl status minio
Minio后台管理系统使用
账号密码就是etc/default/minio
文件里面配置的MINIO_ROOT_USER
和MINIO_ROOT_PASSWORD
介绍
存储桶管理界面介绍
Access Policy:访问策略
-
public:允许所有人对该存储桶进行读写操作
-
private:只允许所有者操作该存储桶,其他人不能读写该存储桶
-
custom:自定义存储桶的访问权限,如:只允许所有者写,但允许所有人读,自定义访问权限有格式要求,格式为JSON
Encryption:加密策略,可选值:Disabled、SSE-S3、SSE-KMS
Reported Usage:存储桶使用情况,即使用的内存大小
Quota:配置存储桶限制的数据量
Browse Bucket:浏览存储桶列表
存储桶列表界面介绍
Upload File:上传文件
Upload Folder:上传文件夹
Refresh:刷新
Download:下载文件
Share:共享图片(可以设置共享的连接的时效性)
Preview:查看图片
Delete:删除图片
Object Info:对象信息,包含了名称、大小、上次修改时间、内容类型等等信息
文件上传
创建
1.创建存储桶
创建时可选值
Bucket Name:存储桶名称
Versioning:允许在同一键下保留同一对象的多个版本。
Object Locking:确保一旦对象存储在特定的存储桶中,它们就不能被删除,这是为了满足数据保留和法律保留的要求。这种特性或策略只能在创建存储桶的时候设置,一旦存储桶被创建,就不能再对这个特性进行更改。
Quota:限制存储桶中的数据量。
2.上传图片
1.选择Upload File
2.选择要上传的文件
3.上传后可以看到界面的右上角有文件的上传进度
访问上传的图片
访问minio的存储桶的图片的url格式就是
http://主机ip:9000/存储桶/文件名
首先在存储桶浏览列表复制存储桶中文件的路径
然后再URL打上自己的Minio服务的主机IP加上端口号最后拼接上我们复制的文件地址
192.168.181.31:9000/test/房间-卧室-1.jpg
访问报错?
原因分析:因为我们的存储桶的访问权限为私有的所以我们访问不了这个存储桶中的图片\文件,所以我们要修改访问权限
步骤:
1.点击存储桶浏览列表右上角的设置
2.编辑存储桶的访问权限,我们选择自定义权限
再存储桶管理界面介绍中就介绍到了存储桶的访问权限,所以这里我们选择自定义,权限为:只允许所有者写,但允许所有人读,但是
自定义访问权限,需要使用一个规定格式的JSON字符串进行描述,具体格式可参考官方文档。
如:允许(Allow
)所有人(*
)读取(s3:GetObject
)指定桶(test
)的所有内容
{
"Statement" : [ {
"Action" : "s3:GetObject",
"Effect" : "Allow",
"Principal" : "*",
"Resource" : "arn:aws:s3:::test/*"
} ],
"Version" : "2012-10-17"
}
按照上图片进行配置之后,再去访问图片,就可以正常访问了
Minio Java SDK使用
Minio提供了多种语言的SDK供开发者使用,具体内容可参考官方文档。
1. 在Spring Boot项目的pom中引入Minio依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
创建一个类方法测试,然后执行查看是否上传成功
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桶的访问权限
//JDK15之后的特性,三引号
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("公寓-外观.jpg")
.filename("D:\\workspace\\hello-minio\\src\\main\\resources\\公寓-外观.jpg")
//这个文件填写你电脑要上传的图片的路径
.build());
System.out.println("上传成功");
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
}
}
}
如果使用的JDK版本不高与15的话可以用这一段代替
String policy = "{\n" +
" \"Statement\" : [ {\n" +
" \"Action\" : \"s3:GetObject\",\n" +
" \"Effect\" : \"Allow\",\n" +
" \"Principal\" : \"*\",\n" +
" \"Resource\" : \"arn:aws:s3:::hello-minio/*\"\n" +
" } ],\n" +
" \"Version\" : \"2012-10-17\"\n" +
"}";
2. 在application.yml或properties中添加Minio配置参数
#项目名
server:
servlet:
context-path: /minioDemo
minio:
endpoint: http://192.168.181.29:9000 #minio地址
access-key: minioadmin #用户名
secret-key: minioadmin #密码
bucket-name: lease #存储的minio名称(自定义)
3. 创建Minio
配置类和配置参数类
3.1. 创建配置类MinioConfiguration
@Configuration
@EnableConfigurationProperties(MinioProperties.class)//单个配置参数类扫描
//@ConfigurationPropertiesScan("com.waitforme.lease.common.minio")扫描一个路径下的所有配置参数类
//@EnableConfigurationProperties、@ConfigurationPropertiesScan两个注解的作用都是为了注册配置参数类,根据需要使用其一即可
public class MinioConfiguration {
@Autowired
private MinioProperties properties;
@Bean
public MinioClient minioClient() {
return MinioClient
.builder()
.endpoint(properties.getEndpoint())
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
}
}
3.2. 创建一个配置参数类
因为配置类中需要用到之前第二步添加的Minio的配置参数,所以创建它
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
}
注意:如果出现了如图的错误提示,是因为你没有在配置类中注册配置参数类,所以要先进行注册,在MinioConfiguration
类上加上@EnableConfigurationProperties
或@ConfigurationPropertiesScan
注解进行注册
4.创建FileService
接口及其实现,实现图片获取、上传、删除逻辑
service
public interface FileService {
//图片上传
String upload(MultipartFile file) throws Exception;
//获取图片列表
List<String> getListObjects() throws Exception;
//删除单个图片
Boolean deleteObject(String fileName) throws Exception;
}
serviceImpl
@Service("fileService")
public class FileServiceImpl implements FileService {
@Autowired
MinioClient minioClient;
@Autowired
MinioProperties properties;
@Override
public String upload(MultipartFile file) throws Exception {
boolean bucketExists = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(properties.getBucketName()).build());
if (!bucketExists) {
//判断minio中是否存在我们配置的存储桶名称
minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getBucketName()).build());
//创建存储桶
minioClient.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(properties.getBucketName())
.config(createBucketPolicyConfig(properties.getBucketName())).build());
//为创建的存储桶设置访问权限
}
String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();
//上传的文件名
minioClient.putObject(PutObjectArgs.builder()
.bucket(properties.getBucketName())
//上传的存储桶名称
.stream(file.getInputStream(), file.getSize(), -1)
/*上传的文件,三个参数分别表示:
1. 上传的文件io流
2. 上传的文件大小
3. -1表示我们确定了自己要上传的文件大小后,让minio自己自动去分配每次上传的大小,因为minio不是一次性删除而是分片上传*/
.object(fileName)
//上传后的文件名
.contentType(file.getContentType())
//上传文件类型,通过file.getContentType获取,因为不设置的话就是steam类型,访问的时候会下载图片,而不是预览图片
.build());
//上传文件
return String.join("/", properties.getEndpoint(), properties.getBucketName(), fileName);
//join方法用于通过指定一个分割符,将传入的的参数拼接成一串字符,可以传入多个参数
}
/**
* 获取图片列表
* @return 返回处理完的图片数组列表
* @throws Exception 直接抛出最大的异常类
*/
@Override
public List<String> getListObjects() throws Exception {
List<String> objectNames = new ArrayList<>();
//创建一个数组用于存储获取到的图片
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(properties.getBucketName())
.build());
//获取指定存储桶中的图片列表
for (Result<Item> result : results) {
Item item = result.get();
objectNames.add(String.join("/", properties.getEndpoint(), properties.getBucketName(), item.objectName()));
//遍历得到的数组,并通过指定为:http://xxx.xxx.xxx.xx:9000/bucketName/fileName.jpg的格式到数组中
}
return objectNames;
}
/**
* 删除存储桶中的单个图片
* @param fileName 图片名称包括后缀
* @return 直接返回ture
* @throws Exception
*/
@Override
public Boolean deleteObject(String fileName) throws Exception {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(properties.getBucketName())
.object(fileName)
.build());
return true;
}
/**
* @param bucketName 存储桶名称
* @return 返回存储桶的访问权限JSON字符串
* 返回值表示允许所以人读取该存储桶下的所有文件,没有设置写权限是因为默认创建人就是写权限拥有者
* 其中resource中的%s表示占位符,.formatted中的就是占位符的值
*/
private String createBucketPolicyConfig(String bucketName) {
//三引号是jdk15添加的新功能
return """
{
"Statement" : [ {
"Action" : "s3:GetObject",
"Effect" : "Allow",
"Principal" : "*",
"Resource" : "arn:aws:s3:::%s/*"
} ],
"Version" : "2012-10-17"
}
""".formatted(bucketName);
}
}
如果使用的JDK版本低于15可以用这段代替createBucketPolicyConfig方法里面的代码
private String createBucketPolicyConfig(String bucketName) {
String policy = String.format(
"{\n" +
" \"Statement\" : [ {\n" +
" \"Action\" : \"s3:GetObject\",\n" +
" \"Effect\" : \"Allow\",\n" +
" \"Principal\" : \"*\",\n" +
" \"Resource\" : \"arn:aws:s3:::%s/*\"\n" +
" } ],\n" +
" \"Version\" : \"2012-10-17\"\n" +
"}", bucketName);
return policy;
}
5.创建Controller
@Controller
public class MinioController {
@Autowired
MinioService minioService;
@RequestMapping("/upload")
public String upload(@RequestParam MultipartFile file, Model model) {
try {
String url = minioService.upload(file);
model.addAttribute("url", url);
model.addAttribute("msg", "上传成功");
} catch (Exception e) {
model.addAttribute("msg", "上传失败:" + e.getMessage());
}
return "redirect:/list";
}
@RequestMapping({"/list", ""})
public String getListObjects(Model model) throws Exception {
List<String> listObjects = minioService.getListObjects();
model.addAttribute("listObjects", listObjects);
model.addAttribute("msg", listObjects == null ? "获取图片失败或是没有图片" : "");
return "index";
}
@RequestMapping("/remove")
@ResponseBody
public Object removeObject(@RequestParam String fileName) {
Map map = null;
try {
Boolean flag = minioService.deleteObject(fileName);
map = new HashMap();
map.put("flag", flag);
map.put("msg", "删除成功");
} catch (Exception e) {
map.put("msg", "删除失败:" + e.getMessage());
return map;
}
return map;
}
}
6.在templates中创建index.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style type="text/css">
ul{
list-style-type: none;
}
ul>li{
margin: 20px;
}
</style>
<body>
<div align="center">
<h1>minio的增删改查示例</h1>
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">上传图片</button>
</form>
</div>
<div align="center">
<h2 align="center">已上传的图片</h2>
<ul>
<li th:each="object : ${listObjects}">
<img th:src="@{${object}}" alt="11" width="450px" height="230px">
<a href="#" th:onclick="del(this,[[${object}]]);" style="padding-left: 100px">删除</a>
</li>
</ul>
<p id="msg" style="color: red" align="center">[[${msg}]]</p>
</div>
</body>
<script th:src="@{/js/jquery-1.12.4.js}" type="text/javascript"></script>
<script type="text/javascript">
function del(obj, url) {
if (confirm("确认要删除吗?")) {
var parts = url.split("/");
var fileName = parts[parts.length - 1];
$.getJSON("remove", "fileName=" + fileName, function (data) {
if (data.flag) {
$(obj).parents("li").remove();
}
$("#msg").html(data.msg);
})
}
}
</script>
</html>
然后,在static中创建js
文件夹放入 jquery-1.12.4.js
页面效果
遇到的错误
获取图片信息报错?
报错原因:因为物理机的请求时间跟服务器的时间相差太大了,简单说就是要同步一下虚拟机的时间
解决步骤:
1.进入虚拟机,点击右上角序号1,然后点击root,再点击账号设置
2.在弹出的窗口中选择日期和时间,然后把自动设置日期和时间、自动设置时区打开,等待时间与物理机相同即可
注意:此方案需要有网络才能实现
a标签用th:onclick="|del(this,${object})|"
报错500?
原因分析:应该是thymeleaf
的版本问题
解决方案:替换th:onclick="|del(this,${object})|"
为th:onclick="del(this,[[${object}]]);"
<a href="#" th:onclick="del(this,[[${object}]]);" style="padding-left: 100px">删除</a>