生成Minio工具starter

引言

  • 我们之前编写了一个控制Minio的工具模块,主要实现了以下几个功能:
  1. 将链接配置写在了配置文件中,并编写一个类读取配置文件中的信息,并注册为一个Bean对象
  2. 将MinioClient初始化后注册为一个Bean
  3. 将Minio原生的方法进行封装

接下来,我们将尝试将这些功能剥离出来,并编写一个starter,以方便我们之后随时通过Maven引入这个starter,实现对Minio的控制

↓ 编写好的starter放在了下面的Git仓库中,虽然只是一个学习和测试的工具,可能很多地方还不够完善,但是欢迎大家访问

minio-mosfield-springboot-starter: Minio对象存储工具starter (gitee.com)

1.1. 结构及功能

  • 与之前直接编写模块代码相比,编写成starter有什么变化?

starter依赖于SpringBoot的自动装配,SpringBoot项目在启动的时候,通过扫描各个依赖包中 META-INF/spring目录下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,获取需要被加入Spring容器中的配置类。 所以,我们需要把我们的配置类的全路径名写在这个文件中。

↓ 关于自动装配的原理,可以参考这篇博客

小默的博客 - SpringBoot框架原理 之 自动装配 (xiaomo.link)

目录结构

  link                  
    └─ xiaomo
      └─ minio
        └─ MinioAutoConfiguration
        └─ MinioHelper
        └─ MinioProperties
        └─ MinioUtils
  META-INF
    └─ spring
      └─ org.springframework.boot.autoconfigure.AutoConfiguration.imports

功能

  1. 操作桶相关(桶是否存在,新建桶,删除桶,获取所有桶,获取桶内对象信息)
  2. 对象操作:
    1. 上传到指定的桶的指定位置
    2. 下载指定桶内的指定文件
    3. 删除指定文件
    4. 获取文件的url
    5. 获取文件的二进制数组
  3. 工具
    1. 对图片附件进行压缩,可以按最大尺寸或按质量压缩

1.2. 使用说明

下面的内容为使用此starter的说明

1.2.1. 依赖

拉取到本starter项目后,需要在maven中编译本项目,并使用maven安装至本地仓库

安装后,使用本starter时需要引入如下依赖

<dependency>
    <groupId>link.xiaomo</groupId>
    <artifactId>minio-mosfield-springboot-starter</artifactId>
    <version>0.1.1</version>
</dependency>

1.2.2. 填写配置文件

  • 引入本starter后,在引入的项目中的配置文件里配置如下信息
  • 此处以application.yml的格式为例
# minio相关配置      
minio:
  endpoint: http://IP:端口
  bucketName: 默认桶名称
  accessKey: 实际accessKey
  secretKey: 实际secretKeyt

# 选填:上传文件大小限制
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

1.3. starter功能方法

1.3.1. 配置类MinioAutoConfiguration

这个类就是我们需要再org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中注册的类,会在Springboot启动的时候扫描到。

这个类主要实现了三个功能:

  1. 通过@EnableConfigurationProperties注解将MinioProperties类注册为Bean,这个Bean对象用于从配置文件中读取Minio配置信息
  2. 通过@Bean注解将Minio对象初始化并注册为Bean,它用于调用Minio的原生方法
  3. 通过@Bean注解将我们的业务类MinioHelper初始化,注入Minio对象和MinioProperties对象,也将其注册为Bean,方便我们随时使用这个业务类。
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioAutoConfiguration {

    @Autowired
    private MinioProperties minioProperties;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
    }

    @Bean
    public MinioHelper minioHelper(@Autowired MinioClient minioClient) {
        return new MinioHelper(minioClient, minioProperties);
    }
}

1.3.2. MinioProperties

在这个类中,我们通过@ConfigurationProperties注解,从配置文件中读取 minio下的配置信息

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucketName;

    public String getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }
}

1.3.3. MinioHelper类的方法

此starter的功能,主要是通过MinioHelper类中的方法实现,我们在这个类中对Minio的原生方法进行了封装,并对很多方法进行了重载,以方便我们在不同的情况下使用。具体的方法如下:

