User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   More   ›   MinIO   ›   正文
MinIO

Minio—快速整理

2024-06-13 14:17:05
1759  0 1
参考目录 隐藏
1) MinIO 介绍
2) MINIO 基础概念
3) Set / Drive 的关系
4) MIINO如何写入对象?
5) Minio存储架构
6) 单主机,单硬盘模式
7) 单主机,多硬盘模式
8) 多主机、多硬盘模式(分布式)
9) 分布式Minio有什么好处?
10) MinIO的数据高可靠
11) Reed-Solomon code
12) Bit Rot Protection
13) 部署实操
14) Springboot集成MinIO基础实现

阅读完需:约 21 分钟

MinIO 介绍

Minio 是个基于 Golang 编写的开源对象存储套件,虽然轻量,却拥有着不错的性能。

官方网站

https://min.io/product/enterpriseoverview

https://github.com/minio/minio

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。

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

MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。

对于中小型企业,如果不选择存储上云,那么 Minio 是个不错的选择,麻雀虽小,五脏俱全。

当然 Minio 除了直接作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。

MINIO 基础概念

MINIO 有几个概念比较重要:

  • Object:存储到 Minio 的基本对象,如文件、字节流,Anything…
  • Bucket:用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
  • Drive:即存储数据的磁盘,在 MinIO 启动时,以参数的方式传入。Minio 中所有的对象数据都会存储在 Drive 里。
  • Set:即一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的 Drive 分布在不同位置。一个对象存储在一个 Set 上。(For example: {1…64} is divided into 4 sets each of size 16.)
    • 一个对象存储在一个Set上
    • 一个集群划分为多个Set
    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
    • 一个SET中的Drive尽可能分布在不同的节点上

Set / Drive 的关系

Set /Drive 这两个概念是 MINIO 里面最重要的两个概念,一个对象最终是存储在 Set 上面的。

我们来看下边 MINIO 集群存储示意图,每一行是一个节点机器,这有 32 个节点,每个节点里有一个小方块我们称之 Drive,Drive 可以简单地理解为一个硬盘。

图中,一个节点有 32 个 Drive,相当于 32 块硬盘。

Set 是另外一个概念,Set 是一组 Drive 的集合,图中,所有蓝色、橙色背景的Drive(硬盘)的就组成了一个 Set.

MIINO如何写入对象?

MINIO 是通过数据编码,将原来的数据编码成 N 份,N 就是一个 Set 上面 Drive 的数量,后面多次提到的 N 都是指这个意思。

上图中,一个 Set 上面 Drive 的数量,是3.

对象被编码成N份之后,把每一份,写到对应的 Drive 上面,这就是把一个对象存储在整个 Set 上。

一个集群包含多个 Set,每个对象最终存储在哪个 Set 上是根据对象的名称进行哈希,然后影射到唯一的 Set 上面,这个方式从理论上保证数据可以均匀的分布到所有的 Set 上。

根据的观测,数据分布的也非常均匀,一个 Set 上包含多少个 Drive 是由系统自动根据集群规模算出来的,当然,也可以自己去配置。

一个 Set 的 Drive 系统会考虑尽可能把它放在多的节点上面,保证它的可靠性。

Minio存储架构

Minio针对不同应用场景也设置了对应的存储架构:

单主机,单硬盘模式

该模式下,Minio只在一台服务器上搭建服务,且数据都存在单块磁盘上,该模式存在单点风险,主要用作开发、测试等使用

单主机,多硬盘模式

该模式下,Minio在一台服务器上搭建服务,但数据分散在多块(大于4块)磁盘上,提供了数据上的安全保障

多主机、多硬盘模式(分布式)

该模式是Minio服务最常用的架构,通过共享一个access_key和secret_key,在多台(2-32)服务器上搭建服务,且数据分散在多块(大于4块,无上限)磁盘上,提供了较为强大的数据冗余机制(Reed-Solomon纠删码)。

分布式Minio有什么好处?

在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。

数据保护

分布式Minio采用 纠删码来防范多个节点宕机和位衰减bit rot。

