Browse Source

Merge pull request #106 from hs-web/3.0.x

3.0.4
zhōuhào 6 years ago
parent
commit
a82c50a57f
100 changed files with 1616 additions and 106 deletions
  1. 1 1
      hsweb-authorization/hsweb-authorization-api/pom.xml
  2. 0 5
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java
  3. 52 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/TwoFactor.java
  4. 10 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java
  5. 19 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java
  6. 56 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java
  7. 26 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java
  8. 98 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java
  9. 13 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java
  10. 26 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java
  11. 9 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java
  12. 13 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorToken.java
  13. 9 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorTokenManager.java
  14. 28 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidator.java
  15. 18 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidatorManager.java
  16. 12 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidatorProvider.java
  17. 38 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidator.java
  18. 54 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidatorManager.java
  19. 30 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidatorProvider.java
  20. 70 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/HashMapTwoFactorTokenManager.java
  21. 26 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/UnsupportedTwoFactorValidator.java
  22. 29 0
      hsweb-authorization/hsweb-authorization-api/src/test/java/org/hswebframework/web/authorization/twofactor/defaults/HashMapTwoFactorTokenManagerTest.java
  23. 20 0
      hsweb-authorization/hsweb-authorization-basic/README.md
  24. 1 1
      hsweb-authorization/hsweb-authorization-basic/pom.xml
  25. 1 0
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AopAuthorizeAutoConfiguration.java
  26. 20 4
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java
  27. 55 0
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/twofactor/TwoFactorHandlerInterceptorAdapter.java
  28. 1 0
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java
  29. 23 0
      hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/FullFunctionTest.groovy
  30. 8 0
      hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/controller/TestCrudController.java
  31. 39 0
      hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/controller/TestTwoFactorValidatorProvider.java
  32. 56 55
      hsweb-authorization/hsweb-authorization-basic/src/test/resources/application.yml
  33. 1 1
      hsweb-authorization/hsweb-authorization-jwt/pom.xml
  34. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-auth-server/pom.xml
  35. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-client/pom.xml
  36. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-core/pom.xml
  37. 1 1
      hsweb-authorization/hsweb-authorization-oauth2/pom.xml
  38. 1 1
      hsweb-authorization/pom.xml
  39. 1 1
      hsweb-boost/hsweb-boost-aop/pom.xml
  40. 1 1
      hsweb-boost/hsweb-boost-excel/pom.xml
  41. 1 1
      hsweb-boost/hsweb-boost-ftp/pom.xml
  42. 1 1
      hsweb-boost/pom.xml
  43. 1 1
      hsweb-commons/hsweb-commons-bean/pom.xml
  44. 1 1
      hsweb-commons/hsweb-commons-controller/pom.xml
  45. 1 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-api/pom.xml
  46. 1 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/pom.xml
  47. 1 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/dict/DictInTermTypeMapper.java
  48. 4 1
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/dict/DictTermTypeMapper.java
  49. 4 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/test/java/org/hswebframework/web/dao/crud/TestCrud.java
  50. 1 0
      hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/test/java/org/hswebframework/web/dao/crud/TestEntity.java
  51. 1 1
      hsweb-commons/hsweb-commons-dao/pom.xml
  52. 1 1
      hsweb-commons/hsweb-commons-entity/pom.xml
  53. 9 0
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/DeleteParamEntity.java
  54. 66 0
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/QueryParamEntity.java
  55. 9 0
      hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/UpdateParamEntity.java
  56. 1 1
      hsweb-commons/hsweb-commons-model/pom.xml
  57. 1 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-api/pom.xml
  58. 1 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-oauth2/pom.xml
  59. 1 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/pom.xml
  60. 8 1
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultQueryByEntityService.java
  61. 4 3
      hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java
  62. 1 1
      hsweb-commons/hsweb-commons-service/pom.xml
  63. 1 1
      hsweb-commons/hsweb-commons-utils/pom.xml
  64. 1 1
      hsweb-commons/pom.xml
  65. 1 1
      hsweb-concurrent/hsweb-concurrent-async-job/pom.xml
  66. 1 1
      hsweb-concurrent/hsweb-concurrent-cache/pom.xml
  67. 7 1
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/pom.xml
  68. 25 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/AbstractBoomFilterManager.java
  69. 19 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/BloomFilter.java
  70. 6 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/BloomFilterManager.java
  71. 9 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/CounterAutoConfiguration.java
  72. 32 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/GuavaBloomFilterManager.java
  73. 25 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/test/java/org/hswebframework/web/concurrent/counter/GuavaBloomFilterManagerTest.java
  74. 1 1
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/pom.xml
  75. 31 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/src/main/java/org/hswebframework/web/counter/redis/RedisBloomFilterManager.java
  76. 8 0
      hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/src/main/java/org/hswebframework/web/counter/redis/RedisCounterAutoConfiguration.java
  77. 1 1
      hsweb-concurrent/hsweb-concurrent-counter/pom.xml
  78. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-api/pom.xml
  79. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-redis/pom.xml
  80. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-starter/pom.xml
  81. 1 1
      hsweb-concurrent/hsweb-concurrent-lock/pom.xml
  82. 23 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/pom.xml
  83. 26 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/AbstractRateLimiterManager.java
  84. 27 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/GuavaRateLimiterManager.java
  85. 17 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/RateLimiter.java
  86. 12 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/RateLimiterManager.java
  87. 39 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/annotation/RateLimiter.java
  88. 28 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/test/java/org/hswebframework/web/concurrent/GuavaRateLimiterManagerTest.java
  89. 52 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/pom.xml
  90. 69 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/java/org/hswebframework/web/concurent/RateLimiterAopAdvisor.java
  91. 30 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/java/org/hswebframework/web/concurent/RateLimiterAutoConfiguration.java
  92. 3 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/resources/META-INF/spring.factories
  93. 48 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/RateLimiterAopAdvisorTest.groovy
  94. 11 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/TestApplication.java
  95. 30 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/TestService.java
  96. 20 0
      hsweb-concurrent/hsweb-concurrent-rate-limiter/pom.xml
  97. 2 1
      hsweb-concurrent/pom.xml
  98. 1 1
      hsweb-core/pom.xml
  99. 18 0
      hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java
  100. 0 0
      hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java

+ 1 - 1
hsweb-authorization/hsweb-authorization-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 0 - 5
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java

@@ -128,7 +128,6 @@ public interface Authentication extends Serializable {
      * @param <T>  属性值类型
      * @return Optional属性值
      */
-    @Deprecated
     <T extends Serializable> Optional<T> getAttribute(String name);
 
     /**
@@ -139,7 +138,6 @@ public interface Authentication extends Serializable {
      * @param object 属性值
      * @see AuthenticationManager#sync(Authentication)
      */
-    @Deprecated
     void setAttribute(String name, Serializable object);
 
     /**
@@ -148,7 +146,6 @@ public interface Authentication extends Serializable {
      * @param attributes 属性值map
      * @see AuthenticationManager#sync(Authentication)
      */
-    @Deprecated
     void setAttributes(Map<String, Serializable> attributes);
 
     /**
@@ -159,7 +156,6 @@ public interface Authentication extends Serializable {
      * @return 被删除的值
      * @see AuthenticationManager#sync(Authentication)
      */
-    @Deprecated
     <T extends Serializable> T removeAttributes(String name);
 
     /**
@@ -167,7 +163,6 @@ public interface Authentication extends Serializable {
      *
      * @return 全部属性集合
      */
-    @Deprecated
     Map<String, Serializable> getAttributes();
 
 }

+ 52 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/TwoFactor.java

