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);
}
源码地址: