首页 > 其他分享 >SpringBoot中集成Minio高性能分布式存储文件服务入门

SpringBoot中集成Minio高性能分布式存储文件服务入门

时间:2024-01-30 09:12:01浏览次数:34  
标签:Minio SpringBoot return minio bucket String bucketName import 分布式

场景

若依前后端分离版手把手教你本地搭建环境并运行项目:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662

参考上面搭建项目。

Minio

Minio是基于Go语言编写的对象存储服务,适合于存储大容量非结构化的数据,例如图片、音频、视频、日志文件、备份数据和容器/虚拟机镜像等,

而一个对象文件可以是任意大小,从几kb到最大5T不等。区别于分布式存储系统,minio的特色在于简单、轻量级,对开发者友好。

特点

简单、可靠:

Minio采用简单可靠的集群方案,摒弃复杂的大规模的集群调度管理,减少风险与性能瓶颈,聚焦产品的核心功能,

打造高可用的集群、灵活的扩展能力以及超过的性能。建立众多的中小规模、易管理的集群,

支持跨数据中心将多个集群聚合成超大资源池,而非直接采用大规模、统一管理的分布式集群。

功能完善:

Minio支持云原生,能与Kubernetes、Docker、Swarm编排系统良好对接,实现灵活部署。

且部署简单,只有一个可执行文件,参数极少,一条命令即可启动一个Minio系统。

Minio为了高性能采取无元数据数据库设计,避免元数据库成为整个系统的性能瓶颈,

并将故障限制在单个集群之内,从而不会涉及其他集群。Minio同时完全兼容S3接口,

因此也可以作为网关使用,对外提供S3访问。同时使用Minio Erasure code和checksum 来防止硬件故障。

即使损失一半以上的硬盘,但是仍然可以从中恢复。分布式中也允许(N/2)-1个节点故障。

官方文档:

https://minio.org.cn/

Java快速指南:

https://minio.org.cn/docs/minio/linux/developers/java/minio-java.html#minio-java-quickstart

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

1、Minio在Windows上下载安装启动

https://www.minio.org.cn/download.shtml#/windows

 

下载之后只有一个minio.exe,然后新建一个文件存储路径,这里是D:\minioData

在minio.exe所在的目录下打开cmd,输入

minio.exe server D:\minioData

后面跟着指定存储的目录

 

提示是因为未修改默认密码。

启动之后访问

http://127.0.0.1:9000/

这里会自动跳转到11466端口。

输入登录用户名密码,默认都为

minioadmin

2、SpringBoot中整合Minio实现客户端

添加项目依赖

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.2.2</version>
        </dependency>

新增yml配置文件内容

minio:
  server: http://127.0.0.1
  port: 9000
  accessKey: minioadmin
  secretKey: minioadmin

配置minio的ip、端口、用户名、密码

然后新建配置类,读取配置文件内容并建立minio连接

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig{

    private String server;

    private int port;

    private String accessKey;

    private String secretKey;

    /**
     * 创建minio连接对象
     * @return
     */
    @Bean
    public MinioClient minioClient(){
        return  MinioClient.builder()
                .endpoint(server,port,false)
                .credentials(accessKey,secretKey)
                .build();
    }
}

3、SpringBoot中操作Minio的工具类和使用示例

Minio中使用Bucket桶的概念,类似文件目录,一般一个项目中使用一个桶。

新建Minio工具类

import cn.hutool.core.io.FastByteArrayOutputStream;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Component
public class MinioUtils {

    @Autowired
    MinioClient minioClient;

    public final String PREFIX = "minio/";

