浏览代码

bump: 发布2.1.0

details see: https://helio.uncarbon.cc/#/i18n/zh-CN/appendix/change-log
Uncarbon 2 年之前
父节点
当前提交
12da74ade3
共有 33 个文件被更改,包括 627 次插入152 次删除
  1. 1 1
      helio-core/pom.xml
  2. 26 0
      helio-core/src/main/java/cc/uncarbon/framework/core/cache/TimedCacheEx.java
  3. 28 0
      helio-core/src/main/java/cc/uncarbon/framework/core/props/HelioProperties.java
  4. 1 1
      helio-starter-aop/pom.xml
  5. 1 1
      helio-starter-cloud/pom.xml
  6. 1 1
      helio-starter-crud/pom.xml
  7. 1 1
      helio-starter-dubbo/pom.xml
  8. 1 1
      helio-starter-i18n/pom.xml
  9. 1 1
      helio-starter-knife4j/pom.xml
  10. 1 1
      helio-starter-rate-limit-redis/pom.xml
  11. 7 1
      helio-starter-redis/pom.xml
  12. 5 5
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/config/HelioRedisAutoConfiguration.java
  13. 28 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/enums/KeyTypeEnum.java
  14. 17 13
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/lock/RedisDistributedLock.java
  15. 8 2
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/lock/impl/RedisDistributedLockImpl.java
  16. 40 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/model/FormattedRedisKey.java
  17. 83 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/model/RedisKeyDefinition.java
  18. 0 42
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/RedisDistributedLockSupport.java
  19. 0 56
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/impl/RedisDistributedLockSupportImpl.java
  20. 68 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/template/RedisDistributedLockTemplate.java
  21. 76 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/template/impl/RedisDistributedLockTemplateImpl.java
  22. 1 1
      helio-starter-rocketmq-aliyun/pom.xml
  23. 1 1
      helio-starter-satoken/pom.xml
  24. 27 0
      helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/config/SaTokenLocalCacheAutoConfiguration.java
  25. 180 0
      helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/dao/SaTokenLocalCacheDao.java
  26. 0 9
      helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/stub/HelioStarterSaTokenStub.java
  27. 1 0
      helio-starter-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  28. 1 1
      helio-starter-seata/pom.xml
  29. 1 1
      helio-starter-tenant/pom.xml
  30. 2 0
      helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/config/HelioTenantAutoConfiguration.java
  31. 1 1
      helio-starter-test/pom.xml
  32. 1 1
      helio-starter-web/pom.xml
  33. 17 10
      pom.xml

+ 1 - 1
helio-core/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 26 - 0
helio-core/src/main/java/cc/uncarbon/framework/core/cache/TimedCacheEx.java

@@ -0,0 +1,26 @@
+package cc.uncarbon.framework.core.cache;
+
+import cn.hutool.cache.impl.TimedCache;
+import cn.hutool.core.lang.mutable.MutableObj;
+
+/**
+ * 继承自 hutool TimedCache
+ * 增加了容量限制,重写了 containsKey 方法判断逻辑提升效率并支持空值
+ * @param <K> key类型
+ * @param <V> value类型
+ *
+ * @since 2.1.0
+ * @author Uncarbon
+ */
+public class TimedCacheEx<K, V> extends TimedCache<K, V> {
+
+    public TimedCacheEx(long timeout, int capacity) {
+        super(timeout);
+        this.capacity = capacity;
+    }
+
+    @Override
+    public boolean containsKey(K key) {
+        return this.cacheMap.containsKey(MutableObj.of(key));
+    }
+}

+ 28 - 0
helio-core/src/main/java/cc/uncarbon/framework/core/props/HelioProperties.java

