【分布式云储存】Springboot微服务接入MinIO实现文件服务

news2025/1/17 6:02:24

文章目录

    • 前言
    • 技术回顾
    • 准备工作
      • 申请accessKey\secretKey
      • 创建数据存储桶
      • 公共资源直接访问测试
    • 接入springboot实现文件服务
      • 依赖引入
      • 配置文件
      • MinIO配置
      • MinIO工具类
    • OkHttpSSLSocketClient兼容ssl
    • 静态资源预览解决方案
    • 资源上传预览测试
    • 测试结果

前言

上篇博客我们介绍了分布式云存储MinIO作业环境的搭建,以及分布式云储存MinIO在实际的文件服务中的优势。那么,今天我们就小试牛刀来将MinIO接入我们的微服务项目,实现一个分布式的文件服务器。

技术回顾

MinIO 提供高性能、与S3 兼容的对象存储系统,让你自己能够构建自己的私有云储存服务。
MinIO原生支持 Kubernetes,它可用于每个独立的公共云、每个 Kubernetes 发行版、私有云和边缘的对象存储套件。
MinIO是软件定义的,不需要购买其他任何硬件,在 GNU AGPL v3 下是 100% 开源的。

准备工作

申请accessKey\secretKey

MinIO控制台申请accessKey\secretKey
http://your_hostname:18001~18004/login minio/minio123
在这里插入图片描述

创建数据存储桶

创建数据存储桶
在这里插入图片描述在这里插入图片描述

点击创建的桶我们进行访问策略配置
在这里插入图片描述
在这里插入图片描述

访问策略有private\custom\public
public 公共的桶,任何人都可以访问资源,直接映射为静态资源,可直接提供预览和下载
custom 自定义桶,用户根据自身需求定义访问规则
private 私有的桶,需要授权才能访问

根据一般的生产需求,我们定义一个private,一个custom桶。private保存私有资源,custom保存公共资源并禁止目录访问。
在这里插入图片描述

private 规则:
在这里插入图片描述

custom 规则:
在这里插入图片描述

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "arn:aws:s3:::sacpublic"
            ]
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:AbortMultipartUpload",
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:ListMultipartUploadParts",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::sacpublic/*"
            ]
        }
    ]
}

公共资源直接访问测试

私有资源代码博文最后测试
公共资源直接访问测试:
在这里插入图片描述
在这里插入图片描述

接入springboot实现文件服务

依赖引入

maven引入依赖

<!--minio-->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.0</version>
</dependency>
<!--minio-->

配置文件

配置文件

minio.url = https://10.10.22.91:9100
minio.accessKey = fUIXbkBZ9UQTHOOZXNGW
minio.secretKey = sy1RAgItAOk9pk1gE7FbrPYzsZI87CfpGkuoY0KW
minio.buckets.public = sacpublic
minio.buckets.private = sacprivate

注意:接口调用minio url 为nginx映射出来的9000端口
http://your_hostname:9000

MinIO配置

MinIO配置

/**
 * MinioConfig
 * @author senfel
 * @version 1.0
 * @date 2023/9/14 11:37
 */
@Configuration
@ConditionalOnProperty(name = "oss.file.service", havingValue = "minio", matchIfMissing = true)
public class MinioConfig {

    @Value("${minio.url}")
    private String minioUrl;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;

    @Bean
    public MinioClient getMinioClient() {
        return MinioClient.builder().endpoint(url)
				.credentials(accessKey, secretKey).build();
    }
    
}

MinIO工具类

MinIO工具类

/**
 * MinioUtil
 * @author senfel
 * @version 1.0
 * @date 2023/9/14 10:26
 */
@Component
@ConditionalOnProperty(name = "oss.file.service", havingValue = "minio", matchIfMissing = true)
public class MinioUtil {

    @Resource
    private MinioClient minioClient;

