Browse Source

代码优化

woody 1 năm trước cách đây
mục cha
commit
aa85f7a9aa
48 tập tin đã thay đổi với 1282 bổ sung791 xóa
  1. 20 8
      framework-base/src/main/java/com/chelvc/framework/base/annotation/Decimal.java
  2. 57 0
      framework-base/src/main/java/com/chelvc/framework/base/annotation/Enumerate.java
  3. 7 0
      framework-base/src/main/java/com/chelvc/framework/base/annotation/ResponseWrapping.java
  4. 5 0
      framework-base/src/main/java/com/chelvc/framework/base/apidoc/CustomizeParameterAnalyser.java
  5. 56 3
      framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java
  6. 79 47
      framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java
  7. 21 0
      framework-base/src/main/java/com/chelvc/framework/base/converter/StringDurationConverter.java
  8. 20 7
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/GlobalExceptionInterceptor.java
  9. 15 4
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseWrapInterceptor.java
  10. 65 15
      framework-base/src/main/java/com/chelvc/framework/base/jackson/DecimalFormatSerializer.java
  11. 126 7
      framework-base/src/main/java/com/chelvc/framework/base/jackson/EnumerationFormatSerializer.java
  12. 5 5
      framework-base/src/main/java/com/chelvc/framework/base/model/Session.java
  13. 85 0
      framework-base/src/main/java/com/chelvc/framework/base/util/ResourceUtils.java
  14. 35 0
      framework-cloud-client-feign/pom.xml
  15. 51 0
      framework-cloud-kubernetes/pom.xml
  16. 29 0
      framework-cloud-nacos-dubbo/pom.xml
  17. 29 0
      framework-cloud-nacos-feign/pom.xml
  18. 1 1
      framework-cloud-nacos/pom.xml
  19. 9 9
      framework-common/src/main/java/com/chelvc/framework/common/model/Enumeration.java
  20. 1 1
      framework-common/src/main/java/com/chelvc/framework/common/model/Media.java
  21. 5 0
      framework-common/src/main/java/com/chelvc/framework/common/model/Paging.java
  22. 9 0
      framework-common/src/main/java/com/chelvc/framework/common/model/Period.java
  23. 5 0
      framework-common/src/main/java/com/chelvc/framework/common/model/Region.java
  24. 6 4
      framework-common/src/main/java/com/chelvc/framework/common/util/IdentityUtils.java
  25. 28 0
      framework-common/src/main/java/com/chelvc/framework/common/util/JacksonUtils.java
  26. 10 12
      framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java
  27. 1 1
      framework-dependencies/pom.xml
  28. 0 4
      framework-feign/pom.xml
  29. 10 1
      framework-feign/src/main/java/com/chelvc/framework/feign/config/FeignConfigurer.java
  30. 27 12
      framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java
  31. 2 5
      framework-jpush/src/main/java/com/chelvc/framework/jpush/DefaultJPushHandler.java
  32. 5 2
      framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthConfigurer.java
  33. 207 34
      framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java
  34. 2 2
      framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenStore.java
  35. 0 36
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/annotation/DelayConsumer.java
  36. 53 273
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java
  37. 0 204
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/model/Delay.java
  38. 9 1
      framework-security/src/main/java/com/chelvc/framework/security/annotation/Desensitize.java
  39. 9 2
      framework-security/src/main/java/com/chelvc/framework/security/annotation/Encrypt.java
  40. 15 15
      framework-security/src/main/java/com/chelvc/framework/security/config/MethodSecurityExpression.java
  41. 12 12
      framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java
  42. 47 28
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/JacksonDesensitizeSerializer.java
  43. 42 13
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/JacksonEncryptSerializer.java
  44. 6 7
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/RequestSecurityInterceptor.java
  45. 6 6
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/SignatureValidateInterceptor.java
  46. 20 0
      framework-sms/src/main/java/com/chelvc/framework/sms/config/CaptchaSmsProperties.java
  47. 25 9
      framework-sms/src/main/java/com/chelvc/framework/sms/support/DefaultCaptchaSmsHandler.java
  48. 5 1
      pom.xml

+ 20 - 8
framework-base/src/main/java/com/chelvc/framework/base/annotation/Decimal.java

@@ -21,29 +21,41 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  */
 @Inherited
 @Documented
+@JacksonAnnotationsInside
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
 @JsonSerialize(using = DecimalFormatSerializer.class)
 public @interface Decimal {
     /**
-     * 是否去掉多余的0
-     *
-     * @return true/false
+     * 默认小数保留位数
      */
-    boolean trimZeros() default false;
+    int DEFAULT_SCALE = 2;
 
     /**
-     * 获取小数保留位数,默认保留2位小数
+     * 获取小数保留位数
      *
      * @return 小数保留位数
      */
-    int scale() default DecimalFormatSerializer.DEFAULT_SCALE;
+    int scale() default DEFAULT_SCALE;
 
     /**
-     * 获取小数舍弃模式,默认四舍五入
+     * 获取小数舍弃模式
      *
      * @return 小数舍弃模式
      */
     RoundingMode mode() default RoundingMode.HALF_UP;
+
+    /**
+     * 是否去掉多余的0
+     *
+     * @return true/false
+     */
+    boolean trimZeros() default false;
+
+    /**
+     * 版本控制
+     *
+     * @return 版本控制数组
+     */
+    Versioning[] versioning() default {};
 }

+ 57 - 0
framework-base/src/main/java/com/chelvc/framework/base/annotation/Enumerate.java

@@ -0,0 +1,57 @@
+package com.chelvc.framework.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.chelvc.framework.base.jackson.EnumerationFormatSerializer;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * Jackson枚举对象结构化注解
+ *
+ * @author Woody
+ * @date 2023/9/17
+ */
+@Inherited
+@Documented
+@JacksonAnnotationsInside
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD})
+@JsonSerialize(using = EnumerationFormatSerializer.class)
+public @interface Enumerate {
+    /**
+     * 默认编号字段名称
+     */
+    String DEFAULT_CODE_NAME = "code";
+
+    /**
+     * 默认描述字段名称
+     */
+    String DEFAULT_DESCRIPTION_NAME = "description";
+
+    /**
+     * 获取编号字段名称
+     *
+     * @return 编号字段名称
+     */
+    String codeName() default DEFAULT_CODE_NAME;
+
+    /**
+     * 获取描述字段名称
+     *
+     * @return 描述字段名称
+     */
+    String descriptionName() default DEFAULT_DESCRIPTION_NAME;
+
+    /**
+     * 版本控制
+     *
+     * @return 版本控制数组
+     */
+    Versioning[] versioning() default {};
+}

+ 7 - 0
framework-base/src/main/java/com/chelvc/framework/base/annotation/ResponseWrapping.java

@@ -32,4 +32,11 @@ public @interface ResponseWrapping {
      * @return true/false
      */
     boolean value() default true;
+
+    /**
+     * 版本控制
+     *
+     * @return 版本控制数组
+     */
+    Versioning[] versioning() default {};
 }

+ 5 - 0
framework-base/src/main/java/com/chelvc/framework/base/apidoc/CustomizeParameterAnalyser.java

@@ -4,6 +4,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -135,6 +136,10 @@ public class CustomizeParameterAnalyser extends ParameterAnalyser {
             parameter.setType(Integer.class);
             parameter.setFields(null);
             parameter.setExample("110000");
+        } else if (parameter.getOriginal() == Duration.class) {
+            parameter.setType(String.class);
+            parameter.setFields(null);
+            parameter.setExample("PT1S");
         } else if (this.isEnumerationFormatted(parameter)) {
             this.initializeEnumerationFormation(parameter);
         }

+ 56 - 3
framework-base/src/main/java/com/chelvc/framework/base/context/ApplicationContextHolder.java

@@ -45,6 +45,11 @@ import org.springframework.stereotype.Component;
 @Slf4j
 @Component
 public class ApplicationContextHolder implements ApplicationContextAware {
+    /**
+     * 环境属性
+     */
+    public static final String PROFILE_PROPERTY = "spring.profiles.active";
+
     /**
      * 应用名称属性
      */
@@ -485,6 +490,25 @@ public class ApplicationContextHolder implements ApplicationContextAware {
         return resources;
     }
 
+    /**
+     * 获取环境名称
+     *
+     * @return 环境名称
+     */
+    public static String getProfile() {
+        return getProfile(getApplicationContext());
+    }
+
+    /**
+     * 获取环境名称
+     *
+     * @param applicationContext 应用上下文实例
+     * @return 环境名称
+     */
+    public static String getProfile(@NonNull ApplicationContext applicationContext) {
+        return applicationContext.getEnvironment().getProperty(PROFILE_PROPERTY);
+    }
+
     /**
      * 获取应用名称
      *
@@ -583,7 +607,19 @@ public class ApplicationContextHolder implements ApplicationContextAware {
      * @return 消息内容
      */
     public static String getMessage(@NonNull String code, @NonNull Object... args) {
-        return getMessage(LocaleContextHolder.getLocale(), code, args);
+        return getMessage(code, code, args);
+    }
+
+    /**
+     * 获取国际化消息
+     *
+     * @param code     消息标识
+     * @param defaults 默认消息
+     * @param args     消息参数
+     * @return 消息内容
+     */
+    public static String getMessage(@NonNull String code, String defaults, @NonNull Object... args) {
+        return getMessage(LocaleContextHolder.getLocale(), code, defaults, args);
     }
 
     /**
@@ -595,14 +631,31 @@ public class ApplicationContextHolder implements ApplicationContextAware {
      * @return 消息内容
      */
     public static String getMessage(@NonNull Locale locale, @NonNull String code, @NonNull Object... args) {
+        return getMessage(locale, code, code, args);
+    }
+
+    /**
+     * 获取国际化消息
+     *
+     * @param locale   本地信息
+     * @param code     消息标识
+     * @param defaults 默认消息
+     * @param args     消息参数
+     * @return 消息内容
+     */
+    public static String getMessage(@NonNull Locale locale, @NonNull String code, String defaults,
+                                    @NonNull Object... args) {
+        if (Objects.isNull(defaults)) {
+            defaults = code;
+        }
         ApplicationContext applicationContext = getApplicationContext(false);
         if (applicationContext != null) {
             try {
-                return applicationContext.getMessage(code, args, code, locale);
+                return applicationContext.getMessage(code, args, defaults, locale);
             } catch (Exception e) {
                 log.warn("Application message get failed [{}]: {}, {}", locale, code, e.getMessage());
             }
         }
-        return code;
+        return defaults;
     }
 }

+ 79 - 47
framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java

@@ -45,6 +45,11 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_ID = "Id";
 
+    /**
+     * 会话类型请求头
+     */
+    public static final String HEADER_TYPE = "Type";
+
     /**
      * 使用信息请求头
      */
@@ -60,11 +65,6 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_DEVICE = "Device";
 
-    /**
-     * 业务类型请求头
-     */
-    public static final String HEADER_BUSINESS = "Business";
-
     /**
      * 渠道来源请求头
      */
@@ -100,6 +100,11 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_FINGERPRINT = "Fingerprint";
 
+    /**
+     * 认证信息请求头
+     */
+    public static final String HEADER_AUTHORIZATION = "Authorization";
+
     /**
      * 响应结果包装请求/响应头
      */
@@ -217,6 +222,35 @@ public class SessionContextHolder implements ServletRequestListener {
         return StringUtils.ifEmpty(request.getHeader(HEADER_ID), Long::parseLong);
     }
 
