• Mall脚手架总结(五) —— SpringBoot整合MinIO实现文件管理


    前言

            在项目中我们经常有资源的上传和下载的功能需求,比如用户头像、产品图片、配置文件等等,大数据量的文件存储无疑需要更高性能的数据存储服务,对于无需对结构实现复杂查询的文件对象来说,对象存储服务无疑是一个较好的选择,在下面的文章中荔枝也会梳理脚手架整合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来封装我们的配置信息。

     完整的类继承关系如下:

    简单看一下这个类的源码:

    1. public class CorsFilter extends OncePerRequestFilter {
    2. private final CorsConfigurationSource configSource;
    3. private CorsProcessor processor = new DefaultCorsProcessor();
    4. public CorsFilter(CorsConfigurationSource configSource) {
    5. Assert.notNull(configSource, "CorsConfigurationSource must not be null");
    6. this.configSource = configSource;
    7. }
    8. public void setCorsProcessor(CorsProcessor processor) {
    9. Assert.notNull(processor, "CorsProcessor must not be null");
    10. this.processor = processor;
    11. }
    12. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    13. //获取配置信息
    14. CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
    15. //处理请求
    16. boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
    17. if (isValid && !CorsUtils.isPreFlightRequest(request)) {
    18. //过滤器链过滤
    19. filterChain.doFilter(request, response);
    20. }
    21. }
    22. }
    • 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接口的一个实现类。

    1. @Configuration
    2. public class GlobalCorsConfig {
    3. /**
    4. * 允许跨域调用的过滤器
    5. */
    6. @Bean
    7. public CorsFilter corsFilter() {
    8. //配置信息
    9. CorsConfiguration config = new CorsConfiguration();
    10. //允许所有域名进行跨域调用
    11. config.addAllowedOriginPattern("*");
    12. //允许跨越发送cookie
    13. config.setAllowCredentials(true);
    14. //放行全部原始头信息
    15. config.addAllowedHeader("*");
    16. //允许所有请求方法跨域调用
    17. config.addAllowedMethod("*");
    18. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    19. source.registerCorsConfiguration("/**", config);
    20. return new CorsFilter(source);
    21. }
    22. }

    区分CorsConfiguration 和CorsConfigurationSource 

    • CorsConfiguration类:用于配置CORS规则。你可以在这个对象上设置允许的来源、允许的HTTP方法、允许的请求头等
    • CorsConfigurationSource接口:用于提供CorsConfiguration对象,通过自定义实现类可以根据请求的不同,为不同的资源提供不同的CORS配置。

    1.3 @RequestPart注解

    前端中我们上传数据到服务器中通常在form表单中用一个enctype标记为multipart/form-dataPOST请求来实现,在input表单中声明type为file即可实现文件上传。

    1. <form action="http://127.0.0.1" method="post" enctype="multipart/form-data">
    2. <input type="file" name="file1" value="请选择文件"/><br/>
    3. <input type="submit"/>
    4. form>

    而在Spring中我们可以通过@RequestPart注解来获取表单上传的数据。该注解是Spring 框架中用于处理multipart/form-data请求中的文件上传的注解。

    1. @ApiOperation("文件上传")
    2. @RequestMapping(value = "/upload", method = RequestMethod.POST)
    3. @ResponseBody
    4. public CommonResult upload(@RequestPart("file") MultipartFile file)

    MultipartFile和File

    这一部分大家可以看看荔枝梳理的文章嘿~

    彻底弄懂Java中的MultipartFile接口和File类_荔枝当大佬的博客-CSDN博客

    1.4 MinioClient

    该类是Java操作MinIO客户端的工具库,用于与MinIO对象存储服务进行交互。

    创建MinIO客户端的操作

    1. //创建一个MinIO的Java客户端
    2. MinioClient minioClient =MinioClient.builder()
    3. .endpoint(访问地址)
    4. .credentials(登陆令牌,登录密钥)
    5. .build();

    文件上传操作代码

    1. @Controller
    2. @Api(tags = "MinioController")
    3. @Tag(name = "MinioController", description = "MinIO对象存储管理")
    4. @RequestMapping("/minio")
    5. public class MinioController {
    6. private static final Logger LOGGER = LoggerFactory.getLogger(MinioController.class);
    7. @Value("${minio.endpoint}")
    8. private String ENDPOINT;
    9. @Value("${minio.bucketName}")
    10. private String BUCKET_NAME;
    11. @Value("${minio.accessKey}")
    12. private String ACCESS_KEY;
    13. @Value("${minio.secretKey}")
    14. private String SECRET_KEY;
    15. @ApiOperation("文件上传")
    16. @RequestMapping(value = "/upload", method = RequestMethod.POST)
    17. @ResponseBody
    18. public CommonResult upload(@RequestPart("file") MultipartFile file) {
    19. try {
    20. //创建一个MinIO的Java客户端
    21. MinioClient minioClient =MinioClient.builder()
    22. .endpoint(ENDPOINT)
    23. .credentials(ACCESS_KEY,SECRET_KEY)
    24. .build();
    25. //检查桶是否存在
    26. boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build());
    27. if (isExist) {
    28. LOGGER.info("存储桶已经存在!");
    29. } else {
    30. //创建存储桶并设置只读权限
    31. minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build());
    32. BucketPolicyConfigDto bucketPolicyConfigDto = createBucketPolicyConfigDto(BUCKET_NAME);
    33. SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
    34. .bucket(BUCKET_NAME)
    35. .config(JSONUtil.toJsonStr(bucketPolicyConfigDto))
    36. .build();
    37. //将策略配置应用到指定的 MinIO 存储桶中
    38. minioClient.setBucketPolicy(setBucketPolicyArgs);
    39. }
    40. String filename = file.getOriginalFilename();
    41. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    42. // 设置存储对象名称
    43. String objectName = sdf.format(new Date()) + "/" + filename;
    44. // 使用putObject上传一个文件到存储桶中
    45. PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    46. .bucket(BUCKET_NAME)
    47. .object(objectName)
    48. .contentType(file.getContentType())
    49. .stream(file.getInputStream(), file.getSize(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
    50. minioClient.putObject(putObjectArgs);
    51. LOGGER.info("文件上传成功!");
    52. MinioUploadDto minioUploadDto = new MinioUploadDto();
    53. minioUploadDto.setName(filename);
    54. minioUploadDto.setUrl(ENDPOINT + "/" + BUCKET_NAME + "/" + objectName);
    55. return CommonResult.success(minioUploadDto);
    56. } catch (Exception e) {
    57. e.printStackTrace();
    58. LOGGER.info("上传发生错误: {}!", e.getMessage());
    59. }
    60. return CommonResult.failed();
    61. }
    62. }

    这里的 BucketPolicyConfigDto类是自定义的访问策略类。

    1. @Data
    2. @EqualsAndHashCode
    3. @Builder
    4. public class BucketPolicyConfigDto {
    5. private String Version;
    6. private List Statement;
    7. @Data
    8. @EqualsAndHashCode
    9. @Builder
    10. public static class Statement {
    11. private String Effect;
    12. private String Principal;
    13. private String Action;
    14. private String Resource;
    15. }
    16. }

    删除文件操作

    1. MinioClient minioClient = MinioClient.builder()
    2. .endpoint(ENDPOINT)
    3. .credentials(ACCESS_KEY,SECRET_KEY)
    4. .build();
    5. 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: 指定被授权的实体,可以是 "*"(表示所有用户)或特定的用户或角色。

    一个简单的访问策略 

    1. {
    2. "Version": "2012-10-17",
    3. "Statement": [
    4. {
    5. "Effect": "Allow",
    6. "Principal": {
    7. "AWS": [
    8. "*"
    9. ]
    10. },
    11. "Action": [
    12. "s3:GetObject"
    13. ],
    14. "Resource": [
    15. "arn:aws:s3:::mall/*.**"
    16. ]
    17. }
    18. ]
    19. }

    总结

            终章!荔枝把脚手架中的知识内容梳理了一遍,也确实学到了之前没有了解过的类和一些工具接口,对项目中用到的中间件的使用体会也更加深入了。有些类的源码确实太多了哈哈哈,根据自己的需求荔枝确实在有选择地阅读和理解,同时也感谢宏哥的帮助和许多博主的博文浇灌哈哈哈哈,继续加油!

    今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~

    如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!

    如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!

  • 相关阅读:
    docker compose 使用
    【AGC】典型问题FAQ 6
    微信小程序新版隐私协议弹窗实现最新版
    三万字盘点Spring/Boot的那些常用扩展点
    Vue-cli全局配置整理
    CSS语法
    【Python基础入门4】关于变量
    prometheus day06
    C/C++逻辑与运算与汇编指令的关系
    Python进阶之迭代器
  • 原文地址:https://blog.csdn.net/qq_62706049/article/details/133762911