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来封装我们的配置信息。

 完整的类继承关系如下:

Mall脚手架总结(五) —— SpringBoot整合MinIO实现文件管理_第1张图片

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

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 {
    /**
     * 允许跨域调用的过滤器
     */
    @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 

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

1.3 @RequestPart注解

前端中我们上传数据到服务器中通常在form表单中用一个enctype标记为multipart/form-dataPOST请求来实现,在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博客

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)
    @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());

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/*.**"
            ]
        }
    ]
}

总结

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

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

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

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

你可能感兴趣的:(项目学习,spring,boot,后端,MinIO,OSS,对象存储服务)