woody hace 1 año
padre
commit
865e38daee
Se han modificado 22 ficheros con 515 adiciones y 596 borrados
  1. 256 333
      framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java
  2. 11 11
      framework-base/src/main/java/com/chelvc/framework/base/model/Session.java
  3. 37 0
      framework-base/src/main/java/com/chelvc/framework/base/model/Using.java
  4. 1 1
      framework-boot/pom.xml
  5. 16 1
      framework-common/src/main/java/com/chelvc/framework/common/model/Terminal.java
  6. 25 0
      framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java
  7. 34 17
      framework-common/src/main/java/com/chelvc/framework/common/util/StringUtils.java
  8. 1 0
      framework-database/pom.xml
  9. 2 0
      framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicDatasourceInterceptor.java
  10. 1 2
      framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java
  11. 1 2
      framework-oauth/src/main/java/com/chelvc/framework/oauth/config/OauthConfigurer.java
  12. 107 23
      framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java
  13. 1 1
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/annotation/DelayConsumer.java
  14. 3 3
      framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/context/RocketMQContextHolder.java
  15. 8 7
      framework-security/src/main/java/com/chelvc/framework/security/config/MethodSecurityExpression.java
  16. 0 26
      framework-security/src/main/java/com/chelvc/framework/security/config/SecurityProperties.java
  17. 4 2
      framework-security/src/main/java/com/chelvc/framework/security/context/PasswordContextHolder.java
  18. 0 32
      framework-security/src/main/java/com/chelvc/framework/security/context/SecurityContextHolder.java
  19. 0 73
      framework-security/src/main/java/com/chelvc/framework/security/interceptor/PermissionValidateInterceptor.java
  20. 0 10
      framework-sms/src/main/java/com/chelvc/framework/sms/PromotionSmsHandler.java
  21. 1 38
      framework-sms/src/main/java/com/chelvc/framework/sms/config/SmsConfigurer.java
  22. 6 14
      framework-sms/src/main/java/com/chelvc/framework/sms/support/DefaultCaptchaSmsHandler.java

+ 256 - 333
framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java

@@ -3,10 +3,7 @@ package com.chelvc.framework.base.context;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayDeque;
-import java.util.Collection;
 import java.util.Deque;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
@@ -18,6 +15,7 @@ import javax.servlet.http.HttpServletResponse;
 import com.chelvc.framework.base.annotation.Versioning;
 import com.chelvc.framework.base.model.Result;
 import com.chelvc.framework.base.model.Session;
+import com.chelvc.framework.base.model.Using;
 import com.chelvc.framework.base.util.HttpUtils;
 import com.chelvc.framework.common.model.Platform;
 import com.chelvc.framework.common.model.Terminal;
@@ -30,7 +28,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
 import org.springframework.web.context.request.NativeWebRequest;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -49,9 +46,9 @@ public class SessionContextHolder implements ServletRequestListener {
     public static final String HEADER_ID = "Id";
 
     /**
-     * 应用范围请求头
+     * 使用信息请求头
      */
-    public static final String HEADER_SCOPE = "Scope";
+    public static final String HEADER_USING = "Using";
 
     /**
      * 电话号码请求头
@@ -64,9 +61,9 @@ public class SessionContextHolder implements ServletRequestListener {
     public static final String HEADER_DEVICE = "Device";
 
     /**
-     * 是否首次请求头
+     * 业务类型请求头
      */
-    public static final String HEADER_INITIAL = "Initial";
+    public static final String HEADER_BUSINESS = "Business";
 
     /**
      * 渠道来源请求头
@@ -88,11 +85,6 @@ public class SessionContextHolder implements ServletRequestListener {
      */
     public static final String HEADER_VERSION = "Version";
 
-    /**
-     * 授权信息请求头
-     */
-    public static final String HEADER_AUTHORITY = "Authority";
-
     /**
      * 时间戳请求头
      */
@@ -119,14 +111,50 @@ public class SessionContextHolder implements ServletRequestListener {
     private static final Session EMPTY_SESSION = new Session();
 
     /**
-     * 对象缓存映射上下文
+     * 用户会话上下文
      */
-    private static final ThreadLocal<Map<Object, Object>> CACHE_CONTEXT = ThreadLocal.withInitial(HashMap::new);
+    private static final ThreadLocal<Deque<Session>> SESSION_CONTEXT = ThreadLocal.withInitial(ArrayDeque::new);
 
     /**
-     * 用户会话上下文
+     * 获取当前请求对象
+     *
+     * @return Http请求对象
      */
-    private static final ThreadLocal<Deque<Session>> SESSION_CONTEXT = ThreadLocal.withInitial(ArrayDeque::new);
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        return ObjectUtils.ifNull(attributes, ServletRequestAttributes::getRequest);
+    }
+
+    /**
+     * 获取当前响应对象
+     *
+     * @return Http响应对象
+     */
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        return ObjectUtils.ifNull(attributes, ServletRequestAttributes::getResponse);
+    }
+
+    /**
+     * 获取请求参数
+     *
+     * @param name 参数名称
+     * @return 参数值
+     */
+    public static String getParameter(@NonNull String name) {
+        return getParameter(name, value -> value);
+    }
+
+    /**
+     * 获取请求参数
+     *
+     * @param name    参数名称
+     * @param adapter 参数值适配器
+     * @return 参数值
+     */
+    public static <T> T getParameter(@NonNull String name, @NonNull Function<String, T> adapter) {
+        return StringUtils.ifEmpty(ObjectUtils.ifNull(getRequest(), request -> request.getParameter(name)), adapter);
+    }
 
     /**
      * 获取会话信息
@@ -179,6 +207,45 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getId);
     }
 
+    /**
+     * 获取客户端身份标识
+     *
+     * @param request Http请求对象
+     * @return 身份标识
+     */
+    public static Long getId(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_ID), Long::parseLong);
+    }
+
+    /**
+     * 获取使用信息
+     *
+     * @return 使用信息
+     */
+    public static Using getUsing() {
+        return getUsing(true);
+    }
+
+    /**
+     * 获取使用信息
+     *
+     * @param requireSession 会话是否是必须
+     * @return 使用信息
+     */
+    public static Using getUsing(boolean requireSession) {
+        return ObjectUtils.ifNull(getSession(requireSession), Session::getUsing);
+    }
+
+    /**
+     * 获取使用信息
+     *
+     * @param request Http请求对象
+     * @return 使用信息
+     */
+    public static Using getUsing(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_USING), Using::valueOf);
+    }
+
     /**
      * 获取当前会话请求地址
      *
@@ -199,22 +266,13 @@ public class SessionContextHolder implements ServletRequestListener {
     }
 
     /**
-     * 获取当前应用范围
-     *
-     * @return 应用范围
-     */
-    public static String getScope() {
-        return getScope(true);
-    }
-
-    /**
-     * 获取当前应用范围
+     * 获取客户端主机地址
      *
-     * @param requireSession 会话是否是必须
-     * @return 应用范围
+     * @param request Http请求对象
+     * @return 主机地址
      */