    /**
     * 创建一个桶
     */
    public void createBucket(String bucket) throws Exception {
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        if (!found) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
        }
    }

    /**
     * 上传一个文件
     */
    public ObjectWriteResponse uploadFile(InputStream stream, String bucket, String objectName) throws Exception {
        String prefix = objectName.substring(objectName.lastIndexOf(".") + 1);
        //静态资源预览解决方案
        //String contentType = ViewContentType.getContentType(prefix);
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName)
                .contentType(contentType)
                .stream(stream, -1, 10485760).build());
        return objectWriteResponse;
    }

    /**
     * 列出所有的桶
     */
    public List<String> listBuckets() throws Exception {
        List<Bucket> list = minioClient.listBuckets();
        List<String> names = new ArrayList<>();
        list.forEach(b -> {
            names.add(b.name());
        });
        return names;
    }

    /**
     * 下载一个文件
     */
    public InputStream download(String bucket, String objectName) throws Exception {
        InputStream stream = minioClient.getObject(
                GetObjectArgs.builder().bucket(bucket).object(objectName).build());
        return stream;
    }

    /**
     * 删除一个桶
     */
    public void deleteBucket(String bucket) throws Exception {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucket).build());
    }

    /**
     * 删除一个对象
     */
    public void deleteObject(String bucket, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());
    }

    /**
     * 复制文件
     *
     * @Param: [sourceBucket, sourceObject, targetBucket, targetObject]
     * @return: void
     * @Date: 2021/11/15
     */
    public void copyObject(String sourceBucket, String sourceObject, String targetBucket, String targetObject) throws Exception {
        this.createBucket(targetBucket);
        minioClient.copyObject(CopyObjectArgs.builder().bucket(targetBucket).object(targetObject)
                .source(CopySource.builder().bucket(sourceBucket).object(sourceObject).build()).build());
    }

    /**
     * 获取文件信息
     *
     * @Param: [bucket, objectName]
     * @return: java.lang.String
     */
    public String getObjectInfo(String bucket, String objectName) throws Exception {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucket).object(objectName).build()).toString();
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。
     * @Param: [bucketName, objectName, expires]
     * @return: java.lang.String
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs
                .builder().bucket(bucketName).object(objectName).expiry(expires).method(Method.GET).build();
        return minioClient.getPresignedObjectUrl(build);
    }

}

OkHttpSSLSocketClient兼容ssl

对于改解决方案一般生产环境都有固定的域名和匹配的ssl证书,如果也不用https我们代码则不用兼容ssl。但是如果我们配置了不可信任的ssl,这里我们则需要进行ssl兼容方案。

/**
 * MinioConfig
 * @author senfel
 * @version 1.0
 * @date 2023/9/14 11:37
 */
@Configuration
@ConditionalOnProperty(name = "oss.file.service", havingValue = "minio", matchIfMissing = true)
public class MinioConfig {

    @Value("${minio.url}")
    private String minioUrl;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;

   @Bean
    public MinioClient getMinioClient() {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .sslSocketFactory(OkHttpSSLSocketClient.getSSLSocketFactory(),OkHttpSSLSocketClient.getX509TrustManager()) // //通过sslSocketFactory方法设置https证书
                .hostnameVerifier(OkHttpSSLSocketClient.getHostnameVerifier())
                .build();
        return MinioClient.builder().endpoint(minioUrl).httpClient(okHttpClient)
                .credentials(accessKey, secretKey).build();
    }
    
}
/**
 * OkHttpSSLSocketClient
 * @author senfel
 * @version 1.0
 * @date 2023/9/15 10:07
 */
public class OkHttpSSLSocketClient{
    //获取SSLSocketFactory
    public static SSLSocketFactory getSSLSocketFactory() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, getTrustManager(), new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //获取TrustManager
    private static TrustManager[] getTrustManager() {
        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
        return trustAllCerts;
    }

