diff --git a/README.md b/README.md index 88ba5f7c..47393ea1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,48 @@ -weixin-java +weixin-tools =========== -微信java开发工具集 +微信java开发工具,本项目提供了两个主要特性:微信消息路由器、微信Java API -## 执行测试 -将 ''src/test/resources/test-config.sample.xml'' 改成 ''test-config.xml'' 并设置appId, secret, 一个过期的accessToken +## 微信消息路由器 + +你可以使用``WxMessageRouter``来对微信推送过来的消息、事件进行路由,交给特定的``WxMessageHandler``处理。 + +使用方法: + +```java +WxMessageRouter router = new WxMessageRouter(); +router + .rule() + .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT") + .interceptor(interceptor, ...).handler(handler, ...) + .end() + .rule() + // 另外一个匹配规则 + .end() +; + +// 将WxXmlMessage交给消息路由器 +router.route(message); +``` + +说明: +1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理 +2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#next()} +3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效 + +## 微信Java API + +使用``WxService``可以调用微信API。目前实现了以下功能,其余功能以后陆续补充: + +1. 发送客服消息 +1. 创建自定义菜单 +1. 删除自定义菜单 +1. 查询自定义菜单 +1. 刷新access_token + +## 如何执行单元测试 +将 ``src/test/resources/test-config.sample.xml`` 改成 ``test-config.xml`` 设置appId, secret, accessToken(可选), openId + +```bash +mvn clean test +``` diff --git a/pom.xml b/pom.xml index 9297530d..b3655797 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 chanjarster.weixin - weixin-java + weixin-toolset 1.0.0-SNAPSHOT WeiXin Java Toolset https://github.com/chanjarster/weixin-java diff --git a/src/main/java/chanjarster/weixin/api/WxConsts.java b/src/main/java/chanjarster/weixin/api/WxConsts.java index a6e77b57..8d7d8d8f 100644 --- a/src/main/java/chanjarster/weixin/api/WxConsts.java +++ b/src/main/java/chanjarster/weixin/api/WxConsts.java @@ -10,6 +10,14 @@ public class WxConsts { public static final String MSG_NEWS = "news"; public static final String MSG_LOCATION = "location"; public static final String MSG_LINK = "link"; + public static final String MSG_EVENT = "event"; + + public static final String EVT_SUBSCRIBE = "subscribe"; + public static final String EVT_UNSUBSCRIBE = "unsubscribe"; + public static final String EVT_SCAN = "SCAN"; + public static final String EVT_LOCATION = "LOCATION"; + public static final String EVT_CLICK = "LOCATION"; + public static final String EVT_VIEW = "VIEW"; + - } diff --git a/src/main/java/chanjarster/weixin/api/WxMessageHandler.java b/src/main/java/chanjarster/weixin/api/WxMessageHandler.java index 9f985e2c..7dcb7a77 100644 --- a/src/main/java/chanjarster/weixin/api/WxMessageHandler.java +++ b/src/main/java/chanjarster/weixin/api/WxMessageHandler.java @@ -5,7 +5,7 @@ import java.util.Map; import chanjarster.weixin.bean.WxXmlMessage; /** - * 处理微信推送消息的处理器 + * 处理微信推送消息的处理器接口 * @author chanjarster * */ diff --git a/src/main/java/chanjarster/weixin/api/WxMessageRouter.java b/src/main/java/chanjarster/weixin/api/WxMessageRouter.java index c256a228..a7b8309f 100644 --- a/src/main/java/chanjarster/weixin/api/WxMessageRouter.java +++ b/src/main/java/chanjarster/weixin/api/WxMessageRouter.java @@ -5,13 +5,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import chanjarster.weixin.api.WxMessageRouterTest.WxEchoMessageHandler; import chanjarster.weixin.bean.WxXmlMessage; /** *
  * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
  * 
+ * 说明:
+ * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
+ * 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#next()}
+ * 3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效
+ * 
  * 使用方法:
  * WxMessageRouter router = new WxMessageRouter();
  * router
@@ -27,9 +31,6 @@ import chanjarster.weixin.bean.WxXmlMessage;
  * // 将WxXmlMessage交给消息路由器
  * router.route(message);
  * 
- * 说明:
- * 1. 配置路由规则时要按照从细到粗的原则
- * 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#reEnter()}
  * 