    /**
     * 查看存储bucket是否存在
     *  bucketName 需要传入桶名
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

    /**
     * 创建存储bucket
     *  bucketName 需要传入桶名
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 删除存储bucket
     * bucketName 需要传入桶名
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }



    /**
     * 文件上传
     *
     * @param file 文件
     * @param bucketName bucketName
     * BucketName 需要传入桶名
     * @return Boolean
     */
    public String upload(String bucketName,MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)){
            throw new RuntimeException();
        }
        String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = PREFIX + fileName;
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 预览
     * @param fileName
     * @param bucketName bucketName
     * @return
     */
    public String preview(String bucketName,String fileName){
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     * @param fileName 文件名称
     * @param bucketName bucketName
     * @param res response
     * @return Boolean
     */
    public void download(String bucketName,String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)){
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                while ((len=response.read(buf))!=-1){
                    os.write(buf,0,len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()){
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看文件对象
     * @param bucketName bucketName
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除文件
     * @param fileName
     * @param bucketName
     * @return
     * @throws Exception
     */
    public boolean remove(String bucketName,String fileName){
        try {
            minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        }catch (Exception e){
            return false;
        }
        return true;
    }
}

 

单元测试,引入工具类

    @Autowired
    private MinioUtils minioUtils;

测试新建桶

    /**
     * 创建存储bucket
     */
    @Test
    public void createBucke(){
        List<Bucket> allBuckets = minioUtils.getAllBuckets();
        allBuckets.stream().forEach(bucket -> System.out.println(bucket.name()));
        minioUtils.makeBucket("new");
        allBuckets = minioUtils.getAllBuckets();
        allBuckets.stream().forEach(bucket -> System.out.println(bucket.name()));
    }

测试获取全部桶

    /**
     * 获取全部bucket
     */
    @Test
    public void getAllBuckets() {
        List<Bucket> allBuckets = minioUtils.getAllBuckets();
        System.out.println(allBuckets);
    }

测试桶是否存在

    /**
     * 测试桶是否存在
     */
    @Test
    public void bucketExists() {
        System.out.println(minioUtils.bucketExists("badao"));//true
        System.out.println(minioUtils.bucketExists("test"));//false
    }

测试移除桶

    /**
     * 移除bucket
     */
    @Test
    public void removeBucke(){
        List<Bucket> allBuckets = minioUtils.getAllBuckets();
        allBuckets.stream().forEach(bucket -> System.out.println(bucket.name()));
        minioUtils.removeBucket("new");
        allBuckets = minioUtils.getAllBuckets();
        allBuckets.stream().forEach(bucket -> System.out.println(bucket.name()));
    }

测试文件上传,这里使用MockMultipartFile模拟文件

    /**
     * upload file
     */
    @Test
    public void upload(){
        //MockmulpipartFile
        //第一个参数是表单中文件上传的字段名
        //第二个参数是文件名
        //第三个参数是文件的MIME类型
        //第四个参数是文件的内容
        MultipartFile mockFile = new MockMultipartFile("file","badao.txt","text/plain","File content".getBytes());
        minioUtils.upload("badao",mockFile);
    }

Java中MockMultipartFile使用来模拟文件的用法

MockmulpipartFile

第一个参数是表单中文件上传的字段名

第二个参数是文件名

第三个参数是文件的MIME类型

第四个参数是文件的内容

上传成功查看效果,可进行预览、下载等操作。

 

此时到minio的存储路径中也能看到文件

 

测试测试Minio预览

    /**
     * preview file
     */
    @Test
    public void preview(){
        String prefix = minioUtils.PREFIX;
        String preview = minioUtils.preview("badao", prefix+"cd6b22b2-55ce-41ea-ba6f-a9115e12a2fe.txt");
        System.out.println(preview);
    }

预览效果

 

测试Minio下载效果

import com.ruoyi.common.utils.MinioUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/test/minio")
public class MinioTestController {

    @Autowired
    private MinioUtils minioUtils;

    @GetMapping("/download")
    public void download(HttpServletResponse response) {
        String prefix = minioUtils.PREFIX;
        minioUtils.download("badao",prefix+"cd6b22b2-55ce-41ea-ba6f-a9115e12a2fe.txt",response);
    }
}

请求接口测试效果

 

更多功能和使用参考官方文档说明。

 

标签:Minio,SpringBoot,return,minio,bucket,String,bucketName,import,分布式
From: https://www.cnblogs.com/badaoliumangqizhi/p/17995744

相关文章

  • 解决 JUnit 版本引起的 SpringBoot 测试环境加载问题
    SpringBoot项目初始化后尝试自己编写测试类时报错空指针异常,在此记录下解决方法,如有错误,欢迎指正!1.问题描述1.1报错信息在执行SpringBoot单元测试时遇到如下报错信息:java.lang.NullPointerException atcom.thr.usercenter.SampleTest.testSelect(SampleTest.java:25......
  • SpringBoot实现动态数据源配置
    场景描述:前一阵子接手的新项目中需要使用2个数据源。一个叫行云数据库,一个叫OceanBase数据库。就是说,我有时候查询要查行云的数据,有时候查询要查OceanBase的数据,咋办?废话不多说,下面以mysql为例,开整。一、环境依赖<dependency><groupId>org.springframework.boot</gr......
  • 接口压力测试常用的性能指标,接口优化的点,分布式锁的方案常用的方案
    1.接口压力测试常用的性能指标2.接口优化的点3.实现分布式锁的方案常用的方案一.接口压力测试常用的性能指标:1、吞吐量吞吐量是系统每秒可以处理的事务数,也称为TPS(TransactionPerSecond)。比如:一次点播流程,从请求进入系统到视频画图显示出来这整个流程就是一次事务。所以......
  • DAPR-分布式系统运行时简介
    Dapr全称DistributedApplicationRuntime,翻译过来就是分布式应用程序运行时,在v1.0发布后得到了极大的发展。本章将向你介绍Dapr架构的核心概念,为您使用Dapr进行开发做好预热和准备工作。可以这么说,Dapr加速了新的云原生应用的开发,并简化了微服务架构的运用。在本章中,我们将讨论......
  • 【SpringBoot】当AOP引发的异常与@RestControllerAdvice擦肩而过:异常处理的盲点揭秘
    各位上午/下午/晚上好呀!今天在写bug的时候发现一个这样的问题:AOP抛出的异常竟然没有被@RestControllerAdvice注解修饰的异常统一处理类处理。 需求是这样子滴:对某些加了自定义注解的方法进行切面处理,通过条件判断是否有权限执行该方法。伪代码大概长这个样子:@Around(......
  • .Net Framework:MinIO objectname异常
     minio-dotnetgithub地址:github.com/minio/minio-dotnet1.异常现象:在调用PutObjectAsync/FileExist/FGetObject等方法操作MinIO时,objectname同时包含汉字、英文括号,MinIO内部throw异常:AuthorizationException:Therequestsignaturewecalculateddoesnotmatchthes......
  • SpringBoot统一结果返回,统一异常处理,大牛都这么玩
    引言在开发SpringBoot应用时,我们经常面临着不同的控制器方法需要处理各种不同类型的响应结果,以及在代码中分散处理异常可能导致项目难以维护的问题。你是否曾经遇到过在不同地方编写相似的返回格式,或者在处理异常时感到有些混乱?这些看似小问题的积累,实际上可能对项目产生深远的......
  • 新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅
    介绍在开发现代应用程序时,数据验证是确保用户输入的正确性和应用程序数据完整性的关键方面。SpringBoot提供了强大的数据验证机制,使开发者能够轻松地执行验证操作。本文将深入介绍SpringBoot中的Validation,以及如何在应用程序中正确使用它。为什么使用数据验证?1.用户输......
  • Pytorch分布式训练,其他GPU进程占用GPU0的原因
    问题最近跑师兄21年的论文代码,代码里使用了Pytorch分布式训练,在单机8卡的情况下,运行代码,出现如下问题。也就是说GPU(1..7)上的进程占用了GPU0,这导致GPU0占的显存太多,以至于我的batchsize不能和原论文保持一致。解决方法我一点一点进行debug。首先,在数据加载部分,由于没有将lo......
  • SpringBoot + SpEL,轻松搞定复杂权限控制
    对于在Springboot中,利用自定义注解+切面来实现接口权限的控制这个大家应该都很熟悉,也有大量的博客来介绍整个的实现过程,整体来说思路如下:自定义一个权限校验的注解,包含参数value配置在对应的接口上定义一个切面类,指定切点在切入的方法体里写上权限判断的逻辑乍一看,没毛病,学......