Ver código fonte

bump: 发布1.10.0

details see: https://helio.uncarbon.cc/#/i18n/zh-CN/appendix/change-log
Uncarbon 2 anos atrás
pai
commit
dee665b188
77 arquivos alterados com 1088 adições e 547 exclusões
  1. 1 1
      helio-core/pom.xml
  2. 38 34
      helio-core/src/main/java/cc/uncarbon/framework/core/config/AdaptHistoryVersionAutoConfiguration.java
  3. 67 38
      helio-core/src/main/java/cc/uncarbon/framework/core/constant/HelioConstant.java
  4. 3 2
      helio-core/src/main/java/cc/uncarbon/framework/core/context/TenantContextHolder.java
  5. 2 10
      helio-core/src/main/java/cc/uncarbon/framework/core/context/UserContext.java
  6. 3 2
      helio-core/src/main/java/cc/uncarbon/framework/core/context/UserContextHolder.java
  7. 6 6
      helio-core/src/main/java/cc/uncarbon/framework/core/enums/EnabledStatusEnum.java
  8. 7 5
      helio-core/src/main/java/cc/uncarbon/framework/core/enums/HelioBaseEnum.java
  9. 6 6
      helio-core/src/main/java/cc/uncarbon/framework/core/enums/YesOrNoEnum.java
  10. 29 14
      helio-core/src/main/java/cc/uncarbon/framework/core/exception/BusinessException.java
  11. 13 0
      helio-core/src/main/java/cc/uncarbon/framework/core/exception/HelioFrameworkException.java
  12. 13 0
      helio-core/src/main/java/cc/uncarbon/framework/core/exception/UnreachableCodeException.java
  13. 5 2
      helio-core/src/main/java/cc/uncarbon/framework/core/function/StreamFunction.java
  14. 25 11
      helio-core/src/main/java/cc/uncarbon/framework/core/page/PageParam.java
  15. 1 1
      helio-starter-aop/pom.xml
  16. 3 6
      helio-starter-cloud/pom.xml
  17. 3 1
      helio-starter-crud/pom.xml
  18. 1 1
      helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/config/HelioMybatisPlusAutoConfiguration.java
  19. 2 1
      helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/handler/HelioSequenceIdGenerateHandler.java
  20. 45 0
      helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/mapper/HelioBaseMapper.java
  21. 3 1
      helio-starter-dubbo/pom.xml
  22. 1 1
      helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ConsumerContextFilter.java
  23. 5 11
      helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/HelioDubboExceptionFilter.java
  24. 1 1
      helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ProviderContextFilter.java
  25. 2 1
      helio-starter-i18n/pom.xml
  26. 6 6
      helio-starter-i18n/src/main/java/cc/uncarbon/framework/i18n/util/I18nUtil.java
  27. 3 1
      helio-starter-knife4j/pom.xml
  28. 5 4
      helio-starter-knife4j/src/main/java/cc/uncarbon/framework/knife4j/config/HelioKnife4jAutoConfiguration.java
  29. 19 19
      helio-starter-knife4j/src/main/java/cc/uncarbon/framework/knife4j/config/Knife4jEnumPropertyAutoConfiguration.java
  30. 45 0
      helio-starter-rate-limit-redis/pom.xml
  31. 41 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/annotation/UseRateLimit.java
  32. 41 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/aspect/UseRateLimitAspect.java
  33. 20 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/constant/RateLimitConstant.java
  34. 13 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/exception/RateLimitStrategyException.java
  35. 36 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/exception/RateLimitedException.java
  36. 27 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/RateLimitStrategy.java
  37. 61 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitByClientIPStrategy.java
  38. 44 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitByUserIdStrategy.java
  39. 35 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitGlobalStrategy.java
  40. 86 0
      helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/SimpleRedisBasedRateLimitStrategy.java
  41. 4 0
      helio-starter-rate-limit-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  42. 2 1
      helio-starter-redis/pom.xml
  43. 23 15
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/config/HelioRedisAutoConfiguration.java
  44. 4 4
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/lock/impl/RedisDistributedLockImpl.java
  45. 42 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/RedisDistributedLockSupport.java
  46. 56 0
      helio-starter-redis/src/main/java/cc/uncarbon/framework/redis/support/impl/RedisDistributedLockSupportImpl.java
  47. 3 1
      helio-starter-rocketmq-aliyun/pom.xml
  48. 1 1
      helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/config/AliyunRocketAutoConfiguration.java
  49. 1 2
      helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/core/strategy/ProducerStrategy.java
  50. 1 0
      helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/core/utils/InterceptRocket.java
  51. 3 2
      helio-starter-satoken/pom.xml
  52. 0 65
      helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/interceptor/DefaultSaTokenParseInterceptor.java
  53. 0 45
      helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/util/IPUtil.java
  54. 0 0
      helio-starter-satoken/src/main/resources/PLACEHOLDER
  55. 2 1
      helio-starter-seata/pom.xml
  56. 3 1
      helio-starter-tenant/pom.xml
  57. 1 1
      helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/config/HelioTenantAutoConfiguration.java
  58. 1 0
      helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/support/TenantDataSourceSupport.java
  59. 1 0
      helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/support/TenantLineSupport.java
  60. 4 0
      helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/tenantdatasource/GlobalTenantDataSourceAdvisor.java
  61. 2 1
      helio-starter-test/pom.xml
  62. 3 1
      helio-starter-web/pom.xml
  63. 1 1
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/DefaultWebMvcAutoConfiguration.java
  64. 59 29
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/GlobalWebExceptionHandlerAutoConfiguration.java
  65. 3 2
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/XssAutoConfiguration.java
  66. 1 0
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/enums/GlobalWebExceptionI18nMessageEnum.java
  67. 2 2
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/jackson/EnumModule.java
  68. 4 4
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/jackson/LocalDateTimeDeserializer.java
  69. 5 3
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/listener/LaunchEventListener.java
  70. 1 0
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/model/response/ApiResult.java
  71. 4 4
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/IPUtil.java
  72. 2 2
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/InvalidFieldUtil.java
  73. 37 86
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/HTMLFilter.java
  74. 8 4
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/SQLFilter.java
  75. 1 8
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/XssFilter.java
  76. 21 20
      helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/XssHttpServletRequestWrapper.java
  77. 15 55
      pom.xml

+ 1 - 1
helio-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 38 - 34
helio-core/src/main/java/cc/uncarbon/framework/core/config/AdaptHistoryVersionAutoConfiguration.java

@@ -1,12 +1,9 @@
 package cc.uncarbon.framework.core.config;
 
-import cn.hutool.core.util.StrUtil;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
+import cn.hutool.core.text.CharSequenceUtil;
 import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.env.OriginTrackedMapPropertySource;
 import org.springframework.context.EnvironmentAware;
@@ -15,13 +12,19 @@ import org.springframework.core.env.Environment;
 import org.springframework.core.env.MutablePropertySources;
 import org.springframework.core.env.PropertySource;
 
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * 兼容历史版本 自动配置类
  *
  * @author Uncarbon
  */
-@AutoConfiguration
 @RequiredArgsConstructor
+@AutoConfiguration
+@Slf4j
 public class AdaptHistoryVersionAutoConfiguration implements EnvironmentAware {
 
     /**
@@ -48,46 +51,47 @@ public class AdaptHistoryVersionAutoConfiguration implements EnvironmentAware {
 
             Map<String, Object> newMap = new LinkedHashMap<>();
             for (PropertySource<?> source : sources) {
+                // 根据类判断是否为 SpringBoot .yml 或者 .properties 的配置
                 if (source instanceof OriginTrackedMapPropertySource) {
-                    // 根据类判断是否为 SpringBoot .yml 或者 .properties 的配置
-                    Map<String, Object> bootProp = ((OriginTrackedMapPropertySource) source).getSource();
-
-                    for (String key : bootProp.keySet()) {
-                        if (key != null) {
-                            for (int i = 0; i < oldPropertyPrefixes.length; i++) {
-                                if (key.startsWith(oldPropertyPrefixes[i])) {
-                                    /*
-                                    将配置文件中所有 [helio.crud.tenant.] 开头的配置转移到 [helio.tenant.] 下
-                                    实践测试 只对 @Value 注解有效
-                                     */
-                                    String newKey = StrUtil.replace(key, oldPropertyPrefixes[i], newPropertyPrefixes[i]);
-                                    newMap.put(newKey, bootProp.get(key));
-
-                                    // 前缀相同的话,提示信息就保持一条(利用 Set 特性)
-                                    deprecatedPropertyWarnings.add(
-                                            StrUtil.format("配置文件属性前缀 {}* 已过时、不向下兼容,请转移到 {}* 下"
-                                                    , oldPropertyPrefixes[i], newPropertyPrefixes[i])
-                                    );
-                                }
-                            }
-                        }
-                    }
+                    processPropertySource((OriginTrackedMapPropertySource) source, newMap);
                 }
             }
 
-            // 追加到总配置里面
-            if (newMap.size() > 0) {
+            if (!newMap.isEmpty()) {
+                // 追加到总配置里面
                 OriginTrackedMapPropertySource source = new OriginTrackedMapPropertySource(
                         "AdaptHistoryVersionAutoConfiguration", newMap);
                 // 追加到末尾,优先级最低
                 c.getPropertySources().addLast(source);
 
-                System.err.println("\n"
-                        + StrUtil.join("\n", deprecatedPropertyWarnings)
-                        + "\n");
+                log.warn("\n{}\n", CharSequenceUtil.join("\n", deprecatedPropertyWarnings));
             }
         } catch (Exception e) {
             // ignored
         }
     }
+
+    /**
+     * 将配置文件中所有 [helio.crud.tenant.] 开头的配置转移到 [helio.tenant.] 下
+     * 实践测试 只对 @Value 注解有效
+     */
+    private void processPropertySource(OriginTrackedMapPropertySource source, Map<String, Object> newMap) {
+        Map<String, Object> bootProp = source.getSource();
+
+        for (Map.Entry<String, Object> entry : bootProp.entrySet()) {
+            String key = entry.getKey();
+            for (int i = 0; i < oldPropertyPrefixes.length; i++) {
+                if (key.startsWith(oldPropertyPrefixes[i])) {
+                    String newKey = key.replace(oldPropertyPrefixes[i], newPropertyPrefixes[i]);
+                    newMap.put(newKey, bootProp.get(key));
+
+                    // 前缀相同的话,提示信息就保持一条(利用 Set 特性)
+                    deprecatedPropertyWarnings.add(
+                            CharSequenceUtil.format("配置文件属性前缀 {}* 已过时、不向下兼容,请转移到 {}* 下",
+                                    oldPropertyPrefixes[i], newPropertyPrefixes[i])
+                    );
+                }
+            }
+        }
+    }
 }

+ 67 - 38
helio-core/src/main/java/cc/uncarbon/framework/core/constant/HelioConstant.java

@@ -1,95 +1,124 @@
 package cc.uncarbon.framework.core.constant;
 
+import cn.hutool.core.date.DatePattern;
+
 /**
  * Helio 基础常量定义
  *
  * @author Uncarbon
  */