import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * Minio业务实现类
 *
 * @Author Mosfield
 */
public class MinioHelper {
    private final MinioClient minioClient;
    private final MinioProperties minioProperties;

    public MinioHelper(MinioClient minioClient, MinioProperties minioProperties) {
        this.minioClient = minioClient;
        this.minioProperties = minioProperties;
    }

    /**
     * 查看bucket是否存在
     *
     * @param bucketName
     * @return
     */
    public Boolean bucketExists(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName
     */
    public void makeBucket(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName
     */
    public void removeBucket(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 获取全部bucket
     *
     * @return
     */
    public List<Bucket> listBuckets() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioClient.listBuckets();
    }

    /**
     * 递归查看桶内的所有文件信息
     *
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return this.listObjects(null, null, true, null);
    }

    /**
     * 列出某个存储桶中的所有对象。
     *
     * @param bucketName  存储桶名称
     * @param prefix      对象名称的前缀
     * @param recursive   是否递归查找,如果是false,就模拟文件夹结构查找
     * @param useVersion1 如果是true, 使用版本1 REST API
     * @return
     */
    public List<Item> listObjects(String bucketName, String prefix, Boolean recursive, Boolean useVersion1) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (bucketName == null) {
            bucketName = minioProperties.getBucketName();
        }
        if (recursive == null) {
            recursive = false;
        }
        if (useVersion1 == null) {
            useVersion1 = false;
        }

        ListObjectsArgs args = ListObjectsArgs.builder().
                bucket(bucketName)
                .prefix(prefix)
                .recursive(recursive)
                .useApiVersion1(useVersion1)
                .build();
        Iterable<Result<Item>> results = minioClient.listObjects(args);
        List<Item> items = new ArrayList<>();
        for (Result<Item> result : results) {
            items.add(result.get());
        }
        return items;
    }

    /**
     * 删除默认桶内的指定文件
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public void remove(String fileName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        this.remove(fileName, null);
    }

    /**
     * 删除指定桶内的指定文件
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public void remove(String fileName, String bucket) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (bucket == null) {
            bucket = minioProperties.getBucketName();
        }
        RemoveObjectArgs args = RemoveObjectArgs.builder()
                .bucket(bucket)
                .object(fileName)
                .build();
        minioClient.removeObject(args);
    }

    /**
     * 把文件上传到默认桶,名字为原名字
     *
     * @param file
     * @return
     */
    public String updateFileObject(MultipartFile file) {
        return this.updateFileObject(file, null, null);
    }

    /**
     * 把文件上传到默认桶
     *
     * @param file
     * @param name 路径和新名字
     * @return
     */
    public String updateFileObject(MultipartFile file, String name) {
        return this.updateFileObject(file, name, null);
    }

    /**
     * 把文件上传到指定桶的指定路径下,桶名为空则上传到默认桶,路径为空则上传到桶的根目录
     *
     * @param file
     * @param name
     * @param bucket
     * @return
     */
    public String updateFileObject(MultipartFile file, String name, String bucket) {
        // 桶默认值为配置文件中的默认桶
        if (bucket == null || "".equals(bucket)) {
            bucket = minioProperties.getBucketName();
        }

        // 名字默认为文件的原名
        if (name == null || "".equals(name)) {
            name = file.getOriginalFilename();
        }

        try {
            //构建要上传文件对象的参数,如文件名、类型等
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().object(name).bucket(bucket).contentType(file.getContentType()).stream(file.getInputStream(), file.getSize(), -1).build();
            //上传文件
            minioClient.putObject(putObjectArgs);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }

    /**
     * 从默认的桶里下载文件
     *
     * @param fileName
     * @return
     */
    public InputStream downloadFileObject(String fileName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return this.downloadFileObject(fileName, null);
    }

    /**
     * 从指定的桶里下载文件
     *
     * @param fileName
     * @param bucket
     * @return
     */
    public InputStream downloadFileObject(String fileName, String bucket) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (bucket == null || "".equals(bucket)) {
            bucket = minioProperties.getBucketName();
        }

        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket(bucket)
                .object(fileName)
                .build();

        return minioClient.getObject(getObjectArgs);
    }

    /**
     * 获取文件的url
     *
     * @param fileName
     * @return
     */
    public String getPresignedObjectUrl(String fileName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return this.getPresignedObjectUrl(fileName, null);
    }

    /**
     * 从指定的桶中获取文件的url
     *
     * @param fileName
     * @return
     */
    public String getPresignedObjectUrl(String fileName, String bucket) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (bucket == null || "".equals(bucket)) {
            bucket = minioProperties.getBucketName();
        }
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
                .bucket(bucket)
                .object(fileName)
                .method(Method.GET)
                .build();
        return minioClient.getPresignedObjectUrl(build);
    }