分布式Minio至少需要4个硬盘,使用分布式Minio自动引入了纠删码功能。

高可用

单机Minio服务存在单点故障,相反,如果是一个有N块硬盘的分布式Minio, 只要有N/2硬盘在线,你的数据就是安全的。

不过你需要至少有N/2+1个硬盘来创建新的对象。

例如,一个16节点的Minio集群,每个节点16块硬盘,就算8台服务器宕机,这个集群仍然是可读的,不过你需要9台服务器才能写数据。

注意,只要遵守分布式Minio的限制,你可以组合不同的节点和每个节点几块硬盘。

比如,你可以使用2个节点,每个节点4块硬盘,也可以使用4个节点,每个节点两块硬盘,诸如此类。

一致性

Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。

MinIO的数据高可靠

Minio使用了Erasure Code 纠删码和 Bit Rot Protection 数据腐化保护这两个特性,所以MinIO的数据可靠性做的高。

Erasure Code纠删码

纠删码(Erasure Code)简称EC,是一种数据保护方法,它将数据分割成片段,把冗余数据块扩展、编码,并将其存储在不同的位置,比如磁盘、存储节点或者其它地理位置。

从数据函数角度来说,纠删码提供的保护可以用下面这个简单的公式来表示:n = k + m。变量“k”代表原始数据或符号的值。变量“m”代表故障后添加的提供保护的额外或冗余符号的值。变量“n”代表纠删码过程后创建的符号的总值。

举个例子,假设n=16,代表有16块磁盘,另外,有10份原始文件一模一样,称为k,16 = 10 +m,这个m就是可以恢复的校验块个数,所以m是6,任意6个不可用,原始文件都可以恢复,极端情况,10个原始文件坏掉6个,靠4个原始的加上6个校验块,可以把坏掉的6个原始文件恢复。

MinIO的编码方式,将一个对象编码成若干个数据块和校验块,我们简称为Erasure Code码,这个是编码的类型,这种编码的类型,还需要算法来实现,minio 采用的是 Reed-Solomon算法。

MinIO使用Reed-Solomon算法,该算法把对象编码成若干个数据块和校验块。

Reed-Solomon算法的特点:

  • 低冗余
  • 高可靠

为了表述方便,把数据块和校验块统称为编码块,之后我们可以通过编码块的一部分就能还原出整个对象。

Reed-Solomon code

Reed-Solomon 是纠删码的实现算法的一种,当然,也是一种恢复丢失和损坏数据的数学算法,

Minio默认采用Reed-Solomon code将数据拆分成N/2个数据块和N/2个奇偶校验块。

这就意味着如果是16块盘,一个对象会被分成8个数据块、8个奇偶校验块,你可以丢失任意8块盘(不管其是存放的数据块还是校验块),你仍可以从剩下的盘中的数据进行恢复。

如上图,如我们所知,一个对象存储在一个Set上面,这个Set包含16个Drive,其中灰色的一半是数据库,橙色的一半是校验块,这种方式最多能忍受一半的编码丢失或损坏。

所有编码块的大小是原对象的2倍,跟传统多副本存储方案相比,他只冗余存了一份,但可靠性更高。

纠删码的工作原理和RAID或者副本不同,像RAID6只能在损失两块盘,或者以下的情况下不丢数据,而Minio纠删码可以在丢失一半的盘的情况下,仍可以保证数据安全。

而且Minio纠删码是作用在对象级别,可以一次恢复一个对象,而RAID是作用在卷级别,数据恢复时间很长。

Minio对每个对象单独编码,存储服务一经部署,通常情况下是不需要更换硬盘或者修复。

此外,针对不同应用所需的数据安全级别不同,Minio还提供了存储级别(Storage Class)的配置,调整数据块和校验块的比例,做到对空间的最佳使用。

比如在将比例调整为14:2后,存储100M的数据占用的空间仅为114M。

Bit Rot Protection

接下来讲Bit Rot Protection,直译就是腐烂。

它只是物理设备上的一些文件细微的损坏,还没有被操作系统所硬件所察觉,但是他已经损坏了。

