在项目中我们经常有资源的上传和下载的功能需求,比如用户头像、产品图片、配置文件等等,大数据量的文件存储无疑需要更高性能的数据存储服务,对于无需对结构实现复杂查询的文件对象来说,对象存储服务无疑是一个较好的选择,在下面的文章中荔枝也会梳理脚手架整合MinIO实现文件管理。希望能帮助到有需要的小伙伴~~~
前言
一、MinIO实现文件管理
1.1 为什么使用对象存储服务
1.1.1 OSS与传统的文件系统的区别
1.1.2 OSS与非关系型数据库的用法区分
1.2 CorsFilter
1.2.1 跨源资源共享
1.2.2 CorsFilter剖析
1.3 @RequestPart注解
MultipartFile和File
1.4 MinioClient
1.5 Minio的访问策略
总结
在项目中,往往有些数据比如头像、资源文件等,这些数据的数据量往往比较大但又不需要执行频繁的数据操作,也没有复杂的结构关系,这些数据的存储我们往往尝试选择更好的存储方式。就拿图片来说吧,存在MySQL这种关系型数据库明显不是很合适,因为它是块存储的,要先将文件编码成二进制文本才能存进去,同时关系型数据库中会增加大量的数据比如索引、字段、日志等,查询效率明显差强人意。这时候我们需要一个能够直接存储整个文件对象的工具,这时我们就要用OSS(对象存储服务)。
相比于传统的文件管理系统,对象存储服务提供了一种更简单、更灵活的数据源存储方式,无需我们预先定义文件系统的目录结构,而是将文件作为一个对象来存储,文件数据的管理和检索更加方便。在对象存储服务中,每个对象都有一个唯一的标识符(通常是一个特定的URL)
小伙伴们可能跟荔枝一样,想起图像、文件这些数据是不是也可以存在非关系型数据库里面比如MongoDB。是的的确可以,但我们要弄清楚要存储文件的操作情况,根据OSS和非关系型数据库的应用场景来选择适合需求的存储工具。
总结一下,之所以选择MinIO是因为其在无需过多操作的文件数据对象的存储上表现更优异,但是注意的是MinIO不是数据库,而是一种数据对象存储服务!
云存储和云数据库区别:MinIO与MySQL对比以及存储的相关知识_minio数据库_Fishermen_sail的博客-CSDN博客
在正式梳理CorsFilter之前我们需要了解什么是CROS
CORS(Cross-origin resource sharing)跨源资源共享,是一种用于在浏览器和服务器之间进行安全跨域数据传输的机制。在 Web 开发中,CORS 允许网页从不同的域(协议、域名、端口)请求受限制资源,而不受同源策略的限制。正常情况下,浏览器是会阻止跨域请求来加载自身的资源,而CORS 通过在服务器端设置响应头的方式,允许服务器声明哪些源可以访问其资源,从而绕过了同源策略的限制。
浏览器将CORS请求分为两类:简单请求和非简单请求。
更为详细的内容参见博文:
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://blog.csdn.net/zzuhkp/article/details/120631687
CorsFilter是一个用于处理跨域资源共享(CORS)的过滤器。在SpringBoot中整合MinIO实现文件管理的时候我们需要自定义一个配置类来允许跨域访问,需要通过Spring中web框架中的CorsFilter来封装我们的配置信息。
完整的类继承关系如下:
简单看一下这个类的源码:
public class CorsFilter extends OncePerRequestFilter {
private final CorsConfigurationSource configSource;
private CorsProcessor processor = new DefaultCorsProcessor();
public CorsFilter(CorsConfigurationSource configSource) {
Assert.notNull(configSource, "CorsConfigurationSource must not be null");
this.configSource = configSource;
}
public void setCorsProcessor(CorsProcessor processor) {
Assert.notNull(processor, "CorsProcessor must not be null");
this.processor = processor;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取配置信息
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
//处理请求
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (isValid && !CorsUtils.isPreFlightRequest(request)) {
//过滤器链过滤
filterChain.doFilter(request, response);
}
}
}
filterChain.doFilter(request, response)
继续处理请求链。在SpringBoot项目中我们仅需要通过一个配置类向其传入配置信息即可,如下面的示例代码,UrlBasedCorsConfigurationSource是CorsConfigurationSource接口的一个实现类。
@Configuration
public class GlobalCorsConfig {
/**
* 允许跨域调用的过滤器
*/
@Bean
public CorsFilter corsFilter() {
//配置信息
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
区分CorsConfiguration 和CorsConfigurationSource
前端中我们上传数据到服务器中通常在form表单中用一个enctype标记为multipart/form-data的POST请求来实现,在input表单中声明type为file即可实现文件上传。
而在Spring中我们可以通过@RequestPart注解来获取表单上传的数据。该注解是Spring 框架中用于处理multipart/form-data
请求中的文件上传的注解。
@ApiOperation("文件上传")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public CommonResult upload(@RequestPart("file") MultipartFile file)
MultipartFile和File
这一部分大家可以看看荔枝梳理的文章嘿~
彻底弄懂Java中的MultipartFile接口和File类_荔枝当大佬的博客-CSDN博客
该类是Java操作MinIO客户端的工具库,用于与MinIO对象存储服务进行交互。
创建MinIO客户端的操作
//创建一个MinIO的Java客户端
MinioClient minioClient =MinioClient.builder()
.endpoint(访问地址)
.credentials(登陆令牌,登录密钥)
.build();
文件上传操作代码
@Controller
@Api(tags = "MinioController")
@Tag(name = "MinioController", description = "MinIO对象存储管理")
@RequestMapping("/minio")
public class MinioController {
private static final Logger LOGGER = LoggerFactory.getLogger(MinioController.class);
@Value("${minio.endpoint}")
private String ENDPOINT;
@Value("${minio.bucketName}")
private String BUCKET_NAME;
@Value("${minio.accessKey}")
private String ACCESS_KEY;
@Value("${minio.secretKey}")
private String SECRET_KEY;
@ApiOperation("文件上传")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public CommonResult upload(@RequestPart("file") MultipartFile file) {
try {
//创建一个MinIO的Java客户端
MinioClient minioClient =MinioClient.builder()
.endpoint(ENDPOINT)
.credentials(ACCESS_KEY,SECRET_KEY)
.build();
//检查桶是否存在
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build());
if (isExist) {
LOGGER.info("存储桶已经存在!");
} else {
//创建存储桶并设置只读权限
minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build());
BucketPolicyConfigDto bucketPolicyConfigDto = createBucketPolicyConfigDto(BUCKET_NAME);
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
.bucket(BUCKET_NAME)
.config(JSONUtil.toJsonStr(bucketPolicyConfigDto))
.build();
//将策略配置应用到指定的 MinIO 存储桶中
minioClient.setBucketPolicy(setBucketPolicyArgs);
}
String filename = file.getOriginalFilename();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
// 设置存储对象名称
String objectName = sdf.format(new Date()) + "/" + filename;
// 使用putObject上传一个文件到存储桶中
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
minioClient.putObject(putObjectArgs);
LOGGER.info("文件上传成功!");
MinioUploadDto minioUploadDto = new MinioUploadDto();
minioUploadDto.setName(filename);
minioUploadDto.setUrl(ENDPOINT + "/" + BUCKET_NAME + "/" + objectName);
return CommonResult.success(minioUploadDto);
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("上传发生错误: {}!", e.getMessage());
}
return CommonResult.failed();
}
}
这里的 BucketPolicyConfigDto类是自定义的访问策略类。
@Data
@EqualsAndHashCode
@Builder
public class BucketPolicyConfigDto {
private String Version;
private List Statement;
@Data
@EqualsAndHashCode
@Builder
public static class Statement {
private String Effect;
private String Principal;
private String Action;
private String Resource;
}
}
删除文件操作
MinioClient minioClient = MinioClient.builder()
.endpoint(ENDPOINT)
.credentials(ACCESS_KEY,SECRET_KEY)
.build();
minioClient.removeObject(RemoveObjectArgs.builder().bucket(BUCKET_NAME).object(objectName).build());
MinIO中的访问策略主要有三种:public、private和consume(自定义)
每个 Statement
包含以下属性:
一个简单的访问策略
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::mall/*.**"
]
}
]
}
终章!荔枝把脚手架中的知识内容梳理了一遍,也确实学到了之前没有了解过的类和一些工具接口,对项目中用到的中间件的使用体会也更加深入了。有些类的源码确实太多了哈哈哈,根据自己的需求荔枝确实在有选择地阅读和理解,同时也感谢宏哥的帮助和许多博主的博文浇灌哈哈哈哈,继续加油!
今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~
如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!
如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!