    //获取HostnameVerifier,验证主机名
    public static HostnameVerifier getHostnameVerifier() {
        HostnameVerifier hostnameVerifier = (s, sslSession) -> true;
        return hostnameVerifier;
    }
    //X509TrustManager:证书信任器管理类
    public static X509TrustManager getX509TrustManager() {
        X509TrustManager x509TrustManager = new X509TrustManager() {
            //检查客户端的证书是否可信
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {

            }
            //检查服务器端的证书是否可信
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
        return x509TrustManager;
    }
}

静态资源预览解决方案

由于我们使用API上传,minio不能区分我们资源的类型,如果资源类型不对则不能正确提供预览功能,都直接变成为访问资源链接就下载了。

所以,在实际的项目集成中我们在上传资源前需要将资源类型写入请求中,方便minio解析并提供预览和下载功能。

/**
 * 上传一个文件
 */
public ObjectWriteResponse uploadFile(InputStream stream, String bucket, String objectName) throws Exception {
    String prefix = objectName.substring(objectName.lastIndexOf(".") + 1);
    //静态资源预览解决方案
    String contentType = ViewContentType.getContentType(prefix);
    ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName)
            .contentType(contentType)
            .stream(stream, -1, 10485760).build());
    return objectWriteResponse;
}
/**
 * ViewContentType
 * @author senfel
 * @version 1.0
 * @date 2023/9/14 17:27
 */
public enum ViewContentType {
    DEFAULT("default","application/octet-stream"),
    JPG("jpg", "image/jpeg"),
    TIFF("tiff", "image/tiff"),
    GIF("gif", "image/gif"),
    JFIF("jfif", "image/jpeg"),
    PNG("png", "image/png"),
    TIF("tif", "image/tiff"),
    ICO("ico", "image/x-icon"),
    JPEG("jpeg", "image/jpeg"),
    WBMP("wbmp", "image/vnd.wap.wbmp"),
    FAX("fax", "image/fax"),
    NET("net", "image/pnetvue"),
    JPE("jpe", "image/jpeg"),
    RP("rp", "image/vnd.rn-realpix");
    private String prefix;
    private String type;
    public static String getContentType(String prefix){
        if(StringUtils.isEmpty(prefix)){
            return DEFAULT.getType();
        }
        prefix = prefix.substring(prefix.lastIndexOf(".") + 1);
        for (ViewContentType value : ViewContentType.values()) {
            if(prefix.equalsIgnoreCase(value.getPrefix())){
                return value.getType();
            }
        }
        return DEFAULT.getType();
    }

    ViewContentType(String prefix, String type) {
        this.prefix = prefix;
        this.type = type;
    }

    public String getPrefix() {
        return prefix;
    }

    public String getType() {
        return type;
    }
}




资源上传预览测试

MinIO对于图片等资源可以直接预览,对于excel文档等直接提供下载功能

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class CodeDbInfoServiceTests  {

    @Resource
    @Lazy
    private MinioUtil minioUtil;


    /**
     * upload
     * @author senfel
     * @date 2023/9/27 10:00
     * @return void
     */
    @Test
    public void upload() throws Exception {
        FileInputStream fileInputStream = new FileInputStream("C:/Users/dev/Desktop/minio.png");
        ObjectWriteResponse sacprivate = minioUtil.uploadFile(fileInputStream, "sacprivate", "test/minio.png");
        System.err.println(sacprivate.bucket());
    }

    /**
     * 获取私库连接
     * @author senfel
     * @date 2023/9/27 10:01
     * @return void
     */
    @Test
    public void getPrivateUrl() throws Exception{
        String url = minioUtil.getPresignedObjectUrl("sacprivate", "test/minio.png", 60);
        System.err.println(url);
    }
}

测试结果

图片成功上传
在这里插入图片描述

私库直接预览访问失败
在这里插入图片描述

获取私库链接过期时间为60s
在这里插入图片描述

访问成功
在这里插入图片描述

60s后再次访问该链接,提示链接已经过期
在这里插入图片描述

至此minio接入并测试完成,对于公开桶的资源直接可任意访问不用获取有效期链接。