Bit Rot 位衰减又被称为数据腐化Data Rot、无声数据损坏Silent Data Corruption

位衰减可以理解为无形中的数据丢失——或者称为“Bit rot”, 是指物理存储介质的衰减所带来的隐患将凸显出来。

位衰减是目前硬盘数据的一种严重数据丢失问题。

硬盘上的数据可能会神不知鬼不觉就损坏了,也没有什么错误日志。

一项对150万块硬盘的研究表明,每90块硬盘就有1块有这种“软错误”,这个错误不但会导致数据丢失,还会导致RAID错误。

针对这一问题,最新的Minio采用了HighwayHash算法计算校验和来防范位衰减,根据测试结果,其可以实现10GB/s的处理速度。

大致的做法是:

MinIO把之前的编码块进行 HighwayHash 编码,最后要校验这个编码,以确保每个编码是正确的。

文件的修复 :

另外,MinIO提供了一个管理工具,可以对所有编码块进行校验,如果发现编码块有问题,再去修复它。

得益于Reed-Solomon纠删码,Minio可以更加灵活的对文件进行修复。

部署实操

部署的方式有很多,K8S,docker,单机,分布式等等

https://min.io/docs/minio/container/index.html

https://hub.docker.com/r/minio/minio

这里测试开发用的是docker的方式部署,单机部署

docker run -d -p 9000:9000 -p 9001:9001 quay.io/minio/minio server /data --console-address ":9001"

页面的端口是9001,服务的端口是9000

默认的账号密码都是 minioadmin,部署后就可以打开页面查看

Springboot集成MinIO基础实现

Java 版 MinIO 客户端 SDK

https://github.com/minio/minio-java

https://min.io/docs/minio/linux/developers/java/API.html

Maven 依赖配置

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>3.3.0</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

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

Minio yml配置

minio:
  endpoint: http://82.157.xxx.xx:9000
  bucketName: test001
  accessKey: XfBJZbrB8szv1GrH3NFH
  secretKey: OdloBZdI1QezuFEYyqr81kd3JbymKI03BsQLyNqj
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;

/**
 * Minio配置
 *
 * @author e
 * @date 2024/06/11
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucketName;

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

Minio 对象创建接收

import lombok.Builder;
import lombok.Data;

/**
 * 对象项
 *
 * @author e
 * @date 2024/06/13
 */
@Data
@Builder
public class ObjectItem {
    private String objectName;
    private Boolean directory;
    private String lastModified;
    private String size;
    private String url;
}

Minio 核心工具

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * IO工具
 *
 * @author e
 * @date 2024/06/12
 */
@Component
@Slf4j
public class MinioUtils {

    @Resource
    private MinioConfig minioConfig;

    @Resource
    private MinioClient minioClient;