@@ -0,0 +1,52 @@
+package org.hswebframework.web.authorization.annotation;
+
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * 开启2FA双重验证
+ *
+ * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager
+ * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider
+ * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidator
+ * @since 3.0.4
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface TwoFactor {
+
+    /**
+     * @return 接口的标识, 用于实现不同的操作, 可能会配置不同的验证规则
+     */
+    String value();
+
+    /**
+     * @return 验证有效期, 超过有效期后需要重新进行验证
+     */
+    long timeout() default 10 * 60 * 1000L;
+
+    /**
+     * 验证器供应商,如: totp,sms,email,由{@link org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider}进行自定义.
+     * <p>
+     * 可通过配置项: hsweb.authorize.two-factor.default-provider 来修改默认配置
+     *
+     * @return provider
+     * @see TwoFactorValidator#getProvider()
+     */
+    String provider() default "default";
+
+    /**
+     * 验证码的http参数名,在进行验证的时候需要传入此参数
+     *
+     * @return 验证码的参数名
+     */
+    String parameter() default "verifyCode";
+
+    /**
+     * @return 关闭验证
+     */
+    boolean ignore() default false;
+}

+ 10 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java

@@ -1,5 +1,7 @@
 package org.hswebframework.web.authorization.exception;
 
+import lombok.Getter;
+
 /**
  * 权限验证异常
  *
@@ -10,6 +12,9 @@ public class AccessDenyException extends RuntimeException {
 
     private static final long serialVersionUID = -5135300127303801430L;
 
+    @Getter
+    private String code;
+
     public AccessDenyException() {
         this("权限不足,拒绝访问!");
     }
@@ -21,4 +26,9 @@ public class AccessDenyException extends RuntimeException {
     public AccessDenyException(String message, Throwable cause) {
         super(message, cause);
     }
+
+    public AccessDenyException(String message, String code, Throwable cause) {
+        super(message, cause);
+        this.code = code;
+    }
 }

+ 19 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java

@@ -0,0 +1,19 @@
+package org.hswebframework.web.authorization.exception;
+
+import lombok.Getter;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Getter
+public class NeedTwoFactorException extends RuntimeException {
+    private static final long   serialVersionUID = 3655980280834947633L;
+    private              String provider;
+
+    public NeedTwoFactorException(String message, String provider) {
+        super(message);
+        this.provider = provider;
+    }
+
+}

+ 56 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java

@@ -0,0 +1,56 @@
+package org.hswebframework.web.authorization.setting;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author zhouhao
+ * @since 1.0.0
+ */
+public class SettingNullValueHolder implements SettingValueHolder {
+
+    public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder();
+
+    private SettingNullValueHolder() {
+    }
+
+    @Override
+    public <T> Optional<List<T>> asList(Class<T> t) {
+        return Optional.empty();
+    }
+
+    @Override
+    public <T> Optional<T> as(Class<T> t) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<String> asString() {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<Long> asLong() {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<Integer> asInt() {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<Double> asDouble() {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<Object> getValue() {
+        return Optional.empty();
+    }
+
+    @Override
+    public UserSettingPermission getPermission() {
+        return UserSettingPermission.NONE;
+    }
+}

+ 26 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java

@@ -0,0 +1,26 @@
+package org.hswebframework.web.authorization.setting;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface SettingValueHolder {
+
+    SettingValueHolder NULL = SettingNullValueHolder.INSTANCE;
+
+    <T> Optional<List<T>> asList(Class<T> t);
+
+    <T> Optional<T> as(Class<T> t);
+
+    Optional<String> asString();
+
+    Optional<Long> asLong();
+
+    Optional<Integer> asInt();
+
+    Optional<Double> asDouble();
+
+    Optional<Object> getValue();
+
+    UserSettingPermission getPermission();
+
+}

+ 98 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java

@@ -0,0 +1,98 @@
+package org.hswebframework.web.authorization.setting;
+
+
+import com.alibaba.fastjson.JSON;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.utils.StringUtils;
+import org.hswebframework.web.dict.EnumDict;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@AllArgsConstructor
+@Getter
+public class StringSourceSettingHolder implements SettingValueHolder {
+
+    private String value;
+
+    private UserSettingPermission permission;
+
+    public static SettingValueHolder of(String value, UserSettingPermission permission) {
+        if (value == null) {
+            return SettingValueHolder.NULL;
+        }
+        return new StringSourceSettingHolder(value, permission);
+    }
+
+    @Override
+    public <T> Optional<List<T>> asList(Class<T> t) {
+        return getNativeValue()
+                .map(v -> JSON.parseArray(v, t));
+    }
+
+    protected <T> T convert(String value, Class<T> t) {
+        if (t.isEnum()) {
+            if (EnumDict.class.isAssignableFrom(t)) {
+                T val = (T) EnumDict.find((Class) t, value).orElse(null);
+                if (null != val) {
+                    return val;
+                }
+            }
+            for (T enumConstant : t.getEnumConstants()) {
+                if (((Enum) enumConstant).name().equalsIgnoreCase(value)) {
+                    return enumConstant;
+                }
+            }
+        }
+        return JSON.parseObject(value, t);
+    }
+
+    @Override
+    @SuppressWarnings("all")
+    public <T> Optional<T> as(Class<T> t) {
+        if (t == String.class) {
+            return (Optional) asString();
+        } else if (Long.class == t || long.class == t) {
+            return (Optional) asLong();
+        } else if (Integer.class == t || int.class == t) {
+            return (Optional) asInt();
+        } else if (Double.class == t || double.class == t) {
+            return (Optional) asDouble();
+        }
+        return getNativeValue().map(v -> convert(v, t));
+    }
+
+    @Override
+    public Optional<String> asString() {
+        return getNativeValue();
+    }
+
+    @Override
+    public Optional<Long> asLong() {
+        return getNativeValue().map(StringUtils::toLong);
+    }
+
+    @Override
+    public Optional<Integer> asInt() {
+        return getNativeValue().map(StringUtils::toInt);
+    }
+
+    @Override
+    public Optional<Double> asDouble() {
+        return getNativeValue().map(StringUtils::toDouble);
+    }
+
+    private Optional<String> getNativeValue() {
+        return Optional.ofNullable(value);
+    }
+
+    @Override
+    public Optional<Object> getValue() {
+        return Optional.ofNullable(value);
+    }
+}

+ 13 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java

@@ -0,0 +1,13 @@
+package org.hswebframework.web.authorization.setting;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface UserSettingManager {
+
+    SettingValueHolder getSetting(String userId, String key);
+
+    void saveSetting(String userId, String key, String value, UserSettingPermission permission);
+
+}

+ 26 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java

@@ -0,0 +1,26 @@
+package org.hswebframework.web.authorization.setting;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.dict.Dict;
+import org.hswebframework.web.dict.EnumDict;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@AllArgsConstructor
+@Getter
+@Dict(id = "user-setting-permission")
+public enum UserSettingPermission implements EnumDict<String> {
+    NONE("无"),
+    R("读"),
+    W("写"),
+    RW("读写");
+    private String text;
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+}

+ 9 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java

@@ -11,6 +11,8 @@ import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfi
 import org.hswebframework.web.authorization.token.DefaultUserTokenManager;
 import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier;
 import org.hswebframework.web.authorization.token.UserTokenManager;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
+import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager;
 import org.hswebframework.web.convert.CustomMessageConverter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -56,6 +58,13 @@ public class DefaultAuthorizationAutoConfiguration {
         return factory;
     }
 
+    @Bean
+    @ConditionalOnMissingBean(TwoFactorValidatorManager.class)
+    @ConfigurationProperties("hsweb.authorize.two-factor")
+    public DefaultTwoFactorValidatorManager defaultTwoFactorValidatorManager() {
+        return new DefaultTwoFactorValidatorManager();
+    }
+
     @Bean
     @ConditionalOnMissingBean(AuthenticationBuilderFactory.class)
     public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) {

+ 13 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorToken.java

@@ -0,0 +1,13 @@
+package org.hswebframework.web.authorization.twofactor;
+
+import java.io.Serializable;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface TwoFactorToken extends Serializable {
+    void generate(long timeout);
+
+    boolean expired();
+}

+ 9 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorTokenManager.java

@@ -0,0 +1,9 @@
+package org.hswebframework.web.authorization.twofactor;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface TwoFactorTokenManager {
+    TwoFactorToken getToken(String userId, String operation);
+}

+ 28 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidator.java

@@ -0,0 +1,28 @@
+package org.hswebframework.web.authorization.twofactor;
+
+/**
+ * 双重验证器,用于某些接口需要双重验证时使用,如: 短信验证码,动态口令等
+ *
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface TwoFactorValidator {
+
+    String getProvider();
+
+    /**
+     * 验证code是否有效,如果验证码有效,则保持此验证有效期.在有效期内,调用{@link this#expired()} 将返回false
+     *
+     * @param code    验证码
+     * @param timeout 保持验证通过有效期
+     * @return 验证码是否有效
+     */
+    boolean verify(String code, long timeout);
+
+    /**
+     * 验证是否已经过期,过期则需要重新进行验证
+     *
+     * @return 是否过期
+     */
+    boolean expired();
+}

+ 18 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidatorManager.java

@@ -0,0 +1,18 @@
+package org.hswebframework.web.authorization.twofactor;
+
+/**
+ * 双重验证管理器
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface TwoFactorValidatorManager {
+
+    /**
+     * 获取用户使用的双重验证器
+     *
+     * @param provider 验证器供应商
+     * @return 验证器
+     */
+    TwoFactorValidator getValidator(String userId,String operation, String provider);
+
+}

+ 12 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/TwoFactorValidatorProvider.java

@@ -0,0 +1,12 @@
+package org.hswebframework.web.authorization.twofactor;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface TwoFactorValidatorProvider {
+
+    String getProvider();
+
+    TwoFactorValidator createTwoFactorValidator(String userId,String operation);
+}

+ 38 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidator.java

@@ -0,0 +1,38 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@AllArgsConstructor
+public class DefaultTwoFactorValidator implements TwoFactorValidator {
+
+    @Getter
+    private String provider;
+
+    private Function<String, Boolean> validator;
+
+    private Supplier<TwoFactorToken> tokenSupplier;
+
+    @Override
+    public boolean verify(String code, long timeout) {
+        boolean success = validator.apply(code);
+        if (success) {
+            tokenSupplier.get().generate(timeout);
+        }
+        return success;
+    }
+
+    @Override
+    public boolean expired() {
+        return tokenSupplier.get().expired();
+    }
+}

+ 54 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidatorManager.java

@@ -0,0 +1,54 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public class DefaultTwoFactorValidatorManager implements TwoFactorValidatorManager, BeanPostProcessor {
+
+    @Getter
+    @Setter
+    private String defaultProvider = "totp";
+
+    private Map<String, TwoFactorValidatorProvider> providers = new HashMap<>();
+
+    @Override
+    public TwoFactorValidator getValidator(String userId, String operation, String provider) {
+        if (provider == null) {
+            provider = defaultProvider;
+        }
+        TwoFactorValidatorProvider validatorProvider = providers.get(provider);
+        if (validatorProvider == null) {
+            return new UnsupportedTwoFactorValidator(provider);
+        }
+        return validatorProvider.createTwoFactorValidator(userId, operation);
+    }
+
+    @Override
+    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+        return bean;
+    }
+
+    @Override
+    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+        if (bean instanceof TwoFactorValidatorProvider) {
+            TwoFactorValidatorProvider provider = ((TwoFactorValidatorProvider) bean);
+            providers.put(provider.getProvider(), provider);
+            if (provider.getProvider().equalsIgnoreCase(defaultProvider)) {
+                providers.put("default", provider);
+            }
+        }
+        return bean;
+    }
+}

