【Spring Cloud】新闻头条微服务项目:分布式文件系统MinIO实现文章页面存取

简介: 主要介绍了MinIO的功能特点以及为什么要用MinIO,并且还介绍了如何封装MinIO为工具快速进行使用

 一:概述

       MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。

       MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

二:为何选用MinIO

       其实我们也可以通过文章id到数据库中查询到该文章然后再通过模板引擎生成静态页面展示给用户,这种方式开发便捷,成本较低,但是将来要扩展业务的话比较困难。除此之外还可以采用第三方存储技术,比如阿里云、七牛云等,这种方案开发简单,功能强大且不用自己维护,缺点就是要收费。最后,还可以选用分布式文件系统来进行存储,其优点是容易实现扩容,但是复杂度较高,常用的分布式文件系统有FastDFS,其有如下优点:

    • 主从备份,高可用
    • 支持主从文件,支持自定义扩展名
    • 支持动态扩容

    其缺点就是没有完备的官方文档,近几年都没有进行过更新,而且环境搭建起来比较麻烦。

    而另外一个就是我们用到的MinIO,其优点如下

      • 性能高,准硬件条件下他能达到55GB/s的读,35GB/s的写速率
      • 部署自带管理页面
      • 它是MinIO.Inc运营的开源项目,社区活跃度高
      • 提供了所有主流开发语言的SDK

      当然其缺点就是不支持动态增加节点。

      三:MinIO特点

        • 数据保护
          Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。
        • 高性能
          作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率
        • 可扩容
          不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
        • SDK支持
          基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持
        • 有操作页面
          面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源
        • 功能简单
          这一设计原则让MinIO不容易出错、更快启动
        • 丰富的API
          支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。
        • 文件变化主动通知
          存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

        四:实现方案

        image.gif编辑

               首先根据文章内容通过模板引擎技术生成静态html文件之后,把生成的html文件访问路径存入到文章数据库中,并同时把文件存入分布式文件系统MinIO,当用户访问某一篇文章时候就会根据文章id查询到数据库中该文章的URL地址,然后再根据该地址从MinIO获取文件并返回。

        五:功能实现

        1.创建容器

        (1)拉取镜像

        docker pull minio/minio server

        image.gif

        (2)启动容器

        docker run -p 9000:9000 -p 9090:9090 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data --console-address ":9090"

        image.gif

        (3)管理控制台

        打开浏览器输入http://你的ip:9090/login即可进行登录,账号为minio,密码为minio123

        image.gif编辑

        (4)创建桶

        image.gif编辑

        点击Create Bucket创建一个名为headlines的桶

        2.封装MinIO为Start

        (1)模块创建

        image.gif编辑

        导入如下依赖:

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
            <dependency>
                <groupId>io.minio</groupId>
                <artifactId>minio</artifactId>
                <version>7.1.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>

        image.gif

        (2)配置类

        MinIOConfigProperties

        package com.my.file.config;
        import lombok.Data;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import java.io.Serializable;
        @Data
        @ConfigurationProperties(prefix = "minio")  // 文件上传 配置前缀file.oss
        public class MinIOConfigProperties implements Serializable {
            private String accessKey;
            private String secretKey;
            private String bucket;
            private String endpoint;
            private String readPath;
        }

        image.gif

        MinIOConfig

        package com.my.file.config;
        import com.my.file.service.FileStorageService;
        import io.minio.MinioClient;
        import lombok.Data;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        @Data
        @Configuration
        @EnableConfigurationProperties({MinIOConfigProperties.class})
        //当引入FileStorageService接口时
        @ConditionalOnClass(FileStorageService.class)
        public class MinIOConfig {
            @Autowired
            private MinIOConfigProperties minIOConfigProperties;
            @Bean
            public MinioClient buildMinioClient() {
                return MinioClient
                        .builder()
                        .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                        .endpoint(minIOConfigProperties.getEndpoint())
                        .build();
            }
        }

        image.gif

        (3)封装操作MinIO类

        FileStorageService

        package com.my.file.service;
        import java.io.InputStream;
        /**
         * @author itheima
         */
        public interface FileStorageService {
            /**
             *  上传图片文件
             * @param prefix  文件前缀
             * @param filename  文件名
             * @param inputStream 文件流
             * @return  文件全路径
             */
            public String uploadImgFile(String prefix, String filename,InputStream inputStream);
            /**
             *  上传html文件
             * @param prefix  文件前缀
             * @param filename   文件名
             * @param inputStream  文件流
             * @return  文件全路径
             */
            public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);
            /**
             * 删除文件
             * @param pathUrl  文件全路径
             */
            public void delete(String pathUrl);
            /**
             * 下载文件
             * @param pathUrl  文件全路径
             * @return
             *
             */
            public byte[]  downLoadFile(String pathUrl);
        }

        image.gif

        MinIOFileStorageService

        package com.my.file.service.impl;
        import com.my.file.config.MinIOConfig;
        import com.my.file.config.MinIOConfigProperties;
        import com.my.file.service.FileStorageService;
        import io.minio.GetObjectArgs;
        import io.minio.MinioClient;
        import io.minio.PutObjectArgs;
        import io.minio.RemoveObjectArgs;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Import;
        import org.springframework.stereotype.Service;
        import org.springframework.util.StringUtils;
        import java.io.ByteArrayOutputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        @Slf4j
        @EnableConfigurationProperties(MinIOConfigProperties.class)
        @Import(MinIOConfig.class)
        @Service
        public class MinIOFileStorageService implements FileStorageService {
            @Autowired
            private MinioClient minioClient;
            @Autowired
            private MinIOConfigProperties minIOConfigProperties;
            private final static String separator = "/";
            /**
             * @param dirPath
             * @param filename  yyyy/mm/dd/file.jpg
             * @return
             */
            public String builderFilePath(String dirPath,String filename) {
                StringBuilder stringBuilder = new StringBuilder(50);
                if(!StringUtils.isEmpty(dirPath)){
                    stringBuilder.append(dirPath).append(separator);
                }
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                String todayStr = sdf.format(new Date());
                stringBuilder.append(todayStr).append(separator);
                stringBuilder.append(filename);
                return stringBuilder.toString();
            }
            /**
             *  上传图片文件
             * @param prefix  文件前缀
             * @param filename  文件名
             * @param inputStream 文件流
             * @return  文件全路径
             */
            @Override
            public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
                String filePath = builderFilePath(prefix, filename);
                try {
                    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .object(filePath)
                            .contentType("image/jpg")
                            .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                            .build();
                    minioClient.putObject(putObjectArgs);
                    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
                    urlPath.append(separator+minIOConfigProperties.getBucket());
                    urlPath.append(separator);
                    urlPath.append(filePath);
                    return urlPath.toString();
                }catch (Exception ex){
                    log.error("minio put file error.",ex);
                    throw new RuntimeException("上传文件失败");
                }
            }
            /**
             *  上传html文件
             * @param prefix  文件前缀
             * @param filename   文件名
             * @param inputStream  文件流
             * @return  文件全路径
             */
            @Override
            public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
                String filePath = builderFilePath(prefix, filename);
                try {
                    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                            .object(filePath)
                            .contentType("text/html")
                            .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
                            .build();
                    minioClient.putObject(putObjectArgs);
                    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
                    urlPath.append(separator+minIOConfigProperties.getBucket());
                    urlPath.append(separator);
                    urlPath.append(filePath);
                    return urlPath.toString();
                }catch (Exception ex){
                    log.error("minio put file error.",ex);
                    ex.printStackTrace();
                    throw new RuntimeException("上传文件失败");
                }
            }
            /**
             * 删除文件
             * @param pathUrl  文件全路径
             */
            @Override
            public void delete(String pathUrl) {
                String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
                int index = key.indexOf(separator);
                String bucket = key.substring(0,index);
                String filePath = key.substring(index+1);
                // 删除Objects
                RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
                try {
                    minioClient.removeObject(removeObjectArgs);
                } catch (Exception e) {
                    log.error("minio remove file error.  pathUrl:{}",pathUrl);
                    e.printStackTrace();
                }
            }
            /**
             * 下载文件
             * @param pathUrl  文件全路径
             * @return  文件流
             *
             */
            @Override
            public byte[] downLoadFile(String pathUrl)  {
                String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
                int index = key.indexOf(separator);
                String bucket = key.substring(0,index);
                String filePath = key.substring(index+1);
                InputStream inputStream = null;
                try {
                    inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
                } catch (Exception e) {
                    log.error("minio down file error.  pathUrl:{}",pathUrl);
                    e.printStackTrace();
                }
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byte[] buff = new byte[100];
                int rc = 0;
                while (true) {
                    try {
                        if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    byteArrayOutputStream.write(buff, 0, rc);
                }
                return byteArrayOutputStream.toByteArray();
            }
        }

        image.gif

        (4)对外加入自动配置

        在resources中新建META-INF/spring.factories  

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.my.file.service.impl.MinIOFileStorageService

        image.gif


        相关文章
        |
        12月前
        |
        Java Maven Android开发
        微服务——SpringBoot使用归纳——Spring Boot开发环境搭建和项目启动
        本文介绍了Spring Boot开发环境的搭建和项目启动流程。主要内容包括:jdk的配置(IDEA、STS/eclipse设置方法)、Spring Boot工程的构建方式(IDEA快速构建、官方构建工具start.spring.io使用)、maven配置(本地maven路径与阿里云镜像设置)以及编码配置(IDEA和eclipse中的编码设置)。通过这些步骤,帮助开发者顺利完成Spring Boot项目的初始化和运行准备。
        1066 0
        微服务——SpringBoot使用归纳——Spring Boot开发环境搭建和项目启动
        |
        12月前
        |
        Java 测试技术 微服务
        微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
        本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
        273 0
        |
        12月前
        |
        Java 微服务 Spring
        微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
        本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
        1291 0
        |
        12月前
        |
        Java 数据库 微服务
        微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——指定项目配置文件
        在实际项目中,开发环境和生产环境的配置往往不同。为简化配置切换,可通过创建 `application-dev.yml` 和 `application-pro.yml` 分别管理开发与生产环境配置,如设置不同端口(8001/8002)。在 `application.yml` 中使用 `spring.profiles.active` 指定加载的配置文件,实现环境快速切换。本节还介绍了通过配置类读取参数的方法,适用于微服务场景,提升代码可维护性。课程源码可从 [Gitee](https://gitee.com/eson15/springboot_study) 下载。
        541 0
        |
        12月前
        |
        Java 微服务 Spring
        微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
        在微服务架构中,随着业务复杂度增加,项目可能需要调用多个微服务。为避免使用`@Value`注解逐一引入配置的繁琐,可通过定义配置类(如`MicroServiceUrl`)并结合`@ConfigurationProperties`注解实现批量管理。此方法需在配置文件中设置微服务地址(如订单、用户、购物车服务),并通过`@Component`将配置类纳入Spring容器。最后,在Controller中通过`@Resource`注入配置类即可便捷使用,提升代码可维护性。
        240 0
        |
        8月前
        |
        Java Spring 容器
        SpringBoot自动配置的原理是什么?
        Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
        1225 0
        |
        9月前
        |
        人工智能 Java 测试技术
        Spring Boot 集成 JUnit 单元测试
        本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
        1024 0
        |
        5月前
        |
        JavaScript Java Maven
        【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
        SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
        514 5
        |
        5月前
        |
        Java 测试技术 数据库连接
        【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
        Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
        1045 3
        |
        12月前
        |
        前端开发 Java 数据库
        微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
        本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
        495 0