* @author qianjia * @@ -52,9 +53,11 @@ public class WxMessageRouter { */ public void route(WxXmlMessage wxMessage) { for (Rule rule : rules) { - boolean doNext = rule.service(wxMessage); - if (!doNext) { - break; + if (rule.test(wxMessage)) { + boolean reEnter = rule.service(wxMessage); + if (!reEnter) { + break; + } } } } @@ -122,16 +125,16 @@ public class WxMessageRouter { } /** - * 将消息交给后面的Rule处理 + * 设置微信消息拦截器 + * @param interceptor * @return */ - public Rule reEnter() { - this.reEnter = true; - return this; + public Rule interceptor(WxMessageInterceptor interceptor) { + return interceptor(interceptor, (WxMessageInterceptor[]) null); } /** - * 添加interceptor + * 设置微信消息拦截器 * @param interceptor * @param otherInterceptors * @return @@ -147,10 +150,20 @@ public class WxMessageRouter { } /** - * 添加handler + * 设置微信消息处理器 * @param handler * @return */ + public Rule handler(WxMessageHandler handler) { + return handler(handler, (WxMessageHandler[]) null); + } + + /** + * 设置微信消息处理器 + * @param handler + * @param otherHandlers + * @return + */ public Rule handler(WxMessageHandler handler, WxMessageHandler... otherHandlers) { this.handlers.add(handler); if (otherHandlers != null && otherHandlers.length > 0) { @@ -162,7 +175,7 @@ public class WxMessageRouter { } /** - * 规则结束 + * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则 * @return */ public WxMessageRouter end() { @@ -170,6 +183,15 @@ public class WxMessageRouter { return this.routerBuilder; } + /** + * 规则结束,但是消息还会进入其他规则 + * @return + */ + public WxMessageRouter next() { + this.reEnter = true; + return end(); + } + protected boolean test(WxXmlMessage wxMessage) { return (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) @@ -188,11 +210,6 @@ public class WxMessageRouter { * @return true 代表继续执行别的router,false 代表停止执行别的router */ protected boolean service(WxXmlMessage wxMessage) { - // 如果不匹配本规则,那么接着执行后面的Rule - if (!test(wxMessage)) { - return true; - } - Map context = new HashMap(); // 如果拦截器不通过 for (WxMessageInterceptor interceptor : this.interceptors) { diff --git a/src/main/java/chanjarster/weixin/api/WxServiceImpl.java b/src/main/java/chanjarster/weixin/api/WxServiceImpl.java index aa319c89..1c203ef4 100644 --- a/src/main/java/chanjarster/weixin/api/WxServiceImpl.java +++ b/src/main/java/chanjarster/weixin/api/WxServiceImpl.java @@ -20,7 +20,7 @@ import chanjarster.weixin.bean.WxCustomMessage; import chanjarster.weixin.bean.WxError; import chanjarster.weixin.bean.WxMenu; import chanjarster.weixin.exception.WxErrorException; -import chanjarster.weixin.util.Utf8StringResponseHandler; +import chanjarster.weixin.util.Utf8ResponseHandler; public class WxServiceImpl implements WxService { @@ -133,7 +133,7 @@ public class WxServiceImpl implements WxService { httpPost.setEntity(entity); } CloseableHttpResponse response = httpclient.execute(httpPost); - resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); + resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); } else if ("GET".equals(method)) { if (data != null) { uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data; @@ -141,7 +141,7 @@ public class WxServiceImpl implements WxService { HttpGet httpGet = new HttpGet(uriWithAccessToken); CloseableHttpResponse response = httpclient.execute(httpGet); response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString()); - resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); + resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); } WxError error = WxError.fromJson(resultContent); diff --git a/src/main/java/chanjarster/weixin/util/Utf8StringResponseHandler.java b/src/main/java/chanjarster/weixin/util/Utf8ResponseHandler.java similarity index 90% rename from src/main/java/chanjarster/weixin/util/Utf8StringResponseHandler.java rename to src/main/java/chanjarster/weixin/util/Utf8ResponseHandler.java index 1b8f0145..9884425d 100644 --- a/src/main/java/chanjarster/weixin/util/Utf8StringResponseHandler.java +++ b/src/main/java/chanjarster/weixin/util/Utf8ResponseHandler.java @@ -15,9 +15,9 @@ import org.apache.http.util.EntityUtils; * @author chanjarster * */ -public class Utf8StringResponseHandler implements ResponseHandler { +public class Utf8ResponseHandler implements ResponseHandler { - public static final ResponseHandler INSTANCE = new Utf8StringResponseHandler(); + public static final ResponseHandler INSTANCE = new Utf8ResponseHandler(); public String handleResponse(final HttpResponse response) throws HttpResponseException, IOException { final StatusLine statusLine = response.getStatusLine(); diff --git a/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java b/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java index 03e83fd4..76b7368b 100644 --- a/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java +++ b/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java @@ -3,30 +3,97 @@ package chanjarster.weixin.api; import java.util.Map; import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import chanjarster.weixin.bean.WxXmlMessage; @Test public class WxMessageRouterTest { - - public void testSimple() { - StringBuilder sb = new StringBuilder(); - WxMessageRouter router = new WxMessageRouter(); + + protected StringBuilder sb; + protected WxMessageRouter router; + + @BeforeMethod + public void prepare() { + this.sb = new StringBuilder(); + this.router = new WxMessageRouter(); router + .rule() + .msgType(WxConsts.MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1").content("CONTENT_1") + .handler(new WxEchoMessageHandler(sb, "COMBINE_4")) + .end() + .rule() + .msgType(WxConsts.MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1") + .handler(new WxEchoMessageHandler(sb, "COMBINE_3")) + .end() + .rule() + .msgType(WxConsts.MSG_TEXT).event(WxConsts.EVT_CLICK) + .handler(new WxEchoMessageHandler(sb, "COMBINE_2")) + .end() .rule().msgType(WxConsts.MSG_TEXT).handler(new WxEchoMessageHandler(sb, WxConsts.MSG_TEXT)).end() - .rule().msgType(WxConsts.MSG_IMAGE).handler(new WxEchoMessageHandler(sb, WxConsts.MSG_IMAGE)).end() + .rule().event(WxConsts.EVT_CLICK).handler(new WxEchoMessageHandler(sb, WxConsts.EVT_CLICK)).end() + .rule().eventKey("KEY_1").handler(new WxEchoMessageHandler(sb, "KEY_1")).end() + .rule().content("CONTENT_1").handler(new WxEchoMessageHandler(sb, "CONTENT_1")).end() + .rule().handler(new WxEchoMessageHandler(sb, "ALL")).end(); ; - - - WxXmlMessage message = new WxXmlMessage(); - message.setMsgType(WxConsts.MSG_TEXT); + } + + @Test(dataProvider="messages-1") + public void testSimple(WxXmlMessage message, String expected) { router.route(message); - Assert.assertEquals(sb.toString(), WxConsts.MSG_TEXT + ","); - + Assert.assertEquals(sb.toString(), expected); + } + + @Test() + public void test() { } + @DataProvider(name="messages-1") + public Object[][] messages2() { + WxXmlMessage message1 = new WxXmlMessage(); + message1.setMsgType(WxConsts.MSG_TEXT); + WxXmlMessage message2 = new WxXmlMessage(); + message2.setEvent(WxConsts.EVT_CLICK); + + WxXmlMessage message3 = new WxXmlMessage(); + message3.setEventKey("KEY_1"); + + WxXmlMessage message4 = new WxXmlMessage(); + message4.setContent("CONTENT_1"); + + WxXmlMessage message5 = new WxXmlMessage(); + message5.setContent("BLA"); + + WxXmlMessage c2 = new WxXmlMessage(); + c2.setMsgType(WxConsts.MSG_TEXT); + c2.setEvent(WxConsts.EVT_CLICK); + + WxXmlMessage c3 = new WxXmlMessage(); + c3.setMsgType(WxConsts.MSG_TEXT); + c3.setEvent(WxConsts.EVT_CLICK); + c3.setEventKey("KEY_1"); + + WxXmlMessage c4 = new WxXmlMessage(); + c4.setMsgType(WxConsts.MSG_TEXT); + c4.setEvent(WxConsts.EVT_CLICK); + c4.setEventKey("KEY_1"); + c4.setContent("CONTENT_1"); + + return new Object[][] { + new Object[] { message1, WxConsts.MSG_TEXT + "," }, + new Object[] { message2, WxConsts.EVT_CLICK + "," }, + new Object[] { message3, "KEY_1," }, + new Object[] { message4, "CONTENT_1," }, + new Object[] { message5, "ALL," }, + new Object[] { c2, "COMBINE_2," }, + new Object[] { c3, "COMBINE_3," }, + new Object[] { c4, "COMBINE_4," } + }; + + } public static class WxEchoMessageHandler implements WxMessageHandler { diff --git a/src/test/java/chanjarster/weixin/api/WxServiceTest.java b/src/test/java/chanjarster/weixin/api/WxServiceTest.java index 4306970b..58650bee 100644 --- a/src/test/java/chanjarster/weixin/api/WxServiceTest.java +++ b/src/test/java/chanjarster/weixin/api/WxServiceTest.java @@ -43,18 +43,18 @@ public class WxServiceTest { Assert.assertTrue(StringUtils.isNotBlank(after)); } - @Test(dependsOnMethods = "testRefreshAccessToken", enabled = false) + @Test(dependsOnMethods = "testRefreshAccessToken") public void sendCustomMessage() throws WxErrorException { WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigProvider; WxCustomMessage message = new WxCustomMessage(); message.setMsgtype(WxConsts.MSG_TEXT); message.setTouser(configProvider.getOpenId()); - message.setContent("欢迎使用教务系统微信公众号\n下面\nHello World"); + message.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World"); wxService.sendCustomMessage(message); } - @Test(dataProvider = "menu", enabled = true, dependsOnMethods = "testRefreshAccessToken") + @Test(dataProvider = "menu", dependsOnMethods = "testRefreshAccessToken") public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { wxService.createMenu(wxMenu); } @@ -64,7 +64,7 @@ public class WxServiceTest { Assert.assertNotNull(wxService.getMenu()); } - @Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }, enabled = false) + @Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }) public void testDeleteMenu() throws WxErrorException { wxService.deleteMenu(); }