@@ -38,6 +38,8 @@ public class HelioProperties {
 
         private final Xss xss = new Xss();
 
+        private final SaTokenLocalCache saTokenLocalCache = new SaTokenLocalCache();
+
 
         @Data
         public static class Xss {
@@ -54,6 +56,32 @@ public class HelioProperties {
             private List<String> excludedRoutes = new ArrayList<>();
 
         }
+
+        /**
+         * !!!试验性功能!!!
+         * SA-Token 读取时,本地缓存一定时长,减少对 Redis 的 IO
+         * 在多实例部署时存在一定安全风险,有可能用户登出时,A机器的缓存已清除,但B机器的缓存还认为有效。所以请谨慎设置本地缓存有效时长
+         * @since 2.1.0
+         */
+        @Data
+        public static class SaTokenLocalCache {
+
+            /**
+             * 是否启用
+             */
+            private boolean enabled = false;
+
+            /**
+             * 本地缓存有效时长,单位=秒
+             */
+            private long duration = 5;
+
+            /**
+             * 本地缓存最大容量,注意:每次超过容量都会触发一次清理过程
+             */
+            private int capacity = 4000;
+
+        }
     }
 
     @Data

+ 1 - 1
helio-starter-aop/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-cloud/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-crud/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-dubbo/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-i18n/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-knife4j/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-rate-limit-redis/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 7 - 1
helio-starter-redis/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 
@@ -19,6 +19,12 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
+        <dependency>
+            <groupId>cc.uncarbon.framework</groupId>
+            <artifactId>helio-core</artifactId>
+        </dependency>
+
         <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.redisson</groupId>

+ 5 - 5
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/config/HelioRedisAutoConfiguration.java

@@ -2,8 +2,8 @@ package cc.uncarbon.framework.redis.config;
 
 import cc.uncarbon.framework.redis.lock.RedisDistributedLock;
 import cc.uncarbon.framework.redis.lock.impl.RedisDistributedLockImpl;
-import cc.uncarbon.framework.redis.support.RedisDistributedLockSupport;
-import cc.uncarbon.framework.redis.support.impl.RedisDistributedLockSupportImpl;
+import cc.uncarbon.framework.redis.template.RedisDistributedLockTemplate;
+import cc.uncarbon.framework.redis.template.impl.RedisDistributedLockTemplateImpl;
 import lombok.RequiredArgsConstructor;
 import org.redisson.api.RedissonClient;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -81,11 +81,11 @@ public class HelioRedisAutoConfiguration {
     }
 
     /**
-     * Redis分布式可重入锁辅助
+     * Redis分布式可重入锁模板
      */
     @Bean
     @ConditionalOnMissingBean
-    public RedisDistributedLockSupport redisDistributedLockSupport(RedisDistributedLock redisDistributedLock) {
-        return new RedisDistributedLockSupportImpl(redisDistributedLock);
+    public RedisDistributedLockTemplate redisDistributedLockTemplate(RedisDistributedLock redisDistributedLock) {
+        return new RedisDistributedLockTemplateImpl(redisDistributedLock);
     }
 }

+ 28 - 0
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/enums/KeyTypeEnum.java

@@ -0,0 +1,28 @@
+package cc.uncarbon.framework.redis.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum KeyTypeEnum {
+
+    // 基本类型
+    STRING,
+    HASH,
+    LIST,
+    SET,
+    ZSET,
+    STREAM,
+    RE_JSON,
+
+    // 封装类型
+    LOCK,
+    PUB_SUB,
+    HYPER_LOG_LOG,
+    GEO,
+
+    // 尚未收录的
+    OTHER
+
+}

+ 17 - 13
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/lock/RedisDistributedLock.java

@@ -13,44 +13,41 @@ import java.util.concurrent.TimeUnit;
 public interface RedisDistributedLock {
 
     /**
-     * 一般加锁 - 主动为锁设置持有时长
-     * 注意:持有时长一定要大于业务的执行时间,锁不会自动续期
+     * 获取分布式锁,等待时长无限
      *
      * @param lockName     锁名称
-     * @param holdDuration 锁持有时长,单位:秒
+     * @param holdDuration 锁持有时长,单位:秒;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
      * @return 锁对象
      */
     RLock lock(String lockName, int holdDuration);
 
-
     /**
-     * 一般加锁 - 主动为锁设置持有时长及单位
-     * 注意:持有时长一定要大于业务的执行时间,锁不会自动续期
+     * 获取分布式锁,等待时长无限
      *
      * @param lockName 锁名称
      * @param unit 时间单位
-     * @param holdDuration 锁持有时长
+     * @param holdDuration 锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
      * @return 锁对象
      */
     RLock lock(String lockName, TimeUnit unit, int holdDuration);
 
     /**
-     * 尝试获取锁,在等待时长内获取到锁则返回true,否则返回false;如果获取到锁,在锁持有时长结束后释放锁
+     * 尝试获取锁,在等待时长内获取到锁则返回true,否则返回false
      *
      * @param lockName     锁名称
-     * @param waitDuration 等待时长,等到最大时间,【强行】获取锁
-     * @param holdDuration 锁持有时长,单位:秒
+     * @param waitDuration 最大等待时长
+     * @param holdDuration 锁持有时长,单位:秒;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
      * @return 锁对象
      */
     boolean tryLock(String lockName, int waitDuration, int holdDuration);
 
     /**
-     * 尝试获取锁,在等待时长内获取到锁则返回true,否则返回false;如果获取到锁,在锁持有时长结束后释放锁
+     * 尝试获取锁,在等待时长内获取到锁则返回true,否则返回false
      *
      * @param lockName     锁名称
      * @param unit         时间单位
-     * @param waitDuration 等待时长,等到最大时间,【强行】获取锁
-     * @param holdDuration 锁持有时长
+     * @param waitDuration 最大等待时长
+     * @param holdDuration 锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
      * @return 锁对象
      */
     boolean tryLock(String lockName, TimeUnit unit, int waitDuration, int holdDuration);
@@ -86,4 +83,11 @@ public interface RedisDistributedLock {
      * @param lock 锁对象
      */
     void unlockSafely(RLock lock);
+
+    /**
+     * 指定本类最终拼接的键名前缀
+     * 默认值为【distributedLock:】
+     */
+    void setLockKeyPrefix(String newPrefix);
+
 }

+ 8 - 2
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/lock/impl/RedisDistributedLockImpl.java

@@ -6,6 +6,7 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Redis分布式可重入锁,基于Redisson实现
@@ -19,7 +20,7 @@ public class RedisDistributedLockImpl implements RedisDistributedLock {
     /**
      * 锁名称前缀
      */
-    private static final String LOCK_KEY_PREFIX = "distributedLock:";
+    private static final AtomicReference<String> LOCK_KEY_PREFIX = new AtomicReference<>("distributedLock:");
 
     private final RedissonClient redissonClient;
 
@@ -80,7 +81,12 @@ public class RedisDistributedLockImpl implements RedisDistributedLock {
         }
     }
 
+    @Override
+    public void setLockKeyPrefix(String newPrefix) {
+        LOCK_KEY_PREFIX.set(newPrefix);
+    }
+
     private RLock getRedissonLockByName(String lockName) {
-        return redissonClient.getLock(LOCK_KEY_PREFIX + lockName);
+        return redissonClient.getLock(LOCK_KEY_PREFIX.get() + lockName);
     }
 }

+ 40 - 0
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/model/FormattedRedisKey.java

@@ -0,0 +1,40 @@
+package cc.uncarbon.framework.redis.model;
+
+import cc.uncarbon.framework.redis.enums.KeyTypeEnum;
+import cn.hutool.core.util.RandomUtil;
+
+/**
+ * 已固定好键名的 Redis Key
+ * @param key 已固定好的键名
+ * @param keyType 键类型
+ * @param valueClass 值类型
+ * @param durationSeconds 有效时长,永久为-1,可通过 hasExpiration() 方法判断
+ * @param <V>
+ */
+public record FormattedRedisKey<V>(
+        String key,
+        KeyTypeEnum keyType,
+        Class<V> valueClass,
+        long durationSeconds
+) {
+    /**
+     * 是否已设置有效时长
+     */
+    public boolean hasExpiration() {
+        return this.durationSeconds > 0;
+    }
+
+    /**
+     * 计算随机有效时长,有助于缓解缓存击穿、缓存雪崩
+     *
+     * @param minRatio 最小比例因子,如0.9表示90%
+     * @param maxRatio 最大比例因子,如1.1表示110%
+     * @return 随机有效时长,单位=秒
+     */
+    public long randomDurationSeconds(double minRatio, double maxRatio) {
+        if (!hasExpiration()) {
+            throw new IllegalArgumentException("no-expiration key CANNOT calculate randomDurationSeconds");
+        }
+        return (long)(RandomUtil.randomDouble(this.durationSeconds * minRatio, this.durationSeconds * maxRatio));
+    }
+}

+ 83 - 0
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/model/RedisKeyDefinition.java

@@ -0,0 +1,83 @@
+package cc.uncarbon.framework.redis.model;
+
+import cc.uncarbon.framework.redis.enums.KeyTypeEnum;
+import cn.hutool.core.text.CharSequenceUtil;
+import lombok.Getter;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 统一 Redis Key 定义,方便集中管理
+ * @param <V> 值类型
+ */
+@Getter
+public class RedisKeyDefinition<V> {
+
+    /**
+     * 键名模板,占位符需要用 {}
+     */
+    private final String keyTemplate;
+
+    /**
+     * 键类型
+     */
+    private final KeyTypeEnum keyType;
+
+    /**
+     * 值类型对应 Class
+     */
+    private final Class<V> valueClass;
+
+    /**
+     * 有效时长,单位=秒
+     */
+    private final long durationSeconds;
+
+
+    /**
+     * 不设置有效时长
+     * @param keyTemplate 键名模板,占位符需要用 {}
+     * @param keyType 键类型
+     * @param valueClass 值类型对应 Class
+     */
+    public RedisKeyDefinition(String keyTemplate, KeyTypeEnum keyType, Class<V> valueClass) {
+        this.keyTemplate = keyTemplate;
+        this.keyType = keyType;
+        this.valueClass = valueClass;
+        this.durationSeconds = -1;
+    }
+
+    /**
+     * 有设置有效时长
+     * @param keyTemplate 键名模板,占位符需要用 {}
+     * @param keyType 键类型
+     * @param valueClass 值类型对应 Class
+     * @param duration 有效时长
+     * @param timeUnit 有效时长的单位,最低为秒
+     */
+    public RedisKeyDefinition(String keyTemplate, KeyTypeEnum keyType, Class<V> valueClass, long duration, TimeUnit timeUnit) {
+        if (timeUnit == TimeUnit.NANOSECONDS || timeUnit == TimeUnit.MICROSECONDS || timeUnit == TimeUnit.MILLISECONDS) {
+            throw new IllegalArgumentException("timeUnit CANNOT be NANOSECONDS or MICROSECONDS or MILLISECONDS");
+        }
+
+        this.keyTemplate = keyTemplate;
+        this.keyType = keyType;
+        this.valueClass = valueClass;
+
+        // 兜底:最短缓存1s
+        long asSeconds = timeUnit.toSeconds(duration);
+        if (asSeconds < 1) {
+            asSeconds = 1;
+        }
+        this.durationSeconds = asSeconds;
+    }
+
+    /**
+     * 固定一个 Redis Key 的键名
+     * @param params 键名模板占位符填充参数
+     */
+    public FormattedRedisKey<V> format(Object... params) {
+        return new FormattedRedisKey<>(CharSequenceUtil.format(this.keyTemplate, params),
+                this.keyType, this.valueClass, this.durationSeconds);
+    }
+}

+ 0 - 42
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/RedisDistributedLockSupport.java

@@ -1,42 +0,0 @@
-package cc.uncarbon.framework.redis.support;
-
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * Redis分布式可重入锁辅助类
- *
- * @author Uncarbon
- */
-public interface RedisDistributedLockSupport {
-
-    /**
-     * 【注意】务必考虑到锁内代码有抛出异常的可能
-     * 编程式获取、释放分布式锁
-     * 规范化分布式锁的使用方式,减少不规范代码
-     *
-     * @param lockName       锁名称
-     * @param unit           时间单位
-     * @param waitDuration   等待时长,等到最大时间,【强行】获取锁
-     * @param holdDuration   锁持有时长
-     * @param lockedRunnable 锁内代码
-     * @return 是否实际拿到锁
-     */
-    boolean executeWithLock(String lockName, TimeUnit unit, int waitDuration, int holdDuration,
-                            Runnable lockedRunnable);
-
-    /**
-     * 编程式获取、释放分布式锁
-     * 规范化分布式锁的使用方式,减少不规范代码
-     *
-     * @param lockName         锁名称
-     * @param unit             时间单位
-     * @param waitDuration     等待时长,等到最大时间,【强行】获取锁
-     * @param holdDuration     锁持有时长
-     * @param lockedRunnable   锁内代码
-     * @param exceptionHandler (可选)锁内代码异常处理
-     * @return 是否实际拿到锁
-     */
-    boolean executeWithLock(String lockName, TimeUnit unit, int waitDuration, int holdDuration,
-                            Runnable lockedRunnable, Consumer<Exception> exceptionHandler);
-}

+ 0 - 56
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/impl/RedisDistributedLockSupportImpl.java

@@ -1,56 +0,0 @@
-package cc.uncarbon.framework.redis.support.impl;
-
-import cc.uncarbon.framework.redis.lock.RedisDistributedLock;
-import cc.uncarbon.framework.redis.support.RedisDistributedLockSupport;
-import lombok.RequiredArgsConstructor;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * Redis分布式可重入锁辅助类实现
- *
- * @author Uncarbon
- */
-@ConditionalOnBean(value = RedisDistributedLock.class)
-@RequiredArgsConstructor
-public class RedisDistributedLockSupportImpl implements RedisDistributedLockSupport {
-
-    private final RedisDistributedLock redisDistributedLock;
-
-
-    @Override
-    public boolean executeWithLock(final String lockName, final TimeUnit unit, int waitDuration, int holdDuration,
-                                   final Runnable lockedRunnable) {
-        return executeWithLock(lockName, unit, waitDuration, holdDuration, lockedRunnable, null);
-    }
-
-    @Override
-    public boolean executeWithLock(final String lockName, final TimeUnit unit, int waitDuration, int holdDuration,
-                                   final Runnable lockedRunnable, final Consumer<Exception> exceptionHandler) {
-        // corner case
-        if (Objects.isNull(lockName) || Objects.isNull(unit) || waitDuration < 0 || holdDuration < 0
-                || Objects.isNull(lockedRunnable)) {
-            throw new IllegalArgumentException("Required arguments are null");
-        }
-
-        boolean lockedFlag = redisDistributedLock.tryLock(lockName, unit, waitDuration, holdDuration);
-        if (!lockedFlag) {
-            return false;
-        }
-        try {
-            lockedRunnable.run();
-        } catch (Exception e) {
-            if (exceptionHandler != null) {
-                exceptionHandler.accept(e);
-            } else {
-                throw e;
-            }
-        } finally {
-            redisDistributedLock.unlockSafely(lockName);
-        }
-        return true;
-    }
-}

+ 68 - 0
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/template/RedisDistributedLockTemplate.java

@@ -0,0 +1,68 @@
+package cc.uncarbon.framework.redis.template;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Redis分布式可重入锁模板类
+ * 编程式获取分布式锁,模板化分布式锁的使用方式
+ *
+ * @author Uncarbon
+ */
+public interface RedisDistributedLockTemplate {
+
+    /**
+     * 获取分布式锁并执行锁内代码,等待时长无限
+     *
+     * @param lockName       锁名称
+     * @param unit           时间单位
+     * @param holdDuration   锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
+     * @param lockedRunnable 锁内代码,建议单独开个子方法,通过lambda表达式引用
+     */
+    void executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int holdDuration,
+                         @Nonnull Runnable lockedRunnable);
+
+    /**
+     * 获取分布式锁并执行锁内代码,等待时长无限
+     *
+     * @param lockName         锁名称
+     * @param unit             时间单位
+     * @param holdDuration     锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
+     * @param lockedRunnable   锁内代码,建议单独开个子方法,通过lambda表达式引用
+     * @param exceptionHandler (可选)锁内代码异常处理过程
+     */
+    void executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int holdDuration,
+                         @Nonnull Runnable lockedRunnable, @Nullable Consumer<Exception> exceptionHandler);
+
+    /**
+     * 获取分布式锁并执行锁内代码
+     * 【注意】务必考虑到锁内代码有抛出异常的可能
+     *
+     * @param lockName       锁名称
+     * @param unit           时间单位
+     * @param waitDuration   最大等待时长
+     * @param holdDuration   锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
+     * @param lockedRunnable 锁内代码,建议单独开个子方法,通过lambda表达式引用
+     * @return 是否实际拿到锁
+     */
+    boolean executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int waitDuration, int holdDuration,
+                            @Nonnull Runnable lockedRunnable);
+
+    /**
+     * 获取分布式锁并执行锁内代码
+     * 可手动指定锁内代码异常处理过程
+     *
+     * @param lockName         锁名称
+     * @param unit             时间单位
+     * @param waitDuration     最大等待时长
+     * @param holdDuration     锁持有时长;【注意】持有时长一定要大于业务的执行时间,锁不会自动续期
+     * @param lockedRunnable   锁内代码,建议单独开个子方法,通过lambda表达式引用
+     * @param exceptionHandler (可选)锁内代码异常处理过程
+     * @return 是否实际拿到锁
+     */
+    boolean executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int waitDuration, int holdDuration,
+                            @Nonnull Runnable lockedRunnable, @Nullable Consumer<Exception> exceptionHandler);
+}