    /**
     * 获取公开桶里文件的url
     *
     * @param name
     * @return
     */
    public String getPublicUrlByName(String name) {
        return this.getPublicUrlByName(name, null);
    }

    /**
     * 获取指定的公开桶里文件的url
     *
     * @param name
     * @return
     */
    public String getPublicUrlByName(String name, String bucket) {
        if (bucket == null || "".equals(bucket)) {
            bucket = minioProperties.getBucketName();
        }
        return minioProperties.getEndpoint() + "/" + bucket + "/" + name;
    }

    /**
     * 根据图片路径和名字,获取默认桶内对应的二进制数组
     *
     * @param name
     * @return
     */
    public byte[] getByteArrByName(String name) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return this.getByteArrByName(name, null);
    }

    /**
     * 根据图片路径和名字,获取指定桶内对应的二进制数组
     *
     * @param name
     * @param bucket
     * @return
     */
    public byte[] getByteArrByName(String name, String bucket) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (bucket == null || "".equals(bucket)) {
            bucket = minioProperties.getBucketName();
        }

        if (name == null || "".equals(name)) {
            return null;
        }
        InputStream inputStream = this.downloadFileObject(name, bucket);
        if (inputStream == null) {
            return null;
        }
        return IOUtils.toByteArray(inputStream);
    }

}

1.3.4.MinioUtils工具类

目前工具类中提供了两个静态的工具方法 分别是 压缩图片获取UUID文件路径名,其中获取文件路径名的方法提供了两种参数形式。

import net.coobird.thumbnailator.Thumbnails;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.mock.web.MockMultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MinioUtils {

    /**
     * 压缩图片
     *
     * @param file      原始文件
     * @param maxWidth  最大宽
     * @param maxHeight 最大高
     * @param quality   质量压缩比,范围是(0,1]
     * @return
     */
    private MultipartFile zipImg(MultipartFile file, Integer maxWidth, Integer maxHeight, Float quality) throws IOException {

        String fileName = file.getName();
        String originalFilename = file.getOriginalFilename();
        String contentType = file.getContentType();

        InputStream is = null;
        // 通过MultipartFile得到InputStream,从而得到BufferedImage
        BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
        // 获取图片的真实长和宽
        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();

        if (maxWidth == null) {
            maxWidth = width;
        }
        if (maxHeight == null) {
            maxHeight = height;
        }

        // 计算图片的压缩比
        float rate = 1f;
        if (width > maxWidth) {
            rate = 1f * maxWidth / width;
        }
        if (height > maxHeight && rate > 1f * maxHeight / height) {
            rate = 1f * maxHeight / height;
        }

        // 质量压缩比
        if(quality == null){
            quality=1f;
        }

        is = file.getInputStream();

        File tempFile = new File("/" + originalFilename);

        Thumbnails.of(is)
                .scale(rate)
                .outputQuality(quality)
                .toFile(tempFile);

        FileInputStream fileInputStream = new FileInputStream(tempFile);
        MockMultipartFile newFile = new MockMultipartFile(fileName, originalFilename, contentType, fileInputStream);

        fileInputStream.close();
        boolean success = tempFile.delete();

        return newFile;
    }
}
如人饮水,冷暖自知。
最后更新于 2023-08-02