写在最后
Springboot微服务接入MinIO实现文件服务较为简单,我们只要按照官方文档引入依赖调用即可。值得注意的是我们根据需求选择接入ssl兼容方案和静态资源预览功能。当然,minio的private\custom\public可以完全实现我们各种需求,可以完全替代市场上付费和笨重的分布式服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1052210.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四川玖璨电子商务有限公司提供专业的抖音培训课程

在今天的数字时代&#xff0c;抖音等社交媒体平台已经成为企业和个人扩大影响力、提高品牌知名度的主要战场。然而&#xff0c;如何在繁杂的社交媒体市场中取得成功呢&#xff1f;四川玖璨电子商务有限公司为你提供了一个解决方案——高质量的抖音培训课程。 四川玖璨电子商务…

黑马程序员Docker快速入门到项目部署(学习笔记)

目录 一、Docker简介 二、安装Docker 2.1、卸载旧版 2.2、配置Docker的yum库 2.3、安装Docker 2.4、启动和校验 2.5、配置镜像加速 2.5.1、注册阿里云账号 2.5.2、开通镜像服务 2.5.3、配置镜像加速 三、快速入门 3.1、部署MYSQL 3.2、命令解读 四、Docker基础 …

PostMan的学习

PostMan的学习 目录 环境变量和全局变量接口关联内置动态参数以及自定义动态参数实现业务闭环Postman断言批量运行collection数据驱动之CSV文件和JSON文件测试必须带请求头的接口Mock Serviers 服务器Cookie鉴权NewmanPostManNewManjenkins实现接口测试持续集成 参考资料&am…

在线文档生成:Swagger

1 简介 现在的项目开发都是前后端分离&#xff0c;前端和后端是两拨人在开发&#xff0c;所以这就涉及到前后端人员的接口交互了。如果使用自己维护的接口文档或口口相传的话&#xff0c;很容易出现接口更新不及时的问题。这个时候就需要像Swagger这样的在线文档生成框架出马了…

300元左右的耳机哪个性价比最好、好用的开放式耳机推荐

作为经常使用蓝牙耳机的小伙伴们而言&#xff0c;入耳式耳机戴久了会带来不舒适的感觉&#xff0c;开放式耳机显然是最舒服的选择&#xff0c;不入耳不会产生异物感&#xff0c;在户外运动可以时刻保持与外界连接更安全&#xff0c;也不会因为耳塞的卫生问题造成耳道感染&#…

嵌入式学习笔记(35)外部中断

6.9.1什么是外部中断 (1)内部中断就是指中断源来自于SoC内部&#xff08;一般是内部外设&#xff09;&#xff0c;譬如串口、定时器等部件产生的中断&#xff1b;外部中断是SoC外部的设备&#xff0c;通过外部中断对应的GPIO引脚产生的中断。 (2)按键在SoC中就使用了外部中断…

【MySQL】数据类型(二)

文章目录 一. char字符串类型二. varchar字符串类型2.1 char和varchar比较 三. 日期和时间类型四. enum和set类型4.1 set的查询 结束语 一. char字符串类型 char (L) 固定长度字符串 L是可以存储的长度&#xff0c;单位是字符&#xff0c;最大长度是255 MySQL中的字符&#xff…

No159.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

Kafka(一)使用Docker Compose安装单机Kafka以及Kafka UI

文章目录 Kafka中涉及到的术语Kafka镜像选择Kafka UI镜像选择Docker Compose文件Kafka配置项说明KRaft vs Zookeeper和KRaft有关的配置关于Controller和Broker的概念解释Listener的各种配置 Kafka UI配置项说明 测试Kafka集群Docker Compose示例配置 Kafka中涉及到的术语 对于…

基于SpringBoot的美发门店管理系统

目录 前言 一、技术栈 二、系统功能介绍 美容项目管理 产品库存管理 产品购买管理 会员卡管理 项目预定管理 产品购买信息 会员充值管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信…