-public interface HelioConstant {
-    interface Dubbo {
-        String ENABLE_VALIDATION = "true";
-        int TIMEOUT = 10000;
-        int RETRIES = -1;
-        int RPC_EXCEPTION_RESPONSE_CODE = 1;
+public class HelioConstant {
+    private HelioConstant() {
+    }
+
+    public static class Dubbo {
+        private Dubbo() {
+        }
+
+        public static final String ENABLE_VALIDATION = "true";
+        public static final int TIMEOUT = 10000;
+        public static final int RETRIES = -1;
+        public static final int RPC_EXCEPTION_RESPONSE_CODE = 1;
     }
 
-    interface Message {
-        String NULL = "暂无数据";
-        String SUCCESS = "操作成功";
+    public static class Message {
+        private Message() {
+        }
+
+        public static final String NULL = "暂无数据";
+        public static final String SUCCESS = "操作成功";
     }
 
-    interface Version {
+    public static class Version {
+        private Version() {
+        }
+
         /**
          * HTTP API 版本 v1
          */
-        String HTTP_API_VERSION_V1 = "/api/v1";
+        public static final String HTTP_API_VERSION_V1 = "/api/v1";
 
         /**
          * DUBBO API 版本 v1
          */
-        String DUBBO_VERSION_V1 = "1.0.0";
+        public static final String DUBBO_VERSION_V1 = "1.0.0";
     }
 
-    interface Jackson {
-        String DATE_FORMAT = "yyyy-MM-dd";
-        String TIME_FORMAT = "HH:mm:ss";
-        String DATE_TIME_FORMAT = Jackson.DATE_FORMAT + " " + Jackson.TIME_FORMAT;
+    public static class Jackson {
+        private Jackson() {
+        }
+
+        public static final String DATE_FORMAT = DatePattern.NORM_DATE_PATTERN;
+        public static final String TIME_FORMAT = DatePattern.NORM_TIME_PATTERN;
+        public static final String DATE_TIME_FORMAT = DatePattern.NORM_DATETIME_PATTERN;
     }
 
-    interface CRUD {
+    public static class CRUD {
+        private CRUD() {
+        }
+
         /**
          * 租户ID
          */
-        String COLUMN_TENANT_ID = "tenant_id";
-        String ENTITY_FIELD_TENANT_ID = "tenantId";
+        public static final String COLUMN_TENANT_ID = "tenant_id";
+        public static final String ENTITY_FIELD_TENANT_ID = "tenantId";
 
         /**
          * 创建时刻
          */
-        String COLUMN_CREATED_AT = "created_at";
-        String ENTITY_FIELD_CREATED_AT = "createdAt";
+        public static final String COLUMN_CREATED_AT = "created_at";
+        public static final String ENTITY_FIELD_CREATED_AT = "createdAt";
 
         /**
          * 创建者
          */
-        String COLUMN_CREATED_BY = "created_by";
-        String ENTITY_FIELD_CREATED_BY = "createdBy";
+        public static final String COLUMN_CREATED_BY = "created_by";
+        public static final String ENTITY_FIELD_CREATED_BY = "createdBy";
 
         /**
          * 更新时刻
          */
-        String COLUMN_UPDATED_AT = "updated_at";
-        String ENTITY_FIELD_UPDATED_AT = "updatedAt";
+        public static final String COLUMN_UPDATED_AT = "updated_at";
+        public static final String ENTITY_FIELD_UPDATED_AT = "updatedAt";
 
         /**
          * 更新者
          */
-        String COLUMN_UPDATED_BY = "updated_by";
-        String ENTITY_FIELD_UPDATED_BY = "updatedBy";
+        public static final String COLUMN_UPDATED_BY = "updated_by";
+        public static final String ENTITY_FIELD_UPDATED_BY = "updatedBy";
 
         /**
          * SQL LIMIT 1
          */
-        String SQL_LIMIT_1 = " LIMIT 1";
+        public static final String SQL_LIMIT_1 = " LIMIT 1";
 
     }
 
-    interface Regex {
-        String CHINA_MAINLAND_PHONE_NO = "^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$";
-        String EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
+    public static class Regex {
+        private Regex() {
+        }
+
+        public static final String CHINA_MAINLAND_PHONE_NO = "^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$";
+        public static final String EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
     }
 
-    interface Permission {
-        String CREATE = "create";
-        String RETRIEVE = "retrieve";
-        String UPDATE = "update";
-        String DELETE = "delete";
+    public static class Permission {
+        private Permission() {
+        }
+
+        public static final String CREATE = "create";
+        public static final String RETRIEVE = "retrieve";
+        public static final String UPDATE = "update";
+        public static final String DELETE = "delete";
     }
 
-    interface Tenant {
+    public static class Tenant {
+        private Tenant() {
+        }
+
         /**
          * 该租户ID为超级租户, 可以无视租户SQL拦截器
          */
-        Long DEFAULT_PRIVILEGED_TENANT_ID = 0L;
+        public static final Long DEFAULT_PRIVILEGED_TENANT_ID = 0L;
     }
 }

+ 3 - 2
helio-core/src/main/java/cc/uncarbon/framework/core/context/TenantContextHolder.java

@@ -1,9 +1,10 @@
 package cc.uncarbon.framework.core.context;
 
 import com.alibaba.ttl.TransmittableThreadLocal;
-import java.util.Optional;
 import lombok.experimental.UtilityClass;
 
+import java.util.Optional;
+
 /**
  * 租户上下文持有者类
  *
@@ -36,7 +37,7 @@ public class TenantContextHolder {
      *
      * @param newContext 新上下文,传 null 则为清除
      */
-    public synchronized void setTenantContext(TenantContext newContext) {
+    public void setTenantContext(TenantContext newContext) {
         if (newContext == null) {
             THREAD_LOCAL_CONTEXT.remove();
             return;

+ 2 - 10
helio-core/src/main/java/cc/uncarbon/framework/core/context/UserContext.java

@@ -1,6 +1,6 @@
 package cc.uncarbon.framework.core.context;
 
-import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import cn.hutool.core.lang.Dict;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -10,7 +10,6 @@ import lombok.experimental.SuperBuilder;
 
 import java.io.Serializable;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -38,13 +37,6 @@ public class UserContext implements Serializable {
     @ApiModelProperty(value = "用户手机号")
     private String userPhoneNo;
 
-    /**
-     * @deprecated since 1.7.1, replaced by "userTypeStr"
-     */
-    @ApiModelProperty(value = "用户类型")
-    @Deprecated
-    private HelioBaseEnum<?> userType;
-
     @ApiModelProperty(value = "用户类型")
     private String userTypeStr;
 
@@ -55,7 +47,7 @@ public class UserContext implements Serializable {
     private List<String> roles;
 
     @ApiModelProperty(value = "附加数据")
-    private Map<String, Object> extraData;
+    private Dict extraData;
 
     @ApiModelProperty(value = "客户端IP地址")
     private String clientIP;

+ 3 - 2
helio-core/src/main/java/cc/uncarbon/framework/core/context/UserContextHolder.java

@@ -1,9 +1,10 @@
 package cc.uncarbon.framework.core.context;
 
 import com.alibaba.ttl.TransmittableThreadLocal;
-import java.util.Optional;
 import lombok.experimental.UtilityClass;
 
+import java.util.Optional;
+
 /**
  * 用户上下文持有者类
  *
@@ -36,7 +37,7 @@ public class UserContextHolder {
      *
      * @param newContext 新上下文,传 null 则为清除
      */
-    public synchronized void setUserContext(UserContext newContext) {
+    public void setUserContext(UserContext newContext) {
         if (newContext == null) {
             THREAD_LOCAL_CONTEXT.remove();
             return;

+ 6 - 6
helio-core/src/main/java/cc/uncarbon/framework/core/enums/EnabledStatusEnum.java

@@ -1,5 +1,6 @@
 package cc.uncarbon.framework.core.enums;
 
+import cc.uncarbon.framework.core.exception.UnreachableCodeException;
 import com.baomidou.mybatisplus.annotation.EnumValue;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -59,13 +60,12 @@ public enum EnabledStatusEnum implements HelioBaseEnum<Integer> {
             return null;
         }
 
-        switch (old) {
-            case DISABLED:
-                return ENABLED;
-            case ENABLED:
-                return DISABLED;
+        if (old == EnabledStatusEnum.DISABLED) {
+            return ENABLED;
+        } else if (old == EnabledStatusEnum.ENABLED) {
+            return DISABLED;
         }
 
-        throw new IllegalArgumentException();
+        throw new UnreachableCodeException();
     }
 }

+ 7 - 5
helio-core/src/main/java/cc/uncarbon/framework/core/enums/HelioBaseEnum.java

@@ -1,11 +1,13 @@
 package cc.uncarbon.framework.core.enums;
 
 import cc.uncarbon.framework.core.exception.BusinessException;
+import cc.uncarbon.framework.core.exception.HelioFrameworkException;
 import cn.hutool.core.collection.IterUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
+
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Optional;
@@ -121,7 +123,7 @@ public interface HelioBaseEnum<T> extends Serializable {
      */
     default int convertValue2Int() {
         if (this.getValue() == null) {
-            throw new IllegalArgumentException("枚举类的 value 不能为 null !");
+            throw new IllegalArgumentException("enum's value CANNOT be null");
         }
 
         if (this.getValue() instanceof Integer) {
@@ -140,7 +142,7 @@ public interface HelioBaseEnum<T> extends Serializable {
             return NumberUtil.toBigDecimal((String) this.getValue()).intValue();
         }
 
-        throw new IllegalArgumentException("枚举类的 value 不能自动转换为 int !");
+        throw new HelioFrameworkException("enum's value CANNOT convert to int");
     }
 
     default String formatLabel(Object... templateParams) {
@@ -148,7 +150,7 @@ public interface HelioBaseEnum<T> extends Serializable {
             return this.getLabel();
         }
 
-        return StrUtil.format(this.getLabel(), templateParams);
+        return CharSequenceUtil.format(this.getLabel(), templateParams);
     }
 
     /**
@@ -174,7 +176,7 @@ public interface HelioBaseEnum<T> extends Serializable {
      * @param templateParams label 中如果有占位符的话,向里面填充的模板参数
      */
     default void assertNotBlank(CharSequence cs, Object... templateParams) {
-        if (StrUtil.isNotBlank(cs)) {
+        if (CharSequenceUtil.isNotBlank(cs)) {
             return;
         }
 

+ 6 - 6
helio-core/src/main/java/cc/uncarbon/framework/core/enums/YesOrNoEnum.java

@@ -1,5 +1,6 @@
 package cc.uncarbon.framework.core.enums;
 
+import cc.uncarbon.framework.core.exception.UnreachableCodeException;
 import com.baomidou.mybatisplus.annotation.EnumValue;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -58,13 +59,12 @@ public enum YesOrNoEnum implements HelioBaseEnum<Integer> {
             return null;
         }
 
-        switch (old) {
-            case YES:
-                return NO;
-            case NO:
-                return YES;
+        if (old == YesOrNoEnum.YES) {
+            return NO;
+        } else if (old == YesOrNoEnum.NO) {
+            return YES;
         }
 
-        throw new IllegalArgumentException();
+        throw new UnreachableCodeException();
     }
 }

+ 29 - 14
helio-core/src/main/java/cc/uncarbon/framework/core/exception/BusinessException.java

@@ -1,9 +1,8 @@
 package cc.uncarbon.framework.core.exception;
 
 import cc.uncarbon.framework.core.enums.HelioBaseEnum;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.NonNull;
 
 import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
@@ -13,50 +12,61 @@ import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
  *
  * @author Uncarbon
  */
-@NoArgsConstructor
+@SuppressWarnings("squid:S1948")
 @Getter
 public class BusinessException extends RuntimeException {
 
-    private Integer code = null;
+    private final Integer code;
 
     /*
     若使用枚举类参数的构造方法创建的本异常,则记录对应枚举类及模板参数
      */
-    private Enum<?> customEnumField;
-    private Object[] templateParams;
+    private final Enum<?> customEnumField;
+    private final Object[] templateParams;
+
 
     /**
      * 仅错误信息
+     *
      * @param msg 错误信息
      */
     public BusinessException(String msg) {
         super(msg);
         this.code = HTTP_INTERNAL_ERROR;
+        this.customEnumField = null;
+        this.templateParams = null;
     }
 
     /**
      * 错误码 + 错误信息
+     *
      * @param code 错误码
-     * @param msg 错误信息
+     * @param msg  错误信息
      */
     public BusinessException(int code, String msg) {
         super(msg);
         this.code = code;
+        this.customEnumField = null;
+        this.templateParams = null;
     }
 
     /**
      * 错误码 + 错误信息(支持模板参数填充)
-     * @param code 错误码
-     * @param msg 错误信息
-     * @param templateParams 模板参数
+     *
+     * @param code            错误码
+     * @param msg             错误信息
+     * @param templateParams  模板参数
      */
     public BusinessException(int code, String msg, Object... templateParams) {
-        super(StrUtil.format(msg, templateParams));
+        super(CharSequenceUtil.format(msg, templateParams));
         this.code = code;
+        this.customEnumField = null;
+        this.templateParams = templateParams;
     }
 
     /**
      * 从枚举类生成异常
+     *
      * @param customEnum 枚举类对象
      */
     public BusinessException(@NonNull HelioBaseEnum<Integer> customEnum) {
@@ -69,13 +79,17 @@ public class BusinessException extends RuntimeException {
          */
         if (customEnum.getClass().isEnum()) {
             this.customEnumField = (Enum<?>) customEnum;
+        } else {
+            this.customEnumField = null;
         }
+        this.templateParams = null;
     }
 
     /**
      * 从枚举类生成异常(错误信息支持模板参数填充)
-     * @param customEnum 枚举类对象
-     * @param templateParams label 中如果有占位符的话,向里面填充的模板参数
+     *
+     * @param customEnum      枚举类对象
+     * @param templateParams  label 中如果有占位符的话,向里面填充的模板参数
      */
     public BusinessException(@NonNull HelioBaseEnum<Integer> customEnum, Object... templateParams) {
         super(customEnum.formatLabel(templateParams));
@@ -87,6 +101,8 @@ public class BusinessException extends RuntimeException {
          */
         if (customEnum.getClass().isEnum()) {
             this.customEnumField = (Enum<?>) customEnum;
+        } else {
+            this.customEnumField = null;
         }
         this.templateParams = templateParams;
     }
@@ -98,5 +114,4 @@ public class BusinessException extends RuntimeException {
     public synchronized Throwable fillInStackTrace() {
         return this;
     }
-
 }

+ 13 - 0
helio-core/src/main/java/cc/uncarbon/framework/core/exception/HelioFrameworkException.java

@@ -0,0 +1,13 @@
+package cc.uncarbon.framework.core.exception;
+
+/**
+ * Helio脚手架内部出错异常类
+ *
+ * @author Uncarbon
+ */
+public class HelioFrameworkException extends RuntimeException {
+
+    public HelioFrameworkException(String msg) {
+        super(msg);
+    }
+}

+ 13 - 0
helio-core/src/main/java/cc/uncarbon/framework/core/exception/UnreachableCodeException.java

@@ -0,0 +1,13 @@
+package cc.uncarbon.framework.core.exception;
+
+/**
+ * 不可达代码异常类
+ *
+ * @author Uncarbon
+ */
+public class UnreachableCodeException extends RuntimeException {
+
+    public UnreachableCodeException() {
+        super("Execution path unreachable");
+    }
+}

+ 5 - 2
helio-core/src/main/java/cc/uncarbon/framework/core/function/StreamFunction.java

@@ -10,10 +10,13 @@ import java.util.function.BinaryOperator;
  */
 public class StreamFunction {
 
+    private StreamFunction() {
+    }
+
     /**
      * 用于 stream-collect-toMap 时,直接覆盖已存在的 key;不忽略的话,遇到重复项时默认会抛出异常
-     *
-     * 用法:stream().collect(Collectors.toMap( XXX, XXX, StreamFunction.ignoredThrowingMerger()))
+     * <p>
+     * 用法:stream().collect(Collectors.toMap(XXX, YYY, StreamFunction.ignoredThrowingMerger()))
      *
      * @param <T> 键类型
      * @return 键名

+ 25 - 11
helio-core/src/main/java/cc/uncarbon/framework/core/page/PageParam.java

@@ -7,7 +7,7 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
-import java.util.function.Function;
+import java.util.function.UnaryOperator;
 
 /**
  * 分页查询参数
@@ -20,16 +20,6 @@ import java.util.function.Function;
 @Data
 public class PageParam implements Serializable {
 
-    /**
-     * 全局分页页码限制函数
-     */
-    public static Function<Integer, Integer> globalPageNumLimiter = null;
-
-    /**
-     * 全局分页大小限制函数(如:限制页大小上限为1000)
-     */
-    public static Function<Integer, Integer> globalPageSizeLimiter = null;
-
     @ApiModelProperty(value = "当前页码")
     private Integer pageNum;
     public Integer getPageNum() {
@@ -55,4 +45,28 @@ public class PageParam implements Serializable {
         }
         return pageSize;
     }
+
+    /**
+     * 全局分页页码限制器
+     */
+    private static UnaryOperator<Integer> globalPageNumLimiter = null;
+
+    /**
+     * 指定全局分页页码限制器
+     */
+    public static synchronized void setGlobalPageNumLimiter(UnaryOperator<Integer> globalPageNumLimiter) {
+        PageParam.globalPageNumLimiter = globalPageNumLimiter;
+    }
+
+    /**
+     * 全局分页大小限制器(如:限制页大小上限为1000)
+     */
+    private static UnaryOperator<Integer> globalPageSizeLimiter = null;
+
+    /**
+     * 指定全局分页大小限制器
+     */
+    public static synchronized void setGlobalPageSizeLimiter(UnaryOperator<Integer> globalPageSizeLimiter) {
+        PageParam.globalPageSizeLimiter = globalPageSizeLimiter;
+    }
 }

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 3 - 6
helio-starter-cloud/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,11 +17,13 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-bootstrap</artifactId>
@@ -42,10 +44,5 @@
             <artifactId>nacos-client</artifactId>
             <version>${nacos-client.vesion}</version>
         </dependency>
-
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-        </dependency>
     </dependencies>
 </project>

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -16,11 +16,13 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>

+ 1 - 1
helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/config/HelioMybatisPlusAutoConfiguration.java

@@ -27,9 +27,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @EnableTransactionManagement(
         proxyTargetClass = true
 )
+@RequiredArgsConstructor
 @AutoConfiguration
 @Slf4j
-@RequiredArgsConstructor
 public class HelioMybatisPlusAutoConfiguration {
 
     private final HelioProperties helioProperties;

+ 2 - 1
helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/handler/HelioSequenceIdGenerateHandler.java

@@ -1,6 +1,7 @@
 package cc.uncarbon.framework.crud.handler;
 
 import cc.uncarbon.framework.core.enums.IdGeneratorStrategyEnum;
+import cn.hutool.core.net.NetUtil;
 import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
 import lombok.extern.slf4j.Slf4j;
 
@@ -13,7 +14,7 @@ import lombok.extern.slf4j.Slf4j;
 public class HelioSequenceIdGenerateHandler extends DefaultIdentifierGenerator {
 
     public HelioSequenceIdGenerateHandler() {
-        super();
+        super(NetUtil.getLocalhost());
         log.info("[主键ID生成器] >> strategy=[{}]",
                 IdGeneratorStrategyEnum.SEQUENCE
         );

+ 45 - 0
helio-starter-crud/src/main/java/cc/uncarbon/framework/crud/mapper/HelioBaseMapper.java

@@ -0,0 +1,45 @@
+package cc.uncarbon.framework.crud.mapper;
+
+import cc.uncarbon.framework.core.function.StreamFunction;
+import cc.uncarbon.framework.crud.entity.HelioBaseEntity;
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 持久层基础模板,在Mybatis-Plus BaseMapper上进一步增加功能
+ * E = ENTITY 实体类
+ * T = 实体类主键ID类型
+ *
+ * @author Uncarbon
+ */
+public interface HelioBaseMapper<E extends HelioBaseEntity<T>, T extends Serializable> extends BaseMapper<E> {
+
+    default List<E> selectByIds(Collection<T> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return this.selectList(new LambdaQueryWrapper<E>().in(HelioBaseEntity::getId, ids));
+    }
+
+    /**
+     * 取ID👉名 映射map
+     */
+    default Map<T, String> getNameMap(Class<E> entityClass, Collection<T> ids,
+                                      Function<E, T> idMapper, Function<E, String> nameMapper) {
+
+        List<E> entityList = this.selectByIds(ids);
+        if (CollUtil.isEmpty(entityList)) {
+            return Collections.emptyMap();
+        }
+        return entityList.stream().collect(Collectors.toMap(idMapper, nameMapper, StreamFunction.ignoredThrowingMerger()));
+    }
+}

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,11 +17,13 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-spring-boot-starter</artifactId>

+ 1 - 1
helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ConsumerContextFilter.java

@@ -15,8 +15,8 @@ import org.apache.dubbo.rpc.*;
  * @author Zhu JW
  * @author Uncarbon
  **/
-@Slf4j
 @Activate(group = CommonConstants.CONSUMER)
+@Slf4j
 public class ConsumerContextFilter implements Filter {
 
     /**

+ 5 - 11
helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/HelioDubboExceptionFilter.java

@@ -2,17 +2,14 @@ package cc.uncarbon.framework.dubbo.filter;
 
 import cc.uncarbon.framework.core.exception.BusinessException;
 import cn.hutool.core.util.StrUtil;
-import java.lang.reflect.Method;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.common.utils.ReflectUtils;
-import org.apache.dubbo.rpc.Invocation;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.Result;
-import org.apache.dubbo.rpc.RpcContext;
-import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.*;
 import org.apache.dubbo.rpc.filter.ExceptionFilter;
 import org.apache.dubbo.rpc.service.GenericService;
 
+import java.lang.reflect.Method;
+
 /**
  * 自定义Dubbo异常处理
  *
@@ -22,8 +19,6 @@ import org.apache.dubbo.rpc.service.GenericService;
 @Slf4j
 public class HelioDubboExceptionFilter extends ExceptionFilter {
 
-    private static final String BUSINESS_EXCEPTION_SIMPLE_CLASS_NAME = BusinessException.class.getSimpleName();
-
     @Override
     public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
         if (!(
@@ -75,9 +70,8 @@ public class HelioDubboExceptionFilter extends ExceptionFilter {
             if (exception instanceof RpcException) {
                 return;
             }
-
-            // 业务异常
-            if (className.contains(BUSINESS_EXCEPTION_SIMPLE_CLASS_NAME)) {
+            // directly throw if it's BusinessException
+            if (exception instanceof BusinessException) {
                 return;
             }
 

+ 1 - 1
helio-starter-dubbo/src/main/java/cc/uncarbon/framework/dubbo/filter/ProviderContextFilter.java

@@ -15,8 +15,8 @@ import org.apache.dubbo.rpc.*;
  * @author Zhu JW
  * @author Uncarbon
  **/
-@Slf4j
 @Activate(group = CommonConstants.PROVIDER)
+@Slf4j
 public class ProviderContextFilter implements Filter {
 
     /**

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>

+ 6 - 6
helio-starter-i18n/src/main/java/cc/uncarbon/framework/i18n/util/I18nUtil.java

@@ -1,8 +1,8 @@
 package cc.uncarbon.framework.i18n.util;
 
 import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.core.text.StrPool;
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
@@ -67,13 +67,13 @@ public class I18nUtil {
 
         try {
             String msg = getMessageSource().getMessage(code, null, locale != null ? locale : getDefaultLocale());
-            if (StrUtil.isEmpty(msg)) {
+            if (CharSequenceUtil.isEmpty(msg)) {
                 return msg;
             }
 
-            if (StrUtil.contains(msg, SLF4J_STYLE_PLACEHOLDER)) {
+            if (CharSequenceUtil.contains(msg, SLF4J_STYLE_PLACEHOLDER)) {
                 // 使用 hutool 的模板填充
-                return StrUtil.format(msg, templateParams);
+                return CharSequenceUtil.format(msg, templateParams);
             }
 
             return msg;
@@ -142,14 +142,14 @@ public class I18nUtil {
         // 尝试以较长的 枚举类短名.枚举值name 为 code 尝试获取翻译值
         i18nCode = String.format("%s.%s", enumField.getDeclaringClass().getSimpleName(), enumField.name());
         String i18nMessage = messageOf(locale, i18nCode, templateParams);
-        if (StrUtil.isNotEmpty(i18nMessage)) {
+        if (CharSequenceUtil.isNotEmpty(i18nMessage)) {
             return i18nMessage;
         }
 
         // 尝试以 枚举值name 为 code 尝试获取翻译值
         i18nCode = enumField.name();
         i18nMessage = messageOf(locale, i18nCode, templateParams);
-        if (StrUtil.isNotEmpty(i18nMessage)) {
+        if (CharSequenceUtil.isNotEmpty(i18nMessage)) {
             return i18nMessage;
         }
 

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,11 +17,13 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-spring-boot-starter</artifactId>

+ 5 - 4
helio-starter-knife4j/src/main/java/cc/uncarbon/framework/knife4j/config/HelioKnife4jAutoConfiguration.java

@@ -3,9 +3,6 @@ package cc.uncarbon.framework.knife4j.config;
 
 import cc.uncarbon.framework.core.props.HelioProperties;
 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -25,6 +22,10 @@ import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spi.service.contexts.SecurityContext;
 import springfox.documentation.spring.web.plugins.Docket;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 
 /**
  * Helio knife4j 自动配置类
@@ -33,10 +34,10 @@ import springfox.documentation.spring.web.plugins.Docket;
  * @author Uncarbon
  * @author xiaoymin
  */
-@RequiredArgsConstructor
 @EnableOpenApi
 @EnableKnife4j
 @Import(BeanValidatorPluginsConfiguration.class)
+@RequiredArgsConstructor
 @AutoConfiguration
 public class HelioKnife4jAutoConfiguration {
 

+ 19 - 19
helio-starter-knife4j/src/main/java/cc/uncarbon/framework/knife4j/config/Knife4jEnumPropertyAutoConfiguration.java

@@ -1,8 +1,8 @@
 package cc.uncarbon.framework.knife4j.config;
 
 import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
 import io.swagger.annotations.ApiModelProperty;
-import java.util.Optional;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import springfox.documentation.schema.Annotations;
@@ -11,15 +11,19 @@ import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
 import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
 import springfox.documentation.swagger.schema.ApiModelProperties;
 
+import java.lang.reflect.AnnotatedElement;
+import java.util.Optional;
+
 /**
  * knife4j 枚举增强自动配置类
  * 非生产环境下,为 knife4j 文档生成枚举类描述
- * 示例代码:
- *     @ApiModelProperty(value = "性别")
- *     private GenderEnum gender;
- *
+ * <p>
+ * 示例代码:<br />
+ * {@code @ApiModelProperty(value} = "性别")<br />
+ * private GenderEnum gender;
+ * <p>
  * 输出文档描述:
- *     性别(0=未知 1=男 2=女)
+ * 性别(0=未知 1=男 2=女)
  *
  * @author Demon-HY
  * @author Uncarbon
@@ -30,20 +34,18 @@ public class Knife4jEnumPropertyAutoConfiguration implements ModelPropertyBuilde
 
     @Override
     public void apply(ModelPropertyContext context) {
-
         Optional<ApiModelProperty> annotation = Optional.empty();
         Class<?> rawPrimaryType = null;
 
-        if (context.getAnnotatedElement().isPresent()) {
-            annotation = ApiModelProperties.findApiModePropertyAnnotation(context.getAnnotatedElement().get());
+        Optional<AnnotatedElement> annotatedElement = context.getAnnotatedElement();
+        if (annotatedElement.isPresent()) {
+            annotation = ApiModelProperties.findApiModePropertyAnnotation(annotatedElement.get());
         }
 
-        if (context.getBeanPropertyDefinition().isPresent()) {
-            annotation = Annotations.findPropertyAnnotation(
-                    context.getBeanPropertyDefinition().get(),
-                    ApiModelProperty.class);
-
-            rawPrimaryType = context.getBeanPropertyDefinition().get().getRawPrimaryType();
+        Optional<BeanPropertyDefinition> beanPropertyDefinition = context.getBeanPropertyDefinition();
+        if (beanPropertyDefinition.isPresent()) {
+            annotation = Annotations.findPropertyAnnotation(beanPropertyDefinition.get(), ApiModelProperty.class);
+            rawPrimaryType = beanPropertyDefinition.get().getRawPrimaryType();
         }
 
         // 过滤得到目标类型
@@ -56,15 +58,13 @@ public class Knife4jEnumPropertyAutoConfiguration implements ModelPropertyBuilde
              */
             StringBuilder newDescription = new StringBuilder(128)
                     .append(annotation.get().value())
-                    .append("(")
-                    ;
+                    .append("(");
 
             for (int i = 0; i < enumItems.length; i++) {
                 newDescription
                         .append(enumItems[i].getValue())
                         .append("=")
-                        .append(enumItems[i].getLabel())
-                        ;
+                        .append(enumItems[i].getLabel());
 
                 if (i < enumItems.length - 1) {
                     // 不是最后一项,增加分割符

+ 45 - 0
helio-starter-rate-limit-redis/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>helio-starters</artifactId>
+        <groupId>cc.uncarbon.framework</groupId>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>helio-starter-rate-limit-redis</artifactId>
+    <description>基于Redis实现的限流组件,内置简易的3种限流策略(全局限流、根据当前用户ID限流、根据客户端IP限流)</description>
+
+    <properties>
+
+    </properties>
+
+    <dependencies>
+        <!-- 脚手架starter -->
+        <dependency>
+            <groupId>cc.uncarbon.framework</groupId>
+            <artifactId>helio-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.uncarbon.framework</groupId>
+            <artifactId>helio-starter-aop</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cc.uncarbon.framework</groupId>
+            <artifactId>helio-starter-redis</artifactId>
+        </dependency>
+
+        <!-- 仅 RateLimitByClientIPStrategy 需要该依赖,若不使用可排除 -->
+        <dependency>
+            <groupId>cc.uncarbon.framework</groupId>
+            <artifactId>helio-starter-web</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 41 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/annotation/UseRateLimit.java

@@ -0,0 +1,41 @@
+package cc.uncarbon.framework.ratelimit.annotation;
+
+import cc.uncarbon.framework.ratelimit.stratrgy.RateLimitStrategy;
+import cc.uncarbon.framework.ratelimit.stratrgy.impl.RateLimitGlobalStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * 标记使用服务端限流,单接口在{duration}秒内只能被请求{max}次
+ * 底层实现:Redis + 计数器
+ * 备选名:FrequencyControl
+ *
+ * @author ruoyi
+ * @author Uncarbon
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface UseRateLimit {
+
+    /**
+     * 限流标识文本,会拼接至Redis Key中
+     * 空文本则会使用Java方法的全限定名
+     */
+    String mark() default "";
+
+    /**
+     * 限流时长,单位=秒
+     */
+    int duration() default 60;
+
+    /**
+     * 在限流时长内,最多可请求多少次
+     */
+    int max() default 100;
+
+    /**
+     * 限流策略;默认为全局限流
+     */
+    Class<? extends RateLimitStrategy> strategy() default RateLimitGlobalStrategy.class;
+}

+ 41 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/aspect/UseRateLimitAspect.java

@@ -0,0 +1,41 @@
+package cc.uncarbon.framework.ratelimit.aspect;
+
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.stratrgy.RateLimitStrategy;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.BeansException;
+
+/**
+ * 限流注解关联切面
+ *
+ * @author ruoyi
+ * @author Uncarbon
+ */
+@Aspect
+@RequiredArgsConstructor
+@Slf4j
+public class UseRateLimitAspect {
+
+    @Before("@annotation(annotation)")
+    public void before(JoinPoint point, UseRateLimit annotation) {
+        // 确定限流策略实例
+        Class<? extends RateLimitStrategy> strategyClass = annotation.strategy();
+        RateLimitStrategy strategyInstance = null;
+        try {
+            strategyInstance = SpringUtil.getBean(strategyClass);
+        } catch (BeansException be) {
+            // 无法从Spring容器中得到策略类实例
+        }
+        if (strategyInstance == null) {
+            strategyInstance = ReflectUtil.newInstance(strategyClass);
+        }
+
+        strategyInstance.performRateLimitCheck(annotation, point);
+    }
+}

+ 20 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/constant/RateLimitConstant.java

@@ -0,0 +1,20 @@
+package cc.uncarbon.framework.ratelimit.constant;
+
+/**
+ * 限流组件常量
+ */
+public final class RateLimitConstant {
+
+    private RateLimitConstant() {}
+
+    /**
+     * Redis Key前缀
+     */
+    public static final String REDIS_KEY_PREFIX = "rateLimit:";
+
+    /**
+     * 未知客户端IP地址
+     */
+    public static final String CLIENT_IP_UNKNOWN = "unknown";
+
+}

+ 13 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/exception/RateLimitStrategyException.java

@@ -0,0 +1,13 @@
+package cc.uncarbon.framework.ratelimit.exception;
+
+import cc.uncarbon.framework.core.exception.HelioFrameworkException;
+
+/**
+ * 限流策略异常
+ */
+public class RateLimitStrategyException extends HelioFrameworkException {
+
+    public RateLimitStrategyException(String msg) {
+        super(msg);
+    }
+}

+ 36 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/exception/RateLimitedException.java

@@ -0,0 +1,36 @@
+package cc.uncarbon.framework.ratelimit.exception;
+
+import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import cc.uncarbon.framework.core.exception.BusinessException;
+import cn.hutool.http.HttpStatus;
+import lombok.NonNull;
+
+/**
+ * 已被限流异常
+ */
+public class RateLimitedException extends BusinessException {
+
+    public RateLimitedException() {
+        super(HttpStatus.HTTP_TOO_MANY_REQUESTS, "操作频率不要太快");
+    }
+
+    public RateLimitedException(String msg) {
+        super(msg);
+    }
+
+    public RateLimitedException(int code, String msg) {
+        super(code, msg);
+    }
+
+    public RateLimitedException(int code, String msg, Object... templateParams) {
+        super(code, msg, templateParams);
+    }
+
+    public RateLimitedException(@NonNull HelioBaseEnum<Integer> customEnum) {
+        super(customEnum);
+    }
+
+    public RateLimitedException(@NonNull HelioBaseEnum<Integer> customEnum, Object... templateParams) {
+        super(customEnum, templateParams);
+    }
+}

+ 27 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/RateLimitStrategy.java

@@ -0,0 +1,27 @@
+package cc.uncarbon.framework.ratelimit.stratrgy;
+
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.exception.RateLimitedException;
+import org.aspectj.lang.JoinPoint;
+
+/**
+ * 限流策略接口
+ */
+public interface RateLimitStrategy {
+
+    /**
+     * 限流异常生产者
+     * 通过覆写本方法,可替换提示消息、实现国际化文案等
+     */
+    default RateLimitedException rateLimitedExceptionSupplier() {
+        return new RateLimitedException();
+    }
+
+    /**
+     * 执行限流检查,根据不同维度判断是否已触达限流上限
+     * @param annotation 注解实例
+     * @param point 切点
+     */
+    void performRateLimitCheck(UseRateLimit annotation, JoinPoint point);
+
+}

+ 61 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitByClientIPStrategy.java

@@ -0,0 +1,61 @@
+package cc.uncarbon.framework.ratelimit.stratrgy.impl;
+
+import cc.uncarbon.framework.core.context.UserContextHolder;
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.constant.RateLimitConstant;
+import cc.uncarbon.framework.ratelimit.stratrgy.RateLimitStrategy;
+import cc.uncarbon.framework.web.util.IPUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.text.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * 以客户端IP为维度限流策略
+ * 基于{@link SimpleRedisBasedRateLimitStrategy}使用相同Lua脚本,仅在RedisKey有差异
+ */
+@Slf4j
+public class RateLimitByClientIPStrategy extends SimpleRedisBasedRateLimitStrategy implements RateLimitStrategy {
+
+    public RateLimitByClientIPStrategy(RedisTemplate<String, Object> objectRedisTemplate) {
+        super(objectRedisTemplate, "RateLimitByClientIPStrategy");
+    }
+
+    @Override
+    public void performRateLimitCheck(UseRateLimit annotation, JoinPoint point) {
+        super.performRateLimitCheck(annotation, point, this::rateLimitedExceptionSupplier);
+    }
+
+    @Override
+    protected String determineRedisKey(UseRateLimit annotation, JoinPoint point) {
+        final String splitter = StrPool.COLON;
+        return RateLimitConstant.REDIS_KEY_PREFIX + "ip" + splitter + currentClientIP() + splitter + determineMark(annotation, point);
+    }
+
+    /**
+     * 取当前客户端IP
+     */
+    protected String currentClientIP() {
+        String clientIP = UserContextHolder.getClientIP();
+        if (CharSequenceUtil.isNotEmpty(clientIP)) {
+            return clientIP;
+        }
+
+        // 尝试从request中得到未登录用户的IP地址
+        try {
+            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (servletRequestAttributes != null) {
+                return IPUtil.getClientIPAddress(servletRequestAttributes.getRequest(), 0);
+            }
+        } catch (Exception | NoClassDefFoundError e) {
+            // 可能没有引入 helio-starter-web 依赖,忽略
+        }
+
+        // 兜底
+        return RateLimitConstant.CLIENT_IP_UNKNOWN;
+    }
+
+}

+ 44 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitByUserIdStrategy.java

@@ -0,0 +1,44 @@
+package cc.uncarbon.framework.ratelimit.stratrgy.impl;
+
+import cc.uncarbon.framework.core.context.UserContextHolder;
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.constant.RateLimitConstant;
+import cc.uncarbon.framework.ratelimit.stratrgy.RateLimitStrategy;
+import cn.hutool.core.text.StrPool;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.math.BigInteger;
+
+/**
+ * 以当前用户ID为维度限流策略
+ * 基于{@link SimpleRedisBasedRateLimitStrategy}使用相同Lua脚本,仅在RedisKey有差异
+ */
+@Slf4j
+public class RateLimitByUserIdStrategy extends SimpleRedisBasedRateLimitStrategy implements RateLimitStrategy {
+
+    public RateLimitByUserIdStrategy(RedisTemplate<String, Object> objectRedisTemplate) {
+        super(objectRedisTemplate, "RateLimitByUserIdStrategy");
+    }
+
+    @Override
+    public void performRateLimitCheck(UseRateLimit annotation, JoinPoint point) {
+        super.performRateLimitCheck(annotation, point, this::rateLimitedExceptionSupplier);
+    }
+
+    @Override
+    protected String determineRedisKey(UseRateLimit annotation, JoinPoint point) {
+        final String splitter = StrPool.COLON;
+        return RateLimitConstant.REDIS_KEY_PREFIX + "userId" + splitter + currentUserId() + splitter + determineMark(annotation, point);
+    }
+
+    /**
+     * 取当前用户ID
+     */
+    protected Long currentUserId() {
+        // 默认值=0
+        return ObjectUtil.defaultIfNull(UserContextHolder.getUserId(), BigInteger.ZERO::longValue);
+    }
+}

+ 35 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/RateLimitGlobalStrategy.java

@@ -0,0 +1,35 @@
+package cc.uncarbon.framework.ratelimit.stratrgy.impl;
+
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.constant.RateLimitConstant;
+import cc.uncarbon.framework.ratelimit.stratrgy.RateLimitStrategy;
+import cn.hutool.core.text.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * 无维度区分,全局限流策略
+ */
+@Slf4j
+public class RateLimitGlobalStrategy extends SimpleRedisBasedRateLimitStrategy implements RateLimitStrategy {
+
+    public RateLimitGlobalStrategy(RedisTemplate<String, Object> objectRedisTemplate) {
+        super(objectRedisTemplate, "RateLimitGlobalStrategy");
+    }
+
+    @Override
+    public void performRateLimitCheck(UseRateLimit annotation, JoinPoint point) {
+        super.performRateLimitCheck(annotation, point, this::rateLimitedExceptionSupplier);
+    }
+
+    /**
+     * 确定RedisKey
+     */
+    @Override
+    protected String determineRedisKey(UseRateLimit annotation, JoinPoint point) {
+        final String splitter = StrPool.COLON;
+        // 由编译器自动使用StringBuilder拼接,下同
+        return RateLimitConstant.REDIS_KEY_PREFIX + "global" + splitter + determineMark(annotation, point);
+    }
+}

+ 86 - 0
helio-starter-rate-limit-redis/src/main/java/cc/uncarbon/framework/ratelimit/stratrgy/impl/SimpleRedisBasedRateLimitStrategy.java

@@ -0,0 +1,86 @@
+package cc.uncarbon.framework.ratelimit.stratrgy.impl;
+
+import cc.uncarbon.framework.ratelimit.annotation.UseRateLimit;
+import cc.uncarbon.framework.ratelimit.exception.RateLimitStrategyException;
+import cc.uncarbon.framework.ratelimit.exception.RateLimitedException;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.text.StrPool;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.function.Supplier;
+
+/**
+ * 脚手架预置的简易限流策略基类
+ */
+@RequiredArgsConstructor
+@Slf4j
+public abstract class SimpleRedisBasedRateLimitStrategy {
+
+    private final RedisTemplate<String, Object> objectRedisTemplate;
+    private final String realClassName;
+
+    /**
+     * 预置限流Lua脚本
+     */
+    private static final String LUA_SCRIPT = "local key = KEYS[1]\n" +
+            "local count = tonumber(ARGV[1])\n" +
+            "local time = tonumber(ARGV[2])\n" +
+            "local current = redis.call('get', key);\n" +
+            "if current and tonumber(current) > count then\n" +
+            "    return tonumber(current);\n" +
+            "end\n" +
+            "current = redis.call('incr', key)\n" +
+            "if tonumber(current) == 1 then\n" +
+            "    redis.call('expire', key, time)\n" +
+            "end\n" +
+            "return tonumber(current);";
+
+    /**
+     * 预置限流Lua脚本的 RedisScript 类实例
+     */
+    private static final RedisScript<Long> REDIS_SCRIPT = RedisScript.of(LUA_SCRIPT, Long.class);
+
+
+    protected void performRateLimitCheck(UseRateLimit annotation, JoinPoint point, Supplier<RateLimitedException> rateLimitedExceptionSupplier) throws RateLimitStrategyException {
+        long duration = annotation.duration();
+        long max = annotation.max();
+
+        String redisKey = determineRedisKey(annotation, point);
+        Long current = objectRedisTemplate.execute(REDIS_SCRIPT, Collections.singletonList(redisKey), max, duration);
+        if (current == null) {
+            log.error("SimpleRedisBasedRateLimitStrategy.REDIS_SCRIPT executed but no result.");
+            throw new RateLimitStrategyException("SCRIPT executed but no result");
+        }
+        if (current > max) {
+            log.info("[UseRateLimit][{}] 键名 {} 已触达限流阈值 {}", realClassName, redisKey, max);
+            throw rateLimitedExceptionSupplier.get();
+        }
+        log.info("[UseRateLimit][{}] 键名 {} 限流进度 {}/{}", realClassName, redisKey, current, max);
+    }
+
+    /**
+     * 确定RedisKey
+     */
+    protected abstract String determineRedisKey(UseRateLimit annotation, JoinPoint point);
+
+    /**
+     * 确定被限流方法的标识
+     */
+    protected String determineMark(UseRateLimit annotation, JoinPoint point) {
+        if (CharSequenceUtil.isNotEmpty(annotation.mark())) {
+            return annotation.mark();
+        }
+        // 使用Java方法的全限定名
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        return targetClass.getName() + StrPool.DASHED + method.getName();
+    }
+}

+ 4 - 0
helio-starter-rate-limit-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,4 @@
+cc.uncarbon.framework.ratelimit.aspect.UseRateLimitAspect
+cc.uncarbon.framework.ratelimit.stratrgy.impl.RateLimitGlobalStrategy
+cc.uncarbon.framework.ratelimit.stratrgy.impl.RateLimitByUserIdStrategy
+cc.uncarbon.framework.ratelimit.stratrgy.impl.RateLimitByClientIPStrategy

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-boot-starter</artifactId>

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

@@ -2,6 +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 lombok.RequiredArgsConstructor;
 import org.redisson.api.RedissonClient;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -23,19 +25,16 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
  * @author Mark sunlightcs@gmail.com
  * @author Uncarbon
  */
-@RequiredArgsConstructor
 @EnableCaching
 @ConditionalOnClass(RedisConnectionFactory.class)
+@RequiredArgsConstructor
 @AutoConfiguration
 public class HelioRedisAutoConfiguration {
 
-    private final RedisConnectionFactory factory;
-    private final RedissonClient redissonClient;
-
-
+    @SuppressWarnings("squid:S1452")
     @Bean
     @ConditionalOnMissingBean
-    public RedisTemplate<?, ?> redisTemplate() {
+    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) {
         RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
 
         redisTemplate.setConnectionFactory(factory);
@@ -53,15 +52,6 @@ public class HelioRedisAutoConfiguration {
         return redisTemplate;
     }
 
-    /**
-     * 基于 Redisson 的分布式锁
-     */
-    @Bean
-    @ConditionalOnMissingBean
-    public RedisDistributedLock redisDistributedLock() {
-        return new RedisDistributedLockImpl(redissonClient);
-    }
-
     /**
      * 缓存键名生成规则
      */
@@ -81,4 +71,22 @@ public class HelioRedisAutoConfiguration {
             return sb.toString();
         };
     }
+
+    /**
+     * 基于 Redisson 的分布式锁
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public RedisDistributedLock redisDistributedLock(RedissonClient redissonClient) {
+        return new RedisDistributedLockImpl(redissonClient);
+    }
+
+    /**
+     * Redis分布式可重入锁辅助类
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public RedisDistributedLockSupport redisDistributedLockSupport(RedisDistributedLock redisDistributedLock) {
+        return new RedisDistributedLockSupportImpl(redisDistributedLock);
+    }
 }

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

@@ -1,10 +1,9 @@
 package cc.uncarbon.framework.redis.lock.impl;
 
 import cc.uncarbon.framework.redis.lock.RedisDistributedLock;
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
-import org.springframework.stereotype.Service;
 
 import java.util.concurrent.TimeUnit;
 
@@ -14,8 +13,7 @@ import java.util.concurrent.TimeUnit;
  * @author dcy
  * @author Uncarbon
  */
-@AllArgsConstructor
-@Service
+@RequiredArgsConstructor
 public class RedisDistributedLockImpl implements RedisDistributedLock {
 
     /**
@@ -31,6 +29,7 @@ public class RedisDistributedLockImpl implements RedisDistributedLock {
         return this.lock(lockName, TimeUnit.SECONDS, holdDuration);
     }
 
+    @SuppressWarnings("squid:S2222")
     @Override
     public RLock lock(String lockName, TimeUnit unit, int holdDuration) {
         RLock lock = this.getRedissonLockByName(lockName);
@@ -44,6 +43,7 @@ public class RedisDistributedLockImpl implements RedisDistributedLock {
         return this.tryLock(lockName, TimeUnit.SECONDS, waitDuration, holdDuration);
     }
 
+    @SuppressWarnings({"squid:S2142", "squid:S2222"})
     @Override
     public boolean tryLock(String lockName, TimeUnit unit, int waitDuration, int holdDuration) {
         RLock lock = this.getRedissonLockByName(lockName);

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

@@ -0,0 +1,42 @@
+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);
+}

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

@@ -0,0 +1,56 @@
+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;
+    }
+}

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
@@ -27,6 +28,7 @@
             <artifactId>helio-starter-aop</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>com.aliyun.openservices</groupId>
             <artifactId>ons-client</artifactId>

+ 1 - 1
helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/config/AliyunRocketAutoConfiguration.java

@@ -39,8 +39,8 @@ import java.util.concurrent.ConcurrentHashMap;
  * @author ThierrySquirrel
  * @author Uncarbon
  */
-@RequiredArgsConstructor
 @EnableConfigurationProperties(value = {AliyunRocketProperties.class})
+@RequiredArgsConstructor
 @AutoConfiguration
 public class AliyunRocketAutoConfiguration {
 

+ 1 - 2
helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/core/strategy/ProducerStrategy.java

@@ -22,7 +22,6 @@ import cc.uncarbon.framework.rocketmq.annotation.RocketMessage;
 import cc.uncarbon.framework.rocketmq.annotation.TransactionMessage;
 import cc.uncarbon.framework.rocketmq.core.factory.ProducerConsumerFactory;
 import cc.uncarbon.framework.rocketmq.core.factory.SendMessageFactory;
-import cc.uncarbon.framework.rocketmq.exception.RocketException;
 import com.aliyun.openservices.ons.api.Producer;
 import com.aliyun.openservices.ons.api.order.OrderProducer;
 import com.aliyun.openservices.ons.api.transaction.TransactionProducer;
@@ -42,7 +41,7 @@ public class ProducerStrategy {
     private ProducerStrategy() {
     }
 
-    public static void statsSendMessage(Long startDeliverTime, String shardingKeyFactory, Map<String, Object> consumerContainer, RocketMessage rocketMessage, Object message, byte[] bytes, ApplicationContext applicationContext) throws RocketException {
+    public static void statsSendMessage(Long startDeliverTime, String shardingKeyFactory, Map<String, Object> consumerContainer, RocketMessage rocketMessage, Object message, byte[] bytes, ApplicationContext applicationContext) {
         if (message instanceof CommonMessage) {
             CommonMessage commonMessage = (CommonMessage) message;
             Producer producer = ProducerConsumerFactory.getProducer(consumerContainer, rocketMessage, commonMessage);

+ 1 - 0
helio-starter-rocketmq-aliyun/src/main/java/cc/uncarbon/framework/rocketmq/core/utils/InterceptRocket.java

@@ -38,6 +38,7 @@ public class InterceptRocket {
     private InterceptRocket() {
     }
 
+    @SuppressWarnings("squid:S107")
     public static <T extends Annotation> Object intercept(Long startDeliverTime, String shardingKeyFactory, RocketMessage rocketMessage, T annotation, Object proceed, Map<String, Object> consumerContainer, ThreadPoolExecutor threadPoolExecutor, ApplicationContext applicationContext) {
         RocketSerializer mqSerializer = applicationContext.getBean(RocketSerializer.class);
         byte[] body = mqSerializer.serialize(proceed);

+ 3 - 2
helio-starter-satoken/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,11 +17,13 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
@@ -36,7 +38,6 @@
             <groupId>cn.dev33</groupId>
             <artifactId>sa-token-redis-jackson</artifactId>
         </dependency>
-
     </dependencies>
 
 </project>

+ 0 - 65
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/interceptor/DefaultSaTokenParseInterceptor.java

@@ -1,65 +0,0 @@
-package cc.uncarbon.framework.satoken.interceptor;
-
-import cc.uncarbon.framework.core.context.TenantContext;
-import cc.uncarbon.framework.core.context.TenantContextHolder;
-import cc.uncarbon.framework.core.context.UserContext;
-import cc.uncarbon.framework.core.context.UserContextHolder;
-import cc.uncarbon.framework.satoken.util.IPUtil;
-import cn.dev33.satoken.session.SaSession;
-import cn.dev33.satoken.stp.StpUtil;
-import lombok.NonNull;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.servlet.AsyncHandlerInterceptor;
-import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * 从请求头解析用户信息,并赋值到上下文;默认用于C端用户的鉴权
- *
- * @author Uncarbon
- * @deprecated since 1.8.0,已内置至脚手架同名类中
- */
-@Deprecated
-@Slf4j
-public class DefaultSaTokenParseInterceptor implements AsyncHandlerInterceptor {
-
-    @Override
-    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
-        if (handler instanceof ResourceHttpRequestHandler) {
-            // 直接放行静态资源
-            return true;
-        }
-
-        // SA-Token 会自动从请求头中解析 token,所以这里可以直接拿到对应 session,从而取出业务字段
-        if (StpUtil.isLogin()) {
-            setContextsFromSaSession(StpUtil.getSession(), request);
-            log.debug("[SA-Token] 从请求头解析出用户上下文 >> {}", UserContextHolder.getUserContext());
-        } else {
-            UserContextHolder.clear();
-            TenantContextHolder.clear();
-        }
-
-        return true;
-    }
-
-    /**
-     * 自 SaSession 更新当前线程用户、租户上下文
-     *
-     * @param session 会话对象
-     * @param request ServletRequest 对象
-     */
-    public static void setContextsFromSaSession(SaSession session, HttpServletRequest request) {
-        // 赋值用户上下文
-        UserContext userContext = (UserContext) session.get(UserContext.CAMEL_NAME);
-
-        // 获取用户公网IP
-        userContext.setClientIP(IPUtil.getClientIPAddress(request));
-        UserContextHolder.setUserContext(userContext);
-
-        // 赋值租户上下文
-        TenantContext tenantContext = (TenantContext) session.get(TenantContext.CAMEL_NAME);
-        TenantContextHolder.setTenantContext(tenantContext);
-    }
-}

+ 0 - 45
helio-starter-satoken/src/main/java/cc/uncarbon/framework/satoken/util/IPUtil.java

@@ -1,45 +0,0 @@
-package cc.uncarbon.framework.satoken.util;
-
-import lombok.experimental.UtilityClass;
-
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * IP地址工具类
- * 方法来自于互联网
- * @author Uncarbon
- * @deprecated since 1.8.0,改为使用helio-starter-web包中的同名工具类
- */
-@Deprecated
-@UtilityClass
-public class IPUtil {
-    private static final String[] HEADERS_TO_TRY = {
-            "X-Forwarded-For",
-            "Proxy-Client-IP",
-            "WL-Proxy-Client-IP",
-            "HTTP_X_FORWARDED_FOR",
-            "HTTP_X_FORWARDED",
-            "HTTP_X_CLUSTER_CLIENT_IP",
-            "HTTP_CLIENT_IP",
-            "HTTP_FORWARDED_FOR",
-            "HTTP_FORWARDED",
-            "HTTP_VIA",
-            "REMOTE_ADDR",
-            "X-Real-IP"};
-
-    /***
-     * 获取客户端IP地址(可以穿透代理)
-     * @param request 请求对象
-     * @return IP地址
-     */
-    public String getClientIPAddress(HttpServletRequest request) {
-        for (String header : HEADERS_TO_TRY) {
-            String ip = request.getHeader(header);
-            if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
-                return ip;
-            }
-        }
-        return request.getRemoteAddr();
-    }
-
-}

+ 0 - 0
helio-starter-satoken/src/main/resources/PLACEHOLDER


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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>io.seata</groupId>
             <artifactId>seata-spring-boot-starter</artifactId>

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -15,6 +15,7 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
@@ -25,6 +26,7 @@
             <artifactId>helio-starter-crud</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>dynamic-datasource-spring-boot-starter</artifactId>

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

@@ -15,8 +15,8 @@ import org.springframework.context.annotation.Bean;
  *
  * @author Uncarbon
  */
-@AutoConfiguration
 @RequiredArgsConstructor
+@AutoConfiguration
 public class HelioTenantAutoConfiguration {
 
     private final HelioProperties helioProperties;

+ 1 - 0
helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/support/TenantDataSourceSupport.java

@@ -14,6 +14,7 @@ import org.springframework.util.ClassUtils;
 @Slf4j
 public class TenantDataSourceSupport implements TenantSupport {
 
+    @SuppressWarnings("squid:S106")
     @Override
     public void support(HelioProperties helioProperties, MybatisPlusInterceptor interceptor) {
         log.info("\n\n[多租户支持] >> 隔离级别: 数据源级");

+ 1 - 0
helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/support/TenantLineSupport.java

@@ -22,6 +22,7 @@ import net.sf.jsqlparser.expression.LongValue;
 @Slf4j
 public class TenantLineSupport implements TenantSupport {
 
+    @SuppressWarnings("squid:S106")
     @Override
     public void support(HelioProperties helioProperties, MybatisPlusInterceptor interceptor) {
         Collection<String> ignoredTables = helioProperties.getTenant().getIgnoredTables();

+ 4 - 0
helio-starter-tenant/src/main/java/cc/uncarbon/framework/tenant/tenantdatasource/GlobalTenantDataSourceAdvisor.java

@@ -1,5 +1,6 @@
 package cc.uncarbon.framework.tenant.tenantdatasource;
 
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
@@ -18,12 +19,15 @@ import org.springframework.beans.factory.BeanFactoryAware;
  * @author chill
  * @author Uncarbon
  */
+@EqualsAndHashCode(callSuper = true)
 @Slf4j
 @Getter
 public class GlobalTenantDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
 
+    @SuppressWarnings("squid:S1948")
     private final Advice advice;
 
+    @SuppressWarnings("squid:S1948")
     private final Pointcut pointcut;
 
     public GlobalTenantDataSourceAdvisor(@NonNull GlobalTenantDataSourceInterceptor globalTenantDataSourceInterceptor) {

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>

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

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>helio-starters</artifactId>
         <groupId>cc.uncarbon.framework</groupId>
-        <version>${revision}</version>
+        <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,7 @@
     </properties>
 
     <dependencies>
+        <!-- 脚手架starter -->
         <dependency>
             <groupId>cc.uncarbon.framework</groupId>
             <artifactId>helio-core</artifactId>
@@ -37,6 +38,7 @@
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
 
+        <!-- 这里写其他第三方依赖 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-undertow</artifactId>

+ 1 - 1
helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/DefaultWebMvcAutoConfiguration.java

@@ -15,8 +15,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  *
  * @author Uncarbon
  **/
-@Slf4j
 @AutoConfiguration
+@Slf4j
 public class DefaultWebMvcAutoConfiguration implements WebMvcConfigurer, WebBindingInitializer {
     /**
      * 自定义静态资源

+ 59 - 29
helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/GlobalWebExceptionHandlerAutoConfiguration.java

@@ -2,6 +2,7 @@ package cc.uncarbon.framework.web.config;
 
 import cc.uncarbon.framework.core.constant.HelioConstant;
 import cc.uncarbon.framework.core.exception.BusinessException;
+import cc.uncarbon.framework.core.exception.HelioFrameworkException;
 import cc.uncarbon.framework.core.props.HelioProperties;
 import cc.uncarbon.framework.i18n.util.I18nUtil;
 import cc.uncarbon.framework.web.enums.GlobalWebExceptionI18nMessageEnum;
@@ -38,12 +39,12 @@ import java.nio.charset.StandardCharsets;
  *
  * @author Uncarbon
  */
-@Slf4j
-@RestController
-@ControllerAdvice
-@AutoConfiguration
 @ConditionalOnMissingBean(value = GlobalWebExceptionHandlerAutoConfiguration.class)
 @RequiredArgsConstructor
+@AutoConfiguration
+@ControllerAdvice
+@RestController
+@Slf4j
 public class GlobalWebExceptionHandlerAutoConfiguration {
 
     protected static final MediaType MEDIA_TYPE = new MediaType("application", "json", StandardCharsets.UTF_8);
@@ -56,14 +57,14 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     @ExceptionHandler({BusinessException.class})
-    public ResponseEntity<ApiResult<?>> handleBusinessException(BusinessException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleBusinessException(BusinessException e, HttpServletRequest request) {
         this.logError(e, request);
 
         /*
         @since 1.7.2,国际化支持;详见文档:进阶使用-国际化
          */
         String msg;
-        if (helioProperties.getI18n().getEnabled() && e.getCustomEnumField() != null) {
+        if (Boolean.TRUE.equals(helioProperties.getI18n().getEnabled()) && e.getCustomEnumField() != null) {
             // 如果启用了国际化,并制定了枚举值,则按国际化翻译值显示
             msg = I18nUtil.messageOf(e.getCustomEnumField(), e.getTemplateParams());
         } else {
@@ -71,20 +72,33 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
             msg = e.getMessage();
         }
 
-        ApiResult<?> ret = ApiResult.fail(e.getCode(), msg);
+        ApiResult<Void> ret = ApiResult.fail(e.getCode(), msg);
         return createResponseEntity(HttpStatus.BAD_REQUEST, ret);
     }
 
+    /**
+     * Helio脚手架内部出错异常类
+     */
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ExceptionHandler({HelioFrameworkException.class})
+    public ResponseEntity<ApiResult<Void>> handleHelioFrameworkException(HelioFrameworkException e, HttpServletRequest request) {
+        this.logError(e, request);
+
+        GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__INTERNAL_ERROR;
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), this.determineI18nMessage(msgEnum));
+        return createResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, ret);
+    }
+
     /**
      * 用户登录异常
      */
     @ResponseStatus(HttpStatus.UNAUTHORIZED)
     @ExceptionHandler({NotLoginException.class})
-    public ResponseEntity<ApiResult<?>> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__NO_LOGIN;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.UNAUTHORIZED.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.UNAUTHORIZED.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.UNAUTHORIZED, ret);
     }
 
@@ -93,11 +107,11 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.FORBIDDEN)
     @ExceptionHandler({NotPermissionException.class})
-    public ResponseEntity<ApiResult<?>> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__PERMISSION_NOT_MATCH;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.FORBIDDEN.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.FORBIDDEN.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.FORBIDDEN, ret);
     }
 
@@ -106,11 +120,11 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.FORBIDDEN)
     @ExceptionHandler({NotRoleException.class})
-    public ResponseEntity<ApiResult<?>> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__ROLE_NOT_MATCH;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.FORBIDDEN.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.FORBIDDEN.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.FORBIDDEN, ret);
     }
 
@@ -119,11 +133,11 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.NOT_FOUND)
     @ExceptionHandler({NoHandlerFoundException.class})
-    public ResponseEntity<ApiResult<?>> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__NOT_FOUND;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.NOT_FOUND.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.NOT_FOUND.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.NOT_FOUND, ret);
     }
 
@@ -140,11 +154,11 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     @ExceptionHandler({JsonParseException.class, HttpMessageNotReadableException.class, IllegalArgumentException.class, MethodArgumentTypeMismatchException.class})
-    public ResponseEntity<ApiResult<?>> handleJsonParseException(Exception e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleJsonParseException(Exception e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__UNACCEPTABLE_PARAMETERS;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.NOT_ACCEPTABLE.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.NOT_ACCEPTABLE.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.NOT_ACCEPTABLE, ret);
     }
 
@@ -154,11 +168,12 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
-    public ResponseEntity<ApiResult<?>> handleBindException(BindException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<InvalidFieldUtil.InvalidField>> handleBindException(BindException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__UNACCEPTABLE_PARAMETERS;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.NOT_ACCEPTABLE.value(), this.getI18nMessage(msgEnum), InvalidFieldUtil.getInvalidField(e.getBindingResult()));
+        ApiResult<InvalidFieldUtil.InvalidField> ret =
+                ApiResult.fail(HttpStatus.NOT_ACCEPTABLE.value(), this.determineI18nMessage(msgEnum), InvalidFieldUtil.getInvalidField(e.getBindingResult()));
         return createResponseEntity(HttpStatus.NOT_ACCEPTABLE, ret);
     }
 
@@ -167,11 +182,11 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
     @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
-    public ResponseEntity<ApiResult<?>> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
+    public ResponseEntity<ApiResult<Void>> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
         this.logError(e, request);
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__METHOD_NOT_ALLOWED;
-        ApiResult<?> ret = ApiResult.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.METHOD_NOT_ALLOWED, ret);
     }
 
@@ -181,10 +196,9 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      */
     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     @ExceptionHandler({Exception.class})
-    public ResponseEntity<ApiResult<?>> handleException(Exception e, HttpServletRequest request) {
-        this.logError(e, request);
+    public ResponseEntity<ApiResult<Void>> handleException(Exception e, HttpServletRequest request) {
         // 打印堆栈,方便溯源
-        e.printStackTrace();
+        this.logError(e, request, true);
 
         int responseCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
 
@@ -194,15 +208,31 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
         }
 
         GlobalWebExceptionI18nMessageEnum msgEnum = GlobalWebExceptionI18nMessageEnum.GLOBAL__INTERNAL_ERROR;
-        ApiResult<?> ret = ApiResult.fail(responseCode, this.getI18nMessage(msgEnum));
+        ApiResult<Void> ret = ApiResult.fail(responseCode, this.determineI18nMessage(msgEnum));
         return createResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, ret);
     }
 
+    /*
+    ----------------------------------------------------------------
+                        私有方法 private methods
+    ----------------------------------------------------------------
+     */
+
+    protected void logError(HelioFrameworkException e, HttpServletRequest request) {
+        log.error("❗ Helio framework has internal exception ❗ >> 异常类=[{}], URI=[{}], 消息=[{}]  ",
+                e.getClass().getName(), request.getRequestURI(), e.getMessage(), e);
+    }
+
     protected void logError(Exception e, HttpServletRequest request) {
-        log.error("[Web][有异常被抛出] >> 异常类=[{}], URI=[{}], 消息=[{}]", e.getClass().getName(), request.getRequestURI(), e.getMessage());
+        logError(e, request, false);
+    }
+
+    protected void logError(Exception e, HttpServletRequest request, boolean printExceptionStack) {
+        log.error("[Web][有异常被抛出] >> 异常类=[{}], URI=[{}], 消息=[{}]  {}",
+                e.getClass().getName(), request.getRequestURI(), e.getMessage(), printExceptionStack ? e : null);
     }
 
-    protected static ResponseEntity<ApiResult<?>> createResponseEntity(HttpStatus httpStatus, ApiResult<?> body) {
+    protected static <T> ResponseEntity<ApiResult<T>> createResponseEntity(HttpStatus httpStatus, ApiResult<T> body) {
         return ResponseEntity.status(httpStatus.value()).contentType(MEDIA_TYPE).body(body);
     }
 
@@ -211,8 +241,8 @@ public class GlobalWebExceptionHandlerAutoConfiguration {
      * @param msgEnum 全局异常处理国际化消息枚举
      * @return 消息文本
      */
-    protected String getI18nMessage(@NonNull GlobalWebExceptionI18nMessageEnum msgEnum) {
-        if (helioProperties.getI18n().getEnabled()) {
+    protected String determineI18nMessage(@NonNull GlobalWebExceptionI18nMessageEnum msgEnum) {
+        if (Boolean.TRUE.equals(helioProperties.getI18n().getEnabled())) {
             return I18nUtil.messageOf(msgEnum.i18nCode(), msgEnum.getDefaultValue());
         }
         return msgEnum.getDefaultValue();

+ 3 - 2
helio-starter-web/src/main/java/cc/uncarbon/framework/web/config/XssAutoConfiguration.java

@@ -1,12 +1,13 @@
 package cc.uncarbon.framework.web.config;
 
 import cc.uncarbon.framework.web.xss.XssFilter;
-import javax.servlet.DispatcherType;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 
+import javax.servlet.DispatcherType;
+
 /**
  * 反 XSS 注入自动配置类
  *
@@ -17,7 +18,7 @@ public class XssAutoConfiguration {
 
     @Bean
     @ConditionalOnMissingBean
-    public FilterRegistrationBean<?> xssFilterRegistration() {
+    public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
         FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
         registration.setDispatcherTypes(DispatcherType.REQUEST);
         registration.setFilter(new XssFilter());

+ 1 - 0
helio-starter-web/src/main/java/cc/uncarbon/framework/web/enums/GlobalWebExceptionI18nMessageEnum.java

@@ -9,6 +9,7 @@ import lombok.Getter;
  *
  * @author Uncarbon
  */
+@SuppressWarnings("squid:S115")
 @AllArgsConstructor
 @Getter
 public enum GlobalWebExceptionI18nMessageEnum {

+ 2 - 2
helio-starter-web/src/main/java/cc/uncarbon/framework/web/jackson/EnumModule.java

@@ -1,7 +1,7 @@
 package cc.uncarbon.framework.web.jackson;
 
 import cc.uncarbon.framework.core.enums.HelioBaseEnum;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.Version;
@@ -57,7 +57,7 @@ public class EnumModule extends SimpleModule {
                 if (parser.getCurrentToken().isNumeric()) {
                     // 前端传递value
                     return HelioBaseEnum.of(this.enumType, parser.getIntValue()).orElseThrow(() -> new IllegalArgumentException("Unable to parse input value"));
-                } else if (StrUtil.isNotBlank(parser.getText())) {
+                } else if (CharSequenceUtil.isNotBlank(parser.getText())) {
                     // 前端传递label
                     return HelioBaseEnum.of(this.enumType, parser.getText()).orElseThrow(() -> new IllegalArgumentException("Unable to parse input value"));
                 } else {

+ 4 - 4
helio-starter-web/src/main/java/cc/uncarbon/framework/web/jackson/LocalDateTimeDeserializer.java

@@ -24,9 +24,8 @@ import static cn.hutool.core.date.DatePattern.*;
  * 6. yyyy/MM/dd HH:mm:ss
  *
  * @author zuihou
- * @date 2020/6/18 上午10:50
  */
-@SuppressWarnings("ALL")
+@SuppressWarnings({"squid:S110", "squid:S6396"})
 public class LocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase<LocalDateTime> {
     public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
     private static final long serialVersionUID = 1L;
@@ -100,12 +99,13 @@ public class LocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase<Lo
         return null;
     }
 
+    @SuppressWarnings({"squid:S6541", "squid:S3776"})
     @Override
     public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {
         // 字符串
         if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
             String string = parser.getText().trim();
-            if (string.length() == 0) {
+            if (string.isEmpty()) {
                 return null;
             }
 
@@ -187,7 +187,7 @@ public class LocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase<Lo
             return (LocalDateTime) parser.getEmbeddedObject();
         }
 
-        return _handleUnexpectedToken(context, parser, "当前参数需要数组、字符串、时间戳。");
+        return _handleUnexpectedToken(context, parser, "Current parameters require array, string, timestamp");
     }
 
 }

+ 5 - 3
helio-starter-web/src/main/java/cc/uncarbon/framework/web/listener/LaunchEventListener.java

@@ -1,6 +1,6 @@
 package cc.uncarbon.framework.web.listener;
 
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.web.context.WebServerInitializedEvent;
 import org.springframework.context.event.EventListener;
@@ -19,6 +19,8 @@ import java.net.UnknownHostException;
  **/
 @Slf4j
 public class LaunchEventListener {
+
+    @SuppressWarnings("squid:S106")
     @Async
     @Order
     @EventListener(WebServerInitializedEvent.class)
@@ -32,7 +34,7 @@ public class LaunchEventListener {
         }
 
         String contextPath = env.getProperty("server.servlet.context-path");
-        if (StrUtil.isBlank(contextPath)) {
+        if (CharSequenceUtil.isBlank(contextPath)) {
             contextPath = "/";
         }
 
@@ -44,7 +46,7 @@ public class LaunchEventListener {
         }
 
         System.out.println(
-                StrUtil.format(
+                CharSequenceUtil.format(
                         "\n"+
                                 "----------------------------------------------------------" +
                                 "\n\tApplication '{}' is running! Access URLs:" +

+ 1 - 0
helio-starter-web/src/main/java/cc/uncarbon/framework/web/model/response/ApiResult.java

@@ -17,6 +17,7 @@ import java.io.Serializable;
  *
  * @author Uncarbon
  */
+@SuppressWarnings("squid:S1948")
 @Accessors(chain = true)
 @AllArgsConstructor
 @NoArgsConstructor

+ 4 - 4
helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/IPUtil.java

@@ -1,8 +1,8 @@
 package cc.uncarbon.framework.web.util;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.core.text.StrPool;
-import cn.hutool.core.util.StrUtil;
 import lombok.experimental.UtilityClass;
 
 import javax.servlet.http.HttpServletRequest;
@@ -52,12 +52,12 @@ public class IPUtil {
      */
     public String getClientIPAddress(HttpServletRequest request, int indexOfCommaSplit) {
         String source = getClientIPAddress(request);
-        if (!StrUtil.contains(source, StrPool.COMMA)) {
+        if (!CharSequenceUtil.contains(source, StrPool.COMMA)) {
             // 不含逗号
             return source;
         }
-        List<String> split = StrUtil.split(source, StrPool.COMMA);
-        return StrUtil.cleanBlank(CollUtil.get(split, indexOfCommaSplit));
+        List<String> split = CharSequenceUtil.split(source, StrPool.COMMA);
+        return CharSequenceUtil.cleanBlank(CollUtil.get(split, indexOfCommaSplit));
     }
 
 }

+ 2 - 2
helio-starter-web/src/main/java/cc/uncarbon/framework/web/util/InvalidFieldUtil.java

@@ -1,7 +1,7 @@
 package cc.uncarbon.framework.web.util;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -107,7 +107,7 @@ public class InvalidFieldUtil {
     private String maskMessage(String defaultMessage) {
         // corner case
         if (defaultMessage == null) {
-            return StrUtil.EMPTY;
+            return CharSequenceUtil.EMPTY;
         }
 
         for (Entry<String, String> entry : MESSAGE_MASKING_MAPPER.entrySet()) {

+ 37 - 86
helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/HTMLFilter.java

@@ -57,6 +57,7 @@ public final class HTMLFilter {
     private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
     private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
     private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
+    @SuppressWarnings("squid:S6019")
     private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
     private static final Pattern P_END_ARROW = Pattern.compile("^>");
     private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
@@ -68,6 +69,7 @@ public final class HTMLFilter {
     private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
     private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
     private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
+    private static final String HTML_TAG_STRONG = "strong";
 
     // @xxx could grow large... maybe use sesat's ReferenceMap
     private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
@@ -80,7 +82,7 @@ public final class HTMLFilter {
     /**
      * counts of open tags for each (allowable) html element
      **/
-    private final Map<String, Integer> vTagCounts = new HashMap<String, Integer>();
+    private final Map<String, Integer> vTagCounts = new HashMap<>();
 
     /**
      * html elements which must always be self-closing (e.g. "<img />")
@@ -129,77 +131,37 @@ public final class HTMLFilter {
     public HTMLFilter() {
         vAllowed = new HashMap<>();
 
-        final ArrayList<String> a_atts = new ArrayList<>();
-        a_atts.add("href");
-        a_atts.add("target");
-        vAllowed.put("a", a_atts);
+        final ArrayList<String> aAttributes = new ArrayList<>();
+        aAttributes.add("href");
+        aAttributes.add("target");
+        vAllowed.put("a", aAttributes);
 
-        final ArrayList<String> img_atts = new ArrayList<>();
-        img_atts.add("src");
-        img_atts.add("width");
-        img_atts.add("height");
-        img_atts.add("alt");
-        vAllowed.put("img", img_atts);
+        final ArrayList<String> imgAttributes = new ArrayList<>();
+        imgAttributes.add("src");
+        imgAttributes.add("width");
+        imgAttributes.add("height");
+        imgAttributes.add("alt");
+        vAllowed.put("img", imgAttributes);
 
-        final ArrayList<String> no_atts = new ArrayList<>();
-        vAllowed.put("b", no_atts);
-        vAllowed.put("strong", no_atts);
-        vAllowed.put("i", no_atts);
-        vAllowed.put("em", no_atts);
+        final ArrayList<String> noAttributes = new ArrayList<>();
+        vAllowed.put("b", noAttributes);
+        vAllowed.put(HTML_TAG_STRONG, noAttributes);
+        vAllowed.put("i", noAttributes);
+        vAllowed.put("em", noAttributes);
 
         vSelfClosingTags = new String[]{"img"};
-        vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
+        vNeedClosingTags = new String[]{"a", "b", HTML_TAG_STRONG, "i", "em"};
         vDisallowed = new String[]{};
         // 不允许ftp
         vAllowedProtocols = new String[]{"http", "mailto", "https"};
         vProtocolAttrs = new String[]{"src", "href"};
-        vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
+        vRemoveBlanks = new String[]{"a", "b", HTML_TAG_STRONG, "i", "em"};
         vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
         stripComment = true;
         encodeQuotes = true;
         alwaysMakeTags = true;
     }
 
-    /**
-     * Set debug flag to true. Otherwise use default settings. See the default constructor.
-     *
-     * @param debug turn debug on with a true argument
-     */
-    public HTMLFilter(final boolean debug) {
-        this();
-        vDebug = debug;
-
-    }
-
-    /**
-     * Map-parameter configurable constructor.
-     *
-     * @param conf map containing configuration. keys match field names.
-     */
-    public HTMLFilter(final Map<String, Object> conf) {
-
-        assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
-        assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
-        assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
-        assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
-        assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
-        assert conf.containsKey("vProtocolAttrs") : "configuration requires vProtocolAttrs";
-        assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
-        assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
-
-        vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
-        vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
-        vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
-        vDisallowed = (String[]) conf.get("vDisallowed");
-        vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
-        vProtocolAttrs = (String[]) conf.get("vProtocolAttrs");
-        vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
-        vAllowedEntities = (String[]) conf.get("vAllowedEntities");
-        stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
-        encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
-        alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
-    }
-
     //---------------------------------------------------------------
     // my versions of some PHP library functions
     public static String chr(final int decimal) {
@@ -215,8 +177,8 @@ public final class HTMLFilter {
         return result;
     }
 
-    private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
-        Matcher m = regex_pattern.matcher(s);
+    private static String regexReplace(final Pattern regexPattern, final String replacement, final String s) {
+        Matcher m = regexPattern.matcher(s);
         return m.replaceAll(replacement);
     }
 
@@ -332,17 +294,13 @@ public final class HTMLFilter {
         }
         m.appendTail(buf);
 
-        s = buf.toString();
-
         // these get tallied in processTag
         // (remember to reset before subsequent calls to filter method)
-        for (String key : vTagCounts.keySet()) {
-            for (int ii = 0; ii < vTagCounts.get(key); ii++) {
-                s += "</" + key + ">";
-            }
+        StringBuilder sb = new StringBuilder(buf.toString());
+        for (Map.Entry<String, Integer> entry : vTagCounts.entrySet()) {
+            sb.append("</").append(entry.getKey()).append(">");
         }
-
-        return s;
+        return sb.toString();
     }
 
     private String processRemoveBlanks(final String s) {
@@ -361,18 +319,15 @@ public final class HTMLFilter {
         return result;
     }
 
+    @SuppressWarnings({"squid:S6541", "squid:S3776"})
     private String processTag(final String s) {
         // ending tags
         Matcher m = P_END_TAG.matcher(s);
         if (m.find()) {
             final String name = m.group(1).toLowerCase();
-            if (allowed(name)) {
-                if (!inArray(name, vSelfClosingTags)) {
-                    if (vTagCounts.containsKey(name)) {
-                        vTagCounts.put(name, vTagCounts.get(name) - 1);
-                        return "</" + name + ">";
-                    }
-                }
+            if (allowed(name) && (!inArray(name, vSelfClosingTags) && (vTagCounts.containsKey(name)))) {
+                vTagCounts.put(name, vTagCounts.get(name) - 1);
+                return "</" + name + ">";
             }
         }
 
@@ -383,14 +338,13 @@ public final class HTMLFilter {
             final String body = m.group(2);
             String ending = m.group(3);
 
-            //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
             if (allowed(name)) {
-                String params = "";
+                StringBuilder params = new StringBuilder();
 
                 final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
                 final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
-                final List<String> paramNames = new ArrayList<String>();
-                final List<String> paramValues = new ArrayList<String>();
+                final List<String> paramNames = new ArrayList<>();
+                final List<String> paramValues = new ArrayList<>();
                 while (m2.find()) {
                     paramNames.add(m2.group(1)); //([a-z0-9]+)
                     paramValues.add(m2.group(3)); //(.*?)
@@ -400,20 +354,17 @@ public final class HTMLFilter {
                     paramValues.add(m3.group(3)); //([^\"\\s']+)
                 }
 
-                String paramName, paramValue;
+                String paramName;
+                String paramValue;
                 for (int ii = 0; ii < paramNames.size(); ii++) {
                     paramName = paramNames.get(ii).toLowerCase();
                     paramValue = paramValues.get(ii);
 
-//          debug( "paramName='" + paramName + "'" );
-//          debug( "paramValue='" + paramValue + "'" );
-//          debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
-
                     if (allowedAttribute(name, paramName)) {
                         if (inArray(paramName, vProtocolAttrs)) {
                             paramValue = processParamProtocol(paramValue);
                         }
-                        params += " " + paramName + "=\"" + paramValue + "\"";
+                        params.append(" ").append(paramName).append("=\"").append(paramValue).append("\"");
                     }
                 }
 
@@ -552,4 +503,4 @@ public final class HTMLFilter {
     private boolean allowedAttribute(final String name, final String paramName) {
         return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
     }
-}
+}

+ 8 - 4
helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/SQLFilter.java

@@ -1,7 +1,7 @@
 package cc.uncarbon.framework.web.xss;
 
 import cc.uncarbon.framework.core.exception.BusinessException;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
 import org.springframework.util.StringUtils;
 
 /**
@@ -11,12 +11,16 @@ import org.springframework.util.StringUtils;
  */
 public class SQLFilter {
 
+    private SQLFilter() {
+    }
+
     /**
      * SQL注入过滤
-     * @param str  待验证的字符串
+     *
+     * @param str 待验证的字符串
      */
-    public static String sqlInject(String str){
-        if(StrUtil.isBlank(str)){
+    public static String sqlInject(String str) {
+        if (CharSequenceUtil.isBlank(str)) {
             return null;
         }
         // 去掉'|"|;|\字符

+ 1 - 8
helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/XssFilter.java

@@ -12,9 +12,6 @@ import java.io.IOException;
 public class XssFilter implements Filter {
 
 	@Override
-	public void init(FilterConfig config) throws ServletException {
-	}
-
 	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
             throws IOException, ServletException {
 		XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
@@ -22,8 +19,4 @@ public class XssFilter implements Filter {
 		chain.doFilter(xssRequest, response);
 	}
 
-	@Override
-	public void destroy() {
-	}
-
-}
+}

+ 21 - 20
helio-starter-web/src/main/java/cc/uncarbon/framework/web/xss/XssHttpServletRequestWrapper.java

@@ -1,7 +1,8 @@
 package cc.uncarbon.framework.web.xss;
 
 import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import lombok.Getter;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 
@@ -20,25 +21,32 @@ import java.util.Map;
  *
  * @author Mark sunlightcs@gmail.com
  */
+@Getter
 public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
-    //html过滤
+
+    /**
+     * html过滤器
+     */
     private static final HTMLFilter htmlFilter = new HTMLFilter();
-    //没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
+
+    /**
+     * 没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
+     */
     HttpServletRequest orgRequest;
 
+
     public XssHttpServletRequestWrapper(HttpServletRequest request) {
         super(request);
         orgRequest = request;
     }
 
     /**
-     * 获取最原始的request
+     * 工具方法:获取最原始的request
      */
     public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
         if (request instanceof XssHttpServletRequestWrapper) {
             return ((XssHttpServletRequestWrapper) request).getOrgRequest();
         }
-
         return request;
     }
 
@@ -56,7 +64,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 
         // 为空,直接返回
         String json = IoUtil.read(super.getInputStream(), StandardCharsets.UTF_8);
-        if (StrUtil.isBlank(json)) {
+        if (CharSequenceUtil.isBlank(json)) {
             return super.getInputStream();
         }
 
@@ -76,7 +84,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
 
             @Override
             public void setReadListener(ReadListener readListener) {
-                // nothing to do
+                // do nothing
             }
 
             @Override
@@ -89,7 +97,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
     @Override
     public String getParameter(String name) {
         String value = super.getParameter(xssEncode(name));
-        if (StrUtil.isNotBlank(value)) {
+        if (CharSequenceUtil.isNotBlank(value)) {
             value = xssEncode(value);
         }
         return value;
@@ -99,7 +107,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
     public String[] getParameterValues(String name) {
         String[] parameters = super.getParameterValues(name);
         if (parameters == null || parameters.length == 0) {
-            return null;
+            return parameters;
         }
 
         for (int i = 0; i < parameters.length; i++) {
@@ -112,8 +120,9 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
     public Map<String, String[]> getParameterMap() {
         Map<String, String[]> map = new LinkedHashMap<>();
         Map<String, String[]> parameters = super.getParameterMap();
-        for (String key : parameters.keySet()) {
-            String[] values = parameters.get(key);
+        for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
+            String key = entry.getKey();
+            String[] values = entry.getValue();
             for (int i = 0; i < values.length; i++) {
                 values[i] = xssEncode(values[i]);
             }
@@ -125,7 +134,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
     @Override
     public String getHeader(String name) {
         String value = super.getHeader(xssEncode(name));
-        if (StrUtil.isNotBlank(value)) {
+        if (CharSequenceUtil.isNotBlank(value)) {
             value = xssEncode(value);
         }
         return value;
@@ -134,12 +143,4 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
     private String xssEncode(String input) {
         return htmlFilter.filter(input);
     }
-
-    /**
-     * 获取最原始的request
-     */
-    public HttpServletRequest getOrgRequest() {
-        return orgRequest;
-    }
-
 }

+ 15 - 55
pom.xml

@@ -12,6 +12,7 @@
         <module>helio-starter-dubbo</module>
         <module>helio-starter-i18n</module>
         <module>helio-starter-knife4j</module>
+        <module>helio-starter-rate-limit-redis</module>
         <module>helio-starter-redis</module>
         <module>helio-starter-rocketmq-aliyun</module>
         <module>helio-starter-satoken</module>
@@ -24,22 +25,22 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.7.17</version>
+        <version>2.7.18</version>
         <relativePath/>
     </parent>
 
     <groupId>cc.uncarbon.framework</groupId>
     <artifactId>helio-starters</artifactId>
-    <version>${revision}</version>
+    <version>1.10.0</version><!-- BATCH REPLACEMENT MARK -->
 
     <properties>
         <!-- core versions -->
-        <revision>1.9.0</revision>
+        <revision>1.10.0</revision>
         <helio-starters.version>${revision}</helio-starters.version>
         <!-- 可根据实际JDK版本修改;JDK8则改为'1.8'  JDK11则改为'11'  JDK17则改为'17'  JDK21则改为'21' -->
         <java.version>1.8</java.version>
         <!-- 若变更Spring Boot版本, 上面parent依赖版本也要变 -->
-        <spring-boot.version>2.7.17</spring-boot.version>
+        <spring-boot.version>2.7.18</spring-boot.version>
 
 
         <!-- Spring Cloud versions -->
@@ -56,11 +57,11 @@
 
         <!-- 3rd-party versions -->
         <lombok.version>1.18.30</lombok.version>
-        <hutool.version>5.8.22</hutool.version>
+        <hutool.version>5.8.24</hutool.version>
         <sa-token.version>1.37.0</sa-token.version>
         <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
-        <!-- 如果 mp 较新版存在问题,可以回退至 3.5.3.1 试试  -->
-        <mybatis-plus.version>3.5.4</mybatis-plus.version>
+        <!-- 如果 mp 较新版存在问题,可以回退至 3.5.3.1 或 3.5.4 试试  -->
+        <mybatis-plus.version>3.5.5</mybatis-plus.version>
         <knife4j.version>3.0.3</knife4j.version>
         <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
         <redisson.version>3.23.3</redisson.version>
@@ -69,8 +70,7 @@
         <mybatis-plus-dynamic-datasource.version>3.5.2</mybatis-plus-dynamic-datasource.version>
 
         <!-- for mitigating vulnerabilities versions -->
-        <guava.version>32.1.3-jre</guava.version>
-        <snakeyaml.version>1.33</snakeyaml.version>
+
 
         <!-- DB Driver versions -->
         <mysql.version>8.0.28</mysql.version>
@@ -107,17 +107,6 @@
     <dependencyManagement>
         <dependencies>
             <!-- for mitigating vulnerabilities -->
-            <dependency>
-                <groupId>com.google.guava</groupId>
-                <artifactId>guava</artifactId>
-                <version>${guava.version}</version>
-            </dependency>
-
-            <dependency>
-                <groupId>org.yaml</groupId>
-                <artifactId>snakeyaml</artifactId>
-                <version>${snakeyaml.version}</version>
-            </dependency>
 
 
             <!-- sub starters -->
@@ -163,6 +152,12 @@
                 <version>${helio-starters.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>cc.uncarbon.framework</groupId>
+                <artifactId>helio-starter-rate-limit-redis</artifactId>
+                <version>${helio-starters.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>cc.uncarbon.framework</groupId>
                 <artifactId>helio-starter-redis</artifactId>
@@ -205,7 +200,6 @@
                 <version>${helio-starters.version}</version>
             </dependency>
 
-
             <!-- CORE dependencies -->
             <dependency>
                 <groupId>com.alibaba</groupId>
@@ -285,12 +279,6 @@
                         <groupId>commons-lang</groupId>
                         <artifactId>commons-lang</artifactId>
                     </exclusion>
-
-                    <!-- 默认引入的 guava 版本过低,存在安全风险,手动指定新版本 -->
-                    <exclusion>
-                        <groupId>com.google.guava</groupId>
-                        <artifactId>guava</artifactId>
-                    </exclusion>
                 </exclusions>
             </dependency>
 
@@ -487,33 +475,5 @@
 
     <!-- 项目构建 -->
     <build>
-        <plugins>
-            <!-- 统一版本号管理 -->
-            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>flatten-maven-plugin</artifactId>
-                <version>1.1.0</version>
-                <configuration>
-                    <updatePomFile>true</updatePomFile>
-                    <flattenMode>resolveCiFriendliesOnly</flattenMode>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>flatten</id>
-                        <phase>process-resources</phase>
-                        <goals>
-                            <goal>flatten</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>flatten.clean</id>
-                        <phase>clean</phase>
-                        <goals>
-                            <goal>clean</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
     </build>
 </project>