摘要:本文将带你了解微信接入之架构的核心价值,希望本文对大家学微信有所帮助。
降低耦合,轻松分离业务
我们希望微信接入细节与业务功能解耦,不要引入过多的框架,还要能轻松组装。于是我们有了如下架构的设计:
派遣器需要接收HttpServletRequest对象的两部分参数,在最初实现的版本中,入参就是request,后来发现这样做就必然引入Servlet-API包,使得架构变得臃肿。于是决定瘦身,将入参变为更加通用的Map<String, String[]> params和String requestBody,request对象中调用getParameterMap()得到的是一个标准的key-values[]映射,而requestBody字符串可以通过读取request对象的输入流来获取。这样就将接口轻松与J2EE进行了解耦。回调接口的接入也很方便,只需要写固定的代码即可:
@Controllerpublic class CallbackController extends BaseController {
@Autowired
private WeChatCallbackDispatcher weChatCallbackDispatcher;
@RequestMapping(value = "/msg")
@ResponseBody
@SuppressWarnings("unchecked")
public String msg(HttpServletRequest request, HttpServletResponse response) throws WeChatCallbackException, IOException{
return weChatCallbackDispatcher.dispatch(request.getParameterMap(), HttpUtil.readStream(request.getInputStream(), "UTF-8"));
}
}
派遣器模式,专注于你要做的
WeChatCallbackDispatcher中集成了签名验证、安全模式加解密、类型判断和消息派遣的重要功能。受Servlet的dispatch模式启发,当接收到请求体的XML时,首先对其转换为刚好能判别消息类型的最小化对象:TypeAnalyzingBean(只包含MsgType和Event字段),然后通过预定义的枚举类型进行类型分析:
TypeAnalyzingBean baseMsg = XMLUtil.fromXML(callbackXML, TypeAnalyzingBean.class);
MsgType msgType = MsgType.getByType(baseMsg.getMsgType());
· 1
· 2
private BaseMsg dispatchMsg(MsgType msgType, TypeAnalyzingBean bean, String xml) throws WeChatCallbackException {
BaseMsg retVal = null;
try {
switch (msgType) {
case text:
TextMsg textMsg = XMLUtil.fromXML(xml, TextMsg.class);
retVal = textMsgDispatchService.dispatchMsg(textMsg);
break;
case voice:
VoiceMsg voiceMsg = XMLUtil.fromXML(xml, VoiceMsg.class);
retVal = voiceMsgDispatchService.dispatchMsg(voiceMsg);
break;
case shortvideo:
ShortVideoMsg shortVideoMsg = XMLUtil.fromXML(xml, ShortVideoMsg.class);
retVal = shortVideoMsgDispatchService.dispatchMsg(shortVideoMsg);
break;
case event:
EventType eventType = EventType.getByType(bean.getEvent());
retVal = dispatchEvent(eventType, bean, xml);
break;
case unknown:
default:
if (logger.isDebugEnabled()){
logger.debug("other type of msg:{}", bean.getMsgType());
}
break;
}
} catch (IOException e) {
throw new WeChatCallbackException(CallbackErrEnum.SysErr, e);
}
return retVal;
}
在dispatchMsg代码中可以看出,每一种DispatchService只处理对应的消息,相互没有关联。换句话说,作为开发者,只需要关心接收到特定类型的消息之后去做什么,而不用关系消息是怎样传递到被动回调接口的。
框架中针对每一种类型的消息都提供了一个默认的DispatchService,如果你将日志级别设置为Debug,就会在日志中看到一条对应类型消息的调试信息,没有其他业务逻辑。这也就保证了当收到开发者不关心的消息类型时的空指向异常(NullPointerException)问题。
public class WeChatCallbackDispatcher {
/** 文本消息处理服务 */
@Autowired(required = false)
private TextMsgDispatchService textMsgDispatchService;
/** 语音消息处理服务 */
@Autowired(required = false)
private VoiceMsgDispatchService voiceMsgDispatchService;
/** 短视频消息处理服务 */
@Autowired(required = false)
private ShortVideoMsgDispatchService shortVideoMsgDispatchService;
/** 关注事件处理服务 */
@Autowired(required = false)
private SubscribeEventDispatchService subscribeEventDispatchService;
/** 关注事件处理服务 */
@Autowired(required = false)
private UnsubscribeEventDispatchService unsubscribeEventDispatchService;
/** 定位事件处理服务 */
@Autowired(required = false)
private LocationEventDispatchService locationEventDispatchService;
/** 自定义菜单事件处理服务 */
@Autowired(required = false)
private CustomMenuEventDispatchService customMenuEventDispatchService;
public WeChatCallbackDispatcher(){
logger.debug("initializing WeChat callback dispatcher");
//回调消息处理
textMsgDispatchService = new DefaultTextMsgDispatchServiceImpl();
voiceMsgDispatchService = new DefaultVoiceMsgDispatchServiceImpl();
shortVideoMsgDispatchService = new DefaultShortVideoMsgDispatchServiceImpl();
//回调事件处理
subscribeEventDispatchService = new DefaultSubscribeEventDispatchServiceImpl();
unsubscribeEventDispatchService = new DefaultUnsubscribeEventDispatchServiceImpl();
locationEventDispatchService = new DefaultLocationEventDispatchServiceImpl();
customMenuEventDispatchService = new DefaultCustomMenuEventDispatchServiceImpl();
}
}
public class DefaultTextMsgDispatchServiceImpl extends WeChatCallbackDispatchService implements TextMsgDispatchService { @Override public BaseMsg dispatchMsg(TextMsg msg) { if (logger.isDebugEnabled()){ logger.debug("receive a text msg, msg id:{}, from:{}, to:{}, content:{}", msg.getMsgId(), msg.getFromUserName(), msg.getToUserName(), msg.getContent()); } return null; } }下面代码实现了一个自动报时功能,当用户发送给公众号任意消息,公众号返回用户一条文本消息,内容为当前时间:
@Servicepublic class TextMsgDispatchServiceImpl extends WeChatCallbackDispatchService implements TextMsgDispatchService {
private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public BaseMsg dispatchMsg(TextMsg msg) {
logger.info("本机微信账号:{}", msg.getToUserName());
logger.info("收到文本消息,From:{}, To:{}, 消息内容:{}", msg.getFromUserName(), msg.getToUserName(), msg.getContent());
TextMsg retMsg = new TextMsg();
retMsg.setFromUserName(msg.getToUserName());
retMsg.setToUserName(msg.getFromUserName());
retMsg.setCreateTime(TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS));
retMsg.setMsgType(MsgType.text.getType());
retMsg.setContent(String.format("当前时间:%s", format.format(new Date())));
return retMsg;
}
}
关于被动回调接口的调试技术
在官方文档中给出了一种调试的手段——人工造数据(文档地址:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=消息接口调试&form=文本消息),但是该手段比较繁琐,很多字段都要手工填写,费时费力。下面给出一种通过真实微信客户端进行调试的方法。
我想要个公网IP
如果你是一个土豪,在家里直接通过ADSL或者光纤猫接入互联网,请忽略本节。很多时候我们接入互联网的都是通过路由器的转发,这样就分成了外网和路由器的内网。内网主机无法直接被外网连接,而微信被动回调接口很显然需要一个在公网就能直接连到的主机来提供服务。如何将本地的Web应用在互联网访问呢?
在家中通过路由器接入互联网。这种模式下一般可以自己设置路由器的DMZ主机,当外网访问你当前获得的公网IP时,特定的端口会被直接转发到你的内网设备上,类似于模拟一个在公网上的设备。由于这种手段没有什么技术难度,并且家中的接入方式IP地址经常会发生变化,不利于测试环境的稳定,故而本文不对其进行描述;
在公司或者复杂内网环境接入互联网。这种模式下一般人不可能有权限去更改公司的路由器配置,而且内网数据也是经过若干网关才转发到公网,单设置一台路由器很难解决问题。已经过实践验证的方式是使用云主机+VPN
其实选择云主机的主要目的是拿到一个比较稳定的公网IP,它和托管在IDC机房的设备一样,都可以选择拥有一个静态的IP地址。在你承租时间内,这个IP地址就不会发生变化。调试微信回调接口使用什么样的云主机呢?
CPU,由于调试时计算集中在我们本地的项目,云主机计算量并不大,选择单核处理器即可,速度没有太高的要求;
内存,在这台云主机上只运行nginx和vpn服务器,内存开销不大,1GB内存足矣;
硬盘空间,除正常的系统占用和软件安装外,不需要太多的存储空间,若不是提供商最少提供40GB空间,笔者都想选择10GB或者20GB的硬盘;
带宽,带宽比较重要,在调试的时候需要有稳定的网络连接,从经济实用度来分析,笔者选择了峰值100Mbps,按使用流量付费的带宽方案。其实调试的过程中流量真的很少,几元的流量费能让你用到很开心;
操作系统,这个看个人喜好,笔者喜欢CentOS,于是安装了CentOS 6.5 64位系统。
按照一个月的调试时间来估算,上述配置的云主机购买成本大约是50~90元左右,还是比较便宜的。
把自己的调试代码接入公网
云主机部署完成后即可安装nginx,它主要用来做反向代理。一般的反向代理都是指向本地后端服务器或者局域网内其他服务器实例。nginx配置好之后外网即可通过IP地址直接访问nginx服务器,如果你能够看到nginx的默认欢迎页,恭喜你,已经完成了第一步。
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之微信频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号