+ 30 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/DefaultTwoFactorValidatorProvider.java

@@ -0,0 +1,30 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import lombok.Getter;
+import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Getter
+public abstract class DefaultTwoFactorValidatorProvider implements TwoFactorValidatorProvider {
+
+    private String provider;
+
+    private TwoFactorTokenManager twoFactorTokenManager;
+
+    public DefaultTwoFactorValidatorProvider(String provider, TwoFactorTokenManager twoFactorTokenManager) {
+        this.provider = provider;
+        this.twoFactorTokenManager = twoFactorTokenManager;
+    }
+
+    protected abstract boolean validate(String userId, String code);
+
+    @Override
+    public TwoFactorValidator createTwoFactorValidator(String userId, String operation) {
+        return new DefaultTwoFactorValidator(getProvider(), code -> validate(userId, code), () -> twoFactorTokenManager.getToken(userId, operation));
+    }
+}

+ 70 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/HashMapTwoFactorTokenManager.java

@@ -0,0 +1,70 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
+import org.hswebframework.web.authorization.twofactor.TwoFactorTokenManager;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public class HashMapTwoFactorTokenManager implements TwoFactorTokenManager {
+
+    private Map<String, WeakReference<TwoFactorTokenInfo>> tokens = new ConcurrentHashMap<>();
+
+    private class TwoFactorTokenInfo implements Serializable {
+        private static final long serialVersionUID = -5246224779564760241L;
+        private volatile long lastRequestTime = System.currentTimeMillis();
+
+        private long timeOut;
+
+        private boolean isExpire() {
+            return System.currentTimeMillis() - lastRequestTime >= timeOut;
+        }
+    }
+
+
+    private String createTokenInfoKey(String userId, String operation) {
+        return userId + "_" + operation;
+    }
+
+    private TwoFactorTokenInfo getTokenInfo(String userId, String operation) {
+        return Optional.ofNullable(tokens.get(createTokenInfoKey(userId, operation)))
+                .map(WeakReference::get)
+                .orElse(null);
+    }
+
+    @Override
+    public TwoFactorToken getToken(String userId, String operation) {
+
+        return new TwoFactorToken() {
+            private static final long serialVersionUID = -5148037320548431456L;
+
+            @Override
+            public void generate(long timeout) {
+                TwoFactorTokenInfo info = new TwoFactorTokenInfo();
+                info.timeOut = timeout;
+                tokens.put(createTokenInfoKey(userId, operation), new WeakReference<>(info));
+            }
+
+            @Override
+            public boolean expired() {
+                TwoFactorTokenInfo info = getTokenInfo(userId, operation);
+                if (info == null) {
+                    return true;
+                }
+                if (info.isExpire()) {
+                    tokens.remove(createTokenInfoKey(userId, operation));
+                    return true;
+                }
+                info.lastRequestTime = System.currentTimeMillis();
+                return false;
+            }
+        };
+    }
+}

+ 26 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/twofactor/defaults/UnsupportedTwoFactorValidator.java

@@ -0,0 +1,26 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@AllArgsConstructor
+public class UnsupportedTwoFactorValidator implements TwoFactorValidator {
+
+    @Getter
+    private String provider;
+
+    @Override
+    public boolean verify(String code, long timeout) {
+        throw new UnsupportedOperationException("不支持的验证规则:" + provider);
+    }
+
+    @Override
+    public boolean expired() {
+        throw new UnsupportedOperationException("不支持的验证规则:" + provider);
+    }
+}

+ 29 - 0
hsweb-authorization/hsweb-authorization-api/src/test/java/org/hswebframework/web/authorization/twofactor/defaults/HashMapTwoFactorTokenManagerTest.java

@@ -0,0 +1,29 @@
+package org.hswebframework.web.authorization.twofactor.defaults;
+
+import lombok.SneakyThrows;
+import org.hswebframework.web.authorization.twofactor.TwoFactorToken;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public class HashMapTwoFactorTokenManagerTest {
+
+    HashMapTwoFactorTokenManager tokenManager = new HashMapTwoFactorTokenManager();
+
+    @Test
+    @SneakyThrows
+    public void test() {
+        TwoFactorToken twoFactorToken = tokenManager.getToken("test", "test");
+
+        Assert.assertTrue(twoFactorToken.expired());
+        twoFactorToken.generate(1000L);
+        Assert.assertFalse(twoFactorToken.expired());
+        Thread.sleep(1100);
+        Assert.assertTrue(twoFactorToken.expired());
+    }
+}

