Explorar el Código

新增业务分组拦截处理逻辑;代码优化

woody hace 4 meses
padre
commit
1ca4c8e4f2
Se han modificado 18 ficheros con 912 adiciones y 38 borrados
  1. 86 0
      framework-base/src/main/java/com/chelvc/framework/base/annotation/Group.java
  2. 25 0
      framework-base/src/main/java/com/chelvc/framework/base/annotation/Groups.java
  3. 8 0
      framework-base/src/main/java/com/chelvc/framework/base/config/ApplicationConfigurer.java
  4. 23 0
      framework-base/src/main/java/com/chelvc/framework/base/context/GroupEvent.java
  5. 21 0
      framework-base/src/main/java/com/chelvc/framework/base/context/GroupEventListener.java
  6. 3 2
      framework-base/src/main/java/com/chelvc/framework/base/context/JacksonContextHolder.java
  7. 3 2
      framework-base/src/main/java/com/chelvc/framework/base/context/RestContextHolder.java
  8. 40 0
      framework-base/src/main/java/com/chelvc/framework/base/context/Session.java
  9. 37 4
      framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java
  10. 2 2
      framework-base/src/main/java/com/chelvc/framework/base/crypto/CipherFactory.java
  11. 20 0
      framework-base/src/main/java/com/chelvc/framework/base/group/GroupHandler.java
  12. 47 0
      framework-base/src/main/java/com/chelvc/framework/base/group/GroupStore.java
  13. 67 0
      framework-base/src/main/java/com/chelvc/framework/base/group/MemoryGroupStore.java
  14. 22 0
      framework-base/src/main/java/com/chelvc/framework/base/group/SimpleGroupHandler.java
  15. 196 0
      framework-base/src/main/java/com/chelvc/framework/base/interceptor/BusinessGroupInterceptor.java
  16. 144 0
      framework-common/src/main/java/com/chelvc/framework/common/model/Caps.java
  17. 48 28
      framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java
  18. 120 0
      framework-redis/src/main/java/com/chelvc/framework/redis/cache/RedisGroupStore.java

+ 86 - 0
framework-base/src/main/java/com/chelvc/framework/base/annotation/Group.java

@@ -0,0 +1,86 @@
+package com.chelvc.framework.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.chelvc.framework.base.group.GroupHandler;
+import com.chelvc.framework.base.group.SimpleGroupHandler;
+import com.chelvc.framework.common.model.Invoking;
+import com.chelvc.framework.common.model.Platform;
+import com.chelvc.framework.common.model.Terminal;
+
+/**
+ * 场景分组注解
+ *
+ * @author Woody
+ * @date 2024/9/8
+ */
+@Documented
+@Target({})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Group {
+    /**
+     * 获取分组场景
+     *
+     * @return 分组场景
+     */
+    String scene();
+
+    /**
+     * 是否异步分组
+     *
+     * @return true/false
+     */
+    boolean async() default false;
+
+    /**
+     * 是否需要存储
+     *
+     * @return true/false
+     */
+    boolean storing() default true;
+
+    /**
+     * 获取渠道数组
+     *
+     * @return 渠道数组
+     */
+    String[] channels() default {};
+
+    /**
+     * 获取版本数组
+     *
+     * @return 版本数组
+     */
+    Version[] versions() default {};
+
+    /**
+     * 获取平台数组
+     *
+     * @return 平台数组
+     */
+    Platform[] platforms() default {};
+
+    /**
+     * 获取终端数组
+     *
+     * @return 终端数组
+     */
+    Terminal[] terminals() default {};
+
+    /**
+     * 获取分组时机
+     *
+     * @return 分组时机
+     */
+    Invoking invoking() default Invoking.REQUEST;
+
+    /**
+     * 获取分组处理器
+     *
+     * @return 分组处理器对象
+     */
+    Class<? extends GroupHandler> handler() default SimpleGroupHandler.class;
+}

+ 25 - 0
framework-base/src/main/java/com/chelvc/framework/base/annotation/Groups.java

@@ -0,0 +1,25 @@
+package com.chelvc.framework.base.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 多场景分组注解
+ *
+ * @author Woody
+ * @date 2024/9/8
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Groups {
+    /**
+     * 获取场景分组数组
+     *
+     * @return 场景分组数组
+     */
+    Group[] value();
+}

