mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-03-22 01:57:17 +08:00
update 更新 完善新的S3客户端相关配置
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
package org.dromara.common.oss.s3.client;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.oss.s3.config.S3StorageClientConfig;
|
||||
import org.dromara.common.oss.s3.domain.GetObjectResult;
|
||||
import org.dromara.common.oss.s3.domain.HandleAsyncResult;
|
||||
@@ -45,6 +48,13 @@ public abstract class AbstractS3StorageClientImpl implements S3StorageClient {
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* S3 存储客户端ID
|
||||
*
|
||||
* 用于标识客户端,初始化后不允许更改
|
||||
*/
|
||||
protected final String clientId;
|
||||
|
||||
/**
|
||||
* S3 存储客户端配置。
|
||||
*/
|
||||
@@ -70,15 +80,34 @@ public abstract class AbstractS3StorageClientImpl implements S3StorageClient {
|
||||
*/
|
||||
protected ExecutorService asyncExecutor;
|
||||
|
||||
public AbstractS3StorageClientImpl(S3StorageClientConfig config) {
|
||||
public AbstractS3StorageClientImpl(String clientId, S3StorageClientConfig config) {
|
||||
Assert.notNull(config, () -> S3StorageException.form("S3StorageClientConfig must not be null"));
|
||||
// 如果没有设置存储客户端ID,则随机生成一个
|
||||
this.clientId = StringUtils.isBlank(clientId)? IdUtil.fastSimpleUUID() : clientId;
|
||||
this.config = config;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String clientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S3StorageClientConfig config() {
|
||||
// 仅返回copy副本,防篡改
|
||||
return this.config.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// 如果已经是初始化状态,则直接返回
|
||||
if (initialized.get()) {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -118,12 +147,13 @@ public abstract class AbstractS3StorageClientImpl implements S3StorageClient {
|
||||
|
||||
@Override
|
||||
public boolean verifyConfig(Function<S3StorageClientConfig, Boolean> verifyConfigAction) {
|
||||
return Boolean.TRUE.equals(verifyConfigAction.apply(config.copy()));
|
||||
S3StorageClientConfig config = config();
|
||||
return Boolean.TRUE.equals(verifyConfigAction.apply(config));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyConfig(S3StorageClientConfig verifyConfig) {
|
||||
return verifyConfig(config -> Objects.equals(config, verifyConfig));
|
||||
return verifyConfig((config) -> Objects.equals(config, verifyConfig));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -456,5 +486,7 @@ public abstract class AbstractS3StorageClientImpl implements S3StorageClient {
|
||||
if (asyncExecutor != null) {
|
||||
asyncExecutor.close();
|
||||
}
|
||||
// 重置初始化状态为 false
|
||||
initialized.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import java.util.concurrent.Executors;
|
||||
*/
|
||||
public class DefaultS3StorageClientImpl extends AbstractS3StorageClientImpl {
|
||||
|
||||
public DefaultS3StorageClientImpl(S3StorageClientConfig config) {
|
||||
super(config);
|
||||
public DefaultS3StorageClientImpl(String clientId, S3StorageClientConfig config) {
|
||||
super(clientId, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.common.oss.s3.client;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import org.dromara.common.oss.s3.config.S3StorageClientConfig;
|
||||
import org.dromara.common.oss.s3.domain.GetObjectResult;
|
||||
import org.dromara.common.oss.s3.domain.HandleAsyncResult;
|
||||
@@ -40,6 +41,27 @@ import java.util.function.Function;
|
||||
*/
|
||||
public interface S3StorageClient extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* S3 存储客户端ID
|
||||
*
|
||||
* 用于标识客户端,初始化后不允许更改
|
||||
*
|
||||
* @return S3 存储客户端ID
|
||||
*/
|
||||
default String clientId(){
|
||||
return IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端配置copy副本
|
||||
*/
|
||||
S3StorageClientConfig config();
|
||||
|
||||
/**
|
||||
* 是否已经初始化
|
||||
*/
|
||||
boolean isInitialized();
|
||||
|
||||
/**
|
||||
* 初始化客户端
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.dromara.common.oss.s3.config;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.Builder;
|
||||
import org.dromara.common.oss.s3.enums.AccessPolicy;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
@@ -11,35 +11,31 @@ import java.util.Optional;
|
||||
/**
|
||||
* S3 ACL访问策略配置
|
||||
*
|
||||
* @param enabled 是否启用ACL
|
||||
* @param accessPolicy 访问策略
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
public class S3AccessControlPolicyConfig implements Config<S3AccessControlPolicyConfig,S3AccessControlPolicyConfig.S3AccessControlPolicyConfigBuilder>,Serializable {
|
||||
public record S3AccessControlPolicyConfig(
|
||||
boolean enabled
|
||||
, AccessPolicy accessPolicy
|
||||
) implements Config<S3AccessControlPolicyConfig, S3AccessControlPolicyConfig.S3AccessControlPolicyConfigBuilder>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final S3AccessControlPolicyConfig DEFAULT = S3AccessControlPolicyConfig.builder().build();
|
||||
|
||||
/**
|
||||
* 是否启用ACL
|
||||
* 默认访问策略配置
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* 访问策略
|
||||
*/
|
||||
private AccessPolicy accessPolicy;
|
||||
|
||||
public boolean enabled() {
|
||||
return enabled;
|
||||
}
|
||||
public static final S3AccessControlPolicyConfig DEFAULT = S3AccessControlPolicyConfig.builder()
|
||||
.enabled(false)
|
||||
.accessPolicy(AccessPolicy.PUBLIC_READ_WRITE)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public @NonNull AccessPolicy accessPolicy() {
|
||||
return Optional.ofNullable(accessPolicy)
|
||||
.orElse(AccessPolicy.PRIVATE);
|
||||
.orElse(AccessPolicy.PUBLIC_READ_WRITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.dromara.common.oss.s3.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -10,12 +8,17 @@ import java.io.Serializable;
|
||||
/**
|
||||
* S3 异步执行器配置
|
||||
*
|
||||
* @param enabledVirtualThread 是否启用虚拟线程
|
||||
* @param corePoolSize 核心线程数
|
||||
* <p>
|
||||
* 默认为当前CPU核心数,该配置项在配置了虚拟线程后会失效
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
public class S3AsyncExecutorConfig implements Config<S3AsyncExecutorConfig,S3AsyncExecutorConfig.S3AsyncExecutorConfigBuilder>,Serializable {
|
||||
public record S3AsyncExecutorConfig(
|
||||
boolean enabledVirtualThread
|
||||
, int corePoolSize
|
||||
) implements Config<S3AsyncExecutorConfig, S3AsyncExecutorConfig.S3AsyncExecutorConfigBuilder>, Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -25,23 +28,18 @@ public class S3AsyncExecutorConfig implements Config<S3AsyncExecutorConfig,S3Asy
|
||||
*/
|
||||
public static final int DEFAULT_CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
public static final S3AsyncExecutorConfig DEFAULT = S3AsyncExecutorConfig.builder().build();
|
||||
|
||||
/**
|
||||
* 是否启用虚拟线程
|
||||
*/
|
||||
private boolean enabledVirtualThread = false;
|
||||
|
||||
/**
|
||||
* 核心线程数
|
||||
*
|
||||
* 默认为当前CPU核心数,该配置项在配置了虚拟线程后会失效
|
||||
*/
|
||||
private int corePoolSize = DEFAULT_CORE_POOL_SIZE;
|
||||
/**
|
||||
* 默认异步执行器配置
|
||||
*/
|
||||
public static final S3AsyncExecutorConfig DEFAULT = S3AsyncExecutorConfig.builder()
|
||||
.enabledVirtualThread(false)
|
||||
.corePoolSize(DEFAULT_CORE_POOL_SIZE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 是否启用虚拟线程
|
||||
*/
|
||||
@Override
|
||||
public boolean enabledVirtualThread() {
|
||||
return enabledVirtualThread;
|
||||
}
|
||||
@@ -49,6 +47,7 @@ public class S3AsyncExecutorConfig implements Config<S3AsyncExecutorConfig,S3Asy
|
||||
/**
|
||||
* 核心线程数
|
||||
*/
|
||||
@Override
|
||||
public int corePoolSize() {
|
||||
return corePoolSize;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import cn.hutool.http.HttpUtil;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.oss.constant.OssConstant;
|
||||
import org.dromara.common.oss.properties.OssProperties;
|
||||
import org.dromara.common.oss.s3.exception.S3StorageException;
|
||||
import org.dromara.common.oss.s3.util.BucketUrlUtil;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
@@ -236,4 +240,41 @@ public class S3StorageClientConfig implements Config<S3StorageClientConfig, S3St
|
||||
.accessControlPolicyConfig(accessControlPolicyConfig().copy())
|
||||
.asyncExecutorConfig(asyncExecutorConfig().copy());
|
||||
}
|
||||
|
||||
public static S3StorageClientConfig formProperties(OssProperties properties){
|
||||
return formPropertiesBuilder(properties).build();
|
||||
}
|
||||
|
||||
public static S3StorageClientConfigBuilder formPropertiesBuilder(OssProperties properties){
|
||||
String regionString = properties.getRegion();
|
||||
Region region = Region.US_EAST_1;
|
||||
if (StringUtils.isNotBlank(regionString)) {
|
||||
region = Region.of(regionString);
|
||||
}
|
||||
|
||||
// 是否使用路径风格应当由使用者明确去配置,此处的配置只是为了适配旧的配置项
|
||||
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
||||
boolean usePathStyleAccess = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
|
||||
|
||||
// // 目前自定义实现的 Client 中并没有实际使用到ACL相关配置,只是作为一个扩展点保留,有需要ACL的自行实现调用逻辑
|
||||
// String accessPolicyString = properties.getAccessPolicy();
|
||||
// // 绝大多数的云厂商都是不允许操作ACL的,所以此处的默认配置也是禁用ACL的
|
||||
// S3AccessControlPolicyConfig accessControlPolicyConfig = S3AccessControlPolicyConfig.DEFAULT;
|
||||
// if (StringUtils.isNotBlank(accessPolicyString)) {
|
||||
// accessControlPolicyConfig = S3AccessControlPolicyConfig.builder()
|
||||
// .enabled(true)
|
||||
// .accessPolicy(AccessPolicy.formType(accessPolicyString))
|
||||
// .build();
|
||||
// }
|
||||
return builder()
|
||||
.endpoint(properties.getEndpoint())
|
||||
.domain(properties.getDomainUrl())
|
||||
.accessKey(properties.getAccessKey())
|
||||
.secretKey(properties.getSecretKey())
|
||||
.bucket(properties.getBucketName())
|
||||
.region(region)
|
||||
.useHttps(SystemConstants.YES.equals(properties.getIsHttps()))
|
||||
.usePathStyleAccess(usePathStyleAccess);
|
||||
// .accessControlPolicyConfig(accessControlPolicyConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package org.dromara.common.oss.s3.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.oss.s3.exception.S3StorageException;
|
||||
import software.amazon.awssdk.services.s3.model.BucketCannedACL;
|
||||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 访问策略
|
||||
*
|
||||
@@ -17,17 +20,22 @@ public enum AccessPolicy {
|
||||
/**
|
||||
* 私有
|
||||
*/
|
||||
PRIVATE(BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
|
||||
PRIVATE(0,BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
|
||||
|
||||
/**
|
||||
* 公有读写
|
||||
*/
|
||||
PUBLIC_READ_WRITE(BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
|
||||
PUBLIC_READ_WRITE(1,BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
|
||||
|
||||
/**
|
||||
* 公有只读
|
||||
*/
|
||||
PUBLIC_READ(BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
|
||||
PUBLIC_READ(2,BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
|
||||
|
||||
/**
|
||||
* 访问策略类型
|
||||
*/
|
||||
private final Integer type;
|
||||
|
||||
/**
|
||||
* 桶权限
|
||||
@@ -39,4 +47,10 @@ public enum AccessPolicy {
|
||||
*/
|
||||
private final ObjectCannedACL objectCannedACL;
|
||||
|
||||
public static AccessPolicy formType(String type) {
|
||||
return Arrays.stream(values())
|
||||
.filter(policy -> policy.getType().toString().equals(type))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> S3StorageException.form("'type' not found By " + type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.oss.constant.OssConstant;
|
||||
import org.dromara.common.oss.properties.OssProperties;
|
||||
import org.dromara.common.oss.s3.client.DefaultS3StorageClientImpl;
|
||||
import org.dromara.common.oss.s3.client.S3StorageClient;
|
||||
import org.dromara.common.oss.s3.config.S3StorageClientConfig;
|
||||
@@ -14,6 +15,7 @@ import org.dromara.common.redis.utils.RedisUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* S3存储客户端工厂
|
||||
@@ -24,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public class S3StorageClientFactory {
|
||||
|
||||
private static final Map<String, S3StorageClient> CLIENT_CACHE = new ConcurrentHashMap<>();
|
||||
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* 获取默认实例
|
||||
@@ -41,20 +44,30 @@ public class S3StorageClientFactory {
|
||||
* 根据类型获取实例
|
||||
*/
|
||||
public static S3StorageClient instance(String configKey) {
|
||||
// 使用租户标识避免多个租户相同key实例覆盖
|
||||
return CLIENT_CACHE.computeIfAbsent(configKey, S3StorageClientFactory::instanceCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用缓存实例化
|
||||
*/
|
||||
private static S3StorageClient instanceCache(String configKey) {
|
||||
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
||||
if (json == null) {
|
||||
throw S3StorageException.form("系统异常, '" + configKey + "'配置信息不存在!");
|
||||
}
|
||||
S3StorageClientConfig config = JsonUtils.parseObject(json, S3StorageClientConfig.class);
|
||||
return new DefaultS3StorageClientImpl(config);
|
||||
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
|
||||
S3StorageClientConfig config = S3StorageClientConfig.formProperties(properties);
|
||||
LOCK.lock();
|
||||
try {
|
||||
// 如果已经存在,则校验配置一致性
|
||||
if (CLIENT_CACHE.containsKey(configKey)) {
|
||||
S3StorageClient client = CLIENT_CACHE.get(configKey);
|
||||
if (!client.verifyConfig(config)) {
|
||||
// 配置不一致,刷新配置
|
||||
client.refresh(config);
|
||||
CLIENT_CACHE.put(configKey, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
DefaultS3StorageClientImpl client = new DefaultS3StorageClientImpl(configKey,config);
|
||||
CLIENT_CACHE.put(configKey, client);
|
||||
return client;
|
||||
} finally {
|
||||
LOCK.lock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +81,7 @@ public class S3StorageClientFactory {
|
||||
try {
|
||||
client.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("S3存储客户端关闭异常,错误信息: {}",e.getMessage(),e);
|
||||
log.warn("S3存储客户端关闭异常,错误信息: {}", e.getMessage(), e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.dromara.common.oss.s3.util;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* S3文件对象工具类
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class S3ObjectUtil {
|
||||
|
||||
/**
|
||||
* 生成一个 【自定义前缀 + 日期路径 + SimpleUUID.文件后缀】 的对象Key 示例: images/20260321/019d0f89c9b1130a48c90dbca0475a.jpg
|
||||
*
|
||||
* @param prefix 前缀
|
||||
* @param withSuffixFileName 带后缀的文件名
|
||||
* @return 文件路径对象Key
|
||||
*/
|
||||
public static String buildPathKey(String prefix, String withSuffixFileName) {
|
||||
// 获取后缀
|
||||
String suffix = StringUtils.substring(withSuffixFileName, withSuffixFileName.lastIndexOf("."), withSuffixFileName.length());
|
||||
// 生成日期路径
|
||||
String datePath = DateUtils.datePath();
|
||||
// 生成uuid
|
||||
String uuid = IdUtil.fastSimpleUUID();
|
||||
// 拼接路径
|
||||
String path = StringUtils.isNotEmpty(prefix) ? prefix + StringUtils.SLASH + datePath + StringUtils.SLASH + uuid : datePath + StringUtils.SLASH + uuid;
|
||||
return path + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个 【日期路径 + SimpleUUID.文件后缀】 的对象Key 示例: 20260321/019d0f89c9b1130a48c90dbca0475a.jpg
|
||||
*
|
||||
* @param withSuffixFileName 带后缀的文件名
|
||||
* @return 文件路径对象Key
|
||||
*/
|
||||
public static String buildPathKey(String withSuffixFileName) {
|
||||
return buildPathKey("", withSuffixFileName);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user