    /**
     * 判断桶是否存在
     *
     * @param bucketName 桶名称
     * @return {@link Boolean}
     */
    public Boolean existBucket(String bucketName) {
        boolean exists = false;
        try {
            exists = minioClient.bucketExists(
                    BucketExistsArgs.builder()
                            .bucket(bucketName)
                            .build());
        } catch (Exception e) {
            log.error("判断桶是否存在异常", e);
        }
        return exists;
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 桶名称
     * @return {@link Boolean}
     */
    public Boolean makeBucket(String bucketName) {
        if (Boolean.FALSE.equals(existBucket(bucketName))) {
            try {
                minioClient.makeBucket(
                        MakeBucketArgs.builder()
                                .bucket(bucketName)
                                .build());
            } catch (Exception e) {
                log.error("创建存储桶异常", e);
                return false;
            }
            return true;
        } else {
            return false;
        }
    }


    /**
     * 删除存储桶
     *
     * @param bucketName 桶名称
     * @return {@link Boolean}
     */
    public Boolean removeBucket(String bucketName) {
        if (Boolean.TRUE.equals(existBucket(bucketName))) {
            try {
                minioClient.removeBucket(
                        RemoveBucketArgs.builder()
                                .bucket(bucketName)
                                .build());
            } catch (Exception e) {
                log.error("删除存储桶异常", e);
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * 列出一个桶中的所有文件目录和大小信息
     *
     * @param bucketName 桶名称
     * @return {@link List}<{@link ObjectItem}>
     */
    public List<ObjectItem> getListFiles(String bucketName) {
        if (Boolean.TRUE.equals(existBucket(bucketName))) {
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .recursive(true)
                    .build());

            List<ObjectItem> objectItems = new ArrayList<>();
            results.forEach(result -> {
                try {
                    Item item = result.get();
                    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                    String fileSize = getFileSize(item.size());
                    String url = minioConfig.getEndpoint() + "/" + bucketName + "/" + item.objectName();
                    ObjectItem objectItem = ObjectItem.builder()
                            .objectName(item.objectName())
                            .directory(item.isDir())
                            .lastModified(item.lastModified()
                                    .format(dateTimeFormatter))
                            .size(fileSize)
                            .url(url)
                            .build();
                    objectItems.add(objectItem);
                } catch (Exception e) {
                    log.error("获取对象信息失败", e);
                }
            });
            return objectItems;
        }
        return null;
    }


    /**
     * 获取文件夹下所有对象
     *
     * @param bucketName 桶名称
     * @param groupName  组名称 桶内开始于指定前缀
     * @return {@link List}<{@link ObjectItem}>
     */
    public List<ObjectItem> getObjectsByDir(String bucketName, String groupName) {
        if (Boolean.TRUE.equals(existBucket(bucketName))) {
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .prefix(groupName)
                    .bucket(bucketName)
                    .recursive(true)
                    .build());

            List<ObjectItem> objectItems = new ArrayList<>();
            results.forEach(result -> {
                try {
                    Item item = result.get();
                    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                    String fileSize = getFileSize(item.size());
                    String url = minioConfig.getEndpoint() + "/" + bucketName + "/" + item.objectName();
                    ObjectItem objectItem = ObjectItem.builder()
                            .objectName(item.objectName())
                            .directory(item.isDir())
                            .lastModified(item.lastModified()
                                    .format(dateTimeFormatter))
                            .size(fileSize)
                            .url(url)
                            .build();
                    objectItems.add(objectItem);
                } catch (Exception e) {
                    log.error("获取对象信息失败", e);
                }
            });
            return objectItems;
        }

        return null;
    }


    /**
     * 迁移文件夹下所有对象到另一文件夹中
     *
     * @param bucketName   桶名称
     * @param groupName    组名称
     * @param newGroupName 新组名
     */
    public void copyObjectByDir(String bucketName,String newBucketName, String groupName, String newGroupName) {

        if (Boolean.TRUE.equals(existBucket(bucketName)) && Boolean.TRUE.equals(existBucket(newBucketName))) {

            List<ObjectItem> objects = getObjectsByDir(bucketName, groupName);
            objects.forEach((object -> {
                String name = object.getObjectName();
                String originName = name.substring(name.lastIndexOf('/') + 1);

                String oldObjectName = null!= groupName ? groupName + "/" + originName : originName;
                String newObjectName = null!= newGroupName ? newGroupName + "/" + originName : originName;
                try {
                    //复制文件夹下所有对象到另一文件夹中
                    minioClient.copyObject(
                            CopyObjectArgs.builder()
                                    .source(CopySource.builder().bucket(bucketName).object(oldObjectName).build())
                                    .bucket(newBucketName)
                                    .object(newObjectName)
                                    .build()
                    );

                    //删除原有对象
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(oldObjectName)
                            .build());

                } catch (Exception e) {
                    log.error("复制文件夹下所有对象到另一文件夹中失败", e);
                }
            }));
        }

    }


    /**
     * 上传文件
     *
     * @param file       文件
     * @param bucketName
     */
    public String upload(MultipartFile file, String bucketName) {
        try {
            //给文件生成一个唯一名称  当日日期-uuid.后缀名
            String folderName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss"));
            String fileName = String.valueOf(UUID.randomUUID());
            //文件后缀名
            String extName = Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf("."));
            String objectName = folderName + "-" + fileName + extName;
            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs
                    .builder()
                    .bucket(null != bucketName ? bucketName : minioConfig.getBucketName())
                    .object(objectName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
            log.info("文件上传成功,文件名为:{}", objectName);
            return objectName;
        } catch (Exception e) {
            log.error("上传文件失败", e);
        }
        return null;
    }


    /**
     * 下载文件
     *
     * @param fileName
     * @param bucketName
     * @return
     */
    public ResponseEntity<byte[]> download(String fileName, String bucketName) {

        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;

        try {
            //获取对象输入流
            in = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(null!= bucketName ?bucketName: minioConfig.getBucketName())
                    .object(fileName)
                    .build());
            //创建输出流
            out = new ByteArrayOutputStream();
            //复制流
            IOUtils.copy(in, out);

            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentLength(bytes.length);
            headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(List.of("*"));
            responseEntity = new ResponseEntity<>(bytes, headers, HttpStatus.OK);

        } catch (Exception e) {
            log.error("下载文件失败", e);
        } finally {
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(out);
        }
        return responseEntity;
    }


