woody 5 месяцев назад
Родитель
Сommit
5e957f2e9a

+ 34 - 0
framework-group/pom.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.chelvc.framework</groupId>
+        <artifactId>framework-cloud-feign-client</artifactId>
+        <version>1.0.0-RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>framework-group</artifactId>
+    <version>1.0.0-RELEASE</version>
+
+    <properties>
+        <framework-base.version>1.0.0-RELEASE</framework-base.version>
+        <framework-redis.version>1.0.0-RELEASE</framework-redis.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-base</artifactId>
+            <version>${framework-base.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.chelvc.framework</groupId>
+            <artifactId>framework-redis</artifactId>
+            <version>${framework-redis.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 58 - 0
framework-group/src/main/java/com/chelvc/framework/group/GroupClient.java

@@ -0,0 +1,58 @@
+package com.chelvc.framework.group;
+
+import java.util.Collection;
+import java.util.Map;
+
+import com.chelvc.framework.common.model.Caps;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * 场景分组客户端
+ *
+ * @author Woody
+ * @date 2024/12/5
+ */
+@FeignClient(value = "${group.store.server}")
+public interface GroupClient {
+    /**
+     * 获取分组标识
+     *
+     * @param scene 场景标识
+     * @return 分组标识
+     */
+    @GetMapping("/grouping/{scene}")
+    Caps get(@PathVariable("scene") String scene);
+
+    /**
+     * 批量获取分组标识
+     *
+     * @param scenes 分组场景集合
+     * @return 分组场景/标识映射表
+     */
+    @GetMapping("/grouping")
+    Map<String, Caps> get(@RequestParam("scenes") Collection<String> scenes);
+
+    /**
+     * 设置分组标识,如果分组不存在则设置分组,否则忽略
+     *
+     * @param scene 分组场景
+     * @param group 分组标识
+     * @return 分组标识
+     */
+    @PostMapping("/grouping/{scene}/{group}")
+    Caps set(@PathVariable("scene") String scene, @PathVariable("group") Caps group);
+
+    /**
+     * 批量设置分组标识,如果分组不存在则设置分组,否则忽略
+     *
+     * @param groups 分组场景/标识映射表
+     * @return 分组场景/标识映射表
+     */
+    @PostMapping("/grouping")
+    Map<String, Caps> set(@RequestBody Map<String, Caps> groups);
+}

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

@@ -0,0 +1,20 @@
+package com.chelvc.framework.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-group/src/main/java/com/chelvc/framework/group/GroupStore.java

@@ -0,0 +1,47 @@
+package com.chelvc.framework.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);
+}

+ 142 - 0
framework-group/src/main/java/com/chelvc/framework/group/RedisGroupStore.java

