微信消息路由器,及单元测试代码

This commit is contained in:
Daniel Qian
2014-08-22 11:50:50 +08:00
parent 189f285259
commit ed3cdfcecd
9 changed files with 181 additions and 48 deletions

View File

@ -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
```

View File

@ -4,7 +4,7 @@
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>chanjarster.weixin</groupId> <groupId>chanjarster.weixin</groupId>
<artifactId>weixin-java</artifactId> <artifactId>weixin-toolset</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<name>WeiXin Java Toolset</name> <name>WeiXin Java Toolset</name>
<url>https://github.com/chanjarster/weixin-java</url> <url>https://github.com/chanjarster/weixin-java</url>

View File

@ -10,6 +10,14 @@ public class WxConsts {
public static final String MSG_NEWS = "news"; public static final String MSG_NEWS = "news";
public static final String MSG_LOCATION = "location"; public static final String MSG_LOCATION = "location";
public static final String MSG_LINK = "link"; 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";
} }

View File

@ -5,7 +5,7 @@ import java.util.Map;
import chanjarster.weixin.bean.WxXmlMessage; import chanjarster.weixin.bean.WxXmlMessage;
/** /**
* 处理微信推送消息的处理器 * 处理微信推送消息的处理器接口
* @author chanjarster * @author chanjarster
* *
*/ */

View File

@ -5,13 +5,17 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import chanjarster.weixin.api.WxMessageRouterTest.WxEchoMessageHandler;
import chanjarster.weixin.bean.WxXmlMessage; import chanjarster.weixin.bean.WxXmlMessage;
/** /**
* <pre> * <pre>
* 微信消息路由器通过代码化的配置把来自微信的消息交给handler处理 * 微信消息路由器通过代码化的配置把来自微信的消息交给handler处理
* *
* 说明:
* 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
* 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#next()}
* 3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效
*
* 使用方法: * 使用方法:
* WxMessageRouter router = new WxMessageRouter(); * WxMessageRouter router = new WxMessageRouter();
* router * router
@ -27,9 +31,6 @@ import chanjarster.weixin.bean.WxXmlMessage;
* // 将WxXmlMessage交给消息路由器 * // 将WxXmlMessage交给消息路由器
* router.route(message); * router.route(message);
* *
* 说明:
* 1. 配置路由规则时要按照从细到粗的原则
* 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#reEnter()}
* </pre> * </pre>
* @author qianjia * @author qianjia
* *
@ -52,9 +53,11 @@ public class WxMessageRouter {
*/ */
public void route(WxXmlMessage wxMessage) { public void route(WxXmlMessage wxMessage) {
for (Rule rule : rules) { for (Rule rule : rules) {
boolean doNext = rule.service(wxMessage); if (rule.test(wxMessage)) {
if (!doNext) { boolean reEnter = rule.service(wxMessage);
break; if (!reEnter) {
break;
}
} }
} }
} }
@ -122,16 +125,16 @@ public class WxMessageRouter {
} }
/** /**
* 将消息交给后面的Rule处理 * 设置微信消息拦截器
* @param interceptor
* @return * @return
*/ */
public Rule reEnter() { public Rule interceptor(WxMessageInterceptor interceptor) {
this.reEnter = true; return interceptor(interceptor, (WxMessageInterceptor[]) null);
return this;
} }
/** /**
* 添加interceptor * 设置微信消息拦截器
* @param interceptor * @param interceptor
* @param otherInterceptors * @param otherInterceptors
* @return * @return
@ -147,10 +150,20 @@ public class WxMessageRouter {
} }
/** /**
* 添加handler * 设置微信消息处理器
* @param handler * @param handler
* @return * @return
*/ */
public Rule handler(WxMessageHandler handler) {
return handler(handler, (WxMessageHandler[]) null);
}
/**
* 设置微信消息处理器
* @param handler
* @param otherHandlers
* @return
*/
public Rule handler(WxMessageHandler handler, WxMessageHandler... otherHandlers) { public Rule handler(WxMessageHandler handler, WxMessageHandler... otherHandlers) {
this.handlers.add(handler); this.handlers.add(handler);
if (otherHandlers != null && otherHandlers.length > 0) { if (otherHandlers != null && otherHandlers.length > 0) {
@ -162,7 +175,7 @@ public class WxMessageRouter {
} }
/** /**
* 规则结束 * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
* @return * @return
*/ */
public WxMessageRouter end() { public WxMessageRouter end() {
@ -170,6 +183,15 @@ public class WxMessageRouter {
return this.routerBuilder; return this.routerBuilder;
} }
/**
* 规则结束,但是消息还会进入其他规则
* @return
*/
public WxMessageRouter next() {
this.reEnter = true;
return end();
}
protected boolean test(WxXmlMessage wxMessage) { protected boolean test(WxXmlMessage wxMessage) {
return return
(this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) (this.msgType == null || this.msgType.equals(wxMessage.getMsgType()))
@ -188,11 +210,6 @@ public class WxMessageRouter {
* @return true 代表继续执行别的routerfalse 代表停止执行别的router * @return true 代表继续执行别的routerfalse 代表停止执行别的router
*/ */
protected boolean service(WxXmlMessage wxMessage) { protected boolean service(WxXmlMessage wxMessage) {
// 如果不匹配本规则那么接着执行后面的Rule
if (!test(wxMessage)) {
return true;
}
Map<String, Object> context = new HashMap<String, Object>(); Map<String, Object> context = new HashMap<String, Object>();
// 如果拦截器不通过 // 如果拦截器不通过
for (WxMessageInterceptor interceptor : this.interceptors) { for (WxMessageInterceptor interceptor : this.interceptors) {

View File

@ -20,7 +20,7 @@ import chanjarster.weixin.bean.WxCustomMessage;
import chanjarster.weixin.bean.WxError; import chanjarster.weixin.bean.WxError;
import chanjarster.weixin.bean.WxMenu; import chanjarster.weixin.bean.WxMenu;
import chanjarster.weixin.exception.WxErrorException; import chanjarster.weixin.exception.WxErrorException;
import chanjarster.weixin.util.Utf8StringResponseHandler; import chanjarster.weixin.util.Utf8ResponseHandler;
public class WxServiceImpl implements WxService { public class WxServiceImpl implements WxService {
@ -133,7 +133,7 @@ public class WxServiceImpl implements WxService {
httpPost.setEntity(entity); httpPost.setEntity(entity);
} }
CloseableHttpResponse response = httpclient.execute(httpPost); CloseableHttpResponse response = httpclient.execute(httpPost);
resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
} else if ("GET".equals(method)) { } else if ("GET".equals(method)) {
if (data != null) { if (data != null) {
uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data; uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data;
@ -141,7 +141,7 @@ public class WxServiceImpl implements WxService {
HttpGet httpGet = new HttpGet(uriWithAccessToken); HttpGet httpGet = new HttpGet(uriWithAccessToken);
CloseableHttpResponse response = httpclient.execute(httpGet); CloseableHttpResponse response = httpclient.execute(httpGet);
response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString()); response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); resultContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
} }
WxError error = WxError.fromJson(resultContent); WxError error = WxError.fromJson(resultContent);

View File

@ -15,9 +15,9 @@ import org.apache.http.util.EntityUtils;
* @author chanjarster * @author chanjarster
* *
*/ */
public class Utf8StringResponseHandler implements ResponseHandler<String> { public class Utf8ResponseHandler implements ResponseHandler<String> {
public static final ResponseHandler<String> INSTANCE = new Utf8StringResponseHandler(); public static final ResponseHandler<String> INSTANCE = new Utf8ResponseHandler();
public String handleResponse(final HttpResponse response) throws HttpResponseException, IOException { public String handleResponse(final HttpResponse response) throws HttpResponseException, IOException {
final StatusLine statusLine = response.getStatusLine(); final StatusLine statusLine = response.getStatusLine();

View File

@ -3,6 +3,8 @@ package chanjarster.weixin.api;
import java.util.Map; import java.util.Map;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import chanjarster.weixin.bean.WxXmlMessage; import chanjarster.weixin.bean.WxXmlMessage;
@ -10,23 +12,88 @@ import chanjarster.weixin.bean.WxXmlMessage;
@Test @Test
public class WxMessageRouterTest { public class WxMessageRouterTest {
public void testSimple() { protected StringBuilder sb;
StringBuilder sb = new StringBuilder(); protected WxMessageRouter router;
WxMessageRouter router = new WxMessageRouter();
@BeforeMethod
public void prepare() {
this.sb = new StringBuilder();
this.router = new WxMessageRouter();
router 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_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);
router.route(message);
Assert.assertEquals(sb.toString(), WxConsts.MSG_TEXT + ",");
} }
@Test(dataProvider="messages-1")
public void testSimple(WxXmlMessage message, String expected) {
router.route(message);
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 { public static class WxEchoMessageHandler implements WxMessageHandler {

View File

@ -43,18 +43,18 @@ public class WxServiceTest {
Assert.assertTrue(StringUtils.isNotBlank(after)); Assert.assertTrue(StringUtils.isNotBlank(after));
} }
@Test(dependsOnMethods = "testRefreshAccessToken", enabled = false) @Test(dependsOnMethods = "testRefreshAccessToken")
public void sendCustomMessage() throws WxErrorException { public void sendCustomMessage() throws WxErrorException {
WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigProvider; WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigProvider;
WxCustomMessage message = new WxCustomMessage(); WxCustomMessage message = new WxCustomMessage();
message.setMsgtype(WxConsts.MSG_TEXT); message.setMsgtype(WxConsts.MSG_TEXT);
message.setTouser(configProvider.getOpenId()); message.setTouser(configProvider.getOpenId());
message.setContent("欢迎使用教务系统微信公众号\n下面\n<a href=\"http://www.baidu.com\">Hello World</a>"); message.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:<a href=\"http://www.baidu.com\">Hello World</a>");
wxService.sendCustomMessage(message); wxService.sendCustomMessage(message);
} }
@Test(dataProvider = "menu", enabled = true, dependsOnMethods = "testRefreshAccessToken") @Test(dataProvider = "menu", dependsOnMethods = "testRefreshAccessToken")
public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { public void testCreateMenu(WxMenu wxMenu) throws WxErrorException {
wxService.createMenu(wxMenu); wxService.createMenu(wxMenu);
} }
@ -64,7 +64,7 @@ public class WxServiceTest {
Assert.assertNotNull(wxService.getMenu()); Assert.assertNotNull(wxService.getMenu());
} }
@Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }, enabled = false) @Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" })
public void testDeleteMenu() throws WxErrorException { public void testDeleteMenu() throws WxErrorException {
wxService.deleteMenu(); wxService.deleteMenu();
} }