+ 76 - 0
helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/template/impl/RedisDistributedLockTemplateImpl.java

@@ -0,0 +1,76 @@
+package cc.uncarbon.framework.redis.template.impl;
+
+import cc.uncarbon.framework.redis.lock.RedisDistributedLock;
+import cc.uncarbon.framework.redis.template.RedisDistributedLockTemplate;
+import cn.hutool.core.lang.Assert;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import lombok.RequiredArgsConstructor;
+import org.redisson.api.RLock;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Redis分布式可重入锁模板类实现
+ *
+ * @author Uncarbon
+ */
+@ConditionalOnMissingBean(value = RedisDistributedLockTemplate.class)
+@ConditionalOnBean(value = RedisDistributedLock.class)
+@RequiredArgsConstructor
+public class RedisDistributedLockTemplateImpl implements RedisDistributedLockTemplate {
+
+    private final RedisDistributedLock redisDistributedLock;
+
+
+    @Override
+    public void executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int holdDuration, @Nonnull Runnable lockedRunnable) {
+        executeWithLock(lockName, unit, holdDuration, lockedRunnable, null);
+    }
+
+    @Override
+    public void executeWithLock(@Nonnull String lockName, @Nonnull TimeUnit unit, int holdDuration, @Nonnull Runnable lockedRunnable, @Nullable Consumer<Exception> exceptionHandler) {
+        Assert.isTrue(holdDuration > 0, "holdDuration必须大于0");
+        RLock lock = redisDistributedLock.lock(lockName, unit, holdDuration);
+        Assert.notNull(lock, "获取{}的锁对象失败", lockName);
+
+        runAndAutoUnlock(lockName, lockedRunnable, exceptionHandler);
+    }
+
+    @Override
+    public boolean executeWithLock(@Nonnull final String lockName, @Nonnull final TimeUnit unit, int waitDuration, int holdDuration,
+                                   @Nonnull final Runnable lockedRunnable) {
+        return executeWithLock(lockName, unit, waitDuration, holdDuration, lockedRunnable, null);
+    }
+
+    @Override
+    public boolean executeWithLock(final @Nonnull String lockName, final @Nonnull TimeUnit unit, int waitDuration, int holdDuration,
+                                   final @Nonnull Runnable lockedRunnable, final @Nullable Consumer<Exception> exceptionHandler) {
+        Assert.isTrue(waitDuration > 0, "waitDuration必须大于0");
+        Assert.isTrue(holdDuration > 0, "holdDuration必须大于0");
+
+        boolean lockedFlag = redisDistributedLock.tryLock(lockName, unit, waitDuration, holdDuration);
+        if (!lockedFlag) {
+            return false;
+        }
+        runAndAutoUnlock(lockName, lockedRunnable, exceptionHandler);
+        return true;
+    }
+
+    protected void runAndAutoUnlock(String lockName, Runnable lockedRunnable, Consumer<Exception> exceptionHandler) {
+        try {
+            lockedRunnable.run();
+        } catch (Exception e) {
+            if (exceptionHandler != null) {
+                exceptionHandler.accept(e);
+            } else {
+                throw e;
+            }
+        } finally {
+            redisDistributedLock.unlockSafely(lockName);
+        }
+    }
+}

