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

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: 主要介绍了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


        相关文章
        |
        16天前
        |
        消息中间件 监控 开发工具
        微服务(三)-实现自动刷新配置(不重启项目情况下)
        微服务(三)-实现自动刷新配置(不重启项目情况下)
        |
        3月前
        |
        负载均衡 Java 开发者
        如何在Spring Boot项目中实现微服务架构?
        如何在Spring Boot项目中实现微服务架构?
        |
        3月前
        |
        消息中间件 负载均衡 Java
        最容易学会的springboot gralde spring cloud 多模块微服务项目
        最容易学会的springboot gralde spring cloud 多模块微服务项目
        |
        4月前
        |
        JavaScript Java 测试技术
        基于springboot+vue.js+uniapp小程序的微服务的车联网位置信息管理附带文章源码部署视频讲解等
        基于springboot+vue.js+uniapp小程序的微服务的车联网位置信息管理附带文章源码部署视频讲解等
        39 1
        |
        4月前
        |
        中间件 应用服务中间件 nginx
        微服务原生部署有那么难吗?一篇文章带你搞定
        微服务原生部署有那么难吗?一篇文章带你搞定
        26 0
        |
        5月前
        |
        前端开发 JavaScript Java
        Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
        Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
        Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)五(前端页面
        |
        5月前
        |
        消息中间件 Java 数据安全/隐私保护
        Spring Cloud 项目中实现推送消息到 RabbitMQ 消息中间件
        Spring Cloud 项目中实现推送消息到 RabbitMQ 消息中间件
        |
        5月前
        |
        Java Spring
        IDEA 创建 SpringCloud项目-多项目方式
        IDEA 创建 SpringCloud项目-多项目方式
        122 0
        |
        5月前
        |
        Java Maven 微服务
        微服务项目-将普通文件夹设为模块与添加services窗口
        微服务项目-将普通文件夹设为模块与添加services窗口
        29 0
        |
        14天前
        |
        SQL 监控 druid
        springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
        这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。