    /**
     * 获取文件预览url
     *
     * @param fileName   文件名文件名
     * @param bucketName 桶名称
     * @return {@link String}
     */
    public String getPresignedUrl(String fileName,String bucketName) {
        String presignedUrl = null;
        try {
            // 判断桶是否存在
            boolean isExist = existBucket(bucketName);
            // 桶存在
            if (isExist) {
                presignedUrl = minioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.GET)
                                .bucket(bucketName)
                                .object(fileName)
                                .build());
                return presignedUrl;
            } else {  // 桶不存在
                log.error("获取文件预览url失败,桶{}不存在", bucketName);
            }
        }catch (Exception e){
            log.error("获取文件预览url失败", e);
        }
        return presignedUrl;
    }


    /**
     * 删除一个文件夹下所有文件
     *
     * @param groupName  组名称
     * @param bucketName 桶名称
     */
    public void removeObjectsByDir(String bucketName, String groupName) {

        if (Boolean.TRUE.equals(existBucket(bucketName))) {
            try {
                Iterable<Result<Item>> results = minioClient.listObjects(
                        ListObjectsArgs.builder()
                                .bucket(bucketName)
                                .prefix(groupName)
                                .recursive(true)
                                .build()
                );

                for (Result<Item> itemResult : results) {
                    Item item = itemResult.get();
                    String objectName = item.objectName();
                    log.info("对象名称:" + objectName);
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build());
                }
            } catch (Exception e) {
                log.error("删除文件异常", e);
            }

        }

    }


    /**
     * 删除对象(支持批量删除)
     *
     * @param bucketName 桶名称
     * @param objects    对象
     * @return {@link Boolean}
     */
    public Boolean removeObjects(String bucketName, List<String> objects) {
        if (Boolean.TRUE.equals(existBucket(bucketName))) {
            List<DeleteObject> deleteObjects = objects.stream()
                    .map(DeleteObject::new).collect(Collectors.toList());

            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(deleteObjects)
                    .build());
            try {
                for (Result<DeleteError> result : results) {
                    DeleteError error = result.get();
                    log.error("MinIo删除错误=>bucketName{},message=>{}", error.bucketName(), error.message());
                }

            } catch (Exception e) {
                log.error("MinIo删除错误异常");
            }
            return true;
        }
        return false;
    }


    /**
     * 判断对象大小
     *
     * @param size 大小
     * @return {@link String}
     */
    public static String getFileSize(long size) {
        //如果字节数少于1024,则直接以B为单位,否则先除于1024,后3位因太少无意义
        if (size < 1024) {
            return size + "B";
        } else {
            size = size / 1024;
        }
        //如果原字节数除于1024之后,少于1024,则可以直接以KB作为单位
        //因为还没有到达要使用另一个单位的时候
        //接下去以此类推
        if (size < 1024) {
            return Math.round(size * 100) / 100 + "KB";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            //因为如果以MB为单位的话,要保留最后1位小数,
            //因此,把此数乘以100之后再取余
            return Math.round(size * 100) / 100 + "MB";
        } else {
            //否则如果要以GB为单位的,先除于1024再作同样的处理
            return Math.round(size / 1024 * 100) / 100 + "GB";
        }
    }
}

