一: 微信第三方平台回调消息是啥
当微信小程序或公众号运营者授权给第三方平台管理后,微信服务器会将粉丝发给公众号的消息,以及各种小程序和公众号上产生的事件推送(如小程序审核事件、自定义菜单点击事件、粉丝取消关注通知等),以回调的形式发给开发者服务器。开发者在接到上述消息和事件以后需要进行处理。下面是一个粉丝在关注了公众号以后,微信服务器向开发者服务器推送的事件消息。MsgType显示这是一个事件推送,Event标签显示这是一个关注事件。
实际生产环境中的第三方平台往往要同时对接小程序和公众号两方面业务。需要处理的消息类型包括对粉丝发送给公众号的消息进行回复,关注和取消关注事件,扫码以及小程序端的客服消息处理,审核事件结果处理等等。大概有10多项不同类型的消息需要分别处理。
二: 策略模式+工厂
写代码的时候我们当然最容易写起来的是if-else结构,就像下面这样的:
// 对微信回调消息解密
WxMpXmlMessage inMessage = callBackService.decryptCallback(appId, request);
// 处理菜单点击事件
if (StringUtils.equals(inMessage.getEvent(), WxConsts.MenuButtonType.CLICK)) {
//do something
} else if (StringUtils.equals(inMessage.getEvent(), WxConsts.MenuButtonType.VIEW)) {
//do something
} else if (StringUtils.equals(inMessage.getEvent(), WxConsts.MenuButtonType.LOCATION_SELECT)) {
//do something
} else {
//do something
}
但是实际生产环境中应该没有人这么写。原因很简单:if-else代码不好维护且太丑了。。当某一天微信新增了一种消息类型的时候,我们的代码也得在这个地方加一个判断的分支。代价较高。
下面将介绍一种稍微好一点的方式是工厂+策略模式。当然这样做仍然是不够完美的。我们先看代码。关于策略模式的介绍可以看:策略模式
首先,定义一个定义行为的接口。
/**
* 微信回调消息逻辑处理接口
*
* @author yanghaolei
* @date 2019-12-26 20:23
*/
public interface MsgStrategy {
/**
* 根据消息与推送事件类型处理
*
* @param wxMpXmlMessage 微信推送消息
*/
void handleMsg(WxMpXmlMessage wxMpXmlMessage);
}
其次是工厂类。这里用的是一个简单工厂,switch-case仍然存在维护性较差的问题。可以换成工厂方法,但是不会显著降低耦合度且会带来比较多的其他类层次设计。关于工厂的选择可以看:工厂模式
/**
* 微信回调消息逻辑处理接口 实例工厂
*
* @author yanghaolei
* @date 2019-12-27 14:54
*/
@Service
@AllArgsConstructor
public class MsgFactory {
private final MsgAuditSuccessHandler msgAuditSuccessHandler;
private final MsgAuditDelayHandler msgAuditDelayHandler;
private final MsgAuditFailHandler msgAuditFailHandler;
private static final Map<MaMsgCallbackEnum, MsgStrategy> MSG_STRATEGY_MAP = Maps.newConcurrentMap();
public MsgStrategy getMsgStrategyByType(MaMsgCallbackEnum maMsgCallbackEnum) {
MsgStrategy msgStrategy = MSG_STRATEGY_MAP.get(maMsgCallbackEnum);
if (ObjectUtil.isNull(msgStrategy)) {
synchronized (MSG_STRATEGY_MAP) {
msgStrategy = MSG_STRATEGY_MAP.get(maMsgCallbackEnum);
if (ObjectUtil.isNull(msgStrategy)) {
msgStrategy = getInstance(maMsgCallbackEnum);
MSG_STRATEGY_MAP.put(maMsgCallbackEnum, msgStrategy);
}
}
}
return msgStrategy;
}
private MsgStrategy getInstance(MaMsgCallbackEnum maMsgCallbackEnum) {
switch (maMsgCallbackEnum) {
case AUDIT_SUCCESS:
return msgAuditSuccessHandler;
case AUDIT_FAIL:
return msgAuditFailHandler;
case AUDIT_DELAY:
return msgAuditDelayHandler;
default:
return null;
}
}
}
这里一是使用concurrentHashMap来避免线程安全的问题。二是考虑到避免重复创建的问题使用了单例。问题看上去解决了。但是仔细想,实际上这里的switch-case和if-else其实并没有本质的区别,都是一种侵入业务的设计。所以有没有办法能够进行一个业务和业务配置的分离呢?
三:消息路由
我认为答案是有的。这个答案来自于一个封装的非常好的第三方包wxJava。我们先看我实现的一段代码。
/**
* 初始化消息路由
*
* @return router
*/
public WxOpenMessageRouter getMsgRouter() {
WxOpenMessageRouter messageRouter = wxOpenConfigService.getWxOpenMessageRouter();
// 客服会话管理事件
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();
// 菜单事件
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end();
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.MenuButtonType.VIEW).handler(menuHandler).end();
// 关注与取消关注事件
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler).end();
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
// 地理位置事件
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end();
// 扫码事件
messageRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SCAN).handler(scanHandler).end();
// 如果都不匹配 --> Default Handler
messageRouter.rule().async(false).handler(msgHandler).end();
return messageRouter;
}
我在微信配置类构建了一个消息路由router,进行统一的消息处理。rule负责匹配规则的构造。因为我们要对消息进行分别处理当然需要过滤。之后可以看到的msgType()、event()就是表明对哪个特定类型的微信事件进行规则的匹配。rule的构造是一个建造者模式(即set方法返回的不是void,而是builder对象)。我们来看下源码:
rule方法返回了对象。
public WxMpMessageRouterRule rule() {
return new WxMpMessageRouterRule(this);
}
继续点下去,rule对象很明显的可以看到builder。msgType和event正是我们之前看微信的回调消息的两个属性。
public class WxMpMessageRouterRule {
private final WxMpMessageRouter routerBuilder;
private boolean async = true;
private String fromUser;
private String msgType;
private String event;
private String eventKey;
private String eventKeyRegex;
private String content;
private String rContent;
private WxMpMessageMatcher matcher;
private boolean reEnter = false;
private List<WxMpMessageHandler> handlers = new ArrayList();
private List<WxMpMessageInterceptor> interceptors = new ArrayList();}
handler方法负责匹配之后的执行。这里也正是我们实现业务和配置分离的地方。将实际的业务逻辑灌注到handler中,这样以后对业务本身的调整我们只考虑handler这一层。当然匹配规则仍然是要加的。
public WxMpMessageRouterRule handler(WxMpMessageHandler handler) {
return this.handler(handler, (WxMpMessageHandler[])null);
}
public WxMpMessageRouterRule handler(WxMpMessageHandler handler, WxMpMessageHandler... otherHandlers) {
this.handlers.add(handler);
if (otherHandlers != null && otherHandlers.length > 0) {
WxMpMessageHandler[] var3 = otherHandlers;
int var4 = otherHandlers.length;
for(int var5 = 0; var5 < var4; ++var5) {
WxMpMessageHandler i = var3[var5];
this.handlers.add(i);
}
}
return this;
}
最后next(),end()负责向路由器维护的规则列表增加规则。实现了链式调用的关键。
private final List<WxMpMessageRouterRule>
public WxMpMessageRouter end() {
this.routerBuilder.getRules().add(this);
return this.routerBuilder;
}
public WxMpMessageRouter next() {
this.reEnter = true;
return this.end();
}
最后这样看下来,耦合度和代码维护的成本相较于if-else和之前的工厂模式都大幅下降。策略工厂这种模式之前在我们的老项目中也存在了很长时间,这次在微信模块的优化设计时候看到了消息路由这种更好的设计,也把它放进了实际生产环境中,效果看来也是不错的。可能有些地方还学习的不够透彻,抛砖引玉,希望大家提出宝贵的意见。