1 概述
数通畅联微信公众号申请之后,由于要满足提供网站推广、功能演示、以及公司内部移动办公三方面的需求,所以把最初的订阅号更改为服务号,同时做了实名认证,这样就可以获取微信公众平台绝大部分接口,在完成数通畅联公众号相关功能过程中参考网上大量资料,期间封装AEAI WX微信扩展框架托管于开源中国社区
在这里感谢特别柳峰对微信公众号知识的普及和推广,在学习微信公众号的过程让笔者少走许多弯路。
AEAI WX微信扩展框架是基于Java封装的微信公众号二次开发框架,基于该框架可以快速接入微信,实现自定义菜单创建、信息按规则自动回复、集成企业的线上系统(HR、CRM、微店、网站等)、同时可以整合集成互联网开放资源(如:百度地图、天气预报、热映电影等)。
2 预期读者
- 数通畅联内部技术人员
- 数通畅联合作伙伴技术人员
- 广大微信公众号开发技术人员
3 技能要求
- 了解Eclipse基本用法;
- 了解Servlet、Ajax、html5等Java Web相关知识;
- 了解XML、JSON、REST、HttpClient、xStream相关知识;
- 了解云服务器、网站域名相关配置;
- 了解微信公众号相关概念(公众号、服务号、订阅号、企业号)和交互机制。
4 名词介绍
- OpenAPI:引百度百科。Open API即开放API,也称开放平台。所谓的开放API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的API,所开放的API就被称作OpenAPI。
- 微信JS-SDK:微信JS版接口,通过JS方式来调用微信接口。而微信Open公众号API,则是采用类似REST调用方式,Java一般采用HttpClient来调用。
5 总体介绍
5.1效果演示
5.2功能架构
AEAI WX的框架包括两个工程aeaiwx_core(java工程)、aeaiwx(java web工程),架构框图如下所示:
核心类库代码结构如下图所示:
各个Java类的具体用途,如下表所示:
包名 | 类名 | 用途 |
com.agileai.weixin.core | MessageBrokerServlet | 接收、解析、转发消息 |
MessageEventHandler | 具体处理消息 | |
com.agileai.weixin.model | Constans | 所有的常量定义集合 |
BaseMessage | 消息父类 | |
NewsMessage | 新闻消息类,引用Article类 | |
Article | 文章实体类 | |
TextMessage | 文本消息类 | |
com.agileai.weixin.tool | MessageBuilder | 解析消息,反序列化消息 |
SecurityAuthHelper | 处理OpenId,AccessToken等 | |
MenuHelper | 创建自定义菜单 | |
LocationHelper | 根据经纬度获取具体地理位置,使用到了百度OpenAPI | |
HttpClientManager | 实例化HttpClient(SSL模式)等 |
5.3原理说明
com.agileai.weixin.core包中的MessageBrokerServlet负责验证、接收、转发微信公众平台转发过来消息,MessageBrokerServlet所需要的相关信息都配置web.xml中,MessageBrokerServlet本身不负责消息的处理,而交付给MessageEventHandler完成,MessageEventHandler是由MessageBrokerServlet根据web.xml配置通过class反射机制来实例化,然后根据msgType和event调用MessageEventHandler不同的方法来处理消息及回复消息,在调用对应方法前,通过MesageBuilder工具类来解析微信公众号传过来的xml封装在HashMap中做为MessageEventHandler各方法的入参。交互时序图如下图所示:
5.4 产品特性
- 实用性:满足微信公众号扩展开发常见需求;
- 易用性:配置简单,10分钟完成接入,看到效果;
- 扩展性:良好的扩展性,修改配置、重写父类实现扩展。
6 使用说明
AEAI WX提供嵌入使用、独立使用两种模式。嵌入使用模式直接把aeaiwx相关jar包放置于目标JavaWeb应用,一般只有一个系统使用AEAI WX微信框架建议使用嵌入使用模式。独立使用则是把AEAI WX微信框架独立出来专门作为一个Application甚至是一个Server,AEAI WX采用接口调用、共享内存方式跟业务系统交互,实现跟业务系统松耦合,可扩展集成多个业务系统。
独立使用模式预置了一些AEAI WX扩展样例,一旦掌握这些样例,完全可以根据实际场景需要调用对应微信Open API实现相关集成功能。在使用AEAI WX框架之前,需要满足以下前置条件:
6.1前置条件
- 有云服务器、有域名且80端口提供http服务;
- 开通微信公众号认证:设置à微信认证;
- 确认微信公众号相关设置:
a)设置à公众号设置à功能设置,要设置JS接口安全域名,如:www.agileai.com
b)开发者中心à配置项à设置服务器配置,修改服务器配置。当完成云服务器的微信应用配置后,再启用服务器配置,不然也启动不了。
c)开发者中心à接口权限表à网页服务à网页账号,点击后面的“修改”链接 ,弹出如下设置界面:
填写回调域名后,点击确认。
6.2 嵌入方式
嵌入使用模式只需复制aeaiwx_core.jar包以及其所依赖的几个jar包到目标应用的WEB-INF/lib目录,对web.xml中进行相关修改设置,然后根据具体需求扩展MessageEvenHandler子类即可。
6.2.1 配置步骤
1.访问地址获取资源
访问http://pan.baidu.com/s/1ntsXKCT网盘地址,进入文件目录如下:
下载release_lib和depends_lib目录下的jar包,放置于目标JavaWeb应用的WEB-INF/lib目录下,该目标Java Web应用是部署在云服务器上的。
2.相关配置说明
在目标Java应用的web.xml中添加servlet配置,如下图:
<servlet> <servlet-name>MessageBroker</servlet-name> <servlet-class>com.agileai.weixin.core.MessageBrokerServlet</servlet-class> <init-param> <param-name>APPID</param-name> <param-value>Your APPID</param-value> </init-param> <init-param> <param-name>APPSECRET</param-name> <param-value>Your APPSECRET</param-value> </init-param> <init-param> <param-name>TOKEN</param-name> <param-value>Your Token</param-value> </init-param> <init-param> <param-name>BAIDU_KEY</param-name> <param-value>Your BaiDu appKey</param-value> </init-param> <init-param> <param-name>MessageEventHandlerClassName</param-name> <param-value>com.agileai.weixin.core.MessageEventHandler</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> |
同时,在web.xml底部添加servlet-mapping设置,如下所示:
<servlet-mapping> <servlet-name>MessageBroker</servlet-name> <url-pattern>/messagebroker</url-pattern> </servlet-mapping> |
配置中红色高亮显示处需要改成自己公众号的相关配置信息;***高亮显示要配置自己的百度AppKey,如果没有调用百度OpenAPI则不用配置;绿色高亮显示默认可以跟上面配置的一致,实际应用过程中则需要配置对应子类,通常是通过继承com.agileai.weixin.core.MessageEventHandler类,在子类中重写相关方法来完成特定功能需求的。
6.2.2 扩展说明
com.agileai.weixin.core.MessageEventHandler是专门设计用于覆盖父类方法来完成特定需求,方法列表如下图所示:
其中,所有以handle开头的方法都是可以覆盖重写的,参数基本都Map类型,参见微信公众号XML格式,key是XML的标记名(tagName),返回值是String,具体各方法用途,参见下表:
方法名 | 作用 |
handleSubscribe | 处理关注公众号事件 |
handleUnsubscribe | 处理取消关注公众号事件 |
handleQrCodeEvent | 处理扫描二维码事件 |
handleMenuClickEvent | 处理菜单(菜单类型为click)点击事件 |
handleMenuViewEvent | 处理菜单(菜单类型为view)点击事件 |
handleLocationEvent | 处理推送位置信息事件 |
handleTextMessage | 处理用户发送至公众号的文本信息 |
handleImageMessage | 处理用户发送至公众号的图片信息 |
handleVoiceMessage | 处理用户发送至公众号的语音信息 |
handleVideoMessage | 处理用户发送至公众号的视频信息 |
handleLocationMessage | 处理用户发送至公众号的位置信息 |
handleLinkMessage | 处理用户发送至公众号的链接信息 |
下面一段代码是handleTextMessage样例方法:
public String handleTextMessage(Map<String, String> requestMap){ String result = null; String content = requestMap.get("Content");
if (RecognizableText.contains(content)){ String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName"); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0); StringBuffer contentMsg = new StringBuffer(); contentMsg.append("亲,您的输入是").append(content); textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage); }else{ String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName"); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0);
StringBuffer contentMsg = new StringBuffer(); contentMsg.append("亲,您的输入不能被识别:)");
textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage); }
return result; } |
注意:上面的代码使用到TextMessage这个模型对象,在AEAI WX扩展框架还提供了NewsMessage模型对象,这样可以直接设置模型对象的属性,然后使用MessageBuidler工具类让其直接转化为符合微信公众号OpenAPI所需要String类型的XML数据。其他相关模型对象参见5.2部分各Java类用途表。
6.2.3 注意事项
- web.xml里servlet-maping的中url-pattern要跟微信公众号后台管理:开发者中心à配置项à设置服务器配置里面的URL服务地址后缀保持一致。
- 重写的com.agileai.weixin.core.MessageEventHandler子类不要有带参数的构造函数,因为MessageBrokerServlet是通过类反射实例化MessageEventHandler的。
6.3 独立方式
独立使用则提供基于Tomcat封装的aeaiwxserver,在aeaiwx server里面预置aeaiwx应用,该应用预置相关配置和演示样例,aeaiwx应用是基于aeaidp(数通畅联开源Java Web开发平台)快速创建出来的,数通畅联公众号就是采用的独立使用模式,AEAI WX专门用户处理微信消息相关功能、提供演示样例,而具体的业务功能则在各自应用完成,比如:微信账户跟应用的系统用户绑定则是在aeaihr(数通畅联开源的人力资源管理系统)实现的,微信签到、微信签退也是人力资源系统中处理的。
6.3.1 配置步骤
1.访问地址获取资源
访问http://pan.baidu.com/s/1ntsXKCT网盘地址,进入文件目录如下:
下载aeaiwx_server目录对应32位或者64位的aeaiwx_server,如下图:
然后解压至对应目录。
2.安装数据库
aeaiwx-server预置的aeaiwx应用依赖mysql数据库,因此,独立使用模式需要安装mysql5.x数据库,mysql数据库的安装步骤在此不详述,注意:数据库的字符集设置为utf-8。
3.导入数据脚本
导入aeai-server的sqls目录的aeaiwx_mysql.sql文件。
4.修改数据库连接配置
打开webapps\aeaiwx\WEB-INF\classes目录下的hotweb.properties配置文件,修改数据库密码:
由于数据库密码是加密的,因此要获取加密后的密文。在aeaiwx-server的bin目录下找到passwdcryptorNaNd文件,双击打开。如下图。
然后将数据库密码,输入到明文里,点击加密。如下图:
密文文本框里的加密后的密码复制并替换hotweb.properties文件的数据库密码。
5.修改web.xml配置
<servlet> <servlet-name>MessageBroker</servlet-name> <servlet-class>com.agileai.weixin.core.MessageBrokerServlet</servlet-class> <init-param> <param-name>APPID</param-name> <param-value>Your APPID</param-value> </init-param> <init-param> <param-name>APPSECRET</param-name> <param-value>Your APPSECRET</param-value> </init-param> <init-param> <param-name>TOKEN</param-name> <param-value>Your Token</param-value> </init-param> <init-param> <param-name>BAIDU_KEY</param-name> <param-value>Your BaiDu appKey</param-value> </init-param> <init-param> <param-name>MessageEventHandlerClassName</param-name> <param-value>com.agileai.weixin.custom.BizMessageEventHandler</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> |
其中,红色高亮显示处需要改成自己公众号的相关配置信息;***高亮显示要配置自己的百度AppKey,如果没有调用百度OpenAPI则不用配置;
6.修改jdk路径
在Oracle官网下载jdk,找到对应1.6.X的版本,安装之。也可参见如下地址:
http://pan.baidu.com/share/link?shareid=1902614409&uk=1901434587
编辑bin目录下setclasspath.bat文件,设置JAVA_HOME,修改为本机对应的JDK路径,如下图所示:
修改完毕双击bin目录下startup.bat可以直接启动aeaiwx-server(HotServer)。
7.登录验证
在浏览器上访问http://localhost:6060/aeaiwx,显示登陆页面,如下图所示:
默认账户admin,密码admin,登录后主页面如下图所示:
应用本身并没有提供相关可以在PC上显示良好显示的界面,因此,登录主界面后其实看不到太多内容,更多的只是一个欢迎主界面,在后续将会有一些微信相关的后台管理功能在此处扩展,比如:AEAI WX微信框架所要集成的应用和oAuth关联设置等。
6.3.2 样例说明
aeaiwx应用预置基于html5的实用工具(相关效果参见5.1),如:扫描二维码(调用微信JS SDK)、我的位置(调用百度地图API)、天气预报(调用百度天气API)、周边搜索(调用百度地址API)、热映电影(调用百度车联网API)。在aeaiwx应用中对应样例代码的JSP所在目录为jsp/webtool,控制器java类所在包为com.agileai.weixin.controller.webtool,具体如下表所示:
名称 | JSP页面 | Handler控制器 |
工具面板 | ServicePanel.jsp | ServicePanelHandler |
我的位置 | CurrentLocation.jsp | CurrentLocationHandler |
热映电影 | HotMovie.jsp | HotMovieHandler |
天气预报 | WeatherForecast.jsp | WeatherForecastHandler |
周边搜索 | AroundSearch.jsp AroundResult.jsp | AroundSearchHandler |
其中:扫描二维码功能在“工具面板”对应的ServicePanel.jsp调用微信JS SDK来完成的。
在“5.1效果演示”中微信签到、微信签退、账户关联绑定功能是在AEAI HR人力资源系统扩展完成的,而商务合作部分的相关页面则来自于数通畅畅联的手机网站。
6.3.3 扩展说明
独立使用模式除了可以通过创建子类覆盖MesageEventHandler相关handle开头相关方法,还可以在微信应用中调用相关互联网的开放平台资源,如:上诉的演示样例,以及扩展微信公众号跟其他应用的关联管理等后台功能。
在aeaiwx应用提供com.agileai.weixin.custom.BizMessageEventHandler子类来完成数通畅联的公众号所需要相关功能,代码如下所示:
package com.agileai.weixin.custom;
import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map;
import com.agileai.weixin.core.MessageEventHandler; import com.agileai.weixin.model.TextMessage; import com.agileai.weixin.model.Constans.ReqType; import com.agileai.weixin.tool.MessageBuilder;
publicclass BizMessageEventHandler extends MessageEventHandler { protectedstatic List<String> RecognizableText = new ArrayList<String>();
public BizMessageEventHandler(){ if (RecognizableText.isEmpty()){ RecognizableText.add("1"); RecognizableText.add("2"); RecognizableText.add("3"); RecognizableText.add("4"); RecognizableText.add("5"); } }
public String handleSubscribe(Map<String, String> requestMap){ String result = null;
String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName");
TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0);
StringBuffer contentMsg = new StringBuffer(); contentMsg.append("多谢关注!沈阳数通畅联软件技术有限公司是耕耘于软件集成领域的专业技术团队,以“分享SOA平台软件、传递敏捷集成机制”为使命,希望以自身所长,为客户和伙伴提供从数据层、服务层、应用层、流程层、交互层全方位的产品和技术解决方案。"); contentMsg.append("欢迎访问<a href=\"http://www.agileai.com\">手机网站</a>。");
textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage);
return result; }
public String handleTextMessage(Map<String, String> requestMap){ String result = null; String content = requestMap.get("Content");
if (RecognizableText.contains(content)){ String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName"); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0); StringBuffer contentMsg = new StringBuffer(); contentMsg.append("亲,您的输入是").append(content); textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage); }else{ String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName"); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0);
StringBuffer contentMsg = new StringBuffer(); contentMsg.append("亲,您的输入不能被识别:)");
textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage); } return result; }
public String handleLocationEvent(Map<String, String> requestMap){ String result = null;
String openId = requestMap.get("FromUserName"); doubleparseDouble(requestMap.get("Latitude")); doubleparseDouble(requestMap.get("Longitude")); doubleparseDouble(requestMap.get("Precision"));
HashMap<String,Object> row = new HashMap<String,Object>(); row.put("Latitude",latitude); row.put("Longitude",longitude); row.put("Precision",precision); row.put("receiveTime",new Date()); LocationCache.put(openId, row);
return result; }
public String handleMenuClickEvent(String eventKey,Map<String, String> requestMap){ String result = null;
if ("MyWork".equals(eventKey)){ String fromUserName = requestMap.get("FromUserName"); String toUserName = requestMap.get("ToUserName"); TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(ReqType.TEXT); textMessage.setFuncFlag(0);
StringBuffer contentMsg = new StringBuffer(); contentMsg.append("我的工作包括所有该用户的工作相关功能,如:我的待办、我的待阅、我的信息、我的日程、我的客户、我的订阅等,目前正在集成中……").append("\n");
textMessage.setContent(contentMsg.toString()); textMessageToXml(textMessage); } return result; } } |
6.3.4 注意事项
网盘上提供32位和64位两种产品介质(aeaiwx_server_x86_v1.0.3_20150506、aeaihr_server_x64_v1.0.3_20150506),部署AEAIWX微信扩展框架时候设置JDK的路径也要是对应64位、32位版本的JDK1.6.X。
7 后续感想
马化腾说过,腾讯做连接器的,而微信公众平台是一个现在较为流行的接入口,微信公共平台Open API出现则为使用这个接入口提供了便利(虽然一直是beta版),同时整合腾讯微信自己的资源,如:扫一扫、朋友圈、微支付。但是,实际更多的事情是在HTML5网站/应用上(如:微店、微网站等)完成的,因此,对于技术人员而言更多的重心应该着力Html5开发上。而移动开发将会越来越多的混合应用模式(原生的APP模式+HTML5模式),微信公众号开发其实就是一个混合模式开发的典型案例。
另外,在互联网各种开放资源平台也越来越多,国内BAT都提供一些高质量的OpenAPI(其实国外的OpenAPI资源更多,但由于网络原因不便使用),新浪、网易的新闻信息RSS也很不错,其他:如有道云笔记也提供了OpenAPI,这些互联网的开放资源都可以在应用中mashup聚合使用。