+ 1 - 1
helio-starter-rocketmq-aliyun/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-satoken/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 27 - 0
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/config/SaTokenLocalCacheAutoConfiguration.java

@@ -0,0 +1,27 @@
+package cc.uncarbon.framework.satoken.config;
+
+import cc.uncarbon.framework.core.props.HelioProperties;
+import cc.uncarbon.framework.satoken.dao.SaTokenLocalCacheDao;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * SA-Token 本地缓存自动配置类
+ *
+ * @author Uncarbon
+ */
+@ConditionalOnExpression(value = "${helio.security.saTokenLocalCache.enabled:false} || ${helio.security.sa-token-local-cache.enabled:false}")
+@AutoConfiguration
+public class SaTokenLocalCacheAutoConfiguration {
+
+    @ConditionalOnMissingBean(value = SaTokenLocalCacheDao.class)
+    @Primary
+    @Bean
+    public SaTokenLocalCacheDao saTokenLocalCacheDao(HelioProperties helioProperties) {
+        HelioProperties.Security.SaTokenLocalCache props = helioProperties.getSecurity().getSaTokenLocalCache();
+        return new SaTokenLocalCacheDao(props.getDuration(), props.getCapacity());
+    }
+}

+ 180 - 0
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/dao/SaTokenLocalCacheDao.java

