mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-14 06:09:03 +08:00
feat(pigx-mp-platform): 重构 WxMpInitConfigRunner 类,添加详细文档注释
- 优化消息路由规则配置,支持多种微信事件处理 - 增加 Redis 监听机制,支持运行时动态重新加载配置 - 完善多租户支持,缓存 appid 与租户的映射关系
This commit is contained in:
1 parent
7e78541763
commit
c19c0f1ed4
1 file changed
+229
-145
+229
-145
@@ -33,66 +33,131 @@ import java.util.stream.Collectors;
|
|||||||
import static me.chanjar.weixin.common.api.WxConsts.*;
|
import static me.chanjar.weixin.common.api.WxConsts.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 微信公众号初始化配置类
|
||||||
|
* <p>
|
||||||
|
* 功能说明:
|
||||||
|
* 1. 应用启动时自动加载所有租户的微信公众号配置
|
||||||
|
* 2. 为每个公众号创建独立的 WxMpService 和消息路由器
|
||||||
|
* 3. 监听 Redis 消息,支持运行时动态重新加载配置
|
||||||
|
* <p>
|
||||||
|
* 核心数据结构:
|
||||||
|
* - routers: appid -> WxMpMessageRouter 映射,用于消息路由分发
|
||||||
|
* - mpServices: appid -> WxMpService 映射,用于调用微信API
|
||||||
|
* - tenants: appid -> tenantId 映射,用于多租户隔离
|
||||||
|
*
|
||||||
* @author Binary Wang
|
* @author Binary Wang
|
||||||
* @author lengleng
|
* @author lengleng
|
||||||
* <p>
|
|
||||||
* 微信公众号基础配置,初始化配置
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class WxMpInitConfigRunner {
|
public class WxMpInitConfigRunner {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存 appid-router 的对应关系
|
* 消息路由器缓存
|
||||||
*/
|
* key: 公众号 appid
|
||||||
private static Map<String, WxMpMessageRouter> routers = Maps.newHashMap();
|
* value: 对应的消息路由器,负责将不同类型的消息分发到对应的处理器
|
||||||
|
*/
|
||||||
|
private static Map<String, WxMpMessageRouter> routers = Maps.newHashMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存 appid-mpservice 的对应关系
|
* 微信公众号服务缓存
|
||||||
*/
|
* key: 公众号 appid
|
||||||
private static Map<String, WxMpService> mpServices = Maps.newHashMap();
|
* value: 微信公众号服务实例,封装了微信API调用能力
|
||||||
|
*/
|
||||||
|
private static Map<String, WxMpService> mpServices = Maps.newHashMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存 appid-tenantId 的对应关系
|
* 租户映射缓存
|
||||||
*/
|
* key: 公众号 appid
|
||||||
private static final Map<String, Long> tenants = Maps.newHashMap();
|
* value: 租户ID,用于多租户数据隔离
|
||||||
|
*/
|
||||||
|
private static final Map<String, Long> 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<WxAccount> accountList = new ArrayList<>();
|
* <p>
|
||||||
// @formatter:off
|
* 执行流程:
|
||||||
|
* 1. 获取系统中所有租户
|
||||||
|
* 2. 遍历每个租户,查询其配置的公众号列表
|
||||||
|
* 3. 为每个公众号创建 WxMpService 和消息路由器
|
||||||
|
* 4. 缓存 appid 与租户的映射关系
|
||||||
|
*/
|
||||||
|
public void initServices() {
|
||||||
|
// 获取全部租户 遍历所有租户对应的公众号列表
|
||||||
|
List<WxAccount> accountList = new ArrayList<>();
|
||||||
|
// @formatter:off
|
||||||
RetOps.of(tenantService.list())
|
RetOps.of(tenantService.list())
|
||||||
.getData()
|
.getData()
|
||||||
.orElseGet(Collections::emptyList)
|
.orElseGet(Collections::emptyList)
|
||||||
@@ -101,133 +166,152 @@ public class WxMpInitConfigRunner {
|
|||||||
);
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
mpServices = accountList.stream().map(a -> {
|
mpServices = accountList.stream().map(a -> {
|
||||||
WxMpInRedisConfigStorage configStorage = new WxMpInRedisConfigStorage();
|
WxMpInRedisConfigStorage configStorage = new WxMpInRedisConfigStorage();
|
||||||
configStorage.setAppId(a.getAppid());
|
configStorage.setAppId(a.getAppid());
|
||||||
configStorage.setSecret(a.getAppsecret());
|
configStorage.setSecret(a.getAppsecret());
|
||||||
configStorage.setToken(a.getToken());
|
configStorage.setToken(a.getToken());
|
||||||
configStorage.setAesKey(a.getAeskey());
|
configStorage.setAesKey(a.getAeskey());
|
||||||
|
|
||||||
WxMpService service = new WxMpServiceImpl();
|
WxMpService service = new WxMpServiceImpl();
|
||||||
service.setWxMpConfigStorage(configStorage);
|
service.setWxMpConfigStorage(configStorage);
|
||||||
routers.put(a.getAppid(), this.newRouter(service));
|
routers.put(a.getAppid(), this.newRouter(service));
|
||||||
tenants.put(a.getAppid(), a.getTenantId());
|
tenants.put(a.getAppid(), a.getTenantId());
|
||||||
return service;
|
return service;
|
||||||
}).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (existing, replace) -> existing));
|
}).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (existing, replace) -> existing));
|
||||||
}
|
}
|
||||||
|
|
||||||
private WxMpMessageRouter newRouter(WxMpService wxMpService) {
|
/**
|
||||||
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
|
* 创建微信消息路由器
|
||||||
|
* <p>
|
||||||
|
* 配置消息路由规则,将不同类型的消息/事件分发到对应的处理器:
|
||||||
|
* - 客服会话事件 -> kfSessionHandler
|
||||||
|
* - 门店审核事件 -> storeCheckNotifyHandler
|
||||||
|
* - 菜单点击事件 -> menuHandler
|
||||||
|
* - 关注/取消关注事件 -> subscribeHandler/unsubscribeHandler
|
||||||
|
* - 地理位置事件 -> locationHandler
|
||||||
|
* - 扫码事件 -> scanHandler
|
||||||
|
* - 其他消息 -> msgHandler(默认处理器)
|
||||||
|
*
|
||||||
|
* @param wxMpService 微信公众号服务实例
|
||||||
|
* @return 配置完成的消息路由器
|
||||||
|
*/
|
||||||
|
private WxMpMessageRouter newRouter(WxMpService wxMpService) {
|
||||||
|
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
|
||||||
|
|
||||||
// 记录所有事件的日志 (异步执行) #5637 大数据量瓶颈
|
// 记录所有事件的日志 (异步执行) #5637 大数据量瓶颈
|
||||||
newRouter.rule().handler(this.logHandler).next();
|
newRouter.rule().handler(this.logHandler).next();
|
||||||
|
|
||||||
// 接收客服会话管理事件
|
// 接收客服会话管理事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
|
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
|
||||||
.handler(this.kfSessionHandler)
|
.handler(this.kfSessionHandler)
|
||||||
.end();
|
.end();
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
|
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
|
||||||
.handler(this.kfSessionHandler)
|
.handler(this.kfSessionHandler)
|
||||||
.end();
|
.end();
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
|
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
|
||||||
.handler(this.kfSessionHandler)
|
.handler(this.kfSessionHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 门店审核事件
|
// 门店审核事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(WxMpEventConstants.POI_CHECK_NOTIFY)
|
.event(WxMpEventConstants.POI_CHECK_NOTIFY)
|
||||||
.handler(this.storeCheckNotifyHandler)
|
.handler(this.storeCheckNotifyHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 自定义菜单事件
|
// 自定义菜单事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(MenuButtonType.CLICK)
|
.event(MenuButtonType.CLICK)
|
||||||
.handler(this.menuHandler)
|
.handler(this.menuHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 点击菜单连接事件
|
// 点击菜单连接事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(MenuButtonType.VIEW)
|
.event(MenuButtonType.VIEW)
|
||||||
.handler(this.nullHandler)
|
.handler(this.nullHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 关注事件
|
// 关注事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(EventType.SUBSCRIBE)
|
.event(EventType.SUBSCRIBE)
|
||||||
.handler(this.subscribeHandler)
|
.handler(this.subscribeHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 取消关注事件
|
// 取消关注事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(EventType.UNSUBSCRIBE)
|
.event(EventType.UNSUBSCRIBE)
|
||||||
.handler(this.unsubscribeHandler)
|
.handler(this.unsubscribeHandler)
|
||||||
.end();
|
.end();
|
||||||
|
|
||||||
// 上报地理位置事件
|
// 上报地理位置事件
|
||||||
newRouter.rule()
|
newRouter.rule()
|
||||||
.async(false)
|
.async(false)
|
||||||
.msgType(XmlMsgType.EVENT)
|
.msgType(XmlMsgType.EVENT)
|
||||||
.event(EventType.LOCATION)
|
.event(EventType.LOCATION)
|
||||||
.handler(this.locationHandler)
|
.handler(this.locationHandler)
|
||||||
.end();
|
.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,重新加载配置
|
* 配置 Redis 消息监听容器
|
||||||
* @param redisConnectionFactory redis 配置
|
* <p>
|
||||||
* @return
|
* 监听 mp_redis_reload_topic 频道,当收到消息时重新加载公众号配置
|
||||||
*/
|
* 适用场景:后台修改公众号配置后,通过发布 Redis 消息触发配置热更新
|
||||||
@Bean
|
*
|
||||||
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
|
* @param redisConnectionFactory Redis 连接工厂
|
||||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
* @return Redis 消息监听容器
|
||||||
container.setConnectionFactory(redisConnectionFactory);
|
*/
|
||||||
container.addMessageListener((message, bytes) -> {
|
@Bean
|
||||||
log.warn("接收到重新加载公众号配置事件");
|
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
|
||||||
initServices();
|
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||||
}, new ChannelTopic(CacheConstants.MP_REDIS_RELOAD_TOPIC));
|
container.setConnectionFactory(redisConnectionFactory);
|
||||||
return container;
|
container.addMessageListener((message, bytes) -> {
|
||||||
}
|
log.warn("接收到重新加载公众号配置事件");
|
||||||
|
initServices();
|
||||||
|
}, new ChannelTopic(CacheConstants.MP_REDIS_RELOAD_TOPIC));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, Long> getTenants() {
|
public static Map<String, Long> getTenants() {
|
||||||
return tenants;
|
return tenants;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, WxMpMessageRouter> getRouters() {
|
public static Map<String, WxMpMessageRouter> getRouters() {
|
||||||
return routers;
|
return routers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, WxMpService> getMpServices() {
|
public static Map<String, WxMpService> getMpServices() {
|
||||||
return mpServices;
|
return mpServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user