+ 20 - 0
hsweb-authorization/hsweb-authorization-basic/README.md

@@ -30,6 +30,26 @@
 ![权限控制](./img/autz-handle-flow.png "权限控制")
 
 
+## 双重验证
+
+配置 application.yml
+```yml
+hsweb:
+    authorize:
+        two-factor:
+            enable: true
+```
+
+在需要验证的接口上注解:
+
+```java
+@PostMapping
+@TwoFactor("update-password")
+public ResponseMessage<Boolean> updatePassword(String password){
+    
+    //
+}
+```
 
 ## 注销
 与授权同理,类`UserOnSignOut`监听`AuthorizationExitEvent` ,当触发事件后,调用`UserTokenManager`移除当前登录的token信息

+ 1 - 1
hsweb-authorization/hsweb-authorization-basic/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 0
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AopAuthorizeAutoConfiguration.java

@@ -31,4 +31,5 @@ public class AopAuthorizeAutoConfiguration {
 
         return  new AopAuthorizingController(authorizingHandler, aopMethodAuthorizeDefinitionParser);
     }
+
 }

+ 20 - 4
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java

@@ -8,17 +8,19 @@ import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationManag
 import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler;
 import org.hswebframework.web.authorization.basic.handler.UserAllowPermissionHandler;
 import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController;
