在项目中我们经常有资源的上传和下载的功能需求,比如用户头像、产品图片、配置文件等等,大数据量的文件存储无疑需要更高性能的数据存储服务,对于无需对结构实现复杂查询的文件对象来说,对象存储服务无疑是一个较好的选择,在下面的文章中荔枝也会梳理脚手架整合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即可实现文件上传。
- <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博客
该类是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/*.**"
- ]
- }
- ]
- }
终章!荔枝把脚手架中的知识内容梳理了一遍,也确实学到了之前没有了解过的类和一些工具接口,对项目中用到的中间件的使用体会也更加深入了。有些类的源码确实太多了哈哈哈,根据自己的需求荔枝确实在有选择地阅读和理解,同时也感谢宏哥的帮助和许多博主的博文浇灌哈哈哈哈,继续加油!
今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~
如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!
如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!