+    /**
+     * 获取会话类型
+     *
+     * @return 会话类型
+     */
+    public static String getType() {
+        return getType(true);
+    }
+
+    /**
+     * 获取会话类型
+     *
+     * @param requireSession 会话是否是必须
+     * @return 会话类型
+     */
+    public static String getType(boolean requireSession) {
+        return ObjectUtils.ifNull(getSession(requireSession), Session::getType);
+    }
+
+    /**
+     * 获取会话类型
+     *
+     * @param request Http请求对象
+     * @return 会话类型
+     */
+    public static String getType(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_TYPE), (String) null);
+    }
+
     /**
      * 获取使用信息
      *
@@ -333,35 +367,6 @@ public class SessionContextHolder implements ServletRequestListener {
         return StringUtils.ifEmpty(request.getHeader(HEADER_DEVICE), (String) null);
     }
 
-    /**
-     * 获取业务类型
-     *
-     * @return 业务类型
-     */
-    public static String getBusiness() {
-        return getBusiness(true);
-    }
-
-    /**
-     * 获取业务类型
-     *
-     * @param requireSession 会话是否是必须
-     * @return 业务类型
-     */
-    public static String getBusiness(boolean requireSession) {
-        return ObjectUtils.ifNull(getSession(requireSession), Session::getBusiness);
-    }
-
-    /**
-     * 获取业务类型
-     *
-     * @param request Http请求对象
-     * @return 业务类型
-     */
-    public static String getBusiness(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_BUSINESS), (String) null);
-    }
-
     /**
      * 获取当前会话渠道来源
      *
@@ -555,6 +560,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return StringUtils.ifEmpty(request.getHeader(HEADER_FINGERPRINT), (String) null);
     }
 
+    /**
+     * 获取认证信息
+     *
+     * @param request Http请求对象
+     * @return 认证信息
+     */
+    public static String getAuthorization(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_AUTHORIZATION), (String) null);
+    }
+
     /**
      * 判断是否是历史首次使用
      *
@@ -648,6 +663,23 @@ public class SessionContextHolder implements ServletRequestListener {
         return false;
     }
 
+    /**
+     * 判断当前版本是否和指定版本相同
+     *
+     * @param versions 版本控制注解实例数组
+     * @return true/false
+     */
+    public static boolean isVersion(Versioning... versions) {
+        if (ObjectUtils.nonEmpty(versions)) {
+            for (Versioning versioning : versions) {
+                if (isVersion(versioning)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * 判断当前版本是否在指定版本之前
      *
@@ -762,29 +794,29 @@ public class SessionContextHolder implements ServletRequestListener {
     /**
      * 初始化会话主体
      *
-     * @param id       主体标识
-     * @param mobile   电话号码
-     * @param business 业务类型
+     * @param id     主体标识
+     * @param type   会话类型
+     * @param mobile 电话号码
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, String mobile, String business) {
-        return initializeSessionPrincipal(id, mobile, business, true);
+    public static Session initializeSessionPrincipal(@NonNull Long id, String type, String mobile) {
+        return initializeSessionPrincipal(id, type, mobile, true);
     }
 
     /**
      * 初始化会话主体
      *
-     * @param id       主体标识
-     * @param mobile   电话号码
-     * @param business 业务类型
-     * @param cover    是否覆盖已认证主体信息
+     * @param id     主体标识
+     * @param type   会话类型
+     * @param mobile 电话号码
+     * @param cover  是否覆盖已认证主体信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, String mobile, String business, boolean cover) {
+    public static Session initializeSessionPrincipal(@NonNull Long id, String type, String mobile, boolean cover) {
         Deque<Session> deque = SESSION_CONTEXT.get();
         Session session = deque.peek();
         if (cover || ObjectUtils.ifNull(session, Session::getId) == null) {
-            session = Session.builder().id(id).mobile(mobile).business(business)
+            session = Session.builder().id(id).type(type).mobile(mobile)
                     .using(ObjectUtils.ifNull(session, Session::getUsing))
                     .host(ObjectUtils.ifNull(session, Session::getHost))
                     .device(ObjectUtils.ifNull(session, Session::getDevice))
@@ -924,8 +956,8 @@ public class SessionContextHolder implements ServletRequestListener {
      * @return 会话信息
      */
     protected Session initializeSession(@NonNull HttpServletRequest request) {
-        return Session.builder().id(getId(request)).using(getUsing(request)).host(getHost(request))
-                .mobile(getMobile(request)).device(getDevice(request)).business(getBusiness(request))
+        return Session.builder().id(getId(request)).type(getType(request)).using(getUsing(request))
+                .host(getHost(request)).mobile(getMobile(request)).device(getDevice(request))
                 .channel(getChannel(request)).platform(getPlatform(request)).terminal(getTerminal(request))
                 .version(getVersion(request)).fingerprint(getFingerprint(request))
                 .timestamp(getTimestamp(request)).build();

+ 21 - 0
framework-base/src/main/java/com/chelvc/framework/base/converter/StringDurationConverter.java

@@ -0,0 +1,21 @@
+package com.chelvc.framework.base.converter;
+
+import java.time.Duration;
+
+import com.chelvc.framework.common.util.StringUtils;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+/**
+ * 字符串时间周期转换器
+ *
+ * @author Woody
+ * @date 2023/9/16
+ */
+@Component
+public class StringDurationConverter implements Converter<String, Duration> {
+    @Override
+    public Duration convert(String source) {
+        return StringUtils.ifEmpty(source, Duration::parse);
+    }
+}

+ 20 - 7
framework-base/src/main/java/com/chelvc/framework/base/interceptor/GlobalExceptionInterceptor.java

@@ -38,6 +38,9 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException;
 import org.springframework.web.HttpMediaTypeNotSupportedException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingMatrixVariableException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
 import org.springframework.web.bind.ServletRequestBindingException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -142,26 +145,36 @@ public class GlobalExceptionInterceptor implements ErrorController {
             Map<String, String> errors = this.format((HttpMessageNotReadableException) e);
             return CollectionUtils.isEmpty(errors) ? Result.build(HttpStatus.BAD_REQUEST) :
                     Result.build(HttpStatus.PRECONDITION_FAILED, errors);
-        } else if (e instanceof HttpMessageConversionException || e instanceof ServletRequestBindingException
-                || e instanceof MultipartException) {
-            return Result.build(HttpStatus.BAD_REQUEST);
+        } else if (e instanceof MissingPathVariableException) {
+            return Result.build(HttpStatus.PRECONDITION_FAILED, ImmutableMap.of(
+                    ((MissingPathVariableException) e).getVariableName(), e.getMessage()
+            ));
+        } else if (e instanceof MissingMatrixVariableException) {
+            return Result.build(HttpStatus.PRECONDITION_FAILED, ImmutableMap.of(
+                    ((MissingMatrixVariableException) e).getVariableName(), e.getMessage()
+            ));
+        } else if (e instanceof MissingServletRequestParameterException) {
+            return Result.build(HttpStatus.PRECONDITION_FAILED, ImmutableMap.of(
+                    ((MissingServletRequestParameterException) e).getParameterName(), e.getMessage()
+            ));
         } else if (e instanceof BindException) {
             return Result.build(HttpStatus.PRECONDITION_FAILED, this.format(((BindException) e).getBindingResult()));
         } else if (e instanceof ConstraintViolationException) {
             return Result.build(HttpStatus.PRECONDITION_FAILED, this.format((ConstraintViolationException) e));
-        } else if (e instanceof ValidationException) {
-            return Result.build(HttpStatus.BAD_REQUEST, null, e.getMessage());
         } else if (e instanceof MethodArgumentNotValidException) {
             return Result.build(HttpStatus.PRECONDITION_FAILED,
                     this.format(((MethodArgumentNotValidException) e).getBindingResult()));
         } else if (e instanceof MethodArgumentTypeMismatchException) {
             return Result.build(HttpStatus.PRECONDITION_FAILED, this.format((MethodArgumentTypeMismatchException) e));
-        } else if (e instanceof TypeMismatchException) {
-            return Result.build(HttpStatus.BAD_REQUEST);
         } else if (e instanceof MissingServletRequestPartException) {
             return Result.build(HttpStatus.PRECONDITION_FAILED, ImmutableMap.of(
                     ((MissingServletRequestPartException) e).getRequestPartName(), e.getMessage()
             ));
+        } else if (e instanceof ValidationException) {
+            return Result.build(HttpStatus.BAD_REQUEST, null, e.getMessage());
+        } else if (e instanceof TypeMismatchException || e instanceof MultipartException
+                || e instanceof HttpMessageConversionException || e instanceof ServletRequestBindingException) {
+            return Result.build(HttpStatus.BAD_REQUEST);
         } else if (e instanceof FrameworkException) {
             FrameworkException exception = (FrameworkException) e;
             return Result.build(exception.getStatus(), exception.getData(), exception.getMessage());

+ 15 - 4
framework-base/src/main/java/com/chelvc/framework/base/interceptor/ResponseWrapInterceptor.java

@@ -2,6 +2,7 @@ package com.chelvc.framework.base.interceptor;
 
 import com.chelvc.framework.base.annotation.ResponseWrapping;
 import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.util.ObjectUtils;
 import lombok.NonNull;
 import org.springframework.core.MethodParameter;
 import org.springframework.web.bind.annotation.ResponseBody;
@@ -27,6 +28,16 @@ public class ResponseWrapInterceptor implements HandlerMethodReturnValueHandler
         this.processor = processor;
     }
 
+    /**
+     * 判断是否受版本控制
+     *
+     * @param annotation 响应包装注解实例
+     * @return true/false
+     */
+    private boolean isVersioning(ResponseWrapping annotation) {
+        return ObjectUtils.isEmpty(annotation.versioning()) || SessionContextHolder.isVersion(annotation.versioning());
+    }
+
     /**
      * 判断目标方法是否使用@ResponseBody注解
      *
@@ -46,11 +57,11 @@ public class ResponseWrapInterceptor implements HandlerMethodReturnValueHandler
      * @return true/false
      */
     private boolean isResponseWrappingMethod(MethodParameter method) {
-        ResponseWrapping wrapping = method.getMethodAnnotation(ResponseWrapping.class);
-        if (wrapping == null) {
-            wrapping = method.getDeclaringClass().getAnnotation(ResponseWrapping.class);
+        ResponseWrapping annotation = method.getMethodAnnotation(ResponseWrapping.class);
+        if (annotation == null) {
+            annotation = method.getDeclaringClass().getAnnotation(ResponseWrapping.class);
         }
-        return wrapping != null && wrapping.value();
+        return annotation != null && annotation.value() && this.isVersioning(annotation);
     }
 
     @Override

+ 65 - 15
framework-base/src/main/java/com/chelvc/framework/base/jackson/DecimalFormatSerializer.java

@@ -2,8 +2,11 @@ package com.chelvc.framework.base.jackson;
 
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 
 import com.chelvc.framework.base.annotation.Decimal;
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.BeanProperty;
 import com.fasterxml.jackson.databind.JsonSerializer;
@@ -15,26 +18,60 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer;
  * Jackson小数序格式化处理器
  *
  * @author Woody
- * @date 2023/4/5
+ * @date 2023/9/9
  */
 public class DecimalFormatSerializer extends StdSerializer<Number> implements ContextualSerializer {
     /**
-     * 默认小数保留位数
+     * 是否启用
      */
-    public static final int DEFAULT_SCALE = 2;
+    private boolean enabled;
 
     /**
      * 小数格式化注解实例
      */
-    private final Decimal decimal;
+    private Decimal annotation;
 
     public DecimalFormatSerializer() {
-        this(null);
+        super(Number.class);
+        this.enabled = true;
     }
 
-    public DecimalFormatSerializer(Decimal decimal) {
-        super(Number.class);
-        this.decimal = decimal;
+    /**
+     * 获取小数保留位数
+     *
+     * @return 小数保留位数
+     */
+    private int getScale() {
+        Integer scale = ObjectUtils.ifNull(this.annotation, Decimal::scale);
+        return scale == null || scale < 1 ? Decimal.DEFAULT_SCALE : scale;
+    }
+
+    /**
+     * 获取小数舍弃模式
+     *
+     * @return 小数舍弃模式
+     */
+    private RoundingMode getMode() {
+        return ObjectUtils.ifNull(ObjectUtils.ifNull(this.annotation, Decimal::mode), RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 是否去掉多余的0
+     *
+     * @return true/false
+     */
+    private boolean isTrimZeros() {
+        return ObjectUtils.ifNull(ObjectUtils.ifNull(this.annotation, Decimal::trimZeros), false);
+    }
+
+    /**
+     * 判断是否受版本控制
+     *
+     * @param annotation 小数格式化注解实例
+     * @return true/false
+     */
+    private boolean isVersioning(Decimal annotation) {
+        return ObjectUtils.isEmpty(annotation.versioning()) || SessionContextHolder.isVersion(annotation.versioning());
     }
 
     /**
@@ -49,15 +86,25 @@ public class DecimalFormatSerializer extends StdSerializer<Number> implements Co
                 || BigDecimal.class.isAssignableFrom(property.getType().getRawClass());
     }
 
+    /**
+     * 获取属性小数注解实例
+     *
+     * @param property 属性对象
+     * @return 小数注解实例
+     */
+    private Decimal getDecimalAnnotation(BeanProperty property) {
+        return property.getAnnotation(Decimal.class);
+    }
+
     @Override
     public void serialize(Number number, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
-        if (this.decimal == null || number == null) {
+        if (!this.enabled || this.annotation == null || number == null) {
             generator.writeObject(number);
         } else {
             BigDecimal decimal = number instanceof BigDecimal ? (BigDecimal) number : new BigDecimal(number.toString());
-            decimal = decimal.setScale(this.decimal.scale(), this.decimal.mode());
-            if (this.decimal.trimZeros()) {
+            decimal = decimal.setScale(this.getScale(), this.getMode());
+            if (this.isTrimZeros()) {
                 decimal = decimal.stripTrailingZeros();
             }
             generator.writeNumber(decimal.toPlainString());
@@ -68,10 +115,13 @@ public class DecimalFormatSerializer extends StdSerializer<Number> implements Co
     public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) {
         if (property == null) {
             return provider.getDefaultNullValueSerializer();
-        } else if (!this.isDecimalProperty(property)) {
-            return this;
         }
-        Decimal decimal = property.getAnnotation(Decimal.class);
-        return decimal == null ? this : new DecimalFormatSerializer(decimal);
+        if (!this.isDecimalProperty(property)) {
+            this.enabled = false;
+        } else {
+            this.annotation = this.getDecimalAnnotation(property);
+            this.enabled = this.annotation != null && this.isVersioning(this.annotation);
+        }
+        return this;
     }
 }

+ 126 - 7
framework-base/src/main/java/com/chelvc/framework/base/jackson/EnumerationFormatSerializer.java

@@ -2,9 +2,17 @@ package com.chelvc.framework.base.jackson;
 
 import java.io.IOException;
 
+import com.chelvc.framework.base.annotation.Enumerate;
+import com.chelvc.framework.base.context.ApplicationContextHolder;
+import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.model.Enumeration;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonSerializer;
 import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
 import com.google.common.collect.ImmutableMap;
 
@@ -12,18 +20,129 @@ import com.google.common.collect.ImmutableMap;
  * Jackson枚举对象结构化处理器
  *
  * @author Woody
- * @date 2023/9/2
+ * @date 2023/9/9
  */
-public class EnumerationFormatSerializer extends StdSerializer<Enumeration> {
+public class EnumerationFormatSerializer extends StdSerializer<Object> implements ContextualSerializer {
+    private boolean enabled;
+    private Enumerate annotation;
+
     public EnumerationFormatSerializer() {
-        super(Enumeration.class);
+        super(Object.class);
+        this.enabled = true;
+    }
+
+    /**
+     * 获取编号字段名称
+     *
+     * @return 编号字段名称
+     */
+    private String getCodeName() {
+        String name = ObjectUtils.ifNull(this.annotation, Enumerate::codeName);
+        return StringUtils.ifEmpty(name, Enumerate.DEFAULT_CODE_NAME);
+    }
+
+    /**
+     * 获取描述字段名称
+     *
+     * @return 描述字段名称
+     */
+    private String getDescriptionName() {
+        String name = ObjectUtils.ifNull(this.annotation, Enumerate::descriptionName);
+        return StringUtils.ifEmpty(name, Enumerate.DEFAULT_DESCRIPTION_NAME);
+    }
+
+    /**
+     * 获取枚举编号
+     *
+     * @param object 枚举对象实例
+     * @return 枚举编号
+     */
+    private String getCode(Object object) {
+        if (object instanceof Enumeration) {
+            Enumeration enumeration = (Enumeration) object;
+            return StringUtils.ifEmpty(StringUtils.ifEmpty(enumeration.getCode(), enumeration::name), object::toString);
+        }
+        return ((Enum<?>) object).name();
+    }
+
+    /**
+     * 获取枚举描述
+     *
+     * @param object 枚举对象实例
+     * @return 枚举描述
+     */
+    private String getDescription(Object object) {
+        String description = null;
+        if (object instanceof Enumeration) {
+            description = ((Enumeration) object).getDescription();
+        }
+        if (StringUtils.nonEmpty(description)) {
+            return description;
+        }
+
+        // 如果描述信息为空则从国际化文件中获取
+        String code = this.getCode(object);
+        return ApplicationContextHolder.getMessage(object.getClass().getName() + "." + code, code);
+    }
+
+    /**
+     * 判断是否受版本控制
+     *
+     * @param annotation 枚举式化注解实例
+     * @return true/false
+     */
+    private boolean isVersioning(Enumerate annotation) {
+        return ObjectUtils.isEmpty(annotation.versioning()) || SessionContextHolder.isVersion(annotation.versioning());
+    }
+
+    /**
+     * 判断是否是枚举属性
+     *
+     * @param property 属性对象
+     * @return true/false
+     */
+    private boolean isEnumerationProperty(BeanProperty property) {
+        return Enum.class.isAssignableFrom(property.getType().getRawClass())
+                || Enumeration.class.isAssignableFrom(property.getType().getRawClass());
+    }
+
+    /**
+     * 获取属性枚举注解实例
+     *
+     * @param property 属性对象
+     * @return 枚举注解实例
+     */
+    private Enumerate getEnumerateAnnotation(BeanProperty property) {
+        Enumerate annotation = property.getAnnotation(Enumerate.class);
+        if (annotation == null) {
+            return property.getType().getRawClass().getAnnotation(Enumerate.class);
+        }
+        return annotation;
     }
 
     @Override
-    public void serialize(Enumeration enumeration, JsonGenerator generator, SerializerProvider provider)
+    public void serialize(Object object, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
-        generator.writeObject(ImmutableMap.of(
-                "code", enumeration.getCode(), "description", enumeration.getDescription()
-        ));
+        if (!this.enabled || this.annotation == null || object == null) {
+            generator.writeObject(object);
+        } else {
+            generator.writeObject(ImmutableMap.of(
+                    this.getCodeName(), this.getCode(object), this.getDescriptionName(), this.getDescription(object)
+            ));
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) {
+        if (property == null) {
+            return provider.getDefaultNullValueSerializer();
+        }
+        if (!this.isEnumerationProperty(property)) {
+            this.enabled = false;
+        } else {
+            this.annotation = this.getEnumerateAnnotation(property);
+            this.enabled = this.annotation != null && this.isVersioning(this.annotation);
+        }
+        return this;
     }
 }

+ 5 - 5
framework-base/src/main/java/com/chelvc/framework/base/model/Session.java

@@ -25,6 +25,11 @@ public class Session implements Serializable {
      */
     private Long id;
 
+    /**
+     * 会话类型
+     */
+    private String type;
+
     /**
      * 使用信息
      */
@@ -45,11 +50,6 @@ public class Session implements Serializable {
      */
     private String device;
 
-    /**
-     * 业务类型
-     */
-    private String business;
-
     /**
      * 渠道来源
      */

+ 85 - 0
framework-base/src/main/java/com/chelvc/framework/base/util/ResourceUtils.java

@@ -1,9 +1,12 @@
 package com.chelvc.framework.base.util;
 
+import java.util.Collection;
+import java.util.Map;
 import java.util.Objects;
 
 import com.chelvc.framework.base.exception.ResourceUnavailableException;
 import com.chelvc.framework.base.exception.ResourceUndefinedException;
+import com.chelvc.framework.common.util.ObjectUtils;
 
 /**
  * 资源工具类
@@ -53,6 +56,88 @@ public final class ResourceUtils {
         return resource;
     }
 
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象数组
+     * @param message   异常信息
+     * @param <T>       对象类型
+     * @return 非空资源
+     */
+    public static <T> T[] required(T[] resources, String message) {
+        return required(resources, message, null);
+    }
+
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象数组
+     * @param message   异常信息
+     * @param business  业务编码
+     * @param <T>       对象类型
+     * @return 非空资源
+     */
+    public static <T> T[] required(T[] resources, String message, String business) {
+        required(ObjectUtils.nonEmpty(resources), message, business);
+        return resources;
+    }
+
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象字典
+     * @param message   异常信息
+     * @param <K>       字典键类型
+     * @param <V>       字典值类型
+     * @param <M>       字典类型
+     * @return 非空资源
+     */
+    public static <K, V, M extends Map<K, V>> M required(M resources, String message) {
+        return required(resources, message, null);
+    }
+
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象字典
+     * @param message   异常信息
+     * @param business  业务编码
+     * @param <K>       字典键类型
+     * @param <V>       字典值类型
+     * @param <M>       字典类型
+     * @return 非空资源
+     */
+    public static <K, V, M extends Map<K, V>> M required(M resources, String message, String business) {
+        required(ObjectUtils.nonEmpty(resources), message, business);
+        return resources;
+    }
+
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象集合
+     * @param message   异常信息
+     * @param <T>       对象类型
+     * @return 非空资源
+     */
+    public static <T, C extends Collection<T>> C required(C resources, String message) {
+        return required(resources, message, null);
+    }
+
+    /**
+     * 资源非空校验
+     *
+     * @param resources 资源对象集合
+     * @param message   异常信息
+     * @param business  业务编码
+     * @param <T>       对象类型
+     * @return 非空资源
+     */
+    public static <T, C extends Collection<T>> C required(C resources, String message, String business) {
+        required(ObjectUtils.nonEmpty(resources), message, business);
+        return resources;
+    }
+
     /**
      * 资源非空校验
      *

+ 35 - 0
framework-cloud-client-feign/pom.xml

@@ -0,0 +1,35 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.chelvc.framework</groupId>
+        <artifactId>framework-dependencies</artifactId>
+        <version>1.0.0-RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>framework-cloud-client-feign</artifactId>
+    <version>1.0.0-RELEASE</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <framework-common.version>1.0.0-RELEASE</framework-common.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-common</artifactId>
+            <version>${framework-common.version}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-openfeign-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 51 - 0
framework-cloud-kubernetes/pom.xml

@@ -0,0 +1,51 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.chelvc.framework</groupId>
+        <artifactId>framework-boot</artifactId>
+        <version>1.0.0-RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>framework-cloud-kubernetes</artifactId>
+    <version>1.0.0-RELEASE</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <framework-feign.version>1.0.0-RELEASE</framework-feign.version>
+        <framework-redis.version>1.0.0-RELEASE</framework-redis.version>
+        <framework-rocketmq.version>1.0.0-RELEASE</framework-rocketmq.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-feign</artifactId>
+            <version>${framework-feign.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-kubernetes</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 29 - 0
framework-cloud-nacos-dubbo/pom.xml

@@ -0,0 +1,29 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.chelvc.framework</groupId>
+        <artifactId>framework-cloud-nacos</artifactId>
+        <version>1.0.0-RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>framework-cloud-nacos-dubbo</artifactId>
+    <version>1.0.0-RELEASE</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <framework-dubbo.version>1.0.0-RELEASE</framework-dubbo.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-dubbo</artifactId>
+            <version>${framework-dubbo.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 29 - 0
framework-cloud-nacos-feign/pom.xml

@@ -0,0 +1,29 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.chelvc.framework</groupId>
+        <artifactId>framework-cloud-nacos</artifactId>
+        <version>1.0.0-RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>framework-cloud-nacos-feign</artifactId>
+    <version>1.0.0-RELEASE</version>
+    <packaging>pom</packaging>
+
+    <properties>
+        <framework-feign.version>1.0.0-RELEASE</framework-feign.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-feign</artifactId>
+            <version>${framework-feign.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 1 - 1
framework-cloud/pom.xml → framework-cloud-nacos/pom.xml

@@ -11,7 +11,7 @@
         <relativePath/>
     </parent>
 
-    <artifactId>framework-cloud</artifactId>
+    <artifactId>framework-cloud-nacos</artifactId>
     <version>1.0.0-RELEASE</version>
     <packaging>pom</packaging>
 

+ 9 - 9
framework-common/src/main/java/com/chelvc/framework/common/model/Enumeration.java

@@ -28,21 +28,21 @@ public interface Enumeration extends Serializable {
     }
 
     /**
-     * 获取枚举编号
+     * 获取排序数字
      *
-     * @return 枚举编号
+     * @return 排序数字
      */
-    default String getCode() {
-        return this.name();
+    default int getOrder() {
+        return this.ordinal();
     }
 
     /**
-     * 获取排序数字
+     * 获取枚举编号
      *
-     * @return 排序数字
+     * @return 枚举编号
      */
-    default int getOrder() {
-        return this.ordinal();
+    default String getCode() {
+        return this.name();
     }
 
     /**
@@ -51,6 +51,6 @@ public interface Enumeration extends Serializable {
      * @return 枚举描述
      */
     default String getDescription() {
-        return this.getCode();
+        return null;
     }
 }

+ 1 - 1
framework-common/src/main/java/com/chelvc/framework/common/model/Media.java

@@ -40,7 +40,7 @@ public enum Media implements Enumeration {
      * 媒体类型枚举项键/值映射表
      */
     private static final Map<String, Media> MEDIAS =
-            Stream.of(Media.values()).collect(Collectors.toMap(Media::name, business -> business));
+            Stream.of(Media.values()).collect(Collectors.toMap(Media::name, media -> media));
 
     Media(String description) {
         this.description = description;

+ 5 - 0
framework-common/src/main/java/com/chelvc/framework/common/model/Paging.java

@@ -156,4 +156,9 @@ public final class Paging implements Serializable {
     public <T> List<T> list(List<T> objects) {
         return list(objects, this.number, this.size);
     }
+
+    @Override
+    public String toString() {
+        return String.format("(%d, %d)", this.number, this.size);
+    }
 }

+ 9 - 0
framework-common/src/main/java/com/chelvc/framework/common/model/Period.java

@@ -12,6 +12,7 @@ import java.util.regex.Pattern;
 
 import com.chelvc.framework.common.util.AssertUtils;
 import com.chelvc.framework.common.util.DateUtils;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.AllArgsConstructor;
@@ -491,6 +492,14 @@ public final class Period implements Serializable {
         return of(begin.apply(this.begin), end.apply(this.end));
     }
 
+    @Override
+    public String toString() {
+        return String.format("(%s, %s)",
+                ObjectUtils.ifNull(DateUtils.format(this.begin), StringUtils.EMPTY),
+                ObjectUtils.ifNull(DateUtils.format(this.end), StringUtils.EMPTY)
+        );
+    }
+
     /**
      * 时间周期函数
      */

+ 5 - 0
framework-common/src/main/java/com/chelvc/framework/common/model/Region.java

@@ -352,4 +352,9 @@ public final class Region implements Serializable {
         int code = this.code / 10000;
         return code == 11 || code == 12 || code == 31 || code == 50;
     }
+
+    @Override
+    public String toString() {
+        return String.valueOf(this.code);
+    }
 }

+ 6 - 4
framework-common/src/main/java/com/chelvc/framework/common/util/IdentityUtils.java

@@ -103,6 +103,7 @@ public final class IdentityUtils {
         } catch (Exception e) {
             log.warn("Datacenter id initialize failed: {}", e.getMessage());
         }
+        log.info("Initialize snowflake datacenter id: {}", id);
         return id;
     }
 
@@ -114,19 +115,20 @@ public final class IdentityUtils {
      * @return 机器标识ID
      */
     private static long initializeWorkerId(long datacenterId, long maxWorkerId) {
-        StringBuilder pid = new StringBuilder();
-        pid.append(datacenterId);
+        String pid = String.valueOf(datacenterId);
         String name = ManagementFactory.getRuntimeMXBean().getName();
         if (StringUtils.nonBlank(name)) {
             /*
              * GET jvmPid
              */
-            pid.append(name.split("@")[0]);
+            pid = pid.concat(name.split("@")[0]);
         }
         /*
          * MAC + PID 的 hashcode 获取16个低位
          */
-        return (pid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
+        long id = (pid.hashCode() & 0xffff) % (maxWorkerId + 1);
+        log.info("Initialize snowflake worker id: {}", id);
+        return id;
     }
 
     /**

+ 28 - 0
framework-common/src/main/java/com/chelvc/framework/common/util/JacksonUtils.java

@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Collection;
@@ -198,6 +199,20 @@ public final class JacksonUtils {
             }
         });
 
+        // 设置时间周期序列化处理器
+        module.addSerializer(new JsonSerializer<Duration>() {
+            @Override
+            public Class<Duration> handledType() {
+                return Duration.class;
+            }
+
+            @Override
+            public void serialize(Duration duration, JsonGenerator generator, SerializerProvider provider)
+                    throws IOException {
+                generator.writeString(duration.toString());
+            }
+        });
+
         // 设置地区反序列化处理器
         module.addDeserializer(Region.class, new JsonDeserializer<Region>() {
             @Override
@@ -210,6 +225,19 @@ public final class JacksonUtils {
                 return Region.parse(parser.getValueAsString());
             }
         });
+
+        // 设置时间周期反序列化处理器
+        module.addDeserializer(Duration.class, new JsonDeserializer<Duration>() {
+            @Override
+            public Class<?> handledType() {
+                return Duration.class;
+            }
+
+            @Override
+            public Duration deserialize(JsonParser parser, DeserializationContext context) throws IOException {
+                return Duration.parse(parser.getValueAsString());
+            }
+        });
         return module;
     }
 

+ 10 - 12
framework-database/src/main/java/com/chelvc/framework/database/context/DatabaseContextHolder.java

@@ -80,9 +80,9 @@ public class DatabaseContextHolder implements ApplicationContextAware {
     private static SqlSessionTemplate SESSION_TEMPLATE = null;
 
     /**
-     * 数据库配置属性
+     * 配置信息
      */
-    private static DatabaseProperties PROPERTIES = null;
+    private static DatabaseProperties CONFIGURATION = null;
 
     /**
      * 数据加密密码处理器
@@ -192,19 +192,17 @@ public class DatabaseContextHolder implements ApplicationContextAware {
     }
 
     /**
-     * 获取数据库配置属性
+     * 获取配置信息
      *
-     * @return 数据库配置属性
+     * @return 配置信息
      */
-    public static DatabaseProperties getProperties() {
-        if (PROPERTIES == null) {
+    public static DatabaseProperties getConfiguration() {
+        if (CONFIGURATION == null) {
             synchronized (DatabaseContextHolder.class) {
-                if (PROPERTIES == null) {
-                    PROPERTIES = ApplicationContextHolder.getBean(DatabaseProperties.class);
-                }
+                CONFIGURATION = ApplicationContextHolder.getBean(DatabaseProperties.class);
             }
         }
-        return PROPERTIES;
+        return CONFIGURATION;
     }
 
     /**
@@ -409,8 +407,8 @@ public class DatabaseContextHolder implements ApplicationContextAware {
      * @return 密码处理器
      */
     public static Cipher getCipher(int mode) {
-        DatabaseProperties properties = getProperties();
-        return AESUtils.getCipher(mode, properties.getSecret(), properties.getIv());
+        DatabaseProperties configuration = getConfiguration();
+        return AESUtils.getCipher(mode, configuration.getSecret(), configuration.getIv());
     }
 
     /**

+ 1 - 1
framework-dependencies/pom.xml

@@ -47,7 +47,7 @@
         <mybatis-plus-boot-starter.version>3.4.2</mybatis-plus-boot-starter.version>
         <dynamic-datasource-spring-boot-starter.version>3.3.2</dynamic-datasource-spring-boot-starter.version>
 
-        <rocketmq-spring-boot-starter.version>2.1.1</rocketmq-spring-boot-starter.version>
+        <rocketmq-spring-boot-starter.version>2.2.3</rocketmq-spring-boot-starter.version>
 
         <dubbo.version>3.0.12</dubbo.version>
         <dubbo-spring-boot-starter.version>3.0.12</dubbo-spring-boot-starter.version>

+ 0 - 4
framework-feign/pom.xml

@@ -24,10 +24,6 @@
             <artifactId>framework-base</artifactId>
             <version>${framework-base.version}</version>
         </dependency>
-        <dependency>
-            <groupId>io.github.openfeign</groupId>
-            <artifactId>feign-httpclient</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>

+ 10 - 1
framework-feign/src/main/java/com/chelvc/framework/feign/config/FeignConfigurer.java

@@ -1,15 +1,19 @@
 package com.chelvc.framework.feign.config;
 
 import com.chelvc.framework.feign.interceptor.FeignExceptionInterceptor;
+import feign.codec.Decoder;
 import feign.codec.Encoder;
 import feign.codec.ErrorDecoder;
 import feign.form.FormEncoder;
+import feign.optionals.OptionalDecoder;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.ObjectFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
 import org.springframework.cloud.openfeign.FeignAutoConfiguration;
+import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
+import org.springframework.cloud.openfeign.support.SpringDecoder;
 import org.springframework.cloud.openfeign.support.SpringEncoder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -33,10 +37,15 @@ public class FeignConfigurer {
     @Bean
     @Primary
     @Scope(SCOPE_PROTOTYPE)
-    public Encoder formEncoder() {
+    public Encoder encoder() {
         return new FormEncoder(new SpringEncoder(this.messageConverters));
     }
 
+    @Bean
+    public Decoder decoder() {
+        return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
+    }
+
     @Bean
     public ErrorDecoder errorDecoder() {
         return new FeignExceptionInterceptor();

+ 27 - 12
framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java

@@ -9,11 +9,11 @@ import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.base.model.Session;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.feign.util.FeignUtils;
 import com.google.common.collect.Maps;
 import feign.RequestInterceptor;
 import feign.RequestTemplate;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
 import org.springframework.stereotype.Component;
 
 /**
@@ -34,18 +34,42 @@ public class FeignHeaderInterceptor implements RequestInterceptor {
         if (names != null) {
             while (names.hasMoreElements()) {
                 String key = names.nextElement();
+                // 排除内容长度请求头
+                if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(key)) {
+                    continue;
+                }
+
+                // 排除签名信息请求头
+                if (SessionContextHolder.HEADER_SIGNATURE.equalsIgnoreCase(key)) {
+                    continue;
+                }
+
+                // 排除认证信息请求头
+                if (SessionContextHolder.HEADER_AUTHORIZATION.equalsIgnoreCase(key)) {
+                    continue;
+                }
                 headers.put(key, request.getHeader(key));
             }
         }
 
-        // 重置会话认证请求头
         Session session = SessionContextHolder.getSession(false);
         if (session != null) {
+            // 设置真实IP请求头
+            String host = session.getHost();
+            if (StringUtils.nonEmpty(host)) {
+                template.header("X-Real-IP", host);
+            }
+
+            // 设置认证主体信息请求头
             headers.put(SessionContextHolder.HEADER_ID, session.getId());
+            headers.put(SessionContextHolder.HEADER_TYPE, session.getType());
+            headers.put(SessionContextHolder.HEADER_USING, session.getUsing());
             headers.put(SessionContextHolder.HEADER_MOBILE, session.getMobile());
-            headers.put(SessionContextHolder.HEADER_BUSINESS, session.getBusiness());
         }
 
+        // 设置获取原始相应对象请求头
+        headers.put(SessionContextHolder.HEADER_RESPONSE_WRAPPING, StringUtils.FALSE);
+
         // 初始化Feign请求头
         boolean debug = log.isDebugEnabled();
         headers.forEach((key, value) -> {
@@ -56,14 +80,5 @@ public class FeignHeaderInterceptor implements RequestInterceptor {
                 template.header(key, String.valueOf(value));
             }
         });
-
-        // 设置请求真实IP
-        String host = ObjectUtils.ifNull(session, Session::getHost);
-        if (StringUtils.nonEmpty(host)) {
-            template.header("X-Real-IP", host);
-        }
-
-        // 设置获取原始相应对象请求头
-        FeignUtils.initializeResponseWrappingHeader(template, false);
     }
 }

+ 2 - 5
framework-jpush/src/main/java/com/chelvc/framework/jpush/DefaultJPushHandler.java

@@ -36,6 +36,7 @@ import cn.jpush.api.push.model.Message;
 import cn.jpush.api.push.model.PushPayload;
 import cn.jpush.api.push.model.notification.Notification;
 import cn.jpush.api.report.ReceivedsResult;
+import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.common.model.Period;
 import com.chelvc.framework.common.model.Platform;
@@ -50,7 +51,6 @@ import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
@@ -91,9 +91,6 @@ public class DefaultJPushHandler implements JPushHandler {
     private JPushClient pushClient;
     private JMessageClient messageClient;
 
-    @Value("${spring.profiles.active}")
-    private String env;
-
     @PostConstruct
     public void initialize() throws GeneralSecurityException {
         // 初始化登录认证配置
@@ -148,7 +145,7 @@ public class DefaultJPushHandler implements JPushHandler {
      * @return true/false
      */
     private boolean isProduction() {
-        return Objects.equals(this.env, "prod");
+        return Objects.equals(ApplicationContextHolder.getProfile(), "prod");
     }
 
     /**

+ 5 - 2
framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthConfigurer.java

@@ -106,11 +106,14 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
         validators.add(new JwtTimestampValidator());
         this.applicationContext.getBeansOfType(TokenActiveValidator.class).forEach((k, v) -> validators.add(v));
         validators.add(jwt -> {
+            // 设置Jwt上下文
+            OauthContextHolder.setJwt(jwt);
+
             // 初始化会话主体信息
             SessionContextHolder.initializeSessionPrincipal(
                     OauthContextHolder.getId(jwt),
-                    OauthContextHolder.getMobile(jwt),
-                    OauthContextHolder.getBusiness(jwt)
+                    OauthContextHolder.getType(jwt),
+                    OauthContextHolder.getMobile(jwt)
             );
             return OAuth2TokenValidatorResult.success();
         });

+ 207 - 34
framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java

@@ -1,25 +1,32 @@
 package com.chelvc.framework.oauth.context;
 
+import java.text.ParseException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletRequestListener;
+import javax.servlet.http.HttpServletRequest;
 
+import com.chelvc.framework.base.context.ApplicationContextHolder;
+import com.chelvc.framework.common.function.Adapter;
 import com.chelvc.framework.common.model.Terminal;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.oauth.config.OauthProperties;
 import com.google.common.collect.Sets;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
 import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.provider.ClientDetails;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
+import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
 import org.springframework.stereotype.Component;
 
 /**
@@ -30,36 +37,41 @@ import org.springframework.stereotype.Component;
  */
 @Slf4j
 @Component
-public class OauthContextHolder implements ServletRequestListener, ApplicationContextAware {
+public class OauthContextHolder implements ServletRequestListener {
     /**
-     * JWT身份标识
+     * Jwt身份标识
      */
     public static final String JWT_CLAIM_JTI = "jti";
 
     /**
-     * JWT手机号标识
+     * Jwt会话类型
      */
-    public static final String JWT_CLAIM_MOBILE = "mobile";
+    public static final String JWT_CLAIM_TYPE = "type";
 
     /**
-     * 业务类型标识
+     * Jwt手机号标识
      */
-    public static final String JWT_CLAIM_BUSINESS = "business";
+    public static final String JWT_CLAIM_MOBILE = "mobile";
 
     /**
-     * 用户账号标识
+     * Jwt用户账号标识
      */
     public static final String JWT_CLAIM_USER_NAME = "user_name";
 
     /**
-     * 用户权限标识
+     * Jwt用户权限标识
      */
     public static final String JWT_CLAIM_AUTHORITIES = "authorities";
 
     /**
-     * JWT解码器
+     * 配置信息
      */
-    private static JwtDecoder JWT_DECODER;
+    private static OauthProperties CONFIGURATION;
+
+    /**
+     * Jwt上下文
+     */
+    private static final ThreadLocal<Jwt> JWT_CONTEXT = new ThreadLocal<>();
 
     /**
      * 认证主体上下文
@@ -76,18 +88,41 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
      */
     private static final ThreadLocal<OAuth2RefreshToken> REFRESH_TOKEN_CONTEXT = new ThreadLocal<>();
 
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        JWT_DECODER = applicationContext.getBean(JwtDecoder.class);
+    /**
+     * Bearer令牌解析器
+     */
+    private static final BearerTokenResolver BEARER_TOKEN_RESOLVER = new DefaultBearerTokenResolver();
+
+    /**
+     * 获取配置信息
+     *
+     * @return 配置信息
+     */
+    public static OauthProperties getConfiguration() {
+        if (CONFIGURATION == null) {
+            synchronized (OauthContextHolder.class) {
+                CONFIGURATION = ApplicationContextHolder.getBean(OauthProperties.class);
+            }
+        }
+        return CONFIGURATION;
     }
 
     /**
-     * 获取JWT解码器
+     * 获取Jwt
      *
-     * @return JWT解码器
+     * @return Jwt实例
      */
-    public static JwtDecoder getJwtDecoder() {
-        return Objects.requireNonNull(JWT_DECODER, "Jwt decoder has not been initialized");
+    public static Jwt getJwt() {
+        return JWT_CONTEXT.get();
+    }
+
+    /**
+     * 设置Jwt
+     *
+     * @param jwt Jwt实例
+     */
+    public static void setJwt(Jwt jwt) {
+        JWT_CONTEXT.set(jwt);
     }
 
     /**
@@ -144,6 +179,83 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
         REFRESH_TOKEN_CONTEXT.set(refreshToken);
     }
 
+    /**
+     * 令牌转换
+     *
+     * @param token 令牌信息
+     * @return JWT对象实例
+     */
+    public static JWT parse(String token) {
+        if (StringUtils.isEmpty(token)) {
+            return null;
+        }
+        try {
+            return JWTParser.parse(token);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 令牌转换
+     *
+     * @param request Http请求对象
+     * @return JWT对象实例
+     */
+    public static JWT parse(HttpServletRequest request) {
+        return StringUtils.ifEmpty(getToken(request), OauthContextHolder::parse);
+    }
+
+    /**
+     * 获取Jwt属性集合
+     *
+     * @param jwt JWT对象实例
+     * @return Jwt属性集合
+     */
+    public static JWTClaimsSet getJwtClaims(JWT jwt) {
+        if (Objects.isNull(jwt)) {
+            return null;
+        }
+        try {
+            return jwt.getJWTClaimsSet();
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取Jwt属性值
+     *
+     * @param jwt      JWT对象实例
+     * @param function 属性获取方法
+     * @param <T>      属性值类型
+     * @return 属性值
+     */
+    public static <T> T getJwtClaim(JWT jwt, @NonNull Adapter<JWTClaimsSet, T> function) {
+        JWTClaimsSet claims = ObjectUtils.ifNull(jwt, OauthContextHolder::getJwtClaims);
+        if (claims == null) {
+            return null;
+        }
+        try {
+            return function.apply(claims);
+        } catch (Exception e) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 获取请求令牌
+     *
+     * @param request Http请求对象
+     * @return 令牌信息
+     */
+    public static String getToken(HttpServletRequest request) {
+        return StringUtils.ifEmpty(ObjectUtils.ifNull(request, BEARER_TOKEN_RESOLVER::resolve), (String) null);
+    }
+
     /**
      * 获取主体身份
      *
@@ -154,6 +266,16 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
         return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_USER_NAME), Long::parseLong);
     }
 
+    /**
+     * 获取主体身份
+     *
+     * @param jwt JWT对象
+     * @return 主体身份标识
+     */
+    public static Long getId(JWT jwt) {
+        return getJwtClaim(jwt, claims -> claims.getLongClaim(JWT_CLAIM_USER_NAME));
+    }
+
     /**
      * 获取JWT身份标识
      *
@@ -165,34 +287,73 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
     }
 
     /**
-     * 获取手机号
+     * 获取JWT身份标识
      *
      * @param jwt JWT对象
-     * @return 手机号
+     * @return JWT身份标识
      */
-    public static String getMobile(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_MOBILE), (String) null);
+    public static String getJti(JWT jwt) {
+        return StringUtils.ifEmpty(getJwtClaim(jwt, claims -> claims.getStringClaim(JWT_CLAIM_JTI)), (String) null);
     }
 
     /**
-     * 获取业务类型
+     * 获取会话类型
+     *
+     * @param jwt Jwt对象
+     * @return 会话类型
+     */
+    public static String getType(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_TYPE), (String) null);
+    }
+
+    /**
+     * 获取会话类型
      *
      * @param jwt JWT对象
-     * @return 业务类型
+     * @return 会话类型
+     */
+    public static String getType(JWT jwt) {
+        return StringUtils.ifEmpty(getJwtClaim(jwt, claims -> claims.getStringClaim(JWT_CLAIM_TYPE)), (String) null);
+    }
+
+    /**
+     * 获取会话类型
+     *
+     * @param token 访问令牌
+     * @return 会话类型
      */
-    public static String getBusiness(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_BUSINESS), (String) null);
+    public static String getType(OAuth2AccessToken token) {
+        return getType(parse(ObjectUtils.ifNull(token, OAuth2AccessToken::getValue)));
     }
 
     /**
-     * 获取业务类型
+     * 获取会话类型
      *
      * @param token 刷新令牌
-     * @return 业务类型
+     * @return 会话类型
+     */
+    public static String getType(OAuth2RefreshToken token) {
+        return getType(parse(ObjectUtils.ifNull(token, OAuth2RefreshToken::getValue)));
+    }
+
+    /**
+     * 获取手机号
+     *
+     * @param jwt JWT对象
+     * @return 手机号
      */
-    public static String getBusiness(OAuth2RefreshToken token) {
-        String value = ObjectUtils.ifNull(token, OAuth2RefreshToken::getValue);
-        return getBusiness(StringUtils.ifEmpty(value, getJwtDecoder()::decode));
+    public static String getMobile(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_MOBILE), (String) null);
+    }
+
+    /**
+     * 获取手机号
+     *
+     * @param jwt JWT对象
+     * @return 手机号
+     */
+    public static String getMobile(JWT jwt) {
+        return StringUtils.ifEmpty(getJwtClaim(jwt, claims -> claims.getStringClaim(JWT_CLAIM_MOBILE)), (String) null);
     }
 
     /**
@@ -209,6 +370,17 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
         return ObjectUtils.isEmpty(authorities) ? Collections.emptySet() : Sets.newHashSet(authorities);
     }
 
+    /**
+     * 获取用户授权信息
+     *
+     * @param jwt JWT对象
+     * @return 授权信息集合
+     */
+    public static Set<String> getAuthorities(JWT jwt) {
+        String[] authorities = getJwtClaim(jwt, claims -> claims.getStringArrayClaim(JWT_CLAIM_AUTHORITIES));
+        return ObjectUtils.isEmpty(authorities) ? Collections.emptySet() : Sets.newHashSet(authorities);
+    }
+
     /**
      * 构建访问令牌标识
      *
@@ -224,6 +396,7 @@ public class OauthContextHolder implements ServletRequestListener, ApplicationCo
      * 清空OAuth上下文
      */
     public static void clearOauthContext() {
+        JWT_CONTEXT.remove();
         PRINCIPAL_CONTEXT.remove();
         CLIENT_DETAILS_CONTEXT.remove();
         REFRESH_TOKEN_CONTEXT.remove();

+ 2 - 2
framework-oauth/src/main/java/com/chelvc/framework/oauth/token/RedisTokenStore.java

@@ -24,8 +24,8 @@ import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
 public class RedisTokenStore extends JwtTokenStore {
     private final RedisTemplate<String, Object> redisTemplate;
 
-    public RedisTokenStore(@NonNull RedisTemplate<String, Object> redisTemplate,
-                           @NonNull JwtAccessTokenConverter jwtAccessTokenConverter) {
+    public RedisTokenStore(@NonNull JwtAccessTokenConverter jwtAccessTokenConverter,
+                           @NonNull RedisTemplate<String, Object> redisTemplate) {
         super(jwtAccessTokenConverter);
         this.redisTemplate = redisTemplate;
     }

+ 0 - 36
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/annotation/DelayConsumer.java

@@ -1,36 +0,0 @@
-package com.chelvc.framework.rocketmq.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import com.chelvc.framework.common.util.StringUtils;
-
-/**
- * 自定义消息延时器注解
- *
- * @author Woody
- * @date 2023/4/5
- */
-@Inherited
-@Documented
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface DelayConsumer {
-    /**
-     * 获取消息主题
-     *
-     * @return 消息主题
-     */
-    String topic() default StringUtils.EMPTY;
-
-    /**
-     * 获取消息类型
-     *
-     * @return 消息类型
-     */
-    Class<?> type() default Object.class;
-}

+ 53 - 273
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java

@@ -1,6 +1,5 @@
 package com.chelvc.framework.rocketmq.context;
 
-import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.time.Duration;
@@ -10,8 +9,6 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -20,53 +17,29 @@ import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.JacksonContextHolder;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.base.model.Session;
-import com.chelvc.framework.common.model.Delayer;
 import com.chelvc.framework.common.util.AssertUtils;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.rocketmq.annotation.DelayConsumer;
+import com.chelvc.framework.common.util.DateUtils;
 import com.chelvc.framework.rocketmq.interceptor.SessionRocketMQListener;
-import com.chelvc.framework.rocketmq.model.Delay;
 import com.chelvc.framework.rocketmq.model.MessageContext;
 import com.chelvc.framework.rocketmq.producer.RocketMQTransactionChecker;
 import com.chelvc.framework.rocketmq.producer.RocketMQTransactionExecutor;
 import com.chelvc.framework.rocketmq.producer.RocketMQTransactionHandler;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtConstructor;
-import javassist.CtMethod;
-import javassist.LoaderClassPath;
-import javassist.bytecode.AnnotationsAttribute;
-import javassist.bytecode.ClassFile;
-import javassist.bytecode.ConstPool;
-import javassist.bytecode.SignatureAttribute;
-import javassist.bytecode.annotation.Annotation;
-import javassist.bytecode.annotation.StringMemberValue;
 import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.client.producer.SendCallback;
 import org.apache.rocketmq.client.producer.SendResult;
-import org.apache.rocketmq.client.producer.SendStatus;
 import org.apache.rocketmq.client.producer.TransactionSendResult;
-import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
 import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
-import org.apache.rocketmq.spring.autoconfigure.ListenerContainerConfiguration;
 import org.apache.rocketmq.spring.core.RocketMQListener;
 import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
 import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
 import org.apache.rocketmq.spring.core.RocketMQReplyListener;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.apache.rocketmq.spring.support.RocketMQMessageConverter;
-import org.springframework.aop.framework.AopProxyUtils;
 import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
-import org.springframework.core.env.Environment;
 import org.springframework.messaging.Message;
 import org.springframework.messaging.converter.MessageConverter;
 import org.springframework.messaging.support.GenericMessage;
@@ -78,49 +51,31 @@ import org.springframework.util.CollectionUtils;
  * RocketMQ上下文工具类
  *
  * @author Woody
- * @date 2021/9/6
+ * @date 2023/9/9
  */
 @Slf4j
 @Component
 @RocketMQTransactionListener
-@RequiredArgsConstructor(onConstructor = @__(@Autowired))
-public class RocketMQContextHolder implements RocketMQLocalTransactionListener, ApplicationContextAware,
-        SmartInitializingSingleton {
+public class RocketMQContextHolder implements RocketMQLocalTransactionListener, ApplicationContextAware {
     /**
      * RocketMQ主题消息头标识
      */
     private static final String ROCKETMQ_TYPE_HEADER = "type";
 
-    /**
-     * 对象池
-     */
-    private static final ClassPool CLASS_POOL = ClassPool.getDefault();
-
-    /**
-     * 自定义延时消息延时器计数器
-     */
-    private static final AtomicLong DELAYER_COUNTER = new AtomicLong(0);
-
-    /**
-     * 自定义延时消息主题名称集合
-     */
-    private static final Set<String> DELAYER_TOPIC_NAME = Sets.newConcurrentHashSet();
-
     /**
      * 异常上下文
      */
     private static final ThreadLocal<RuntimeException> EXCEPTION_CONTEXT = new ThreadLocal<>();
 
     /**
-     * 自定义延时消息延时器包名
+     * 消息类型/本地事务检测器映射表
      */
-    private static final String DELAYER_PACKAGE =
-            RocketMQContextHolder.class.getPackage().getName().replace("context", "delayer");
+    private static Map<String, RocketMQTransactionChecker<?>> MESSAGE_CHECKER_MAPPING = Collections.emptyMap();
 
     /**
-     * 环境名称
+     * 消息类型/本地事务执行器映射表
      */
-    private static String PROFILE;
+    private static Map<String, RocketMQTransactionExecutor<?>> MESSAGE_EXECUTOR_MAPPING = Collections.emptyMap();
 
     /**
      * RocketMQ模版实例
@@ -132,26 +87,9 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
      */
     private static MessageConverter MESSAGE_CONVERTER;
 
-    /**
-     * 消息类型/本地事务检测器映射表
-     */
-    private static Map<String, RocketMQTransactionChecker<?>> MESSAGE_CHECKER_MAPPING = Collections.emptyMap();
-
-    /**
-     * 消息类型/本地事务执行器映射表
-     */
-    private static Map<String, RocketMQTransactionExecutor<?>> MESSAGE_EXECUTOR_MAPPING = Collections.emptyMap();
-
-    static {
-        CLASS_POOL.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
-    }
-
     @Override
     @SuppressWarnings("rawtypes")
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        // 初始化环境名称
-        PROFILE = applicationContext.getEnvironment().getProperty("spring.profiles.active");
-
         // 初始化RocketMQ模版实例
         ROCKETMQ_TEMPLATE = applicationContext.getBean(RocketMQTemplate.class);
 
@@ -195,14 +133,6 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
         }
     }
 
-    @Override
-    public void afterSingletonsInstantiated() {
-        // 初始化延时消息延时器
-        ApplicationContext context = ApplicationContextHolder.getApplicationContext();
-        Map<String, Object> consumers = context.getBeansWithAnnotation(DelayConsumer.class);
-        consumers.values().forEach(this::registerMessageDelayerContainer);
-    }
-
     @Override
     @SuppressWarnings({"rawtypes", "unchecked"})
     public RocketMQLocalTransactionState checkLocalTransaction(@NonNull Message message) {
@@ -230,6 +160,24 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
         }
     }
 
+    /**
+     * 获取RocketMQ模版实例
+     *
+     * @return RocketMQ模版实例
+     */
+    public static RocketMQTemplate getRocketMQTemplate() {
+        return Objects.requireNonNull(ROCKETMQ_TEMPLATE, "Rocketmq template has not been initialized");
+    }
+
+    /**
+     * 获取消息转换器实例
+     *
+     * @return 消息转换器实例
+     */
+    public static MessageConverter getMessageConverter() {
+        return Objects.requireNonNull(MESSAGE_CONVERTER, "Message converter has not been initialized");
+    }
+
     /**
      * 查找消费者消息类型
      *
@@ -264,104 +212,6 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
         return Object.class;
     }
 
-    /**
-     * 注册消息延时器消费者容器
-     *
-     * @param consumer 消费者实例
-     */
-    private void registerMessageDelayerContainer(Object consumer) {
-        // 获取延时消息主题
-        Class<?> clazz = AopProxyUtils.ultimateTargetClass(consumer);
-        DelayConsumer annotation = clazz.getAnnotation(DelayConsumer.class);
-        String topic = annotation.topic();
-        if (StringUtils.isEmpty(topic)) {
-            RocketMQMessageListener listener = clazz.getAnnotation(RocketMQMessageListener.class);
-            topic = ObjectUtils.ifNull(listener, RocketMQMessageListener::topic);
-        }
-        AssertUtils.check(StringUtils.nonEmpty(topic), "Message delayer topic unassigned: " + clazz.getName());
-        Environment environment = ApplicationContextHolder.getApplicationContext().getEnvironment();
-        topic = environment.resolvePlaceholders(topic);
-        if (!DELAYER_TOPIC_NAME.add(topic)) {
-            return;
-        }
-
-        // 获取延时消息类型
-        Class<?> type = annotation.type();
-        if (type == Object.class) {
-            type = (Class<?>) lookupConsumerMessageType(clazz);
-        }
-        AssertUtils.check(type != Object.class, "Message delayer type unassigned: " + clazz.getName());
-
-        // 注册消息延时器消费者容器
-        try {
-            Class<?> delayer = this.generateMessageDelayerClass(topic, type);
-            ListenerContainerConfiguration configuration =
-                    ApplicationContextHolder.getApplicationContext().getBean(ListenerContainerConfiguration.class);
-            Method method = ListenerContainerConfiguration.class.getDeclaredMethod(
-                    "registerContainer", String.class, Object.class
-            );
-            method.setAccessible(true);
-            method.invoke(configuration, delayer.getName(), delayer.newInstance());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * 动态生成消息延时器类对象
-     *
-     * @param topic 延时器消息主题
-     * @param type  消息类型
-     * @return 消息延时器类对象
-     * @throws Exception 处理异常
-     */
-    private Class<?> generateMessageDelayerClass(String topic, Class<?> type) throws Exception {
-        // 构建Class及实现接口
-        CtClass delayer = CLASS_POOL.makeClass(
-                String.format("%s.CustomMessageDelayer_%d", DELAYER_PACKAGE, DELAYER_COUNTER.incrementAndGet())
-        );
-        delayer.setInterfaces(new CtClass[]{CLASS_POOL.get(RocketMQListener.class.getName())});
-        delayer.setGenericSignature(new SignatureAttribute.ClassSignature(null, null,
-                new SignatureAttribute.ClassType[]{new SignatureAttribute.ClassType(
-                        RocketMQListener.class.getName(), new SignatureAttribute.TypeArgument[]{
-                        new SignatureAttribute.TypeArgument(
-                                new SignatureAttribute.ClassType(Delayer.class.getName())
-                        )})}).encode()
-        );
-
-        // 添加无参构造方法
-        CtConstructor constructor = new CtConstructor(new CtClass[]{}, delayer);
-        constructor.setBody("{}");
-        delayer.addConstructor(constructor);
-
-        // 添加自定义延时消息消费方法实现
-        delayer.addMethod(CtMethod.make(String.format(
-                "public void onMessage(%s message){%s.processing(\"%s\", %s.class, message);}",
-                Delayer.class.getName(),
-                RocketMQContextHolder.class.getName(),
-                topic,
-                type.getName()
-        ), delayer));
-
-        // 添加泛型参数接口方法桥接
-        delayer.addMethod(CtMethod.make(String.format(
-                "public void onMessage(Object message){this.onMessage((%s) message);}",
-                Delayer.class.getName()
-        ), delayer));
-
-        // 设置@RocketMQMessageListener注解
-        String unique = getDelayerTopic(topic);
-        ClassFile file = delayer.getClassFile();
-        ConstPool constPool = file.getConstPool();
-        AnnotationsAttribute attribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
-        Annotation annotation = new Annotation(RocketMQMessageListener.class.getName(), constPool);
-        annotation.addMemberValue("topic", new StringMemberValue(unique, constPool));
-        annotation.addMemberValue("consumerGroup", new StringMemberValue(unique, constPool));
-        attribute.addAnnotation(annotation);
-        file.addAttribute(attribute);
-        return delayer.toClass();
-    }
-
     /**
      * 根据当前环境名称获取实际消息主题
      *
@@ -369,35 +219,19 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
      * @return 消息主题
      */
     public static String getProfileTopic(@NonNull String topic) {
-        return Objects.requireNonNull(PROFILE, "Profile has not been initialized") + "-" + topic;
-    }
-
-    /**
-     * 获取自定义延时消息实际消息主题
-     *
-     * @param topic 消息主题
-     * @return 消息主题
-     */
-    public static String getDelayerTopic(@NonNull String topic) {
-        return topic + "-delay";
-    }
-
-    /**
-     * 获取RocketMQ模版实例
-     *
-     * @return RocketMQ模版实例
-     */
-    public static RocketMQTemplate getRocketMQTemplate() {
-        return Objects.requireNonNull(ROCKETMQ_TEMPLATE, "Rocketmq template has not been initialized");
+        return ApplicationContextHolder.getProfile() + "-" + topic;
     }
 
     /**
-     * 获取消息转换器实例
+     * 获取消息类型
      *
-     * @return 消息转换器实例
+     * @param message 消息对象实例
+     * @return 消息类型
      */
-    public static MessageConverter getMessageConverter() {
-        return Objects.requireNonNull(MESSAGE_CONVERTER, "Message converter has not been initialized");
+    public static String getMessageType(@NonNull Message<?> message) {
+        String type = message.getHeaders().get(ROCKETMQ_TYPE_HEADER, String.class);
+        AssertUtils.nonnull(type, "Rocketmq message type not found: " + message);
+        return type;
     }
 
     /**
@@ -433,18 +267,6 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
         return (T) converter.fromMessage(new GenericMessage<>(payload), type);
     }
 
-    /**
-     * 获取消息类型
-     *
-     * @param message 消息对象实例
-     * @return 消息类型
-     */
-    public static String getMessageType(@NonNull Message<?> message) {
-        String type = message.getHeaders().get(ROCKETMQ_TYPE_HEADER, String.class);
-        AssertUtils.nonnull(type, "Rocketmq message type not found: " + message);
-        return type;
-    }
-
     /**
      * 发送消息
      *
@@ -467,93 +289,51 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
     }
 
     /**
-     * 发送延时消息
+     * 发送自定义延时消息
      *
      * @param topic   消息主题
      * @param payload 消息内容
-     * @param delay   延时类型
+     * @param delay   延时时间
      */
-    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Delay delay) {
+    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Date delay) {
         send(topic, payload, delay, true);
     }
 
     /**
-     * 发送延时消息
+     * 发送自定义延时消息
      *
      * @param topic   消息主题
      * @param payload 消息内容
-     * @param delay   延时类型
+     * @param delay   延时时间
      * @param isolate 是否需要环境隔离
      */
-    private static void send(@NonNull String topic, @NonNull Object payload, @NonNull Delay delay, boolean isolate) {
-        RocketMQTemplate template = getRocketMQTemplate();
-        int timeout = template.getProducer().getSendMsgTimeout();
-        SendResult result = template.syncSend(
-                isolate ? getProfileTopic(topic) : topic, payload2message(payload), timeout, delay.level()
+    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Date delay, boolean isolate) {
+        getRocketMQTemplate().syncSendDeliverTimeMills(
+                isolate ? getProfileTopic(topic) : topic, payload2message(payload), delay.getTime()
         );
-        SendStatus status = ObjectUtils.ifNull(result, SendResult::getSendStatus);
-        if (status != SendStatus.SEND_OK) {
-            throw new IllegalStateException("Send delay message failed: " + status);
-        }
     }
 
     /**
      * 发送自定义延时消息
      *
-     * @param topic    消息主题
-     * @param payload  消息内容
-     * @param delaying 延时时长
-     */
-    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Duration delaying) {
-        Delay delay = Delay.close(delaying);
-        if (delay == Delay.S1) {
-            send(topic, payload);
-        } else if (delay.time() == delaying.getSeconds()) {
-            send(topic, payload, delay);
-        } else {
-            String json = JacksonContextHolder.serialize(payload);
-            long expiration = System.currentTimeMillis() + Math.max(delaying.toMillis(), 0);
-            send(getDelayerTopic(getProfileTopic(topic)), new Delayer<>(json, expiration), delay, false);
-        }
+     * @param topic   消息主题
+     * @param payload 消息内容
+     * @param delay   延时时长
+     */
+    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Duration delay) {
+        send(topic, payload, delay, true);
     }
 
     /**
      * 发送自定义延时消息
      *
-     * @param topic    消息主题
-     * @param payload  消息内容
-     * @param delaying 延时时间
-     */
-    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Date delaying) {
-        long duration = (delaying.getTime() - System.currentTimeMillis()) / 1000;
-        Delay delay = Delay.close(duration);
-        if (delay == Delay.S1) {
-            send(topic, payload);
-        } else if (delay.time() == duration) {
-            send(topic, payload, delay);
-        } else {
-            String json = JacksonContextHolder.serialize(payload);
-            send(getDelayerTopic(getProfileTopic(topic)), new Delayer<>(json, delaying.getTime()), delay, false);
-        }
-    }
-
-    /**
-     * 延时消息处理,供消息延时器调用
-     *
      * @param topic   消息主题
-     * @param type    消息类型
-     * @param message 延时消息实例
-     */
-    public static void processing(@NonNull String topic, @NonNull Class<?> type, @NonNull Delayer<String> message) {
-        long duration = (message.getExpiration() - System.currentTimeMillis()) / 1000;
-        Delay delay = Delay.close(duration);
-        if (delay == Delay.S1) {
-            send(topic, JacksonContextHolder.deserialize(message.getPayload(), type), false);
-        } else if (delay.time() == duration) {
-            send(topic, JacksonContextHolder.deserialize(message.getPayload(), type), delay, false);
-        } else {
-            send(getDelayerTopic(topic), message, delay, false);
-        }
+     * @param payload 消息内容
+     * @param delay   延时时长
+     * @param isolate 是否需要环境隔离
+     */
+    public static void send(@NonNull String topic, @NonNull Object payload, @NonNull Duration delay, boolean isolate) {
+        send(topic, payload, DateUtils.add(delay), isolate);
     }
 
     /**

+ 0 - 204
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/model/Delay.java

@@ -1,204 +0,0 @@
-package com.chelvc.framework.rocketmq.model;
-
-import java.time.Duration;
-import java.util.Date;
-
-import lombok.NonNull;
-
-/**
- * 消息延时类型枚举
- *
- * @author Woody
- * @date 2023/4/5
- */
-public enum Delay {
-    /**
-     * 1秒
-     */
-    S1(1, 1),
-
-    /**
-     * 5秒
-     */
-    S5(2, 5),
-
-    /**
-     * 10秒
-     */
-    S10(3, 10),
-
-    /**
-     * 30秒
-     */
-    S30(4, 30),
-
-    /**
-     * 1分钟
-     */
-    M1(5, 60),
-
-    /**
-     * 2分钟
-     */
-    M2(6, 120),
-
-    /**
-     * 3分钟
-     */
-    M3(7, 180),
-
-    /**
-     * 4分钟
-     */
-    M4(8, 240),
-
-    /**
-     * 5分钟
-     */
-    M5(9, 300),
-
-    /**
-     * 6分钟
-     */
-    M6(10, 360),
-
-    /**
-     * 7分钟
-     */
-    M7(11, 420),
-
-    /**
-     * 8分钟
-     */
-    M8(12, 480),
-
-    /**
-     * 9分钟
-     */
-    M9(13, 540),
-
-    /**
-     * 10分钟
-     */
-    M10(14, 600),
-
-    /**
-     * 20分钟
-     */
-    M20(15, 1200),
-
-    /**
-     * 30分钟
-     */
-    M30(16, 1800),
-
-    /**
-     * 1小时
-     */
-    H1(17, 3600),
-
-    /**
-     * 2小时
-     */
-    H2(18, 7200);
-
-    /**
-     * 延时类型值数组
-     */
-    private static final Delay[] VALUES = Delay.values();
-
-    /**
-     * 延时等级
-     */
-    private final int level;
-
-    /**
-     * 延时时间(秒)
-     */
-    private final long time;
-
-    Delay(int level, long time) {
-        this.level = level;
-        this.time = time;
-    }
-
-    /**
-     * 根据延时等级获取延时类型
-     *
-     * @param level 延时等级
-     * @return 延时类型
-     */
-    public static Delay of(int level) {
-        return VALUES[level - 1];
-    }
-
-    /**
-     * 获取时间接近的延时类型
-     *
-     * @param time 延时时间(秒)
-     * @return 延时类型
-     */
-    public static Delay close(long time) {
-        if (time <= 1) {
-            return Delay.S1;
-        }
-        Delay delay = null;
-        int median = VALUES.length / 2;
-        int i = time >= VALUES[median].time ? median : 0;
-        for (; i < VALUES.length; i++) {
-            Delay value = VALUES[i];
-            if (time < value.time) {
-                break;
-            }
-            delay = value;
-        }
-        return delay == null ? Delay.S1 : delay;
-    }
-
-    /**
-     * 获取目标日期接近的延时类型
-     *
-     * @param date 目标日期
-     * @return 延时类型
-     */
-    public static Delay close(@NonNull Date date) {
-        return close((date.getTime() - System.currentTimeMillis()) / 1000);
-    }
-
-    /**
-     * 获取时间周期接近的延时类型
-     *
-     * @param duration 时间周期
-     * @return 延时类型
-     */
-    public static Delay close(@NonNull Duration duration) {
-        return close(duration.getSeconds());
-    }
-
-    /**
-     * 获取延时等级
-     *
-     * @return 延时等级
-     */
-    public int level() {
-        return this.level;
-    }
-
-    /**
-     * 获取延时时间(秒)
-     *
-     * @return 延时时间
-     */
-    public long time() {
-        return this.time;
-    }
-
-    /**
-     * 获取延时周期
-     *
-     * @return 时间周期
-     */
-    public Duration duration() {
-        return Duration.ofSeconds(this.time);
-    }
-}

+ 9 - 1
framework-security/src/main/java/com/chelvc/framework/security/annotation/Desensitize.java

@@ -7,6 +7,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+import com.chelvc.framework.base.annotation.Versioning;
 import com.chelvc.framework.security.interceptor.JacksonDesensitizeSerializer;
 import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -19,9 +20,9 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  */
 @Inherited
 @Documented
+@JacksonAnnotationsInside
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
-@JacksonAnnotationsInside
 @JsonSerialize(using = JacksonDesensitizeSerializer.class)
 public @interface Desensitize {
     /**
@@ -30,4 +31,11 @@ public @interface Desensitize {
      * @return 表达式
      */
     String expression();
+
+    /**
+     * 版本控制
+     *
+     * @return 版本控制数组
+     */
+    Versioning[] versioning() default {};
 }

+ 9 - 2
framework-security/src/main/java/com/chelvc/framework/security/annotation/Encrypt.java

@@ -7,6 +7,7 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+import com.chelvc.framework.base.annotation.Versioning;
 import com.chelvc.framework.security.interceptor.JacksonEncryptSerializer;
 import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -19,9 +20,15 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  */
 @Inherited
 @Documented
-@Target({ElementType.FIELD, ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
 @JacksonAnnotationsInside
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
 @JsonSerialize(using = JacksonEncryptSerializer.class)
 public @interface Encrypt {
+    /**
+     * 版本控制
+     *
+     * @return 版本控制数组
+     */
+    Versioning[] versioning() default {};
 }

+ 15 - 15
framework-security/src/main/java/com/chelvc/framework/security/config/MethodSecurityExpression.java

@@ -144,6 +144,21 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
         this.root.setDefaultRolePrefix(defaultRolePrefix);
     }
 
+    /**
+     * 判断是否是指定会话类型
+     *
+     * @param types 会话类型数组
+     * @return true/false
+     */
+    public boolean isType(String... types) {
+        if (ObjectUtils.isEmpty(types)) {
+            return false;
+        }
+        String current = SessionContextHolder.getType(false);
+        return StringUtils.nonEmpty(current) && Stream.of(types).filter(StringUtils::nonEmpty)
+                .anyMatch(type -> Objects.equals(type, current));
+    }
+
     /**
      * 判断是否是指定手机号
      *
@@ -174,21 +189,6 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
                 .anyMatch(identity -> Objects.equals(identity, current));
     }
 
-    /**
-     * 判断是否是指定业务类型
-     *
-     * @param businesses 业务类型数组
-     * @return true/false
-     */
-    public boolean isBusiness(String... businesses) {
-        if (ObjectUtils.isEmpty(businesses)) {
-            return false;
-        }
-        String current = SessionContextHolder.getBusiness(false);
-        return StringUtils.nonEmpty(current) && Stream.of(businesses).filter(StringUtils::nonEmpty)
-                .anyMatch(business -> Objects.equals(business, current));
-    }
-
     /**
      * 判断是否是指定平台
      *

+ 12 - 12
framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java

@@ -30,22 +30,22 @@ public class SecurityContextHolder {
     public static final String CODEC_CHARACTERISTIC_SUFFIX = ")";
 
     /**
-     * 安全配置属性
+     * 配置信息
      */
-    private static SecurityProperties PROPERTIES;
+    private static SecurityProperties CONFIGURATION;
 
     /**
-     * 获取安全配置属性
+     * 获取配置信息
      *
-     * @return 安全配置属性实例
+     * @return 配置信息
      */
-    public static SecurityProperties getProperties() {
-        if (PROPERTIES == null) {
+    public static SecurityProperties getConfiguration() {
+        if (CONFIGURATION == null) {
             synchronized (SecurityContextHolder.class) {
-                PROPERTIES = ApplicationContextHolder.getBean(SecurityProperties.class);
+                CONFIGURATION = ApplicationContextHolder.getBean(SecurityProperties.class);
             }
         }
-        return PROPERTIES;
+        return CONFIGURATION;
     }
 
     /**
@@ -58,8 +58,8 @@ public class SecurityContextHolder {
         if (StringUtils.isEmpty(plaintext)) {
             return plaintext;
         }
-        SecurityProperties properties = getProperties();
-        String secret = Objects.requireNonNull(properties.getSecret(), "secret invalid");
+        SecurityProperties configuration = getConfiguration();
+        String secret = Objects.requireNonNull(configuration.getSecret(), "secret invalid");
         String device = Objects.requireNonNull(SessionContextHolder.getDevice(), "device invalid");
         String iv = StringUtils.substring(device, 0, 16);
         String ciphertext = AESUtils.encode(plaintext, secret, iv);
@@ -103,8 +103,8 @@ public class SecurityContextHolder {
                 && ciphertext.endsWith(CODEC_CHARACTERISTIC_SUFFIX))) {
             return ciphertext;
         }
-        SecurityProperties properties = getProperties();
-        String secret = Objects.requireNonNull(properties.getSecret(), "secret invalid");
+        SecurityProperties configuration = getConfiguration();
+        String secret = Objects.requireNonNull(configuration.getSecret(), "secret invalid");
         String device = Objects.requireNonNull(SessionContextHolder.getDevice(), "device invalid");
         String iv = StringUtils.substring(device, 0, 16);
         ciphertext = ciphertext.substring(

+ 47 - 28
framework-security/src/main/java/com/chelvc/framework/security/interceptor/JacksonDesensitizeSerializer.java

@@ -4,8 +4,9 @@ import java.io.IOException;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import com.chelvc.framework.common.util.AssertUtils;
+import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.DesensitizeUtils;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.security.annotation.Desensitize;
 import com.fasterxml.jackson.core.JsonGenerator;
@@ -20,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
  * 敏感数据脱敏序列化处理器
  *
  * @author Woody
- * @date 2023/4/5
+ * @date 2023/9/9
  */
 @Slf4j
 public class JacksonDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
@@ -29,48 +30,66 @@ public class JacksonDesensitizeSerializer extends StdSerializer<String> implemen
      */
     private static final Map<String, DesensitizeUtils.Mode> DESENSITIZE_MODE_MAPPING = new ConcurrentHashMap<>();
 
-    /**
-     * 数据脱敏模式
-     */
-    private final DesensitizeUtils.Mode mode;
+    private boolean enabled;
+    private Desensitize annotation;
 
     public JacksonDesensitizeSerializer() {
-        this(null);
+        super(String.class);
     }
 
-    public JacksonDesensitizeSerializer(DesensitizeUtils.Mode mode) {
-        super(String.class);
-        this.mode = mode;
+    /**
+     * 判断是否受版本控制
+     *
+     * @param annotation 脱敏注解实例
+     * @return true/false
+     */
+    private boolean isVersioning(Desensitize annotation) {
+        return ObjectUtils.isEmpty(annotation.versioning()) || SessionContextHolder.isVersion(annotation.versioning());
     }
 
     /**
-     * 根据脱敏表达式获取敏感数据脱敏模型
+     * 判断是否是可脱敏属性
      *
-     * @param expression 数据脱敏表达式
-     * @return 数据脱敏模型
+     * @param property 属性对象
+     * @return true/false
      */
-    private DesensitizeUtils.Mode expression2mode(String expression) {
-        return DESENSITIZE_MODE_MAPPING.computeIfAbsent(expression, k -> DesensitizeUtils.analyse(expression));
+    private boolean isDesensitizeProperty(BeanProperty property) {
+        return property.getType().getRawClass() == String.class;
     }
 
     /**
-     * 获取脱敏表达式
+     * 获取属性脱敏注解实例
      *
-     * @param property 对象属性
-     * @return 脱敏表达式
+     * @param property 属性对象
+     * @return 脱敏注解实例
      */
-    private String getDesensitizeExpression(BeanProperty property) {
-        Desensitize desensitize = property.getAnnotation(Desensitize.class);
-        return desensitize == null ? null : StringUtils.replace(desensitize.expression(), " ", StringUtils.EMPTY);
+    private Desensitize getDesensitizeAnnotation(BeanProperty property) {
+        return property.getAnnotation(Desensitize.class);
+    }
+
+    /**
+     * 获取数据脱敏模式
+     *
+     * @param annotation 脱敏注解实例
+     * @return 数据脱敏模式
+     */
+    private DesensitizeUtils.Mode getDesensitizeMode(Desensitize annotation) {
+        String expression = ObjectUtils.ifNull(annotation, Desensitize::expression);
+        if (StringUtils.isEmpty(expression)) {
+            return null;
+        }
+        return DESENSITIZE_MODE_MAPPING.computeIfAbsent(expression, DesensitizeUtils::analyse);
     }
 
     @Override
     public void serialize(String plaintext, JsonGenerator generator, SerializerProvider provider)
             throws IOException {
-        if (this.mode == null || StringUtils.isEmpty(plaintext)) {
+        DesensitizeUtils.Mode mode;
+        if (!this.enabled || this.annotation == null || StringUtils.isEmpty(plaintext)
+                || (mode = this.getDesensitizeMode(this.annotation)) == null) {
             generator.writeString(plaintext);
         } else {
-            String ciphertext = this.mode.desensitize(plaintext);
+            String ciphertext = mode.desensitize(plaintext);
             if (log.isDebugEnabled()) {
                 log.debug("Data desensitize: {} -> {}", plaintext, ciphertext);
             }
@@ -82,11 +101,11 @@ public class JacksonDesensitizeSerializer extends StdSerializer<String> implemen
     public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) {
         if (property == null) {
             return provider.getDefaultNullValueSerializer();
-        } else if (property.getType().getRawClass() != String.class) {
-            return this;
         }
-        String expression = this.getDesensitizeExpression(property);
-        AssertUtils.check(StringUtils.nonEmpty(expression), "Desensitize expression must not be empty");
-        return new JacksonDesensitizeSerializer(this.expression2mode(expression));
+        if (this.isDesensitizeProperty(property)) {
+            this.annotation = this.getDesensitizeAnnotation(property);
+            this.enabled = this.annotation != null && this.isVersioning(this.annotation);
+        }
+        return this;
     }
 }

+ 42 - 13
framework-security/src/main/java/com/chelvc/framework/security/interceptor/JacksonEncryptSerializer.java

@@ -2,6 +2,8 @@ package com.chelvc.framework.security.interceptor;
 
 import java.io.IOException;
 
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.security.annotation.Encrypt;
 import com.chelvc.framework.security.context.SecurityContextHolder;
@@ -16,26 +18,50 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer;
  * 敏感数据加密序列化处理器
  *
  * @author Woody
- * @date 2023/7/28
+ * @date 2023/9/9
  */
 public class JacksonEncryptSerializer extends StdSerializer<String> implements ContextualSerializer {
+    private boolean enabled;
+    private Encrypt annotation;
+
+    public JacksonEncryptSerializer() {
+        super(String.class);
+        this.enabled = true;
+    }
+
     /**
-     * 敏感字段加密注解实例
+     * 判断是否受版本控制
+     *
+     * @param annotation 加密注解实例
+     * @return true/false
      */
-    private final Encrypt encrypt;
+    private boolean isVersioning(Encrypt annotation) {
+        return ObjectUtils.isEmpty(annotation.versioning()) || SessionContextHolder.isVersion(annotation.versioning());
+    }
 
-    public JacksonEncryptSerializer() {
-        this(null);
+    /**
+     * 判断是否是可加密属性
+     *
+     * @param property 属性对象
+     * @return true/false
+     */
+    private boolean isEncryptProperty(BeanProperty property) {
+        return property.getType().getRawClass() == String.class;
     }
 
-    public JacksonEncryptSerializer(Encrypt encrypt) {
-        super(String.class);
-        this.encrypt = encrypt;
+    /**
+     * 获取属性加密注解实例
+     *
+     * @param property 属性对象
+     * @return 加密注解实例
+     */
+    private Encrypt getEncryptAnnotation(BeanProperty property) {
+        return property.getAnnotation(Encrypt.class);
     }
 
     @Override
     public void serialize(String plaintext, JsonGenerator generator, SerializerProvider provider) throws IOException {
-        if (this.encrypt == null || StringUtils.isEmpty(plaintext)) {
+        if (!this.enabled || this.annotation == null || StringUtils.isEmpty(plaintext)) {
             generator.writeString(plaintext);
         } else {
             generator.writeString(SecurityContextHolder.encrypt(plaintext, true));
@@ -46,10 +72,13 @@ public class JacksonEncryptSerializer extends StdSerializer<String> implements C
     public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) {
         if (property == null) {
             return provider.getDefaultNullValueSerializer();
-        } else if (property.getType().getRawClass() != String.class) {
-            return this;
         }
-        Encrypt encrypt = property.getAnnotation(Encrypt.class);
-        return encrypt == null ? this : new JacksonEncryptSerializer(encrypt);
+        if (!this.isEncryptProperty(property)) {
+            this.enabled = false;
+        } else {
+            this.annotation = this.getEncryptAnnotation(property);
+            this.enabled = this.annotation != null && this.isVersioning(this.annotation);
+        }
+        return this;
     }
 }

+ 6 - 7
framework-security/src/main/java/com/chelvc/framework/security/interceptor/RequestSecurityInterceptor.java

@@ -42,20 +42,19 @@ public class RequestSecurityInterceptor implements Filter {
      */
     private boolean validate(HttpServletRequest request, HttpServletResponse response) throws IOException {
         // 判断是否需要放行
-        SecurityProperties properties = SecurityContextHolder.getProperties();
-        SecurityProperties.Request config = properties.getRequest();
-        if (ObjectUtils.isEmpty(config.getPermit()) && ObjectUtils.isEmpty(config.getRequire())) {
+        SecurityProperties.Request configuration = SecurityContextHolder.getConfiguration().getRequest();
+        if (ObjectUtils.isEmpty(configuration.getPermit()) && ObjectUtils.isEmpty(configuration.getRequire())) {
             return true;
         }
         String uri = HttpUtils.getRequestURI(request);
-        if (SpringUtils.isPath(uri, config.getPermit()) || (ObjectUtils.nonEmpty(config.getRequire())
-                && !SpringUtils.isPath(uri, config.getRequire()))) {
+        if (SpringUtils.isPath(uri, configuration.getPermit()) || (ObjectUtils.nonEmpty(configuration.getRequire())
+                && !SpringUtils.isPath(uri, configuration.getRequire()))) {
             return true;
         }
 
         // 判断请求是否合法
         String timestamp;
-        long duration = Math.max(config.getDuration(), 1000);
+        long duration = Math.max(configuration.getDuration(), 1000);
         if (StringUtils.nonEmpty(request.getHeader(SessionContextHolder.HEADER_DEVICE))
                 && StringUtils.nonEmpty(request.getHeader(SessionContextHolder.HEADER_PLATFORM))
                 && StringUtils.nonEmpty(request.getHeader(SessionContextHolder.HEADER_TERMINAL))
@@ -68,7 +67,7 @@ public class RequestSecurityInterceptor implements Filter {
 
         // 无效请求
         log.warn(SessionContextHolder.getSessionMessage(request, HttpStatus.FORBIDDEN, "Request invalid"));
-        if (SpringUtils.isPath(uri, config.getIgnore())) {
+        if (SpringUtils.isPath(uri, configuration.getIgnore())) {
             return true;
         }
         response.setStatus(HttpStatus.FORBIDDEN.value());

+ 6 - 6
framework-security/src/main/java/com/chelvc/framework/security/interceptor/SignatureValidateInterceptor.java

@@ -43,14 +43,14 @@ public class SignatureValidateInterceptor implements Filter {
      */
     private boolean validate(HttpServletRequest request, HttpServletResponse response) throws IOException {
         // 判断请求是否需要放行
-        SecurityProperties properties = SecurityContextHolder.getProperties();
-        SecurityProperties.Signature config = properties.getSignature();
-        if (ObjectUtils.isEmpty(config.getPermit()) && ObjectUtils.isEmpty(config.getRequire())) {
+        SecurityProperties properties = SecurityContextHolder.getConfiguration();
+        SecurityProperties.Signature configuration = properties.getSignature();
+        if (ObjectUtils.isEmpty(configuration.getPermit()) && ObjectUtils.isEmpty(configuration.getRequire())) {
             return true;
         }
         String uri = HttpUtils.getRequestURI(request);
-        if (SpringUtils.isPath(uri, config.getPermit()) || (ObjectUtils.nonEmpty(config.getRequire())
-                && !SpringUtils.isPath(uri, config.getRequire()))) {
+        if (SpringUtils.isPath(uri, configuration.getPermit()) || (ObjectUtils.nonEmpty(configuration.getRequire())
+                && !SpringUtils.isPath(uri, configuration.getRequire()))) {
             return true;
         }
 
@@ -76,7 +76,7 @@ public class SignatureValidateInterceptor implements Filter {
 
         // 签名无效
         log.warn(SessionContextHolder.getSessionMessage(request, HttpStatus.FORBIDDEN, "Signature invalid"));
-        if (SpringUtils.isPath(uri, config.getIgnore())) {
+        if (SpringUtils.isPath(uri, configuration.getIgnore())) {
             return true;
         }
         response.setStatus(HttpStatus.FORBIDDEN.value());

+ 20 - 0
framework-sms/src/main/java/com/chelvc/framework/sms/config/CaptchaSmsProperties.java

@@ -30,4 +30,24 @@ public class CaptchaSmsProperties {
      * 验证码有效时间(秒)
      */
     private Integer expiration = 10 * 60;
+
+    /**
+     * 验证码长度
+     */
+    private Length length = Length.SIX;
+
+    /**
+     * 验证码长度枚举项
+     */
+    public enum Length {
+        /**
+         * 四位
+         */
+        FOUR,
+
+        /**
+         * 六位
+         */
+        SIX;
+    }
 }

+ 25 - 9
framework-sms/src/main/java/com/chelvc/framework/sms/support/DefaultCaptchaSmsHandler.java

@@ -48,6 +48,19 @@ public class DefaultCaptchaSmsHandler implements CaptchaSmsHandler {
     private final TemplateSmsHandler templateSmsHandler;
     private final RedisTemplate<String, Object> redisTemplate;
 
+    /**
+     * 生成随机验证码
+     *
+     * @return 验证码
+     */
+    private String random() {
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        if (this.properties.getLength() == CaptchaSmsProperties.Length.FOUR) {
+            return String.valueOf(random.nextInt(1000, 10000));
+        }
+        return String.valueOf(random.nextInt(100000, 1000000));
+    }
+
     /**
      * 构建令牌Redis标识
      *
@@ -59,13 +72,15 @@ public class DefaultCaptchaSmsHandler implements CaptchaSmsHandler {
     }
 
     /**
-     * 查找手机号测试验证码
+     * 查找手机号验证码白名单
      *
      * @param mobile 手机号
      * @return 验证码
      */
-    private String lookupTestingCaptcha(String mobile) {
-        List<Captcha> testes = ApplicationContextHolder.getSafeProperty("platform.sms.captcha.test", CAPTCHA_REFERENCE);
+    private String lookupWhitelistCaptcha(String mobile) {
+        List<Captcha> testes = ApplicationContextHolder.getSafeProperty(
+                "platform.sms.captcha.whitelist", CAPTCHA_REFERENCE
+        );
         if (CollectionUtils.isEmpty(testes)) {
             return null;
         }
@@ -79,17 +94,18 @@ public class DefaultCaptchaSmsHandler implements CaptchaSmsHandler {
         String lock = "sms:captcha:interval:" + mobile;
         String secret = RedisUtils.tryLock(lock, Duration.ofSeconds(this.properties.getInterval()));
         if (StringUtils.isEmpty(secret)) {
-            throw new ResourceUnavailableException(TemplateSmsHandler.SMS_SEND_FREQUENTLY, "短信发送过于频繁,请稍后再试");
+            String message = ApplicationContextHolder.getMessage(TemplateSmsHandler.SMS_SEND_FREQUENTLY);
+            throw new ResourceUnavailableException(TemplateSmsHandler.SMS_SEND_FREQUENTLY, message);
         }
 
-        // 如果目标手机号属于测试手机号,则直接返回配置的验证码信息,否则获取真实的验证码
+        // 如果目标手机号属于白名单手机号,则直接返回配置的验证码信息,否则获取真实的验证码
         try {
-            String test = this.lookupTestingCaptcha(mobile);
-            boolean testing = StringUtils.nonEmpty(test);
-            String code = testing ? test : String.valueOf(ThreadLocalRandom.current().nextInt(100000, 1000000));
+            String whitelist = this.lookupWhitelistCaptcha(mobile);
+            boolean whitelisted = StringUtils.nonEmpty(whitelist);
+            String code = whitelisted ? whitelist : this.random();
             String key = this.key(mobile), token = StringUtils.uuid();
             Captcha captcha = Captcha.builder().token(token).code(code).mobile(mobile).build();
-            if (!testing) {
+            if (!whitelisted) {
                 this.templateSmsHandler.send(this.properties.getTemplate(), captcha);
             }
             this.redisTemplate.opsForValue().set(key, captcha, this.properties.getExpiration(), TimeUnit.SECONDS);

+ 5 - 1
pom.xml

@@ -12,7 +12,6 @@
     <modules>
         <module>framework-base</module>
         <module>framework-boot</module>
-        <module>framework-cloud</module>
         <module>framework-database</module>
         <module>framework-dependencies</module>
         <module>framework-dubbo</module>
@@ -29,5 +28,10 @@
         <module>framework-wechat</module>
         <module>framework-oauth</module>
         <module>framework-common</module>
+        <module>framework-cloud-nacos</module>
+        <module>framework-cloud-nacos-dubbo</module>
+        <module>framework-cloud-nacos-feign</module>
+        <module>framework-cloud-kubernetes</module>
+        <module>framework-cloud-client-feign</module>
     </modules>
 </project>