diff --git a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/config/WxMpInitConfigRunner.java b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/config/WxMpInitConfigRunner.java index 71b768c60..458e5334a 100644 --- a/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/config/WxMpInitConfigRunner.java +++ b/pigx-visual/pigx-mp-platform/src/main/java/com/pig4cloud/pigx/mp/config/WxMpInitConfigRunner.java @@ -33,66 +33,131 @@ import java.util.stream.Collectors; import static me.chanjar.weixin.common.api.WxConsts.*; /** + * 微信公众号初始化配置类 + *

+ * 功能说明: + * 1. 应用启动时自动加载所有租户的微信公众号配置 + * 2. 为每个公众号创建独立的 WxMpService 和消息路由器 + * 3. 监听 Redis 消息,支持运行时动态重新加载配置 + *

+ * 核心数据结构: + * - routers: appid -> WxMpMessageRouter 映射,用于消息路由分发 + * - mpServices: appid -> WxMpService 映射,用于调用微信API + * - tenants: appid -> tenantId 映射,用于多租户隔离 + * * @author Binary Wang * @author lengleng - *

- * 微信公众号基础配置,初始化配置 */ @Slf4j @Configuration @RequiredArgsConstructor public class WxMpInitConfigRunner { - /** - * 保存 appid-router 的对应关系 - */ - private static Map routers = Maps.newHashMap(); + /** + * 消息路由器缓存 + * key: 公众号 appid + * value: 对应的消息路由器,负责将不同类型的消息分发到对应的处理器 + */ + private static Map routers = Maps.newHashMap(); - /** - * 保存 appid-mpservice 的对应关系 - */ - private static Map mpServices = Maps.newHashMap(); + /** + * 微信公众号服务缓存 + * key: 公众号 appid + * value: 微信公众号服务实例,封装了微信API调用能力 + */ + private static Map mpServices = Maps.newHashMap(); - /** - * 保存 appid-tenantId 的对应关系 - */ - private static final Map tenants = Maps.newHashMap(); + /** + * 租户映射缓存 + * key: 公众号 appid + * value: 租户ID,用于多租户数据隔离 + */ + private static final Map tenants = Maps.newHashMap(); - private final RemoteTenantService tenantService; + /** + * 远程租户服务,用于获取所有租户信息 + */ + private final RemoteTenantService tenantService; - private final WxAccountService accountService; + /** + * 微信公众号账户服务,用于查询公众号配置 + */ + private final WxAccountService accountService; - private final LogHandler logHandler; + /** + * 日志处理器,记录所有消息事件日志(异步执行) + */ + private final LogHandler logHandler; - private final NullHandler nullHandler; + /** + * 空处理器,用于不需要特殊处理的事件(如点击菜单链接) + */ + private final NullHandler nullHandler; - private final KfSessionHandler kfSessionHandler; + /** + * 客服会话处理器,处理客服会话创建/关闭/转接事件 + */ + private final KfSessionHandler kfSessionHandler; - private final StoreCheckNotifyHandler storeCheckNotifyHandler; + /** + * 门店审核通知处理器,处理门店审核结果事件 + */ + private final StoreCheckNotifyHandler storeCheckNotifyHandler; - private final LocationHandler locationHandler; + /** + * 地理位置处理器,处理用户上报的地理位置信息 + */ + private final LocationHandler locationHandler; - private final MenuHandler menuHandler; + /** + * 菜单点击处理器,处理自定义菜单点击事件 + */ + private final MenuHandler menuHandler; - private final MsgHandler msgHandler; + /** + * 消息处理器,处理用户发送的普通消息(默认处理器) + */ + private final MsgHandler msgHandler; - private final UnsubscribeHandler unsubscribeHandler; + /** + * 取消关注处理器,处理用户取消关注公众号事件 + */ + private final UnsubscribeHandler unsubscribeHandler; - private final SubscribeHandler subscribeHandler; + /** + * 关注处理器,处理用户关注公众号事件 + */ + private final SubscribeHandler subscribeHandler; - private final ScanHandler scanHandler; + /** + * 扫码处理器,处理用户扫描二维码事件 + */ + private final ScanHandler scanHandler; - @Async - @Order - @EventListener({ ApplicationReadyEvent.class }) - public void WebServerInit() { - this.initServices(); - } + /** + * 应用启动完成事件监听 + * 异步初始化所有租户的微信公众号服务 + */ + @Async + @Order + @EventListener({ApplicationReadyEvent.class}) + public void WebServerInit() { + this.initServices(); + } - public void initServices() { - // 获取全部租户 遍历所有租户对应的公众号列表 - List accountList = new ArrayList<>(); - // @formatter:off + /** + * 初始化微信公众号服务 + *

+ * 执行流程: + * 1. 获取系统中所有租户 + * 2. 遍历每个租户,查询其配置的公众号列表 + * 3. 为每个公众号创建 WxMpService 和消息路由器 + * 4. 缓存 appid 与租户的映射关系 + */ + public void initServices() { + // 获取全部租户 遍历所有租户对应的公众号列表 + List accountList = new ArrayList<>(); + // @formatter:off RetOps.of(tenantService.list()) .getData() .orElseGet(Collections::emptyList) @@ -101,133 +166,152 @@ public class WxMpInitConfigRunner { ); // @formatter:on - mpServices = accountList.stream().map(a -> { + mpServices = accountList.stream().map(a -> { WxMpInRedisConfigStorage configStorage = new WxMpInRedisConfigStorage(); - configStorage.setAppId(a.getAppid()); - configStorage.setSecret(a.getAppsecret()); - configStorage.setToken(a.getToken()); - configStorage.setAesKey(a.getAeskey()); + configStorage.setAppId(a.getAppid()); + configStorage.setSecret(a.getAppsecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAeskey()); - WxMpService service = new WxMpServiceImpl(); - service.setWxMpConfigStorage(configStorage); - routers.put(a.getAppid(), this.newRouter(service)); - tenants.put(a.getAppid(), a.getTenantId()); - return service; - }).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (existing, replace) -> existing)); - } + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(configStorage); + routers.put(a.getAppid(), this.newRouter(service)); + tenants.put(a.getAppid(), a.getTenantId()); + return service; + }).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (existing, replace) -> existing)); + } - private WxMpMessageRouter newRouter(WxMpService wxMpService) { - final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + /** + * 创建微信消息路由器 + *

+ * 配置消息路由规则,将不同类型的消息/事件分发到对应的处理器: + * - 客服会话事件 -> kfSessionHandler + * - 门店审核事件 -> storeCheckNotifyHandler + * - 菜单点击事件 -> menuHandler + * - 关注/取消关注事件 -> subscribeHandler/unsubscribeHandler + * - 地理位置事件 -> locationHandler + * - 扫码事件 -> scanHandler + * - 其他消息 -> msgHandler(默认处理器) + * + * @param wxMpService 微信公众号服务实例 + * @return 配置完成的消息路由器 + */ + private WxMpMessageRouter newRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); - // 记录所有事件的日志 (异步执行) #5637 大数据量瓶颈 - newRouter.rule().handler(this.logHandler).next(); + // 记录所有事件的日志 (异步执行) #5637 大数据量瓶颈 + newRouter.rule().handler(this.logHandler).next(); - // 接收客服会话管理事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) - .handler(this.kfSessionHandler) - .end(); - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) - .handler(this.kfSessionHandler) - .end(); - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) - .handler(this.kfSessionHandler) - .end(); + // 接收客服会话管理事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION) + .handler(this.kfSessionHandler) + .end(); + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION) + .handler(this.kfSessionHandler) + .end(); + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION) + .handler(this.kfSessionHandler) + .end(); - // 门店审核事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(WxMpEventConstants.POI_CHECK_NOTIFY) - .handler(this.storeCheckNotifyHandler) - .end(); + // 门店审核事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(WxMpEventConstants.POI_CHECK_NOTIFY) + .handler(this.storeCheckNotifyHandler) + .end(); - // 自定义菜单事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(MenuButtonType.CLICK) - .handler(this.menuHandler) - .end(); + // 自定义菜单事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(MenuButtonType.CLICK) + .handler(this.menuHandler) + .end(); - // 点击菜单连接事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(MenuButtonType.VIEW) - .handler(this.nullHandler) - .end(); + // 点击菜单连接事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(MenuButtonType.VIEW) + .handler(this.nullHandler) + .end(); - // 关注事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(EventType.SUBSCRIBE) - .handler(this.subscribeHandler) - .end(); + // 关注事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(EventType.SUBSCRIBE) + .handler(this.subscribeHandler) + .end(); - // 取消关注事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(EventType.UNSUBSCRIBE) - .handler(this.unsubscribeHandler) - .end(); + // 取消关注事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(EventType.UNSUBSCRIBE) + .handler(this.unsubscribeHandler) + .end(); - // 上报地理位置事件 - newRouter.rule() - .async(false) - .msgType(XmlMsgType.EVENT) - .event(EventType.LOCATION) - .handler(this.locationHandler) - .end(); + // 上报地理位置事件 + newRouter.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(EventType.LOCATION) + .handler(this.locationHandler) + .end(); - // 接收地理位置消息 - newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end(); + // 接收地理位置消息 + newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end(); - // 扫码事件 - newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); + // 扫码事件 + newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); - // 默认 - newRouter.rule().async(false).handler(this.msgHandler).end(); + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); - return newRouter; - } + return newRouter; + } - /** - * redis 监听配置,监听 mp_redis_reload_topic,重新加载配置 - * @param redisConnectionFactory redis 配置 - * @return - */ - @Bean - public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(redisConnectionFactory); - container.addMessageListener((message, bytes) -> { - log.warn("接收到重新加载公众号配置事件"); - initServices(); - }, new ChannelTopic(CacheConstants.MP_REDIS_RELOAD_TOPIC)); - return container; - } + /** + * 配置 Redis 消息监听容器 + *

+ * 监听 mp_redis_reload_topic 频道,当收到消息时重新加载公众号配置 + * 适用场景:后台修改公众号配置后,通过发布 Redis 消息触发配置热更新 + * + * @param redisConnectionFactory Redis 连接工厂 + * @return Redis 消息监听容器 + */ + @Bean + public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(redisConnectionFactory); + container.addMessageListener((message, bytes) -> { + log.warn("接收到重新加载公众号配置事件"); + initServices(); + }, new ChannelTopic(CacheConstants.MP_REDIS_RELOAD_TOPIC)); + return container; + } - public static Map getTenants() { - return tenants; - } + public static Map getTenants() { + return tenants; + } - public static Map getRouters() { - return routers; - } + public static Map getRouters() { + return routers; + } - public static Map getMpServices() { - return mpServices; - } + public static Map getMpServices() { + return mpServices; + } }