@@ -0,0 +1,142 @@
+package com.chelvc.framework.group;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.chelvc.framework.base.context.SessionContextHolder;
+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.group.context.GroupContextHolder;
+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 GroupClient client;
+
+    public RedisGroupStore(@NonNull GroupClient client) {
+        this.client = client;
+    }
+
+    /**
+     * 获取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;
+        }
+
+        // 优先使用配置分组
+        Caps group = GroupContextHolder.getConfig(scene);
+        if (group != null) {
+            return group;
+        }
+
+        // 获取分组标识,如果缓存不存在则重新加载分组并更新缓存
+        RedisTemplate<String, Object> template = RedisContextHolder.getDefaultTemplate();
+        HashOperations<String, String, String> operations = template.opsForHash();
+        group = StringUtils.ifEmpty(operations.get(key, scene), Caps::valueOf);
+        if (group == null && (group = this.client.get(scene)) != null) {
+            RedisHashHolder.set(template, key, scene, group, Duration.ofDays(2));
+        }
+        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());
+        for (String scene : scenes) {
+            Caps group = GroupContextHolder.getConfig(scene);
+            if (group == null) {
+                nones.add(scene);
+            } else {
+                mapping.put(scene, group);
+            }
+        }
+
+        // 批量获取缓存分组映射
+        if (ObjectUtils.notEmpty(nones)) {
+            RedisTemplate<String, Object> template = RedisContextHolder.getDefaultTemplate();
+            HashOperations<String, String, String> operations = template.opsForHash();
+            List<String> groups = operations.multiGet(key, nones);
+
+            int i = 0;
+            Iterator<String> iterator = nones.iterator();
+            while (iterator.hasNext()) {
+                String group = groups.get(i++);
+                String scene = iterator.next();
+                if (StringUtils.notEmpty(group)) {
+                    mapping.put(scene, Caps.valueOf(group));
+                    iterator.remove();
+                }
+            }
+        }
+
+        // 重新加载场景分组为null的数据
+        if (ObjectUtils.notEmpty(nones)) {
+            Map<String, Caps> reloads = this.client.get(nones);
+            if (ObjectUtils.notEmpty(reloads)) {
+                mapping.putAll(reloads);
+                RedisHashHolder.set(RedisContextHolder.getDefaultTemplate(), key, reloads, Duration.ofDays(2));
+            }
+        }
+        return mapping;
+    }
+
+    @Override
+    public Caps set(@NonNull String scene, @NonNull Caps group) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key)) {
+            return group;
+        }
+
+        // 重置场景分组并更新缓存
+        group = this.client.set(scene, group);
+        RedisHashHolder.set(RedisContextHolder.getDefaultTemplate(), key, scene, group, Duration.ofDays(2));
+        return group;
+    }
+
+    @Override
+    public Map<String, Caps> set(@NonNull Map<String, Caps> groups) {
+        String key = this.key();
+        if (StringUtils.isEmpty(key) || ObjectUtils.isEmpty(groups)) {
+            return groups;
+        }
+
+        // 重置场景分组并更新缓存
+        groups = this.client.set(groups);
+        RedisHashHolder.set(RedisContextHolder.getDefaultTemplate(), key, groups, Duration.ofDays(2));
+        return groups;
+    }
+}

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

@@ -0,0 +1,22 @@
+package com.chelvc.framework.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;
+    }
+}

+ 87 - 0
framework-group/src/main/java/com/chelvc/framework/group/annotation/Group.java

@@ -0,0 +1,87 @@
+package com.chelvc.framework.group.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.annotation.Version;
+import com.chelvc.framework.common.model.Invoking;
+import com.chelvc.framework.common.model.Platform;
+import com.chelvc.framework.common.model.Terminal;
+import com.chelvc.framework.group.GroupHandler;
+import com.chelvc.framework.group.SimpleGroupHandler;
+
+/**
+ * 场景分组注解
+ *
+ * @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 false;
+
+    /**
+     * 获取渠道数组
+     *
+     * @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-group/src/main/java/com/chelvc/framework/group/annotation/Groups.java

@@ -0,0 +1,25 @@
+package com.chelvc.framework.group.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();
+}

+ 23 - 0
framework-group/src/main/java/com/chelvc/framework/group/config/GroupConfigurer.java

@@ -0,0 +1,23 @@
+package com.chelvc.framework.group.config;
+
+import com.chelvc.framework.group.GroupClient;
+import com.chelvc.framework.group.GroupStore;
+import com.chelvc.framework.group.RedisGroupStore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 分组配置
+ *
+ * @author Woody
+ * @date 2024/12/5
+ */
+@Configuration
+public class GroupConfigurer {
+    @Bean
+    @ConditionalOnMissingBean(GroupStore.class)
+    public GroupStore groupStore(GroupClient client) {
+        return new RedisGroupStore(client);
+    }
+}

+ 131 - 0
framework-group/src/main/java/com/chelvc/framework/group/context/GroupContextHolder.java

