前言
在项目中我们经常有资源的上传和下载的功能需求,比如用户头像、产品图片、配置文件等等,大数据量的文件存储无疑需要更高性能的数据存储服务,对于无需对结构实现复杂查询的文件对象来说,对象存储服务无疑是一个较好的选择,在下面的文章中荔枝也会梳理脚手架整合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的访问策略
总结
一、MinIO实现文件管理
1.1 为什么使用对象存储服务
在项目中,往往有些数据比如头像、资源文件等,这些数据的数据量往往比较大但又不需要执行频繁的数据操作,也没有复杂的结构关系,这些数据的存储我们往往尝试选择更好的存储方式。就拿图片来说吧,存在MySQL这种关系型数据库明显不是很合适,因为它是块存储的,要先将文件编码成二进制文本才能存进去,同时关系型数据库中会增加大量的数据比如索引、字段、日志等,查询效率明显差强人意。这时候我们需要一个能够直接存储整个文件对象的工具,这时我们就要用OSS(对象存储服务)。
1.1.1 OSS与传统的文件系统的区别
相比于传统的文件管理系统,对象存储服务提供了一种更简单、更灵活的数据源存储方式,无需我们预先定义文件系统的目录结构,而是将文件作为一个对象来存储,文件数据的管理和检索更加方便。在对象存储服务中,每个对象都有一个唯一的标识符(通常是一个特定的URL)
1.1.2 OSS与非关系型数据库的用法区分
小伙伴们可能跟荔枝一样,想起图像、文件这些数据是不是也可以存在非关系型数据库里面比如MongoDB。是的的确可以,但我们要弄清楚要存储文件的操作情况,根据OSS和非关系型数据库的应用场景来选择适合需求的存储工具。
- 对象存储服务: 适用于大规模的、静态的文件存储需求,例如图片、音频、视频文件等。对象存储通常具有较高的可扩展性和稳定性。
- 非关系型数据库: 适用于需要对存储的文件进行复杂查询、分析和变换的场景,例如社交网络应用、日志分析等。
总结一下,之所以选择MinIO是因为其在无需过多操作的文件数据对象的存储上表现更优异,但是注意的是MinIO不是数据库,而是一种数据对象存储服务!
云存储和云数据库区别:MinIO与MySQL对比以及存储的相关知识_minio数据库_Fishermen_sail的博客-CSDN博客
1.2 CorsFilter
在正式梳理CorsFilter之前我们需要了解什么是CROS
1.2.1 跨源资源共享
CORS(Cross-origin resource sharing)跨源资源共享,是一种用于在浏览器和服务器之间进行安全跨域数据传输的机制。在 Web 开发中,CORS 允许网页从不同的域(协议、域名、端口)请求受限制资源,而不受同源策略的限制。正常情况下,浏览器是会阻止跨域请求来加载自身的资源,而CORS 通过在服务器端设置响应头的方式,允许服务器声明哪些源可以访问其资源,从而绕过了同源策略的限制。
浏览器将CORS请求分为两类:简单请求和非简单请求。
- 简单请求:浏览器会自动在请求头中增加一个Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)之后服务器再根据这个值,决定是否同意这次请求;
- 非简单请求:浏览器会自动发送一个请求方法为Option的预检请求,里面也包含着Origin字段、Access-Control-Request-Method(列出浏览器的CORS请求会用到哪些HTTP方法)以及Access-Control-Request-Headers(指定浏览器CORS请求会额外发送的头信息字段)。
更为详细的内容参见博文:
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://blog.csdn.net/zzuhkp/article/details/120631687
1.2.2 CorsFilter剖析
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);}}
}
- CorsConfigurationSource对象:代表了当前请求的 CORS 配置。
- CorsProcessor类:负责处理请求,并根据 CORS 配置信息判断是否允许该请求。
- doFilterInternal 方法:该方法CorsFilter的父类 OncePerRequestFilter 类中的一个抽象方法,需要在子类中实现。在这个方法中,首先从configSource获取当前请求的 CORS 配置信息。然后,通过CorsProcessor对象(默认使用DefaultCorsProcessor类)处理当前请求。
- processRequest 方法:processRequest 方法是 CorsProcessor 接口中的一个方法,用于处理请求。具体的 CORS 处理逻辑在该方法中实现。
- filterChain.doFilter 方法:如果请求是有效的且不是预检请求(Preflight Request),则调用
filterChain.doFilter(request, response)
继续处理请求链。
在SpringBoot项目中我们仅需要通过一个配置类向其传入配置信息即可,如下面的示例代码,UrlBasedCorsConfigurationSource是CorsConfigurationSource接口的一个实现类。
@Configuration
public class GlobalCorsConfig {/*** 允许跨域调用的过滤器*/@Beanpublic CorsFilter corsFilter() {//配置信息CorsConfiguration config = new CorsConfiguration();//允许所有域名进行跨域调用config.addAllowedOriginPattern("*");//允许跨越发送cookieconfig.setAllowCredentials(true);//放行全部原始头信息config.addAllowedHeader("*");//允许所有请求方法跨域调用config.addAllowedMethod("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return new CorsFilter(source);}
}
区分CorsConfiguration 和CorsConfigurationSource
- CorsConfiguration类:用于配置CORS规则。你可以在这个对象上设置允许的来源、允许的HTTP方法、允许的请求头等
- CorsConfigurationSource接口:用于提供CorsConfiguration对象,通过自定义实现类可以根据请求的不同,为不同的资源提供不同的CORS配置。
1.3 @RequestPart注解
前端中我们上传数据到服务器中通常在form表单中用一个enctype标记为multipart/form-data的POST请求来实现,在input表单中声明type为file即可实现文件上传。
<form action="http://127.0.0.1" method="post" enctype="multipart/form-data"><input type="file" name="file1" value="请选择文件"/><br/><input type="submit"/>
</form>
而在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博客
1.4 MinioClient
该类是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)@ResponseBodypublic 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> Statement;@Data@EqualsAndHashCode@Builderpublic 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());
1.5 Minio的访问策略
MinIO中的访问策略主要有三种:public、private和consume(自定义)
- Version: 策略的版本,目前的版本为 "2012-10-17"。
- Statement: 一个数组,包含了访问规则的定义。
每个 Statement
包含以下属性:
- Effect: 指定策略的效果,可以是 "Allow"(允许)或 "Deny"(拒绝)。
- Action: 指定允许或拒绝的操作,可以是一个字符串或一个字符串数组,例如 "s3:GetObject" 或 ["s3:GetObject", "s3:PutObject"]。
- Resource: 指定操作作用的资源,可以是一个字符串或一个字符串数组,例如 "arn:aws:s3:::my-bucket/*"。
- Principal: 指定被授权的实体,可以是 "*"(表示所有用户)或特定的用户或角色。
一个简单的访问策略
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"AWS": ["*"]},"Action": ["s3:GetObject"],"Resource": ["arn:aws:s3:::mall/*.**"]}]
}
总结
终章!荔枝把脚手架中的知识内容梳理了一遍,也确实学到了之前没有了解过的类和一些工具接口,对项目中用到的中间件的使用体会也更加深入了。有些类的源码确实太多了哈哈哈,根据自己的需求荔枝确实在有选择地阅读和理解,同时也感谢宏哥的帮助和许多博主的博文浇灌哈哈哈哈,继续加油!
今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~
如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!
如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!