测试接口

import com.linktopa.corex.multipart.core.MultipartData;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;


/**
 * 文件控制器
 *
 * @author e
 * @date 2024/06/13
 */
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController {


    @Resource
    private MinioUtils minioUtils;

    /**
     * 查看存储bucket是否存在
     *
     * @param bucketName 桶名称
     * @return {@link MultipartData}
     */
    @GetMapping("/bucketExists")
    public Boolean bucketExists(@RequestParam("bucketName") String bucketName) {
        return minioUtils.existBucket(bucketName);
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName 桶名称
     * @return {@link MultipartData}
     */
    @GetMapping("/makeBucket")
    public Boolean makeBucket(String bucketName) {
        return minioUtils.makeBucket(bucketName);
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 桶名称
     * @return {@link MultipartData}
     */
    @DeleteMapping("/removeBucket")
    public Boolean removeBucket(String bucketName) {
        return minioUtils.removeBucket(bucketName);
    }

    /**
     * 获取文件夹下所有对象
     *
     * @return {@link MultipartData}
     */
    @GetMapping("/getObjectsByDir")
    public List<ObjectItem> getObjectsByDir(String bucketName, String groupName) {
        return minioUtils.getObjectsByDir(bucketName,groupName);
    }


    /**
     * 列出一个桶中的所有文件目录和大小信息
     *
     * @param bucketName 桶名称
     * @return {@link List}<{@link ObjectItem}>
     */
    @GetMapping("/getListFiles")
    public List<ObjectItem> getListFiles(String bucketName) {
        return minioUtils.getListFiles(bucketName);
    }

    /**
     * 迁移文件夹下所有对象到另一文件夹中
     *
     * @param bucketName 桶名称
     * @return {@link List}<{@link ObjectItem}>
     */
    @GetMapping("/copyObjectByDir")
    public void copyObjectByDir(String bucketName, String newBucketName,String groupName, String newGroupName) {
        minioUtils.copyObjectByDir(bucketName,newBucketName,groupName,newGroupName);
    }


    /**
     * 文件上传返回名称
     *
     * @param file 文件
     * @return {@link MultipartData}
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file,String bucketName) {
        return minioUtils.upload(file,bucketName);
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名文件名
     * @return {@link ResponseEntity}<{@link byte[]}>
     */
    @GetMapping("/download")
    public ResponseEntity<byte[]> download(@RequestParam("fileName") String fileName,String bucketName) {
        return minioUtils.download(fileName,bucketName);
    }

    /**
     * 获取文件预览url
     *
     * @param fileName   文件名文件名
     * @param bucketName 桶名称
     * @return {@link String}
     */
    @GetMapping("/getPresignedUrl")
    public String getPresignedUrl(String fileName,String bucketName){
        return minioUtils.getPresignedUrl(fileName,bucketName);
    }


    /**
     * 删除一个文件夹下所有文件
     *
     * @param groupName  组名称
     * @param bucketName 桶名称
     */
    @DeleteMapping("/removeObjectsByDir")
    public void removeObjectsByDir(String bucketName, String groupName) {
        minioUtils.removeObjectsByDir(bucketName,groupName);
    }

    /**
     * 删除一个文件夹下所有文件
     *
     * @param bucketName 桶名称
     * @param objects    对象
     * @return {@link Boolean}
     */
    @DeleteMapping("/removeObjects")
    public Boolean removeObjects(String bucketName, @RequestParam List<String> objects) {
        return minioUtils.removeObjects(bucketName, objects);
    }

}

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593223浏览

随机文章
SpringCloud—Hystrix(五)请求合并
5年前
Spring—IOC容器(构建)
3年前
Java面向对象(工厂代理)
5年前
SpringBoot—整合SpringSecurity(基于数据库的认证)
5年前
ElasticSearch—索引基本操作(五)
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1926 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593223 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付