+import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
 import org.hswebframework.web.authorization.basic.web.*;
 import org.hswebframework.web.authorization.basic.web.session.UserTokenAutoExpiredListener;
 import org.hswebframework.web.authorization.token.UserTokenManager;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.*;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@@ -58,6 +60,20 @@ public class AuthorizingHandlerAutoConfiguration {
 
 
     @Bean
+    @ConditionalOnProperty(prefix = "hsweb.authorize.two-factor", name = "enable", havingValue = "true")
+    @Order(100)
+    public WebMvcConfigurer twoFactorHandlerConfigurer(TwoFactorValidatorManager manager) {
+        return new WebMvcConfigurerAdapter() {
+            @Override
+            public void addInterceptors(InterceptorRegistry registry) {
+                registry.addInterceptor(new TwoFactorHandlerInterceptorAdapter(manager));
+                super.addInterceptors(registry);
+            }
+        };
+    }
+
+    @Bean
+    @Order(Ordered.HIGHEST_PRECEDENCE)
     public WebMvcConfigurer webUserTokenInterceptorConfigurer(UserTokenManager userTokenManager,
                                                               AopMethodAuthorizeDefinitionParser parser,
                                                               List<UserTokenParser> userTokenParser) {

+ 55 - 0
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/twofactor/TwoFactorHandlerInterceptorAdapter.java

@@ -0,0 +1,55 @@
+package org.hswebframework.web.authorization.basic.twofactor;
+
+import lombok.AllArgsConstructor;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.User;
+import org.hswebframework.web.authorization.annotation.TwoFactor;
+import org.hswebframework.web.authorization.exception.NeedTwoFactorException;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@AllArgsConstructor
+public class TwoFactorHandlerInterceptorAdapter extends HandlerInterceptorAdapter {
+
+    private TwoFactorValidatorManager validatorManager;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        if (handler instanceof HandlerMethod) {
+            HandlerMethod method = ((HandlerMethod) handler);
+            TwoFactor factor = method.getMethodAnnotation(TwoFactor.class);
+            if (factor == null || factor.ignore()) {
+                return true;
+            }
+            String userId = Authentication.current()
+                    .map(Authentication::getUser)
+                    .map(User::getId)
+                    .orElse(null);
+            TwoFactorValidator validator = validatorManager.getValidator(userId, factor.value(), factor.provider());
+            if (!validator.expired()) {
+                return true;
+            }
+            String code = request.getParameter(factor.parameter());
+            if (code == null) {
+                code = request.getHeader(factor.parameter());
+            }
+            if (StringUtils.isEmpty(code)) {
+                throw new NeedTwoFactorException("需要进行双重验证", factor.provider());
+            } else if (!validator.verify(code, factor.timeout())) {
+                throw new NeedTwoFactorException("验证码错误", factor.provider());
+            }
+        }
+        return super.preHandle(request, response, handler);
+    }
+}

+ 1 - 0
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java

@@ -110,6 +110,7 @@ public class AuthorizationController {
             eventPublisher.publishEvent(beforeEvent);
             // 验证通过
             Authentication authentication = authenticationManager.authenticate(new PlainTextUsernamePasswordAuthenticationRequest(username, password));
+
             //触发授权成功事件
             AuthorizationSuccessEvent event = new AuthorizationSuccessEvent(authentication, parameterGetter);
             event.getResult().put("userId", authentication.getUser().getId());

+ 23 - 0
hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/FullFunctionTest.groovy

@@ -51,6 +51,29 @@ class FullFunctionTest extends Specification {
     }
 
 
+    def "测试双重验证"() {
+        given: "登录"
+        def token = doLogin("admin", "admin")
+        when: "登录成功"
+        token != null
+        then: "调用双重验证接口"
+        mockMvc.perform(get("/test/two-factor")
+                .header("token", token))
+                .andExpect(status().is(403))
+                .andReturn()
+                .getResponse()
+                .getContentAsString()
+        def resp = mockMvc.perform(get("/test/two-factor")
+                .header("token", token)
+                .param("verifyCode", "test"))
+                .andExpect(status().is(200))
+                .andReturn()
+                .getResponse()
+                .getContentAsString()
+        expect:
+        resp != null
+    }
+
     def "测试查询"() {
         given: "登录"
         def token = doLogin("admin", "admin")

+ 8 - 0
hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/controller/TestCrudController.java

@@ -1,8 +1,10 @@
 package org.hswebframework.web.authorization.full.controller;
 
 import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.authorization.annotation.TwoFactor;
 import org.hswebframework.web.authorization.full.controller.model.TestModel;
 import org.hswebframework.web.controller.message.ResponseMessage;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -21,4 +23,10 @@ public class TestCrudController implements CrudController<TestModel> {
 
         return ResponseMessage.ok();
     }
+
+    @TwoFactor(value = "test", provider = "test")
+    @GetMapping("/two-factor")
+    public ResponseMessage<String> testTowFactor() {
+        return ResponseMessage.ok();
+    }
 }

+ 39 - 0
hsweb-authorization/hsweb-authorization-basic/src/test/groovy/org/hswebframework/web/authorization/full/controller/TestTwoFactorValidatorProvider.java

@@ -0,0 +1,39 @@
+package org.hswebframework.web.authorization.full.controller;
+
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
+import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Component
+public class TestTwoFactorValidatorProvider implements TwoFactorValidatorProvider {
+    @Override
+    public String getProvider() {
+        return "test";
+    }
+
+    @Override
+    public TwoFactorValidator createTwoFactorValidator(String userId, String operation) {
+        return new TwoFactorValidator() {
+            boolean success = false;
+
+            @Override
+            public String getProvider() {
+                return "test";
+            }
+
+            @Override
+            public boolean verify(String code, long timeout) {
+                return success = code.equalsIgnoreCase("test");
+            }
+
+            @Override
+            public boolean expired() {
+                return !success;
+            }
+        };
+    }
+}

+ 56 - 55
hsweb-authorization/hsweb-authorization-basic/src/test/resources/application.yml

@@ -1,59 +1,60 @@
-
 spring:
-    aop:
-        auto: true
-        proxy-target-class: true
-    datasource:
-       url : jdbc:h2:mem:example-oauth2-client
-       username : sa
-       password :
-       type: com.alibaba.druid.pool.DruidDataSource
-       driver-class-name : org.h2.Driver
-    cache:
-       type: simple
+  aop:
+    auto: true
+    proxy-target-class: true
+  datasource:
+    url: jdbc:h2:mem:example-oauth2-client
+    username: sa
+    password:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: org.h2.Driver
+  cache:
+    type: simple
 hsweb:
-    app:
-      name: hsweb-oauth2 客户端示例
-      version: 3.0.0
-    authorize:
-        allows:
-          users:
-            admin: "**.TestController.*"
-    users:
-        admin:
-          name: 超级管理员
-          username: admin
-          password: admin
-          roles: #用户的角色
-            - id: admin
-              name: 管理员
-            - id: user
-              name: 用户
-          permissions-simple:
-              test: query,get
-          permissions:
-            - id: user-manager
-              actions: query,get,update,delete
-              dataAccesses:
-                - action: query
-                  type: DENY_FIELDS
-                  fields:
-                    - password
-                    - salt
-            - id: test
-              actions: query,add,update
-              dataAccesses:
-                - action: query
-                  type: DENY_FIELDS
-                  fields:
-                    - password
-                - action: update
-                  type: DENY_FIELDS
-                  fields:
-                    - name
-                - action: add
-                  type: DENY_FIELDS
-                  fields:
-                    - id
+  app:
+    name: hsweb-oauth2 客户端示例
+    version: 3.0.0
+  authorize:
+    allows:
+      users:
+        admin: "**.TestController.*"
+    two-factor:
+      enable: true
+  users:
+    admin:
+      name: 超级管理员
+      username: admin
+      password: admin
+      roles: #用户的角色
+      - id: admin
+        name: 管理员
+      - id: user
+        name: 用户
+      permissions-simple:
+        test: query,get
+      permissions:
+      - id: user-manager
+        actions: query,get,update,delete
+        dataAccesses:
+        - action: query
+          type: DENY_FIELDS
+          fields:
+          - password
+          - salt
+      - id: test
+        actions: query,add,update
+        dataAccesses:
+        - action: query
+          type: DENY_FIELDS
+          fields:
+          - password
+        - action: update
+          type: DENY_FIELDS
+          fields:
+          - name
+        - action: add
+          type: DENY_FIELDS
+          fields:
+          - id
 server:
   port: 8808

+ 1 - 1
hsweb-authorization/hsweb-authorization-jwt/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-auth-server/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-client/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/hsweb-authorization-oauth2-core/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-authorization-oauth2</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <!--<relativePath>../../pom.xml</relativePath>-->
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-authorization/hsweb-authorization-oauth2/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-authorization</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-authorization/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-aop/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-excel/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/hsweb-boost-ftp/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-boost</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-boost/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-commons/hsweb-commons-bean/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-controller/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-api/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons-dao</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons-dao</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/dict/DictInTermTypeMapper.java

@@ -119,7 +119,7 @@ public class DictInTermTypeMapper extends AbstractSqlTermCustomizer {
 
         String columnName = dialect.buildColumnName(tableAlias, column.getName());
         SqlAppender appender = new SqlAppender();
-        appender.add(columnName, not ? " NOT" : " ").add("IN(");
+        appender.add(columnName, not ? " NOT " : " ").add("IN(");
         for (int i = 0; i < values.size(); i++) {
             appender.add("#{", wherePrefix, ".value.value[", i, "]}", ",");
         }

+ 4 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/mapper/dict/DictTermTypeMapper.java

@@ -89,7 +89,10 @@ public class DictTermTypeMapper extends AbstractSqlTermCustomizer {
     }
 
     protected SqlAppender buildNotSupport(String wherePrefix, Term term, RDBColumnMetaData column, String tableAlias) {
-        createChangedTermValue(term);
+        ChangedTermValue termValue = createChangedTermValue(term);
+        // fix https://github.com/hs-web/hsweb-framework/issues/102
+        Object newValue = BoostTermTypeMapper.convertValue(column, termValue.getOld());
+        termValue.setValue(newValue);
 
         Dialect dialect = column.getTableMetaData().getDatabaseMetaData().getDialect();
         String columnName = dialect.buildColumnName(tableAlias, column.getName());

+ 4 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/test/java/org/hswebframework/web/dao/crud/TestCrud.java

@@ -69,6 +69,10 @@ public class TestCrud extends AbstractTransactionalJUnit4SpringContextTests {
         QueryParamEntity query = new QueryParamEntity();
         //any in
         query.where("dataTypes$in$any", Arrays.asList(DataType.TYPE1, DataType.TYPE2));
+
+        //#102
+        query.where("createTime","2017-11-10");
+
         query.includes("nest.name", "*");
 
         //  DataSourceHolder.tableSwitcher().use("h_test", "h_test2");

+ 1 - 0
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/test/java/org/hswebframework/web/dao/crud/TestEntity.java

@@ -45,4 +45,5 @@ public class TestEntity implements org.hswebframework.web.commons.entity.Entity
     private DataType[] dataTypes;
 
     private NestEntity nest;
+
 }

+ 1 - 1
hsweb-commons/hsweb-commons-dao/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-entity/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 9 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/DeleteParamEntity.java

@@ -1,5 +1,7 @@
 package org.hswebframework.web.commons.entity.param;
 
+import org.hswebframework.ezorm.core.dsl.Delete;
+import org.hswebframework.ezorm.core.dsl.Update;
 import org.hswebframework.ezorm.core.param.Param;
 import org.hswebframework.web.commons.entity.Entity;
 import org.hswebframework.web.commons.entity.QueryEntity;
@@ -28,6 +30,13 @@ public class DeleteParamEntity extends Param implements QueryEntity {
         return new DeleteParamEntity();
     }
 
+    /**
+     * @since 3.0.4
+     */
+    public static Delete<DeleteParamEntity> newDelete() {
+        return new Delete<>(new DeleteParamEntity());
+    }
+
     @Override
     public String toString() {
         return toHttpQueryParamString();

+ 66 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/QueryParamEntity.java

@@ -1,8 +1,16 @@
 package org.hswebframework.web.commons.entity.param;
 
+import org.hswebframework.ezorm.core.NestConditional;
+import org.hswebframework.ezorm.core.dsl.Query;
 import org.hswebframework.ezorm.core.param.QueryParam;
+import org.hswebframework.ezorm.core.param.Term;
 import org.hswebframework.web.commons.entity.Entity;
 import org.hswebframework.web.commons.entity.QueryEntity;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * 查询参数实体,使用<a href="https://github.com/hs-web/hsweb-easy-orm">easyorm</a>进行动态查询参数构建<br>
@@ -51,6 +59,64 @@ public class QueryParamEntity extends QueryParam implements QueryEntity {
         return empty().where(field, value);
     }
 
+    /**
+     * @since 3.0.4
+     */
+    public static <T> Query<T, QueryParamEntity> newQuery() {
+        return Query.empty(new QueryParamEntity());
+    }
+
+    /**
+     * @since 3.0.4
+     */
+    public <T> Query<T, QueryParamEntity> toQuery() {
+        return Query.empty(this);
+    }
+
+    /**
+     * 将已有的条件包装到一个嵌套的条件里,并返回一个Query对象.例如:
+     * <pre>
+     *     entity.toNestQuery().and("userId",userId);
+     * </pre>
+     * <p>
+     * 原有条件: name=? or type=?
+     * <p>
+     * 执行后条件: (name=? or type=?) and userId=?
+     *
+     * @see this#toNestQuery(Consumer)
+     * @since 3.0.4
+     */
+    public <T> Query<T, QueryParamEntity> toNestQuery() {
+        return toNestQuery(null);
+    }
+
+    /**
+     * 将已有的条件包装到一个嵌套的条件里,并返回一个Query对象.例如:
+     * <pre>
+     *     entity.toNestQuery(query->query.and("userId",userId));
+     * </pre>
+     * <p>
+     * 原有条件: name=? or type=?
+     * <p>
+     * 执行后条件: userId=? (name=? or type=?)
+     *
+     * @param before 在包装之前执行,将条件包装到已有条件之前
+     * @since 3.0.4
+     */
+    public <T> Query<T, QueryParamEntity> toNestQuery(Consumer<Query<T, QueryParamEntity>> before) {
+        List<Term> terms = getTerms();
+        setTerms(new ArrayList<>());
+        Query<T, QueryParamEntity> query = toQuery();
+        if (null != before) {
+            before.accept(query);
+        }
+        return query
+                .nest()
+                .each(terms, NestConditional::accept)
+                .end();
+    }
+
+
     @Override
     public String toString() {
         return toHttpQueryParamString();

+ 9 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/UpdateParamEntity.java

@@ -1,5 +1,7 @@
 package org.hswebframework.web.commons.entity.param;
 
+import org.hswebframework.ezorm.core.dsl.Query;
+import org.hswebframework.ezorm.core.dsl.Update;
 import org.hswebframework.ezorm.core.param.UpdateParam;
 import org.hswebframework.web.commons.entity.Entity;
 import org.hswebframework.web.commons.entity.QueryEntity;
@@ -54,6 +56,13 @@ public class UpdateParamEntity<T> extends UpdateParam<T> implements QueryEntity
         return new UpdateParamEntity<>(data).where(field, value);
     }
 
+    /**
+     * @since 3.0.4
+     */
+    public static <T> Update<T, UpdateParamEntity<T>> newUpdate() {
+        return new Update<>(new UpdateParamEntity<>());
+    }
+
     @Override
     public String toString() {
         return toHttpQueryParamString();

+ 1 - 1
hsweb-commons/hsweb-commons-model/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-api/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-oauth2/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
 
         <relativePath>../pom.xml</relativePath>
     </parent>

+ 1 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons-service</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
 
     </parent>

+ 8 - 1
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultQueryByEntityService.java

@@ -38,6 +38,8 @@ public interface DefaultQueryByEntityService<E>
      *
      * @param param 查询参数
      * @return 分页查询结果
+     * @see QueryParamEntity
+     * @see QueryParamEntity#newQuery()
      */
     @Override
     default PagerResult<E> selectPager(Entity param) {
@@ -60,7 +62,7 @@ public interface DefaultQueryByEntityService<E>
             if (param instanceof QueryParamEntity) {
                 ((QueryParamEntity) param).rePaging(total);
             }
-            pagerResult.setData(getDao().query(param));
+            pagerResult.setData(select(param));
         }
         return pagerResult;
     }
@@ -71,6 +73,7 @@ public interface DefaultQueryByEntityService<E>
      * @param param 查询参数
      * @return 查询结果
      * @see QueryParamEntity
+     * @see QueryParamEntity#newQuery()
      */
     @Override
     @Transactional(readOnly = true)
@@ -87,6 +90,8 @@ public interface DefaultQueryByEntityService<E>
      *
      * @param param 查询参数
      * @return 查询结果,实现mapper中的sql应指定默认值,否则可能抛出异常
+     * @see QueryParamEntity
+     * @see QueryParamEntity#newQuery()
      */
     @Override
     @Transactional(readOnly = true)
@@ -102,6 +107,8 @@ public interface DefaultQueryByEntityService<E>
      *
      * @param param 查询条件
      * @return 单个查询结果
+     * @see QueryParamEntity
+     * @see QueryParamEntity#newQuery()
      */
     @Override
     @Transactional(readOnly = true)

+ 4 - 3
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java

@@ -29,6 +29,7 @@ import org.hswebframework.web.validator.group.UpdateGroup;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import javax.annotation.PostConstruct;
@@ -60,7 +61,7 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
 
     @PostConstruct
     public void init() {
-        if (null != logicPrimaryKeyValidator && logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) {
+        if (logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) {
             DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(getEntityInstanceType(), bean -> this.createQuery().not("id", bean.getId()));
         }
     }
@@ -142,7 +143,7 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
     @Override
     @Transactional(readOnly = true)
     public E selectByPk(PK pk) {
-        if (null == pk) {
+        if (StringUtils.isEmpty(pk)) {
             return null;
         }
         return createQuery().where(GenericEntity.id, pk).single();
@@ -151,7 +152,7 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
     @Override
     @Transactional(readOnly = true)
     public List<E> selectByPk(List<PK> id) {
-        if (id == null || id.isEmpty()) {
+        if (CollectionUtils.isEmpty(id)) {
             return new ArrayList<>();
         }
         return createQuery().where().in(GenericEntity.id, id).listNoPaging();

+ 1 - 1
hsweb-commons/hsweb-commons-service/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
 
         <relativePath>../pom.xml</relativePath>
     </parent>

+ 1 - 1
hsweb-commons/hsweb-commons-utils/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-commons</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-commons/pom.xml

@@ -23,7 +23,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-async-job/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-cache/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 7 - 1
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent-counter</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -36,5 +36,11 @@
             <artifactId>spring-boot-starter</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 </project>

+ 25 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/AbstractBoomFilterManager.java

@@ -0,0 +1,25 @@
+package org.hswebframework.web.concurrent.counter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ */
+public abstract class AbstractBoomFilterManager implements BloomFilterManager {
+
+    private final Map<String, BloomFilter> counterStore = new HashMap<>(128);
+
+    @Override
+    public BloomFilter getBloomFilter(String key) {
+        BloomFilter filter = counterStore.get(key);
+        if (filter != null) {
+            return filter;
+        }
+        synchronized (counterStore) {
+            return counterStore.computeIfAbsent(key, this::createBloomFilter);
+        }
+    }
+
+    protected abstract BloomFilter createBloomFilter(String name);
+}

+ 19 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/BloomFilter.java

@@ -0,0 +1,19 @@
+package org.hswebframework.web.concurrent.counter;
+
+import lombok.SneakyThrows;
+
+import java.util.function.Supplier;
+
+public interface BloomFilter {
+
+    boolean put(String unique);
+
+    boolean contains(String unique);
+
+    @SneakyThrows
+    default void tryPut(String unique, Supplier<? extends Exception> supplier) {
+        if (!put(unique)) {
+            throw supplier.get();
+        }
+    }
+}

+ 6 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/BloomFilterManager.java

@@ -0,0 +1,6 @@
+package org.hswebframework.web.concurrent.counter;
+
+public interface BloomFilterManager {
+
+    BloomFilter getBloomFilter(String key);
+}

+ 9 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/CounterAutoConfiguration.java

@@ -1,5 +1,6 @@
 package org.hswebframework.web.concurrent.counter;
 
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -16,4 +17,12 @@ public class CounterAutoConfiguration {
     public CounterManager counterManager() {
         return new SimpleCounterManager();
     }
+
+
+    @Bean
+    @ConditionalOnMissingBean(BloomFilterManager.class)
+    @ConditionalOnClass(name = "com.google.common.hash.BloomFilter")
+    public BloomFilterManager bloomFilterManager() {
+        return new GuavaBloomFilterManager();
+    }
 }

+ 32 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/main/java/org/hswebframework/web/concurrent/counter/GuavaBloomFilterManager.java

@@ -0,0 +1,32 @@
+package org.hswebframework.web.concurrent.counter;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.nio.charset.StandardCharsets;
+
+@Getter
+@Setter
+public class GuavaBloomFilterManager extends AbstractBoomFilterManager {
+
+    private long expectedInsertions = 55000000L;
+
+    private double fpp = 0.01;
+
+    @Override
+    protected BloomFilter createBloomFilter(String name) {
+        com.google.common.hash.BloomFilter<String>
+                filter = com.google.common.hash.BloomFilter.create((str, sink) -> sink.putString(str, StandardCharsets.UTF_8), expectedInsertions, fpp);
+        return new BloomFilter() {
+            @Override
+            public boolean put(String unique) {
+                return filter.put(unique);
+            }
+
+            @Override
+            public boolean contains(String unique) {
+                return filter.mightContain(unique);
+            }
+        };
+    }
+}

+ 25 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-api/src/test/java/org/hswebframework/web/concurrent/counter/GuavaBloomFilterManagerTest.java

@@ -0,0 +1,25 @@
+package org.hswebframework.web.concurrent.counter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class GuavaBloomFilterManagerTest {
+
+    @Test
+    public void testGuavaBloomFilter() {
+        BloomFilterManager manager = new GuavaBloomFilterManager();
+
+        BloomFilter filter = manager.getBloomFilter("test");
+        Assert.assertNotNull(filter);
+
+        for (int i = 0; i < 100000; i++) {
+            Assert.assertTrue(filter.put("test" + i));
+            Assert.assertTrue(filter.contains("test" + i));
+            Assert.assertFalse(filter.put("test" + i));
+        }
+
+    }
+
+}

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-counter</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 31 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/src/main/java/org/hswebframework/web/counter/redis/RedisBloomFilterManager.java

@@ -0,0 +1,31 @@
+package org.hswebframework.web.counter.redis;
+
+import lombok.AllArgsConstructor;
+import org.hswebframework.web.concurrent.counter.AbstractBoomFilterManager;
+import org.hswebframework.web.concurrent.counter.BloomFilter;
+import org.redisson.api.RBloomFilter;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.codec.StringCodec;
+
+@AllArgsConstructor
+public class RedisBloomFilterManager extends AbstractBoomFilterManager {
+
+    private RedissonClient redissonClient;
+
+    @Override
+    protected BloomFilter createBloomFilter(String name) {
+        RBloomFilter<String> filter = redissonClient.getBloomFilter("hsweb:bloom-filter:" + name, StringCodec.INSTANCE);
+        filter.tryInit(55000000L, 0.01);
+        return new BloomFilter() {
+            @Override
+            public boolean put(String unique) {
+                return filter.add(unique);
+            }
+
+            @Override
+            public boolean contains(String unique) {
+                return filter.contains(unique);
+            }
+        };
+    }
+}

+ 8 - 0
hsweb-concurrent/hsweb-concurrent-counter/hsweb-concurrent-counter-redis/src/main/java/org/hswebframework/web/counter/redis/RedisCounterAutoConfiguration.java

@@ -1,5 +1,6 @@
 package org.hswebframework.web.counter.redis;
 
+import org.hswebframework.web.concurrent.counter.BloomFilterManager;
 import org.hswebframework.web.concurrent.counter.CounterAutoConfiguration;
 import org.hswebframework.web.concurrent.counter.CounterManager;
 import org.redisson.api.RedissonClient;
@@ -21,4 +22,11 @@ public class RedisCounterAutoConfiguration {
     public CounterManager counterManager(RedissonClient client) {
         return new RedissonCounterManager(client);
     }
+
+
+    @Bean
+    @ConditionalOnBean(BloomFilterManager.class)
+    public BloomFilterManager bloomFilterManager(RedissonClient client) {
+        return new RedisBloomFilterManager(client);
+    }
 }

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-counter/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-api/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-redis/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/hsweb-concurrent-lock-starter/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-concurrent-lock</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-lock/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-concurrent</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 23 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/pom.xml

@@ -0,0 +1,23 @@
+<?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>hsweb-concurrent-rate-limiter</artifactId>
+        <groupId>org.hswebframework.web</groupId>
+        <version>3.0.4</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>hsweb-concurrent-rate-limiter-api</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 26 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/AbstractRateLimiterManager.java

@@ -0,0 +1,26 @@
+package org.hswebframework.web.concurrent;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public abstract class AbstractRateLimiterManager implements RateLimiterManager {
+    private final Map<String, RateLimiter> counterStore = new HashMap<>(128);
+
+    protected abstract RateLimiter createRateLimiter(String key, double permits, TimeUnit timeUnit);
+
+    @Override
+    public RateLimiter getRateLimiter(String key, double permits, TimeUnit timeUnit) {
+        RateLimiter counter = counterStore.get(key);
+        if (counter != null) {
+            return counter;
+        }
+        synchronized (counterStore) {
+            return counterStore.computeIfAbsent(key, k -> createRateLimiter(key, permits, timeUnit));
+        }
+    }
+}

+ 27 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/GuavaRateLimiterManager.java

@@ -0,0 +1,27 @@
+package org.hswebframework.web.concurrent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public class GuavaRateLimiterManager extends AbstractRateLimiterManager {
+    @Override
+    protected RateLimiter createRateLimiter(String key, double permits, TimeUnit timeUnit) {
+        long seconds = timeUnit.toSeconds(1);
+        double permitsPerSecond = permits;
+
+        if (seconds > 0) {
+            permitsPerSecond = permits / timeUnit.toSeconds(1);
+        } else {
+            if (timeUnit == TimeUnit.MILLISECONDS) {
+                permitsPerSecond = permits / 1000D;
+            }
+        }
+
+        com.google.common.util.concurrent.RateLimiter rateLimiter =
+                com.google.common.util.concurrent.RateLimiter.create(permitsPerSecond);
+        return rateLimiter::tryAcquire;
+    }
+}

+ 17 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/RateLimiter.java

@@ -0,0 +1,17 @@
+package org.hswebframework.web.concurrent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface RateLimiter {
+
+    boolean tryAcquire(int permits, long timeout, TimeUnit timeUnit);
+
+    default boolean tryAcquire(long timeout, TimeUnit unit) {
+        return tryAcquire(1, timeout, unit);
+    }
+
+}

+ 12 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/RateLimiterManager.java

@@ -0,0 +1,12 @@
+package org.hswebframework.web.concurrent;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public interface RateLimiterManager {
+
+    RateLimiter getRateLimiter(String key, double permits, TimeUnit timeUnit);
+}

+ 39 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/main/java/org/hswebframework/web/concurrent/annotation/RateLimiter.java

@@ -0,0 +1,39 @@
+package org.hswebframework.web.concurrent.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 开启限流
+ *
+ * @author zhouhao
+ * @see org.hswebframework.web.concurrent.RateLimiter
+ * @since 3.0.4
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface RateLimiter {
+
+    /**
+     * key,支持spel表达式: ${#username+'login'} .默认以当前注解的方法为key
+     *
+     * @return 限流Key
+     */
+    String[] key() default {};
+
+    /**
+     * @return 时间单位内允许访问次数, 如:每秒100次
+     */
+    double permits() default 100D;
+
+    /**
+     * @return 时间单位, 支持毫秒及以上的时间单位
+     */
+    TimeUnit timeUnit() default TimeUnit.SECONDS;
+
+    long acquire() default 10;
+
+    TimeUnit acquireTimeUnit() default TimeUnit.SECONDS;
+}

+ 28 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-api/src/test/java/org/hswebframework/web/concurrent/GuavaRateLimiterManagerTest.java

@@ -0,0 +1,28 @@
+package org.hswebframework.web.concurrent;
+
+import lombok.SneakyThrows;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+public class GuavaRateLimiterManagerTest {
+    GuavaRateLimiterManager manager = new GuavaRateLimiterManager();
+
+    @SneakyThrows
+    @Test
+    public void testRateLimiter() {
+        RateLimiter limiter = manager.getRateLimiter("test", 1, TimeUnit.SECONDS);
+        for (int i = 0; i < 100; i++) {
+            if (!limiter.tryAcquire(10, TimeUnit.SECONDS)) {
+                throw new UnsupportedOperationException();
+            }
+            System.out.println(i + ":" + System.currentTimeMillis());
+        }
+
+    }
+}

+ 52 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/pom.xml

@@ -0,0 +1,52 @@
+<?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>hsweb-concurrent-rate-limiter</artifactId>
+        <groupId>org.hswebframework.web</groupId>
+        <version>3.0.4</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>hsweb-concurrent-rate-limiter-starter</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-concurrent-rate-limiter-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-commons-utils</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-authorization-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-tests</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aspects</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 69 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/java/org/hswebframework/web/concurent/RateLimiterAopAdvisor.java

@@ -0,0 +1,69 @@
+package org.hswebframework.web.concurent;
+
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.ExpressionUtils;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.concurrent.RateLimiterManager;
+import org.hswebframework.web.concurrent.annotation.RateLimiter;
+import org.springframework.aop.MethodBeforeAdvice;
+import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ClassUtils;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Slf4j
+public class RateLimiterAopAdvisor extends StaticMethodMatcherPointcutAdvisor {
+
+    private static final long                    serialVersionUID = -1076122956392948260L;
+    private static final ParameterNameDiscoverer nameDiscoverer   = new LocalVariableTableParameterNameDiscoverer();
+
+    public RateLimiterAopAdvisor(RateLimiterManager rateLimiterManager) {
+        setAdvice((MethodBeforeAdvice) (method, args, target) -> {
+            String[] names = nameDiscoverer.getParameterNames(method);
+            RateLimiter limiter = Optional.ofNullable(AnnotationUtils.findAnnotation(method, RateLimiter.class))
+                    .orElseGet(() -> AnnotationUtils.findAnnotation(ClassUtils.getUserClass(target), RateLimiter.class));
+            if (limiter != null) {
+                List<String> keyExpressionList = new ArrayList<>(Arrays.asList(limiter.key()));
+                if (keyExpressionList.isEmpty()) {
+                    keyExpressionList.add(method.toString());
+                }
+                for (String keyExpress : keyExpressionList) {
+                    if (keyExpress.contains("${")) {
+                        Map<String, Object> params = new HashMap<>();
+                        params.put("user", Authentication.current().map(Authentication::getUser).orElse(null));
+                        for (int i = 0; i < args.length; i++) {
+                            params.put(names.length > i ? names[i] : "arg" + i, args[i]);
+                            params.put("arg" + i, args[i]);
+
+                        }
+                        keyExpress = ExpressionUtils.analytical(keyExpress, params, "spel");
+                    }
+                    log.debug("do rate limiter:[{}]. ", keyExpress);
+                    boolean success = rateLimiterManager
+                            .getRateLimiter(keyExpress, limiter.permits(), limiter.timeUnit())
+                            .tryAcquire(limiter.acquire(), limiter.acquireTimeUnit());
+                    if (!success) {
+                        throw new TimeoutException("请求超时");
+                    }
+                }
+            }
+        });
+    }
+
+
+    @Override
+    public boolean matches(Method method, Class<?> targetClass) {
+
+        return AnnotationUtils.findAnnotation(method, RateLimiter.class) != null
+                || AnnotationUtils.findAnnotation(targetClass, RateLimiter.class) != null;
+    }
+}

+ 30 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/java/org/hswebframework/web/concurent/RateLimiterAutoConfiguration.java

@@ -0,0 +1,30 @@
+package org.hswebframework.web.concurent;
+
+import org.hswebframework.web.concurrent.GuavaRateLimiterManager;
+import org.hswebframework.web.concurrent.RateLimiterManager;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Configuration
+public class RateLimiterAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(RateLimiterManager.class)
+    @ConditionalOnClass(name = "com.google.common.util.concurrent.RateLimiter")
+    public GuavaRateLimiterManager guavaRateLimiterManager() {
+        return new GuavaRateLimiterManager();
+    }
+
+    @Bean
+    @ConditionalOnBean(RateLimiterManager.class)
+    public RateLimiterAopAdvisor rateLimiterAopAdvisor(RateLimiterManager rateLimiterManager) {
+        return new RateLimiterAopAdvisor(rateLimiterManager);
+    }
+}

+ 3 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.hswebframework.web.concurent.RateLimiterAutoConfiguration

+ 48 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/RateLimiterAopAdvisorTest.groovy

@@ -0,0 +1,48 @@
+package org.hswebframework.web.concurent
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@SpringBootTest(classes = TestApplication.class)
+@ContextConfiguration
+class RateLimiterAopAdvisorTest extends Specification {
+
+    @Autowired
+    public TestService testService;
+
+    def "测试限流"() {
+        TestService.counter.set(0);
+        given:
+        testService.test();
+        def timeoutException;
+        try {
+            testService.test();
+        } catch (Exception e) {
+            timeoutException = e;
+        }
+        expect:
+        TestService.counter.get() == 1
+        timeoutException != null
+    }
+
+    def "测试指定key限流"() {
+        TestService.counter.set(0);
+        given:
+        testService.test("test");
+        def timeoutException;
+        try {
+            testService.test("test");
+        } catch (Exception e) {
+            timeoutException = e;
+        }
+        expect:
+        TestService.counter.get() == 1
+        timeoutException != null
+    }
+}

+ 11 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/TestApplication.java

@@ -0,0 +1,11 @@
+package org.hswebframework.web.concurent;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@SpringBootApplication
+public class TestApplication {
+}

+ 30 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/hsweb-concurrent-rate-limiter-starter/src/test/groovy/org/hswebframework/web/concurent/TestService.java

@@ -0,0 +1,30 @@
+package org.hswebframework.web.concurent;
+
+import org.hswebframework.web.concurrent.annotation.RateLimiter;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author zhouhao
+ * @since 3.0.4
+ */
+@Service
+public class TestService {
+
+    public static AtomicLong counter = new AtomicLong();
+
+
+    @RateLimiter(permits = 1, acquire = 500, acquireTimeUnit = TimeUnit.MILLISECONDS) //一秒一次
+    public String test() {
+        counter.incrementAndGet();
+        return "ok";
+    }
+
+    @RateLimiter(key = "${#name}", permits = 1, acquire = 500, acquireTimeUnit = TimeUnit.MILLISECONDS)
+    public String test(String name) {
+        counter.incrementAndGet();
+        return name;
+    }
+}

+ 20 - 0
hsweb-concurrent/hsweb-concurrent-rate-limiter/pom.xml

@@ -0,0 +1,20 @@
+<?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>hsweb-concurrent</artifactId>
+        <groupId>org.hswebframework.web</groupId>
+        <version>3.0.4</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>hsweb-concurrent-rate-limiter</artifactId>
+    <packaging>pom</packaging>
+    <modules>
+        <module>hsweb-concurrent-rate-limiter-api</module>
+        <module>hsweb-concurrent-rate-limiter-starter</module>
+    </modules>
+
+
+</project>

+ 2 - 1
hsweb-concurrent/pom.xml

@@ -22,7 +22,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -35,6 +35,7 @@
         <module>hsweb-concurrent-lock</module>
         <module>hsweb-concurrent-counter</module>
         <module>hsweb-concurrent-async-job</module>
+        <module>hsweb-concurrent-rate-limiter</module>
     </modules>
 
 

+ 1 - 1
hsweb-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <artifactId>hsweb-framework</artifactId>
         <groupId>org.hswebframework.web</groupId>
-        <version>3.0.3</version>
+        <version>3.0.4</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>

+ 18 - 0
hsweb-core/src/main/java/org/hswebframework/web/dict/EnumDict.java

@@ -96,6 +96,10 @@ public interface EnumDict<V> extends JSONSerializable {
         return (mask & getMask()) != 0;
     }
 
+    default boolean in(EnumDict<V>... dict) {
+        return in(toMask(dict));
+    }
+
     /**
      * 枚举选项的描述,对一个选项进行详细的描述有时候是必要的.默认值为{@link this#getText()}
      *
@@ -163,6 +167,20 @@ public interface EnumDict<V> extends JSONSerializable {
         return value;
     }
 
+
+    @SafeVarargs
+    static <T extends Enum & EnumDict> boolean in(T target, T... t) {
+        Enum[] all = target.getClass().getEnumConstants();
+
+        if (all.length >= 64) {
+            List<T> list = Arrays.asList(t);
+            return Arrays.stream(all)
+                    .map(EnumDict.class::cast)
+                    .anyMatch(list::contains);
+        }
+        return maskIn(toMask(t), target);
+    }
+
     @SafeVarargs
     static <T extends EnumDict> boolean maskIn(long mask, T... t) {
         long value = toMask(t);

+ 0 - 0
hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java


Some files were not shown because too many files changed in this diff