@@ -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 | |||
``` |
@@ -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> | |||
@@ -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"; | |||
} |
@@ -5,7 +5,7 @@ import java.util.Map; | |||
import chanjarster.weixin.bean.WxXmlMessage; | |||
/** | |||
* 处理微信推送消息的处理器 | |||
* 处理微信推送消息的处理器接口 | |||
* @author chanjarster | |||
* | |||
*/ | |||
@@ -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 代表继续执行别的router,false 代表停止执行别的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) { | |||
@@ -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); | |||
@@ -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(); |
@@ -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 { | |||
@@ -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(); | |||
} | |||