笔试强训Day8

链接&#xff1a;求最小公倍数__牛客网 T1:求最小公倍数 正整数A和正整数B 的最小公倍数是指 能被A和B整除的最小的正整数值&#xff0c;设计一个算法&#xff0c;求输入A和B的最小公倍数。 数据范围&#xff1a;1≤a,b≤100000 #include<iostream> using namespace…

第四章 c数组

数组的概念 若干个相同类型的变量在内存中有序存储的集合。 数组在内存中是连续存储的。 数组的分类 按元素的类型分类 char a[10]; short a[10]; int a[10]; long int a[10]; int *a[10]; struct stu boy[10];按维度分类 int a[10]; int b[1][2]; int c[1][2][3]; int d[…

win10打开VMware 16 pro里面的虚拟机就蓝屏怎么办

2023年9月30日&#xff0c;周六下午 今天下午我也遇到了这个问题&#xff0c;后来解决了&#xff0c;于是记录一下我的解决办法 目录 1、打开控制面板&#xff0c;并选择“程序和功能” 2、点击“启动或关闭Windows服务” 3、勾选两个服务 4、重启电脑&#xff0c;大功告成…

博弈论中静态博弈经典场景案例

博弈论中静态博弈经典场景案例 1、齐威王田忌赛马 田忌赛马是中国家喻户晓的故事&#xff0c;故事讲述的是齐国大将田忌的谋士孙膑如何运用计谋帮助田忌在与齐威王赛马时以弱胜强的故事&#xff0c;这个故事其实本质也是一个博弈的过程。     齐威王要和田忌赛马&#xff…

LongLoRA:不需要大量计算资源的情况下增强了预训练语言模型的上下文能力

麻省理工学院和香港中文大学推出了LongLoRA&#xff0c;这是一种革命性的微调方法&#xff0c;可以在不需要大量计算资源的情况下提高大量预训练语言模型的上下文能力。 LongLoRA是一种新方法&#xff0c;它使改进大型语言计算机程序变得更容易&#xff0c;成本更低。训练LLM往…

2023-09-30 LeetCode每日一题(全部开花的最早一天)

2023-03-29每日一题 一、题目编号 2136. 全部开花的最早一天二、题目链接 点击跳转到题目位置 三、题目描述 你有 n 枚花的种子。每枚种子必须先种下&#xff0c;才能开始生长、开花。播种需要时间&#xff0c;种子的生长也是如此。给你两个下标从 0 开始的整数数组 plant…

【每日一题】全部开花的最早一天

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;贪心排序 写在最后 Tag 【贪心】【排序】【数组】【2023-09-30】 题目来源 2136. 全部开花的最早一天 题目解读 每一朵花需要先种下种子才会生长、开花。种种子需要花一些时间&#xff0c;生长也需要一些时间。每天只…

26973-2011 空气源热泵辅助的太阳能热水系统 储水箱容积大于0.6m3 技术规范

声明 本文是学习GB-T 26973-2011 空气源热泵辅助的太阳能热水系统 储水箱容积大于0.6m3 技术规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了空气源热泵辅助的太阳能热水系统的定义、符号和单位、组成与分类、设计要求、技术…

excel筛选后求和

需要对excel先筛选&#xff0c;后对“完成数量”进行求和。初始表格如下&#xff1a; 一、选中表内任意单元格&#xff0c;按ctrlshiftL&#xff0c;开启筛选 二、根据“部门”筛选&#xff0c;比如选择“一班” 筛选完毕后&#xff0c;选中上图单元格&#xff0c;然后按alt后&…

PyQt/PySide ImportError: DLL load failed while importing Shiboken,PyQt库和python

最近在测试PySide项目&#xff0c;在新环境下报错了&#xff1a;ImportError: DLL load failed while importing Shiboken: 找不到指定的程序。 Traceback (most recent call last):File "D:/xxx.py", line 10, in <module>from PySide6.QtWidgets import QAp…