+ 8 - 0
framework-base/src/main/java/com/chelvc/framework/base/config/ApplicationConfigurer.java

@@ -5,6 +5,8 @@ import javax.servlet.Filter;
 
 import com.chelvc.framework.base.context.DefaultSessionFactory;
 import com.chelvc.framework.base.context.SessionFactory;
+import com.chelvc.framework.base.group.GroupStore;
+import com.chelvc.framework.base.group.MemoryGroupStore;
 import com.chelvc.framework.base.interceptor.ResponseWrapInterceptor;
 import com.chelvc.framework.common.util.JacksonUtils;
 import com.chelvc.framework.common.util.ObjectUtils;
@@ -40,6 +42,12 @@ public class ApplicationConfigurer {
         return JacksonUtils.initializeSerializer(builder.build(), safely);
     }
 
+    @Bean
+    @ConditionalOnMissingBean(GroupStore.class)
+    public GroupStore groupStore() {
+        return new MemoryGroupStore();
+    }
+
     @Bean
     @ConditionalOnMissingBean(SessionFactory.class)
     public SessionFactory sessionFactory() {

+ 23 - 0
framework-base/src/main/java/com/chelvc/framework/base/context/GroupEvent.java

@@ -0,0 +1,23 @@
+package com.chelvc.framework.base.context;
+
+import com.chelvc.framework.common.model.Caps;
+import lombok.Getter;
+import lombok.NonNull;
+
+/**
+ * 场景分组事件
+ *
+ * @author Woody
+ * @date 2024/11/29
+ */
+@Getter
+public class GroupEvent extends BasicEvent {
+    private final String scene;
+    private final Caps group;
+
+    public GroupEvent(@NonNull Object source, @NonNull String scene, @NonNull Caps group) {
+        super(source);
+        this.scene = scene;
+        this.group = group;
+    }
+}

+ 21 - 0
framework-base/src/main/java/com/chelvc/framework/base/context/GroupEventListener.java

@@ -0,0 +1,21 @@
+package com.chelvc.framework.base.context;
+
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * 场景分组事件监听器
+ *
+ * @author Woody
+ * @date 2024/12/3
+ */
+@Component
+public class GroupEventListener implements ApplicationListener<GroupEvent> {
+    @Override
+    public void onApplicationEvent(GroupEvent event) {
+        Session session = SessionContextHolder.getSession(false);
+        if (session != null) {
+            session.setGroup(event.getScene(), event.getGroup());
+        }
+    }
+}

+ 3 - 2
framework-base/src/main/java/com/chelvc/framework/base/context/JacksonContextHolder.java

@@ -33,8 +33,9 @@ public final class JacksonContextHolder {
     public static ObjectMapper getGlobalSerializer() {
         if (GLOBAL_SERIALIZER == null) {
             synchronized (ObjectMapper.class) {
-                if (GLOBAL_SERIALIZER == null) {
-                    GLOBAL_SERIALIZER = ApplicationContextHolder.getBean(ObjectMapper.class);
+                if (GLOBAL_SERIALIZER == null
+                        && (GLOBAL_SERIALIZER = ApplicationContextHolder.getBean(ObjectMapper.class, false)) == null) {
+                    GLOBAL_SERIALIZER = new ObjectMapper();
                 }
             }
         }

+ 3 - 2
framework-base/src/main/java/com/chelvc/framework/base/context/RestContextHolder.java

@@ -40,8 +40,9 @@ public final class RestContextHolder {
     public static RestTemplate getTemplate() {
         if (TEMPLATE == null) {
             synchronized (RestTemplate.class) {
-                if (TEMPLATE == null) {
-                    TEMPLATE = ApplicationContextHolder.getBean(RestTemplate.class);
+                if (TEMPLATE == null
+                        && (TEMPLATE = ApplicationContextHolder.getBean(RestTemplate.class, false)) == null) {
+                    TEMPLATE = new RestTemplate();
                 }
             }
         }

+ 40 - 0
framework-base/src/main/java/com/chelvc/framework/base/context/Session.java

@@ -2,15 +2,21 @@ package com.chelvc.framework.base.context;
 
 import java.io.Serializable;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 
+import com.chelvc.framework.common.model.Caps;
 import com.chelvc.framework.common.model.Platform;
 import com.chelvc.framework.common.model.Terminal;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.google.common.collect.Maps;
+import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.NonNull;
+import lombok.Setter;
 import lombok.ToString;
 
 /**
@@ -90,6 +96,40 @@ public class Session implements Serializable {
      */
     private Long timestamp;
 
+    /**
+     * 分组场景/标识映射表
+     */
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
+    private Map<String, Caps> groups;
+
+    /**
+     * 获取场景分组
+     *
+     * @param scene 分组场景
+     * @return 分组标识
+     */
+    Caps getGroup(@NonNull String scene) {
+        return ObjectUtils.isEmpty(this.groups) ? null : this.groups.get(scene);
+    }
+
+    /**
+     * 设置场景分组
+     *
+     * @param scene 分组场景
+     * @param group 分组标识
+     */
+    void setGroup(@NonNull String scene, @NonNull Caps group) {
+        if (this.groups == null) {
+            synchronized (this) {
+                if (this.groups == null) {
+                    this.groups = Maps.newConcurrentMap();
+                }
+            }
+        }
+        this.groups.put(scene, group);
+    }
+
     /**
      * 初始化会话主体信息
      *

+ 37 - 4
framework-base/src/main/java/com/chelvc/framework/base/context/SessionContextHolder.java

@@ -14,7 +14,8 @@ import javax.servlet.ServletRequestListener;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import com.chelvc.framework.base.annotation.Versions;
+import com.chelvc.framework.base.group.GroupStore;
+import com.chelvc.framework.common.model.Caps;
 import com.chelvc.framework.common.model.Compare;
 import com.chelvc.framework.common.model.Platform;
 import com.chelvc.framework.common.model.Terminal;
@@ -467,11 +468,11 @@ public class SessionContextHolder implements ServletRequestListener {
     /**
      * 判断是否是指定版本
      *
-     * @param versions 版本注解
+     * @param versions 版本注解数组
      * @return true/false
      */
-    public static boolean isVersion(@NonNull Versions versions) {
-        return ObjectUtils.notEmpty(versions.value()) && Stream.of(versions.value()).anyMatch(version -> {
+    public static boolean isVersion(@NonNull com.chelvc.framework.base.annotation.Version... versions) {
+        return ObjectUtils.notEmpty(versions) && Stream.of(versions).anyMatch(version -> {
             if (version.compare() == Compare.EQ) {
                 return isVersion(version.terminal(), version.value());
             } else if (version.compare() == Compare.NE) {
@@ -572,6 +573,38 @@ public class SessionContextHolder implements ServletRequestListener {
         return ObjectUtils.ifNull(attributes, ServletRequestAttributes::getResponse);
     }
 
+    /**
+     * 获取场景分组
+     *
+     * @param scene 分组场景
+     * @return 分组标识
+     */
+    public static Caps getGroup(@NonNull String scene) {
+        return getGroup(scene, false);
+    }
+
+    /**
+     * 获取场景分组
+     *
+     * @param scene   分组场景
+     * @param refresh 是否刷新
+     * @return 分组标识
+     */
+    public static Caps getGroup(@NonNull String scene, boolean refresh) {
+        Session session = getSession(false);
+        if (session == null) {
+            return null;
+        }
+        Caps group = session.getGroup(scene);
+        if (group == null && refresh) {
+            GroupStore store = ApplicationContextHolder.getBean(GroupStore.class);
+            if ((group = store.get(scene)) != null) {
+                session.setGroup(scene, group);
+            }
+        }
+        return group == Caps.NONE ? null : group;
+    }
+
     /**
      * 获取请求参数
      *

+ 2 - 2
framework-base/src/main/java/com/chelvc/framework/base/crypto/CipherFactory.java

@@ -100,7 +100,7 @@ public interface CipherFactory {
             if (!force) {
                 throw e;
             }
-            log.warn("Plaintext encrypt failed: {}, {}", plaintext, e.getMessage());
+            log.error("Plaintext encrypt failed: {}, {}", plaintext, e.getMessage());
         }
         return plaintext;
     }
@@ -235,7 +235,7 @@ public interface CipherFactory {
             if (!force) {
                 throw e;
             }
-            log.warn("Ciphertext decrypt failed: {}, {}", ciphertext, e.getMessage());
+            log.error("Ciphertext decrypt failed: {}, {}", ciphertext, e.getMessage());
         }
         return ciphertext;
     }

+ 20 - 0
framework-base/src/main/java/com/chelvc/framework/base/group/GroupHandler.java

@@ -0,0 +1,20 @@
+package com.chelvc.framework.base.group;
+
+import com.chelvc.framework.common.model.Caps;
+
+/**
+ * 分组处理器接口
+ *
+ * @author Woody
+ * @date 2024/9/8
+ */
+public interface GroupHandler {
+    /**
+     * 执行分组
+     *
+     * @param scene 分组场景
+     * @param args  方法输入/出参数数组
+     * @return 分组结果
+     */
+    Caps execute(String scene, Object... args);
+}

+ 47 - 0
framework-base/src/main/java/com/chelvc/framework/base/group/GroupStore.java

@@ -0,0 +1,47 @@
+package com.chelvc.framework.base.group;
+
+import java.util.Collection;
+import java.util.Map;
+
+import com.chelvc.framework.common.model.Caps;
+
+/**
+ * 场景分组存储接口
+ *
+ * @author Woody
+ * @date 2024/11/23
+ */
+public interface GroupStore {
+    /**
+     * 获取分组标识
+     *
+     * @param scene 场景标识
+     * @return 分组标识
+     */
+    Caps get(String scene);
+
+    /**
+     * 批量获取分组标识
+     *
+     * @param scenes 分组场景集合
+     * @return 分组场景/标识映射表
+     */
+    Map<String, Caps> get(Collection<String> scenes);
+
+    /**
+     * 设置分组标识,如果分组不存在则设置分组,否则忽略
+     *
+     * @param scene 分组场景
+     * @param group 分组标识
+     * @return 分组标识
+     */
+    Caps set(String scene, Caps group);
+
+    /**
+     * 批量设置分组标识,如果分组不存在则设置分组,否则忽略
+     *
+     * @param groups 分组场景/标识映射表
+     * @return 分组场景/标识映射表
+     */
+    Map<String, Caps> set(Map<String, Caps> groups);
+}

+ 67 - 0
framework-base/src/main/java/com/chelvc/framework/base/group/MemoryGroupStore.java

@@ -0,0 +1,67 @@
+package com.chelvc.framework.base.group;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.model.Caps;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.google.common.collect.Maps;
+import lombok.NonNull;
+
+/**
+ * 基于内存场景分组存储实现
+ *
+ * @author Woody
+ * @date 2024/11/24
+ */
+public class MemoryGroupStore implements GroupStore {
+    private final Map<Long, Map<String, Caps>> cache = Maps.newConcurrentMap();
+
+    @Override
+    public Caps get(@NonNull String scene) {
+        Long id = SessionContextHolder.getId();
+        if (id == null) {
+            return null;
+        }
+        return ObjectUtils.ifNull(this.cache.get(id), groups -> groups.get(scene));
+    }
+
+    @Override
+    public Map<String, Caps> get(@NonNull Collection<String> scenes) {
+        if (ObjectUtils.isEmpty(scenes)) {
+            return Collections.emptyMap();
+        }
+        Long id = SessionContextHolder.getId();
+        if (id == null) {
+            return Collections.emptyMap();
+        }
+        Map<String, Caps> groups = ObjectUtils.ifNull(this.cache.get(id), Collections::emptyMap);
+        return scenes.stream().collect(Collectors.toMap(scene -> scene, groups::get));
+    }
+
+    @Override
+    public Caps set(@NonNull String scene, @NonNull Caps group) {
+        Long id = SessionContextHolder.getId();
+        if (id == null) {
+            return group;
+        }
+        Map<String, Caps> groups = this.cache.get(id);
+        if (groups == null) {
+            groups = this.cache.computeIfAbsent(id, k -> Maps.newConcurrentMap());
+        }
+        return groups.computeIfAbsent(scene, k -> group);
+    }
+
+    @Override
+    public Map<String, Caps> set(@NonNull Map<String, Caps> groups) {
+        if (ObjectUtils.isEmpty(groups)) {
+            return Collections.emptyMap();
+        }
+        Map<String, Caps> values = Maps.newHashMapWithExpectedSize(groups.size());
+        groups.forEach((scene, group) -> values.put(scene, this.set(scene, group)));
+        return values;
+    }
+}

+ 22 - 0
framework-base/src/main/java/com/chelvc/framework/base/group/SimpleGroupHandler.java

@@ -0,0 +1,22 @@
+package com.chelvc.framework.base.group;
+
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.model.Caps;
+
+/**
+ * 分组处理器简单实现
+ *
+ * @author Woody
+ * @date 2024/9/8
+ */
+public class SimpleGroupHandler implements GroupHandler {
+    @Override
+    public Caps execute(String scene, Object... args) {
+        Long id = SessionContextHolder.getId();
+        if (id == null) {
+            return null;
+        }
+        int mod = (int) Math.abs(id % 2);
+        return mod == 0 ? Caps.A : Caps.B;
+    }
+}

+ 196 - 0
framework-base/src/main/java/com/chelvc/framework/base/interceptor/BusinessGroupInterceptor.java

@@ -0,0 +1,196 @@
+package com.chelvc.framework.base.interceptor;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.chelvc.framework.base.annotation.Group;
+import com.chelvc.framework.base.annotation.Groups;
+import com.chelvc.framework.base.context.ApplicationContextHolder;
+import com.chelvc.framework.base.context.GroupEvent;
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.base.context.ThreadContextHolder;
+import com.chelvc.framework.base.group.GroupHandler;
+import com.chelvc.framework.base.group.GroupStore;
+import com.chelvc.framework.common.model.Caps;
+import com.chelvc.framework.common.model.Invoking;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用户业务分组拦截器
+ *
+ * @author Woody
+ * @date 2024/11/24
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class BusinessGroupInterceptor {
+    private final GroupStore store;
+    private final ApplicationContext applicationContext;
+    private final Map<Class<?>, GroupHandler> handlers = Maps.newConcurrentMap();
+
+    /**
+     * 判断请求是否匹配分组
+     *
+     * @param group 分组注解实例
+     * @return true/false
+     */
+    private boolean matches(Group group) {
+        return StringUtils.notEmpty(group.scene())
+                && (ObjectUtils.isEmpty(group.channels()) || SessionContextHolder.isChannel(group.channels()))
+                && (ObjectUtils.isEmpty(group.versions()) || SessionContextHolder.isVersion(group.versions()))
+                && (ObjectUtils.isEmpty(group.platforms()) || SessionContextHolder.isPlatform(group.platforms()))
+                && (ObjectUtils.isEmpty(group.terminals()) || SessionContextHolder.isTerminal(group.terminals()));
+    }
+
+    /**
+     * 初始化分组处理器
+     *
+     * @param type 处理器类型
+     * @return 分组处理器实例
+     */
+    private GroupHandler initializeGroupHandler(Class<? extends GroupHandler> type) {
+        GroupHandler handler = ApplicationContextHolder.getBean(this.applicationContext, type, false);
+        if (handler == null && (handler = this.handlers.get(type)) == null) {
+            handler = this.handlers.computeIfAbsent(type, k -> ObjectUtils.instance(type));
+        }
+        return handler;
+    }
+
+    /**
+     * 执行分组逻辑
+     *
+     * @param target 拦截目标
+     * @param groups 分组注解列表
+     * @param args   分组执行参数
+     */
+    private void execute(Object target, List<Group> groups, Object... args) {
+        Map<String, Caps> stores = null;
+        for (Group group : groups) {
+            try {
+                GroupHandler handler = this.initializeGroupHandler(group.handler());
+                Caps value = handler.execute(group.scene(), args);
+                if (value != null && group.storing()) {
+                    if (stores == null) {
+                        stores = Maps.newHashMapWithExpectedSize(groups.size());
+                    }
+                    stores.put(group.scene(), value);
+                } else {
+                    value = ObjectUtils.ifNull(value, Caps.NONE);
+                    this.applicationContext.publishEvent(new GroupEvent(target, group.scene(), value));
+                }
+            } catch (Exception e) {
+                log.error("Business group handle failed: {}", group.scene(), e);
+            }
+        }
+        try {
+            stores = ObjectUtils.ifEmpty(stores, this.store::set, Collections::emptyMap);
+            stores.forEach((scene, group) -> {
+                group = ObjectUtils.ifNull(group, Caps.NONE);
+                this.applicationContext.publishEvent(new GroupEvent(target, scene, group));
+            });
+        } catch (Exception e) {
+            log.error("Business group handle failed", e);
+        }
+    }
+
+    /**
+     * 执行分组逻辑
+     *
+     * @param target   拦截目标
+     * @param invoking 分组时间点
+     * @param groups   分组注解列表
+     * @param args     分组执行参数
+     */
+    private void execute(Object target, Invoking invoking, List<Group> groups, Object... args) {
+        List<Group> sync = null, async = null;
+        for (Group group : groups) {
+            if (group.invoking() == invoking) {
+                if (group.async()) {
+                    if (async == null) {
+                        async = Lists.newLinkedList();
+                    }
+                    async.add(group);
+                } else {
+                    if (sync == null) {
+                        sync = Lists.newLinkedList();
+                    }
+                    sync.add(group);
+                }
+            }
+        }
+        if (ObjectUtils.notEmpty(async)) {
+            List<Group> copy = async;
+            ThreadContextHolder.run(() -> this.execute(target, copy, args));
+        }
+        if (ObjectUtils.notEmpty(sync)) {
+            this.execute(target, sync, args);
+        }
+    }
+
+    /**
+     * 方法调用分组拦截
+     *
+     * @param point  方法拦截点
+     * @param groups 分组注解实例
+     * @return 方法调用结果
+     * @throws Throwable 方法调用异常
+     */
+    @Around("@annotation(groups)")
+    public Object intercept(ProceedingJoinPoint point, Groups groups) throws Throwable {
+        if (Objects.isNull(SessionContextHolder.getId())) {
+            return point.proceed(point.getArgs());
+        }
+
+        // 获取匹配的分组注解列表
+        List<Group> matches = Stream.of(groups.value()).filter(this::matches)
+                .collect(Collectors.toCollection(Lists::newLinkedList));
+        if (ObjectUtils.isEmpty(matches)) {
+            return point.proceed(point.getArgs());
+        }
+
+        // 获取已存储分组结果
+        Object target = point.getTarget();
+        try {
+            Set<String> scenes = matches.stream().filter(Group::storing).map(Group::scene).collect(Collectors.toSet());
+            Map<String, Caps> stores = ObjectUtils.ifEmpty(scenes, this.store::get, Collections::emptyMap);
+            stores.forEach((scene, group) -> {
+                if (group != null) {
+                    matches.removeIf(annotation -> Objects.equals(annotation.scene(), scene));
+                    this.applicationContext.publishEvent(new GroupEvent(target, scene, group));
+                }
+            });
+        } catch (Exception e) {
+            log.error("Business group handle failed", e);
+            return point.proceed(point.getArgs());
+        }
+
+        // 执行分组逻辑(方法调用前后)
+        Object[] args = point.getArgs();
+        if (ObjectUtils.notEmpty(matches)) {
+            this.execute(target, Invoking.REQUEST, matches, args);
+        }
+        Object value = point.proceed(args);
+        if (ObjectUtils.notEmpty(matches)) {
+            this.execute(target, Invoking.RESPONSE, matches, value);
+        }
+        return value;
+    }
+}

+ 144 - 0
framework-common/src/main/java/com/chelvc/framework/common/model/Caps.java

@@ -0,0 +1,144 @@
+package com.chelvc.framework.common.model;
+
+/**
+ * 英文大写字母枚举
+ *
+ * @author Woody
+ * @date 2024/12/3
+ */
+public enum Caps {
+    /**
+     * A
+     */
+    A,
+
+    /**
+     * B
+     */
+    B,
+
+    /**
+     * C
+     */
+    C,
+
+    /**
+     * D
+     */
+    D,
+
+    /**
+     * E
+     */
+    E,
+
+    /**
+     * F
+     */
+    F,
+
+    /**
+     * G
+     */
+    G,
+
+    /**
+     * H
+     */
+    H,
+
+    /**
+     * I
+     */
+    I,
+
+    /**
+     * J
+     */
+    J,
+
+    /**
+     * K
+     */
+    K,
+
+    /**
+     * L
+     */
+    L,
+
+    /**
+     * M
+     */
+    M,
+
+    /**
+     * N
+     */
+    N,
+
+    /**
+     * O
+     */
+    O,
+
+    /**
+     * P
+     */
+    P,
+
+    /**
+     * Q
+     */
+    Q,
+
+    /**
+     * R
+     */
+    R,
+
+    /**
+     * S
+     */
+    S,
+
+    /**
+     * T
+     */
+    T,
+
+    /**
+     * U
+     */
+    U,
+
+    /**
+     * V
+     */
+    V,
+
+    /**
+     * W
+     */
+    W,
+
+    /**
+     * X
+     */
+    X,
+
+    /**
+     * Y
+     */
+    Y,
+
+    /**
+     * Z
+     */
+    Z,
+
+    /**
+     * 空
+     */
+    NONE;
+}

+ 48 - 28
framework-common/src/main/java/com/chelvc/framework/common/util/ObjectUtils.java

@@ -647,7 +647,7 @@ public final class ObjectUtils {
         }
         T instance;
         try {
-            instance = (T) type.newInstance();
+            instance = type.newInstance();
         } catch (ReflectiveOperationException e) {
             throw new RuntimeException(e);
         }
@@ -744,42 +744,48 @@ public final class ObjectUtils {
     }
 
     /**
-     * 判断数组是否为空,如果数组为空则调用适配函数并返回
+     * 判断字典是否为空,如果字典为空则调用适配函数并返回
      *
-     * @param array    对象数组
-     * @param supplier 默认数组提供函数
-     * @param <T>      对象类型
-     * @return 对象数组
+     * @param map      字典
+     * @param supplier 默认字典提供函数
+     * @param <K>      健类型
+     * @param <V>      值类型
+     * @param <M>      字典类型
+     * @return 字典
      */
-    public static <T> T[] ifEmpty(T[] array, @NonNull Supplier<T[]> supplier) {
-        return isEmpty(array) ? supplier.get() : array;
+    public static <K, V, M extends Map<K, V>> M ifEmpty(M map, @NonNull Supplier<M> supplier) {
+        return isEmpty(map) ? supplier.get() : map;
     }
 
     /**
-     * 判断数组是否为空,如果集合非空则使用适配函数
+     * 判断字典是否为空,如果字典为空则调用适配函数并返回
      *
-     * @param array    对象数组
-     * @param function 数组处理函数
-     * @param <T>      对象类型
-     * @param <R>      目标类型
-     * @return 对象数组
+     * @param map      字典
+     * @param function 字典处理函数
+     * @param <K>      健类型
+     * @param <V>      值类型
+     * @param <M>      字典类型
+     * @return 字典
      */
-    public static <T, R> R[] ifEmpty(T[] array, @NonNull Function<T[], R[]> function) {
-        return isEmpty(array) ? null : function.apply(array);
+    public static <K, V, M extends Map<K, V>> Map<K, V> ifEmpty(M map, @NonNull Function<M, Map<K, V>> function) {
+        return ifEmpty(map, function, Collections::emptyMap);
     }
 
     /**
-     * 判断数组是否为空,如果集合非空则使用适配函数
+     * 判断字典是否为空,如果字典为空则调用适配函数并返回
      *
-     * @param array    对象数组
-     * @param function 数组处理函数
-     * @param supplier 默认数组提供函数
-     * @param <T>      对象类型
+     * @param map      字典
+     * @param function 字典处理函数
+     * @param supplier 默认字典提供函数
+     * @param <K>      健类型
+     * @param <V>      值类型
      * @param <R>      目标类型
-     * @return 对象数组
+     * @param <M>      字典类型
+     * @return 字典
      */
-    public static <T, R> R[] ifEmpty(T[] array, @NonNull Function<T[], R[]> function, @NonNull Supplier<R[]> supplier) {
-        return isEmpty(array) ? supplier.get() : ifEmpty(function.apply(array), supplier);
+    public static <K, V, R, M extends Map<K, V>> R ifEmpty(M map, @NonNull Function<M, R> function,
+                                                           @NonNull Supplier<R> supplier) {
+        return isEmpty(map) ? supplier.get() : ifNull(function.apply(map), supplier);
     }
 
     /**
@@ -807,7 +813,7 @@ public final class ObjectUtils {
      */
     public static <T, R, C extends Collection<T>> List<R> ifEmpty(C collection,
                                                                   @NonNull Function<C, List<R>> function) {
-        return isEmpty(collection) ? Collections.emptyList() : function.apply(collection);
+        return ifEmpty(collection, function, Collections::emptyList);
     }
 
     /**
@@ -821,9 +827,23 @@ public final class ObjectUtils {
      * @param <C>        集合类型
      * @return 对象集合
      */
-    public static <T, R, C extends Collection<T>> List<R> ifEmpty(C collection, @NonNull Function<C, List<R>> function,
-                                                                  @NonNull Supplier<List<R>> supplier) {
-        return isEmpty(collection) ? supplier.get() : ifEmpty(function.apply(collection), supplier);
+    public static <T, R, C extends Collection<T>> R ifEmpty(C collection, @NonNull Function<C, R> function,
+                                                            @NonNull Supplier<R> supplier) {
+        return isEmpty(collection) ? supplier.get() : ifNull(function.apply(collection), supplier);
+    }
+
+    /**
+     * 判断数组是否为空,如果数组非空则使用适配函数
+     *
+     * @param array    对象数组
+     * @param function 数组处理函数
+     * @param supplier 默认数组提供函数
+     * @param <T>      对象类型
+     * @param <R>      目标类型
+     * @return 对象数组
+     */
+    public static <T, R> R ifEmpty(T[] array, @NonNull Function<T[], R> function, @NonNull Supplier<R> supplier) {
+        return isEmpty(array) ? supplier.get() : ifNull(function.apply(array), supplier);
     }
 
     /**

+ 120 - 0
framework-redis/src/main/java/com/chelvc/framework/redis/cache/RedisGroupStore.java

@@ -0,0 +1,120 @@
+package com.chelvc.framework.redis.cache;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.base.group.GroupStore;
+import com.chelvc.framework.common.model.Caps;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.redis.context.RedisContextHolder;
+import com.chelvc.framework.redis.context.RedisHashHolder;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import lombok.NonNull;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+
+/**
+ * 场景分组存储Redis缓存实现
+ *
+ * @author Woody
+ * @date 2024/11/28
+ */
+public class RedisGroupStore implements GroupStore {
+    private final GroupStore delegate;
+
+    public RedisGroupStore(@NonNull GroupStore delegate) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * 获取Redis用户分组标识
+     *
+     * @return Redis标识
+     */
+    private String key() {
+        Long id = SessionContextHolder.getId();
+        return id == null ? null : ("group:" + id);
+    }
+
+    @Override
+    public Caps get(@NonNull String scene) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key)) {
+            return null;
+        }
+
+        // 获取分组标识,如果缓存不存在则重新加载分组并更新缓存
+        RedisTemplate<String, Object> template = RedisContextHolder.getDefaultTemplate();
+        Caps group = (Caps) template.opsForHash().get(key, scene);
+        if (group == null && (group = this.delegate.get(scene)) != null) {
+            RedisHashHolder.set(template, key, scene, group, Duration.ofDays(1));
+        }
+        return group;
+    }
+
+    @Override
+    public Map<String, Caps> get(@NonNull Collection<String> scenes) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key) || ObjectUtils.isEmpty(scenes)) {
+            return Collections.emptyMap();
+        }
+
+        // 批量获取缓存分组映射
+        List<String> nones = Lists.newLinkedList();
+        Map<String, Caps> mapping = Maps.newHashMapWithExpectedSize(scenes.size());
+        RedisTemplate<String, Object> template = RedisContextHolder.getDefaultTemplate();
+        HashOperations<String, String, Caps> operations = template.opsForHash();
+        List<Caps> groups = operations.multiGet(key, scenes);
+        int i = 0;
+        for (String scene : scenes) {
+            Caps group = ObjectUtils.isEmpty(groups) ? null : groups.get(i++);
+            if (group == null) {
+                nones.add(scene);
+            } else {
+                mapping.put(scene, group);
+            }
+        }
+
+        // 重新加载场景分组为null的数据
+        if (ObjectUtils.notEmpty(nones)) {
+            Map<String, Caps> reloads = this.delegate.get(nones);
+            if (ObjectUtils.notEmpty(reloads)) {
+                mapping.putAll(reloads);
+                RedisHashHolder.set(template, key, reloads, Duration.ofDays(1));
+            }
+        }
+        return mapping;
+    }
+
+    @Override
+    public Caps set(@NonNull String scene, @NonNull Caps group) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key)) {
+            return group;
+        }
+
+        // 重置场景分组并更新缓存
+        group = this.delegate.set(scene, group);
+        RedisHashHolder.set(RedisContextHolder.getDefaultTemplate(), key, scene, group, Duration.ofDays(1));
+        return group;
+    }
+
+    @Override
+    public Map<String, Caps> set(@NonNull Map<String, Caps> groups) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key)) {
+            return groups;
+        }
+
+        // 重置场景分组并更新缓存
+        groups = this.delegate.set(groups);
+        RedisHashHolder.set(RedisContextHolder.getDefaultTemplate(), key, groups, Duration.ofDays(1));
+        return groups;
+    }
+}