关于微信公众号开发,Wechat-Group/weixin-java-tools框架比较流行。写篇文章来分析下,该框架处理消息的基本流程。
以下源码均来自:
(weixin-java-tools框架源码)https://github.com/Wechat-Group/weixin-java-tools
(使用框架Demo)https://github.com/binarywang/weixin-java-mp-demo-springboot
从用户发送消息
-> 微信后台收到消息,再发送消息xml给开发者后台
-> 开发者后台收到消息xml,然后根据消息的路由操作,最后组装回复的消息xml
-> 发送给微信后台
-> 给用户
以Wx-toos框架源码为例子,理清楚从微信客户端发送一条消息的整个代码流程。(主要解析消息路由器的过程)
文章中所有源码均省略get、set方法
该过程主要依靠,WechatMpConfiguration、WechatMpProperties两个类完成。
WechatMpProperties负责读取配置信息
@ConfigurationProperties(prefix = "wechat.mp")
public class WechatMpProperties {
/**
* 设置微信公众号的appid
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
/**
* 设置微信公众号的token
*/
private String token;
/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;
...
}
WechatMpConfiguration配置类,比较重要。
@Configuration
@ConditionalOnClass(WxMpService.class)
@EnableConfigurationProperties(WechatMpProperties.class)
public class WechatMpConfiguration {
...
@Bean
@ConditionalOnMissingBean
public WxMpConfigStorage configStorage() {
WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
configStorage.setAppId(this.properties.getAppId());
configStorage.setSecret(this.properties.getSecret());
configStorage.setToken(this.properties.getToken());
configStorage.setAesKey(this.properties.getAesKey());
return configStorage;
}
@Bean
@ConditionalOnMissingBean
public WxMpService wxMpService(WxMpConfigStorage configStorage) {
WxMpService wxMpService = new me.chanjar.weixin.mp.api.impl.WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(configStorage);
return wxMpService;
}
@Bean
public WxMpMessageRouter router(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next();
......
// 扫码事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
.event(EventType.SCAN).handler(this.getScanHandler()).end();
// 默认
newRouter.rule().async(false).handler(this.getMsgHandler()).end();
return newRouter;
}
...
}
主要分析该类的三个方法。
首先在容器启动时,由于该类使用@Configuration注解,那么spring框架会按照由上至下的顺序自动调用configStorage()、wxMpService(…)、router(…)三个方法。之前的spring boot文章中讲过,@Bean注解在方法上,该方法返回的对象由spring容器管理。
主要分析WechatMpConfiguration类的router(…)方法。分析router(…)方法之前,先认识下WxMpMessageRouter、WxMpMessageRouterRule两个类。
rules
该路由器下的所有规则集合route(...)
处理微信消息的方法WxMpMessageRouter路由器中有多个路由规则rules,在route方法中会遍历其中的规则对消息进行处理。(后面会详细讲到)
routerBuilder
该规则所属的路由器msgType
该规则所匹配的消息类型event
该规则所匹配的事件类型async
是否异步执行,默认异步reEnter
是否进入到下一个规则,默认falsehandlers
该消息规则,所拥有的所有的消息处理器集合test(WxMpXmlMessage wxMessage)
根据wxMessage消息的msgType和event匹配消息规则handler(WxMpMessageHandler handler)
设置微信消息处理器end()
规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则next()
规则结束,但是消息还会进入其他规则@Bean
public WxMpMessageRouter router(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
...
// 点击菜单链接事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT)
.event(MenuButtonType.VIEW).handler(this.nullHandler).end();
...
}
直接分析上面点击菜单链接事件代码:
-> newRouter.rule()
返回一个该路由器下的新的WxMpMessageRouterRule规则类。
-> .async(false)
设置该规则为同步
-> .msgType(XmlMsgType.EVENT)
设置该规则匹配的消息类型为event
-> .event(MenuButtonType.VIEW)
设置该规则匹配的事件类型为VIEW
-> .handler(this.nullHandler)
设置该规则的消息处理器
-> .end()
匹配该规则后,不会匹配其他规则
router(…)方法中其他部分代码,同上。
spring boot配置加载完成,启动后。消息到达controller
,会调用WxMpMessageRouter类中的router(...)方法
处理微信消息。
/**
* 处理微信消息
*/
public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage, final Map<String, Object> context) {
if (isMsgDuplicated(wxMessage)) {
// 如果是重复消息,那么就不做处理
return null;
}
//满足匹配条件的规则
final List<WxMpMessageRouterRule> matchRules = new ArrayList<>();
// 收集匹配的规则
for (final WxMpMessageRouterRule rule : this.rules) {
if (rule.test(wxMessage)) {//test方法用于判断消息类型是否与该规则匹配,并排除大小写
matchRules.add(rule);
if (!rule.isReEnter()) {//判断是否需要匹配下一个规则
break;
}
}
}
if (matchRules.size() == 0) {
return null;
}
WxMpXmlOutMessage res = null;
final List<Future<?>> futures = new ArrayList<>();
for (final WxMpMessageRouterRule rule : matchRules) {
if (rule.isAsync()) {
futures.add(
//submit()方法接收Runable,会把线程执行的结果返回出来
this.executorService.submit(new Runnable() {
@Override
public void run() {
rule.service(wxMessage, context, WxMpMessageRouter.this.wxMpService, WxMpMessageRouter.this.sessionManager, WxMpMessageRouter.this.exceptionHandler);
}
})
);
} else {
// 返回最后一个非异步的rule的执行结果
res = rule.service(wxMessage, context, this.wxMpService, this.sessionManager, this.exceptionHandler);
// 在同步操作结束,session访问结束
this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
sessionEndAccess(wxMessage);
}
}
if (futures.size() > 0) {
this.executorService.submit(new Runnable() {
@Override
public void run() {
for (Future<?> future : futures) {
try {
future.get();
WxMpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
// 异步操作结束,session访问结束
sessionEndAccess(wxMessage);
} catch (InterruptedException | ExecutionException e) {
WxMpMessageRouter.this.log.error("Error happened when wait task finish", e);
}
}
}
});
}
return res;
}
/**
* 处理微信推送过来的消息
*
* @param wxMessage
* @return true 代表继续执行别的router,false 代表停止执行别的router
*/
protected WxMpXmlOutMessage service(WxMpXmlMessage wxMessage,
Map<String, Object> context,
WxMpService wxMpService,
WxSessionManager sessionManager,
WxErrorExceptionHandler exceptionHandler) {
if (context == null) {
context = new HashMap<>();
}
try {
// 如果拦截器不通过
for (WxMpMessageInterceptor interceptor : this.interceptors) {
if (!interceptor.intercept(wxMessage, context, wxMpService, sessionManager)) {
return null;
}
}
// 交给handler处理
WxMpXmlOutMessage res = null;
for (WxMpMessageHandler handler : this.handlers) {
// 返回最后handler的结果
if (handler == null) {
continue;
}
res = handler.handle(wxMessage, context, wxMpService, sessionManager);
}
return res;
} catch (WxErrorException e) {
exceptionHandler.handle(e);
}
return null;
}
方法执行流程如下:
WxMpMessageRouter类中的router(...)方法
-> 会调用WxMpMessageRouterRule类中的service(...)方法处理消息
-> 最终会调用XxxHandler类中的handler(...)方法
因此最终,如何处理微信消息,就在自定义的XxxHandler类中的handler(…)方法中去实现