MinIO分布式储存简介以及整合SpringBoot

MinIO介绍以及springBoot整合

什么是MinIO?

Minio 是个基于 Golang 编写的开源对象存储套件,基于Apache License v2.0开源协议,虽然轻量,却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用,例如 NodeJS、Redis、MySQL等。

应用场景

MinIO 的应用场景除了可以作为私有云的对象存储服务来使用,也可以作为云对象存储的网关层,无缝对接 Amazon S3 或者 MicroSoft Azure

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mlnZQOQq-1664501166499)(https://mmbiz.qpic.cn/mmbiz/19cc2hfD2rDibbGkYt5AQq0q6rpU6JPruoPXYHSzZcotyKqfuCbstAludarRAQcibTH19gVZsRbEdod7cGbiaIApQ/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1
)]

特点

  1. 高性能:作为一款高性能存储,在标准硬件条件下,其读写速率分别可以达到 55Gb/s35Gb/s。并且MinIO 支持一个对象文件可以是任意大小,从几kb到最大5T不等。

  2. 可扩展:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并且支持跨越多个数据中心。

  3. 云原生:容器化、基于K8S的编排、多租户支持。

  4. Amazon S3兼容:使用 Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK 和 AWS CLI 访问Minio服务器。

  5. SDK支持

    1. GO SDK:https://github.com/minio/minio-go
    2. JavaSDK:https://github.com/minio/minio-java
    3. PythonSDK:https://github.com/minio/minio-py
  6. 图形化界面:有操作页面

    扫描二维码关注公众号,回复: 16860910 查看本文章
  7. 支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据。

功能很强大,本文只是抛砖引玉,有兴趣的朋友自己去探索吧~

安装MinIO

安装非常简单,笔者这里使用docker安装,步骤如下:

获取镜像

执行命令如下:

docker pull minio/minio

启动镜像

执行命令如下:

 docker run -p 9000:9000 -p 9001:9001 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=admin" -v /home/data:/data -v /home/config:/root/.minio minio/minio server --console-address ":9000" --address ":9001" /data

命令解释如下:

  • -p9000是图形界面的端口,9001是API的端口,在使用SDK连接需要用到
  • MINIO_ACCESS_KEY:指定图形界面的用户名
  • MINIO_SECRET_KEY:指定图形界面的密码

按照上述两个步骤启动成功即可。

图形界面操作

安装成功后直接访问地址:http:/ip:9000/login,如下:

图片

输入用户名和密码登录成功后,如下:

图片

菜单很多,这里就不再详细介绍了,笔者这里直接在Buckets菜单中创建一个桶为test,如下图:

图片

并且设置这个桶的隐私规则为public,如下:

图片

MinIO到此已经安装设置成功了

Spring Boot 整合MinIO 上传文件

虽然MinIO在图形界面提供了手动上传的操作,但是也可以通过SDK的方式去上传,下面介绍一下Spring Boot 整合MinIO上传文件。

获取accessKey和secretKey

这里的accessKeysecretKey并不是图形界面登录名和密码,获取很简单,直接在图形界面中操作,如下图:

图片

图片

添加依赖

添加MinIO的依赖,如下:

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

添加配置

添加MInIO相关的配置,如下:

@Configuration
public class MinioConfig {
    
    

    @Bean
    public MinioClient minioClient()  {
    
    
        return MinioClient.builder()
                .endpoint("http://xxxxxxxxxx:9090")
                .credentials("你自己的key", "你自己的key")//accessKey和secretKey
                .build();
    }

}

新建上传文件接口

笔者这里定义了一个上传文件接口,如下:

    @WebLog
    @PostMapping("/upload")
    @SneakyThrows
    public ApiResponse<Object> upload(MultipartFile file, String bucketName){
    
    
        System.out.println(file.toString());
        String fileType = FileTypeUtils.getFileType(file);
        String originalFilename = file.getOriginalFilename();
        MinIOUtil.putObject(bucketName,file,"test/"+originalFilename,fileType);
        return new ApiResponse<>();
    }

测试

上述4个步骤已经整合完成了,下面直接调用接口上传一张图片试一下,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EnZpvzur-1664501166507)(MinIO%E4%BB%8B%E7%BB%8D%E4%BB%A5%E5%8F%8Aspringboot%E6%95%B4%E5%90%88.assets/image-20220930092521093.png)]

