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

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">
<modelVersion>4.0.0</modelVersion>
<groupId>chanjarster.weixin</groupId>
<artifactId>weixin-java</artifactId>
<artifactId>weixin-toolset</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>WeiXin Java Toolset</name>
<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_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";
}

View File

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

View File

@ -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;
/**
* <pre>
* 微信消息路由器通过代码化的配置把来自微信的消息交给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()}
* </pre>
* @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 代表继续执行别的routerfalse 代表停止执行别的router
*/
protected boolean service(WxXmlMessage wxMessage) {
// 如果不匹配本规则那么接着执行后面的Rule
if (!test(wxMessage)) {
return true;
}
Map<String, Object> context = new HashMap<String, Object>();
// 如果拦截器不通过
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.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);

View File

@ -15,9 +15,9 @@ import org.apache.http.util.EntityUtils;
* @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 {
final StatusLine statusLine = response.getStatusLine();

View File

@ -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);
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 {

View File

@ -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下面\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);
}
@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();
}