GitHub地址:wx-tools
最终DEMO源码地址: music_collector
先理一下大概的开发步骤:
简单吗?接下来一起写一个简单的Demo吧
注意!本demo使用的是SpringBoot,如果你使用原生servlet,原理是一样的。这里就不再演示。
使用maven创建,或者在eclipse/IDEA创建web项目。
如何创建web项目,相信大家都会的了。就不详细介绍了。如果不会,自行度娘。
这里我我基于springBoot创建了一个项目名为:music_collector
music_collector是一个爬虫项目,爬取各大音乐网的排行榜,并且可以支持设置关键字,来查询微信图文推送并推送给用户。(原理是通过搜狗搜索)
具体创建SpringBoot,这里就不赘述了。百度一下就知道了。
mvn archetype:generate -DgroupId=wxtools.demo -DartifactId=demo -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeCatalog=local
demo
com.soecode.wx-tools
wx-tools
2.1.4-RELEASE
注意:如果需要看源代码的务必手动修改编码。导入后记得修改jar包的编码。window下默认读GBK,而框架本身是UTF-8。
#配置如下
wx.appId=你的appId
wx.appSecret=你的appSecret
wx.token=你设置的token
wx.aesKey=如果选择安全模式,需要填入。如果是明文模式,填空就好了
wx.mchId=商户ID
最终项目目录结构如下:
不要填错了哦!注意大小写。
可以使用SpringBoot启动或者tomcat/jBoss都可以。
微信开发需要把本地127.0.0.1映射到公网上,微信服务器才可以把消息推送给你的程序。
映射工具有很多,例如:花生壳、Ngrok等。这里使用了免费版的Ngrok。
ngrok客户端windows+64版
这时候,wx-tools下的所有api都可以调用了。
我们验证一下服务器的有效性。
验证接口官方文档 建议先看官方文档,理解好开发步骤,在继续下去。
@RestController
@RequestMapping("/wx")
public class WxController {
private IService iService = new WxService();
@GetMapping
public String check(String signature, String timestamp, String nonce, String echostr) {
if (iService.checkSignature(signature, timestamp, nonce, echostr)) {
return echostr;
}
return null;
}
}
开启服务,然后去微信公众平台后台或者测试号后台填写资料验证即可。( 确保wx.properties与微信后台配置一致即可。)
打开微信配置后台:(这里我是用测试号的,还没有测试号的可以点击这里申请)
如果发现可以连接,就说明已经与微信服务器对接成功(验证成功)。接下来任何对你的公众号操作,微信服务器都会转发消息给你的服务器(你的程序)。
当你验证服务器有消息成功后,微信服务器就会把你的公众号任何事件和消息,以post请求推送到你验证的那个url地址上。所以我们现在需要做的就是写一个Post接收方法,来接收发来的消息~
@RestController
@RequestMapping("/wx")
public class WxController {
private IService iService = new WxService();
@GetMapping
public String check(String signature, String timestamp, String nonce, String echostr) {
if (iService.checkSignature(signature, timestamp, nonce, echostr)) {
return echostr;
}
return null;
}
@PostMapping
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
try {
// 微信服务器推送过来的是XML格式。
WxXmlMessage wx = XStreamTransformer.fromXml(WxXmlMessage.class, request.getInputStream());
System.out.println("消息:\n " + wx.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
}
}
}
可以看到,我把微信服务器发来的消息(request)的输入流扔给了XStreamTransformer来帮忙解析XML,并返回WxXmlMessage类。
这时候,我们可以尝试向公众号发送一句:“Hello”。我们看一下结果。
wx-tools自动把发来的消息封装成一个类,方便处理。
我们再试试接收并解析事件(Event)推送,例如:新用户关注事件推送。
我用另一个微信号关注了一波我的测试号,观察控制台:
好了,至此。你已经成功一大半了。为什么呢!因为剩下的就是拿着这个消息(Message)去各种处理,最后返回给用户想要的东西即可~
下篇会讲一些路由器的使用方式,请移步下篇!嘻嘻
假设一下,我现在想把公众号的菜单栏设置成这样的:
我们可以写一个Main方法,来更改公众号的菜单栏,代码如下:
public class Menu {
public static void main(String[] args) {
IService iService = new WxService();
WxMenu menu = new WxMenu();
List btnList = new ArrayList<>();
//飙升功能
WxMenu.WxMenuButton btn1 = new WxMenu.WxMenuButton();
btn1.setName("分类");
List subList = new ArrayList<>();
WxMenu.WxMenuButton btn1_1 = new WxMenu.WxMenuButton();
btn1_1.setType(WxConsts.MENU_BUTTON_CLICK);
btn1_1.setKey(MenuKey.HOT_SONG);
btn1_1.setName("飙升榜");
WxMenu.WxMenuButton btn1_2 = new WxMenu.WxMenuButton();
btn1_2.setType(WxConsts.MENU_BUTTON_CLICK);
btn1_2.setKey(MenuKey.TOP_500);
btn1_2.setName("TOP500");
WxMenu.WxMenuButton btn1_3 = new WxMenu.WxMenuButton();
btn1_3.setType(WxConsts.MENU_BUTTON_CLICK);
btn1_3.setKey(MenuKey.NET_HOT_SONG);
btn1_3.setName("网络红歌");
WxMenu.WxMenuButton btn1_4 = new WxMenu.WxMenuButton();
btn1_4.setType(WxConsts.MENU_BUTTON_CLICK);
btn1_4.setKey(MenuKey.HUAYU_SONG);
btn1_4.setName("华语新歌");
WxMenu.WxMenuButton btn1_5 = new WxMenu.WxMenuButton();
btn1_5.setType(WxConsts.MENU_BUTTON_CLICK);
btn1_5.setKey(MenuKey.XINAO_SONG);
btn1_5.setName("洗脑神曲");
WxMenu.WxMenuButton btn2 = new WxMenu.WxMenuButton();
btn2.setType(WxConsts.MENU_BUTTON_CLICK);
btn2.setKey(MenuKey.CHANGE_NEWS);
btn2.setName("换一组");
WxMenu.WxMenuButton btn3 = new WxMenu.WxMenuButton();
btn3.setType(WxConsts.MENU_BUTTON_CLICK);
btn3.setKey(MenuKey.HELP);
btn3.setName("帮助");
subList.addAll(Arrays.asList(btn1_1, btn1_2, btn1_3, btn1_4, btn1_5));
btn1.setSub_button(subList);
//将三个按钮设置进btnList
btnList.add(btn1);
btnList.add(btn2);
btnList.add(btn3);
//设置进菜单类
menu.setButton(btnList);
//调用API即可
try {
//参数1--menu ,参数2--是否是个性化定制。如果是个性化菜单栏,需要设置MenuRule
iService.createMenu(menu, false);
} catch (WxErrorException e) {
e.printStackTrace();
}
}
}
菜单栏的参数设置,请参考官方文档 - 菜单栏的创建, 菜单栏的按钮有分很多种类型(type),例如click,view等。详情建议先看官方文档。
假设需求如下: 我只接收菜单栏类型为:Click,且Key为MenuKey.HELP的消息,其他一律放弃不接收。
正常来说,常规写法应该是加一个if判断,例如:
if("CLICK".equals(message.event) && MenuKey.HELP.equals(message.eventKey)){
doSomething();
}
这样的写法会随着需求增多,而越来越庞大臃肿。
而wx-tools使用消息路由的方式去过滤处理消息,写法如下:
@PostMapping
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
// 创建一个路由器
WxMessageRouter router = new WxMessageRouter(iService);
try {
// 微信服务器推送过来的是XML格式。
WxXmlMessage wx = XStreamTransformer.fromXml(WxXmlMessage.class, request.getInputStream());
System.out.println("消息:\n " + wx.toString());
router.rule().event(WxConsts.EVT_CLICK).eventKey(MenuKey.HELP).handler(HelpDocHandler.getInstance()).end();
// 把消息传递给路由器进行处理
WxXmlOutMessage xmlOutMsg = router.route(wx);
if (xmlOutMsg != null)
// 因为是明文,所以不用加密,直接返回给用户
out.print(xmlOutMsg.toXml());。
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
}
}
Handler代码采用单例模式,为了解决消息重试的问题。但由于网络问题导致返回结果慢了。会自动重试,返回多条信息。当然,这种做法不建议你们使用,因为这样写,同一时间仅仅允许一个线程进来处理,并不适合多线程环境。
因为这个音乐爬虫项目是做给我女票使用的,用户仅仅只有一个。所以我可以这么做,而你不们作为项目,不可以!!!好了继续~
官方关于消息重试的机制点击这里查看。
public class HelpDocHandler implements WxMessageHandler {
private static HelpDocHandler instance = null;
private boolean isRun = false;
private HelpDocHandler(){}
public static synchronized HelpDocHandler getInstance(){
if (instance == null) {
instance = new HelpDocHandler();
}
return instance;
}
private synchronized boolean getIsRun() {
return isRun;
}
private synchronized void setRun(boolean run) {
isRun = run;
}
@Override
public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context, IService iService) throws WxErrorException {
WxXmlOutMessage response = null;
if (!getIsRun()) {
setRun(true);
response = execute(wxMessage);
setRun(false);
}
return response;
}
private WxXmlOutMessage execute(WxXmlMessage wxMessage) {
return WxXmlOutMessage.TEXT().content(ResponseConst.HELP).toUser(wxMessage.getFromUserName()).fromUser(wxMessage.getToUserName()).build();
}
}
这样我们就成功利用消息路由和创建第一个规则(Rule)去过滤消息,并处理返回了。
是不是很简单~2333
现在我们尝试使用wx-tools其他特性,添加更多的功能进去吧~gogogo
需求:当用户点击此按钮,后台爬虫帮我爬当天酷狗飙升榜的列表下来,并返回。 酷狗飙升榜在这。
此handler专门处理爬虫分类Event事件。
RankHandler.java:(负责处理消息部分)
public class RankHandler implements WxMessageHandler {
private static RankHandler instance = null;
private boolean isRun = false;
private RankHandler() {}
public static synchronized RankHandler getInstance() {
if (instance == null) {
instance = new RankHandler();
}
return instance;
}
@Override
public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context, IService iService)
throws WxErrorException {
StringBuilder result = new StringBuilder();
if (!getIsRun()) {
setRun(true);
try {
result = execute(wxMessage);
} catch (Exception e) {
e.printStackTrace();
} finally {
setRun(false);
}
} else {
result.append(ResponseConst.DUPLICATE_REQUEST);
}
return WxXmlOutMessage.TEXT().content(result.toString()).toUser(wxMessage.getFromUserName()).fromUser(wxMessage.getToUserName()).build();
}
private StringBuilder execute(WxXmlMessage wxMessage) throws Exception{
StringBuilder stringBuilder = new StringBuilder();
try {
switch (wxMessage.getEventKey()) {
case MenuKey.HOT_SONG:
collectSongRank(stringBuilder, UrlConst.HOT_RANK_URL);
break;
default:
stringBuilder.append("暂时无此分类噢!");
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return stringBuilder;
}
private void collectSongRank(StringBuilder stringBuilder, String url) throws IOException {
RankCollector collector = new RankCollector();
Rank rank = collector.collect(url);
stringBuilder.append("\uD83D\uDD25" + rank.getScope() + "[" + rank.getUpdateTime() + "]\n\n");
for (HotSinger hotSinger : rank.getHotSingerList()) {
stringBuilder.append(hotSinger.getName() + "-" + hotSinger.getHotSong() + "\n");
}
}
private synchronized boolean getIsRun() {
return isRun;
}
private synchronized void setRun(boolean run) {
isRun = run;
}
}
RankCollector.java: (爬虫部分)
public class RankCollector {
public Rank collect(String url) throws IOException {
return getRank(url);
}
private Rank getRank(String rankUrl) throws IOException {
Rank rank = new Rank();
String body = HttpClientUtil.get(rankUrl);
Document doc = Jsoup.parse(body);
rank.setScope(doc.select("a[class=current]").attr("title"));
rank.setUpdateTime(doc.select("span[class=rank_update]").text());
List aElements = doc.select("a[data-active=playDwn]");
for(int i = 0; i < aElements.size(); i++){
String[] splitArray = aElements.get(i).text().split("-");
String name = splitArray[0].toString().trim();
String song = splitArray[1].toString().trim();
rank.getHotSingerList().add(new HotSinger(doc.select("a[class=current]").attr("title"), name, song));
}
return rank;
}
}
修改新增WxController的路由规则:
router.rule().event(WxConsts.EVT_CLICK).eventKey(MenuKey.HELP).handler(HelpDocHandler.getInstance()).next()
.rule().eventKey(MenuKey.HOT_SONG).handler(RankHandler.getInstance()).end();
当天的飙升榜就这样返回回来了~很激动有木有!
WxMessageMatcher(消息匹配器)接口用于一些简单的匹配,可以自定义匹配逻辑,如格式验证。匹配成功则继续往下执行,否则不允许通过。
需求:我想当用户发送文本消息:“我是谁”时,后台获取该用户的微信用户信息(昵称)并返回。
用于匹配符合”我是谁“的消息。
public class WhoAmIMatcher implements WxMessageMatcher{
@Override
public boolean match(WxXmlMessage message) {
if(StringUtils.isNotEmpty(message.getContent())){
if(message.getContent().equals("我是谁")){
return true;
}
}
return false;
}
}
用于处理当匹配到“我是谁”的消息。
router.rule().msgType(WxConsts.XML_MSG_TEXT).matcher(new WhoAmIMatcher()).handler(new WhoAmIHandler()).end()
.rule().event(WxConsts.EVT_CLICK).eventKey(MenuKey.HELP).handler(HelpDocHandler.getInstance()).next()
.rule().eventKey(MenuKey.HOT_SONG).handler(RankHandler.getInstance()).next()
.rule().eventKey(MenuKey.TOP_500).handler(RankHandler.getInstance()).next()
.rule().eventKey(MenuKey.NET_HOT_SONG).handler(RankHandler.getInstance()).next()
.rule().eventKey(MenuKey.HUAYU_SONG).handler(RankHandler.getInstance()).next()
.rule().eventKey(MenuKey.XINAO_SONG).handler(RankHandler.getInstance()).end();
如图所示,输入”我是谁“的时候,返回了我微信的昵称。
至此,我们已经尝试使用了路由器Router,规则Rule,匹配器Matcher,处理器Handler。
还有使用了IServer统一接口调用去获取用户信息。
IServer 接口是集成所有wx-tools已经实现的微信接口,统一调用入口。它的实现是WxService.java。想看实现源码的可以戳:这里
WxMessageInterceptor(拦截器)功能与Matcher相同,用于过滤拦截,但是与Matcher最大的不同就是,它支持更复杂的业务处理,因为它携带了IService和上下文context,可以利用这两个参数进行业务处理。
WxMessageInterceptor拦截器接口,可以处理更加复杂的验证。例如身份验证,时效校验等等。
由于我的音乐爬虫项目并没有涉及到这么深的处理,所以这里只给出一个DemoInterceptor看看。
假设需求:只有用户关注公众号时长大于3天才能参与活动。
/**
* Demo 拦截器,可以通过WxService做更加复杂的拦截,例如身份验证,权限验证等操作。
* @author antgan
*
*/
public class DemoInterceptor implements WxMessageInterceptor{
public boolean intercept(WxXmlMessage wxMessage, Map context, IService wxService) throws WxErrorException {
//可以使用wxService的微信API方法
//可以在Handler和Interceptor传递消息,使用context上下文
//可以实现自己的业务逻辑
//这里就不编写验证关注三天以上的用户了
if(/*用户关注时长大于3天*/){
return true;
}
return false;
}
}
例如:
router.rule().matcher(new DemoMatcher()).interceptor(new DemoInterceptor()).handler(new DemoMessageHandler()).end();
搞定。接下来交给wx-tools去做吧~
简单吧!
WxMessageRouter消息路由器,不知道你们理解了多少,接下来还是详细讲解一下需要注意的细节。
提到这个路由器,就要说说另一个东西:WxMessageRouterRule。简称规则(Rule)。
定义规则,用于对来自微信服务器的消息进行过滤和筛选,只针对有效消息进行处理,提高服务器处理效率。
通过链式配置路由规则(Rule),根据规则把来自微信的消息交给handler处理。
//初始化一个路由器,把wxService传入。
WxMessageRouter router = new WxMessageRouter(wxService);
//新建路由规则,通过rule()方法创建新的规则,然后链式填写过滤条件。MSG_TYPE等参数填入WxConst中的常量,这里不作展示,可以查看WxConst代码或官方文档,有注释。
router.rule().msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT").matcher(matcher).interceptor(interceptor, ...).handler(handler, ...).end()
.rule().msgType("MSG_TYPE")...//另外一个匹配规则.end();
// 将WxXmlMessage交给消息路由器,处理后得到结果。
WxXmlOutMessage xmlOutMsg = router.route(wxXmlMessage);
//正则表达式:^[0-9]*$只接受数字消息,其他消息过滤。
router.rule().rContent("^[0-9]*$").handler(new DemoHandler()).end();
去除多余消息,高效处理争对性消息,真是好用又简单。
至此,如果你跟着做,并且都成功了话。你已经入门了wx-tools了。以后无非就是根据用户需求添加路由规则,并添加处理器去处理,入门教程就到此结束了。
DEMO的源码下载地址: music_collector
其实DEMO里还有关键字搜索并推送图文消息给用户的功能. 文档里没演示出来.有兴趣的同学可以查阅源代码.
最终效果图~可以是这样
互联网开发讨论群:577984291