@@ -0,0 +1,180 @@
+package cc.uncarbon.framework.satoken.dao;
+
+import cc.uncarbon.framework.core.cache.TimedCacheEx;
+import cn.dev33.satoken.dao.SaTokenDaoRedisJackson;
+
+/**
+ * SA-Token 读取时,本地缓存一定时长,减少对 Redis 的 IO
+ * 在多实例部署时存在一定安全风险,有可能用户登出时,A机器的缓存已清除,但B机器的缓存还认为有效。所以请谨慎设置本地缓存有效时长
+ * 底层实现为读写锁,大量数据下需要注意 FullGC 风险
+ *
+ * @since 2.1.0
+ * @author Uncarbon
+ */
+public class SaTokenLocalCacheDao extends SaTokenDaoRedisJackson {
+
+    /**
+     * 标记 key 为空值
+     */
+    protected final TimedCacheEx<String, Boolean> nullValueMarker;
+    protected final TimedCacheEx<String, String> stringValueCache;
+    protected final TimedCacheEx<String, Object> objectValueCache;
+
+
+    /**
+     * 创建本地缓存 SaTokenDao
+     * @param duration 本地缓存有效时长,单位=秒
+     * @param capacity 本地缓存最大容量,注意:每次超过容量都会触发一次清理过程
+     */
+    public SaTokenLocalCacheDao(long duration, int capacity) {
+        if (duration < 1) {
+            throw new IllegalArgumentException("duration CANNOT less than 1");
+        }
+        if (capacity < 1) {
+            throw new IllegalArgumentException("capacity CANNOT less than 1");
+        }
+
+        long asMilliseconds = duration * 1000;
+
+        this.nullValueMarker = new TimedCacheEx<>(asMilliseconds, capacity);
+        this.stringValueCache = new TimedCacheEx<>(asMilliseconds, capacity);
+        this.objectValueCache = new TimedCacheEx<>(asMilliseconds, capacity);
+        // 默认开nullValueMarker的清理定时任务,避免长时间缓存空值
+        enableNullValueMarkerPruneSchedule(400);
+        // 不默认开stringValueCache、objectValueCache的清理定时任务
+    }
+
+    /**
+     * 开nullValueMarker的清理定时任务
+     * @param delay 间隔时长,单位毫秒
+     */
+    public void enableNullValueMarkerPruneSchedule(long delay) {
+        this.nullValueMarker.schedulePrune(delay);
+    }
+
+    /**
+     * 关nullValueMarker的清理定时任务
+     */
+    public void disableNullValueMarkerPruneSchedule() {
+        this.nullValueMarker.cancelPruneSchedule();
+    }
+
+    /**
+     * 开stringValueCache的清理定时任务
+     * @param delay 间隔时长,单位毫秒
+     */
+    public void enableStringValueCachePruneSchedule(long delay) {
+        this.stringValueCache.schedulePrune(delay);
+    }
+
+    /**
+     * 关stringValueCache的清理定时任务
+     */
+    public void disableStringValueCachePruneSchedule() {
+        this.stringValueCache.cancelPruneSchedule();
+    }
+
+    /**
+     * 开objectValueCache的清理定时任务
+     * @param delay 间隔时长,单位毫秒
+     */
+    public void enableObjectValueCachePruneSchedule(long delay) {
+        this.objectValueCache.schedulePrune(delay);
+    }
+
+    /**
+     * 关objectValueCache的清理定时任务
+     */
+    public void disableObjectValueCachePruneSchedule() {
+        this.objectValueCache.cancelPruneSchedule();
+    }
+
+    @Override
+    public String get(String key) {
+        if (nullValueMarker.containsKey(key)) {
+            // 明确知道为空,直接返回
+            return null;
+        }
+        String cacheValue = stringValueCache.get(key, false);
+        if (cacheValue == null) {
+            // 本地缓存为空,实时查询
+            cacheValue = super.get(key);
+        }
+        // cache-aside
+        if (cacheValue == null) {
+            nullValueMarker.put(key, Boolean.TRUE);
+        } else {
+            stringValueCache.put(key, cacheValue);
+        }
+        return cacheValue;
+    }
+
+    @Override
+    public void set(String key, String value, long timeout) {
+        super.set(key, value, timeout);
+        // cache-aside
+        nullValueMarker.remove(key);
+        stringValueCache.remove(key);
+    }
+
+    @Override
+    public void update(String key, String value) {
+        super.update(key, value);
+        // cache-aside
+        nullValueMarker.remove(key);
+        stringValueCache.remove(key);
+    }
+
+    @Override
+    public void delete(String key) {
+        super.delete(key);
+        // cache-aside
+        nullValueMarker.remove(key);
+        stringValueCache.remove(key);
+    }
+
+    @Override
+    public Object getObject(String key) {
+        if (nullValueMarker.containsKey(key)) {
+            // 明确知道为空,直接返回
+            return null;
+        }
+        Object cacheValue = objectValueCache.get(key, false);
+        if (cacheValue == null) {
+            // 本地缓存为空,实时查询
+            cacheValue = super.getObject(key);
+        }
+        // cache-aside
+        if (cacheValue == null) {
+            nullValueMarker.put(key, Boolean.TRUE);
+        } else {
+            objectValueCache.put(key, cacheValue);
+        }
+        return cacheValue;
+    }
+
+    @Override
+    public void setObject(String key, Object object, long timeout) {
+        super.setObject(key, object, timeout);
+        // cache-aside
+        nullValueMarker.remove(key);
+        objectValueCache.remove(key);
+    }
+
+    @Override
+    public void updateObject(String key, Object object) {
+        super.updateObject(key, object);
+        // cache-aside
+        nullValueMarker.remove(key);
+        objectValueCache.remove(key);
+    }
+
+    @Override
+    public void deleteObject(String key) {
+        super.deleteObject(key);
+        // cache-aside
+        nullValueMarker.remove(key);
+        objectValueCache.remove(key);
+    }
+
+}