-    public static String getScope(boolean requireSession) {
-        return ObjectUtils.ifNull(getSession(requireSession), Session::getScope);
+    public static String getHost(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(HttpUtils.getRequestAddress(request), (String) null);
     }
 
     /**
@@ -236,6 +294,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getMobile);
     }
 
+    /**
+     * 获取会话电话号码
+     *
+     * @param request Http请求对象
+     * @return 电话号码
+     */
+    public static String getMobile(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_MOBILE), (String) null);
+    }
+
     /**
      * 获取当前会话设备标识
      *
@@ -255,6 +323,45 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getDevice);
     }
 
+    /**
+     * 获取客户端设备标识
+     *
+     * @param request Http请求对象
+     * @return 设备标识
+     */
+    public static String getDevice(@NonNull HttpServletRequest request) {
+        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);
+    }
+
     /**
      * 获取当前会话渠道来源
      *
@@ -274,6 +381,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getChannel);
     }
 
+    /**
+     * 获取客户端渠道来源
+     *
+     * @param request Http请求对象
+     * @return 渠道来源
+     */
+    public static String getChannel(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_CHANNEL), (String) null);
+    }
+
     /**
      * 获取当前会话平台信息
      *
@@ -293,6 +410,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getPlatform);
     }
 
+    /**
+     * 获取客户端平台标识
+     *
+     * @param request Http请求对象
+     * @return 平台标识
+     */
+    public static Platform getPlatform(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_PLATFORM), Platform::valueOf);
+    }
+
     /**
      * 获取当前会话终端信息
      *
@@ -312,6 +439,16 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(getSession(requireSession), Session::getTerminal);
     }
 
+    /**
+     * 获取客户端终端
+     *
+     * @param request Http请求对象
+     * @return 终端标识
+     */
+    public static Terminal getTerminal(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_TERMINAL), Terminal::valueOf);
+    }
+
     /**
      * 获取当前会话版本信息
      *
@@ -332,41 +469,13 @@ public class SessionContextHolder implements ServletRequestListener {
     }
 
     /**
-     * 获取当前会话授权信息
-     *
-     * @return 授权信息
-     */
-    public static String getAuthority() {
-        return getAuthority(true);
-    }
-
-    /**
-     * 获取当前会话授权信息
-     *
-     * @param requireSession 会话是否是必须
-     * @return 授权信息
-     */
-    public static String getAuthority(boolean requireSession) {
-        return ObjectUtils.ifNull(getSession(requireSession), Session::getAuthority);
-    }
-
-    /**
-     * 判断当前会话是否是首次请求
-     *
-     * @return true/false
-     */
-    public static boolean isInitial() {
-        return isInitial(true);
-    }
-
-    /**
-     * 判断当前会话是否是首次请求
+     * 获取客户端版本号
      *
-     * @param requireSession 会话是否是必须
-     * @return true/false
+     * @param request Http请求对象
+     * @return 版本号
      */
-    public static boolean isInitial(boolean requireSession) {
-        return Boolean.TRUE.equals(ObjectUtils.ifNull(getSession(requireSession), Session::getInitial));
+    public static String getVersion(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_VERSION), (String) null);
     }
 
     /**
@@ -389,104 +498,88 @@ public class SessionContextHolder implements ServletRequestListener {
     }
 
     /**
-     * 判断当前会话是否存在手机号
+     * 获取请求时间戳
      *
-     * @return true/false
+     * @param request Http请求对象
+     * @return 时间戳
      */
-    public static boolean hasMobile() {
-        return StringUtils.nonEmpty(getMobile(false));
+    public static Long getTimestamp(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_TIMESTAMP), Long::parseLong);
     }
 
     /**
-     * 判断当前会话是否已认证
+     * 获取签名信息
      *
-     * @return true/false
+     * @return 签名信息
      */
-    public static boolean isAuthenticated() {
-        return Objects.nonNull(getId(false));
+    public static String getSignature() {
+        return ObjectUtils.ifNull(getRequest(), SessionContextHolder::getSignature, () -> null);
     }
 
     /**
-     * 获取缓存对象
+     * 获取签名信息
      *
-     * @param key 缓存标识
-     * @param <K> 缓存键类型泛型
-     * @param <V> 缓存值得类型泛型
-     * @return 对象实例
+     * @param request Http请求对象
+     * @return 签名信息
      */
-    public static <K, V> V getCache(@NonNull K key) {
-        return getCache(key, k -> null);
+    public static String getSignature(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_SIGNATURE), (String) null);
     }
 
     /**
-     * 获取缓存对象
+     * 获取设备指纹
      *
-     * @param key      缓存标识
-     * @param provider 对象提供方法
-     * @param <K>      缓存键类型泛型
-     * @param <V>      缓存值得类型泛型
-     * @return 对象实例
+     * @return 设备指纹
      */
