MinIO中文文档:http://docs.minio.org.cn/docs/

pom.xml

<!-- minio 对象存储 -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.2</version>
</dependency>

application.yml

minio:
  url:  # S3服务的域名,IPv4或IPv6地址
  accessKey:  # 在S3服务中访问您帐户的密钥(又名用户ID)
  secretKey:  # 您在S3服务中的账户密码
  endpoint:
    enable:  true # 是否启用

MinioConfig.java

package com.yuyouyang.notebook.oss.depot.minio.config;

import com.yuyouyang.notebook.oss.depot.minio.MinioTemplate;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <p>
 * minio 配置
 * </p>
 *
 * @author walming
 * @date 2021-07-03 12:54
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    private String url;
    private String accessKey;
    private String secretKey;

    private Endpoint endpoint;

    @Bean
    @ConditionalOnProperty(name = "minio.url")
    MinioTemplate template() {
        return new MinioTemplate(url, accessKey, secretKey);
    }

    @Data
    public static class Endpoint {
        private Boolean enable;
    }

}

BucketInfo.java

package com.yuyouyang.notebook.oss.depot.minio.vo;

import io.minio.messages.Bucket;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * <p>
 * Bucket 信息
 * </p>
 *
 * @author walming
 * @date 2021-03-11 12:50
 */
@Data
public class BucketInfo {

    private String bucketName;
    private LocalDateTime createDate;

    public BucketInfo(Bucket bucket) {
        this.bucketName = bucket.name();
        this.createDate = bucket.creationDate().toLocalDateTime();
    }

}

MinioItem.java

package com.yuyouyang.notebook.oss.depot.minio.vo;

import io.minio.messages.Item;
import io.minio.messages.Owner;
import lombok.Data;

import java.time.ZonedDateTime;

/**
 * <p>
 * The object info in bucket of minio
 * </p>
 *
 * @author walming
 * @date 2021-07-03 13:08
 */
@Data
public class MinioItem {

    private String objectName;
    private ZonedDateTime lastModified;
    private String etag;
    private Long size;
    private String storageClass;
    private Owner owner;
    private String type;

    public MinioItem(Item item) {
        this.objectName = item.objectName();
        this.lastModified = item.lastModified();
        this.etag = item.etag();
        this.size = item.size();
        this.storageClass = item.storageClass();
        this.owner = item.owner();
        this.type = item.isDir() ? "directory" : "file";
    }

}

MinioObject.java

package com.yuyouyang.notebook.oss.depot.minio.vo;

import io.minio.StatObjectResponse;
import lombok.Data;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * metadata of object in minio
 * </p>
 *
 * @author walming
 * @date 2021-07-03 12:54
 */
@Data
public class MinioObject {

    private String bucketName;
    private String name;
    private ZonedDateTime createdTime;
    private Long length;
    private String etag;
    private String contentType;
    private Map<String, List<String>> httpHeaders;

    public MinioObject(StatObjectResponse statObject) {
        this.bucketName = statObject.bucket();
        this.name = statObject.object();
        this.createdTime = statObject.lastModified();
        this.length = statObject.size();
        this.etag = statObject.etag();
        this.contentType = statObject.contentType();
        this.httpHeaders = statObject.headers().toMultimap();
    }

}

MinioTemplate.java

package com.yuyouyang.notebook.oss.depot.minio;

import com.yuyouyang.notebook.oss.depot.minio.vo.BucketInfo;
import com.yuyouyang.notebook.oss.depot.minio.vo.MinioItem;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Minio模版方法
 *
 * @author walming
 * @date 2020-07-28 16:23
 */
@Slf4j
public class MinioTemplate {

    /** Minio客户端 */
    private static MinioClient minioClient;

    /**
     * 使用给定的端点、访问密钥和密钥创建MinIO客户端对象
     *
     * @param endpoint  S3服务的域名,IPv4或IPv6地址
     * @param accessKey 在S3服务中访问您帐户的密钥(又名用户ID)
     * @param secretKey 您在S3服务中的账户密码
     */
    public MinioTemplate(String endpoint, String accessKey, String secretKey) {
        try {
            if (null == minioClient) {
                log.info("minioClient create start");
                minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
                log.info("minioClient create end");
            }
        } catch (Exception e) {
            log.error("连接MinIO服务器异常:{}", e.getMessage());
        }
    }

