From 2dc289979ffe83cc210c3fde05648fb25de4e48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E8=BE=9E=E6=9C=AA=E5=AF=92?= <545073804@qq.com> Date: Sat, 21 Mar 2026 17:08:29 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E6=9B=B4=E6=96=B0=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=96=B0=E7=9A=84S3=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/AbstractS3StorageClientImpl.java | 40 ++++++++++++++-- .../s3/client/DefaultS3StorageClientImpl.java | 4 +- .../common/oss/s3/client/S3StorageClient.java | 22 +++++++++ .../config/S3AccessControlPolicyConfig.java | 32 ++++++------- .../oss/s3/config/S3AsyncExecutorConfig.java | 35 +++++++------- .../oss/s3/config/S3StorageClientConfig.java | 41 +++++++++++++++++ .../common/oss/s3/enums/AccessPolicy.java | 20 ++++++-- .../s3/factory/S3StorageClientFactory.java | 35 +++++++++----- .../common/oss/s3/util/S3ObjectUtil.java | 46 +++++++++++++++++++ 9 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/util/S3ObjectUtil.java diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/AbstractS3StorageClientImpl.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/AbstractS3StorageClientImpl.java index 4227d708c..4b214648d 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/AbstractS3StorageClientImpl.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/AbstractS3StorageClientImpl.java @@ -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 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); } } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/DefaultS3StorageClientImpl.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/DefaultS3StorageClientImpl.java index 379e92346..2aed095d1 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/DefaultS3StorageClientImpl.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/DefaultS3StorageClientImpl.java @@ -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 diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/S3StorageClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/S3StorageClient.java index edf2ed17a..31128201e 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/S3StorageClient.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/client/S3StorageClient.java @@ -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(); + /** * 初始化客户端 */ diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AccessControlPolicyConfig.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AccessControlPolicyConfig.java index 986437544..90a9430fc 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AccessControlPolicyConfig.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AccessControlPolicyConfig.java @@ -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,Serializable { +public record S3AccessControlPolicyConfig( + boolean enabled + , AccessPolicy accessPolicy +) implements Config, 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 diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AsyncExecutorConfig.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AsyncExecutorConfig.java index b9b7c055d..b828de42c 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AsyncExecutorConfig.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/config/S3AsyncExecutorConfig.java @@ -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 核心线程数 + *

+ * 默认为当前CPU核心数,该配置项在配置了虚拟线程后会失效 * @author 秋辞未寒 */ -@RequiredArgsConstructor @Builder -@EqualsAndHashCode -public class S3AsyncExecutorConfig implements Config,Serializable { +public record S3AsyncExecutorConfig( + boolean enabledVirtualThread + , int corePoolSize +) implements Config, Serializable { @Serial private static final long serialVersionUID = 1L; @@ -25,23 +28,18 @@ public class S3AsyncExecutorConfig implements Config policy.getType().toString().equals(type)) + .findFirst() + .orElseThrow(() -> S3StorageException.form("'type' not found By " + type)); + } } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/factory/S3StorageClientFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/factory/S3StorageClientFactory.java index 0f1c99726..83fe7cdfa 100644 --- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/factory/S3StorageClientFactory.java +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/factory/S3StorageClientFactory.java @@ -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 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; } diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/util/S3ObjectUtil.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/util/S3ObjectUtil.java new file mode 100644 index 000000000..08f92bb06 --- /dev/null +++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/s3/util/S3ObjectUtil.java @@ -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); + } + +}