-    @SuppressWarnings("unchecked")
-    public static <K, V> V getCache(@NonNull K key, @NonNull Function<K, V> provider) {
-        return (V) CACHE_CONTEXT.get().computeIfAbsent(key, k -> provider.apply((K) k));
+    public static String getFingerprint() {
+        return getFingerprint(true);
     }
 
     /**
-     * 设置缓存对象
+     * 获取设备指纹
      *
-     * @param key   缓存标识
-     * @param value 缓存对象
-     * @param <K>   缓存键类型泛型
-     * @param <V>   缓存值得类型泛型
+     * @param requireSession 会话是否是必须
+     * @return 设备指纹
      */
-    public static <K, V> void setCache(@NonNull K key, V value) {
-        CACHE_CONTEXT.get().put(key, value);
+    public static String getFingerprint(boolean requireSession) {
+        return ObjectUtils.ifNull(getSession(requireSession), Session::getFingerprint);
     }
 
     /**
-     * 移除缓存对象
+     * 获取设备指纹
      *
-     * @param key 缓存标识
+     * @param request Http请求对象
+     * @return 设备指纹
      */
-    public static void removeCache(@NonNull String key) {
-        CACHE_CONTEXT.get().remove(key);
+    public static String getFingerprint(@NonNull HttpServletRequest request) {
+        return StringUtils.ifEmpty(request.getHeader(HEADER_FINGERPRINT), (String) null);
     }
 
     /**
-     * 移除缓存对象
+     * 判断是否是历史首次使用
      *
-     * @param keys 缓存标识数组
+     * @return true/false
      */
-    public static void removeCaches(@NonNull String... keys) {
-        if (keys.length > 0) {
-            Map<Object, Object> caches = CACHE_CONTEXT.get();
-            for (String key : keys) {
-                caches.remove(key);
-            }
-        }
+    public static boolean isInitialUsing() {
+        return getUsing(false) == Using.INITIAL;
     }
 
     /**
-     * 移除缓存对象
+     * 判断当前用户是否已注册
      *
-     * @param keys 缓存标识集合
+     * @return true/false
      */
-    public static void removeCaches(@NonNull Collection<String> keys) {
-        if (!CollectionUtils.isEmpty(keys)) {
-            Map<Object, Object> caches = CACHE_CONTEXT.get();
-            keys.forEach(caches::remove);
-        }
+    public static boolean isRegistered() {
+        return StringUtils.nonEmpty(getMobile(false));
     }
 
     /**
-     * 判断是否是指定应用范围
+     * 判断当前会话是否已认证
      *
-     * @param scope 应用范围
      * @return true/false
      */
-    public static boolean isScope(String scope) {
-        return StringUtils.nonEmpty(scope) && Objects.equals(scope, getScope(false));
+    public static boolean isAuthenticated() {
+        return Objects.nonNull(getId(false));
     }
 
     /**
@@ -634,196 +727,6 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.nonEmpty(accounts) && accounts.contains(account);
     }
 
-    /**
-     * 获取当前请求对象
-     *
-     * @return Http请求对象
-     */
-    public static HttpServletRequest getRequest() {
-        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-        return ObjectUtils.ifNull(attributes, ServletRequestAttributes::getRequest);
-    }
-
-    /**
-     * 获取当前响应对象
-     *
-     * @return Http响应对象
-     */
-    public static HttpServletResponse getResponse() {
-        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-        return ObjectUtils.ifNull(attributes, ServletRequestAttributes::getResponse);
-    }
-
-    /**
-     * 获取请求参数
-     *
-     * @param name 参数名称
-     * @return 参数值
-     */
-    public static String getParameter(@NonNull String name) {
-        return getParameter(name, value -> value);
-    }
-
-    /**
-     * 获取请求参数
-     *
-     * @param name    参数名称
-     * @param adapter 参数值适配器
-     * @return 参数值
-     */
-    public static <T> T getParameter(@NonNull String name, @NonNull Function<String, T> adapter) {
-        return StringUtils.ifEmpty(ObjectUtils.ifNull(getRequest(), request -> request.getParameter(name)), adapter);
-    }
-
-    /**
-     * 清空上下文信息
-     */
-    public static void clearSessionContext() {
-        // 清空缓存
-        CACHE_CONTEXT.remove();
-
-        // 清空会话
-        Deque<Session> sessions = SESSION_CONTEXT.get();
-        sessions.poll();
-        if (sessions.isEmpty()) {
-            SESSION_CONTEXT.remove();
-        }
-    }
-
-    /**
-     * 获取客户端身份标识
-     *
-     * @param request Http请求对象
-     * @return 身份标识
-     */
-    protected Long getId(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_ID), Long::parseLong);
-    }
-
-    /**
-     * 获取客户端主机地址
-     *
-     * @param request Http请求对象
-     * @return 主机地址
-     */
-    protected String getHost(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(HttpUtils.getRequestAddress(request), (String) null);
-    }
-
-    /**
-     * 获取应用范围
-     *
-     * @param request Http请求对象
-     * @return 应用范围
-     */
-    protected String getScope(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_SCOPE), (String) null);
-    }
-
-    /**
-     * 获取会话电话号码
-     *
-     * @param request Http请求对象
-     * @return 电话号码
-     */
-    protected String getMobile(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_MOBILE), (String) null);
-    }
-
-    /**
-     * 获取客户端设备标识
-     *
-     * @param request Http请求对象
-     * @return 设备标识
-     */
-    protected String getDevice(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_DEVICE), (String) null);
-    }
-
-    /**
-     * 获取客户端渠道来源
-     *
-     * @param request Http请求对象
-     * @return 渠道来源
-     */
-    protected String getChannel(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_CHANNEL), (String) null);
-    }
-
-    /**
-     * 获取客户端平台标识
-     *
-     * @param request Http请求对象
-     * @return 平台标识
-     */
-    protected Platform getPlatform(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_PLATFORM), Platform::valueOf);
-    }
-
-    /**
-     * 获取客户端终端
-     *
-     * @param request Http请求对象
-     * @return 终端标识
-     */
-    protected Terminal getTerminal(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_TERMINAL), Terminal::valueOf);
-    }
-
-    /**
-     * 获取客户端版本号
-     *
-     * @param request Http请求对象
-     * @return 版本号
-     */
-    protected String getVersion(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_VERSION), (String) null);
-    }
-
-    /**
-     * 获取客户端授权信息
-     *
-     * @param request Http请求对象
-     * @return 授权信息
-     */
-    protected String getAuthority(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_AUTHORITY), (String) null);
-    }
-
-    /**
-     * 获取是否首次请求
-     *
-     * @param request Http请求对象
-     * @return true/false
-     */
-    protected Boolean getInitial(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_INITIAL), Boolean::parseBoolean);
-    }
-
-    /**
-     * 获取请求时间戳
-     *
-     * @param request Http请求对象
-     * @return 时间戳
-     */
-    protected Long getTimestamp(@NonNull HttpServletRequest request) {
-        return StringUtils.ifEmpty(request.getHeader(HEADER_TIMESTAMP), Long::parseLong);
-    }
-
-    /**
-     * 初始化会话信息
-     *
-     * @param request Http请求对象
-     * @return 会话信息
-     */
-    protected Session initializeSession(@NonNull HttpServletRequest request) {
-        return Session.builder().id(this.getId(request)).host(this.getHost(request)).scope(this.getScope(request))
-                .mobile(this.getMobile(request)).device(this.getDevice(request)).channel(this.getChannel(request))
-                .platform(this.getPlatform(request)).terminal(this.getTerminal(request))
-                .version(this.getVersion(request)).authority(this.getAuthority(request))
-                .initial(this.getInitial(request)).timestamp(this.getTimestamp(request)).build();
-    }
-
     /**
      * 初始化会话信息
      *
@@ -838,64 +741,59 @@ public class SessionContextHolder implements ServletRequestListener {
     /**
      * 初始化会话主体
      *
-     * @param id    主体标识
-     * @param scope 应用范围
+     * @param id 主体标识
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope) {
-        return initializeSessionPrincipal(id, scope, null, null, true);
+    public static Session initializeSessionPrincipal(@NonNull Long id) {
+        return initializeSessionPrincipal(id, true);
     }
 
     /**
      * 初始化会话主体
      *
      * @param id    主体标识
-     * @param scope 应用范围
      * @param cover 是否覆盖已认证主体信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, boolean cover) {
-        return initializeSessionPrincipal(id, scope, null, null, cover);
+    public static Session initializeSessionPrincipal(@NonNull Long id, boolean cover) {
+        return initializeSessionPrincipal(id, null, null, cover);
     }
 
     /**
      * 初始化会话主体
      *
-     * @param id        主体标识
-     * @param scope     应用范围
-     * @param mobile    电话号码
-     * @param authority 授权信息
+     * @param id       主体标识
+     * @param mobile   电话号码
+     * @param business 业务类型
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, String mobile,
-                                                     String authority) {
-        return initializeSessionPrincipal(id, scope, mobile, authority, true);
+    public static Session initializeSessionPrincipal(@NonNull Long id, String mobile, String business) {
+        return initializeSessionPrincipal(id, mobile, business, true);
     }
 
     /**
      * 初始化会话主体
      *
-     * @param id        主体标识
-     * @param scope     应用范围
-     * @param mobile    电话号码
-     * @param authority 授权信息
-     * @param cover     是否覆盖已认证主体信息
+     * @param id       主体标识
+     * @param mobile   电话号码
+     * @param business 业务类型
+     * @param cover    是否覆盖已认证主体信息
      * @return 会话信息
      */
-    public static Session initializeSessionPrincipal(@NonNull Long id, @NonNull String scope, String mobile,
-                                                     String authority, boolean cover) {
+    public static Session initializeSessionPrincipal(@NonNull Long id, String mobile, String business, 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).scope(scope).mobile(mobile).authority(authority)
+            session = Session.builder().id(id).mobile(mobile).business(business)
+                    .using(ObjectUtils.ifNull(session, Session::getUsing))
                     .host(ObjectUtils.ifNull(session, Session::getHost))
                     .device(ObjectUtils.ifNull(session, Session::getDevice))
                     .channel(ObjectUtils.ifNull(session, Session::getChannel))
                     .platform(ObjectUtils.ifNull(session, Session::getPlatform))
                     .terminal(ObjectUtils.ifNull(session, Session::getTerminal))
                     .version(ObjectUtils.ifNull(session, Session::getVersion))
-                    .timestamp(ObjectUtils.ifNull(session, Session::getTimestamp))
-                    .initial(ObjectUtils.ifNull(session, Session::getInitial)).build();
+                    .fingerprint(ObjectUtils.ifNull(session, Session::getFingerprint))
+                    .timestamp(ObjectUtils.ifNull(session, Session::getTimestamp)).build();
             deque.poll();
             deque.push(session);
         }