+ 0 - 9
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/stub/HelioStarterSaTokenStub.java

@@ -1,9 +0,0 @@
-package cc.uncarbon.framework.satoken.stub;
-
-/**
- * 需要一个空类以解决:Missing: no javadoc jar found in folder
- */
-public final class HelioStarterSaTokenStub {
-    private HelioStarterSaTokenStub() {
-    }
-}

+ 1 - 0
helio-starter-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cc.uncarbon.framework.satoken.config.SaTokenLocalCacheAutoConfiguration

+ 1 - 1
helio-starter-seata/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-tenant/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 0
helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/config/HelioTenantAutoConfiguration.java

@@ -8,6 +8,7 @@ import cc.uncarbon.framework.tenant.support.TenantLineSupport;
 import lombok.RequiredArgsConstructor;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
 
 /**
  * Helio 多租户自动配置类
@@ -23,6 +24,7 @@ public class HelioTenantAutoConfiguration {
 
 
     @Bean
+    @Primary
     public TenantSupport tenantSupport() {
         if (!Boolean.TRUE.equals(helioProperties.getTenant().getEnabled())) {
             // 引入了 starter,但未启用多租户

+ 1 - 1
helio-starter-test/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 1 - 1
helio-starter-web/pom.xml

@@ -8,7 +8,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+        <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
 

+ 17 - 10
pom.xml

@@ -25,31 +25,31 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.2.3</version>
+        <version>3.3.2</version>
         <relativePath/>
     </parent>
 
     <groupId>cc.uncarbon.framework</groupId>
     <artifactId>helio-starters</artifactId>
-    <version>2.0.0</version><!-- BATCH REPLACEMENT MARK -->
+    <version>2.1.0</version><!-- BATCH REPLACEMENT MARK -->
 
     <properties>
         <!-- core versions -->
-        <revision>2.0.0</revision>
+        <revision>2.1.0</revision>
         <helio-starters.version>${revision}</helio-starters.version>
         <!-- 可根据实际JDK版本修改;JDK17则改为'17'  JDK21则改为'21' -->
         <java.version>17</java.version>
         <!-- 若变更Spring Boot版本, 上面parent依赖版本也要变 -->
-        <spring-boot.version>3.2.3</spring-boot.version>
+        <spring-boot.version>3.3.2</spring-boot.version>
 
 
         <!-- Spring Cloud versions -->
         <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
-        <spring-cloud.version>2023.0.0</spring-cloud.version>
+        <spring-cloud.version>2023.0.3</spring-cloud.version>
         <spring-cloud-starter-bootstrap.version>4.1.1</spring-cloud-starter-bootstrap.version>
 
         <!-- Spring Cloud Alibaba versions -->
-        <spring-cloud-alibaba.version>2023.0.0.0-RC1</spring-cloud-alibaba.version>
+        <spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
         <dubbo.version>3.2.11</dubbo.version>
         <seata.vesion>2.0.0</seata.vesion>
         <nacos-client.vesion>2.3.0</nacos-client.vesion>
@@ -58,8 +58,8 @@
         <!-- 3rd-party versions -->
         <lombok.version>1.18.30</lombok.version>
         <slf4j.version>2.0.12</slf4j.version>
-        <hutool.version>5.8.25</hutool.version>
-        <sa-token.version>1.37.0</sa-token.version>
+        <hutool.version>5.8.28</hutool.version>
+        <sa-token.version>1.38.0</sa-token.version>
         <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
         <!-- 如果 mp 较新版存在问题,可以回退至 3.5.3.1 或 3.5.4 试试  -->
         <mybatis-plus.version>3.5.5</mybatis-plus.version>
@@ -90,8 +90,9 @@
 
 
         <!-- for mitigating vulnerabilities -->
-        <guava.version>33.0.0-jre</guava.version>
-        <commons-compress.version>1.26.0</commons-compress.version>
+        <guava.version>33.2.1-jre</guava.version>
+        <commons-compress.version>1.26.2</commons-compress.version>
+        <xnio-api.version>3.8.16.Final</xnio-api.version>
     </properties>
 
     <!-- 全局使用 -->
@@ -125,6 +126,12 @@
                 <version>${commons-compress.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.jboss.xnio</groupId>
+                <artifactId>xnio-api</artifactId>
+                <version>${xnio-api.version}</version>
+            </dependency>
+
             <!-- sub starters -->
             <dependency>
                 <groupId>cc.uncarbon.framework</groupId>