    /**
     * 创建具有默认区域的bucket
     *
     * @param bucketName bucket的名称
     * @return this
     */
    @SneakyThrows
    public MinioTemplate createBucket(String bucketName) {
        boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!exists) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
        return this;
    }

    /**
     * 列出所有bucket的信息
     *
     * @return List<Bucket>
     */
    @SneakyThrows
    public List<BucketInfo> getAllBuckets() {
        List<BucketInfo> list = new ArrayList<>();
        minioClient.listBuckets().forEach(bucket -> list.add(new BucketInfo(bucket)));
        return list;
    }

    /**
     * 根据bucket的名称获取bucket
     *
     * @param bucketName bucket的名称
     * @return Optional<Bucket>
     */
    @SneakyThrows
    public BucketInfo getBucket(String bucketName) {
        Bucket bucket = minioClient
                .listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Bucket Name not found!"));
        return new BucketInfo(bucket);
    }

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

    /**
     * 递归地列出prefix作为前缀的bucket的对象的信息
     *
     * @param bucketName bucket的名称
     * @param prefix     对象名称前缀开头
     * @param recursive  递归列表,而不是目录结构仿真
     * @return List<MinioItem>
     */
    @SneakyThrows
    public List<MinioItem> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
        List<MinioItem> objectList = new ArrayList<>();
        Iterable<Result<Item>> objIterator = minioClient
                .listObjects(
                        ListObjectsArgs.builder()
                                .bucket(bucketName)
                                .prefix(prefix)
                                .recursive(recursive)
                                .build()
                );
        while (objIterator.iterator().hasNext()) {
            objectList.add(new MinioItem(objIterator.iterator().next().get()));
        }
        return objectList;
    }

    /**
     * 获取对象的预签名URL,以下载其过期数据
     *
     * @param bucketName bucket的名称
     * @param objectName 在bucket中对象的名称
     * @param expires    有效时间(以秒为单位)
     * @return 对象的预签名URL
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, Integer expires) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expires, TimeUnit.SECONDS)
                        .build()
        );
    }

    /**
     * 获取对象的数据,返回的{@link InputStream}必须在使用后关闭以释放网络资源
     *
     * @param bucketName bucket的名称
     * @param objectName 在bucket中对象的名称
     * @return InputStream
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
        );
    }

    /**
     * 将给定流上传为bucket中的对象
     *
     * @param bucketName bucket的名称
     * @param objectName 在bucket中对象的名称
     * @param stream     流包含对象数据
     */
    @SneakyThrows
    public MinioTemplate putObject(String bucketName, String objectName, InputStream stream) {
        return putObject(bucketName, objectName, stream, "application/octet-stream");
    }

    /**
     * 将给定流上传为bucket中的对象
     *
     * @param bucketName  bucket的名称
     * @param objectName  在bucket中对象的名称
     * @param stream      流包含对象数据
     * @param contentType 内容类型
     */
    @SneakyThrows
    public MinioTemplate putObject(String bucketName, String objectName, InputStream stream, String contentType) {
        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(stream, stream.available(), -1)
                        .contentType(contentType)
                        .build()
        );
        return this;
    }

    /**
     * 获取对象的对象信息和元数据
     *
     * @param bucketName bucket的名称
     * @param objectName 在bucket中对象的名称
     * @return ObjectStat 对象信息和对象的元数据
     */
    @Deprecated
    @SneakyThrows
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) {
        return minioClient.statObject(
                StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
        );
    }

    /**
     * 从指定bucket中移除指定对象
     *
     * @param bucketName bucket的名称
     * @param objectName 在bucket中对象的名称
     */
    @SneakyThrows
    public void removeObject(String bucketName, String objectName) {
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
        );
    }

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

}

上传案例

/**
 * 上传文件后返回文件的具体信息
 * 返回临时访问地址,链接默认有效期为一天
 *
 * @param multipartFile 文件流
 * @param ossParam 参数
 * @return 文件信息
 */
@Override
@SneakyThrows
@PostMapping(value = "/upload", produces = MediaType.ALL_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "上传文件后返回文件的具体信息", notes = "返回临时访问地址,链接默认有效期为一天")
@ApiImplicitParam(name = "multipartFile", value = "文件流", dataType = "__file", required = true)
public OssFile upload(@RequestPart("multipartFile") MultipartFile multipartFile, OssParam ossParam) {
    OssFile ossFile = BeanUtils.copyTo(ossParam, OssFile.class);
    // 根据原文件补充文件相关信息
    OssFileHandler.processing(ossFile, multipartFile);
    String fileUrl = template
            // 检查存储桶是否存在,若不存在则创建
            .createBucket(ossFile.getBucketName())
            // 上传文件到到对应存储桶
            .putObject(ossFile.getBucketName(), ossFile.getFilename(), multipartFile.getInputStream(), ossFile.getContentType())
            // 获取对象的预签名URL
            .getObjectUrl(ossFile.getBucketName(), ossFile.getFilename(), OssConstants.N_EXPIRES);
    // 文件组不为空时,判断文件组是否存在,不存在则新建
    OssFileSet ossFileSet = new OssFileSet();
    if (StringUtils.isNotBlank(ossParam.getFileSetId()) || StringUtils.isNotBlank(ossParam.getFileSetName())) {
        ossFileSet = fileSetService.getById(ossParam.getFileSetId());
        if (ossFileSet == null) {
            ossFileSet = BeanUtils.copyTo(ossParam, OssFileSet.class);
            fileSetService.saveOrUpdate(ossFileSet);
        }
    }
    fileService.save(ossFile.setFileUrl(fileUrl));
    return ossFile.setFileSet(ossFileSet);
}

源码地址:

https://gitee.com/walming/yuyouyang-notebook/tree/master/yuyouyang-notebook-service/yuyouyang-notebook-service-oss