@@ -1008,6 +906,31 @@ public class SessionContextHolder implements ServletRequestListener {
         return getSessionMessage(request, status.value(), exception);
     }
 
+    /**
+     * 清空上下文信息
+     */
+    public static void clearSessionContext() {
+        Deque<Session> sessions = SESSION_CONTEXT.get();
+        sessions.poll();
+        if (sessions.isEmpty()) {
+            SESSION_CONTEXT.remove();
+        }
+    }
+
+    /**
+     * 初始化会话信息
+     *
+     * @param request Http请求对象
+     * @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))
+                .channel(getChannel(request)).platform(getPlatform(request)).terminal(getTerminal(request))
+                .version(getVersion(request)).fingerprint(getFingerprint(request))
+                .timestamp(getTimestamp(request)).build();
+    }
+
     @Override
     public void requestInitialized(ServletRequestEvent event) {
         setSession(this.initializeSession((HttpServletRequest) event.getServletRequest()));

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

@@ -26,14 +26,14 @@ public class Session implements Serializable {
     private Long id;
 
     /**
-     * 请求地址
+     * 使用信息
      */
-    private String host;
+    private Using using;
 
     /**
-     * 应用范围
+     * 请求地址
      */
-    private String scope;
+    private String host;
 
     /**
      * 电话号码
@@ -45,6 +45,11 @@ public class Session implements Serializable {
      */
     private String device;
 
+    /**
+     * 业务类型
+     */
+    private String business;
+
     /**
      * 渠道来源
      */
