| @@ -2,3 +2,6 @@ weixin-java | |||||
| =========== | =========== | ||||
| 微信java开发工具集 | 微信java开发工具集 | ||||
| ## 执行测试 | |||||
| 将 ''src/test/resources/test-config.sample.xml'' 改成 ''test-config.xml'' 并设置appId, secret, 一个过期的accessToken | |||||
| @@ -7,7 +7,7 @@ import chanjarster.weixin.bean.WxAccessToken; | |||||
| * @author chanjarster | * @author chanjarster | ||||
| * | * | ||||
| */ | */ | ||||
| public interface WxConfigProvider { | |||||
| public interface WxConfigStorage { | |||||
| public void updateAccessToken(WxAccessToken accessToken); | public void updateAccessToken(WxAccessToken accessToken); | ||||
| @@ -2,13 +2,14 @@ package chanjarster.weixin.api; | |||||
| public class WxConsts { | public class WxConsts { | ||||
| public static final String TEXT = "text"; | |||||
| public static final String IMAGE = "image"; | |||||
| public static final String VOICE = "voice"; | |||||
| public static final String MUSIC = "music"; | |||||
| public static final String VIDEO = "video"; | |||||
| public static final String NEWS = "news"; | |||||
| public static final String LOCATION = "location"; | |||||
| public static final String LINK = "link"; | |||||
| public static final String MSG_TEXT = "text"; | |||||
| public static final String MSG_IMAGE = "image"; | |||||
| public static final String MSG_VOICE = "voice"; | |||||
| public static final String MSG_MUSIC = "music"; | |||||
| public static final String MSG_VIDEO = "video"; | |||||
| public static final String MSG_NEWS = "news"; | |||||
| public static final String MSG_LOCATION = "location"; | |||||
| public static final String MSG_LINK = "link"; | |||||
| } | } | ||||
| @@ -3,11 +3,11 @@ package chanjarster.weixin.api; | |||||
| import chanjarster.weixin.bean.WxAccessToken; | import chanjarster.weixin.bean.WxAccessToken; | ||||
| /** | /** | ||||
| * 基于内存的微信配置provider | |||||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||||
| * @author chanjarster | * @author chanjarster | ||||
| * | * | ||||
| */ | */ | ||||
| public class WxMemoryConfigProvider implements WxConfigProvider { | |||||
| public class WxInMemoryConfigStorage implements WxConfigStorage { | |||||
| protected String appId; | protected String appId; | ||||
| protected String secret; | protected String secret; | ||||
| @@ -44,4 +44,24 @@ public class WxMemoryConfigProvider implements WxConfigProvider { | |||||
| return this.expiresIn; | return this.expiresIn; | ||||
| } | } | ||||
| public void setAppId(String appId) { | |||||
| this.appId = appId; | |||||
| } | |||||
| public void setSecret(String secret) { | |||||
| this.secret = secret; | |||||
| } | |||||
| public void setToken(String token) { | |||||
| this.token = token; | |||||
| } | |||||
| public void setAccessToken(String accessToken) { | |||||
| this.accessToken = accessToken; | |||||
| } | |||||
| public void setExpiresIn(int expiresIn) { | |||||
| this.expiresIn = expiresIn; | |||||
| } | |||||
| } | } | ||||
| @@ -11,6 +11,11 @@ import chanjarster.weixin.bean.WxXmlMessage; | |||||
| */ | */ | ||||
| public interface WxMessageHandler { | public interface WxMessageHandler { | ||||
| /** | |||||
| * | |||||
| * @param wxMessage | |||||
| * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
| */ | |||||
| public void handle(WxXmlMessage wxMessage, Map<String, Object> context); | public void handle(WxXmlMessage wxMessage, Map<String, Object> context); | ||||
| } | } | ||||
| @@ -5,10 +5,32 @@ 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; | ||||
| /** | /** | ||||
| * 微信消息路由器,通过代码化的配置,把来自微信的消息交给某个的handler处理 | |||||
| * <pre> | |||||
| * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | |||||
| * | |||||
| * 使用方法: | |||||
| * 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#reEnter()} | |||||
| * </pre> | |||||
| * @author qianjia | * @author qianjia | ||||
| * | * | ||||
| */ | */ | ||||
| @@ -20,7 +42,7 @@ public class WxMessageRouter { | |||||
| * 开始一个新的Route规则 | * 开始一个新的Route规则 | ||||
| * @return | * @return | ||||
| */ | */ | ||||
| public Rule start() { | |||||
| public Rule rule() { | |||||
| return new Rule(this); | return new Rule(this); | ||||
| } | } | ||||
| @@ -49,7 +71,7 @@ public class WxMessageRouter { | |||||
| private String content; | private String content; | ||||
| private boolean forward = false; | |||||
| private boolean reEnter = false; | |||||
| private List<WxMessageHandler> handlers = new ArrayList<WxMessageHandler>(); | private List<WxMessageHandler> handlers = new ArrayList<WxMessageHandler>(); | ||||
| @@ -100,11 +122,11 @@ public class WxMessageRouter { | |||||
| } | } | ||||
| /** | /** | ||||
| * 如果本规则命中,在执行完handler后,还会接着给后面的Rule执行 | |||||
| * 将消息交给后面的Rule处理 | |||||
| * @return | * @return | ||||
| */ | */ | ||||
| public Rule forward() { | |||||
| this.forward = true; | |||||
| public Rule reEnter() { | |||||
| this.reEnter = true; | |||||
| return this; | return this; | ||||
| } | } | ||||
| @@ -175,7 +197,7 @@ public class WxMessageRouter { | |||||
| // 如果拦截器不通过 | // 如果拦截器不通过 | ||||
| for (WxMessageInterceptor interceptor : this.interceptors) { | for (WxMessageInterceptor interceptor : this.interceptors) { | ||||
| if (!interceptor.intercept(wxMessage, context)) { | if (!interceptor.intercept(wxMessage, context)) { | ||||
| return this.forward; | |||||
| return this.reEnter; | |||||
| } | } | ||||
| } | } | ||||
| @@ -184,7 +206,7 @@ public class WxMessageRouter { | |||||
| interceptor.handle(wxMessage, context); | interceptor.handle(wxMessage, context); | ||||
| } | } | ||||
| return this.forward; | |||||
| return this.reEnter; | |||||
| } | } | ||||
| } | } | ||||
| @@ -10,45 +10,58 @@ import chanjarster.weixin.exception.WxErrorException; | |||||
| public interface WxService { | public interface WxService { | ||||
| /** | /** | ||||
| * 刷新access_token,这个方法是线程安全的 | |||||
| * 且在高并发情况下只会刷新一次,而不是刷新多次 | |||||
| * 在非必要情况下不要主动调用此方法 | |||||
| * 本service的所有方法都会在access_token过期的情况下调用此方法 | |||||
| * <pre> | |||||
| * 获取access_token,本方法线程安全 | |||||
| * 且在多线程同时刷新时只刷新一次,避免超出1200次的调用次数上限 | |||||
| * | * | ||||
| * 另:本service的所有方法都会在access_token过期是调用此方法 | |||||
| * | |||||
| * 程序员在非必要情况下尽量不要主动调用此方法 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | ||||
| * </pre> | |||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| */ | */ | ||||
| public void refreshAccessToken() throws WxErrorException; | public void refreshAccessToken() throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 发送客服消息 | * 发送客服消息 | ||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息 | |||||
| * </pre> | |||||
| * @param message | * @param message | ||||
| * @return | |||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| */ | */ | ||||
| public String sendCustomMessage(WxCustomMessage message) throws WxErrorException; | public String sendCustomMessage(WxCustomMessage message) throws WxErrorException; | ||||
| /** | /** | ||||
| * 创建菜单 | |||||
| * <pre> | |||||
| * 自定义菜单创建接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||||
| * </pre> | |||||
| * @param menu | * @param menu | ||||
| * @return | |||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| */ | */ | ||||
| public String createMenu(WxMenu menu) throws WxErrorException; | public String createMenu(WxMenu menu) throws WxErrorException; | ||||
| /** | /** | ||||
| * 删除菜单 | |||||
| * @return | |||||
| * <pre> | |||||
| * 自定义菜单删除接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||||
| * </pre> | |||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| */ | */ | ||||
| public String deleteMenu() throws WxErrorException; | public String deleteMenu() throws WxErrorException; | ||||
| /** | /** | ||||
| * 获得菜单 | |||||
| * <pre> | |||||
| * 自定义菜单查询接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||||
| * </pre> | |||||
| * @return | * @return | ||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| */ | */ | ||||
| public WxMenu getMenu() throws WxErrorException; | public WxMenu getMenu() throws WxErrorException; | ||||
| public void setWxConfigProvider(WxConfigProvider wxConfigProvider); | |||||
| public void setWxConfigProvider(WxConfigStorage wxConfigProvider); | |||||
| } | } | ||||
| @@ -1,14 +1,15 @@ | |||||
| package chanjarster.weixin.api; | package chanjarster.weixin.api; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.nio.charset.Charset; | |||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.apache.http.Consts; | |||||
| import org.apache.http.client.ClientProtocolException; | import org.apache.http.client.ClientProtocolException; | ||||
| import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||
| import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||
| import org.apache.http.client.methods.HttpPost; | import org.apache.http.client.methods.HttpPost; | ||||
| import org.apache.http.entity.ContentType; | |||||
| import org.apache.http.entity.StringEntity; | import org.apache.http.entity.StringEntity; | ||||
| import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
| @@ -19,6 +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; | |||||
| public class WxServiceImpl implements WxService { | public class WxServiceImpl implements WxService { | ||||
| @@ -31,9 +33,7 @@ public class WxServiceImpl implements WxService { | |||||
| protected static final CloseableHttpClient httpclient = HttpClients.createDefault(); | protected static final CloseableHttpClient httpclient = HttpClients.createDefault(); | ||||
| protected static final Charset UTF8 = Charset.forName("UTF-8"); | |||||
| protected WxConfigProvider wxConfigProvider; | |||||
| protected WxConfigStorage wxConfigProvider; | |||||
| public void refreshAccessToken() throws WxErrorException { | public void refreshAccessToken() throws WxErrorException { | ||||
| if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | ||||
| @@ -46,6 +46,10 @@ public class WxServiceImpl implements WxService { | |||||
| HttpGet httpGet = new HttpGet(url); | HttpGet httpGet = new HttpGet(url); | ||||
| CloseableHttpResponse response = httpclient.execute(httpGet); | CloseableHttpResponse response = httpclient.execute(httpGet); | ||||
| String resultContent = new BasicResponseHandler().handleResponse(response); | String resultContent = new BasicResponseHandler().handleResponse(response); | ||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrcode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | ||||
| wxConfigProvider.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in()); | wxConfigProvider.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in()); | ||||
| } catch (ClientProtocolException e) { | } catch (ClientProtocolException e) { | ||||
| @@ -68,17 +72,35 @@ public class WxServiceImpl implements WxService { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 发送客服消息 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息 | |||||
| * @param message | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| public String sendCustomMessage(WxCustomMessage message) throws WxErrorException { | public String sendCustomMessage(WxCustomMessage message) throws WxErrorException { | ||||
| String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | ||||
| return post(url, message.toJson()); | return post(url, message.toJson()); | ||||
| } | } | ||||
| public String createMenu(WxMenu menu) throws WxErrorException { | |||||
| String url = "https://api.weixin.qq.com/cgi-bin/menu/create"; | |||||
| return post(url, menu.toJson()); | |||||
| } | |||||
| public String deleteMenu() throws WxErrorException { | |||||
| String url = "https://api.weixin.qq.com/cgi-bin/menu/delete"; | |||||
| return get(url, null); | |||||
| } | |||||
| public WxMenu getMenu() throws WxErrorException { | |||||
| String url = "https://api.weixin.qq.com/cgi-bin/menu/get"; | |||||
| try { | |||||
| String resultContent = get(url, null); | |||||
| return WxMenu.fromJson(resultContent); | |||||
| } catch (WxErrorException e) { | |||||
| // 46003 不存在的菜单数据 | |||||
| if (e.getError().getErrcode() == 46003) { | |||||
| return null; | |||||
| } | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| protected String post(String uri, String data) throws WxErrorException { | protected String post(String uri, String data) throws WxErrorException { | ||||
| return execute("POST", uri, data); | return execute("POST", uri, data); | ||||
| } | } | ||||
| @@ -106,20 +128,26 @@ public class WxServiceImpl implements WxService { | |||||
| String resultContent = null; | String resultContent = null; | ||||
| if ("POST".equals(method)) { | if ("POST".equals(method)) { | ||||
| HttpPost httpPost = new HttpPost(uriWithAccessToken); | HttpPost httpPost = new HttpPost(uriWithAccessToken); | ||||
| StringEntity entity = new StringEntity(data, UTF8); | |||||
| httpPost.setEntity(entity); | |||||
| if (data != null) { | |||||
| StringEntity entity = new StringEntity(data, Consts.UTF_8); | |||||
| httpPost.setEntity(entity); | |||||
| } | |||||
| CloseableHttpResponse response = httpclient.execute(httpPost); | CloseableHttpResponse response = httpclient.execute(httpPost); | ||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); | |||||
| } else if ("GET".equals(method)) { | } else if ("GET".equals(method)) { | ||||
| if (data != null) { | |||||
| uriWithAccessToken += uriWithAccessToken.endsWith("&") ? data : '&' + data; | |||||
| } | |||||
| HttpGet httpGet = new HttpGet(uriWithAccessToken); | HttpGet httpGet = new HttpGet(uriWithAccessToken); | ||||
| CloseableHttpResponse response = httpclient.execute(httpGet); | CloseableHttpResponse response = httpclient.execute(httpGet); | ||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString()); | |||||
| resultContent = Utf8StringResponseHandler.INSTANCE.handleResponse(response); | |||||
| } | } | ||||
| WxError error = WxError.fromJson(resultContent); | WxError error = WxError.fromJson(resultContent); | ||||
| /* | /* | ||||
| * 关于微信返回错误码 详情请看 http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 | |||||
| * 40001 微信图片不对 | |||||
| * 发生以下情况时尝试刷新access_token | |||||
| * 40001 获取access_token时AppSecret错误,或者access_token无效 | |||||
| * 42001 access_token超时 | * 42001 access_token超时 | ||||
| */ | */ | ||||
| if (error.getErrcode() == 42001 || error.getErrcode() == 40001) { | if (error.getErrcode() == 42001 || error.getErrcode() == 40001) { | ||||
| @@ -137,36 +165,7 @@ public class WxServiceImpl implements WxService { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * | |||||
| * @param menu | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| public String createMenu(WxMenu menu) throws WxErrorException { | |||||
| // TODO | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| public String deleteMenu() throws WxErrorException { | |||||
| // TODO | |||||
| return null; | |||||
| } | |||||
| /** | |||||
| * | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| public WxMenu getMenu() throws WxErrorException { | |||||
| // TODO | |||||
| return null; | |||||
| } | |||||
| public void setWxConfigProvider(WxConfigProvider wxConfigProvider) { | |||||
| public void setWxConfigProvider(WxConfigStorage wxConfigProvider) { | |||||
| this.wxConfigProvider = wxConfigProvider; | this.wxConfigProvider = wxConfigProvider; | ||||
| } | } | ||||
| @@ -1,12 +1,108 @@ | |||||
| package chanjarster.weixin.bean; | package chanjarster.weixin.bean; | ||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import chanjarster.weixin.util.WxGsonBuilder; | import chanjarster.weixin.util.WxGsonBuilder; | ||||
| /** | |||||
| * 微信错误码说明 | |||||
| * http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 | |||||
| * @author chanjarster | |||||
| * | |||||
| */ | |||||
| public class WxError { | public class WxError { | ||||
| private int errcode; | |||||
| protected static final Map<Integer, String> errMap = new HashMap<Integer, String>(); | |||||
| static { | |||||
| errMap.put(-1, "系统繁忙"); | |||||
| errMap.put(0, "请求成功"); | |||||
| errMap.put(40001, "获取access_token时AppSecret错误,或者access_token无效"); | |||||
| errMap.put(40002, "不合法的凭证类型"); | |||||
| errMap.put(40003, "不合法的OpenID"); | |||||
| errMap.put(40004, "不合法的媒体文件类型"); | |||||
| errMap.put(40005, "不合法的文件类型"); | |||||
| errMap.put(40006, "不合法的文件大小"); | |||||
| errMap.put(40007, "不合法的媒体文件id"); | |||||
| errMap.put(40008, "不合法的消息类型"); | |||||
| errMap.put(40009, "不合法的图片文件大小"); | |||||
| errMap.put(40010, "不合法的语音文件大小"); | |||||
| errMap.put(40011, "不合法的视频文件大小"); | |||||
| errMap.put(40012, "不合法的缩略图文件大小"); | |||||
| errMap.put(40013, "不合法的APPID"); | |||||
| errMap.put(40014, "不合法的access_token"); | |||||
| errMap.put(40015, "不合法的菜单类型"); | |||||
| errMap.put(40016, "不合法的按钮个数"); | |||||
| errMap.put(40017, "不合法的按钮个数"); | |||||
| errMap.put(40018, "不合法的按钮名字长度"); | |||||
| errMap.put(40019, "不合法的按钮KEY长度"); | |||||
| errMap.put(40020, "不合法的按钮URL长度"); | |||||
| errMap.put(40021, "不合法的菜单版本号"); | |||||
| errMap.put(40022, "不合法的子菜单级数"); | |||||
| errMap.put(40023, "不合法的子菜单按钮个数"); | |||||
| errMap.put(40024, "不合法的子菜单按钮类型"); | |||||
| errMap.put(40025, "不合法的子菜单按钮名字长度"); | |||||
| errMap.put(40026, "不合法的子菜单按钮KEY长度"); | |||||
| errMap.put(40027, "不合法的子菜单按钮URL长度"); | |||||
| errMap.put(40028, "不合法的自定义菜单使用用户"); | |||||
| errMap.put(40029, "不合法的oauth_code"); | |||||
| errMap.put(40030, "不合法的refresh_token"); | |||||
| errMap.put(40031, "不合法的openid列表"); | |||||
| errMap.put(40032, "不合法的openid列表长度"); | |||||
| errMap.put(40033, "不合法的请求字符,不能包含\\uxxxx格式的字符"); | |||||
| errMap.put(40035, "不合法的参数"); | |||||
| errMap.put(40038, "不合法的请求格式"); | |||||
| errMap.put(40039, "不合法的URL长度"); | |||||
| errMap.put(40050, "不合法的分组id"); | |||||
| errMap.put(40051, "分组名字不合法"); | |||||
| errMap.put(41001, "缺少access_token参数"); | |||||
| errMap.put(41002, "缺少appid参数"); | |||||
| errMap.put(41003, "缺少refresh_token参数"); | |||||
| errMap.put(41004, "缺少secret参数"); | |||||
| errMap.put(41005, "缺少多媒体文件数据"); | |||||
| errMap.put(41006, "缺少media_id参数"); | |||||
| errMap.put(41007, "缺少子菜单数据"); | |||||
| errMap.put(41008, "缺少oauth code"); | |||||
| errMap.put(41009, "缺少openid"); | |||||
| errMap.put(42001, "access_token超时"); | |||||
| errMap.put(42002, "refresh_token超时"); | |||||
| errMap.put(42003, "oauth_code超时"); | |||||
| errMap.put(43001, "需要GET请求"); | |||||
| errMap.put(43002, "需要POST请求"); | |||||
| errMap.put(43003, "需要HTTPS请求"); | |||||
| errMap.put(43004, "需要接收者关注"); | |||||
| errMap.put(43005, "需要好友关系"); | |||||
| errMap.put(44001, "多媒体文件为空"); | |||||
| errMap.put(44002, "POST的数据包为空"); | |||||
| errMap.put(44003, "图文消息内容为空"); | |||||
| errMap.put(44004, "文本消息内容为空"); | |||||
| errMap.put(45001, "多媒体文件大小超过限制"); | |||||
| errMap.put(45002, "消息内容超过限制"); | |||||
| errMap.put(45003, "标题字段超过限制"); | |||||
| errMap.put(45004, "描述字段超过限制"); | |||||
| errMap.put(45005, "链接字段超过限制"); | |||||
| errMap.put(45006, "图片链接字段超过限制"); | |||||
| errMap.put(45007, "语音播放时间超过限制"); | |||||
| errMap.put(45008, "图文消息超过限制"); | |||||
| errMap.put(45009, "接口调用超过限制"); | |||||
| errMap.put(45010, "创建菜单个数超过限制"); | |||||
| errMap.put(45015, "回复时间超过限制"); | |||||
| errMap.put(45016, "系统分组,不允许修改"); | |||||
| errMap.put(45017, "分组名字过长"); | |||||
| errMap.put(45018, "分组数量超过上限"); | |||||
| errMap.put(46001, "不存在媒体数据"); | |||||
| errMap.put(46002, "不存在的菜单版本"); | |||||
| errMap.put(46003, "不存在的菜单数据"); | |||||
| errMap.put(46004, "不存在的用户"); | |||||
| errMap.put(47001, "解析JSON/XML内容错误"); | |||||
| errMap.put(48001, "api功能未授权"); | |||||
| errMap.put(50001, "用户未授权该api"); | |||||
| } | |||||
| private String errmsg; | |||||
| protected int errcode; | |||||
| protected String errmsg; | |||||
| public int getErrcode() { | public int getErrcode() { | ||||
| return errcode; | return errcode; | ||||
| @@ -24,14 +120,17 @@ public class WxError { | |||||
| this.errmsg = errmsg; | this.errmsg = errmsg; | ||||
| } | } | ||||
| public String getDescription() { | |||||
| return errMap.get(errcode); | |||||
| } | |||||
| public static WxError fromJson(String json) { | public static WxError fromJson(String json) { | ||||
| return WxGsonBuilder.create().fromJson(json, WxError.class); | return WxGsonBuilder.create().fromJson(json, WxError.class); | ||||
| } | } | ||||
| @Override | @Override | ||||
| public String toString() { | public String toString() { | ||||
| return "{ errcode=" + errcode + ", errmsg=" + errmsg + "}"; | |||||
| return "微信错误 errcode=" + errcode + ", errmsg=" + errmsg + ", description=" + getDescription(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ package chanjarster.weixin.bean; | |||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import javax.management.RuntimeErrorException; | |||||
| import javax.xml.bind.JAXBException; | import javax.xml.bind.JAXBException; | ||||
| import javax.xml.bind.annotation.XmlAccessType; | import javax.xml.bind.annotation.XmlAccessType; | ||||
| import javax.xml.bind.annotation.XmlAccessorType; | import javax.xml.bind.annotation.XmlAccessorType; | ||||
| @@ -291,27 +292,24 @@ public class WxXmlMessage { | |||||
| try { | try { | ||||
| return XmlTransformer.toXml(WxXmlMessage.class, this); | return XmlTransformer.toXml(WxXmlMessage.class, this); | ||||
| } catch (JAXBException e) { | } catch (JAXBException e) { | ||||
| e.printStackTrace(); | |||||
| throw new RuntimeException(e); | |||||
| } | } | ||||
| return ""; | |||||
| } | } | ||||
| public static WxXmlMessage fromXml(String xml) { | public static WxXmlMessage fromXml(String xml) { | ||||
| try { | try { | ||||
| return XmlTransformer.fromXml(WxXmlMessage.class, xml); | return XmlTransformer.fromXml(WxXmlMessage.class, xml); | ||||
| } catch (JAXBException e) { | } catch (JAXBException e) { | ||||
| e.printStackTrace(); | |||||
| throw new RuntimeException(e); | |||||
| } | } | ||||
| return null; | |||||
| } | } | ||||
| public static WxXmlMessage fromXml(InputStream is) { | public static WxXmlMessage fromXml(InputStream is) { | ||||
| try { | try { | ||||
| return XmlTransformer.fromXml(WxXmlMessage.class, is); | return XmlTransformer.fromXml(WxXmlMessage.class, is); | ||||
| } catch (JAXBException e) { | } catch (JAXBException e) { | ||||
| e.printStackTrace(); | |||||
| throw new RuntimeException(e); | |||||
| } | } | ||||
| return null; | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -0,0 +1,32 @@ | |||||
| package chanjarster.weixin.util; | |||||
| import java.io.IOException; | |||||
| import org.apache.http.Consts; | |||||
| import org.apache.http.HttpEntity; | |||||
| import org.apache.http.HttpResponse; | |||||
| import org.apache.http.StatusLine; | |||||
| import org.apache.http.client.HttpResponseException; | |||||
| import org.apache.http.client.ResponseHandler; | |||||
| import org.apache.http.util.EntityUtils; | |||||
| /** | |||||
| * copy from {@link org.apache.http.impl.client.BasicResponseHandler} | |||||
| * @author chanjarster | |||||
| * | |||||
| */ | |||||
| public class Utf8StringResponseHandler implements ResponseHandler<String> { | |||||
| public static final ResponseHandler<String> INSTANCE = new Utf8StringResponseHandler(); | |||||
| public String handleResponse(final HttpResponse response) throws HttpResponseException, IOException { | |||||
| final StatusLine statusLine = response.getStatusLine(); | |||||
| final HttpEntity entity = response.getEntity(); | |||||
| if (statusLine.getStatusCode() >= 300) { | |||||
| EntityUtils.consume(entity); | |||||
| throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); | |||||
| } | |||||
| return entity == null ? null : EntityUtils.toString(entity, Consts.UTF_8); | |||||
| } | |||||
| } | |||||
| @@ -32,25 +32,25 @@ public class WxCustomMessageGsonAdapter implements JsonSerializer<WxCustomMessag | |||||
| messageJson.addProperty("touser", message.getTouser()); | messageJson.addProperty("touser", message.getTouser()); | ||||
| messageJson.addProperty("msgtype", message.getMsgtype()); | messageJson.addProperty("msgtype", message.getMsgtype()); | ||||
| if (WxConsts.TEXT.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_TEXT.equals(message.getMsgtype())) { | |||||
| JsonObject text = new JsonObject(); | JsonObject text = new JsonObject(); | ||||
| text.addProperty("content", message.getContent()); | text.addProperty("content", message.getContent()); | ||||
| messageJson.add("text", text); | messageJson.add("text", text); | ||||
| } | } | ||||
| if (WxConsts.IMAGE.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_IMAGE.equals(message.getMsgtype())) { | |||||
| JsonObject image = new JsonObject(); | JsonObject image = new JsonObject(); | ||||
| image.addProperty("media_id", message.getMedia_id()); | image.addProperty("media_id", message.getMedia_id()); | ||||
| messageJson.add("image", image); | messageJson.add("image", image); | ||||
| } | } | ||||
| if (WxConsts.VOICE.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_VOICE.equals(message.getMsgtype())) { | |||||
| JsonObject voice = new JsonObject(); | JsonObject voice = new JsonObject(); | ||||
| voice.addProperty("media_id", message.getMedia_id()); | voice.addProperty("media_id", message.getMedia_id()); | ||||
| messageJson.add("voice", voice); | messageJson.add("voice", voice); | ||||
| } | } | ||||
| if (WxConsts.VIDEO.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_VIDEO.equals(message.getMsgtype())) { | |||||
| JsonObject video = new JsonObject(); | JsonObject video = new JsonObject(); | ||||
| video.addProperty("media_id", message.getMedia_id()); | video.addProperty("media_id", message.getMedia_id()); | ||||
| video.addProperty("thumb_media_id", message.getThumb_media_id()); | video.addProperty("thumb_media_id", message.getThumb_media_id()); | ||||
| @@ -59,7 +59,7 @@ public class WxCustomMessageGsonAdapter implements JsonSerializer<WxCustomMessag | |||||
| messageJson.add("video", video); | messageJson.add("video", video); | ||||
| } | } | ||||
| if (WxConsts.MUSIC.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_MUSIC.equals(message.getMsgtype())) { | |||||
| JsonObject music = new JsonObject(); | JsonObject music = new JsonObject(); | ||||
| music.addProperty("title", message.getTitle()); | music.addProperty("title", message.getTitle()); | ||||
| music.addProperty("description", message.getDescription()); | music.addProperty("description", message.getDescription()); | ||||
| @@ -69,7 +69,7 @@ public class WxCustomMessageGsonAdapter implements JsonSerializer<WxCustomMessag | |||||
| messageJson.add("music", music); | messageJson.add("music", music); | ||||
| } | } | ||||
| if (WxConsts.NEWS.equals(message.getMsgtype())) { | |||||
| if (WxConsts.MSG_NEWS.equals(message.getMsgtype())) { | |||||
| JsonArray articleJsonArray = new JsonArray(); | JsonArray articleJsonArray = new JsonArray(); | ||||
| for (WxArticle article : message.getArticles()) { | for (WxArticle article : message.getArticles()) { | ||||
| JsonObject articleJson = new JsonObject(); | JsonObject articleJson = new JsonObject(); | ||||
| @@ -59,8 +59,14 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||||
| } | } | ||||
| public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||||
| /* | |||||
| * 操蛋的微信 | |||||
| * 创建菜单时是 { button : ... } | |||||
| * 查询菜单时是 { menu : { button : ... } } | |||||
| */ | |||||
| WxMenu menu = new WxMenu(); | WxMenu menu = new WxMenu(); | ||||
| JsonArray buttonsJson = json.getAsJsonObject().get("button").getAsJsonArray(); | |||||
| JsonObject menuJson = json.getAsJsonObject().get("menu").getAsJsonObject(); | |||||
| JsonArray buttonsJson = menuJson.get("button").getAsJsonArray(); | |||||
| for (int i = 0; i < buttonsJson.size(); i++) { | for (int i = 0; i < buttonsJson.size(); i++) { | ||||
| JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | ||||
| WxMenuButton button = convertFromJson(buttonJson); | WxMenuButton button = convertFromJson(buttonJson); | ||||
| @@ -11,6 +11,8 @@ import javax.xml.bind.JAXBException; | |||||
| import javax.xml.bind.Marshaller; | import javax.xml.bind.Marshaller; | ||||
| import javax.xml.bind.Unmarshaller; | import javax.xml.bind.Unmarshaller; | ||||
| import org.xml.sax.InputSource; | |||||
| import com.sun.xml.bind.marshaller.CharacterEscapeHandler; | import com.sun.xml.bind.marshaller.CharacterEscapeHandler; | ||||
| public class XmlTransformer { | public class XmlTransformer { | ||||
| @@ -32,7 +34,9 @@ public class XmlTransformer { | |||||
| public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | ||||
| JAXBContext context = JAXBContext.newInstance(clazz); | JAXBContext context = JAXBContext.newInstance(clazz); | ||||
| Unmarshaller um = context.createUnmarshaller(); | Unmarshaller um = context.createUnmarshaller(); | ||||
| T object = (T) um.unmarshal(is); | |||||
| InputSource inputSource = new InputSource(is); | |||||
| inputSource.setEncoding("utf-8"); | |||||
| T object = (T) um.unmarshal(inputSource); | |||||
| return object; | return object; | ||||
| } | } | ||||
| @@ -1,6 +1,48 @@ | |||||
| package chanjarster.weixin.api; | package chanjarster.weixin.api; | ||||
| // TODO | |||||
| import java.util.Map; | |||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Test; | |||||
| import chanjarster.weixin.bean.WxXmlMessage; | |||||
| @Test | |||||
| public class WxMessageRouterTest { | public class WxMessageRouterTest { | ||||
| public void testSimple() { | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| WxMessageRouter router = new WxMessageRouter(); | |||||
| router | |||||
| .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() | |||||
| ; | |||||
| WxXmlMessage message = new WxXmlMessage(); | |||||
| message.setMsgType(WxConsts.MSG_TEXT); | |||||
| router.route(message); | |||||
| Assert.assertEquals(sb.toString(), WxConsts.MSG_TEXT + ","); | |||||
| } | |||||
| public static class WxEchoMessageHandler implements WxMessageHandler { | |||||
| private StringBuilder sb; | |||||
| private String echoStr; | |||||
| public WxEchoMessageHandler(StringBuilder sb, String echoStr) { | |||||
| this.sb = sb; | |||||
| this.echoStr = echoStr; | |||||
| } | |||||
| @Override | |||||
| public void handle(WxXmlMessage wxMessage, Map<String, Object> context) { | |||||
| sb.append(this.echoStr).append(','); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -9,68 +9,117 @@ import javax.xml.bind.annotation.XmlRootElement; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.testng.Assert; | import org.testng.Assert; | ||||
| import org.testng.annotations.BeforeTest; | |||||
| import org.testng.annotations.DataProvider; | import org.testng.annotations.DataProvider; | ||||
| import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
| import chanjarster.weixin.api.WxConsts; | |||||
| import chanjarster.weixin.api.WxMemoryConfigProvider; | |||||
| import chanjarster.weixin.api.WxService; | |||||
| import chanjarster.weixin.api.WxServiceImpl; | |||||
| import chanjarster.weixin.bean.WxCustomMessage; | import chanjarster.weixin.bean.WxCustomMessage; | ||||
| import chanjarster.weixin.bean.WxMenu; | |||||
| import chanjarster.weixin.bean.WxMenu.WxMenuButton; | |||||
| import chanjarster.weixin.exception.WxErrorException; | import chanjarster.weixin.exception.WxErrorException; | ||||
| import chanjarster.weixin.util.XmlTransformer; | import chanjarster.weixin.util.XmlTransformer; | ||||
| public class WxServiceTest { | public class WxServiceTest { | ||||
| @Test(dataProvider = "configs") | |||||
| public void testRefreshAccessToken(WxTestConfigProvider config) throws WxErrorException { | |||||
| String before = config.getAccessToken(); | |||||
| WxService wxService = new WxServiceImpl(); | |||||
| wxService.setWxConfigProvider(config); | |||||
| private WxServiceImpl wxService; | |||||
| @BeforeTest | |||||
| public void prepare() throws JAXBException { | |||||
| InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | |||||
| WxXmlConfigStorage config1 = XmlTransformer.fromXml(WxXmlConfigStorage.class, is1); | |||||
| this.wxService = new WxServiceImpl(); | |||||
| this.wxService.setWxConfigProvider(config1); | |||||
| } | |||||
| @Test | |||||
| public void testRefreshAccessToken() throws WxErrorException { | |||||
| WxConfigStorage configProvider = wxService.wxConfigProvider; | |||||
| String before = configProvider.getAccessToken(); | |||||
| wxService.refreshAccessToken(); | wxService.refreshAccessToken(); | ||||
| String after = config.getAccessToken(); | |||||
| String after = configProvider.getAccessToken(); | |||||
| Assert.assertNotEquals(before, after); | Assert.assertNotEquals(before, after); | ||||
| Assert.assertTrue(StringUtils.isNotBlank(after)); | Assert.assertTrue(StringUtils.isNotBlank(after)); | ||||
| } | } | ||||
| @Test(dataProvider = "configs") | |||||
| public void sendCustomMessage(WxTestConfigProvider config) throws WxErrorException { | |||||
| WxService wxService = new WxServiceImpl(); | |||||
| wxService.setWxConfigProvider(config); | |||||
| @Test(dependsOnMethods = "testRefreshAccessToken", enabled = false) | |||||
| public void sendCustomMessage() throws WxErrorException { | |||||
| WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigProvider; | |||||
| WxCustomMessage message = new WxCustomMessage(); | WxCustomMessage message = new WxCustomMessage(); | ||||
| message.setMsgtype(WxConsts.TEXT); | |||||
| message.setTouser(config.getOpenId()); | |||||
| message.setContent("欢迎使用教务系统微信公众号\n下面\n<a href=\"http://192.168.1.249:9180/eams-rc/login.action\">Hello World</a>"); | |||||
| message.setMsgtype(WxConsts.MSG_TEXT); | |||||
| message.setTouser(configProvider.getOpenId()); | |||||
| message.setContent("欢迎使用教务系统微信公众号\n下面\n<a href=\"http://www.baidu.com\">Hello World</a>"); | |||||
| wxService.sendCustomMessage(message); | wxService.sendCustomMessage(message); | ||||
| } | } | ||||
| /** | |||||
| * 返回新的access_token | |||||
| * @return | |||||
| * @throws JAXBException | |||||
| */ | |||||
| @DataProvider(name = "configs") | |||||
| public Object[][] getConfig() throws JAXBException { | |||||
| /** | |||||
| * 将 src/test/resources/test-config.sample.xml 改成 test-config.xml 并设置appId, secret, 一个过期的accessToken | |||||
| */ | |||||
| // 没有access_token | |||||
| InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | |||||
| WxTestConfigProvider config1 = XmlTransformer.fromXml(WxTestConfigProvider.class, is1); | |||||
| @Test(dataProvider = "menu", enabled = true, dependsOnMethods = "testRefreshAccessToken") | |||||
| public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { | |||||
| wxService.createMenu(wxMenu); | |||||
| } | |||||
| @Test(dependsOnMethods = { "testRefreshAccessToken" , "testCreateMenu"}) | |||||
| public void testGetMenu() throws WxErrorException { | |||||
| Assert.assertNotNull(wxService.getMenu()); | |||||
| } | |||||
| @Test(dependsOnMethods = { "testRefreshAccessToken", "testGetMenu" }, enabled = false) | |||||
| public void testDeleteMenu() throws WxErrorException { | |||||
| wxService.deleteMenu(); | |||||
| } | |||||
| @DataProvider(name="menu") | |||||
| public Object[][] getMenu() throws JAXBException { | |||||
| WxMenu menu = new WxMenu(); | |||||
| WxMenuButton button1 = new WxMenuButton(); | |||||
| button1.setType("click"); | |||||
| button1.setName("今日歌曲"); | |||||
| button1.setKey("V1001_TODAY_MUSIC"); | |||||
| WxMenuButton button2 = new WxMenuButton(); | |||||
| button2.setType("click"); | |||||
| button2.setName("歌手简介"); | |||||
| button2.setKey("V1001_TODAY_SINGER"); | |||||
| WxMenuButton button3 = new WxMenuButton(); | |||||
| button3.setName("菜单"); | |||||
| menu.getButton().add(button1); | |||||
| menu.getButton().add(button2); | |||||
| menu.getButton().add(button3); | |||||
| WxMenuButton button31 = new WxMenuButton(); | |||||
| button31.setType("view"); | |||||
| button31.setName("搜索"); | |||||
| button31.setUrl("http://www.soso.com/"); | |||||
| WxMenuButton button32 = new WxMenuButton(); | |||||
| button32.setType("view"); | |||||
| button32.setName("视频"); | |||||
| button32.setUrl("http://v.qq.com/"); | |||||
| WxMenuButton button33 = new WxMenuButton(); | |||||
| button33.setType("click"); | |||||
| button33.setName("赞一下我们"); | |||||
| button33.setKey("V1001_GOOD"); | |||||
| button3.getSub_button().add(button31); | |||||
| button3.getSub_button().add(button32); | |||||
| button3.getSub_button().add(button33); | |||||
| return new Object[][] { | return new Object[][] { | ||||
| new Object[] { | new Object[] { | ||||
| config1 | |||||
| menu | |||||
| } | } | ||||
| }; | }; | ||||
| } | } | ||||
| @XmlRootElement(name = "xml") | @XmlRootElement(name = "xml") | ||||
| @XmlAccessorType(XmlAccessType.FIELD) | @XmlAccessorType(XmlAccessType.FIELD) | ||||
| public static class WxTestConfigProvider extends WxMemoryConfigProvider { | |||||
| public static class WxXmlConfigStorage extends WxInMemoryConfigStorage { | |||||
| protected String openId; | protected String openId; | ||||
| @@ -13,7 +13,7 @@ public class WxCustomMessageTest { | |||||
| public void testTextReply() { | public void testTextReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.TEXT); | |||||
| reply.setMsgtype(WxConsts.MSG_TEXT); | |||||
| reply.setContent("sfsfdsdf"); | reply.setContent("sfsfdsdf"); | ||||
| Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); | Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ public class WxCustomMessageTest { | |||||
| public void testImageReply() { | public void testImageReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.IMAGE); | |||||
| reply.setMsgtype(WxConsts.MSG_IMAGE); | |||||
| reply.setMedia_id("MEDIA_ID"); | reply.setMedia_id("MEDIA_ID"); | ||||
| Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); | Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); | ||||
| } | } | ||||
| @@ -29,7 +29,7 @@ public class WxCustomMessageTest { | |||||
| public void testVoiceReply() { | public void testVoiceReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.VOICE); | |||||
| reply.setMsgtype(WxConsts.MSG_VOICE); | |||||
| reply.setMedia_id("MEDIA_ID"); | reply.setMedia_id("MEDIA_ID"); | ||||
| Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); | Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); | ||||
| } | } | ||||
| @@ -37,7 +37,7 @@ public class WxCustomMessageTest { | |||||
| public void testVideoReply() { | public void testVideoReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.VIDEO); | |||||
| reply.setMsgtype(WxConsts.MSG_VIDEO); | |||||
| reply.setMedia_id("MEDIA_ID"); | reply.setMedia_id("MEDIA_ID"); | ||||
| reply.setThumb_media_id("MEDIA_ID"); | reply.setThumb_media_id("MEDIA_ID"); | ||||
| reply.setTitle("TITLE"); | reply.setTitle("TITLE"); | ||||
| @@ -48,7 +48,7 @@ public class WxCustomMessageTest { | |||||
| public void testMusicReply() { | public void testMusicReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.MUSIC); | |||||
| reply.setMsgtype(WxConsts.MSG_MUSIC); | |||||
| reply.setThumb_media_id("MEDIA_ID"); | reply.setThumb_media_id("MEDIA_ID"); | ||||
| reply.setDescription("DESCRIPTION"); | reply.setDescription("DESCRIPTION"); | ||||
| reply.setTitle("TITLE"); | reply.setTitle("TITLE"); | ||||
| @@ -60,7 +60,7 @@ public class WxCustomMessageTest { | |||||
| public void testNewsReply() { | public void testNewsReply() { | ||||
| WxCustomMessage reply = new WxCustomMessage(); | WxCustomMessage reply = new WxCustomMessage(); | ||||
| reply.setTouser("OPENID"); | reply.setTouser("OPENID"); | ||||
| reply.setMsgtype(WxConsts.NEWS); | |||||
| reply.setMsgtype(WxConsts.MSG_NEWS); | |||||
| WxArticle article1 = new WxArticle(); | WxArticle article1 = new WxArticle(); | ||||
| article1.setUrl("URL"); | article1.setUrl("URL"); | ||||
| @@ -77,7 +77,6 @@ public class WxCustomMessageTest { | |||||
| reply.getArticles().add(article2); | reply.getArticles().add(article2); | ||||
| System.out.println(reply.toJson()); | |||||
| Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}"); | Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}"); | ||||
| } | } | ||||
| @@ -1,6 +1,5 @@ | |||||
| package chanjarster.weixin.bean; | package chanjarster.weixin.bean; | ||||
| import org.apache.http.util.Asserts; | |||||
| import org.testng.Assert; | import org.testng.Assert; | ||||
| import org.testng.annotations.DataProvider; | import org.testng.annotations.DataProvider; | ||||
| import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
| @@ -10,13 +9,13 @@ import chanjarster.weixin.bean.WxMenu.WxMenuButton; | |||||
| @Test | @Test | ||||
| public class WxMenuTest { | public class WxMenuTest { | ||||
| @Test(dataProvider="json") | |||||
| @Test(dataProvider="wxReturnMenu") | |||||
| public void testFromJson(String json) { | public void testFromJson(String json) { | ||||
| WxMenu menu = WxMenu.fromJson(json); | WxMenu menu = WxMenu.fromJson(json); | ||||
| Assert.assertEquals(menu.getButton().size(), 3); | Assert.assertEquals(menu.getButton().size(), 3); | ||||
| } | } | ||||
| @Test(dataProvider="json") | |||||
| @Test(dataProvider="wxPushMenu") | |||||
| public void testToJson(String json) { | public void testToJson(String json) { | ||||
| WxMenu menu = new WxMenu(); | WxMenu menu = new WxMenu(); | ||||
| WxMenuButton button1 = new WxMenuButton(); | WxMenuButton button1 = new WxMenuButton(); | ||||
| @@ -55,12 +54,20 @@ public class WxMenuTest { | |||||
| button3.getSub_button().add(button32); | button3.getSub_button().add(button32); | ||||
| button3.getSub_button().add(button33); | button3.getSub_button().add(button33); | ||||
| System.out.println(menu.toJson()); | |||||
| Assert.assertEquals(menu.toJson(), json); | Assert.assertEquals(menu.toJson(), json); | ||||
| } | } | ||||
| @DataProvider(name="json") | |||||
| public Object[][] getMenuJson() { | |||||
| @Test(dataProvider="wxReturnMenu") | |||||
| public Object[][] wxReturnMenu() { | |||||
| Object[][] res = menuJson(); | |||||
| String json = "{ \"menu\" : " + res[0][0] + " }"; | |||||
| return new Object[][] { | |||||
| new Object[] { json } | |||||
| }; | |||||
| } | |||||
| @DataProvider(name="wxPushMenu") | |||||
| public Object[][] menuJson() { | |||||
| String json = | String json = | ||||
| "{" | "{" | ||||
| +"\"button\":[" | +"\"button\":[" | ||||
| @@ -40,7 +40,7 @@ public class WxXmlMessageTest { | |||||
| Assert.assertEquals(wxMessage.getToUserName(), "toUser"); | Assert.assertEquals(wxMessage.getToUserName(), "toUser"); | ||||
| Assert.assertEquals(wxMessage.getFromUserName(), "fromUser"); | Assert.assertEquals(wxMessage.getFromUserName(), "fromUser"); | ||||
| Assert.assertEquals(wxMessage.getCreateTime(), new Long(1348831860l)); | Assert.assertEquals(wxMessage.getCreateTime(), new Long(1348831860l)); | ||||
| Assert.assertEquals(wxMessage.getMsgType(), WxConsts.TEXT); | |||||
| Assert.assertEquals(wxMessage.getMsgType(), WxConsts.MSG_TEXT); | |||||
| Assert.assertEquals(wxMessage.getContent(), "this is a test"); | Assert.assertEquals(wxMessage.getContent(), "this is a test"); | ||||
| Assert.assertEquals(wxMessage.getMsgId(), new Long(1234567890123456l)); | Assert.assertEquals(wxMessage.getMsgId(), new Long(1234567890123456l)); | ||||
| Assert.assertEquals(wxMessage.getPicUrl(), "this is a url"); | Assert.assertEquals(wxMessage.getPicUrl(), "this is a url"); | ||||
| @@ -67,7 +67,7 @@ public class WxXmlMessageTest { | |||||
| wxMessage.setToUserName("toUser"); | wxMessage.setToUserName("toUser"); | ||||
| wxMessage.setFromUserName("fromUser"); | wxMessage.setFromUserName("fromUser"); | ||||
| wxMessage.setCreateTime(new Long(1348831860l)); | wxMessage.setCreateTime(new Long(1348831860l)); | ||||
| wxMessage.setMsgType(WxConsts.TEXT); | |||||
| wxMessage.setMsgType(WxConsts.MSG_TEXT); | |||||
| wxMessage.setContent("this is a test"); | wxMessage.setContent("this is a test"); | ||||
| wxMessage.setMsgId(new Long(1234567890123456l)); | wxMessage.setMsgId(new Long(1234567890123456l)); | ||||
| wxMessage.setPicUrl("this is a url"); | wxMessage.setPicUrl("this is a url"); | ||||