一、Minio介绍:
目前可用于文件存储的网络服务选择也有不少,比如阿里云OSS、七牛云、腾讯云等等,可是收费都有点小贵。为了省钱,很多公司使用MinIO做为文件服务器。
官网:MinIO | 用于AI的S3 & Kubernetes原生对象存储
MinIO是一个开源的分布式对象存储服务器,支持S3协议并且可以在多节点上实现数据的高可用和容错。它采用Go语言开发,拥有轻量级、高性能、易部署等特点,并且可以自由选择底层存储介质。它基于Apache License 开源协议,兼容Amazon S3云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。
二、Minio的下载:
有Windows和Linux两种方式,不过我们一般把服务器下载到Linux中。使用docker来部署,能够很方便的管理(默认你的Linux中已经下载好docker了)。
//拉取镜像
docker pull quay.io/minio/minio
// 创建数据存储目录
mkdir -p ~/minio/data
// 创建minio
docker run \
-p 9001:9000 \
-p 9090:9090 \
--name minio \
-v ~/minio/data:/data \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123456" \
-d \
quay.io/minio/minio server /data --console-address ":9090"
在这里解释一下这个命令:
docker pull 拉取Minio的镜像,我没有指定版本,所以默认下载最新版本。
mkdir -p ~/minio/data:创建一个文件夹来存储我们上传到Minio中的文件。
实际上,我们上传到Minio中的文件都存储到了这个文件夹中。也就是说我们上传到Minio中的文件其实都保存在你的Linux服务器中。
docker run :创建容器并运行。解释一下重要的几个参数。
9001:Minio的服务端端口
9090:Minio的客户端端口
-v ~/minio/data:/data :数据卷挂载我们之前创建的文件夹,使之成为Minio的存储容器
-e "MINIO_ROOT_USER=admin" :用户名为admin
-e "MINIO_ROOT_PASSWORD=admin123456" :密码为admin123456
将容器运行起来:
现在登录我们的客户端,并输入用户名和密码:
http://Ip地址:9090
我们还要了解一下“桶”在Minio中的概念:
"桶"(Bucket)是用来组织和管理存储的对象(文件或文件夹)的基本单位。你可以把它想象成一个容器,用来存放你的对象。
以下是关于桶的一些重要概念:
-
命名:每个桶都有一个全局唯一的名称。这意味着在同一个MinIO服务中,不能有两个名称相同的桶。
-
隔离:桶之间是完全隔离的。一个桶中的对象不能直接访问另一个桶中的对象。
-
访问控制:每个桶都可以有自己的访问控制策略。例如,你可以设置一个桶为公开,任何人都可以读取它的内容;也可以设置一个桶为私有,只有特定的用户可以访问它的内容。
-
无限制的对象存储:每个桶内可以存储无限数量的对象,只要你的存储空间足够。
-
元数据:每个桶都可以有一些元数据,如创建时间、修改时间等。
可以选择在客户端创建桶,也可以使用Java代码来创建,我这里就使用Java代码创建桶了。
三、使用spring boot整合Minio,完成文件的上传、下载和删除。
版本:spring boot3、jdk17、Minio的版本为:RELEASE.2023-11-01T18-37-25Z
本次使用knife4j进行测试;
创建一个空的spring boot项目,并引入如下依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency></dependencies>
在yml配置文件中引入如下配置:
minio: url: http://192.168.231.110:9001 username: admin password: admin123456 bucketName: test spring: servlet: multipart: max-file-size: 10MB # 单个文件上传的最大上限 max-request-size: 100MB # 整个请求体的最大上限
minio:我们自定义的属性
url:你的服务端地址
username:用户名
password:密码
bucketName:test(桶的名称,我们现在没有创建,一会用Java代码实现)
max-file-size:一定要配置这个属性,不然springboot默认的单个文件上传的最大上限为1MB,超过这个上限,就会报错。
1、创建一个实体类来继承yml文件中minio的连接信息;
@ConfigurationProperties(prefix = "minio") @Component @Data public class MinioPojo { private String url; private String username; private String password; private String bucketName; }
2、自定义bean,将MinioClient初始化;
@Configuration public class MinioConfig { @Autowired private MinioPojo minioPojo; @Bean public MinioClient minioClient(){ return MinioClient.builder() .endpoint(minioPojo.getUrl()) //传入url地址 //传入用户名和密码 .credentials(minioPojo.getUsername(), minioPojo.getPassword()) .build(); //完成MinioClient的初始化 } }
3、新创建一个MinioService类,用来进行文件的操作,并注册为bean。将来在这里进行具体代码的编写。
@Component public class MinioService { @Autowired private MinioClient minioClient; @Autowired private MinioPojo minioPojo;}
4、新建一个Controller,用来进行文件的上传。
@RestController @RequestMapping("/tests") public class TestController { @Autowired private MinioService minioService; @Autowired private MinioPojo minioPojo; //文件上传 @Operation(summary = "上传图片") @PostMapping("/uploadImage") public String aa(MultipartFile file){ String url = minioService.uploadImage(minioPojo.getBucketName(), file); return url; }}
我们在MinioService中定义了uploadImage方法,传入了两个参数。一个是桶的名称,一个是文件file。(注意参数名称一定要叫file,因为现在前端默认传文件时都会叫这个名字,如果你随意的更改名字,后端会接收不到参数。)
返回文件在Minio中的地址url。(注意,只要你的容器Minio还在运行,就可以直接访问到。)
5、在MinioService中进行方法的实现。
@Autowired
private MinioClient minioClient;
@Autowired
private MinioProperties minioProperties;
/*
* 上传文件
* */
public String uploadFile(MultipartFile file) {
try {
//判断桶是否存在
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(minioProperties.getBucketName()).build());
if (!bucketExists){
// 如果不存在,就创建桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucketName()).build());
}
// 本地时间,具体到年、月、日
String yyyymmdd = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// String uuid= UUID.randomUUID().toString();
String filename = yyyymmdd+"/"+file.getOriginalFilename();
// 加一个/表示创建一个文件夹
minioClient.putObject(PutObjectArgs.builder().
bucket(minioProperties.getBucketName()).
object(filename).
stream(file.getInputStream(), file.getSize(), -1).
// 文件上传的类型,如果不指定,那么每次访问时都要先下载文件
contentType(file.getContentType()).
build());
String url= minioProperties.getUrl()+"/"+ minioProperties.getBucketName()+"/"+filename;
return url;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("文件上传失败");
}
}
现在,我们可以在knife4j中去测试发送信息了。
如图所示,我们发送文件成功了。返回了一个url地址,我们输入这个地址就可以直接访问到这张图片了
(注意,这里有坑。我们虽然能够上传文件,并返回了文件在Minio中的地址,但是我们现在并不能直接去访问,因为现在这个桶test默认的访问权限不够,我们要在Minio的客户端将这个桶的访问权限设为public。),创建存储桶时无法直接设置为公开访问,你需要在创建存储桶后再设置存储桶的策略来实现公开访问。这是因为MinIO遵循最小权限原则,以确保数据安全。
在进行文件的上传时,如果文件名称相同会进行覆盖。这时一般的方法为在每一次文件上传时,对文件生成一个UUID,以保证每一次上传的文件名称都不一样,以免覆盖。
也可以在Java代码中实现桶访问权限的修改,但是很麻烦。
// 设置存储桶的策略
String policyJson = "{\"Statement\"[{\"Action[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"],\"Sid\":\"\"}],\"Version\":\"2012-10-17\"}"; minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
现在,我们就可以直接输入返回的url地址,来查看图片并下载。
我这次上传的文件类型是图片,其他的文件类型也可以上传。
6、实现文件的下载:
/*
* 根据url 下载文件
* */
public String getObject(HttpServletRequest request, HttpServletResponse response,String url){
// 将传递的url 进行切割,获取到相对于桶的文件名
String objectUrl = url.split(minioProperties.getBucketName()+"/")[1];
// 文件名
String fileName = objectUrl.split("/")[1];
// 设置响应的内容类型为文件类型
response.setContentType("application/octet-stream");
// 设置响应头,指定文件名
response.setHeader("Content-Disposition", "attachment; filename=\"filename.extension\"");
// 缓冲区大小
byte[] buffer = new byte[1024];
int bytesRead;
InputStream inputStream = null;
OutputStream outputStream = null;
try{
inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(objectUrl)
.build());
/* // 将输入流的内容复制到一个文件中
String filePath = "D://"+fileName;
Files.copy(inputStream, Path.of(filePath),
StandardCopyOption.REPLACE_EXISTING);
return "文件下载成功,地址为"+filePath;*/
// 获取输出流
outputStream = response.getOutputStream();
// 将输入流中的数据写入输出流
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outputStream.close();
}catch (Exception e){
e.printStackTrace();
}
return "文件下载失败";
}
这里传入的url是在Minion,成功上传文件之后返回的url地址
假如现在在Minio中有一个url地址:http://192.168.231.110:9001/test/2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png
7、实现文件的删除:
/*
* 根据url删除文件
* */
public void deleteFile(String url) {
// 将传递的url 进行切割,获取到相对于桶的文件名
String objectUrl = url.split(minioProperties.getBucketName()+"/")[1];
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(minioProperties.getBucketName())
.object(objectUrl)
.build());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(url+"文件删除失败");
}
}
文件的删除,传入上传成功的url:http://192.168.231.110:9001/test/2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png
运行这个方法是在minio中将图片该删除掉了。
总结:
我们使用minio的常用操作就是,上传、下载和删除。
这些文件都存储在我们的Linux服务器中,并不能凭空存在,所以上传时要注意把握力度。如果上传的文件太多太大的话,Linux会先撑不住的。