对象存储服务(Object Storage Service,简称 OSS),以 HTTP RESTful API 的形式对外提供服务,提供的海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。本文介绍基于基于MinIO的文件存储实现。
版本:
- JDK 11
- Spring boot 2.6.9
- io.minio 8.4.1
配置类
@Data
@ConfigurationProperties("alkaid.oss.minio")
public class MinioProperties {
/** 服务器参数 */
private Server server = new Server();
/** 连接参数 */
private Connection connection = new Connection();
/** 请求方法 */
private Method method = new Method();
@Data
public static class Method {
/**
* 单位:秒
*/
private Duration postExpiry = Duration.ofDays(7);
/**
* 单位:秒
*/
private Duration getExpiry = Duration.ofDays(7);
/**
* 单位:秒
*/
private Duration deleteExpiry = Duration.ofDays(7);
}
@Data
public static class Connection {
/**
* Define the connect timeout for the Minio Client.
*/
private Duration connectTimeout = Duration.ofSeconds(10);
/**
* Define the write timeout for the Minio Client.
*/
private Duration writeTimeout = Duration.ofSeconds(60);
/**
* Define the read timeout for the Minio Client.
*/
private Duration readTimeout = Duration.ofSeconds(10);
}
@Data
public static class Server {
/**
* URL for Minio instance. Can include the HTTP scheme. Must include the port.
* If the port is not provided, then the port of the HTTP is taken.
*/
private String endpoint;
private int port;
/**
* Access key (login) on Minio instance
*/
private String accessKey;
/**
* Secret key (password) on Minio instance
*/
private String secretKey;
/**
* If the scheme is not provided in {@code url} property, define if the
* connection is done via HTTP or HTTPS.
*/
private boolean secure = false;
}
}
核心服务
-
GetOptService
/**
* get操作
*
* @author Lucky Yang
* @since 0.0.1
*/
@Slf4j
@Service
public class GetOptService {
@Autowired
private MinioClient client;
@Autowired
private MinioProperties properties;
/**
* 获取对象流
*
* @param bucket
* @param objectId
* @param consumer
* @return
*/
public InputStream getObject(final String bucket, final String objectId, final Consumer<GetObjectArgs.Builder> consumer) {
final String minioId = MinioUtils.minioId(objectId);
try {
final GetObjectArgs.Builder builder = GetObjectArgs.builder()
.bucket(bucket)
.object(minioId);
if (consumer != null) {
consumer.accept(builder);
}
final GetObjectArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("GetObjectArgs={}", MinioArgsUtils.printGetObjectArgs(args));
}
return client.getObject(builder.build());
} catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
}
/**
* 获取对象流
*
* @param bucket
* @param objectId
* @return
*/
public InputStream getObject(final String bucket, final String objectId) {
return getObject(bucket, objectId, null);
}
/**
* 获取对象预签名URL
*
* @param bucket
* @param objectId
* @param method
* @param consumer
* @return
*/
public String getPresignedObjectUrl(final String bucket, final String objectId, final Method method,
final Consumer<GetPresignedObjectUrlArgs.Builder> consumer) {
final String minioId = MinioUtils.minioId(objectId);
try {
final GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
.bucket(bucket)
.object(minioId)
.method(method);
/** 使用属性配置数据 */
switch (method) {
case GET:
builder.expiry((int) properties.getMethod().getGetExpiry().toSeconds());
break;
case POST:
builder.expiry((int) properties.getMethod().getPostExpiry().toSeconds());
break;
case DELETE:
builder.expiry((int) properties.getMethod().getDeleteExpiry().toSeconds());
break;
default:
break;
}
if (consumer != null) {
consumer.accept(builder);
}
final GetPresignedObjectUrlArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("GetPresignedObjectUrlArgs={}", MinioArgsUtils.printGetPresignedObjectUrlArgs(args));
}
return client.getPresignedObjectUrl(args);
} catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
}
/**
* 获取对象预签名URL
*
* @param bucket
* @param objectId
* @param method 请求方法,包括:POST, GET, PUT, DELETE
* @return
*/
public String getPresignedObjectUrl(final String bucket, final String objectId, final Method method) {
return getPresignedObjectUrl(bucket, objectId, method, null);
}
}
-
ListOptsService
/**
* list操作
*
* @author Lucky Yang
* @since 0.0.1
*/
@Slf4j
@Service
public class ListOptsService {
@Autowired
private MinioClient client;
/**
* 获取对象信息
*
* @param bucket
* @param consumer
* @return
*/
public List<Item> listObjects(final String bucket, final Consumer<ListObjectsArgs.Builder> consumer) {
final ListObjectsArgs.Builder builder = ListObjectsArgs.builder()
.bucket(bucket)
.recursive(false);
if (consumer != null) {
consumer.accept(builder);
}
final ListObjectsArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("ListObjectsArgs={}", MinioArgsUtils.printListObjectsArgs(args));
}
return convert(client.listObjects(args));
}
/**
* 转换对象
*
* @param items
* @return
*/
private List<Item> convert(Iterable<Result<Item>> items) {
return StreamSupport
.stream(items.spliterator(), true)
.map(itemResult -> {
try {
return itemResult.get();
} catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
})
.collect(Collectors.toList());
}
}
-
PutOptService
/**
* put操作
*
* @author Lucky Yang
* @since 0.0.1
*/
@Slf4j
@Service
public class PutOptService {
@Autowired
private MinioClient client;
/**
* 上传一个文件流
*
* @param bucket
* @param objectId
* @param file 文件流
* @param contentType
* @param consumer
*/
public void putObject(final MinioUserMetadata metadata, final InputStream file, final Consumer<PutObjectArgs.Builder> consumer) {
final String minioId = MinioUtils.minioId(metadata.getObjectId());
metadata.setMinioId(minioId);
try {
final PutObjectArgs.Builder builder = PutObjectArgs.builder()
.bucket(metadata.getBucket())
.object(metadata.getMinioId())
.userMetadata(metadata.toMap())
.stream(file, file.available(), -1)
.contentType(metadata.getContentType());
if (consumer != null) {
consumer.accept(builder);
}
final PutObjectArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("PutObjectArgs={}", MinioArgsUtils.printPutObjectArgs(args));
}
client.putObject(args);
} catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
}
/**
* 上传一个文件流
* @param metadata
* @param file
*/
public void putObject(final MinioUserMetadata metadata, final InputStream file) {
this.putObject(metadata, file, null);
}
}
避免将所有文件存储到根目录下,程序自动生成两级目录,参考MinioUtils.minioId方法
-
RemoveOptService
/**
* remove操作
*
* @author Lucky Yang
* @since 0.0.1
*/
@Slf4j
@Service
public class RemoveOptService {
@Autowired
private MinioClient client;
/**
* 删除对象
*
* @param bucket
* @param objectId
* @param consumer
*/
public void removeObject(final String bucket, final String objectId, final Consumer<RemoveObjectArgs.Builder> consumer) {
final String minioId = MinioUtils.minioId(objectId);
try {
final RemoveObjectArgs.Builder builder = RemoveObjectArgs.builder()
.bucket(bucket)
.object(minioId);
if (consumer != null) {
consumer.accept(builder);
}
final RemoveObjectArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("RemoveObjectArgs={}", MinioArgsUtils.printRemoveObjectArgs(args));
}
client.removeObject(args);
}catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
}
/**
* 删除对象
*
* @param bucket
* @param objectId
*/
public void removeObject(final String bucket, final String objectId) {
removeObject(bucket, objectId, null);
}
}
-
StatOptService
/**
* stat操作
*
* @author Lucky Yang
* @since 0.0.1
*/
@Slf4j
@Service
public class StatOptService {
@Autowired
private MinioClient client;
/**
* 对象是否存在
*
* @param bucket
* @param objectId
* @return
*/
public boolean exist(final String bucket, final String objectId) {
boolean found = false;
try {
StatObjectResponse response = statObject(bucket, objectId);
found = MinioUtils.minioId(objectId).equals(response.object());
}catch(MinioOssException e) {
if (! (e instanceof NoSuchObjectException || e instanceof NoSuchKeyException)) {
throw e;
}
}
return found;
}
/**
* 获取单个对象元数据
*
* @param bucket
* @param objectId
* @param consumer
* @return
*/
public MinioUserMetadata getUserMetadata(final String bucket, final String objectId,
final Consumer<StatObjectArgs.Builder> consumer) {
final StatObjectResponse statObjectResponse = this.statObject(bucket, objectId, consumer);
return MinioUserMetadata.of(statObjectResponse.userMetadata());
}
/**
* 获取单个对象元数据
*
* @param bucket
* @param objectId
* @return
*/
public MinioUserMetadata getUserMetadata(final String bucket, final String objectId) {
final StatObjectResponse statObjectResponse = this.statObject(bucket, objectId);
return MinioUserMetadata.of(statObjectResponse.userMetadata());
}
/**
* 获取多个对象元数据
*
* @param bucket
* @param objectIds
* @return
*/
public List<MinioUserMetadata> getUserMetadata(final String bucket, final Iterable<String> objectIds) {
return StreamSupport.stream(objectIds.spliterator(), false)
.map(objectId -> getUserMetadata(bucket, objectId))
.collect(Collectors.toList());
}
/**
* 获取单个对象状态
*
* @param bucket
* @param objectId
* @param consumer
* @return
*/
public StatObjectResponse statObject(final String bucket, final String objectId,
final Consumer<StatObjectArgs.Builder> consumer) {
final String minioId = MinioUtils.minioId(objectId);
try {
final StatObjectArgs.Builder builder = StatObjectArgs.builder()
.bucket(bucket)
.object(minioId);
if (consumer != null) {
consumer.accept(builder);
}
final StatObjectArgs args = builder.build();
if (log.isDebugEnabled()) {
log.debug("StatObjectArgs={}", MinioArgsUtils.printStatObjectArgs(args));
}
return client.statObject(args);
} catch (Exception e) {
throw MinioExceptionUtil.wapper(e);
}
}
/**
* 获取单个对象状态
*
* @param bucket
* @param objectId
* @return
*/
public StatObjectResponse statObject(final String bucket, final String objectId) {
return statObject(bucket, objectId, null);
}
/**
* 获取多个对象状态
*
* @param bucket
* @param objectIds
* @return
*/
public Map<String, StatObjectResponse> statObject(final String bucket,
final Iterable<String> objectIds) {
return StreamSupport.stream(objectIds.spliterator(), false)
.map(objectId -> new HashMap.SimpleEntry<>(objectId, statObject(bucket, objectId)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}
以上是核心代码,完整的项目可以在Gitee找到,欢迎下载。