@@ -66,14 +71,9 @@ public class Session implements Serializable {
     private String version;
 
     /**
-     * 授权信息
-     */
-    private String authority;
-
-    /**
-     * 是否是首次请求
+     * 设备指纹
      */
-    private Boolean initial;
+    private String fingerprint;
 
     /**
      * 请求时间戳

+ 37 - 0
framework-base/src/main/java/com/chelvc/framework/base/model/Using.java

@@ -0,0 +1,37 @@
+package com.chelvc.framework.base.model;
+
+import com.chelvc.framework.common.model.Enumeration;
+import lombok.Getter;
+
+/**
+ * 系统使用枚举
+ *
+ * @author Woody
+ * @date 2023/9/10
+ */
+@Getter
+public enum Using implements Enumeration {
+    /**
+     * 常规使用
+     */
+    NORMAL("常规使用"),
+
+    /**
+     * 当日首次使用
+     */
+    NEWLY("当日首次使用"),
+
+    /**
+     * 历史首次使用
+     */
+    INITIAL("历史首次使用");
+
+    /**
+     * 使用描述
+     */
+    private final String description;
+
+    Using(String description) {
+        this.description = description;
+    }
+}

+ 1 - 1
framework-boot/pom.xml

@@ -63,7 +63,7 @@
                 <artifactId>apidoc-maven-plugin</artifactId>
                 <version>${apidoc-maven-plugin.version}</version>
                 <configuration>
-                    <includeHeaders>{Long} Id 主体标识,{String} Scope 应用范围,{String} Mobile 手机号码,{String} Device 设备标识,{String} Channel 渠道来源,{String} Platform 平台标识: PC(PC)、IOS(苹果)、ANDROID(安卓),{String} Terminal 终端标识: WEB(Web)、APP(App)、APPLET(小程序),{String} Version 终端版本,{String} Authority 授权信息,{Boolean} Initial 是否首次请,{Long} Timestamp 请求时间戳,{String} Signature 签名信息,{String} Fingerprint 设备指纹,{String} Authorization 认证信息</includeHeaders>
+                    <includeHeaders>{Long} Id 主体标识,{String} Using 使用信息: NORMAL(常规使用)、NEWLY(当日首次使用)、INITIAL(历史首次使用),{String} Scope 应用范围,{String} Mobile 手机号码,{String} Device 设备标识,{String} Channel 渠道来源,{String} Platform 平台标识: PC(PC)、IOS(苹果)、ANDROID(安卓),{String} Terminal 终端标识: H5(H5)、WEB(Web)、APP(App)、APP_H5(App H5)、APPLET(小程序)、APPLET_H5(小程序H5),{String} Version 终端版本,{String} Authority 授权信息,{Long} Timestamp 请求时间戳,{String} Signature 签名信息,{String} Fingerprint 设备指纹,{String} Authorization 认证信息</includeHeaders>
                     <excludeClasses>com.chelvc.framework.base.interceptor.GlobalExceptionInterceptor</excludeClasses>
                     <analyserFactoryClass>com.chelvc.framework.base.apidoc.MethodAnalyserFactory</analyserFactoryClass>
                 </configuration>

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

@@ -10,6 +10,11 @@ import lombok.Getter;
  */
 @Getter
 public enum Terminal implements Enumeration {
+    /**
+     * H5
+     */
+    H5("H5"),
+
     /**
      * Web
      */
@@ -20,10 +25,20 @@ public enum Terminal implements Enumeration {
      */
     APP("App"),
 
+    /**
+     * App H5
+     */
+    APP_H5("App H5"),
+
     /**
      * 小程序
      */
-    APPLET("小程序");
+    APPLET("小程序"),
+
+    /**
+     * 小程序H5
+     */
+    APPLET_H5("小程序H5");
 
     /**
      * 终端描述

+ 25 - 0
framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java

@@ -514,6 +514,31 @@ public final class ObjectUtils {
                 getCopierBuilder().getMapperFacade().mapAsList(sources, target);
     }
 
+    /**
+     * 判断数组是否为空,如果数组为空则调用适配函数并返回
+     *
+     * @param array    对象数组
+     * @param supplier 对象数组适配函数
+     * @param <T>      对象类型
+     * @return 对象数组
+     */
+    public static <T> T[] ifEmpty(T[] array, @NonNull Supplier<T[]> supplier) {
+        return isEmpty(array) ? supplier.get() : array;
+    }
+
+    /**
+     * 判断数组是否为空,如果集合非空则使用适配函数
+     *
+     * @param array   对象数组
+     * @param adapter 对象数组适配函数
+     * @param <T>     对象类型
+     * @param <R>     目标类型
+     * @return 对象数组
+     */
+    public static <T, R> R[] ifEmpty(T[] array, @NonNull Function<T[], R[]> adapter) {
+        return isEmpty(array) ? null : adapter.apply(array);
+    }
+
     /**
      * 判断集合是否为空,如果集合为空则调用适配函数并返回
      *

+ 34 - 17
framework-common/src/main/java/com/chelvc/framework/common/util/StringUtils.java

@@ -287,9 +287,10 @@ public final class StringUtils {
      * 判断字符串是否为空白
      *
      * @param sequence 字符串
+     * @param <T>      字符串类型
      * @return true/false
      */
-    public static boolean isBlank(CharSequence sequence) {
+    public static <T extends CharSequence> boolean isBlank(T sequence) {
         return org.apache.commons.lang3.StringUtils.isBlank(sequence);
     }
 
@@ -297,9 +298,10 @@ public final class StringUtils {
      * 判断字符串是否为非空白
      *
      * @param sequence 字符串
+     * @param <T>      字符串类型
      * @return true/false
      */
-    public static boolean nonBlank(CharSequence sequence) {
+    public static <T extends CharSequence> boolean nonBlank(T sequence) {
         return !isBlank(sequence);
     }
 
@@ -361,9 +363,10 @@ public final class StringUtils {
      *
      * @param source 源字符串
      * @param regex  正则表达式
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean matches(CharSequence source, String regex) {
+    public static <T extends CharSequence> boolean matches(T source, String regex) {
         return nonEmpty(source) && getPattern(regex).matcher(source).matches();
     }
 
@@ -371,9 +374,10 @@ public final class StringUtils {
      * 判断字符串是否是表情符号
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isEmoji(CharSequence source) {
+    public static <T extends CharSequence> boolean isEmoji(T source) {
         return nonEmpty(source) && getPattern(EMOJI_REGEX).matcher(source).matches();
     }
 
@@ -381,9 +385,10 @@ public final class StringUtils {
      * 判断字符串是否是英文字母
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isLetter(CharSequence source) {
+    public static <T extends CharSequence> boolean isLetter(T source) {
         return nonEmpty(source) && getPattern(LETTER_REGEX).matcher(source).matches();
     }
 
@@ -391,9 +396,10 @@ public final class StringUtils {
      * 判断字符串是否是URL
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isUrl(CharSequence source) {
+    public static <T extends CharSequence> boolean isUrl(T source) {
         return nonEmpty(source) && getPattern(URL_REGEX).matcher(source).matches();
     }
 
@@ -401,9 +407,10 @@ public final class StringUtils {
      * 判断字符串是否是图片地址
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isImage(CharSequence source) {
+    public static <T extends CharSequence> boolean isImage(T source) {
         return nonEmpty(source) && getPattern(IMAGE_REGEX).matcher(source).matches();
     }
 
@@ -411,9 +418,10 @@ public final class StringUtils {
      * 判断字符串是否是邮箱地址
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isEmail(CharSequence source) {
+    public static <T extends CharSequence> boolean isEmail(T source) {
         return nonEmpty(source) && getPattern(EMAIL_REGEX).matcher(source).matches();
     }
 
@@ -421,9 +429,10 @@ public final class StringUtils {
      * 判断是否是特殊字符
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isSpecial(CharSequence source) {
+    public static <T extends CharSequence> boolean isSpecial(T source) {
         return nonEmpty(source) && getPattern(SPECIAL_REGEX).matcher(source).matches();
     }
 
@@ -431,9 +440,10 @@ public final class StringUtils {
      * 判断字符串是否是整数
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isInteger(CharSequence source) {
+    public static <T extends CharSequence> boolean isInteger(T source) {
         return nonEmpty(source) && getPattern(INTEGER_REGEX).matcher(source).matches();
     }
 
@@ -441,9 +451,10 @@ public final class StringUtils {
      * 判断字符串是否是手机号
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isMobile(CharSequence source) {
+    public static <T extends CharSequence> boolean isMobile(T source) {
         return nonEmpty(source) && getPattern(MOBILE_REGEX).matcher(source).matches();
     }
 
@@ -451,9 +462,10 @@ public final class StringUtils {
      * 判断字符串是否是数字
      *
      * @param source 字符串
+     * @param <T>    字符串类型
      * @return true/false
      */
-    public static boolean isNumber(CharSequence source) {
+    public static <T extends CharSequence> boolean isNumber(T source) {
         return nonEmpty(source) && getPattern(NUMBER_REGEX).matcher(source).matches();
     }
 
@@ -463,9 +475,10 @@ public final class StringUtils {
      * @param source      源字符串
      * @param regex       正则表达式
      * @param replacement 替换字符串
+     * @param <T>         字符串类型
      * @return 替换后字符串
      */
-    public static String replace(CharSequence source, String regex, String replacement) {
+    public static <T extends CharSequence> String replace(T source, String regex, String replacement) {
         return source == null ? null : replacement == null ? source.toString() :
                 getPattern(regex).matcher(source).replaceAll(replacement);
     }
@@ -475,9 +488,10 @@ public final class StringUtils {
      *
      * @param source 被拆分字符串
      * @param regex  分割符正则表达式
+     * @param <T>    字符串类型
      * @return 拆分字符串数组
      */
-    public static String[] split(CharSequence source, String regex) {
+    public static <T extends CharSequence> String[] split(T source, String regex) {
         return source == null ? null : getPattern(regex).split(source);
     }
 
@@ -487,9 +501,10 @@ public final class StringUtils {
      * @param source 被拆分字符串
      * @param regex  分割符正则表达式
      * @param limit  开始下标
+     * @param <T>    字符串类型
      * @return 拆分字符串数组
      */
-    public static String[] split(CharSequence source, String regex, int limit) {
+    public static <T extends CharSequence> String[] split(T source, String regex, int limit) {
         return source == null ? null : getPattern(regex).split(source, limit);
     }
 
@@ -497,9 +512,10 @@ public final class StringUtils {
      * 拆分有效字符串(默认使用","号拆分)
      *
      * @param source 被拆分字符串
+     * @param <T>    字符串类型
      * @return 字符串拆分数组
      */
-    public static String[] splitActives(CharSequence source) {
+    public static <T extends CharSequence> String[] splitActives(T source) {
         return splitActives(source, COMMA_SPLIT_DELIMITER);
     }
 
@@ -508,9 +524,10 @@ public final class StringUtils {
      *
      * @param regex  拆分符号正则表达式
      * @param source 被拆分字符串
+     * @param <T>    字符串类型
      * @return 字符串拆分数组
      */
-    public static String[] splitActives(CharSequence source, String regex) {
+    public static <T extends CharSequence> String[] splitActives(T source, String regex) {
         return source == null ? null :
                 Stream.of(split(source, regex)).filter(StringUtils::nonEmpty).toArray(String[]::new);
     }

+ 1 - 0
framework-database/pom.xml

@@ -43,6 +43,7 @@
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <optional>true</optional>
         </dependency>
     </dependencies>
 </project>

+ 2 - 0
framework-database/src/main/java/com/chelvc/framework/database/interceptor/DynamicDatasourceInterceptor.java

@@ -19,6 +19,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
 import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
 import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.core.annotation.Order;
 import org.springframework.util.CollectionUtils;
 
@@ -34,6 +35,7 @@ import org.springframework.util.CollectionUtils;
  * @date 2023/4/5
  */
 @Slf4j
+@ConditionalOnClass(DynamicDataSourceContextHolder.class)
 public class DynamicDatasourceInterceptor implements BeanDefinitionRegistryPostProcessor {
     @Override
     public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {

+ 1 - 2
framework-feign/src/main/java/com/chelvc/framework/feign/interceptor/FeignHeaderInterceptor.java

@@ -42,9 +42,8 @@ public class FeignHeaderInterceptor implements RequestInterceptor {
         Session session = SessionContextHolder.getSession(false);
         if (session != null) {
             headers.put(SessionContextHolder.HEADER_ID, session.getId());
-            headers.put(SessionContextHolder.HEADER_SCOPE, session.getScope());
             headers.put(SessionContextHolder.HEADER_MOBILE, session.getMobile());
-            headers.put(SessionContextHolder.HEADER_AUTHORITY, session.getAuthority());
+            headers.put(SessionContextHolder.HEADER_BUSINESS, session.getBusiness());
         }
 
         // 初始化Feign请求头

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

@@ -109,9 +109,8 @@ public class OauthConfigurer extends WebSecurityConfigurerAdapter {
             // 初始化会话主体信息
             SessionContextHolder.initializeSessionPrincipal(
                     OauthContextHolder.getId(jwt),
-                    OauthContextHolder.getScope(jwt),
                     OauthContextHolder.getMobile(jwt),
-                    OauthContextHolder.getAuthority(jwt)
+                    OauthContextHolder.getBusiness(jwt)
             );
             return OAuth2TokenValidatorResult.success();
         });

+ 107 - 23
framework-oauth/src/main/java/com/chelvc/framework/oauth/context/OauthContextHolder.java

@@ -1,17 +1,26 @@
 package com.chelvc.framework.oauth.context;
 
+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 com.chelvc.framework.common.model.Terminal;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
+import com.google.common.collect.Sets;
 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.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.stereotype.Component;
-import org.springframework.util.CollectionUtils;
 
 /**
  * OAuth上下文工具类
@@ -21,21 +30,21 @@ import org.springframework.util.CollectionUtils;
  */
 @Slf4j
 @Component
-public class OauthContextHolder implements ServletRequestListener {
+public class OauthContextHolder implements ServletRequestListener, ApplicationContextAware {
     /**
      * JWT身份标识
      */
     public static final String JWT_CLAIM_JTI = "jti";
 
     /**
-     * 应用范围标识
+     * JWT手机号标识
      */
-    public static final String JWT_CLAIM_SCOPE = "scope";
+    public static final String JWT_CLAIM_MOBILE = "mobile";
 
     /**
-     * JWT手机号标识
+     * 业务类型标识
      */
-    public static final String JWT_CLAIM_MOBILE = "mobile";
+    public static final String JWT_CLAIM_BUSINESS = "business";
 
     /**
      * 用户账号标识
@@ -47,14 +56,74 @@ public class OauthContextHolder implements ServletRequestListener {
      */
     public static final String JWT_CLAIM_AUTHORITIES = "authorities";
 
+    /**
+     * JWT解码器
+     */
+    private static JwtDecoder JWT_DECODER;
+
+    /**
+     * 认证主体上下文
+     */
+    private static final ThreadLocal<Object> PRINCIPAL_CONTEXT = new ThreadLocal<>();
+
+    /**
+     * 客户端详情上下文
+     */
+    private static final ThreadLocal<ClientDetails> CLIENT_DETAILS_CONTEXT = new ThreadLocal<>();
+
     /**
      * 刷新令牌上下文
      */
     private static final ThreadLocal<OAuth2RefreshToken> REFRESH_TOKEN_CONTEXT = new ThreadLocal<>();
 
     @Override
-    public void requestDestroyed(ServletRequestEvent event) {
-        clearOauthContext();
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        JWT_DECODER = applicationContext.getBean(JwtDecoder.class);
+    }
+
+    /**
+     * 获取JWT解码器
+     *
+     * @return JWT解码器
+     */
+    public static JwtDecoder getJwtDecoder() {
+        return Objects.requireNonNull(JWT_DECODER, "Jwt decoder has not been initialized");
+    }
+
+    /**
+     * 获取认证主体
+     *
+     * @return 认证主体
+     */
+    public static Object getPrincipal() {
+        return PRINCIPAL_CONTEXT.get();
+    }
+
+    /**
+     * 设置认证主体
+     *
+     * @param principal 认证主体
+     */
+    public static void setPrincipal(Object principal) {
+        PRINCIPAL_CONTEXT.set(principal);
+    }
+
+    /**
+     * 获取客户端详情
+     *
+     * @return 客户端详情
+     */
+    public static ClientDetails getClientDetails() {
+        return CLIENT_DETAILS_CONTEXT.get();
+    }
+
+    /**
+     * 设置客户端详情
+     *
+     * @param details 客户端详情
+     */
+    public static void setClientDetails(ClientDetails details) {
+        CLIENT_DETAILS_CONTEXT.set(details);
     }
 
     /**
@@ -96,40 +165,48 @@ public class OauthContextHolder implements ServletRequestListener {
     }
 
     /**
-     * 获取应用范围
+     * 获取手机号
      *
      * @param jwt JWT对象
-     * @return 应用范围
+     * @return 手机号
      */
-    public static String getScope(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_SCOPE), (String) null);
+    public static String getMobile(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_MOBILE), (String) null);
     }
 
     /**
-     * 获取手机号
+     * 获取业务类型
      *
      * @param jwt JWT对象
-     * @return 手机号
+     * @return 业务类型
      */
-    public static String getMobile(Jwt jwt) {
-        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_MOBILE), (String) null);
+    public static String getBusiness(Jwt jwt) {
+        return jwt == null ? null : StringUtils.ifEmpty(jwt.getClaimAsString(JWT_CLAIM_BUSINESS), (String) null);
+    }
+
+    /**
+     * 获取业务类型
+     *
+     * @param token 刷新令牌
+     * @return 业务类型
+     */
+    public static String getBusiness(OAuth2RefreshToken token) {
+        String value = ObjectUtils.ifNull(token, OAuth2RefreshToken::getValue);
+        return getBusiness(StringUtils.ifEmpty(value, getJwtDecoder()::decode));
     }
 
     /**
      * 获取用户授权信息
      *
      * @param jwt JWT对象
-     * @return 授权信息
+     * @return 授权信息集合
      */
-    public static String getAuthority(Jwt jwt) {
+    public static Set<String> getAuthorities(Jwt jwt) {
         if (jwt == null) {
-            return null;
+            return Collections.emptySet();
         }
         List<String> authorities = jwt.getClaimAsStringList(JWT_CLAIM_AUTHORITIES);
-        if (CollectionUtils.isEmpty(authorities)) {
-            return null;
-        }
-        return StringUtils.ifEmpty(StringUtils.join(authorities, ","), (String) null);
+        return ObjectUtils.isEmpty(authorities) ? Collections.emptySet() : Sets.newHashSet(authorities);
     }
 
     /**
@@ -147,6 +224,13 @@ public class OauthContextHolder implements ServletRequestListener {
      * 清空OAuth上下文
      */
     public static void clearOauthContext() {
+        PRINCIPAL_CONTEXT.remove();
+        CLIENT_DETAILS_CONTEXT.remove();
         REFRESH_TOKEN_CONTEXT.remove();
     }
+
+    @Override
+    public void requestDestroyed(ServletRequestEvent event) {
+        clearOauthContext();
+    }
 }

+ 1 - 1
framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/annotation/MessageDelayer.java → framework-rocketmq/src/main/java/com/chelvc/framework/rocketmq/annotation/DelayConsumer.java

@@ -19,7 +19,7 @@ import com.chelvc.framework.common.util.StringUtils;
 @Documented
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
-public @interface MessageDelayer {
+public @interface DelayConsumer {
     /**
      * 获取消息主题
      *

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

@@ -24,7 +24,7 @@ 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.MessageDelayer;
+import com.chelvc.framework.rocketmq.annotation.DelayConsumer;
 import com.chelvc.framework.rocketmq.interceptor.SessionRocketMQListener;
 import com.chelvc.framework.rocketmq.model.Delay;
 import com.chelvc.framework.rocketmq.model.MessageContext;
@@ -199,7 +199,7 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
     public void afterSingletonsInstantiated() {
         // 初始化延时消息延时器
         ApplicationContext context = ApplicationContextHolder.getApplicationContext();
-        Map<String, Object> consumers = context.getBeansWithAnnotation(MessageDelayer.class);
+        Map<String, Object> consumers = context.getBeansWithAnnotation(DelayConsumer.class);
         consumers.values().forEach(this::registerMessageDelayerContainer);
     }
 
@@ -272,7 +272,7 @@ public class RocketMQContextHolder implements RocketMQLocalTransactionListener,
     private void registerMessageDelayerContainer(Object consumer) {
         // 获取延时消息主题
         Class<?> clazz = AopProxyUtils.ultimateTargetClass(consumer);
-        MessageDelayer annotation = clazz.getAnnotation(MessageDelayer.class);
+        DelayConsumer annotation = clazz.getAnnotation(DelayConsumer.class);
         String topic = annotation.topic();
         if (StringUtils.isEmpty(topic)) {
             RocketMQMessageListener listener = clazz.getAnnotation(RocketMQMessageListener.class);

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

@@ -7,6 +7,7 @@ import java.util.stream.Stream;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.model.Platform;
 import com.chelvc.framework.common.model.Terminal;
+import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import org.springframework.security.access.PermissionEvaluator;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
@@ -150,7 +151,7 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isMobile(String... mobiles) {
-        if (mobiles == null || mobiles.length == 0) {
+        if (ObjectUtils.isEmpty(mobiles)) {
             return false;
         }
         String current = SessionContextHolder.getMobile(false);
@@ -165,7 +166,7 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isIdentity(Serializable... identities) {
-        if (identities == null || identities.length == 0) {
+        if (ObjectUtils.isEmpty(identities)) {
             return false;
         }
         Serializable current = SessionContextHolder.getId(false);
@@ -180,11 +181,11 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isBusiness(String... businesses) {
-        if (businesses == null || businesses.length == 0) {
+        if (ObjectUtils.isEmpty(businesses)) {
             return false;
         }
         String current = SessionContextHolder.getBusiness(false);
-        return StringUtils.nonEmpty(current) && Stream.of(businesses).filter(Objects::nonNull)
+        return StringUtils.nonEmpty(current) && Stream.of(businesses).filter(StringUtils::nonEmpty)
                 .anyMatch(business -> Objects.equals(business, current));
     }
 
@@ -195,7 +196,7 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isPlatform(Platform... platforms) {
-        if (platforms == null || platforms.length == 0) {
+        if (ObjectUtils.isEmpty(platforms)) {
             return false;
         }
         Platform current = SessionContextHolder.getPlatform(false);
@@ -210,7 +211,7 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isTerminal(Terminal... terminals) {
-        if (terminals == null || terminals.length == 0) {
+        if (ObjectUtils.isEmpty(terminals)) {
             return false;
         }
         Terminal current = SessionContextHolder.getTerminal(false);
@@ -225,7 +226,7 @@ public class MethodSecurityExpression implements MethodSecurityExpressionOperati
      * @return true/false
      */
     public boolean isVersion(String... versions) {
-        if (versions == null || versions.length == 0) {
+        if (ObjectUtils.isEmpty(versions)) {
             return false;
         }
         String current = SessionContextHolder.getVersion(false);

+ 0 - 26
framework-security/src/main/java/com/chelvc/framework/security/config/SecurityProperties.java

@@ -29,11 +29,6 @@ public class SecurityProperties {
      */
     private final Signature signature = new Signature();
 
-    /**
-     * 接口权限
-     */
-    private final Permission permission = new Permission();
-
     /**
      * 请求配置
      */
@@ -80,25 +75,4 @@ public class SecurityProperties {
          */
         private String[] require;
     }
-
-    /**
-     * 权限配置
-     */
-    @Data
-    public static class Permission {
-        /**
-         * 放行资源地址,多个地址使用","号隔开
-         */
-        private String[] permit;
-
-        /**
-         * 忽略的资源地址,多个地址使用","号隔开
-         */
-        private String[] ignore;
-
-        /**
-         * 必须校验的资源地址,多个地址使用","号隔开
-         */
-        private String[] require;
-    }
 }

+ 4 - 2
framework-security/src/main/java/com/chelvc/framework/security/context/PasswordContextHolder.java

@@ -38,9 +38,10 @@ public class PasswordContextHolder implements ApplicationContextAware {
      * 编码密码明文
      *
      * @param plaintext 密码文明
+     * @param <T>       字符串类型
      * @return 密码密文
      */
-    public static String encode(CharSequence plaintext) {
+    public static <T extends CharSequence> String encode(T plaintext) {
         return getPasswordEncoder().encode(plaintext);
     }
 
@@ -49,9 +50,10 @@ public class PasswordContextHolder implements ApplicationContextAware {
      *
      * @param plaintext  密码文明
      * @param ciphertext 密码密文
+     * @param <T>        字符串类型
      * @return true/false
      */
-    public static boolean matches(CharSequence plaintext, String ciphertext) {
+    public static <T extends CharSequence> boolean matches(T plaintext, String ciphertext) {
         return StringUtils.nonEmpty(plaintext) && StringUtils.nonEmpty(ciphertext)
                 && getPasswordEncoder().matches(plaintext, ciphertext);
     }

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

@@ -1,13 +1,10 @@
 package com.chelvc.framework.security.context;
 
 import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Stream;
 
 import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.context.SessionContextHolder;
 import com.chelvc.framework.common.util.AESUtils;
-import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.security.config.SecurityProperties;
 import lombok.extern.slf4j.Slf4j;
@@ -32,11 +29,6 @@ public class SecurityContextHolder {
      */
     public static final String CODEC_CHARACTERISTIC_SUFFIX = ")";
 
-    /**
-     * 权限属性名称前缀
-     */
-    public static final String PERMISSION_PROPERTY_PREFIX = "platform.permission.";
-
     /**
      * 安全配置属性
      */
@@ -124,28 +116,4 @@ public class SecurityContextHolder {
         }
         return plaintext;
     }
-
-    /**
-     * 判断当前用户是否拥有指定资源权限
-     *
-     * @param method 请求方式
-     * @param uri    资源地址
-     * @return true/false
-     */
-    public static boolean isPermissible(String method, String uri) {
-        if (StringUtils.isEmpty(method) || StringUtils.isEmpty(uri)) {
-            return false;
-        }
-
-        // 基于用户角色及对应的资源判断是否拥有权限
-        String[] roles = StringUtils.splitActives(SessionContextHolder.getAuthority(false));
-        if (ObjectUtils.isEmpty(roles)) {
-            return false;
-        }
-        String resource = method + StringUtils.SPACE + uri;
-        return Stream.of(roles).anyMatch(role -> {
-            Set<?> permissions = ApplicationContextHolder.getSafeProperty(PERMISSION_PROPERTY_PREFIX + role, Set.class);
-            return ObjectUtils.nonEmpty(permissions) && permissions.contains(resource);
-        });
-    }
 }

+ 0 - 73
framework-security/src/main/java/com/chelvc/framework/security/interceptor/PermissionValidateInterceptor.java

@@ -1,73 +0,0 @@
-package com.chelvc.framework.security.interceptor;
-
-import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import com.chelvc.framework.base.context.SessionContextHolder;
-import com.chelvc.framework.base.model.Result;
-import com.chelvc.framework.base.util.HttpUtils;
-import com.chelvc.framework.base.util.SpringUtils;
-import com.chelvc.framework.common.util.ObjectUtils;
-import com.chelvc.framework.security.config.SecurityProperties;
-import com.chelvc.framework.security.context.SecurityContextHolder;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.annotation.Order;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Component;
-
-/**
- * 接口权限校验拦截器
- *
- * @author Woody
- * @date 2023/4/5
- */
-@Slf4j
-@Order(30)
-@Component
-public class PermissionValidateInterceptor implements Filter {
-    /**
-     * 权限校验
-     *
-     * @param request  Http请求对象
-     * @param response Http相应对象
-     * @return true/false
-     * @throws IOException I/O操作异常
-     */
-    private boolean validate(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        // 判断是否拥有访问权限
-        SecurityProperties properties = SecurityContextHolder.getProperties();
-        SecurityProperties.Permission config = properties.getPermission();
-        if (ObjectUtils.isEmpty(config.getPermit()) && ObjectUtils.isEmpty(config.getRequire())) {
-            return true;
-        }
-        String method = request.getMethod(), uri = HttpUtils.getRequestURI(request);
-        if (SpringUtils.isPath(uri, config.getPermit()) || (ObjectUtils.nonEmpty(config.getRequire())
-                && !SpringUtils.isPath(uri, config.getRequire()))
-                || SecurityContextHolder.isPermissible(method, uri)) {
-            return true;
-        }
-
-        // 无访问权限
-        log.warn(SessionContextHolder.getSessionMessage(request, HttpStatus.FORBIDDEN, "Permission denied"));
-        if (SpringUtils.isPath(uri, config.getIgnore())) {
-            return true;
-        }
-        response.setStatus(HttpStatus.FORBIDDEN.value());
-        SessionContextHolder.response(response, Result.build(HttpStatus.FORBIDDEN));
-        return false;
-    }
-
-    @Override
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-            throws IOException, ServletException {
-        if (this.validate((HttpServletRequest) request, (HttpServletResponse) response)) {
-            chain.doFilter(request, response);
-        }
-    }
-}

+ 0 - 10
framework-sms/src/main/java/com/chelvc/framework/sms/PromotionSmsHandler.java

@@ -1,10 +0,0 @@
-package com.chelvc.framework.sms;
-
-/**
- * 营销短信操作接口
- *
- * @author Woody
- * @date 2021/11/23
- */
-public interface PromotionSmsHandler extends NormalSmsHandler {
-}

+ 1 - 38
framework-sms/src/main/java/com/chelvc/framework/sms/config/SmsConfigurer.java

@@ -2,18 +2,11 @@ package com.chelvc.framework.sms.config;
 
 import com.aliyun.dysmsapi20170525.Client;
 import com.aliyun.teaopenapi.models.Config;
-import com.chelvc.framework.common.util.StringUtils;
-import com.chelvc.framework.sms.support.SwitchableCaptchaSmsHandler;
 import com.github.qcloudsms.SmsSingleSender;
 import com.github.qcloudsms.httpclient.PoolingHTTPClient;
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.support.GenericApplicationContext;
 
 /**
  * 短信配置
@@ -22,11 +15,7 @@ import org.springframework.context.support.GenericApplicationContext;
  * @date 2023/4/5
  */
 @Configuration
-@RequiredArgsConstructor(onConstructor = @__(@Autowired))
-public class SmsConfigurer implements BeanPostProcessor {
-    private volatile boolean initialized;
-    private final GenericApplicationContext applicationContext;
-
+public class SmsConfigurer {
     /**
      * 构建阿里云短信配置实例
      *
@@ -69,30 +58,4 @@ public class SmsConfigurer implements BeanPostProcessor {
     public SmsSingleSender tencentSmsSender(TencentSmsProperties properties) {
         return new SmsSingleSender(properties.getAppid(), properties.getSecret(), new PoolingHTTPClient());
     }
-
-    /**
-     * 初始化可切换短信处理器
-     */
-    private void initializeSwitchableSmsHandler() {
-        // 初始化可切换验证码短信处理器
-        if (this.applicationContext.containsBean(AliyunSmsProperties.class.getName())
-                || this.applicationContext.containsBean(StringUtils.hump(AliyunSmsProperties.class.getSimpleName()))
-                || this.applicationContext.containsBean(TencentSmsProperties.class.getName())
-                || this.applicationContext.containsBean(StringUtils.hump(TencentSmsProperties.class.getSimpleName()))) {
-            this.applicationContext.registerBean(SwitchableCaptchaSmsHandler.class);
-        }
-    }
-
-    @Override
-    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
-        if (!this.initialized) {
-            synchronized (this) {
-                if (!this.initialized) {
-                    this.initializeSwitchableSmsHandler();
-                    this.initialized = true;
-                }
-            }
-        }
-        return bean;
-    }
 }

+ 6 - 14
framework-sms/src/main/java/com/chelvc/framework/sms/support/SwitchableCaptchaSmsHandler.java → framework-sms/src/main/java/com/chelvc/framework/sms/support/DefaultCaptchaSmsHandler.java

@@ -24,18 +24,20 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 
 /**
- * 可切换验证码短信处理器实现
+ * 验证码短信处理器默认实现
  *
  * @author Woody
  * @date 2023/4/5
  */
 @Slf4j
+@Component
 @ConditionalOnBean(CaptchaSmsProperties.class)
 @RequiredArgsConstructor(onConstructor = @__(@Autowired))
-public class SwitchableCaptchaSmsHandler implements CaptchaSmsHandler {
+public class DefaultCaptchaSmsHandler implements CaptchaSmsHandler {
     /**
      * 验证码配置引用
      */
@@ -43,6 +45,7 @@ public class SwitchableCaptchaSmsHandler implements CaptchaSmsHandler {
     };
 
     private final CaptchaSmsProperties properties;
+    private final TemplateSmsHandler templateSmsHandler;
     private final RedisTemplate<String, Object> redisTemplate;
 
     /**
@@ -55,17 +58,6 @@ public class SwitchableCaptchaSmsHandler implements CaptchaSmsHandler {
         return "sms:captcha:" + mobile;
     }
 
-    /**
-     * 获取模版短信处理器
-     *
-     * @return 模版短信处理器
-     */
-    private TemplateSmsHandler getTemplateSmsHandler() {
-        return ApplicationContextHolder.getSafeBean(ApplicationContextHolder.getSafeProperty(
-                "platform.sms.captcha.handler", AliyunSmsHandler.class.getName()
-        ));
-    }
-
     /**
      * 查找手机号测试验证码
      *
@@ -98,7 +90,7 @@ public class SwitchableCaptchaSmsHandler implements CaptchaSmsHandler {
             String key = this.key(mobile), token = StringUtils.uuid();
             Captcha captcha = Captcha.builder().token(token).code(code).mobile(mobile).build();
             if (!testing) {
-                this.getTemplateSmsHandler().send(this.properties.getTemplate(), captcha);
+                this.templateSmsHandler.send(this.properties.getTemplate(), captcha);
             }
             this.redisTemplate.opsForValue().set(key, captcha, this.properties.getExpiration(), TimeUnit.SECONDS);
             if (log.isDebugEnabled()) {