@@ -0,0 +1,131 @@
+package com.chelvc.framework.group.context;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import com.chelvc.framework.base.context.ApplicationContextHolder;
+import com.chelvc.framework.base.context.Session;
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.common.model.Caps;
+import com.chelvc.framework.common.util.ObjectUtils;
+import com.chelvc.framework.group.GroupStore;
+import com.fasterxml.jackson.core.type.TypeReference;
+import lombok.NonNull;
+
+/**
+ * 场景分组上下文工具类
+ *
+ * @author Woody
+ * @date 2024/12/5
+ */
+public final class GroupContextHolder {
+    /**
+     * 场景分组存储器
+     */
+    private static volatile GroupStore GROUP_STORE;
+
+    /**
+     * 场景分组模值映射类型
+     */
+    private static final TypeReference<Map<Caps, Set<Integer>>> GROUP_MOLD_TYPE =
+            new TypeReference<Map<Caps, Set<Integer>>>() {
+            };
+
+    private GroupContextHolder() {
+    }
+
+    /**
+     * 获取场景分组存储器
+     *
+     * @return 场景分组存储器
+     */
+    public static GroupStore getStore() {
+        if (GROUP_STORE == null) {
+            synchronized (GroupContextHolder.class) {
+                if (GROUP_STORE == null) {
+                    GROUP_STORE = ApplicationContextHolder.getBean(GroupStore.class);
+                }
+            }
+        }
+        return GROUP_STORE;
+    }
+
+    /**
+     * 获取配置分组
+     *
+     * @param scene 分组场景
+     * @return 分组标识
+     */
+    public static Caps getConfig(@NonNull String scene) {
+        return ApplicationContextHolder.getProperty("business.group." + scene, Caps.class);
+    }
+
+    /**
+     * 获取配置分组
+     *
+     * @param property 配置属性
+     * @param mold     分组模值
+     * @return 分组标识
+     */
+    public static Caps getConfig(@NonNull String property, int mold) {
+        return getConfig(property, mold, group -> group);
+    }
+
+    /**
+     * 获取配置分组
+     *
+     * @param property 配置属性
+     * @param mold     分组模值
+     * @param function 分组回调函数
+     * @return 分组标识
+     */
+    public static Caps getConfig(@NonNull String property, int mold, @NonNull Function<Caps, Caps> function) {
+        Map<Caps, Set<Integer>> mapping = ApplicationContextHolder.getProperty(property, GROUP_MOLD_TYPE);
+        if (ObjectUtils.notEmpty(mapping)) {
+            for (Map.Entry<Caps, Set<Integer>> entry : mapping.entrySet()) {
+                if (ObjectUtils.notEmpty(entry.getValue()) && entry.getValue().contains(mold)) {
+                    return function.apply(entry.getKey());
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 获取场景分组
+     *
+     * @param scene 分组场景
+     * @return 分组标识
+     */
+    public static Caps getGroup(@NonNull String scene) {
+        Caps group = getConfig(scene);
+        if (group != null) {
+            return group;
+        }
+
+        Session session = SessionContextHolder.getSession(false);
+        if (session == null) {
+            return null;
+        }
+        if ((group = session.getGroup(scene)) == null) {
+            GroupStore store = getStore();
+            group = ObjectUtils.ifNull(store.get(scene), Caps.NONE);
+            session.setGroup(scene, group);
+        }
+        return group == Caps.NONE ? null : group;
+    }
+
+    /**
+     * 设置场景分组
+     *
+     * @param scene 分组场景
+     * @param group 分组标识
+     */
+    public static void setGroup(@NonNull String scene, @NonNull Caps group) {
+        Session session = SessionContextHolder.getSession(false);
+        if (session != null) {
+            session.setGroup(scene, group);
+        }
+    }
+}

+ 187 - 0
framework-group/src/main/java/com/chelvc/framework/group/context/GroupMethodInterceptor.java

@@ -0,0 +1,187 @@
+package com.chelvc.framework.group.context;
+
+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.context.ApplicationContextHolder;
+import com.chelvc.framework.base.context.SessionContextHolder;
+import com.chelvc.framework.base.context.ThreadContextHolder;
+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.chelvc.framework.group.GroupHandler;
+import com.chelvc.framework.group.GroupStore;
+import com.chelvc.framework.group.annotation.Group;
+import com.chelvc.framework.group.annotation.Groups;
+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 GroupMethodInterceptor {
+    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 groups 分组注解列表
+     * @param args   分组执行参数
+     */
+    private void execute(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 {
+                    GroupContextHolder.setGroup(group.scene(), ObjectUtils.ifNull(value, Caps.NONE));
+                }
+            } catch (Exception e) {
+                log.error("Group handle failed: {}", group.scene(), e);
+            }
+        }
+        try {
+            stores = ObjectUtils.ifEmpty(stores, this.store::set, Collections::emptyMap);
+            stores.forEach((scene, group) -> GroupContextHolder.setGroup(scene, ObjectUtils.ifNull(group, Caps.NONE)));
+        } catch (Exception e) {
+            log.error("Group handle failed", e);
+        }
+    }
+
+    /**
+     * 执行分组逻辑
+     *
+     * @param invoking 分组时间点
+     * @param groups   分组注解列表
+     * @param args     分组执行参数
+     */
+    private void execute(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(copy, args));
+        }
+        if (ObjectUtils.notEmpty(sync)) {
+            this.execute(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();
+        }
+
+        // 获取匹配的分组注解列表
+        List<Group> matches = Stream.of(groups.value()).filter(this::matches)
+                .collect(Collectors.toCollection(Lists::newLinkedList));
+        if (ObjectUtils.isEmpty(matches)) {
+            return point.proceed();
+        }
+
+        // 获取已存储分组结果
+        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) {
+                    GroupContextHolder.setGroup(scene, group);
+                    matches.removeIf(annotation -> Objects.equals(annotation.scene(), scene));
+                }
+            });
+        } catch (Exception e) {
+            log.error("Group handle failed", e);
+            return point.proceed();
+        }
+
+        // 执行分组逻辑(方法调用前后)
+        if (ObjectUtils.notEmpty(matches)) {
+            this.execute(Invoking.REQUEST, matches, point.getArgs());
+        }
+        Object value = point.proceed();
+        if (ObjectUtils.notEmpty(matches)) {
+            this.execute(Invoking.RESPONSE, matches, value);
+        }
+        return value;
+    }
+}

+ 1 - 0
pom.xml

@@ -17,6 +17,7 @@
         <module>framework-dubbo</module>
         <module>framework-email</module>
         <module>framework-feign</module>
+        <module>framework-group</module>
         <module>framework-jpush</module>
         <module>framework-kafka</module>
         <module>framework-location</module>