接口返回的URL就是文件的访问地址,直接输入浏览器访问即可。

在MInIO中也可以看到存储的文件,如下图:

图片

如果你需要分享给别人,也可以手动分享,有效期是7天,一旦过了这个有效期将会失效,如下:

图片

总结

MInIO虽然是个开源项目,但是功能非常强大,小型项目中完全可以用它实现对象存储,也可以使用MinIO搭建一个免费的图床

附录

MinIO工具类

package com.example.mallgoods.util;


import com.alibaba.fastjson.JSONObject;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;


/**
 * 功能描述 文件服务器工具类
 *
 * @author:
 * @date:
 */
@Component
@Slf4j
public class MinIOUtil {
    
    


    private static MinioClient minioClient;

    public MinIOUtil(MinioClient minioClient) {
    
    
        MinIOUtil.minioClient=minioClient;
    }

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     */
    @SneakyThrows
    public static void createBucket(String bucketName) {
    
    
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
    
    
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取存储桶策略
     *
     * @param bucketName 存储桶名称
     * @return json
     */
    @SneakyThrows
    private JSONObject getBucketPolicy(String bucketName) {
    
    
        String bucketPolicy = minioClient
                .getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
        return JSONObject.parseObject(bucketPolicy);
    }

    /**
     * 获取全部bucket
     * <p>
     * https://docs.minio.io/cn/java-client-api-reference.html#listBuckets
     */
    @SneakyThrows
    public static List<Bucket> getAllBuckets() {
    
    
        return minioClient.listBuckets();
    }

    /**
     * 根据bucketName获取信息
     *
     * @param bucketName bucket名称
     */
    @SneakyThrows
    public static Optional<Bucket> getBucket(String bucketName) {
    
    
        return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 根据bucketName删除信息
     *
     * @param bucketName bucket名称
     */
    @SneakyThrows
    public static void removeBucket(String bucketName) {
    
    
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

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

    /**
     * 判断文件是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 对象
     * @return true:存在
     */
    @SneakyThrows
    public static boolean doesObjectExist(String bucketName, String objectName) {
    
    
        boolean exist = true;
        try {
    
    
            minioClient
                    .statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
    
    
            exist = false;
        }
        return exist;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件夹名称(去掉/)
     * @return true:存在
     */
    @SneakyThrows
    public static boolean doesFolderExist(String bucketName, String objectName) {
    
    
        boolean exist = false;
        try {
    
    
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
            for (Result<Item> result : results) {
    
    
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
    
    
                    exist = true;
                }
            }
        } catch (Exception e) {
    
    
            exist = false;
        }
        return exist;
    }

    /**
     * 根据文件前置查询文件
     *
     * @param bucketName bucket名称
     * @param prefix 前缀
     * @param recursive 是否递归查询
     * @return MinioItem 列表
     */
    @SneakyThrows
    public static List<Item> getAllObjectsByPrefix(String bucketName, String prefix,
                                                   boolean recursive) {
    
    
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
    
    
            for (Result<Item> o : objectsIterator) {
    
    
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }

    /**
     * 获取文件流(下载文件)
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     */
    @SneakyThrows
    public static InputStream getObject(String bucketName, String objectName) {
    
    
        return minioClient
                .getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 断点下载
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param offset 起始字节的位置
     * @param length 要读取的长度
     * @return 流
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, long offset, long length) {
    
    
        return minioClient.getObject(
                GetObjectArgs
                        .builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .offset(offset)
                        .length(length)
                        .build());
    }

    /**
     * 获取路径下文件列表
     *
     * @param bucketName bucket名称
     * @param prefix 文件名称
     * @param recursive 是否递归查找,如果是false,就模拟文件夹结构查找
     * @return 二进制流
     */
    @SneakyThrows
    public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,
                                                     boolean recursive) {
    
    
        return minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
    }

    /**
     * 通过MultipartFile,上传文件
     *
     * @param bucketName 存储桶
     * @param file 文件
     * @param objectName 对象名
     */
    @SneakyThrows
    public static ObjectWriteResponse putObject(String bucketName, MultipartFile file,
                                                String objectName, String contentType) {
    
    
        InputStream inputStream = file.getInputStream();
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType)
                        .stream(
                                inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 上传本地文件
     *
     * @param bucketName 存储桶
     * @param objectName 对象名称
     * @param fileName 本地文件路径
     */
    @SneakyThrows
    public static ObjectWriteResponse putObject(String bucketName, String objectName,
                                                String fileName) {
    
    
        return minioClient.uploadObject(UploadObjectArgs
                        .builder()
                        .bucket(bucketName)
                        .object(objectName)
                .filename(fileName)
                .build());
    }

    /**
     * 通过流上传文件
     *
     * @param bucketName 存储桶
     * @param objectName 文件对象
     * @param inputStream 文件流
     */
    @SneakyThrows
    public static ObjectWriteResponse putObject(String bucketName, String objectName,
                                                InputStream inputStream) {
    
    
        return minioClient.putObject(
                PutObjectArgs
                        .builder()
                        .bucket(bucketName)
                        .object(objectName).stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    /**
     * 创建文件夹或目录
     *
     * @param bucketName 存储桶
     * @param objectName 目录路径
     */
    @SneakyThrows
    public static ObjectWriteResponse putDirObject(String bucketName, String objectName) {
    
    
        return minioClient.putObject(
                PutObjectArgs
                        .builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(new ByteArrayInputStream(new byte[]{
    
    }), 0, -1)
                        .build());
    }

    /**
     * 获取文件信息, 如果抛出异常则说明文件不存在
     *  @param bucketName bucket名称
     * @param objectName 文件名称
     * @return
     */
    @SneakyThrows
    public static StatObjectResponse statObject(String bucketName, String objectName) {
    
    
        return minioClient.statObject(StatObjectArgs
                .builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * 拷贝文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param srcBucketName 目标bucket名称
     * @param srcObjectName 目标文件名称
     */
    @SneakyThrows
    public static ObjectWriteResponse copyObject(String bucketName, String objectName,
                                                 String srcBucketName, String srcObjectName) {
    
    
        return minioClient.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(bucketName).object(objectName).build())
                        .bucket(srcBucketName)
                        .object(srcObjectName)
                        .build());
    }

    /**
     * 删除文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     */
    @SneakyThrows
    public static void removeObject(String bucketName, String objectName) {
    
    
        minioClient.removeObject(RemoveObjectArgs
                                .builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .build());
    }

    /**
     * 批量删除文件
     *
     * @param bucketName bucket
     * @param keys 需要删除的文件列表
     * @return
     */
    @SneakyThrows
    public static void removeObjects(String bucketName, List<String> keys) {
    
    
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
    
    
            objects.add(new DeleteObject(s));
            try {
    
    
                removeObject(bucketName, s);
            } catch (Exception e) {
    
    
                log.error("批量删除失败!");
            }
        });
    }

    /**
     * 获取文件外链
     * @param bucketName 存储桶
     * @param objectName 文件名
     * @param expires 过期时间 <=7 秒 (外链有效时间(单位:秒))
     * @return url
     *
     */
    @SneakyThrows
    public static String getPresignedObjectUrl(String bucketName, String objectName, Integer expires){
    
    
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs
                .builder()
                .expiry(expires)
                .bucket(bucketName)
                .object(objectName)
                .build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /**
     * 获得文件外链
     * @param bucketName
     * @param objectName
     * @return url
     *
     */
    @SneakyThrows
    public static String getPresignedObjectUrl(String bucketName, String objectName){
    
    
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .method(Method.GET).build();
        return minioClient.getPresignedObjectUrl(args);
    }


    /**
     * 将URLDecoder编码转成UTF8
     *
     * @param str
     */
    @SneakyThrows
    public static String getUtf8ByURLDecoder(String str){
    
    
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        return URLDecoder.decode(url, "UTF-8");
    }


    /**
     * 将MultipartFile转换为File
     * @param multiFile
     * @return
     */
    @SneakyThrows
    public static File multipartFileToFile(MultipartFile multiFile) {
    
    
        // 获取文件名
        String fileName = multiFile.getOriginalFilename();
        // 获取文件后缀
        assert fileName != null;
        String prefix = fileName.substring(fileName.lastIndexOf("."));
        // 若须要防止生成的临时文件重复,能够在文件名后添加随机码
            File file = File.createTempFile(fileName, prefix);
            multiFile.transferTo(file);
            return file;
    }

}

猜你喜欢

转载自blog.csdn.net/asdf12388999/article/details/127117812