| @@ -13,6 +13,7 @@ test-output | |||
| hs_err_pid* | |||
| target | |||
| bin | |||
| .project | |||
| .classpath | |||
| .settings | |||
| @@ -21,3 +22,7 @@ sw-pom.xml | |||
| *.iml | |||
| test-config.xml | |||
| .idea | |||
| /.gradle/ | |||
| /gradle/ | |||
| *.bat | |||
| /gradlew | |||
| @@ -8,22 +8,7 @@ | |||
| #### 由于本次更新涉及接口调整比较大,主要是公众号的调整,企业号无过多调整,主要是为了解决主接口类过于庞大不方便管理的问题,将接口实现代码按模块进行拆分。因此版本号直接从1.X.X直接升级到2.0.0,所以如果习惯于1.X.X版本的同学不想做过多更改的话,请慎重考虑升级到最新版本。 | |||
| --- | |||
| #### 本项目主要存放在github上,地址为 : | |||
| * https://github.com/wechat-group/weixin-java-tools | |||
| * ===========但同时会在其他几个网站同步更新,地址分别是: | |||
| * https://bitbucket.org/binarywang/weixin-java-tools | |||
| * http://git.oschina.net/binary/weixin-java-tools | |||
| * https://git.coding.net/binarywang/weixin-java-tools.git | |||
| ### 详细开发文档请看 [wiki](https://github.com/chanjarster/weixin-java-tools/wiki)。 | |||
| ## 目前可参考的Demo项目: | |||
| * https://github.com/wechat-group/weixin-java-tools-springmvc | |||
| * https://github.com/wechat-group/weixin-mp-demo | |||
| * ===========以下为备份仓库,会保持跟主仓库同步 | |||
| * http://git.oschina.net/binary/weixin-mp-demo | |||
| * https://bitbucket.org/binarywang/weixin-mp-demo | |||
| =========== | |||
| ## 开发交流工具: | |||
| * QQ群:343954419 [](http://shang.qq.com/wpa/qunwpa?idkey=078f7a153d243853e24cf2b542e7a6ccbf2a592bc138080f84d11297f736ec46) | |||
| @@ -40,10 +25,9 @@ | |||
| - [【企业号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-cp%22) | |||
| ## Quick Start | |||
| * 如果要开发公众号(订阅号、服务号)应用,在你的maven项目中添加: | |||
| ## Maven & Gradle | |||
| * 公众号(订阅号、服务号): | |||
| ```xml | |||
| <dependency> | |||
| <groupId>com.github.binarywang</groupId> | |||
| @@ -52,8 +36,11 @@ | |||
| </dependency> | |||
| ``` | |||
| * 如果要开发企业号应用,在你的maven项目中添加: | |||
| ```groovy | |||
| compile 'com.github.binarywang:weixin-java-mp:2.0.0' | |||
| ``` | |||
| * 企业号: | |||
| ```xml | |||
| <dependency> | |||
| <groupId>com.github.binarywang</groupId> | |||
| @@ -62,6 +49,25 @@ | |||
| </dependency> | |||
| ``` | |||
| ```groovy | |||
| compile 'com.github.binarywang:weixin-java-cp:2.0.0' | |||
| ``` | |||
| #### 本项目主要存放在github上,地址为 : | |||
| * https://github.com/wechat-group/weixin-java-tools | |||
| * ===========但同时会在其他几个网站同步更新,地址分别是: | |||
| * https://bitbucket.org/binarywang/weixin-java-tools | |||
| * http://git.oschina.net/binary/weixin-java-tools | |||
| * https://git.coding.net/binarywang/weixin-java-tools.git | |||
| ## 目前可参考的Demo项目: | |||
| * https://github.com/wechat-group/weixin-java-tools-springmvc | |||
| * https://github.com/wechat-group/weixin-mp-demo | |||
| * ===========以下为备份仓库,会保持跟主仓库同步 | |||
| * http://git.oschina.net/binary/weixin-mp-demo | |||
| * https://bitbucket.org/binarywang/weixin-mp-demo | |||
| ## 关于代码贡献 | |||
| * 非常欢迎和感谢对本项目发起Pull Request的同学,本项目可以采用两种方式接受代码贡献: | |||
| @@ -0,0 +1,34 @@ | |||
| allprojects { | |||
| apply plugin: 'maven' | |||
| group = 'com.github.binarywang' | |||
| version = '2.1.0-SNAPSHOT' | |||
| } | |||
| subprojects { | |||
| apply plugin: 'java' | |||
| sourceCompatibility = 1.7 | |||
| targetCompatibility = 1.7 | |||
| repositories { | |||
| mavenLocal() | |||
| maven { url "http://maven.aliyun.com/nexus/content/groups/public" } | |||
| } | |||
| dependencies { | |||
| compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.10' | |||
| compile group: 'org.apache.httpcomponents', name: 'fluent-hc', version:'4.5' | |||
| compile group: 'org.apache.httpcomponents', name: 'httpmime', version:'4.5' | |||
| compile group: 'org.jodd', name: 'jodd-http', version:'3.6.7' | |||
| compile group: 'com.google.code.gson', name: 'gson', version:'2.7' | |||
| compile group: 'commons-codec', name: 'commons-codec', version:'1.10' | |||
| compile group: 'commons-io', name: 'commons-io', version:'2.5' | |||
| compile group: 'org.apache.commons', name: 'commons-lang3', version:'3.4' | |||
| compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:'2.8.0' | |||
| compile group: 'redis.clients', name: 'jedis', version:'2.9.0' | |||
| testCompile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2' | |||
| } | |||
| } | |||
| @@ -5,7 +5,7 @@ | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.0.0</version> | |||
| <version>2.1.0</version> | |||
| <packaging>pom</packaging> | |||
| <name>WeiXin Java Tools - Parent</name> | |||
| <description>微信公众号、企业号上级POM</description> | |||
| @@ -50,6 +50,7 @@ | |||
| <logback.version>1.1.2</logback.version> | |||
| <jodd-http.version>3.6.7</jodd-http.version> | |||
| <jackson.version>2.8.0</jackson.version> | |||
| <jedis.version>2.9.0</jedis.version> | |||
| <gson.version>2.7</gson.version> | |||
| <commons-lang3.version>3.4</commons-lang3.version> | |||
| <commons-io.version>2.5</commons-io.version> | |||
| @@ -110,8 +111,13 @@ | |||
| <artifactId>jackson-databind</artifactId> | |||
| <version>${jackson.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>redis.clients</groupId> | |||
| <artifactId>jedis</artifactId> | |||
| <version>${jedis.version}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| <dependencyManagement> | |||
| <dependencies> | |||
| <dependency> | |||
| @@ -0,0 +1,8 @@ | |||
| rootProject.name = 'weixin-java-parent' | |||
| include ':weixin-java-common' | |||
| include ':weixin-java-cp' | |||
| include ':weixin-java-mp' | |||
| project(':weixin-java-common').projectDir = "$rootDir/weixin-java-common" as File | |||
| project(':weixin-java-cp').projectDir = "$rootDir/weixin-java-cp" as File | |||
| project(':weixin-java-mp').projectDir = "$rootDir/weixin-java-mp" as File | |||
| @@ -0,0 +1,12 @@ | |||
| description = 'WeiXin Java Tools - Common' | |||
| dependencies { | |||
| compile group: 'com.thoughtworks.xstream', name: 'xstream', version:'1.4.7' | |||
| testCompile group: 'junit', name: 'junit', version:'4.11' | |||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.M0' | |||
| } | |||
| test.useTestNG() | |||
| @@ -6,7 +6,7 @@ | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.0.0</version> | |||
| <version>2.1.0</version> | |||
| </parent> | |||
| <artifactId>weixin-java-common</artifactId> | |||
| @@ -19,7 +19,7 @@ public class WxConsts { | |||
| public static final String XML_MSG_LINK = "link"; | |||
| public static final String XML_MSG_EVENT = "event"; | |||
| public static final String XML_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; | |||
| /////////////////////// | |||
| // 主动发送消息(即客服消息)的消息类型 | |||
| /////////////////////// | |||
| @@ -33,7 +33,7 @@ public class WxConsts { | |||
| public static final String CUSTOM_MSG_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; | |||
| public static final String CUSTOM_MSG_SAFE_NO = "0"; | |||
| public static final String CUSTOM_MSG_SAFE_YES = "1"; | |||
| /////////////////////// | |||
| // 群发消息的消息类型 | |||
| /////////////////////// | |||
| @@ -42,7 +42,7 @@ public class WxConsts { | |||
| public static final String MASS_MSG_VOICE = "voice"; | |||
| public static final String MASS_MSG_IMAGE = "image"; | |||
| public static final String MASS_MSG_VIDEO = "mpvideo"; | |||
| /////////////////////// | |||
| // 群发消息后微信端推送给服务器的反馈消息 | |||
| /////////////////////// | |||
| @@ -57,25 +57,11 @@ public class WxConsts { | |||
| public static final String MASS_ST_涉嫌版权 = "err(20013)"; | |||
| public static final String MASS_ST_涉嫌互推_互相宣传 = "err(22000)"; | |||
| public static final String MASS_ST_涉嫌其他 = "err(21000)"; | |||
| /** | |||
| * 群发反馈消息代码所对应的文字描述 | |||
| */ | |||
| public static final Map<String, String> MASS_ST_2_DESC = new HashMap<String, String>(); | |||
| static { | |||
| MASS_ST_2_DESC.put(MASS_ST_SUCCESS, "发送成功"); | |||
| MASS_ST_2_DESC.put(MASS_ST_FAIL, "发送失败"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌广告, "涉嫌广告"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌政治, "涉嫌政治"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌社会, "涉嫌社会"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌色情, "涉嫌色情"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌违法犯罪, "涉嫌违法犯罪"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌欺诈, "涉嫌欺诈"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌版权, "涉嫌版权"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌互推_互相宣传, "涉嫌互推_互相宣传"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌其他, "涉嫌其他"); | |||
| } | |||
| /////////////////////// | |||
| // 微信端推送过来的事件类型 | |||
| /////////////////////// | |||
| @@ -103,11 +89,9 @@ public class WxConsts { | |||
| public static final String EVT_USER_VIEW_CARD = "user_view_card"; | |||
| public static final String EVT_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card"; | |||
| public static final String EVT_CARD_SKU_REMIND = "card_sku_remind"; // 库存报警 | |||
| public static final String EVT_KF_CREATE_SESSION = "kf_create_session"; // 客服接入会话 | |||
| public static final String EVT_KF_CLOSE_SESSION = "kf_close_session"; // 客服关闭会话 | |||
| public static final String EVT_KF_SWITCH_SESSION = "kf_switch_session"; // 客服转接会话 | |||
| /////////////////////// | |||
| // 上传多媒体文件的类型 | |||
| /////////////////////// | |||
| @@ -116,7 +100,6 @@ public class WxConsts { | |||
| public static final String MEDIA_VIDEO = "video"; | |||
| public static final String MEDIA_THUMB = "thumb"; | |||
| public static final String MEDIA_FILE = "file"; | |||
| /////////////////////// | |||
| // 文件类型 | |||
| /////////////////////// | |||
| @@ -124,40 +107,63 @@ public class WxConsts { | |||
| public static final String FILE_MP3 = "mp3"; | |||
| public static final String FILE_AMR = "amr"; | |||
| public static final String FILE_MP4 = "mp4"; | |||
| /** | |||
| * 点击推事件 | |||
| */ | |||
| public static final String BUTTON_CLICK = "click"; | |||
| /////////////////////// | |||
| // 自定义菜单的按钮类型 | |||
| /////////////////////// | |||
| /** 点击推事件 */ | |||
| public static final String BUTTON_CLICK = "click"; | |||
| /** 跳转URL */ | |||
| /** | |||
| * 跳转URL | |||
| */ | |||
| public static final String BUTTON_VIEW = "view"; | |||
| /** 扫码推事件 */ | |||
| /** | |||
| * 扫码推事件 | |||
| */ | |||
| public static final String BUTTON_SCANCODE_PUSH = "scancode_push"; | |||
| /** 扫码推事件且弹出“消息接收中”提示框 */ | |||
| /** | |||
| * 扫码推事件且弹出“消息接收中”提示框 | |||
| */ | |||
| public static final String BUTTON_SCANCODE_WAITMSG = "scancode_waitmsg"; | |||
| /** 弹出系统拍照发图 */ | |||
| /** | |||
| * 弹出系统拍照发图 | |||
| */ | |||
| public static final String BUTTON_PIC_SYSPHOTO = "pic_sysphoto"; | |||
| /** 弹出拍照或者相册发图 */ | |||
| /** | |||
| * 弹出拍照或者相册发图 | |||
| */ | |||
| public static final String BUTTON_PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||
| /** 弹出微信相册发图器 */ | |||
| /** | |||
| * 弹出微信相册发图器 | |||
| */ | |||
| public static final String BUTTON_PIC_WEIXIN = "pic_weixin"; | |||
| /** 弹出地理位置选择器 */ | |||
| /** | |||
| * 弹出地理位置选择器 | |||
| */ | |||
| public static final String BUTTON_LOCATION_SELECT = "location_select"; | |||
| /** 下发消息(除文本消息) */ | |||
| /** | |||
| * 下发消息(除文本消息) | |||
| */ | |||
| public static final String BUTTON_MEDIA_ID = "media_id"; | |||
| /** 跳转图文消息URL */ | |||
| /** | |||
| * 跳转图文消息URL | |||
| */ | |||
| public static final String BUTTON_VIEW_LIMITED = "view_limited"; | |||
| /** | |||
| * 不弹出授权页面,直接跳转,只能获取用户openid | |||
| */ | |||
| public static final String OAUTH2_SCOPE_BASE = "snsapi_base"; | |||
| /////////////////////// | |||
| // oauth2网页授权的scope | |||
| /////////////////////// | |||
| /** 不弹出授权页面,直接跳转,只能获取用户openid */ | |||
| public static final String OAUTH2_SCOPE_BASE = "snsapi_base"; | |||
| /** 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 */ | |||
| /** | |||
| * 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 | |||
| */ | |||
| public static final String OAUTH2_SCOPE_USER_INFO = "snsapi_userinfo"; | |||
| /////////////////////// | |||
| // 永久素材类型 | |||
| /////////////////////// | |||
| @@ -165,4 +171,18 @@ public class WxConsts { | |||
| public static final String MATERIAL_VOICE = "voice"; | |||
| public static final String MATERIAL_IMAGE = "image"; | |||
| public static final String MATERIAL_VIDEO = "video"; | |||
| static { | |||
| MASS_ST_2_DESC.put(MASS_ST_SUCCESS, "发送成功"); | |||
| MASS_ST_2_DESC.put(MASS_ST_FAIL, "发送失败"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌广告, "涉嫌广告"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌政治, "涉嫌政治"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌社会, "涉嫌社会"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌色情, "涉嫌色情"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌违法犯罪, "涉嫌违法犯罪"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌欺诈, "涉嫌欺诈"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌版权, "涉嫌版权"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌互推_互相宣传, "涉嫌互推_互相宣传"); | |||
| MASS_ST_2_DESC.put(MASS_ST_涉嫌其他, "涉嫌其他"); | |||
| } | |||
| } | |||
| @@ -10,15 +10,16 @@ public interface WxMessageDuplicateChecker { | |||
| /** | |||
| * <h2>公众号的排重方式</h2> | |||
| * | |||
| * <p> | |||
| * <p>普通消息:关于重试的消息排重,推荐使用msgid排重。<a href="http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html">文档参考</a>。</p> | |||
| * <p>事件消息:关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。<a href="http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html">文档参考</a></p> | |||
| * | |||
| * <p> | |||
| * <h2>企业号的排重方式</h2> | |||
| * | |||
| * <p> | |||
| * 官方文档完全没有写,参照公众号的方式排重。 | |||
| * | |||
| * <p> | |||
| * <p>或者可以采取更简单的方式,如果有MsgId就用MsgId排重,如果没有就用FromUserName+CreateTime排重</p> | |||
| * | |||
| * @param messageId messageId需要根据上面讲的方式构造 | |||
| * @return 如果是重复消息,返回true,否则返回false | |||
| */ | |||
| @@ -46,7 +46,8 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec | |||
| /** | |||
| * WxMsgIdInMemoryDuplicateChecker构造函数 | |||
| * @param timeToLive 一个消息ID在内存的过期时间:毫秒 | |||
| * | |||
| * @param timeToLive 一个消息ID在内存的过期时间:毫秒 | |||
| * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 | |||
| */ | |||
| public WxMessageInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { | |||
| @@ -8,9 +8,13 @@ public class WxAccessToken implements Serializable { | |||
| private static final long serialVersionUID = 8709719312922168909L; | |||
| private String accessToken; | |||
| private int expiresIn = -1; | |||
| public static WxAccessToken fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxAccessToken.class); | |||
| } | |||
| public String getAccessToken() { | |||
| return accessToken; | |||
| } | |||
| @@ -27,8 +31,4 @@ public class WxAccessToken implements Serializable { | |||
| this.expiresIn = expiresIn; | |||
| } | |||
| public static WxAccessToken fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxAccessToken.class); | |||
| } | |||
| } | |||
| @@ -9,7 +9,7 @@ public class WxJsapiSignature implements Serializable { | |||
| private static final long serialVersionUID = -1116808193154384804L; | |||
| private String appid; | |||
| private String noncestr; | |||
| private long timestamp; | |||
| @@ -51,11 +51,11 @@ public class WxJsapiSignature implements Serializable { | |||
| } | |||
| public String getAppid() { | |||
| return appid; | |||
| return appid; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| this.appid = appid; | |||
| } | |||
| } | |||
| @@ -1,208 +0,0 @@ | |||
| package me.chanjar.weixin.common.bean; | |||
| import java.io.InputStream; | |||
| import java.io.InputStreamReader; | |||
| import java.io.Serializable; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| /** | |||
| * 企业号菜单 | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxMenu implements Serializable { | |||
| private static final long serialVersionUID = -7083914585539687746L; | |||
| private List<WxMenuButton> buttons = new ArrayList<WxMenuButton>(); | |||
| private WxMenuRule matchRule; | |||
| public List<WxMenuButton> getButtons() { | |||
| return buttons; | |||
| } | |||
| public void setButtons(List<WxMenuButton> buttons) { | |||
| this.buttons = buttons; | |||
| } | |||
| public WxMenuRule getMatchRule() { | |||
| return matchRule; | |||
| } | |||
| public void setMatchRule(WxMenuRule matchRule) { | |||
| this.matchRule = matchRule; | |||
| } | |||
| public String toJson() { | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| /** | |||
| * 要用 http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html 格式来反序列化 | |||
| * 相比 http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 的格式,外层多套了一个menu | |||
| */ | |||
| public static WxMenu fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxMenu.class); | |||
| } | |||
| /** | |||
| * 要用 http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html 格式来反序列化 | |||
| * 相比 http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 的格式,外层多套了一个menu | |||
| */ | |||
| public static WxMenu fromJson(InputStream is) { | |||
| return WxGsonBuilder.create().fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), WxMenu.class); | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMenu{" + | |||
| "buttons=" + buttons + | |||
| '}'; | |||
| } | |||
| public static class WxMenuButton { | |||
| private String type; | |||
| private String name; | |||
| private String key; | |||
| private String url; | |||
| private List<WxMenuButton> subButtons = new ArrayList<WxMenuButton>(); | |||
| public String getType() { | |||
| return type; | |||
| } | |||
| public void setType(String type) { | |||
| this.type = type; | |||
| } | |||
| public String getName() { | |||
| return name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getKey() { | |||
| return key; | |||
| } | |||
| public void setKey(String key) { | |||
| this.key = key; | |||
| } | |||
| public String getUrl() { | |||
| return url; | |||
| } | |||
| public void setUrl(String url) { | |||
| this.url = url; | |||
| } | |||
| public List<WxMenuButton> getSubButtons() { | |||
| return subButtons; | |||
| } | |||
| public void setSubButtons(List<WxMenuButton> subButtons) { | |||
| this.subButtons = subButtons; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMenuButton{" + | |||
| "type='" + type + '\'' + | |||
| ", name='" + name + '\'' + | |||
| ", key='" + key + '\'' + | |||
| ", url='" + url + '\'' + | |||
| ", subButtons=" + subButtons + | |||
| '}'; | |||
| } | |||
| } | |||
| public static class WxMenuRule { | |||
| private String tagId; | |||
| private String sex; | |||
| private String country; | |||
| private String province; | |||
| private String city; | |||
| private String clientPlatformType; | |||
| private String language; | |||
| public String getTagId() { | |||
| return tagId; | |||
| } | |||
| public void setTagId(String tagId) { | |||
| this.tagId = tagId; | |||
| } | |||
| public String getSex() { | |||
| return sex; | |||
| } | |||
| public void setSex(String sex) { | |||
| this.sex = sex; | |||
| } | |||
| public String getCountry() { | |||
| return country; | |||
| } | |||
| public void setCountry(String country) { | |||
| this.country = country; | |||
| } | |||
| public String getProvince() { | |||
| return province; | |||
| } | |||
| public void setProvince(String province) { | |||
| this.province = province; | |||
| } | |||
| public String getCity() { | |||
| return city; | |||
| } | |||
| public void setCity(String city) { | |||
| this.city = city; | |||
| } | |||
| public String getClientPlatformType() { | |||
| return clientPlatformType; | |||
| } | |||
| public void setClientPlatformType(String clientPlatformType) { | |||
| this.clientPlatformType = clientPlatformType; | |||
| } | |||
| public String getLanguage() { | |||
| return language; | |||
| } | |||
| public void setLanguage(String language) { | |||
| this.language = language; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "matchrule:{" + | |||
| "tag_id='" + tagId + '\'' + | |||
| ", sex='" + sex + '\'' + | |||
| ", country" + country + '\'' + | |||
| ", province" + province + '\'' + | |||
| ", city" + city + '\'' + | |||
| ", client_platform_type" + clientPlatformType + '\'' + | |||
| ", language" + language + '\'' + | |||
| "}"; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| package me.chanjar.weixin.common.bean.menu; | |||
| import java.io.InputStream; | |||
| import java.io.InputStreamReader; | |||
| import java.io.Serializable; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuButton; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| /** | |||
| * 企业号菜单 | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxMenu implements Serializable { | |||
| private static final long serialVersionUID = -7083914585539687746L; | |||
| private List<WxMenuButton> buttons = new ArrayList<WxMenuButton>(); | |||
| private WxMenuRule matchRule; | |||
| /** | |||
| * 要用 http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html 格式来反序列化 | |||
| * 相比 http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 的格式,外层多套了一个menu | |||
| */ | |||
| public static WxMenu fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxMenu.class); | |||
| } | |||
| /** | |||
| * 要用 http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html 格式来反序列化 | |||
| * 相比 http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html 的格式,外层多套了一个menu | |||
| */ | |||
| public static WxMenu fromJson(InputStream is) { | |||
| return WxGsonBuilder.create().fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), WxMenu.class); | |||
| } | |||
| public List<WxMenuButton> getButtons() { | |||
| return buttons; | |||
| } | |||
| public void setButtons(List<WxMenuButton> buttons) { | |||
| this.buttons = buttons; | |||
| } | |||
| public WxMenuRule getMatchRule() { | |||
| return matchRule; | |||
| } | |||
| public void setMatchRule(WxMenuRule matchRule) { | |||
| this.matchRule = matchRule; | |||
| } | |||
| public String toJson() { | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMenu{" + | |||
| "buttons=" + buttons + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| package me.chanjar.weixin.common.bean.menu; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| public class WxMenuButton { | |||
| private String type; | |||
| private String name; | |||
| private String key; | |||
| private String url; | |||
| private String mediaId; | |||
| private List<WxMenuButton> subButtons = new ArrayList<WxMenuButton>(); | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, | |||
| ToStringStyle.JSON_STYLE); | |||
| } | |||
| public String getType() { | |||
| return type; | |||
| } | |||
| public void setType(String type) { | |||
| this.type = type; | |||
| } | |||
| public String getName() { | |||
| return name; | |||
| } | |||
| public void setName(String name) { | |||
| this.name = name; | |||
| } | |||
| public String getKey() { | |||
| return key; | |||
| } | |||
| public void setKey(String key) { | |||
| this.key = key; | |||
| } | |||
| public String getUrl() { | |||
| return url; | |||
| } | |||
| public void setUrl(String url) { | |||
| this.url = url; | |||
| } | |||
| public List<WxMenuButton> getSubButtons() { | |||
| return subButtons; | |||
| } | |||
| public void setSubButtons(List<WxMenuButton> subButtons) { | |||
| this.subButtons = subButtons; | |||
| } | |||
| public String getMediaId() { | |||
| return mediaId; | |||
| } | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| } | |||
| @@ -0,0 +1,75 @@ | |||
| package me.chanjar.weixin.common.bean.menu; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| public class WxMenuRule { | |||
| private String tagId; | |||
| private String sex; | |||
| private String country; | |||
| private String province; | |||
| private String city; | |||
| private String clientPlatformType; | |||
| private String language; | |||
| public String getTagId() { | |||
| return tagId; | |||
| } | |||
| public void setTagId(String tagId) { | |||
| this.tagId = tagId; | |||
| } | |||
| public String getSex() { | |||
| return sex; | |||
| } | |||
| public void setSex(String sex) { | |||
| this.sex = sex; | |||
| } | |||
| public String getCountry() { | |||
| return country; | |||
| } | |||
| public void setCountry(String country) { | |||
| this.country = country; | |||
| } | |||
| public String getProvince() { | |||
| return province; | |||
| } | |||
| public void setProvince(String province) { | |||
| this.province = province; | |||
| } | |||
| public String getCity() { | |||
| return city; | |||
| } | |||
| public void setCity(String city) { | |||
| this.city = city; | |||
| } | |||
| public String getClientPlatformType() { | |||
| return clientPlatformType; | |||
| } | |||
| public void setClientPlatformType(String clientPlatformType) { | |||
| this.clientPlatformType = clientPlatformType; | |||
| } | |||
| public String getLanguage() { | |||
| return language; | |||
| } | |||
| public void setLanguage(String language) { | |||
| this.language = language; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| } | |||
| } | |||
| @@ -6,19 +6,28 @@ import java.io.Serializable; | |||
| /** | |||
| * 微信错误码说明,请阅读: <a href="http://mp.weixin.qq.com/wiki/10/6380dc743053a91c544ffd2b7c959166.html">全局返回码说明</a> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxError implements Serializable { | |||
| private static final long serialVersionUID = 7869786563361406291L; | |||
| private int errorCode; | |||
| private String errorMsg; | |||
| private String json; | |||
| public static WxError fromJson(String json) { | |||
| WxError error = WxGsonBuilder.create().fromJson(json, WxError.class); | |||
| return error; | |||
| } | |||
| public static Builder newBuilder() { | |||
| return new Builder(); | |||
| } | |||
| public int getErrorCode() { | |||
| return errorCode; | |||
| } | |||
| @@ -43,40 +52,34 @@ public class WxError implements Serializable { | |||
| this.json = json; | |||
| } | |||
| public static WxError fromJson(String json) { | |||
| WxError error = WxGsonBuilder.create().fromJson(json, WxError.class); | |||
| return error; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "微信错误: errcode=" + errorCode + ", errmsg=" + errorMsg + "\njson:" + json; | |||
| if (json != null) { | |||
| return json; | |||
| } | |||
| return "错误: Code=" + errorCode + ", Msg=" + errorMsg; | |||
| } | |||
| public static Builder newBuilder(){ | |||
| return new Builder(); | |||
| } | |||
| public static class Builder{ | |||
| public static class Builder { | |||
| private int errorCode; | |||
| private String errorMsg; | |||
| public Builder setErrorCode(int errorCode) { | |||
| this.errorCode = errorCode; | |||
| return this; | |||
| } | |||
| public Builder setErrorMsg(String errorMsg) { | |||
| this.errorMsg = errorMsg; | |||
| return this; | |||
| } | |||
| public WxError build(){ | |||
| public WxError build() { | |||
| WxError wxError = new WxError(); | |||
| wxError.setErrorCode(this.errorCode); | |||
| wxError.setErrorMsg(this.errorMsg); | |||
| return wxError; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,12 +6,16 @@ import java.io.Serializable; | |||
| public class WxMediaUploadResult implements Serializable { | |||
| private static final long serialVersionUID = 330834334738622341L; | |||
| private String type; | |||
| private String mediaId; | |||
| private String thumbMediaId; | |||
| private long createdAt; | |||
| public static WxMediaUploadResult fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxMediaUploadResult.class); | |||
| } | |||
| public String getType() { | |||
| return type; | |||
| } | |||
| @@ -44,14 +48,10 @@ public class WxMediaUploadResult implements Serializable { | |||
| this.thumbMediaId = thumbMediaId; | |||
| } | |||
| public static WxMediaUploadResult fromJson(String json) { | |||
| return WxGsonBuilder.create().fromJson(json, WxMediaUploadResult.class); | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxUploadResult [type=" + type + ", media_id=" + mediaId + ", thumb_media_id=" + thumbMediaId | |||
| + ", created_at=" + createdAt + "]"; | |||
| + ", created_at=" + createdAt + "]"; | |||
| } | |||
| } | |||
| @@ -5,7 +5,7 @@ import me.chanjar.weixin.common.bean.result.WxError; | |||
| public class WxErrorException extends Exception { | |||
| private static final long serialVersionUID = -6357149550353160810L; | |||
| private WxError error; | |||
| public WxErrorException(WxError error) { | |||
| @@ -17,5 +17,5 @@ public class WxErrorException extends Exception { | |||
| return error; | |||
| } | |||
| } | |||
| @@ -26,6 +26,6 @@ package me.chanjar.weixin.common.session; | |||
| public class Constants { | |||
| public static final String Package = "me.chanjar.weixin.common.session"; | |||
| public static final String Package = "me.chanjar.weixin.common.session"; | |||
| } | |||
| @@ -8,6 +8,11 @@ public interface InternalSession { | |||
| */ | |||
| WxSession getSession(); | |||
| /** | |||
| * Return the <code>isValid</code> flag for this session. | |||
| */ | |||
| boolean isValid(); | |||
| /** | |||
| * Set the <code>isValid</code> flag for this session. | |||
| * | |||
| @@ -15,11 +20,6 @@ public interface InternalSession { | |||
| */ | |||
| void setValid(boolean isValid); | |||
| /** | |||
| * Return the <code>isValid</code> flag for this session. | |||
| */ | |||
| boolean isValid(); | |||
| /** | |||
| * Return the session identifier for this session. | |||
| */ | |||
| @@ -7,11 +7,10 @@ public interface InternalSessionManager { | |||
| * specified session id (if any); otherwise return <code>null</code>. | |||
| * | |||
| * @param id The session id for the session to be returned | |||
| * | |||
| * @exception IllegalStateException if a new session cannot be | |||
| * instantiated for any reason | |||
| * @exception java.io.IOException if an input/output error occurs while | |||
| * processing this request | |||
| * @throws IllegalStateException if a new session cannot be | |||
| * instantiated for any reason | |||
| * @throws java.io.IOException if an input/output error occurs while | |||
| * processing this request | |||
| */ | |||
| InternalSession findSession(String id); | |||
| @@ -23,10 +22,10 @@ public interface InternalSessionManager { | |||
| * <code>null</code>. | |||
| * | |||
| * @param sessionId The session id which should be used to create the | |||
| * new session; if <code>null</code>, a new session id will be | |||
| * generated | |||
| * @exception IllegalStateException if a new session cannot be | |||
| * instantiated for any reason | |||
| * new session; if <code>null</code>, a new session id will be | |||
| * generated | |||
| * @throws IllegalStateException if a new session cannot be | |||
| * instantiated for any reason | |||
| */ | |||
| InternalSession createSession(String sessionId); | |||
| @@ -40,8 +39,8 @@ public interface InternalSessionManager { | |||
| /** | |||
| * Remove this Session from the active Sessions for this Manager. | |||
| * | |||
| * @param session Session to be removed | |||
| * @param update Should the expiration statistics be updated | |||
| * @param session Session to be removed | |||
| * @param update Should the expiration statistics be updated | |||
| */ | |||
| void remove(InternalSession session, boolean update); | |||
| @@ -59,6 +58,7 @@ public interface InternalSessionManager { | |||
| * @return number of sessions active | |||
| */ | |||
| int getActiveSessions(); | |||
| /** | |||
| * Get a session from the recycled ones or create a new empty one. | |||
| * The PersistentManager manager does not need to create session data | |||
| @@ -88,6 +88,7 @@ public interface InternalSessionManager { | |||
| * 要和{@link #setBackgroundProcessorDelay(int)}联合起来看 | |||
| * 如果把这个数字设置为6(默认),那么就是说manager要等待 6 * backgroundProcessorDay的时间才会清理过期session | |||
| * </pre> | |||
| * | |||
| * @param processExpiresFrequency the new manager checks frequency | |||
| */ | |||
| void setProcessExpiresFrequency(int processExpiresFrequency); | |||
| @@ -97,6 +98,7 @@ public interface InternalSessionManager { | |||
| * Set the manager background processor delay | |||
| * 设置manager sleep几秒,尝试执行一次background操作(清理过期session) | |||
| * </pre> | |||
| * | |||
| * @param backgroundProcessorDelay | |||
| */ | |||
| void setBackgroundProcessorDelay(int backgroundProcessorDelay); | |||
| @@ -106,6 +108,7 @@ public interface InternalSessionManager { | |||
| * Set the maximum number of active Sessions allowed, or -1 for | |||
| * no limit. | |||
| * 设置最大活跃session数,默认无限 | |||
| * | |||
| * @param max The new maximum number of sessions | |||
| */ | |||
| void setMaxActiveSessions(int max); | |||
| @@ -12,7 +12,6 @@ | |||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| # See the License for the specific language governing permissions and | |||
| # limitations under the License. | |||
| applicationSession.session.ise=invalid session state | |||
| applicationSession.value.iae=null value | |||
| fileStore.saving=Saving Session {0} to file {1} | |||
| @@ -12,17 +12,68 @@ public class StandardSession implements WxSession, InternalSession { | |||
| * The string manager for this package. | |||
| */ | |||
| protected static final StringManager sm = | |||
| StringManager.getManager(Constants.Package); | |||
| StringManager.getManager(Constants.Package); | |||
| /** | |||
| * Type array. | |||
| */ | |||
| protected static final String EMPTY_ARRAY[] = new String[0]; | |||
| // ------------------------------ WxSession | |||
| protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>(); | |||
| /** | |||
| * The session identifier of this Session. | |||
| */ | |||
| protected String id = null; | |||
| /** | |||
| * Flag indicating whether this session is valid or not. | |||
| */ | |||
| protected volatile boolean isValid = false; | |||
| /** | |||
| * We are currently processing a session expiration, so bypass | |||
| * certain IllegalStateException tests. NOTE: This value is not | |||
| * included in the serialized version of this object. | |||
| */ | |||
| protected transient volatile boolean expiring = false; | |||
| /** | |||
| * The Manager with which this Session is associated. | |||
| */ | |||
| protected transient InternalSessionManager manager = null; | |||
| // ------------------------------ InternalSession | |||
| /** | |||
| * The time this session was created, in milliseconds since midnight, | |||
| * January 1, 1970 GMT. | |||
| */ | |||
| protected long creationTime = 0L; | |||
| /** | |||
| * The current accessed time for this session. | |||
| */ | |||
| protected volatile long thisAccessedTime = creationTime; | |||
| /** | |||
| * The default maximum inactive interval for Sessions created by | |||
| * this Manager. | |||
| */ | |||
| protected int maxInactiveInterval = 30 * 60; | |||
| /** | |||
| * The facade associated with this session. NOTE: This value is not | |||
| * included in the serialized version of this object. | |||
| */ | |||
| protected transient StandardSessionFacade facade = null; | |||
| /** | |||
| * The access count for this session. | |||
| */ | |||
| protected transient AtomicInteger accessCount = null; | |||
| public StandardSession(InternalSessionManager manager) { | |||
| this.manager = manager; | |||
| this.accessCount = new AtomicInteger(); | |||
| } | |||
| @Override | |||
| public Object getAttribute(String name) { | |||
| if (!isValidInternal()) | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionImpl.getAttribute.ise")); | |||
| (sm.getString("sessionImpl.getAttribute.ise")); | |||
| if (name == null) return null; | |||
| @@ -33,7 +84,7 @@ public class StandardSession implements WxSession, InternalSession { | |||
| public Enumeration<String> getAttributeNames() { | |||
| if (!isValidInternal()) | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionImpl.getAttributeNames.ise")); | |||
| (sm.getString("sessionImpl.getAttributeNames.ise")); | |||
| Set<String> names = new HashSet<String>(); | |||
| names.addAll(attributes.keySet()); | |||
| @@ -45,7 +96,7 @@ public class StandardSession implements WxSession, InternalSession { | |||
| // Name cannot be null | |||
| if (name == null) | |||
| throw new IllegalArgumentException | |||
| (sm.getString("sessionImpl.setAttribute.namenull")); | |||
| (sm.getString("sessionImpl.setAttribute.namenull")); | |||
| // Null value is the same as removeAttribute() | |||
| if (value == null) { | |||
| @@ -56,97 +107,32 @@ public class StandardSession implements WxSession, InternalSession { | |||
| // Validate our current state | |||
| if (!isValidInternal()) | |||
| throw new IllegalStateException(sm.getString( | |||
| "sessionImpl.setAttribute.ise", getIdInternal())); | |||
| "sessionImpl.setAttribute.ise", getIdInternal())); | |||
| attributes.put(name, value); | |||
| } | |||
| @Override | |||
| public void removeAttribute(String name) { | |||
| removeAttributeInternal(name); | |||
| } | |||
| @Override | |||
| public void invalidate() { | |||
| if (!isValidInternal()) | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionImpl.invalidate.ise")); | |||
| (sm.getString("sessionImpl.invalidate.ise")); | |||
| // Cause this session to expire | |||
| expire(); | |||
| } | |||
| // ------------------------------ InternalSession | |||
| /** | |||
| * The session identifier of this Session. | |||
| */ | |||
| protected String id = null; | |||
| /** | |||
| * Flag indicating whether this session is valid or not. | |||
| */ | |||
| protected volatile boolean isValid = false; | |||
| /** | |||
| * We are currently processing a session expiration, so bypass | |||
| * certain IllegalStateException tests. NOTE: This value is not | |||
| * included in the serialized version of this object. | |||
| */ | |||
| protected transient volatile boolean expiring = false; | |||
| /** | |||
| * The Manager with which this Session is associated. | |||
| */ | |||
| protected transient InternalSessionManager manager = null; | |||
| /** | |||
| * Type array. | |||
| */ | |||
| protected static final String EMPTY_ARRAY[] = new String[0]; | |||
| /** | |||
| * The time this session was created, in milliseconds since midnight, | |||
| * January 1, 1970 GMT. | |||
| */ | |||
| protected long creationTime = 0L; | |||
| /** | |||
| * The current accessed time for this session. | |||
| */ | |||
| protected volatile long thisAccessedTime = creationTime; | |||
| /** | |||
| * The default maximum inactive interval for Sessions created by | |||
| * this Manager. | |||
| */ | |||
| protected int maxInactiveInterval = 30 * 60; | |||
| /** | |||
| * The facade associated with this session. NOTE: This value is not | |||
| * included in the serialized version of this object. | |||
| */ | |||
| protected transient StandardSessionFacade facade = null; | |||
| /** | |||
| * The access count for this session. | |||
| */ | |||
| protected transient AtomicInteger accessCount = null; | |||
| public StandardSession(InternalSessionManager manager) { | |||
| this.manager = manager; | |||
| this.accessCount = new AtomicInteger(); | |||
| } | |||
| @Override | |||
| public WxSession getSession() { | |||
| if (facade == null){ | |||
| if (facade == null) { | |||
| facade = new StandardSessionFacade(this); | |||
| } | |||
| return (facade); | |||
| @@ -161,16 +147,6 @@ public class StandardSession implements WxSession, InternalSession { | |||
| return this.isValid; | |||
| } | |||
| /** | |||
| * Set the <code>isValid</code> flag for this session. | |||
| * | |||
| * @param isValid The new value for the <code>isValid</code> flag | |||
| */ | |||
| @Override | |||
| public void setValid(boolean isValid) { | |||
| this.isValid = isValid; | |||
| } | |||
| @Override | |||
| public boolean isValid() { | |||
| if (!this.isValid) { | |||
| @@ -197,6 +173,16 @@ public class StandardSession implements WxSession, InternalSession { | |||
| return this.isValid; | |||
| } | |||
| /** | |||
| * Set the <code>isValid</code> flag for this session. | |||
| * | |||
| * @param isValid The new value for the <code>isValid</code> flag | |||
| */ | |||
| @Override | |||
| public void setValid(boolean isValid) { | |||
| this.isValid = isValid; | |||
| } | |||
| @Override | |||
| public String getIdInternal() { | |||
| return (this.id); | |||
| @@ -13,60 +13,26 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||
| */ | |||
| public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | |||
| protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class); | |||
| protected static final StringManager sm = | |||
| StringManager.getManager(Constants.Package); | |||
| StringManager.getManager(Constants.Package); | |||
| /** | |||
| * The set of currently active Sessions for this Manager, keyed by | |||
| * session identifier. | |||
| * The descriptive name of this Manager implementation (for logging). | |||
| */ | |||
| protected Map<String, InternalSession> sessions = new ConcurrentHashMap<String, InternalSession>(); | |||
| @Override | |||
| public WxSession getSession(String sessionId) { | |||
| return getSession(sessionId, true); | |||
| } | |||
| @Override | |||
| public WxSession getSession(String sessionId, boolean create) { | |||
| if (sessionId == null) { | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionManagerImpl.getSession.ise")); | |||
| } | |||
| InternalSession session = findSession(sessionId); | |||
| if ((session != null) && !session.isValid()) { | |||
| session = null; | |||
| } | |||
| if (session != null) { | |||
| session.access(); | |||
| return session.getSession(); | |||
| } | |||
| // Create a new session if requested and the response is not committed | |||
| if (!create) { | |||
| return (null); | |||
| } | |||
| session = createSession(sessionId); | |||
| if (session == null) { | |||
| return null; | |||
| } | |||
| session.access(); | |||
| return session.getSession(); | |||
| } | |||
| private static final String name = "SessionManagerImpl"; | |||
| protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class); | |||
| private final Object maxActiveUpdateLock = new Object(); | |||
| /** | |||
| * 后台清理线程是否已经开启 | |||
| */ | |||
| private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||
| // -------------------------------------- InternalSessionManager | |||
| /** | |||
| * The descriptive name of this Manager implementation (for logging). | |||
| * The set of currently active Sessions for this Manager, keyed by | |||
| * session identifier. | |||
| */ | |||
| private static final String name = "SessionManagerImpl"; | |||
| protected Map<String, InternalSession> sessions = new ConcurrentHashMap<String, InternalSession>(); | |||
| /** | |||
| * The maximum number of active Sessions allowed, or -1 for no limit. | |||
| */ | |||
| @@ -84,22 +50,13 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| protected int maxInactiveInterval = 30 * 60; | |||
| // Number of sessions created by this manager | |||
| protected long sessionCounter=0; | |||
| protected volatile int maxActive=0; | |||
| private final Object maxActiveUpdateLock = new Object(); | |||
| protected long sessionCounter = 0; | |||
| protected volatile int maxActive = 0; | |||
| /** | |||
| * Processing time during session expiration. | |||
| */ | |||
| protected long processingTime = 0; | |||
| /** | |||
| * Iteration count for background processing. | |||
| */ | |||
| private int count = 0; | |||
| /** | |||
| * Frequency of the session expiration, and related manager operations. | |||
| * Manager operations will be done once for the specified amount of | |||
| @@ -107,16 +64,50 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| * checks will occur). | |||
| */ | |||
| protected int processExpiresFrequency = 6; | |||
| /** | |||
| * background processor delay in seconds | |||
| */ | |||
| protected int backgroundProcessorDelay = 10; | |||
| /** | |||
| * 后台清理线程是否已经开启 | |||
| * Iteration count for background processing. | |||
| */ | |||
| private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||
| private int count = 0; | |||
| @Override | |||
| public WxSession getSession(String sessionId) { | |||
| return getSession(sessionId, true); | |||
| } | |||
| @Override | |||
| public WxSession getSession(String sessionId, boolean create) { | |||
| if (sessionId == null) { | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionManagerImpl.getSession.ise")); | |||
| } | |||
| InternalSession session = findSession(sessionId); | |||
| if ((session != null) && !session.isValid()) { | |||
| session = null; | |||
| } | |||
| if (session != null) { | |||
| session.access(); | |||
| return session.getSession(); | |||
| } | |||
| // Create a new session if requested and the response is not committed | |||
| if (!create) { | |||
| return (null); | |||
| } | |||
| session = createSession(sessionId); | |||
| if (session == null) { | |||
| return null; | |||
| } | |||
| session.access(); | |||
| return session.getSession(); | |||
| } | |||
| @Override | |||
| public void remove(InternalSession session) { | |||
| @@ -131,7 +122,6 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| } | |||
| @Override | |||
| public InternalSession findSession(String id) { | |||
| @@ -145,15 +135,15 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| public InternalSession createSession(String sessionId) { | |||
| if (sessionId == null) { | |||
| throw new IllegalStateException | |||
| (sm.getString("sessionManagerImpl.createSession.ise")); | |||
| (sm.getString("sessionManagerImpl.createSession.ise")); | |||
| } | |||
| if ((maxActiveSessions >= 0) && | |||
| (getActiveSessions() >= maxActiveSessions)) { | |||
| (getActiveSessions() >= maxActiveSessions)) { | |||
| rejectedSessions++; | |||
| throw new TooManyActiveSessionsException( | |||
| sm.getString("sessionManagerImpl.createSession.tmase"), | |||
| maxActiveSessions); | |||
| sm.getString("sessionManagerImpl.createSession.tmase"), | |||
| maxActiveSessions); | |||
| } | |||
| // Recycle or create a Session instance | |||
| @@ -216,14 +206,14 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| sessions.put(session.getIdInternal(), session); | |||
| int size = getActiveSessions(); | |||
| if( size > maxActive ) { | |||
| synchronized(maxActiveUpdateLock) { | |||
| if( size > maxActive ) { | |||
| if (size > maxActive) { | |||
| synchronized (maxActiveUpdateLock) { | |||
| if (size > maxActive) { | |||
| maxActive = size; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| @@ -251,19 +241,19 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| long timeNow = System.currentTimeMillis(); | |||
| InternalSession sessions[] = findSessions(); | |||
| int expireHere = 0 ; | |||
| int expireHere = 0; | |||
| if(log.isDebugEnabled()) | |||
| if (log.isDebugEnabled()) | |||
| log.debug("Start expire sessions {} at {} sessioncount {}", getName(), timeNow, sessions.length); | |||
| for (int i = 0; i < sessions.length; i++) { | |||
| if (sessions[i]!=null && !sessions[i].isValid()) { | |||
| if (sessions[i] != null && !sessions[i].isValid()) { | |||
| expireHere++; | |||
| } | |||
| } | |||
| long timeEnd = System.currentTimeMillis(); | |||
| if(log.isDebugEnabled()) | |||
| if (log.isDebugEnabled()) | |||
| log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(), timeEnd - timeNow, expireHere); | |||
| processingTime += ( timeEnd - timeNow ); | |||
| processingTime += (timeEnd - timeNow); | |||
| } | |||
| @@ -21,37 +21,34 @@ package me.chanjar.weixin.common.session; | |||
| * reached and the server is refusing to create any new sessions. | |||
| */ | |||
| public class TooManyActiveSessionsException | |||
| extends IllegalStateException | |||
| { | |||
| private static final long serialVersionUID = 1L; | |||
| extends IllegalStateException { | |||
| private static final long serialVersionUID = 1L; | |||
| /** | |||
| * The maximum number of active sessions the server will tolerate. | |||
| */ | |||
| private final int maxActiveSessions; | |||
| /** | |||
| * The maximum number of active sessions the server will tolerate. | |||
| */ | |||
| private final int maxActiveSessions; | |||
| /** | |||
| * Creates a new TooManyActiveSessionsException. | |||
| * | |||
| * @param message A description for the exception. | |||
| * @param maxActive The maximum number of active sessions allowed by the | |||
| * session manager. | |||
| */ | |||
| public TooManyActiveSessionsException(String message, | |||
| int maxActive) | |||
| { | |||
| super(message); | |||
| maxActiveSessions = maxActive; | |||
| } | |||
| /** | |||
| * Gets the maximum number of sessions allowed by the session manager. | |||
| * | |||
| * @return The maximum number of sessions allowed by the session manager. | |||
| */ | |||
| public int getMaxActiveSessions() | |||
| { | |||
| return maxActiveSessions; | |||
| } | |||
| /** | |||
| * Creates a new TooManyActiveSessionsException. | |||
| * | |||
| * @param message A description for the exception. | |||
| * @param maxActive The maximum number of active sessions allowed by the | |||
| * session manager. | |||
| */ | |||
| public TooManyActiveSessionsException(String message, | |||
| int maxActive) { | |||
| super(message); | |||
| maxActiveSessions = maxActive; | |||
| } | |||
| /** | |||
| * Gets the maximum number of sessions allowed by the session manager. | |||
| * | |||
| * @return The maximum number of sessions allowed by the session manager. | |||
| */ | |||
| public int getMaxActiveSessions() { | |||
| return maxActiveSessions; | |||
| } | |||
| } | |||
| @@ -7,7 +7,7 @@ public class StringUtils { | |||
| /** | |||
| * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p> | |||
| * | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isBlank(null) = true | |||
| * StringUtils.isBlank("") = true | |||
| @@ -16,9 +16,8 @@ public class StringUtils { | |||
| * StringUtils.isBlank(" bob ") = false | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is null, empty or whitespace | |||
| * @since 2.0 | |||
| * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) | |||
| */ | |||
| public static boolean isBlank(CharSequence cs) { | |||
| @@ -36,7 +35,7 @@ public class StringUtils { | |||
| /** | |||
| * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p> | |||
| * | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isNotBlank(null) = false | |||
| * StringUtils.isNotBlank("") = false | |||
| @@ -45,10 +44,9 @@ public class StringUtils { | |||
| * StringUtils.isNotBlank(" bob ") = true | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is | |||
| * not empty and not null and not whitespace | |||
| * @since 2.0 | |||
| * not empty and not null and not whitespace | |||
| * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) | |||
| */ | |||
| public static boolean isNotBlank(CharSequence cs) { | |||
| @@ -57,7 +55,7 @@ public class StringUtils { | |||
| /** | |||
| * <p>Checks if a CharSequence is empty ("") or null.</p> | |||
| * | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isEmpty(null) = true | |||
| * StringUtils.isEmpty("") = true | |||
| @@ -65,12 +63,12 @@ public class StringUtils { | |||
| * StringUtils.isEmpty("bob") = false | |||
| * StringUtils.isEmpty(" bob ") = false | |||
| * </pre> | |||
| * | |||
| * <p> | |||
| * <p>NOTE: This method changed in Lang version 2.0. | |||
| * It no longer trims the CharSequence. | |||
| * That functionality is available in isBlank().</p> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is empty or null | |||
| * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) | |||
| */ | |||
| @@ -80,7 +78,7 @@ public class StringUtils { | |||
| /** | |||
| * <p>Checks if a CharSequence is not empty ("") and not null.</p> | |||
| * | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isNotEmpty(null) = false | |||
| * StringUtils.isNotEmpty("") = false | |||
| @@ -89,7 +87,7 @@ public class StringUtils { | |||
| * StringUtils.isNotEmpty(" bob ") = true | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is not empty and not null | |||
| * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) | |||
| */ | |||
| @@ -3,24 +3,24 @@ package me.chanjar.weixin.common.util.crypto; | |||
| import java.util.ArrayList; | |||
| public class ByteGroup { | |||
| ArrayList<Byte> byteContainer = new ArrayList<Byte>(); | |||
| ArrayList<Byte> byteContainer = new ArrayList<Byte>(); | |||
| public byte[] toBytes() { | |||
| byte[] bytes = new byte[byteContainer.size()]; | |||
| for (int i = 0; i < byteContainer.size(); i++) { | |||
| bytes[i] = byteContainer.get(i); | |||
| } | |||
| return bytes; | |||
| } | |||
| byte[] bytes = new byte[byteContainer.size()]; | |||
| for (int i = 0; i < byteContainer.size(); i++) { | |||
| bytes[i] = byteContainer.get(i); | |||
| } | |||
| return bytes; | |||
| } | |||
| public ByteGroup addBytes(byte[] bytes) { | |||
| for (byte b : bytes) { | |||
| byteContainer.add(b); | |||
| } | |||
| return this; | |||
| } | |||
| public ByteGroup addBytes(byte[] bytes) { | |||
| for (byte b : bytes) { | |||
| byteContainer.add(b); | |||
| } | |||
| return this; | |||
| } | |||
| public int size() { | |||
| return byteContainer.size(); | |||
| } | |||
| public int size() { | |||
| return byteContainer.size(); | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| /** | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| */ | |||
| @@ -16,53 +16,53 @@ import java.util.Arrays; | |||
| */ | |||
| public class PKCS7Encoder { | |||
| private static final Charset CHARSET = Charset.forName("utf-8"); | |||
| private static final int BLOCK_SIZE = 32; | |||
| private static final Charset CHARSET = Charset.forName("utf-8"); | |||
| private static final int BLOCK_SIZE = 32; | |||
| /** | |||
| * 获得对明文进行补位填充的字节. | |||
| * | |||
| * @param count 需要进行填充补位操作的明文字节个数 | |||
| * @return 补齐用的字节数组 | |||
| */ | |||
| public static byte[] encode(int count) { | |||
| // 计算需要填充的位数 | |||
| int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); | |||
| if (amountToPad == 0) { | |||
| amountToPad = BLOCK_SIZE; | |||
| } | |||
| // 获得补位所用的字符 | |||
| char padChr = chr(amountToPad); | |||
| String tmp = new String(); | |||
| for (int index = 0; index < amountToPad; index++) { | |||
| tmp += padChr; | |||
| } | |||
| return tmp.getBytes(CHARSET); | |||
| } | |||
| /** | |||
| * 获得对明文进行补位填充的字节. | |||
| * | |||
| * @param count 需要进行填充补位操作的明文字节个数 | |||
| * @return 补齐用的字节数组 | |||
| */ | |||
| public static byte[] encode(int count) { | |||
| // 计算需要填充的位数 | |||
| int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); | |||
| if (amountToPad == 0) { | |||
| amountToPad = BLOCK_SIZE; | |||
| } | |||
| // 获得补位所用的字符 | |||
| char padChr = chr(amountToPad); | |||
| String tmp = new String(); | |||
| for (int index = 0; index < amountToPad; index++) { | |||
| tmp += padChr; | |||
| } | |||
| return tmp.getBytes(CHARSET); | |||
| } | |||
| /** | |||
| * 删除解密后明文的补位字符 | |||
| * | |||
| * @param decrypted 解密后的明文 | |||
| * @return 删除补位字符后的明文 | |||
| */ | |||
| public static byte[] decode(byte[] decrypted) { | |||
| int pad = (int) decrypted[decrypted.length - 1]; | |||
| if (pad < 1 || pad > 32) { | |||
| pad = 0; | |||
| } | |||
| return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); | |||
| } | |||
| /** | |||
| * 删除解密后明文的补位字符 | |||
| * | |||
| * @param decrypted 解密后的明文 | |||
| * @return 删除补位字符后的明文 | |||
| */ | |||
| public static byte[] decode(byte[] decrypted) { | |||
| int pad = (int) decrypted[decrypted.length - 1]; | |||
| if (pad < 1 || pad > 32) { | |||
| pad = 0; | |||
| } | |||
| return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); | |||
| } | |||
| /** | |||
| * 将数字转化成ASCII码对应的字符,用于对明文进行补码 | |||
| * | |||
| * @param a 需要转化的数字 | |||
| * @return 转化得到的字符 | |||
| */ | |||
| public static char chr(int a) { | |||
| byte target = (byte) (a & 0xFF); | |||
| return (char) target; | |||
| } | |||
| /** | |||
| * 将数字转化成ASCII码对应的字符,用于对明文进行补码 | |||
| * | |||
| * @param a 需要转化的数字 | |||
| * @return 转化得到的字符 | |||
| */ | |||
| public static char chr(int a) { | |||
| byte target = (byte) (a & 0xFF); | |||
| return (char) target; | |||
| } | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import org.apache.commons.codec.digest.DigestUtils; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.util.Arrays; | |||
| import org.apache.commons.codec.digest.DigestUtils; | |||
| /** | |||
| * Created by Daniel Qian on 14/10/19. | |||
| */ | |||
| @@ -37,4 +37,4 @@ public class SHA1 { | |||
| } | |||
| return DigestUtils.sha1Hex(sb.toString()); | |||
| } | |||
| } | |||
| } | |||
| @@ -2,6 +2,10 @@ | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| @@ -62,12 +66,51 @@ public class WxCryptUtil { | |||
| * @param appidOrCorpid 公众平台appid/corpid | |||
| */ | |||
| public WxCryptUtil(String token, String encodingAesKey, | |||
| String appidOrCorpid) { | |||
| String appidOrCorpid) { | |||
| this.token = token; | |||
| this.appidOrCorpid = appidOrCorpid; | |||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||
| } | |||
| /** | |||
| * 微信公众号支付签名算法(详见:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3) | |||
| * @param packageParams 原始参数 | |||
| * @param signKey 加密Key(即 商户Key) | |||
| * @return 签名字符串 | |||
| */ | |||
| public static String createSign(Map<String, String> packageParams, | |||
| String signKey) { | |||
| SortedMap<String, String> sortedMap = new TreeMap<String, String>(); | |||
| sortedMap.putAll(packageParams); | |||
| List<String> keys = new ArrayList<String>(packageParams.keySet()); | |||
| Collections.sort(keys); | |||
| StringBuffer toSign = new StringBuffer(); | |||
| for (String key : keys) { | |||
| String value = packageParams.get(key); | |||
| if (null != value && !"".equals(value) && !"sign".equals(key) | |||
| && !"key".equals(key)) { | |||
| toSign.append(key + "=" + value + "&"); | |||
| } | |||
| } | |||
| toSign.append("key=" + signKey); | |||
| String sign = DigestUtils.md5Hex(toSign.toString()).toUpperCase(); | |||
| return sign; | |||
| } | |||
| static String extractEncryptPart(String xml) { | |||
| try { | |||
| DocumentBuilder db = builderLocal.get(); | |||
| Document document = db.parse(new InputSource(new StringReader(xml))); | |||
| Element root = document.getDocumentElement(); | |||
| return root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 将公众平台回复用户的消息加密打包. | |||
| * <ol> | |||
| @@ -107,7 +150,7 @@ public class WxCryptUtil { | |||
| byte[] randomStringBytes = randomStr.getBytes(CHARSET); | |||
| byte[] plainTextBytes = plainText.getBytes(CHARSET); | |||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | |||
| plainTextBytes.length); | |||
| plainTextBytes.length); | |||
| byte[] appIdBytes = appidOrCorpid.getBytes(CHARSET); | |||
| // randomStr + networkBytesOrder + text + appid | |||
| @@ -157,7 +200,7 @@ public class WxCryptUtil { | |||
| * @return 解密后的原文 | |||
| */ | |||
| public String decrypt(String msgSignature, String timeStamp, String nonce, | |||
| String encryptedXml) { | |||
| String encryptedXml) { | |||
| // 密钥,公众账号的app corpSecret | |||
| // 提取密文 | |||
| String cipherText = extractEncryptPart(encryptedXml); | |||
| @@ -190,7 +233,7 @@ public class WxCryptUtil { | |||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
| SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); | |||
| IvParameterSpec iv = new IvParameterSpec( | |||
| Arrays.copyOfRange(aesKey, 0, 16)); | |||
| Arrays.copyOfRange(aesKey, 0, 16)); | |||
| cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | |||
| // 使用BASE64对密文进行解码 | |||
| @@ -213,9 +256,9 @@ public class WxCryptUtil { | |||
| int xmlLength = bytesNetworkOrder2Number(networkOrder); | |||
| xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | |||
| CHARSET); | |||
| CHARSET); | |||
| from_appid = new String( | |||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| @@ -229,33 +272,6 @@ public class WxCryptUtil { | |||
| } | |||
| /** | |||
| * 微信公众号支付签名算法(详见:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=4_3) | |||
| * @param packageParams 原始参数 | |||
| * @param signKey 加密Key(即 商户Key) | |||
| * @return 签名字符串 | |||
| */ | |||
| public static String createSign(Map<String, String> packageParams, | |||
| String signKey) { | |||
| SortedMap<String, String> sortedMap = new TreeMap<String, String>(); | |||
| sortedMap.putAll(packageParams); | |||
| List<String> keys = new ArrayList<String>(packageParams.keySet()); | |||
| Collections.sort(keys); | |||
| StringBuffer toSign = new StringBuffer(); | |||
| for (String key : keys) { | |||
| String value = packageParams.get(key); | |||
| if (null != value && !"".equals(value) && !"sign".equals(key) | |||
| && !"key".equals(key)) { | |||
| toSign.append(key + "=" + value + "&"); | |||
| } | |||
| } | |||
| toSign.append("key=" + signKey); | |||
| String sign = DigestUtils.md5Hex(toSign.toString()).toUpperCase(); | |||
| return sign; | |||
| } | |||
| /** | |||
| * 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||
| * | |||
| @@ -308,24 +324,12 @@ public class WxCryptUtil { | |||
| * @return 生成的xml字符串 | |||
| */ | |||
| private String generateXml(String encrypt, String signature, String timestamp, | |||
| String nonce) { | |||
| String nonce) { | |||
| String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||
| + "</xml>"; | |||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||
| + "</xml>"; | |||
| return String.format(format, encrypt, signature, timestamp, nonce); | |||
| } | |||
| static String extractEncryptPart(String xml) { | |||
| try { | |||
| DocumentBuilder db = builderLocal.get(); | |||
| Document document = db.parse(new InputSource(new StringReader(xml))); | |||
| Element root = document.getDocumentElement(); | |||
| return root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| } | |||
| @@ -10,17 +10,18 @@ public class FileUtils { | |||
| /** | |||
| * 创建临时文件 | |||
| * | |||
| * @param inputStream | |||
| * @param name 文件名 | |||
| * @param ext 扩展名 | |||
| * @param tmpDirFile 临时文件夹目录 | |||
| * @param name 文件名 | |||
| * @param ext 扩展名 | |||
| * @param tmpDirFile 临时文件夹目录 | |||
| */ | |||
| public static File createTmpFile(InputStream inputStream, String name, String ext, File tmpDirFile) throws IOException { | |||
| FileOutputStream fos = null; | |||
| try { | |||
| File tmpFile; | |||
| if (tmpDirFile == null) { | |||
| tmpFile = File.createTempFile(name, '.' + ext); | |||
| tmpFile = File.createTempFile(name, '.' + ext); | |||
| } else { | |||
| tmpFile = File.createTempFile(name, '.' + ext, tmpDirFile); | |||
| } | |||
| @@ -51,12 +52,13 @@ public class FileUtils { | |||
| /** | |||
| * 创建临时文件 | |||
| * | |||
| * @param inputStream | |||
| * @param name 文件名 | |||
| * @param ext 扩展名 | |||
| * @param name 文件名 | |||
| * @param ext 扩展名 | |||
| */ | |||
| public static File createTmpFile(InputStream inputStream, String name, String ext) throws IOException { | |||
| return createTmpFile(inputStream, name, ext, null); | |||
| } | |||
| } | |||
| @@ -10,36 +10,42 @@ public interface ApacheHttpClientBuilder { | |||
| /** | |||
| * 构建httpclient实例 | |||
| * | |||
| * @return new instance of CloseableHttpClient | |||
| */ | |||
| CloseableHttpClient build(); | |||
| /** | |||
| * 代理服务器地址 | |||
| * | |||
| * @param httpProxyHost | |||
| */ | |||
| ApacheHttpClientBuilder httpProxyHost(String httpProxyHost); | |||
| /** | |||
| * 代理服务器端口 | |||
| * | |||
| * @param httpProxyPort | |||
| */ | |||
| ApacheHttpClientBuilder httpProxyPort(int httpProxyPort); | |||
| /** | |||
| * 代理服务器用户名 | |||
| * | |||
| * @param httpProxyUsername | |||
| */ | |||
| ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername); | |||
| /** | |||
| * 代理服务器密码 | |||
| * | |||
| * @param httpProxyPassword | |||
| */ | |||
| ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword); | |||
| /** | |||
| * ssl连接socket工厂 | |||
| * | |||
| * @param sslConnectionSocketFactory | |||
| */ | |||
| ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory); | |||
| @@ -28,12 +28,12 @@ import java.util.concurrent.TimeUnit; | |||
| * httpclient 连接管理器 | |||
| */ | |||
| @NotThreadSafe | |||
| public class DefaultApacheHttpHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| private int connectionRequestTimeout = 3000; | |||
| private int connectionTimeout = 5000; | |||
| private int soTimeout = 5000; | |||
| private int idleConnTimeout = 60000; | |||
| private int checkWaitTime = 5000; | |||
| private int checkWaitTime = 60000; | |||
| private int maxConnPerHost = 10; | |||
| private int maxTotalConn = 50; | |||
| private String userAgent; | |||
| @@ -51,109 +51,112 @@ public class DefaultApacheHttpHttpClientBuilder implements ApacheHttpClientBuild | |||
| private String httpProxyUsername; | |||
| private String httpProxyPassword; | |||
| /** | |||
| * 连接管理器 | |||
| */ | |||
| private PoolingHttpClientConnectionManager connectionManager; | |||
| /** | |||
| * 闲置连接监控线程 | |||
| */ | |||
| private IdleConnectionMonitorThread idleConnectionMonitorThread; | |||
| /** | |||
| * httpClientBuilder | |||
| */ | |||
| private HttpClientBuilder httpClientBuilder; | |||
| private boolean prepared = false; | |||
| private DefaultApacheHttpHttpClientBuilder() { | |||
| private DefaultApacheHttpClientBuilder() { | |||
| } | |||
| public static DefaultApacheHttpHttpClientBuilder get() { | |||
| return new DefaultApacheHttpHttpClientBuilder(); | |||
| public static DefaultApacheHttpClientBuilder get() { | |||
| return new DefaultApacheHttpClientBuilder(); | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) { | |||
| this.httpProxyHost = httpProxyHost; | |||
| return this; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) { | |||
| this.httpProxyPort = httpProxyPort; | |||
| return this; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) { | |||
| this.httpProxyUsername = httpProxyUsername; | |||
| return this; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) { | |||
| this.httpProxyPassword = httpProxyPassword; | |||
| return this; | |||
| } | |||
| public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory){ | |||
| @Override | |||
| public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { | |||
| this.sslConnectionSocketFactory = sslConnectionSocketFactory; | |||
| return this; | |||
| } | |||
| public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { | |||
| return idleConnectionMonitorThread; | |||
| return this.idleConnectionMonitorThread; | |||
| } | |||
| private void prepare(){ | |||
| private void prepare() { | |||
| Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | |||
| .register("http", plainConnectionSocketFactory) | |||
| .register("https", sslConnectionSocketFactory) | |||
| .build(); | |||
| connectionManager = new PoolingHttpClientConnectionManager(registry); | |||
| connectionManager.setMaxTotal(maxTotalConn); | |||
| connectionManager.setDefaultMaxPerRoute(maxConnPerHost); | |||
| .register("http", this.plainConnectionSocketFactory) | |||
| .register("https", this.sslConnectionSocketFactory) | |||
| .build(); | |||
| PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); | |||
| connectionManager.setMaxTotal(this.maxTotalConn); | |||
| connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost); | |||
| connectionManager.setDefaultSocketConfig( | |||
| SocketConfig.copy(SocketConfig.DEFAULT) | |||
| .setSoTimeout(soTimeout) | |||
| .build() | |||
| SocketConfig.copy(SocketConfig.DEFAULT) | |||
| .setSoTimeout(this.soTimeout) | |||
| .build() | |||
| ); | |||
| idleConnectionMonitorThread = new IdleConnectionMonitorThread(connectionManager, idleConnTimeout, checkWaitTime); | |||
| idleConnectionMonitorThread.setDaemon(true); | |||
| idleConnectionMonitorThread.start(); | |||
| httpClientBuilder = HttpClients.custom() | |||
| .setConnectionManager(connectionManager) | |||
| .setDefaultRequestConfig( | |||
| RequestConfig.custom() | |||
| .setSocketTimeout(soTimeout) | |||
| .setConnectTimeout(connectionTimeout) | |||
| .setConnectionRequestTimeout(connectionRequestTimeout) | |||
| .build() | |||
| ) | |||
| .setRetryHandler(httpRequestRetryHandler); | |||
| if (StringUtils.isNotBlank(httpProxyHost) && StringUtils.isNotBlank(httpProxyUsername)) { | |||
| this.idleConnectionMonitorThread = new IdleConnectionMonitorThread( | |||
| connectionManager, this.idleConnTimeout, this.checkWaitTime); | |||
| this.idleConnectionMonitorThread.setDaemon(true); | |||
| this.idleConnectionMonitorThread.start(); | |||
| this.httpClientBuilder = HttpClients.custom() | |||
| .setConnectionManager(connectionManager) | |||
| .setDefaultRequestConfig( | |||
| RequestConfig.custom() | |||
| .setSocketTimeout(this.soTimeout) | |||
| .setConnectTimeout(this.connectionTimeout) | |||
| .setConnectionRequestTimeout(this.connectionRequestTimeout) | |||
| .build() | |||
| ) | |||
| .setRetryHandler(this.httpRequestRetryHandler); | |||
| if (StringUtils.isNotBlank(this.httpProxyHost) | |||
| && StringUtils.isNotBlank(this.httpProxyUsername)) { | |||
| // 使用代理服务器 需要用户认证的代理服务器 | |||
| CredentialsProvider credsProvider = new BasicCredentialsProvider(); | |||
| credsProvider.setCredentials( | |||
| new AuthScope(httpProxyHost, httpProxyPort), | |||
| new UsernamePasswordCredentials(httpProxyUsername, httpProxyPassword)); | |||
| httpClientBuilder.setDefaultCredentialsProvider(credsProvider); | |||
| CredentialsProvider provider = new BasicCredentialsProvider(); | |||
| provider.setCredentials( | |||
| new AuthScope(this.httpProxyHost, this.httpProxyPort), | |||
| new UsernamePasswordCredentials(this.httpProxyUsername, | |||
| this.httpProxyPassword)); | |||
| this.httpClientBuilder.setDefaultCredentialsProvider(provider); | |||
| } | |||
| if (StringUtils.isNotBlank(userAgent)) { | |||
| httpClientBuilder.setUserAgent(userAgent); | |||
| if (StringUtils.isNotBlank(this.userAgent)) { | |||
| this.httpClientBuilder.setUserAgent(this.userAgent); | |||
| } | |||
| } | |||
| @Override | |||
| public CloseableHttpClient build() { | |||
| if(!prepared){ | |||
| if (!this.prepared) { | |||
| prepare(); | |||
| prepared = true; | |||
| this.prepared = true; | |||
| } | |||
| return httpClientBuilder.build(); | |||
| return this.httpClientBuilder.build(); | |||
| } | |||
| public static class IdleConnectionMonitorThread extends Thread { | |||
| @@ -172,11 +175,12 @@ public class DefaultApacheHttpHttpClientBuilder implements ApacheHttpClientBuild | |||
| @Override | |||
| public void run() { | |||
| try { | |||
| while (!shutdown) { | |||
| while (!this.shutdown) { | |||
| synchronized (this) { | |||
| wait(checkWaitTime); | |||
| connMgr.closeExpiredConnections(); | |||
| connMgr.closeIdleConnections(idleConnTimeout, TimeUnit.MILLISECONDS); | |||
| wait(this.checkWaitTime); | |||
| this.connMgr.closeExpiredConnections(); | |||
| this.connMgr.closeIdleConnections(this.idleConnTimeout, | |||
| TimeUnit.MILLISECONDS); | |||
| } | |||
| } | |||
| } catch (InterruptedException ignore) { | |||
| @@ -190,7 +194,7 @@ public class DefaultApacheHttpHttpClientBuilder implements ApacheHttpClientBuild | |||
| } | |||
| public void shutdown() { | |||
| shutdown = true; | |||
| this.shutdown = true; | |||
| synchronized (this) { | |||
| notifyAll(); | |||
| } | |||
| @@ -1,8 +1,5 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import org.apache.http.HttpEntity; | |||
| import org.apache.http.HttpResponse; | |||
| import org.apache.http.StatusLine; | |||
| @@ -10,10 +7,13 @@ import org.apache.http.client.HttpResponseException; | |||
| import org.apache.http.client.ResponseHandler; | |||
| import org.apache.http.util.EntityUtils; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| public class InputStreamResponseHandler implements ResponseHandler<InputStream> { | |||
| public static final ResponseHandler<InputStream> INSTANCE = new InputStreamResponseHandler(); | |||
| public InputStream handleResponse(final HttpResponse response) throws IOException { | |||
| final StatusLine statusLine = response.getStatusLine(); | |||
| final HttpEntity entity = response.getEntity(); | |||
| @@ -18,39 +18,39 @@ import java.io.IOException; | |||
| */ | |||
| public class JoddGetRequestExecutor implements RequestExecutor<String, String> { | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String queryParam) throws WxErrorException, IOException { | |||
| if (queryParam != null) { | |||
| if (uri.indexOf('?') == -1) { | |||
| uri += '?'; | |||
| } | |||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||
| } | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getHostName(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("GET"); | |||
| request.charset("UTF-8"); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String queryParam) throws WxErrorException, IOException { | |||
| if (queryParam != null) { | |||
| if (uri.indexOf('?') == -1) { | |||
| uri += '?'; | |||
| } | |||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||
| } | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getHostName(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("GET"); | |||
| request.charset("UTF-8"); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| @@ -18,33 +18,33 @@ import java.io.IOException; | |||
| */ | |||
| public class JoddPostRequestExecutor implements RequestExecutor<String, String> { | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String postEntity) throws WxErrorException, IOException { | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getAddress().getHostAddress(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("POST"); | |||
| request.charset("UTF-8"); | |||
| request.bodyText(postEntity); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String postEntity) throws WxErrorException, IOException { | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getAddress().getHostAddress(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("POST"); | |||
| request.charset("UTF-8"); | |||
| request.bodyText(postEntity); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| @@ -6,7 +6,6 @@ import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||
| import org.apache.http.Header; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpGet; | |||
| @@ -21,11 +20,11 @@ import java.util.regex.Pattern; | |||
| /** | |||
| * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class MediaDownloadRequestExecutor implements RequestExecutor<File, String> { | |||
| private File tmpDirFile; | |||
| public MediaDownloadRequestExecutor() { | |||
| @@ -36,7 +35,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor<File, Strin | |||
| super(); | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| @Override | |||
| public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { | |||
| @@ -46,7 +45,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor<File, Strin | |||
| } | |||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||
| } | |||
| HttpGet httpGet = new HttpGet(uri); | |||
| if (httpProxy != null) { | |||
| RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
| @@ -74,7 +73,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor<File, Strin | |||
| File localFile = FileUtils.createTmpFile(inputStream, name_ext[0], name_ext[1], tmpDirFile); | |||
| return localFile; | |||
| }finally { | |||
| } finally { | |||
| httpGet.releaseConnection(); | |||
| } | |||
| @@ -88,5 +87,5 @@ public class MediaDownloadRequestExecutor implements RequestExecutor<File, Strin | |||
| String fileName = m.group(1); | |||
| return fileName; | |||
| } | |||
| } | |||
| @@ -5,7 +5,6 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import org.apache.http.HttpEntity; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpPost; | |||
| @@ -19,8 +18,8 @@ import java.io.IOException; | |||
| /** | |||
| * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUploadResult, File> { | |||
| @@ -33,10 +32,10 @@ public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUpload | |||
| } | |||
| if (file != null) { | |||
| HttpEntity entity = MultipartEntityBuilder | |||
| .create() | |||
| .addBinaryBody("media", file) | |||
| .setMode(HttpMultipartMode.RFC6532) | |||
| .build(); | |||
| .create() | |||
| .addBinaryBody("media", file) | |||
| .setMode(HttpMultipartMode.RFC6532) | |||
| .build(); | |||
| httpPost.setEntity(entity); | |||
| httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString()); | |||
| } | |||
| @@ -47,7 +46,7 @@ public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUpload | |||
| throw new WxErrorException(error); | |||
| } | |||
| return WxMediaUploadResult.fromJson(responseContent); | |||
| }finally { | |||
| } finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| } | |||
| @@ -1,12 +1,11 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import java.io.IOException; | |||
| /** | |||
| * http请求执行器 | |||
| @@ -17,11 +16,10 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
| public interface RequestExecutor<T, E> { | |||
| /** | |||
| * | |||
| * @param httpclient 传入的httpClient | |||
| * @param httpProxy http代理对象,如果没有配置代理则为空 | |||
| * @param uri uri | |||
| * @param data 数据 | |||
| * @param httpProxy http代理对象,如果没有配置代理则为空 | |||
| * @param uri uri | |||
| * @param data 数据 | |||
| * @throws WxErrorException | |||
| * @throws ClientProtocolException | |||
| * @throws IOException | |||
| @@ -3,7 +3,6 @@ package me.chanjar.weixin.common.util.http; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpGet; | |||
| @@ -13,8 +12,8 @@ import java.io.IOException; | |||
| /** | |||
| * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class SimpleGetRequestExecutor implements RequestExecutor<String, String> { | |||
| @@ -39,7 +38,7 @@ public class SimpleGetRequestExecutor implements RequestExecutor<String, String> | |||
| throw new WxErrorException(error); | |||
| } | |||
| return responseContent; | |||
| }finally { | |||
| } finally { | |||
| httpGet.releaseConnection(); | |||
| } | |||
| } | |||
| @@ -4,7 +4,6 @@ import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import org.apache.http.Consts; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpPost; | |||
| @@ -15,8 +14,8 @@ import java.io.IOException; | |||
| /** | |||
| * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class SimplePostRequestExecutor implements RequestExecutor<String, String> { | |||
| @@ -40,7 +39,7 @@ public class SimplePostRequestExecutor implements RequestExecutor<String, String | |||
| throw new WxErrorException(error); | |||
| } | |||
| return responseContent; | |||
| }finally { | |||
| } finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| } | |||
| @@ -1,7 +1,5 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import org.apache.http.Consts; | |||
| import org.apache.http.HttpEntity; | |||
| import org.apache.http.HttpResponse; | |||
| @@ -10,15 +8,17 @@ import org.apache.http.client.HttpResponseException; | |||
| import org.apache.http.client.ResponseHandler; | |||
| import org.apache.http.util.EntityUtils; | |||
| import java.io.IOException; | |||
| /** | |||
| * copy from {@link org.apache.http.impl.client.BasicResponseHandler} | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class Utf8ResponseHandler implements ResponseHandler<String> { | |||
| public static final ResponseHandler<String> INSTANCE = new Utf8ResponseHandler(); | |||
| public String handleResponse(final HttpResponse response) throws IOException { | |||
| final StatusLine statusLine = response.getStatusLine(); | |||
| final HttpEntity entity = response.getEntity(); | |||
| @@ -15,101 +15,101 @@ import com.google.gson.JsonObject; | |||
| public class GsonHelper { | |||
| public static boolean isNull(JsonElement element) { | |||
| return element == null || element.isJsonNull(); | |||
| } | |||
| public static boolean isNotNull(JsonElement element) { | |||
| return !isNull(element); | |||
| } | |||
| public static Long getLong(JsonObject json, String property) { | |||
| return getAsLong(json.get(property)); | |||
| } | |||
| public static long getPrimitiveLong(JsonObject json, String property) { | |||
| return getAsPrimitiveLong(json.get(property)); | |||
| } | |||
| public static Integer getInteger(JsonObject json, String property) { | |||
| return getAsInteger(json.get(property)); | |||
| } | |||
| public static int getPrimitiveInteger(JsonObject json, String property) { | |||
| return getAsPrimitiveInt(json.get(property)); | |||
| } | |||
| public static Double getDouble(JsonObject json, String property) { | |||
| return getAsDouble(json.get(property)); | |||
| } | |||
| public static double getPrimitiveDouble(JsonObject json, String property) { | |||
| return getAsPrimitiveDouble(json.get(property)); | |||
| } | |||
| public static Float getFloat(JsonObject json, String property) { | |||
| return getAsFloat(json.get(property)); | |||
| } | |||
| public static float getPrimitiveFloat(JsonObject json, String property) { | |||
| return getAsPrimitiveFloat(json.get(property)); | |||
| } | |||
| public static Boolean getBoolean(JsonObject json, String property) { | |||
| return getAsBoolean(json.get(property)); | |||
| } | |||
| public static String getString(JsonObject json, String property) { | |||
| return getAsString(json.get(property)); | |||
| } | |||
| public static String getAsString(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsString(); | |||
| } | |||
| public static Long getAsLong(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsLong(); | |||
| } | |||
| public static long getAsPrimitiveLong(JsonElement element) { | |||
| Long r = getAsLong(element); | |||
| return r == null ? 0l : r; | |||
| } | |||
| public static Integer getAsInteger(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsInt(); | |||
| } | |||
| public static int getAsPrimitiveInt(JsonElement element) { | |||
| Integer r = getAsInteger(element); | |||
| return r == null ? 0 : r; | |||
| } | |||
| public static Boolean getAsBoolean(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsBoolean(); | |||
| } | |||
| public static boolean getAsPrimitiveBool(JsonElement element) { | |||
| Boolean r = getAsBoolean(element); | |||
| return r != null && r.booleanValue(); | |||
| } | |||
| public static Double getAsDouble(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsDouble(); | |||
| } | |||
| public static double getAsPrimitiveDouble(JsonElement element) { | |||
| Double r = getAsDouble(element); | |||
| return r == null ? 0d : r; | |||
| } | |||
| public static Float getAsFloat(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsFloat(); | |||
| } | |||
| public static float getAsPrimitiveFloat(JsonElement element) { | |||
| Float r = getAsFloat(element); | |||
| return r == null ? 0f : r; | |||
| } | |||
| public static boolean isNull(JsonElement element) { | |||
| return element == null || element.isJsonNull(); | |||
| } | |||
| public static boolean isNotNull(JsonElement element) { | |||
| return !isNull(element); | |||
| } | |||
| public static Long getLong(JsonObject json, String property) { | |||
| return getAsLong(json.get(property)); | |||
| } | |||
| public static long getPrimitiveLong(JsonObject json, String property) { | |||
| return getAsPrimitiveLong(json.get(property)); | |||
| } | |||
| public static Integer getInteger(JsonObject json, String property) { | |||
| return getAsInteger(json.get(property)); | |||
| } | |||
| public static int getPrimitiveInteger(JsonObject json, String property) { | |||
| return getAsPrimitiveInt(json.get(property)); | |||
| } | |||
| public static Double getDouble(JsonObject json, String property) { | |||
| return getAsDouble(json.get(property)); | |||
| } | |||
| public static double getPrimitiveDouble(JsonObject json, String property) { | |||
| return getAsPrimitiveDouble(json.get(property)); | |||
| } | |||
| public static Float getFloat(JsonObject json, String property) { | |||
| return getAsFloat(json.get(property)); | |||
| } | |||
| public static float getPrimitiveFloat(JsonObject json, String property) { | |||
| return getAsPrimitiveFloat(json.get(property)); | |||
| } | |||
| public static Boolean getBoolean(JsonObject json, String property) { | |||
| return getAsBoolean(json.get(property)); | |||
| } | |||
| public static String getString(JsonObject json, String property) { | |||
| return getAsString(json.get(property)); | |||
| } | |||
| public static String getAsString(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsString(); | |||
| } | |||
| public static Long getAsLong(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsLong(); | |||
| } | |||
| public static long getAsPrimitiveLong(JsonElement element) { | |||
| Long r = getAsLong(element); | |||
| return r == null ? 0l : r; | |||
| } | |||
| public static Integer getAsInteger(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsInt(); | |||
| } | |||
| public static int getAsPrimitiveInt(JsonElement element) { | |||
| Integer r = getAsInteger(element); | |||
| return r == null ? 0 : r; | |||
| } | |||
| public static Boolean getAsBoolean(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsBoolean(); | |||
| } | |||
| public static boolean getAsPrimitiveBool(JsonElement element) { | |||
| Boolean r = getAsBoolean(element); | |||
| return r != null && r.booleanValue(); | |||
| } | |||
| public static Double getAsDouble(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsDouble(); | |||
| } | |||
| public static double getAsPrimitiveDouble(JsonElement element) { | |||
| Double r = getAsDouble(element); | |||
| return r == null ? 0d : r; | |||
| } | |||
| public static Float getAsFloat(JsonElement element) { | |||
| return isNull(element) ? null : element.getAsFloat(); | |||
| } | |||
| public static float getAsPrimitiveFloat(JsonElement element) { | |||
| Float r = getAsFloat(element); | |||
| return r == null ? 0f : r; | |||
| } | |||
| } | |||
| @@ -14,9 +14,7 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxAccessTokenAdapter implements JsonDeserializer<WxAccessToken> { | |||
| @@ -32,5 +30,5 @@ public class WxAccessTokenAdapter implements JsonDeserializer<WxAccessToken> { | |||
| } | |||
| return accessToken; | |||
| } | |||
| } | |||
| @@ -14,9 +14,7 @@ import me.chanjar.weixin.common.bean.result.WxError; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxErrorAdapter implements JsonDeserializer<WxError> { | |||
| @@ -33,5 +31,5 @@ public class WxErrorAdapter implements JsonDeserializer<WxError> { | |||
| wxError.setJson(json.toString()); | |||
| return wxError; | |||
| } | |||
| } | |||
| @@ -2,9 +2,10 @@ package me.chanjar.weixin.common.util.json; | |||
| import com.google.gson.Gson; | |||
| import com.google.gson.GsonBuilder; | |||
| import me.chanjar.weixin.common.bean.WxMenu; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| public class WxGsonBuilder { | |||
| @@ -14,9 +14,7 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxMediaUploadResultAdapter implements JsonDeserializer<WxMediaUploadResult> { | |||
| @@ -38,5 +36,5 @@ public class WxMediaUploadResultAdapter implements JsonDeserializer<WxMediaUploa | |||
| } | |||
| return uploadResult; | |||
| } | |||
| } | |||
| @@ -8,6 +8,8 @@ | |||
| */ | |||
| package me.chanjar.weixin.common.util.json; | |||
| import java.lang.reflect.Type; | |||
| import com.google.gson.JsonArray; | |||
| import com.google.gson.JsonDeserializationContext; | |||
| import com.google.gson.JsonDeserializer; | |||
| @@ -16,14 +18,14 @@ import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParseException; | |||
| import com.google.gson.JsonSerializationContext; | |||
| import com.google.gson.JsonSerializer; | |||
| import me.chanjar.weixin.common.bean.WxMenu; | |||
| import java.lang.reflect.Type; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuButton; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuRule; | |||
| /** | |||
| * | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializer<WxMenu> { | |||
| @@ -31,28 +33,29 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||
| JsonObject json = new JsonObject(); | |||
| JsonArray buttonArray = new JsonArray(); | |||
| for (WxMenu.WxMenuButton button : menu.getButtons()) { | |||
| for (WxMenuButton button : menu.getButtons()) { | |||
| JsonObject buttonJson = convertToJson(button); | |||
| buttonArray.add(buttonJson); | |||
| } | |||
| json.add("button", buttonArray); | |||
| if (menu.getMatchRule() != null) { | |||
| json.add("matchrule", convertToJson(menu.getMatchRule())); | |||
| } | |||
| return json; | |||
| } | |||
| protected JsonObject convertToJson(WxMenu.WxMenuButton button) { | |||
| protected JsonObject convertToJson(WxMenuButton button) { | |||
| JsonObject buttonJson = new JsonObject(); | |||
| buttonJson.addProperty("type", button.getType()); | |||
| buttonJson.addProperty("name", button.getName()); | |||
| buttonJson.addProperty("key", button.getKey()); | |||
| buttonJson.addProperty("url", button.getUrl()); | |||
| buttonJson.addProperty("media_id", button.getMediaId()); | |||
| if (button.getSubButtons() != null && button.getSubButtons().size() > 0) { | |||
| JsonArray buttonArray = new JsonArray(); | |||
| for (WxMenu.WxMenuButton sub_button : button.getSubButtons()) { | |||
| for (WxMenuButton sub_button : button.getSubButtons()) { | |||
| buttonArray.add(convertToJson(sub_button)); | |||
| } | |||
| buttonJson.add("sub_button", buttonArray); | |||
| @@ -60,15 +63,15 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||
| return buttonJson; | |||
| } | |||
| protected JsonObject convertToJson(WxMenu.WxMenuRule menuRule){ | |||
| protected JsonObject convertToJson(WxMenuRule menuRule) { | |||
| JsonObject matchRule = new JsonObject(); | |||
| matchRule.addProperty("tag_id",menuRule.getTagId()); | |||
| matchRule.addProperty("sex",menuRule.getSex()); | |||
| matchRule.addProperty("country",menuRule.getCountry()); | |||
| matchRule.addProperty("province",menuRule.getProvince()); | |||
| matchRule.addProperty("city",menuRule.getCity()); | |||
| matchRule.addProperty("client_platform_type",menuRule.getClientPlatformType()); | |||
| matchRule.addProperty("language",menuRule.getLanguage()); | |||
| matchRule.addProperty("tag_id", menuRule.getTagId()); | |||
| matchRule.addProperty("sex", menuRule.getSex()); | |||
| matchRule.addProperty("country", menuRule.getCountry()); | |||
| matchRule.addProperty("province", menuRule.getProvince()); | |||
| matchRule.addProperty("city", menuRule.getCity()); | |||
| matchRule.addProperty("client_platform_type", menuRule.getClientPlatformType()); | |||
| matchRule.addProperty("language", menuRule.getLanguage()); | |||
| return matchRule; | |||
| } | |||
| @@ -83,7 +86,7 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||
| JsonArray buttonsJson = menuJson.get("button").getAsJsonArray(); | |||
| for (int i = 0; i < buttonsJson.size(); i++) { | |||
| JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | |||
| WxMenu.WxMenuButton button = convertFromJson(buttonJson); | |||
| WxMenuButton button = convertFromJson(buttonJson); | |||
| menu.getButtons().add(button); | |||
| if (buttonJson.get("sub_button") == null || buttonJson.get("sub_button").isJsonNull()) { | |||
| continue; | |||
| @@ -96,13 +99,14 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||
| } | |||
| return menu; | |||
| } | |||
| protected WxMenu.WxMenuButton convertFromJson(JsonObject json) { | |||
| WxMenu.WxMenuButton button = new WxMenu.WxMenuButton(); | |||
| protected WxMenuButton convertFromJson(JsonObject json) { | |||
| WxMenuButton button = new WxMenuButton(); | |||
| button.setName(GsonHelper.getString(json, "name")); | |||
| button.setKey(GsonHelper.getString(json, "key")); | |||
| button.setUrl(GsonHelper.getString(json, "url")); | |||
| button.setType(GsonHelper.getString(json, "type")); | |||
| button.setMediaId(GsonHelper.getString(json, "media_id")); | |||
| return button; | |||
| } | |||
| @@ -18,30 +18,24 @@ | |||
| package me.chanjar.weixin.common.util.res; | |||
| import java.text.MessageFormat; | |||
| import java.util.Enumeration; | |||
| import java.util.Hashtable; | |||
| import java.util.LinkedHashMap; | |||
| import java.util.Locale; | |||
| import java.util.Map; | |||
| import java.util.MissingResourceException; | |||
| import java.util.ResourceBundle; | |||
| import java.util.*; | |||
| /** | |||
| * An internationalization / localization helper class which reduces | |||
| * the bother of handling ResourceBundles and takes care of the | |||
| * common cases of message formating which otherwise require the | |||
| * creation of Object arrays and such. | |||
| * | |||
| * <p> | |||
| * <p>The StringManager operates on a package basis. One StringManager | |||
| * per package can be created and accessed via the getManager method | |||
| * call. | |||
| * | |||
| * <p> | |||
| * <p>The StringManager will look for a ResourceBundle named by | |||
| * the package name given plus the suffix of "LocalStrings". In | |||
| * practice, this means that the localized information will be contained | |||
| * in a LocalStrings.properties file located in the package | |||
| * directory of the classpath. | |||
| * | |||
| * <p> | |||
| * <p>Please see the documentation for java.util.ResourceBundle for | |||
| * more information. | |||
| * | |||
| @@ -52,152 +46,80 @@ import java.util.ResourceBundle; | |||
| */ | |||
| public class StringManager { | |||
| private static int LOCALE_CACHE_SIZE = 10; | |||
| /** | |||
| * The ResourceBundle for this StringManager. | |||
| */ | |||
| private final ResourceBundle bundle; | |||
| private final Locale locale; | |||
| /** | |||
| * Creates a new StringManager for a given package. This is a | |||
| * private method and all access to it is arbitrated by the | |||
| * static getManager method call so that only one StringManager | |||
| * per package will be created. | |||
| * | |||
| * @param packageName Name of package to create StringManager for. | |||
| */ | |||
| private StringManager(String packageName, Locale locale) { | |||
| String bundleName = packageName + ".LocalStrings"; | |||
| ResourceBundle bnd = null; | |||
| private static final Map<String, Map<Locale, StringManager>> managers = | |||
| new Hashtable<String, Map<Locale, StringManager>>(); | |||
| private static int LOCALE_CACHE_SIZE = 10; | |||
| /** | |||
| * The ResourceBundle for this StringManager. | |||
| */ | |||
| private final ResourceBundle bundle; | |||
| private final Locale locale; | |||
| /** | |||
| * Creates a new StringManager for a given package. This is a | |||
| * private method and all access to it is arbitrated by the | |||
| * static getManager method call so that only one StringManager | |||
| * per package will be created. | |||
| * | |||
| * @param packageName Name of package to create StringManager for. | |||
| */ | |||
| private StringManager(String packageName, Locale locale) { | |||
| String bundleName = packageName + ".LocalStrings"; | |||
| ResourceBundle bnd = null; | |||
| try { | |||
| bnd = ResourceBundle.getBundle(bundleName, locale); | |||
| } catch (MissingResourceException ex) { | |||
| // Try from the current loader (that's the case for trusted apps) | |||
| // Should only be required if using a TC5 style classloader structure | |||
| // where common != shared != server | |||
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); | |||
| if (cl != null) { | |||
| try { | |||
| bnd = ResourceBundle.getBundle(bundleName, locale); | |||
| } catch( MissingResourceException ex ) { | |||
| // Try from the current loader (that's the case for trusted apps) | |||
| // Should only be required if using a TC5 style classloader structure | |||
| // where common != shared != server | |||
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); | |||
| if( cl != null ) { | |||
| try { | |||
| bnd = ResourceBundle.getBundle(bundleName, locale, cl); | |||
| } catch(MissingResourceException ex2) { | |||
| // Ignore | |||
| } | |||
| } | |||
| } | |||
| bundle = bnd; | |||
| // Get the actual locale, which may be different from the requested one | |||
| if (bundle != null) { | |||
| Locale bundleLocale = bundle.getLocale(); | |||
| if (bundleLocale.equals(Locale.ROOT)) { | |||
| this.locale = Locale.ENGLISH; | |||
| } else { | |||
| this.locale = bundleLocale; | |||
| } | |||
| } else { | |||
| this.locale = null; | |||
| bnd = ResourceBundle.getBundle(bundleName, locale, cl); | |||
| } catch (MissingResourceException ex2) { | |||
| // Ignore | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| Get a string from the underlying resource bundle or return | |||
| null if the String is not found. | |||
| @param key to desired resource String | |||
| @return resource String matching <i>key</i> from underlying | |||
| bundle or null if not found. | |||
| @throws IllegalArgumentException if <i>key</i> is null. | |||
| */ | |||
| public String getString(String key) { | |||
| if(key == null){ | |||
| String msg = "key may not have a null value"; | |||
| throw new IllegalArgumentException(msg); | |||
| } | |||
| String str = null; | |||
| try { | |||
| // Avoid NPE if bundle is null and treat it like an MRE | |||
| if (bundle != null) { | |||
| str = bundle.getString(key); | |||
| } | |||
| } catch(MissingResourceException mre) { | |||
| //bad: shouldn't mask an exception the following way: | |||
| // str = "[cannot find message associated with key '" + key + | |||
| // "' due to " + mre + "]"; | |||
| // because it hides the fact that the String was missing | |||
| // from the calling code. | |||
| //good: could just throw the exception (or wrap it in another) | |||
| // but that would probably cause much havoc on existing | |||
| // code. | |||
| //better: consistent with container pattern to | |||
| // simply return null. Calling code can then do | |||
| // a null check. | |||
| str = null; | |||
| } | |||
| return str; | |||
| bundle = bnd; | |||
| // Get the actual locale, which may be different from the requested one | |||
| if (bundle != null) { | |||
| Locale bundleLocale = bundle.getLocale(); | |||
| if (bundleLocale.equals(Locale.ROOT)) { | |||
| this.locale = Locale.ENGLISH; | |||
| } else { | |||
| this.locale = bundleLocale; | |||
| } | |||
| } else { | |||
| this.locale = null; | |||
| } | |||
| /** | |||
| * Get a string from the underlying resource bundle and format | |||
| * it with the given set of arguments. | |||
| * | |||
| * @param key | |||
| * @param args | |||
| */ | |||
| public String getString(final String key, final Object... args) { | |||
| String value = getString(key); | |||
| if (value == null) { | |||
| value = key; | |||
| } | |||
| MessageFormat mf = new MessageFormat(value); | |||
| mf.setLocale(locale); | |||
| return mf.format(args, new StringBuffer(), null).toString(); | |||
| } | |||
| /** | |||
| * Identify the Locale this StringManager is associated with | |||
| */ | |||
| public Locale getLocale() { | |||
| return locale; | |||
| } | |||
| // -------------------------------------------------------------- | |||
| // STATIC SUPPORT METHODS | |||
| // -------------------------------------------------------------- | |||
| private static final Map<String, Map<Locale,StringManager>> managers = | |||
| new Hashtable<String, Map<Locale,StringManager>>(); | |||
| /** | |||
| * Get the StringManager for a particular package. If a manager for | |||
| * a package already exists, it will be reused, else a new | |||
| * StringManager will be created and returned. | |||
| * | |||
| * @param packageName The package name | |||
| */ | |||
| public static final synchronized StringManager getManager( | |||
| String packageName) { | |||
| return getManager(packageName, Locale.getDefault()); | |||
| } | |||
| /** | |||
| * Get the StringManager for a particular package and Locale. If a manager | |||
| * for a package/Locale combination already exists, it will be reused, else | |||
| * a new StringManager will be created and returned. | |||
| * | |||
| * @param packageName The package name | |||
| * @param locale The Locale | |||
| */ | |||
| public static final synchronized StringManager getManager( | |||
| String packageName, Locale locale) { | |||
| Map<Locale,StringManager> map = managers.get(packageName); | |||
| if (map == null) { | |||
| } | |||
| /** | |||
| * Get the StringManager for a particular package. If a manager for | |||
| * a package already exists, it will be reused, else a new | |||
| * StringManager will be created and returned. | |||
| * | |||
| * @param packageName The package name | |||
| */ | |||
| public static final synchronized StringManager getManager( | |||
| String packageName) { | |||
| return getManager(packageName, Locale.getDefault()); | |||
| } | |||
| /** | |||
| * Get the StringManager for a particular package and Locale. If a manager | |||
| * for a package/Locale combination already exists, it will be reused, else | |||
| * a new StringManager will be created and returned. | |||
| * | |||
| * @param packageName The package name | |||
| * @param locale The Locale | |||
| */ | |||
| public static final synchronized StringManager getManager( | |||
| String packageName, Locale locale) { | |||
| Map<Locale, StringManager> map = managers.get(packageName); | |||
| if (map == null) { | |||
| /* | |||
| * Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE. | |||
| * Expansion occurs when size() exceeds capacity. Therefore keep | |||
| @@ -206,43 +128,113 @@ public class StringManager { | |||
| * for removal needs to use one less than the maximum desired size | |||
| * | |||
| */ | |||
| map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) { | |||
| private static final long serialVersionUID = 1L; | |||
| @Override | |||
| protected boolean removeEldestEntry( | |||
| Map.Entry<Locale,StringManager> eldest) { | |||
| return size() > (LOCALE_CACHE_SIZE - 1); | |||
| } | |||
| }; | |||
| managers.put(packageName, map); | |||
| } | |||
| map = new LinkedHashMap<Locale, StringManager>(LOCALE_CACHE_SIZE, 1, true) { | |||
| private static final long serialVersionUID = 1L; | |||
| StringManager mgr = map.get(locale); | |||
| if (mgr == null) { | |||
| mgr = new StringManager(packageName, locale); | |||
| map.put(locale, mgr); | |||
| @Override | |||
| protected boolean removeEldestEntry( | |||
| Map.Entry<Locale, StringManager> eldest) { | |||
| return size() > (LOCALE_CACHE_SIZE - 1); | |||
| } | |||
| return mgr; | |||
| }; | |||
| managers.put(packageName, map); | |||
| } | |||
| /** | |||
| * Retrieve the StringManager for a list of Locales. The first StringManager | |||
| * found will be returned. | |||
| * | |||
| * @param requestedLocales the list of Locales | |||
| * | |||
| * @return the found StringManager or the default StringManager | |||
| */ | |||
| public static StringManager getManager(String packageName, | |||
| Enumeration<Locale> requestedLocales) { | |||
| while (requestedLocales.hasMoreElements()) { | |||
| Locale locale = requestedLocales.nextElement(); | |||
| StringManager result = getManager(packageName, locale); | |||
| if (result.getLocale().equals(locale)) { | |||
| return result; | |||
| } | |||
| } | |||
| // Return the default | |||
| return getManager(packageName); | |||
| StringManager mgr = map.get(locale); | |||
| if (mgr == null) { | |||
| mgr = new StringManager(packageName, locale); | |||
| map.put(locale, mgr); | |||
| } | |||
| return mgr; | |||
| } | |||
| // -------------------------------------------------------------- | |||
| // STATIC SUPPORT METHODS | |||
| // -------------------------------------------------------------- | |||
| /** | |||
| * Retrieve the StringManager for a list of Locales. The first StringManager | |||
| * found will be returned. | |||
| * | |||
| * @param requestedLocales the list of Locales | |||
| * @return the found StringManager or the default StringManager | |||
| */ | |||
| public static StringManager getManager(String packageName, | |||
| Enumeration<Locale> requestedLocales) { | |||
| while (requestedLocales.hasMoreElements()) { | |||
| Locale locale = requestedLocales.nextElement(); | |||
| StringManager result = getManager(packageName, locale); | |||
| if (result.getLocale().equals(locale)) { | |||
| return result; | |||
| } | |||
| } | |||
| // Return the default | |||
| return getManager(packageName); | |||
| } | |||
| /** | |||
| * Get a string from the underlying resource bundle or return | |||
| * null if the String is not found. | |||
| * | |||
| * @param key to desired resource String | |||
| * @return resource String matching <i>key</i> from underlying | |||
| * bundle or null if not found. | |||
| * @throws IllegalArgumentException if <i>key</i> is null. | |||
| */ | |||
| public String getString(String key) { | |||
| if (key == null) { | |||
| String msg = "key may not have a null value"; | |||
| throw new IllegalArgumentException(msg); | |||
| } | |||
| String str = null; | |||
| try { | |||
| // Avoid NPE if bundle is null and treat it like an MRE | |||
| if (bundle != null) { | |||
| str = bundle.getString(key); | |||
| } | |||
| } catch (MissingResourceException mre) { | |||
| //bad: shouldn't mask an exception the following way: | |||
| // str = "[cannot find message associated with key '" + key + | |||
| // "' due to " + mre + "]"; | |||
| // because it hides the fact that the String was missing | |||
| // from the calling code. | |||
| //good: could just throw the exception (or wrap it in another) | |||
| // but that would probably cause much havoc on existing | |||
| // code. | |||
| //better: consistent with container pattern to | |||
| // simply return null. Calling code can then do | |||
| // a null check. | |||
| str = null; | |||
| } | |||
| return str; | |||
| } | |||
| /** | |||
| * Get a string from the underlying resource bundle and format | |||
| * it with the given set of arguments. | |||
| * | |||
| * @param key | |||
| * @param args | |||
| */ | |||
| public String getString(final String key, final Object... args) { | |||
| String value = getString(key); | |||
| if (value == null) { | |||
| value = key; | |||
| } | |||
| MessageFormat mf = new MessageFormat(value); | |||
| mf.setLocale(locale); | |||
| return mf.format(args, new StringBuffer(), null).toString(); | |||
| } | |||
| /** | |||
| * Identify the Locale this StringManager is associated with | |||
| */ | |||
| public Locale getLocale() { | |||
| return locale; | |||
| } | |||
| } | |||
| @@ -1,7 +1,5 @@ | |||
| package me.chanjar.weixin.common.util.xml; | |||
| import java.io.Writer; | |||
| import com.thoughtworks.xstream.XStream; | |||
| import com.thoughtworks.xstream.core.util.QuickWriter; | |||
| import com.thoughtworks.xstream.io.HierarchicalStreamWriter; | |||
| @@ -10,6 +8,8 @@ import com.thoughtworks.xstream.io.xml.XppDriver; | |||
| import com.thoughtworks.xstream.security.NullPermission; | |||
| import com.thoughtworks.xstream.security.PrimitiveTypePermission; | |||
| import java.io.Writer; | |||
| public class XStreamInitializer { | |||
| public static XStream getInstance() { | |||
| @@ -22,6 +22,7 @@ public class XStreamInitializer { | |||
| protected String SUFFIX_CDATA = "]]>"; | |||
| protected String PREFIX_MEDIA_ID = "<MediaId>"; | |||
| protected String SUFFIX_MEDIA_ID = "</MediaId>"; | |||
| @Override | |||
| protected void writeText(QuickWriter writer, String text) { | |||
| if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) { | |||
| @@ -14,5 +14,5 @@ public class WxAccessTokenTest { | |||
| Assert.assertTrue(wxError.getExpiresIn() == 7200); | |||
| } | |||
| } | |||
| @@ -15,7 +15,7 @@ public class WxErrorTest { | |||
| Assert.assertEquals(wxError.getErrorMsg(), "invalid openid"); | |||
| } | |||
| public void testFromBadJson1() { | |||
| String json = "{ \"errcode\": 40003, \"errmsg\": \"invalid openid\", \"media_id\": \"12323423dsfafsf232f\" }"; | |||
| @@ -24,7 +24,7 @@ public class WxErrorTest { | |||
| Assert.assertEquals(wxError.getErrorMsg(), "invalid openid"); | |||
| } | |||
| public void testFromBadJson2() { | |||
| String json = "{\"access_token\":\"ACCESS_TOKEN\",\"expires_in\":7200}"; | |||
| @@ -33,5 +33,5 @@ public class WxErrorTest { | |||
| Assert.assertEquals(wxError.getErrorMsg(), null); | |||
| } | |||
| } | |||
| @@ -1,58 +1,61 @@ | |||
| package me.chanjar.weixin.common.bean; | |||
| import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; | |||
| import org.testng.Assert; | |||
| import org.testng.annotations.DataProvider; | |||
| import org.testng.annotations.Test; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuButton; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenuRule; | |||
| @Test | |||
| public class WxMenuTest { | |||
| @Test(dataProvider="wxReturnMenu") | |||
| @Test(dataProvider = "wxReturnMenu") | |||
| public void testFromJson(String json) { | |||
| WxMenu menu = WxMenu.fromJson(json); | |||
| Assert.assertEquals(menu.getButtons().size(), 3); | |||
| } | |||
| @Test(dataProvider="wxPushMenu") | |||
| @Test(dataProvider = "wxPushMenu") | |||
| public void testToJson(String json) { | |||
| 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.getButtons().add(button1); | |||
| menu.getButtons().add(button2); | |||
| menu.getButtons().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.getSubButtons().add(button31); | |||
| button3.getSubButtons().add(button32); | |||
| button3.getSubButtons().add(button33); | |||
| Assert.assertEquals(menu.toJson(), json); | |||
| } | |||
| @@ -66,7 +69,7 @@ public class WxMenuTest { | |||
| menu.getButtons().add(button1); | |||
| WxMenu.WxMenuRule wxMenuRule = new WxMenu.WxMenuRule(); | |||
| WxMenuRule wxMenuRule = new WxMenuRule(); | |||
| wxMenuRule.setTagId("2"); | |||
| wxMenuRule.setSex("1"); | |||
| wxMenuRule.setCountry("中国"); | |||
| @@ -78,82 +81,82 @@ public class WxMenuTest { | |||
| Assert.assertEquals(menu.toJson(), json); | |||
| } | |||
| @DataProvider | |||
| public Object[][] wxReturnMenu() { | |||
| Object[][] res = menuJson(); | |||
| Object[][] res = menuJson(); | |||
| String json = "{ \"menu\" : " + res[0][0] + " }"; | |||
| return new Object[][] { | |||
| new Object[] { json } | |||
| return new Object[][]{ | |||
| new Object[]{json} | |||
| }; | |||
| } | |||
| @DataProvider(name="wxPushMenu") | |||
| @DataProvider(name = "wxPushMenu") | |||
| public Object[][] menuJson() { | |||
| String json = | |||
| "{" | |||
| +"\"button\":[" | |||
| +"{" | |||
| +"\"type\":\"click\"," | |||
| +"\"name\":\"今日歌曲\"," | |||
| +"\"key\":\"V1001_TODAY_MUSIC\"" | |||
| +"}," | |||
| +"{" | |||
| +"\"type\":\"click\"," | |||
| +"\"name\":\"歌手简介\"," | |||
| +"\"key\":\"V1001_TODAY_SINGER\"" | |||
| +"}," | |||
| +"{" | |||
| +"\"name\":\"菜单\"," | |||
| +"\"sub_button\":[" | |||
| +"{" | |||
| +"\"type\":\"view\"," | |||
| +"\"name\":\"搜索\"," | |||
| +"\"url\":\"http://www.soso.com/\"" | |||
| +"}," | |||
| +"{" | |||
| +"\"type\":\"view\"," | |||
| +"\"name\":\"视频\"," | |||
| +"\"url\":\"http://v.qq.com/\"" | |||
| +"}," | |||
| +"{" | |||
| +"\"type\":\"click\"," | |||
| +"\"name\":\"赞一下我们\"," | |||
| +"\"key\":\"V1001_GOOD\"" | |||
| +"}" | |||
| +"]" | |||
| +"}" | |||
| +"]" | |||
| +"}"; | |||
| return new Object[][] { | |||
| new Object[] { json } | |||
| String json = | |||
| "{" | |||
| + "\"button\":[" | |||
| + "{" | |||
| + "\"type\":\"click\"," | |||
| + "\"name\":\"今日歌曲\"," | |||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||
| + "}," | |||
| + "{" | |||
| + "\"type\":\"click\"," | |||
| + "\"name\":\"歌手简介\"," | |||
| + "\"key\":\"V1001_TODAY_SINGER\"" | |||
| + "}," | |||
| + "{" | |||
| + "\"name\":\"菜单\"," | |||
| + "\"sub_button\":[" | |||
| + "{" | |||
| + "\"type\":\"view\"," | |||
| + "\"name\":\"搜索\"," | |||
| + "\"url\":\"http://www.soso.com/\"" | |||
| + "}," | |||
| + "{" | |||
| + "\"type\":\"view\"," | |||
| + "\"name\":\"视频\"," | |||
| + "\"url\":\"http://v.qq.com/\"" | |||
| + "}," | |||
| + "{" | |||
| + "\"type\":\"click\"," | |||
| + "\"name\":\"赞一下我们\"," | |||
| + "\"key\":\"V1001_GOOD\"" | |||
| + "}" | |||
| + "]" | |||
| + "}" | |||
| + "]" | |||
| + "}"; | |||
| return new Object[][]{ | |||
| new Object[]{json} | |||
| }; | |||
| } | |||
| @DataProvider(name = "wxAddConditionalMenu") | |||
| public Object[][] addConditionalMenuJson(){ | |||
| public Object[][] addConditionalMenuJson() { | |||
| String json = | |||
| "{" | |||
| +"\"button\":[" | |||
| +"{" | |||
| +"\"type\":\"click\"," | |||
| +"\"name\":\"今日歌曲\"," | |||
| +"\"key\":\"V1001_TODAY_MUSIC\"" | |||
| +"}" | |||
| +"]," | |||
| +"\"matchrule\":{" | |||
| +"\"group_id\":\"2\"," | |||
| +"\"sex\":\"1\"," | |||
| +"\"country\":\"中国\"," | |||
| +"\"province\":\"广东\"," | |||
| +"\"city\":\"广州\"," | |||
| +"\"client_platform_type\":\"2\"," | |||
| +"\"language\":\"zh_CN\"" | |||
| +"}" | |||
| +"}"; | |||
| "{" | |||
| + "\"button\":[" | |||
| + "{" | |||
| + "\"type\":\"click\"," | |||
| + "\"name\":\"今日歌曲\"," | |||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||
| + "}" | |||
| + "]," | |||
| + "\"matchrule\":{" | |||
| + "\"group_id\":\"2\"," | |||
| + "\"sex\":\"1\"," | |||
| + "\"country\":\"中国\"," | |||
| + "\"province\":\"广东\"," | |||
| + "\"city\":\"广州\"," | |||
| + "\"client_platform_type\":\"2\"," | |||
| + "\"language\":\"zh_CN\"" | |||
| + "}" | |||
| + "}"; | |||
| return new Object[][]{ | |||
| new Object[]{json} | |||
| new Object[]{json} | |||
| }; | |||
| } | |||
| } | |||
| @@ -10,8 +10,8 @@ public class SessionTest { | |||
| @DataProvider | |||
| public Object[][] getSessionManager() { | |||
| return new Object[][] { | |||
| new Object[] { new StandardSessionManager() } | |||
| return new Object[][]{ | |||
| new Object[]{new StandardSessionManager()} | |||
| }; | |||
| } | |||
| @@ -8,7 +8,7 @@ import org.testng.annotations.Test; | |||
| public class WxMessageInMemoryDuplicateCheckerTest { | |||
| public void test() throws InterruptedException { | |||
| Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; | |||
| Long[] msgIds = new Long[]{1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l}; | |||
| WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l); | |||
| // 第一次检查 | |||
| @@ -45,16 +45,24 @@ public class WxCryptUtilTest { | |||
| Element root = document.getDocumentElement(); | |||
| String cipherText = root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| System.out.println(cipherText); | |||
| String msgSignature = root.getElementsByTagName("MsgSignature").item(0).getTextContent(); | |||
| System.out.println(msgSignature); | |||
| String timestamp = root.getElementsByTagName("TimeStamp").item(0).getTextContent(); | |||
| System.out.println(timestamp); | |||
| String nonce = root.getElementsByTagName("Nonce").item(0).getTextContent(); | |||
| System.out.println(nonce); | |||
| String messageText = String.format(xmlFormat, cipherText); | |||
| System.out.println(messageText); | |||
| // 第三方收到企业号平台发送的消息 | |||
| String plainMessage = pc.decrypt(cipherText); | |||
| System.out.println(plainMessage); | |||
| assertEquals(plainMessage, replyMsg); | |||
| } | |||
| @@ -69,7 +77,7 @@ public class WxCryptUtilTest { | |||
| } | |||
| public void testValidateSignatureError() throws ParserConfigurationException, SAXException, | |||
| IOException { | |||
| IOException { | |||
| try { | |||
| WxCryptUtil pc = new WxCryptUtil(token, encodingAesKey, appId); | |||
| String afterEncrpt = pc.encrypt(replyMsg); | |||
| @@ -1,5 +1,6 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/2002/xmlspec/dtd/2.10/xmlspec.dtd"> | |||
| <configuration> | |||
| <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |||
| <!-- encoders are assigned the type | |||
| ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | |||
| @@ -0,0 +1,12 @@ | |||
| description = 'WeiXin Java Tools - CP' | |||
| dependencies { | |||
| compile project(':weixin-java-common') | |||
| testCompile group: 'junit', name: 'junit', version:'4.11' | |||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.M0' | |||
| } | |||
| test.useTestNG() | |||
| @@ -6,7 +6,7 @@ | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.0.0</version> | |||
| <version>2.1.0</version> | |||
| </parent> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| @@ -7,8 +7,8 @@ import java.io.File; | |||
| /** | |||
| * 微信客户端配置存储 | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public interface WxCpConfigStorage { | |||
| @@ -36,12 +36,13 @@ public interface WxCpConfigStorage { | |||
| /** | |||
| * 应该是线程安全的 | |||
| * | |||
| * @param jsapiTicket | |||
| */ | |||
| void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
| String getCorpId(); | |||
| String getCorpSecret(); | |||
| String getAgentId(); | |||
| @@ -61,11 +62,12 @@ public interface WxCpConfigStorage { | |||
| String getHttp_proxy_username(); | |||
| String getHttp_proxy_password(); | |||
| File getTmpDirFile(); | |||
| /** | |||
| * http client builder | |||
| * | |||
| * @return ApacheHttpClientBuilder | |||
| */ | |||
| ApacheHttpClientBuilder getApacheHttpClientBuilder(); | |||
| @@ -7,8 +7,8 @@ import java.io.File; | |||
| /** | |||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| @@ -39,6 +39,10 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| return this.accessToken; | |||
| } | |||
| public void setAccessToken(String accessToken) { | |||
| this.accessToken = accessToken; | |||
| } | |||
| public boolean isAccessTokenExpired() { | |||
| return System.currentTimeMillis() > this.expiresTime; | |||
| } | |||
| @@ -50,7 +54,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
| updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } | |||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||
| this.accessToken = accessToken; | |||
| this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
| @@ -91,28 +95,32 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| return this.corpId; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| } | |||
| public String getCorpSecret() { | |||
| return this.corpSecret; | |||
| } | |||
| public String getToken() { | |||
| return this.token; | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| } | |||
| public long getExpiresTime() { | |||
| return this.expiresTime; | |||
| public String getToken() { | |||
| return this.token; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| } | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| public long getExpiresTime() { | |||
| return this.expiresTime; | |||
| } | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| public void setExpiresTime(long expiresTime) { | |||
| this.expiresTime = expiresTime; | |||
| } | |||
| public String getAesKey() { | |||
| @@ -123,14 +131,6 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| this.aesKey = aesKey; | |||
| } | |||
| public void setAccessToken(String accessToken) { | |||
| this.accessToken = accessToken; | |||
| } | |||
| public void setExpiresTime(long expiresTime) { | |||
| this.expiresTime = expiresTime; | |||
| } | |||
| public String getAgentId() { | |||
| return agentId; | |||
| } | |||
| @@ -183,21 +183,21 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| @Override | |||
| public String toString() { | |||
| return "WxCpInMemoryConfigStorage{" + | |||
| "corpId='" + corpId + '\'' + | |||
| ", corpSecret='" + corpSecret + '\'' + | |||
| ", token='" + token + '\'' + | |||
| ", accessToken='" + accessToken + '\'' + | |||
| ", aesKey='" + aesKey + '\'' + | |||
| ", agentId='" + agentId + '\'' + | |||
| ", expiresTime=" + expiresTime + | |||
| ", http_proxy_host='" + http_proxy_host + '\'' + | |||
| ", http_proxy_port=" + http_proxy_port + | |||
| ", http_proxy_username='" + http_proxy_username + '\'' + | |||
| ", http_proxy_password='" + http_proxy_password + '\'' + | |||
| ", jsapiTicket='" + jsapiTicket + '\'' + | |||
| ", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
| ", tmpDirFile='" + tmpDirFile + '\'' + | |||
| '}'; | |||
| "corpId='" + corpId + '\'' + | |||
| ", corpSecret='" + corpSecret + '\'' + | |||
| ", token='" + token + '\'' + | |||
| ", accessToken='" + accessToken + '\'' + | |||
| ", aesKey='" + aesKey + '\'' + | |||
| ", agentId='" + agentId + '\'' + | |||
| ", expiresTime=" + expiresTime + | |||
| ", http_proxy_host='" + http_proxy_host + '\'' + | |||
| ", http_proxy_port=" + http_proxy_port + | |||
| ", http_proxy_username='" + http_proxy_username + '\'' + | |||
| ", http_proxy_password='" + http_proxy_password + '\'' + | |||
| ", jsapiTicket='" + jsapiTicket + '\'' + | |||
| ", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
| ", tmpDirFile='" + tmpDirFile + '\'' + | |||
| '}'; | |||
| } | |||
| public File getTmpDirFile() { | |||
| @@ -0,0 +1,267 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import java.io.File; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
| import redis.clients.jedis.Jedis; | |||
| import redis.clients.jedis.JedisPool; | |||
| /** | |||
| * Jedis client implementor for wechat config storage | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
| /* Redis keys here */ | |||
| private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN"; | |||
| private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; | |||
| private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; | |||
| private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; | |||
| private volatile String corpId; | |||
| private volatile String corpSecret; | |||
| private volatile String token; | |||
| private volatile String aesKey; | |||
| private volatile String agentId; | |||
| private volatile String oauth2redirectUri; | |||
| private volatile String http_proxy_host; | |||
| private volatile int http_proxy_port; | |||
| private volatile String http_proxy_username; | |||
| private volatile String http_proxy_password; | |||
| private volatile File tmpDirFile; | |||
| private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
| /* Redis clients pool */ | |||
| private final JedisPool jedisPool; | |||
| public WxCpJedisConfigStorage(String host, int port) { | |||
| this.jedisPool = new JedisPool(host, port); | |||
| } | |||
| /** | |||
| * | |||
| * This method will be destroy jedis pool | |||
| */ | |||
| public void destroy() { | |||
| this.jedisPool.destroy(); | |||
| } | |||
| @Override | |||
| public String getAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(ACCESS_TOKEN_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isAccessTokenExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
| this.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_KEY, accessToken); | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L) + ""); | |||
| } | |||
| } | |||
| @Override | |||
| public String getJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(JS_API_TICKET_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isJsapiTicketExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_KEY, jsapiTicket); | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | |||
| } | |||
| } | |||
| @Override | |||
| public String getCorpId() { | |||
| return this.corpId; | |||
| } | |||
| @Override | |||
| public String getCorpSecret() { | |||
| return this.corpSecret; | |||
| } | |||
| @Override | |||
| public String getAgentId() { | |||
| return this.agentId; | |||
| } | |||
| @Override | |||
| public String getToken() { | |||
| return this.token; | |||
| } | |||
| @Override | |||
| public String getAesKey() { | |||
| return this.aesKey; | |||
| } | |||
| @Override | |||
| public long getExpiresTime() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return expiresTime; | |||
| } | |||
| return 0L; | |||
| } | |||
| } | |||
| @Override | |||
| public String getOauth2redirectUri() { | |||
| return this.oauth2redirectUri; | |||
| } | |||
| @Override | |||
| public String getHttp_proxy_host() { | |||
| return this.http_proxy_host; | |||
| } | |||
| @Override | |||
| public int getHttp_proxy_port() { | |||
| return this.http_proxy_port; | |||
| } | |||
| @Override | |||
| public String getHttp_proxy_username() { | |||
| return this.http_proxy_username; | |||
| } | |||
| @Override | |||
| public String getHttp_proxy_password() { | |||
| return this.http_proxy_password; | |||
| } | |||
| @Override | |||
| public File getTmpDirFile() { | |||
| return this.tmpDirFile; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||
| return this.apacheHttpClientBuilder; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| } | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| } | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| } | |||
| public void setAesKey(String aesKey) { | |||
| this.aesKey = aesKey; | |||
| } | |||
| public void setAgentId(String agentId) { | |||
| this.agentId = agentId; | |||
| } | |||
| // ============================ Setters below | |||
| public void setOauth2redirectUri(String oauth2redirectUri) { | |||
| this.oauth2redirectUri = oauth2redirectUri; | |||
| } | |||
| public void setHttp_proxy_host(String http_proxy_host) { | |||
| this.http_proxy_host = http_proxy_host; | |||
| } | |||
| public void setHttp_proxy_port(int http_proxy_port) { | |||
| this.http_proxy_port = http_proxy_port; | |||
| } | |||
| public void setHttp_proxy_username(String http_proxy_username) { | |||
| this.http_proxy_username = http_proxy_username; | |||
| } | |||
| public void setHttp_proxy_password(String http_proxy_password) { | |||
| this.http_proxy_password = http_proxy_password; | |||
| } | |||
| public void setTmpDirFile(File tmpDirFile) { | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { | |||
| this.apacheHttpClientBuilder = apacheHttpClientBuilder; | |||
| } | |||
| } | |||
| @@ -1,7 +1,6 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSession; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| @@ -1,13 +1,13 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
| import me.chanjar.weixin.common.session.InternalSession; | |||
| import me.chanjar.weixin.common.session.InternalSessionManager; | |||
| import me.chanjar.weixin.common.session.StandardSessionManager; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| import org.slf4j.Logger; | |||
| @@ -45,15 +45,13 @@ import java.util.concurrent.Future; | |||
| * router.route(message); | |||
| * | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxCpMessageRouter { | |||
| protected final Logger log = LoggerFactory.getLogger(WxCpMessageRouter.class); | |||
| private static final int DEFAULT_THREAD_POOL_SIZE = 100; | |||
| protected final Logger log = LoggerFactory.getLogger(WxCpMessageRouter.class); | |||
| private final List<WxCpMessageRouterRule> rules = new ArrayList<WxCpMessageRouterRule>(); | |||
| private final WxCpService wxCpService; | |||
| @@ -79,6 +77,7 @@ public class WxCpMessageRouter { | |||
| * 设置自定义的 {@link ExecutorService} | |||
| * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100) | |||
| * </pre> | |||
| * | |||
| * @param executorService | |||
| */ | |||
| public void setExecutorService(ExecutorService executorService) { | |||
| @@ -90,6 +89,7 @@ public class WxCpMessageRouter { | |||
| * 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
| * </pre> | |||
| * | |||
| * @param messageDuplicateChecker | |||
| */ | |||
| public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { | |||
| @@ -101,6 +101,7 @@ public class WxCpMessageRouter { | |||
| * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager} | |||
| * </pre> | |||
| * | |||
| * @param sessionManager | |||
| */ | |||
| public void setSessionManager(WxSessionManager sessionManager) { | |||
| @@ -112,6 +113,7 @@ public class WxCpMessageRouter { | |||
| * 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
| * </pre> | |||
| * | |||
| * @param exceptionHandler | |||
| */ | |||
| public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) { | |||
| @@ -131,6 +133,7 @@ public class WxCpMessageRouter { | |||
| /** | |||
| * 处理微信消息 | |||
| * | |||
| * @param wxMessage | |||
| */ | |||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | |||
| @@ -144,7 +147,7 @@ public class WxCpMessageRouter { | |||
| for (final WxCpMessageRouterRule rule : rules) { | |||
| if (rule.test(wxMessage)) { | |||
| matchRules.add(rule); | |||
| if(!rule.isReEnter()) { | |||
| if (!rule.isReEnter()) { | |||
| break; | |||
| } | |||
| } | |||
| @@ -158,13 +161,13 @@ public class WxCpMessageRouter { | |||
| final List<Future> futures = new ArrayList<Future>(); | |||
| for (final WxCpMessageRouterRule rule : matchRules) { | |||
| // 返回最后一个非异步的rule的执行结果 | |||
| if(rule.isAsync()) { | |||
| if (rule.isAsync()) { | |||
| futures.add( | |||
| executorService.submit(new Runnable() { | |||
| public void run() { | |||
| rule.service(wxMessage, wxCpService, sessionManager, exceptionHandler); | |||
| } | |||
| }) | |||
| executorService.submit(new Runnable() { | |||
| public void run() { | |||
| rule.service(wxMessage, wxCpService, sessionManager, exceptionHandler); | |||
| } | |||
| }) | |||
| ); | |||
| } else { | |||
| res = rule.service(wxMessage, wxCpService, sessionManager, exceptionHandler); | |||
| @@ -201,10 +204,10 @@ public class WxCpMessageRouter { | |||
| String messageId = ""; | |||
| if (wxMessage.getMsgId() == null) { | |||
| messageId = String.valueOf(wxMessage.getCreateTime()) | |||
| + "-" +String.valueOf(wxMessage.getAgentId() == null ? "" : wxMessage.getAgentId()) | |||
| + "-" + wxMessage.getFromUserName() | |||
| + "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| + "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| + "-" + String.valueOf(wxMessage.getAgentId() == null ? "" : wxMessage.getAgentId()) | |||
| + "-" + wxMessage.getFromUserName() | |||
| + "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| + "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| ; | |||
| } else { | |||
| messageId = String.valueOf(wxMessage.getMsgId()); | |||
| @@ -216,11 +219,12 @@ public class WxCpMessageRouter { | |||
| /** | |||
| * 对session的访问结束 | |||
| * | |||
| * @param wxMessage | |||
| */ | |||
| protected void sessionEndAccess(WxCpXmlMessage wxMessage) { | |||
| InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); | |||
| InternalSession session = ((InternalSessionManager) sessionManager).findSession(wxMessage.getFromUserName()); | |||
| if (session != null) { | |||
| session.endAccess(); | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| @@ -186,7 +186,6 @@ public class WxCpMessageRouterRule { | |||
| /** | |||
| * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则 | |||
| * | |||
| */ | |||
| public WxCpMessageRouter end() { | |||
| this.routerBuilder.getRules().add(this); | |||
| @@ -195,7 +194,6 @@ public class WxCpMessageRouterRule { | |||
| /** | |||
| * 规则结束,但是消息还会进入其他规则 | |||
| * | |||
| */ | |||
| public WxCpMessageRouter next() { | |||
| this.reEnter = true; | |||
| @@ -204,24 +202,24 @@ public class WxCpMessageRouterRule { | |||
| protected boolean test(WxCpXmlMessage wxMessage) { | |||
| return | |||
| (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName())) | |||
| && | |||
| (this.agentId == null || this.agentId.equals(wxMessage.getAgentId())) | |||
| && | |||
| (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) | |||
| && | |||
| (this.event == null || this.event.equals(wxMessage.getEvent())) | |||
| && | |||
| (this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) | |||
| && | |||
| (this.content == null || this.content | |||
| .equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) | |||
| && | |||
| (this.rContent == null || Pattern | |||
| .matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim())) | |||
| && | |||
| (this.matcher == null || this.matcher.match(wxMessage)) | |||
| ; | |||
| (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName())) | |||
| && | |||
| (this.agentId == null || this.agentId.equals(wxMessage.getAgentId())) | |||
| && | |||
| (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) | |||
| && | |||
| (this.event == null || this.event.equals(wxMessage.getEvent())) | |||
| && | |||
| (this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) | |||
| && | |||
| (this.content == null || this.content | |||
| .equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) | |||
| && | |||
| (this.rContent == null || Pattern | |||
| .matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim())) | |||
| && | |||
| (this.matcher == null || this.matcher.match(wxMessage)) | |||
| ; | |||
| } | |||
| /** | |||
| @@ -231,9 +229,9 @@ public class WxCpMessageRouterRule { | |||
| * @return true 代表继续执行别的router,false 代表停止执行别的router | |||
| */ | |||
| protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage, | |||
| WxCpService wxCpService, | |||
| WxSessionManager sessionManager, | |||
| WxErrorExceptionHandler exceptionHandler) { | |||
| WxCpService wxCpService, | |||
| WxSessionManager sessionManager, | |||
| WxErrorExceptionHandler exceptionHandler) { | |||
| try { | |||
| @@ -1,7 +1,7 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
| import me.chanjar.weixin.common.bean.WxMenu; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSession; | |||
| @@ -47,8 +47,9 @@ public interface WxCpService { | |||
| /** | |||
| * 获取access_token, 不强制刷新access_token | |||
| * @see #getAccessToken(boolean) | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #getAccessToken(boolean) | |||
| */ | |||
| String getAccessToken() throws WxErrorException; | |||
| @@ -60,6 +61,7 @@ public interface WxCpService { | |||
| * 程序员在非必要情况下尽量不要主动调用此方法 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | |||
| * </pre> | |||
| * | |||
| * @param forceRefresh 强制刷新 | |||
| * @throws me.chanjar.weixin.common.exception.WxErrorException | |||
| */ | |||
| @@ -67,8 +69,9 @@ public interface WxCpService { | |||
| /** | |||
| * 获得jsapi_ticket,不强制刷新jsapi_ticket | |||
| * @see #getJsapiTicket(boolean) | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #getJsapiTicket(boolean) | |||
| */ | |||
| String getJsapiTicket() throws WxErrorException; | |||
| @@ -79,6 +82,7 @@ public interface WxCpService { | |||
| * | |||
| * 详情请见:http://qydev.weixin.qq.com/wiki/index.php?title=微信JS接口#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||
| * </pre> | |||
| * | |||
| * @param forceRefresh 强制刷新 | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -90,7 +94,8 @@ public interface WxCpService { | |||
| * | |||
| * 详情请见:http://qydev.weixin.qq.com/wiki/index.php?title=微信JS接口#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||
| * </pre> | |||
| * @param url url | |||
| * | |||
| * @param url url | |||
| */ | |||
| WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
| @@ -111,7 +116,7 @@ public interface WxCpService { | |||
| * @throws WxErrorException | |||
| */ | |||
| WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||
| throws WxErrorException, IOException; | |||
| throws WxErrorException, IOException; | |||
| /** | |||
| * @param mediaType | |||
| @@ -128,9 +133,9 @@ public interface WxCpService { | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||
| * </pre> | |||
| * | |||
| * @param media_id | |||
| * @return 保存到本地的临时文件 | |||
| * @throws WxErrorException | |||
| * @param media_id | |||
| */ | |||
| File mediaDownload(String media_id) throws WxErrorException; | |||
| @@ -152,10 +157,10 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||
| * </pre> | |||
| * @see #menuCreate(String, me.chanjar.weixin.common.bean.WxMenu) | |||
| * | |||
| * @param menu | |||
| * @throws WxErrorException | |||
| * @see #menuCreate(String, me.chanjar.weixin.common.bean.menu.WxMenu) | |||
| */ | |||
| void menuCreate(WxMenu menu) throws WxErrorException; | |||
| @@ -166,11 +171,11 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||
| * </pre> | |||
| * @see #menuCreate(me.chanjar.weixin.common.bean.WxMenu) | |||
| * | |||
| * @param agentId 企业号应用的id | |||
| * @param menu | |||
| * @throws WxErrorException | |||
| * @see #menuCreate(me.chanjar.weixin.common.bean.menu.WxMenu) | |||
| */ | |||
| void menuCreate(String agentId, WxMenu menu) throws WxErrorException; | |||
| @@ -181,9 +186,9 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||
| * </pre> | |||
| * @see #menuDelete(String) | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #menuDelete(String) | |||
| */ | |||
| void menuDelete() throws WxErrorException; | |||
| @@ -194,10 +199,10 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||
| * </pre> | |||
| * @see #menuDelete() | |||
| * | |||
| * @param agentId 企业号应用的id | |||
| * @throws WxErrorException | |||
| * @see #menuDelete() | |||
| */ | |||
| void menuDelete(String agentId) throws WxErrorException; | |||
| @@ -208,9 +213,9 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||
| * </pre> | |||
| * @see #menuGet(String) | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #menuGet(String) | |||
| */ | |||
| WxMenu menuGet() throws WxErrorException; | |||
| @@ -221,10 +226,10 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||
| * </pre> | |||
| * @see #menuGet() | |||
| * | |||
| * @param agentId 企业号应用的id | |||
| * @throws WxErrorException | |||
| * @see #menuGet() | |||
| */ | |||
| WxMenu menuGet(String agentId) throws WxErrorException; | |||
| @@ -279,9 +284,10 @@ public interface WxCpService { | |||
| * | |||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29 | |||
| * </pre> | |||
| * @param departId 必填。部门id | |||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||
| * | |||
| * @param departId 必填。部门id | |||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||
| * @throws WxErrorException | |||
| */ | |||
| List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
| @@ -330,7 +336,8 @@ public interface WxCpService { | |||
| * | |||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98 | |||
| * </pre> | |||
| * @param userids 员工UserID列表。对应管理端的帐号 | |||
| * | |||
| * @param userids 员工UserID列表。对应管理端的帐号 | |||
| * @throws WxErrorException | |||
| */ | |||
| void userDelete(String[] userids) throws WxErrorException; | |||
| @@ -367,7 +374,6 @@ public interface WxCpService { | |||
| /** | |||
| * 获得标签列表 | |||
| * | |||
| */ | |||
| List<WxCpTag> tagGet() throws WxErrorException; | |||
| @@ -386,11 +392,22 @@ public interface WxCpService { | |||
| */ | |||
| void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 构造oauth2授权的url连接 | |||
| * </pre> | |||
| * | |||
| * @param state | |||
| * @return url | |||
| */ | |||
| String oauth2buildAuthorizationUrl(String state); | |||
| /** | |||
| * <pre> | |||
| * 构造oauth2授权的url连接 | |||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code | |||
| * </pre> | |||
| * | |||
| * @param redirectUri | |||
| * @param state | |||
| * @return url | |||
| @@ -405,10 +422,10 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||
| * </pre> | |||
| * @see #oauth2getUserInfo(String, String) | |||
| * | |||
| * @param code | |||
| * @return [userid, deviceid] | |||
| * @see #oauth2getUserInfo(String, String) | |||
| */ | |||
| String[] oauth2getUserInfo(String code) throws WxErrorException; | |||
| @@ -420,11 +437,11 @@ public interface WxCpService { | |||
| * | |||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||
| * </pre> | |||
| * @see #oauth2getUserInfo(String) | |||
| * | |||
| * @param agentId 企业号应用的id | |||
| * @param code | |||
| * @return [userid, deviceid] | |||
| * @see #oauth2getUserInfo(String) | |||
| */ | |||
| String[] oauth2getUserInfo(String agentId, String code) throws WxErrorException; | |||
| @@ -442,8 +459,9 @@ public interface WxCpService { | |||
| * 邀请成员关注 | |||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E9.82.80.E8.AF.B7.E6.88.90.E5.91.98.E5.85.B3.E6.B3.A8 | |||
| * </pre> | |||
| * @param userId 用户的userid | |||
| * @param inviteTips 推送到微信上的提示语(只有认证号可以使用)。当使用微信推送时,该字段默认为“请关注XXX企业号”,邮件邀请时,该字段无效。 | |||
| * | |||
| * @param userId 用户的userid | |||
| * @param inviteTips 推送到微信上的提示语(只有认证号可以使用)。当使用微信推送时,该字段默认为“请关注XXX企业号”,邮件邀请时,该字段无效。 | |||
| * @return 1:微信邀请 2.邮件邀请 | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -454,6 +472,7 @@ public interface WxCpService { | |||
| * 获取微信服务器的ip段 | |||
| * http://qydev.weixin.qq.com/wiki/index.php?title=回调模式#.E8.8E.B7.E5.8F.96.E5.BE.AE.E4.BF.A1.E6.9C.8D.E5.8A.A1.E5.99.A8.E7.9A.84ip.E6.AE.B5 | |||
| * </pre> | |||
| * | |||
| * @return { "ip_list": ["101.226.103.*", "101.226.62.*"] } | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -461,6 +480,7 @@ public interface WxCpService { | |||
| /** | |||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
| * | |||
| * @param url | |||
| * @param queryParam | |||
| * @throws WxErrorException | |||
| @@ -469,6 +489,7 @@ public interface WxCpService { | |||
| /** | |||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||
| * | |||
| * @param url | |||
| * @param postData | |||
| * @throws WxErrorException | |||
| @@ -481,6 +502,7 @@ public interface WxCpService { | |||
| * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | |||
| * 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法 | |||
| * </pre> | |||
| * | |||
| * @param executor | |||
| * @param uri | |||
| * @param data | |||
| @@ -502,6 +524,7 @@ public interface WxCpService { | |||
| * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||
| * 默认:1000ms | |||
| * </pre> | |||
| * | |||
| * @param retrySleepMillis | |||
| */ | |||
| void setRetrySleepMillis(int retrySleepMillis); | |||
| @@ -511,19 +534,22 @@ public interface WxCpService { | |||
| * 设置当微信系统响应系统繁忙时,最大重试次数 | |||
| * 默认:5次 | |||
| * </pre> | |||
| * | |||
| * @param maxRetryTimes | |||
| */ | |||
| void setMaxRetryTimes(int maxRetryTimes); | |||
| /** | |||
| * 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 | |||
| * | |||
| * @param id id可以为任意字符串,建议使用FromUserName作为id | |||
| */ | |||
| WxSession getSession(String id); | |||
| /** | |||
| * 获取某个sessionId对应的session,如果sessionId没有对应的session,若create为true则新建一个,否则返回null。 | |||
| * @param id id可以为任意字符串,建议使用FromUserName作为id | |||
| * | |||
| * @param id id可以为任意字符串,建议使用FromUserName作为id | |||
| * @param create | |||
| */ | |||
| WxSession getSession(String id, boolean create); | |||
| @@ -533,12 +559,14 @@ public interface WxCpService { | |||
| * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | |||
| * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.StandardSessionManager} | |||
| * </pre> | |||
| * | |||
| * @param sessionManager | |||
| */ | |||
| void setSessionManager(WxSessionManager sessionManager); | |||
| /** | |||
| * 上传部门列表覆盖企业号上的部门信息 | |||
| * | |||
| * @param mediaId | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -546,6 +574,7 @@ public interface WxCpService { | |||
| /** | |||
| * 上传用户列表覆盖企业号上的用户信息 | |||
| * | |||
| * @param mediaId | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -553,6 +582,7 @@ public interface WxCpService { | |||
| /** | |||
| * 获取异步任务结果 | |||
| * | |||
| * @param joinId | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -9,7 +9,7 @@ import com.google.gson.reflect.TypeToken; | |||
| import com.google.gson.stream.JsonReader; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
| import me.chanjar.weixin.common.bean.WxMenu; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| @@ -64,17 +64,13 @@ public class WxCpServiceImpl implements WxCpService { | |||
| protected CloseableHttpClient httpClient; | |||
| protected HttpHost httpProxy; | |||
| private int retrySleepMillis = 1000; | |||
| private int maxRetryTimes = 5; | |||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | |||
| /** | |||
| * 临时文件目录 | |||
| */ | |||
| protected File tmpDirFile; | |||
| private int retrySleepMillis = 1000; | |||
| private int maxRetryTimes = 5; | |||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
| try { | |||
| @@ -101,8 +97,8 @@ public class WxCpServiceImpl implements WxCpService { | |||
| synchronized (globalAccessTokenRefreshLock) { | |||
| if (wxCpConfigStorage.isAccessTokenExpired()) { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||
| + "&corpid=" + wxCpConfigStorage.getCorpId() | |||
| + "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); | |||
| + "&corpid=" + wxCpConfigStorage.getCorpId() | |||
| + "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); | |||
| try { | |||
| HttpGet httpGet = new HttpGet(url); | |||
| if (httpProxy != null) { | |||
| @@ -113,7 +109,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String resultContent = null; | |||
| try (CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||
| }finally { | |||
| } finally { | |||
| httpGet.releaseConnection(); | |||
| } | |||
| WxError error = WxError.fromJson(resultContent); | |||
| @@ -163,16 +159,20 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String jsapiTicket = getJsapiTicket(false); | |||
| try { | |||
| String signature = SHA1.genWithAmple( | |||
| "jsapi_ticket=" + jsapiTicket, | |||
| "noncestr=" + noncestr, | |||
| "timestamp=" + timestamp, | |||
| "url=" + url | |||
| "jsapi_ticket=" + jsapiTicket, | |||
| "noncestr=" + noncestr, | |||
| "timestamp=" + timestamp, | |||
| "url=" + url | |||
| ); | |||
| WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||
| jsapiSignature.setTimestamp(timestamp); | |||
| jsapiSignature.setNoncestr(noncestr); | |||
| jsapiSignature.setUrl(url); | |||
| jsapiSignature.setSignature(signature); | |||
| // Fixed bug | |||
| jsapiSignature.setAppid(this.wxCpConfigStorage.getCorpId()); | |||
| return jsapiSignature; | |||
| } catch (NoSuchAlgorithmException e) { | |||
| throw new RuntimeException(e); | |||
| @@ -227,7 +227,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||
| throws WxErrorException, IOException { | |||
| throws WxErrorException, IOException { | |||
| return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||
| } | |||
| @@ -245,9 +245,9 @@ public class WxCpServiceImpl implements WxCpService { | |||
| public Integer departCreate(WxCpDepart depart) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | |||
| String responseContent = execute( | |||
| new SimplePostRequestExecutor(), | |||
| url, | |||
| depart.toJson()); | |||
| new SimplePostRequestExecutor(), | |||
| url, | |||
| depart.toJson()); | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||
| } | |||
| @@ -271,11 +271,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
| */ | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("department"), | |||
| new TypeToken<List<WxCpDepart>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("department"), | |||
| new TypeToken<List<WxCpDepart>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -331,10 +331,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String responseContent = get(url, params); | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -353,10 +354,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String responseContent = get(url, params); | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -390,11 +392,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String responseContent = get(url, null); | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||
| new TypeToken<List<WxCpTag>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||
| new TypeToken<List<WxCpTag>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -403,10 +405,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String responseContent = get(url, null); | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -445,8 +448,16 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| @Override | |||
| public String oauth2buildAuthorizationUrl(String state) { | |||
| return this.oauth2buildAuthorizationUrl( | |||
| this.wxCpConfigStorage.getOauth2redirectUri(), | |||
| state | |||
| ); | |||
| } | |||
| @Override | |||
| public String oauth2buildAuthorizationUrl(String redirectUri, String state) { | |||
| String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" ; | |||
| String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; | |||
| url += "appid=" + wxCpConfigStorage.getCorpId(); | |||
| url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); | |||
| url += "&response_type=code"; | |||
| @@ -466,12 +477,12 @@ public class WxCpServiceImpl implements WxCpService { | |||
| @Override | |||
| public String[] oauth2getUserInfo(String agentId, String code) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" | |||
| + "code=" + code | |||
| + "&agendid=" + agentId; | |||
| + "code=" + code | |||
| + "&agendid=" + agentId; | |||
| String responseText = get(url, null); | |||
| JsonElement je = Streams.parse(new JsonReader(new StringReader(responseText))); | |||
| JsonObject jo = je.getAsJsonObject(); | |||
| return new String[] {GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId")}; | |||
| return new String[]{GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId")}; | |||
| } | |||
| @Override | |||
| @@ -494,7 +505,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
| JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | |||
| String[] ips = new String[jsonArray.size()]; | |||
| for(int i = 0; i < jsonArray.size(); i++) { | |||
| for (int i = 0; i < jsonArray.size(); i++) { | |||
| ips[i] = jsonArray.get(i).getAsString(); | |||
| } | |||
| return ips; | |||
| @@ -533,7 +544,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| throw e; | |||
| } | |||
| } | |||
| } while(++retryTimes < maxRetryTimes); | |||
| } while (++retryTimes < maxRetryTimes); | |||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
| } | |||
| @@ -571,6 +582,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| protected CloseableHttpClient getHttpclient() { | |||
| return httpClient; | |||
| } | |||
| @@ -579,12 +591,12 @@ public class WxCpServiceImpl implements WxCpService { | |||
| this.wxCpConfigStorage = wxConfigProvider; | |||
| ApacheHttpClientBuilder apacheHttpClientBuilder = wxCpConfigStorage.getApacheHttpClientBuilder(); | |||
| if (null == apacheHttpClientBuilder) { | |||
| apacheHttpClientBuilder = DefaultApacheHttpHttpClientBuilder.get(); | |||
| apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||
| } | |||
| apacheHttpClientBuilder.httpProxyHost(wxCpConfigStorage.getHttp_proxy_host()) | |||
| .httpProxyPort(wxCpConfigStorage.getHttp_proxy_port()) | |||
| .httpProxyUsername(wxCpConfigStorage.getHttp_proxy_username()) | |||
| .httpProxyPassword(wxCpConfigStorage.getHttp_proxy_password()); | |||
| .httpProxyPort(wxCpConfigStorage.getHttp_proxy_port()) | |||
| .httpProxyUsername(wxCpConfigStorage.getHttp_proxy_username()) | |||
| .httpProxyPassword(wxCpConfigStorage.getHttp_proxy_password()); | |||
| httpClient = apacheHttpClientBuilder.build(); | |||
| } | |||
| @@ -621,27 +633,27 @@ public class WxCpServiceImpl implements WxCpService { | |||
| public void setSessionManager(WxSessionManager sessionManager) { | |||
| this.sessionManager = sessionManager; | |||
| } | |||
| @Override | |||
| public String replaceParty(String mediaId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("media_id", mediaId); | |||
| return post(url, jsonObject.toString()); | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("media_id", mediaId); | |||
| return post(url, jsonObject.toString()); | |||
| } | |||
| @Override | |||
| public String replaceUser(String mediaId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("media_id", mediaId); | |||
| return post(url, jsonObject.toString()); | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("media_id", mediaId); | |||
| return post(url, jsonObject.toString()); | |||
| } | |||
| @Override | |||
| public String getTaskResult(String joinId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid="+joinId; | |||
| return get(url, null); | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId; | |||
| return get(url, null); | |||
| } | |||
| public File getTmpDirFile() { | |||
| @@ -16,6 +16,10 @@ public class WxCpDepart implements Serializable { | |||
| private Integer parentId; | |||
| private Integer order; | |||
| public static WxCpDepart fromJson(String json) { | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpDepart.class); | |||
| } | |||
| public Integer getId() { | |||
| return id; | |||
| } | |||
| @@ -48,10 +52,6 @@ public class WxCpDepart implements Serializable { | |||
| this.order = order; | |||
| } | |||
| public static WxCpDepart fromJson(String json) { | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpDepart.class); | |||
| } | |||
| public String toJson() { | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @@ -59,10 +59,10 @@ public class WxCpDepart implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxCpDepart{" + | |||
| "id=" + id + | |||
| ", name='" + name + '\'' + | |||
| ", parentId=" + parentId + | |||
| ", order=" + order + | |||
| '}'; | |||
| "id=" + id + | |||
| ", name='" + name + '\'' + | |||
| ", parentId=" + parentId + | |||
| ", order=" + order + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -9,8 +9,8 @@ import java.util.List; | |||
| /** | |||
| * 消息 | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxCpMessage implements Serializable { | |||
| @@ -29,9 +29,52 @@ public class WxCpMessage implements Serializable { | |||
| private String safe; | |||
| private List<WxArticle> articles = new ArrayList<WxArticle>(); | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得文件消息builder | |||
| */ | |||
| public static FileBuilder FILE() { | |||
| return new FileBuilder(); | |||
| } | |||
| public String getToUser() { | |||
| return toUser; | |||
| } | |||
| public void setToUser(String toUser) { | |||
| this.toUser = toUser; | |||
| } | |||
| @@ -64,14 +107,6 @@ public class WxCpMessage implements Serializable { | |||
| return msgType; | |||
| } | |||
| public String getSafe() { | |||
| return safe; | |||
| } | |||
| public void setSafe(String safe) { | |||
| this.safe = safe; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 请使用 | |||
| @@ -82,56 +117,81 @@ public class WxCpMessage implements Serializable { | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_VIDEO} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_NEWS} | |||
| * </pre> | |||
| * | |||
| * @param msgType | |||
| */ | |||
| public void setMsgType(String msgType) { | |||
| this.msgType = msgType; | |||
| } | |||
| public String getSafe() { | |||
| return safe; | |||
| } | |||
| public void setSafe(String safe) { | |||
| this.safe = safe; | |||
| } | |||
| public String getContent() { | |||
| return content; | |||
| } | |||
| public void setContent(String content) { | |||
| this.content = content; | |||
| } | |||
| public String getMediaId() { | |||
| return mediaId; | |||
| } | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| public String getThumbMediaId() { | |||
| return thumbMediaId; | |||
| } | |||
| public void setThumbMediaId(String thumbMediaId) { | |||
| this.thumbMediaId = thumbMediaId; | |||
| } | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getDescription() { | |||
| return description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getMusicUrl() { | |||
| return musicUrl; | |||
| } | |||
| public void setMusicUrl(String musicUrl) { | |||
| this.musicUrl = musicUrl; | |||
| } | |||
| public String getHqMusicUrl() { | |||
| return hqMusicUrl; | |||
| } | |||
| public void setHqMusicUrl(String hqMusicUrl) { | |||
| this.hqMusicUrl = hqMusicUrl; | |||
| } | |||
| public List<WxArticle> getArticles() { | |||
| return articles; | |||
| } | |||
| public void setArticles(List<WxArticle> articles) { | |||
| this.articles = articles; | |||
| } | |||
| @@ -150,70 +210,35 @@ public class WxCpMessage implements Serializable { | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getDescription() { | |||
| return description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getUrl() { | |||
| return url; | |||
| } | |||
| public void setUrl(String url) { | |||
| this.url = url; | |||
| } | |||
| public String getPicUrl() { | |||
| return picUrl; | |||
| } | |||
| public void setPicUrl(String picUrl) { | |||
| this.picUrl = picUrl; | |||
| } | |||
| } | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得文件消息builder | |||
| */ | |||
| public static FileBuilder FILE() { | |||
| return new FileBuilder(); | |||
| } | |||
| } | |||
| @@ -23,6 +23,10 @@ public class WxCpTag implements Serializable { | |||
| this.name = name; | |||
| } | |||
| public static WxCpTag fromJson(String json) { | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpTag.class); | |||
| } | |||
| public String getName() { | |||
| return name; | |||
| } | |||
| @@ -39,10 +43,6 @@ public class WxCpTag implements Serializable { | |||
| this.id = id; | |||
| } | |||
| public static WxCpTag fromJson(String json) { | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpTag.class); | |||
| } | |||
| public String toJson() { | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @@ -13,6 +13,7 @@ import java.util.List; | |||
| */ | |||
| public class WxCpUser implements Serializable { | |||
| private final List<Attr> extAttrs = new ArrayList<Attr>(); | |||
| private String userId; | |||
| private String name; | |||
| private Integer[] departIds; | |||
| @@ -25,7 +26,10 @@ public class WxCpUser implements Serializable { | |||
| private String avatar; | |||
| private Integer status; | |||
| private Integer enable; | |||
| private final List<Attr> extAttrs = new ArrayList<Attr>(); | |||
| public static WxCpUser fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); | |||
| } | |||
| public String getUserId() { | |||
| return userId; | |||
| @@ -116,13 +120,13 @@ public class WxCpUser implements Serializable { | |||
| } | |||
| public Integer getEnable() { | |||
| return enable; | |||
| return enable; | |||
| } | |||
| public void setEnable(Integer enable) { | |||
| this.enable = enable; | |||
| this.enable = enable; | |||
| } | |||
| public void addExtAttr(String name, String value) { | |||
| this.extAttrs.add(new Attr(name, value)); | |||
| } | |||
| @@ -135,10 +139,6 @@ public class WxCpUser implements Serializable { | |||
| return WxCpGsonBuilder.INSTANCE.create().toJson(this); | |||
| } | |||
| public static WxCpUser fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); | |||
| } | |||
| public static class Attr { | |||
| private String name; | |||
| @@ -36,41 +36,41 @@ public class WxCpXmlMessage implements Serializable { | |||
| private Integer agentId; | |||
| @XStreamAlias("ToUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String toUserName; | |||
| @XStreamAlias("FromUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String fromUserName; | |||
| @XStreamAlias("CreateTime") | |||
| private Long createTime; | |||
| @XStreamAlias("MsgType") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String msgType; | |||
| @XStreamAlias("Content") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String content; | |||
| @XStreamAlias("MsgId") | |||
| private Long msgId; | |||
| @XStreamAlias("PicUrl") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String picUrl; | |||
| @XStreamAlias("MediaId") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String mediaId; | |||
| @XStreamAlias("Format") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String format; | |||
| @XStreamAlias("ThumbMediaId") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String thumbMediaId; | |||
| @XStreamAlias("Location_X") | |||
| @@ -83,31 +83,31 @@ public class WxCpXmlMessage implements Serializable { | |||
| private Double scale; | |||
| @XStreamAlias("Label") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String label; | |||
| @XStreamAlias("Title") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String title; | |||
| @XStreamAlias("Description") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String description; | |||
| @XStreamAlias("Url") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String url; | |||
| @XStreamAlias("Event") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String event; | |||
| @XStreamAlias("EventKey") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String eventKey; | |||
| @XStreamAlias("Ticket") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String ticket; | |||
| @XStreamAlias("Latitude") | |||
| @@ -120,7 +120,7 @@ public class WxCpXmlMessage implements Serializable { | |||
| private Double precision; | |||
| @XStreamAlias("Recognition") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String recognition; | |||
| /////////////////////////////////////// | |||
| @@ -130,7 +130,7 @@ public class WxCpXmlMessage implements Serializable { | |||
| * 群发的结果 | |||
| */ | |||
| @XStreamAlias("Status") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String status; | |||
| /** | |||
| * group_id下粉丝数;或者openid_list中的粉丝数 | |||
| @@ -162,6 +162,43 @@ public class WxCpXmlMessage implements Serializable { | |||
| @XStreamAlias("SendLocationInfo") | |||
| private SendLocationInfo sendLocationInfo = new SendLocationInfo(); | |||
| protected static WxCpXmlMessage fromXml(String xml) { | |||
| return XStreamTransformer.fromXml(WxCpXmlMessage.class, xml); | |||
| } | |||
| protected static WxCpXmlMessage fromXml(InputStream is) { | |||
| return XStreamTransformer.fromXml(WxCpXmlMessage.class, is); | |||
| } | |||
| /** | |||
| * 从加密字符串转换 | |||
| * | |||
| * @param encryptedXml | |||
| * @param wxCpConfigStorage | |||
| * @param timestamp | |||
| * @param nonce | |||
| * @param msgSignature | |||
| */ | |||
| public static WxCpXmlMessage fromEncryptedXml( | |||
| String encryptedXml, | |||
| WxCpConfigStorage wxCpConfigStorage, | |||
| String timestamp, String nonce, String msgSignature) { | |||
| WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); | |||
| String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); | |||
| return fromXml(plainText); | |||
| } | |||
| public static WxCpXmlMessage fromEncryptedXml( | |||
| InputStream is, | |||
| WxCpConfigStorage wxCpConfigStorage, | |||
| String timestamp, String nonce, String msgSignature) { | |||
| try { | |||
| return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| public Integer getAgentId() { | |||
| return agentId; | |||
| } | |||
| @@ -197,7 +234,6 @@ public class WxCpXmlMessage implements Serializable { | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_LINK} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_EVENT} | |||
| * </pre> | |||
| * | |||
| */ | |||
| public String getMsgType() { | |||
| return msgType; | |||
| @@ -387,43 +423,6 @@ public class WxCpXmlMessage implements Serializable { | |||
| this.fromUserName = fromUserName; | |||
| } | |||
| protected static WxCpXmlMessage fromXml(String xml) { | |||
| return XStreamTransformer.fromXml(WxCpXmlMessage.class, xml); | |||
| } | |||
| protected static WxCpXmlMessage fromXml(InputStream is) { | |||
| return XStreamTransformer.fromXml(WxCpXmlMessage.class, is); | |||
| } | |||
| /** | |||
| * 从加密字符串转换 | |||
| * | |||
| * @param encryptedXml | |||
| * @param wxCpConfigStorage | |||
| * @param timestamp | |||
| * @param nonce | |||
| * @param msgSignature | |||
| */ | |||
| public static WxCpXmlMessage fromEncryptedXml( | |||
| String encryptedXml, | |||
| WxCpConfigStorage wxCpConfigStorage, | |||
| String timestamp, String nonce, String msgSignature) { | |||
| WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); | |||
| String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); | |||
| return fromXml(plainText); | |||
| } | |||
| public static WxCpXmlMessage fromEncryptedXml( | |||
| InputStream is, | |||
| WxCpConfigStorage wxCpConfigStorage, | |||
| String timestamp, String nonce, String msgSignature) { | |||
| try { | |||
| return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| public String getStatus() { | |||
| return status; | |||
| } | |||
| @@ -491,51 +490,51 @@ public class WxCpXmlMessage implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxCpXmlMessage{" + | |||
| "agentId=" + agentId + | |||
| ", toUserName='" + toUserName + '\'' + | |||
| ", fromUserName='" + fromUserName + '\'' + | |||
| ", createTime=" + createTime + | |||
| ", msgType='" + msgType + '\'' + | |||
| ", content='" + content + '\'' + | |||
| ", msgId=" + msgId + | |||
| ", picUrl='" + picUrl + '\'' + | |||
| ", mediaId='" + mediaId + '\'' + | |||
| ", format='" + format + '\'' + | |||
| ", thumbMediaId='" + thumbMediaId + '\'' + | |||
| ", locationX=" + locationX + | |||
| ", locationY=" + locationY + | |||
| ", scale=" + scale + | |||
| ", label='" + label + '\'' + | |||
| ", title='" + title + '\'' + | |||
| ", description='" + description + '\'' + | |||
| ", url='" + url + '\'' + | |||
| ", event='" + event + '\'' + | |||
| ", eventKey='" + eventKey + '\'' + | |||
| ", ticket='" + ticket + '\'' + | |||
| ", latitude=" + latitude + | |||
| ", longitude=" + longitude + | |||
| ", precision=" + precision + | |||
| ", recognition='" + recognition + '\'' + | |||
| ", status='" + status + '\'' + | |||
| ", totalCount=" + totalCount + | |||
| ", filterCount=" + filterCount + | |||
| ", sentCount=" + sentCount + | |||
| ", errorCount=" + errorCount + | |||
| ", scanCodeInfo=" + scanCodeInfo + | |||
| ", sendPicsInfo=" + sendPicsInfo + | |||
| ", sendLocationInfo=" + sendLocationInfo + | |||
| '}'; | |||
| "agentId=" + agentId + | |||
| ", toUserName='" + toUserName + '\'' + | |||
| ", fromUserName='" + fromUserName + '\'' + | |||
| ", createTime=" + createTime + | |||
| ", msgType='" + msgType + '\'' + | |||
| ", content='" + content + '\'' + | |||
| ", msgId=" + msgId + | |||
| ", picUrl='" + picUrl + '\'' + | |||
| ", mediaId='" + mediaId + '\'' + | |||
| ", format='" + format + '\'' + | |||
| ", thumbMediaId='" + thumbMediaId + '\'' + | |||
| ", locationX=" + locationX + | |||
| ", locationY=" + locationY + | |||
| ", scale=" + scale + | |||
| ", label='" + label + '\'' + | |||
| ", title='" + title + '\'' + | |||
| ", description='" + description + '\'' + | |||
| ", url='" + url + '\'' + | |||
| ", event='" + event + '\'' + | |||
| ", eventKey='" + eventKey + '\'' + | |||
| ", ticket='" + ticket + '\'' + | |||
| ", latitude=" + latitude + | |||
| ", longitude=" + longitude + | |||
| ", precision=" + precision + | |||
| ", recognition='" + recognition + '\'' + | |||
| ", status='" + status + '\'' + | |||
| ", totalCount=" + totalCount + | |||
| ", filterCount=" + filterCount + | |||
| ", sentCount=" + sentCount + | |||
| ", errorCount=" + errorCount + | |||
| ", scanCodeInfo=" + scanCodeInfo + | |||
| ", sendPicsInfo=" + sendPicsInfo + | |||
| ", sendLocationInfo=" + sendLocationInfo + | |||
| '}'; | |||
| } | |||
| @XStreamAlias("ScanCodeInfo") | |||
| public static class ScanCodeInfo { | |||
| @XStreamAlias("ScanType") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String scanType; | |||
| @XStreamAlias("ScanResult") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String scanResult; | |||
| /** | |||
| @@ -566,11 +565,10 @@ public class WxCpXmlMessage implements Serializable { | |||
| @XStreamAlias("SendPicsInfo") | |||
| public static class SendPicsInfo { | |||
| @XStreamAlias("Count") | |||
| private Long count; | |||
| @XStreamAlias("PicList") | |||
| protected final List<Item> picList = new ArrayList<Item>(); | |||
| @XStreamAlias("Count") | |||
| private Long count; | |||
| public Long getCount() { | |||
| return count; | |||
| @@ -588,7 +586,7 @@ public class WxCpXmlMessage implements Serializable { | |||
| public static class Item { | |||
| @XStreamAlias("PicMd5Sum") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String PicMd5Sum; | |||
| public String getPicMd5Sum() { | |||
| @@ -605,23 +603,23 @@ public class WxCpXmlMessage implements Serializable { | |||
| public static class SendLocationInfo { | |||
| @XStreamAlias("Location_X") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String locationX; | |||
| @XStreamAlias("Location_Y") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String locationY; | |||
| @XStreamAlias("Scale") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String scale; | |||
| @XStreamAlias("Label") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String label; | |||
| @XStreamAlias("Poiname") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String poiname; | |||
| public String getLocationX() { | |||
| @@ -7,15 +7,15 @@ import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||
| @XStreamAlias("xml") | |||
| public class WxCpXmlOutImageMessage extends WxCpXmlOutMessage { | |||
| @XStreamAlias("Image") | |||
| @XStreamConverter(value=XStreamMediaIdConverter.class) | |||
| @XStreamConverter(value = XStreamMediaIdConverter.class) | |||
| private String mediaId; | |||
| public WxCpXmlOutImageMessage() { | |||
| this.msgType = WxConsts.XML_MSG_IMAGE; | |||
| } | |||
| public String getMediaId() { | |||
| return mediaId; | |||
| } | |||
| @@ -23,5 +23,5 @@ public class WxCpXmlOutImageMessage extends WxCpXmlOutMessage { | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| } | |||
| @@ -12,20 +12,55 @@ import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
| public abstract class WxCpXmlOutMessage { | |||
| @XStreamAlias("ToUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String toUserName; | |||
| @XStreamAlias("FromUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String fromUserName; | |||
| @XStreamAlias("CreateTime") | |||
| protected Long createTime; | |||
| @XStreamAlias("MsgType") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String msgType; | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| public String getToUserName() { | |||
| return toUserName; | |||
| } | |||
| @@ -59,7 +94,7 @@ public abstract class WxCpXmlOutMessage { | |||
| } | |||
| protected String toXml() { | |||
| return XStreamTransformer.toXml((Class)this.getClass(), this); | |||
| return XStreamTransformer.toXml((Class) this.getClass(), this); | |||
| } | |||
| /** | |||
| @@ -70,39 +105,4 @@ public abstract class WxCpXmlOutMessage { | |||
| WxCpCryptUtil pc = new WxCpCryptUtil(wxCpConfigStorage); | |||
| return pc.encrypt(plainXml); | |||
| } | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| } | |||
| @@ -11,12 +11,11 @@ import java.util.List; | |||
| @XStreamAlias("xml") | |||
| public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | |||
| @XStreamAlias("ArticleCount") | |||
| protected int articleCount; | |||
| @XStreamAlias("Articles") | |||
| protected final List<Item> articles = new ArrayList<Item>(); | |||
| @XStreamAlias("ArticleCount") | |||
| protected int articleCount; | |||
| public WxCpXmlOutNewsMessage() { | |||
| this.msgType = WxConsts.XML_MSG_NEWS; | |||
| } | |||
| @@ -29,31 +28,31 @@ public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | |||
| this.articles.add(item); | |||
| this.articleCount = this.articles.size(); | |||
| } | |||
| public List<Item> getArticles() { | |||
| return articles; | |||
| } | |||
| @XStreamAlias("item") | |||
| public static class Item { | |||
| @XStreamAlias("Title") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String Title; | |||
| @XStreamAlias("Description") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String Description; | |||
| @XStreamAlias("PicUrl") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String PicUrl; | |||
| @XStreamAlias("Url") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String Url; | |||
| public String getTitle() { | |||
| return Title; | |||
| } | |||
| @@ -7,15 +7,15 @@ import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| @XStreamAlias("xml") | |||
| public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage { | |||
| @XStreamAlias("Content") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String content; | |||
| public WxCpXmlOutTextMessage() { | |||
| this.msgType = WxConsts.XML_MSG_TEXT; | |||
| } | |||
| public String getContent() { | |||
| return content; | |||
| } | |||
| @@ -24,5 +24,5 @@ public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage { | |||
| this.content = content; | |||
| } | |||
| } | |||
| @@ -38,21 +38,21 @@ public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { | |||
| public void setDescription(String description) { | |||
| video.setDescription(description); | |||
| } | |||
| @XStreamAlias("Video") | |||
| public static class Video { | |||
| @XStreamAlias("MediaId") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String mediaId; | |||
| @XStreamAlias("Title") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String title; | |||
| @XStreamAlias("Description") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String description; | |||
| public String getMediaId() { | |||
| @@ -78,7 +78,7 @@ public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| } | |||
| } | |||
| @@ -7,15 +7,15 @@ import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||
| @XStreamAlias("xml") | |||
| public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage { | |||
| @XStreamAlias("Voice") | |||
| @XStreamConverter(value=XStreamMediaIdConverter.class) | |||
| @XStreamConverter(value = XStreamMediaIdConverter.class) | |||
| private String mediaId; | |||
| public WxCpXmlOutVoiceMessage() { | |||
| this.msgType = WxConsts.XML_MSG_VOICE; | |||
| } | |||
| public String getMediaId() { | |||
| return mediaId; | |||
| } | |||
| @@ -23,5 +23,5 @@ public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage { | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| } | |||
| @@ -44,7 +44,7 @@ public class BaseBuilder<T> { | |||
| m.setToParty(this.toParty); | |||
| m.setToTag(this.toTag); | |||
| m.setSafe( | |||
| (this.safe == null || "".equals(this.safe))? WxConsts.CUSTOM_MSG_SAFE_NO: this.safe); | |||
| (this.safe == null || "".equals(this.safe)) ? WxConsts.CUSTOM_MSG_SAFE_NO : this.safe); | |||
| return m; | |||
| } | |||
| @@ -8,8 +8,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.FILE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class FileBuilder extends BaseBuilder<FileBuilder> { | |||
| private String mediaId; | |||
| @@ -8,8 +8,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.IMAGE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class ImageBuilder extends BaseBuilder<ImageBuilder> { | |||
| private String mediaId; | |||
| @@ -12,13 +12,13 @@ import java.util.List; | |||
| * 用法: | |||
| * WxCustomMessage m = WxCustomMessage.NEWS().addArticle(article).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class NewsBuilder extends BaseBuilder<NewsBuilder> { | |||
| private List<WxCpMessage.WxArticle> articles = new ArrayList<WxCpMessage.WxArticle>(); | |||
| public NewsBuilder() { | |||
| this.msgType = WxConsts.CUSTOM_MSG_NEWS; | |||
| } | |||
| @@ -8,8 +8,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.TEXT().content(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class TextBuilder extends BaseBuilder<TextBuilder> { | |||
| private String content; | |||
| @@ -14,8 +14,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| * .toUser(...) | |||
| * .build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class VideoBuilder extends BaseBuilder<VideoBuilder> { | |||
| private String mediaId; | |||
| @@ -8,8 +8,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.VOICE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class VoiceBuilder extends BaseBuilder<VoiceBuilder> { | |||
| private String mediaId; | |||
| @@ -3,27 +3,27 @@ package me.chanjar.weixin.cp.bean.outxmlbuilder; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| public abstract class BaseBuilder<BuilderType, ValueType> { | |||
| protected String toUserName; | |||
| protected String fromUserName; | |||
| public BuilderType toUser(String touser) { | |||
| this.toUserName = touser; | |||
| return (BuilderType) this; | |||
| } | |||
| public BuilderType fromUser(String fromusername) { | |||
| this.fromUserName = fromusername; | |||
| return (BuilderType) this; | |||
| } | |||
| public abstract ValueType build(); | |||
| public void setCommon(WxCpXmlOutMessage m) { | |||
| m.setToUserName(this.toUserName); | |||
| m.setFromUserName(this.fromUserName); | |||
| m.setCreateTime(System.currentTimeMillis() / 1000l); | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage; | |||
| /** | |||
| * 图片消息builder | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxCpXmlOutImageMessage> { | |||
| @@ -21,5 +22,5 @@ public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxCpXmlOutImag | |||
| m.setMediaId(this.mediaId); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -8,24 +8,25 @@ import java.util.List; | |||
| /** | |||
| * 图文消息builder | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class NewsBuilder extends BaseBuilder<NewsBuilder, WxCpXmlOutNewsMessage> { | |||
| protected final List<Item> articles = new ArrayList<Item>(); | |||
| public NewsBuilder addArticle(Item item) { | |||
| this.articles.add(item); | |||
| return this; | |||
| } | |||
| public WxCpXmlOutNewsMessage build() { | |||
| WxCpXmlOutNewsMessage m = new WxCpXmlOutNewsMessage(); | |||
| for(Item item : articles) { | |||
| for (Item item : articles) { | |||
| m.addArticle(item); | |||
| } | |||
| setCommon(m); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -4,8 +4,8 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||
| /** | |||
| * 文本消息builder | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class TextBuilder extends BaseBuilder<TextBuilder, WxCpXmlOutTextMessage> { | |||
| private String content; | |||
| @@ -4,8 +4,8 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage; | |||
| /** | |||
| * 视频消息builder | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class VideoBuilder extends BaseBuilder<VideoBuilder, WxCpXmlOutVideoMessage> { | |||
| @@ -17,15 +17,17 @@ public final class VideoBuilder extends BaseBuilder<VideoBuilder, WxCpXmlOutVide | |||
| this.title = title; | |||
| return this; | |||
| } | |||
| public VideoBuilder description(String description) { | |||
| this.description = description; | |||
| return this; | |||
| } | |||
| public VideoBuilder mediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| return this; | |||
| } | |||
| public WxCpXmlOutVideoMessage build() { | |||
| WxCpXmlOutVideoMessage m = new WxCpXmlOutVideoMessage(); | |||
| setCommon(m); | |||
| @@ -34,5 +36,5 @@ public final class VideoBuilder extends BaseBuilder<VideoBuilder, WxCpXmlOutVide | |||
| m.setMediaId(mediaId); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage; | |||
| /** | |||
| * 语音消息builder | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public final class VoiceBuilder extends BaseBuilder<VoiceBuilder, WxCpXmlOutVoiceMessage> { | |||
| @@ -14,12 +15,12 @@ public final class VoiceBuilder extends BaseBuilder<VoiceBuilder, WxCpXmlOutVoic | |||
| this.mediaId = mediaId; | |||
| return this; | |||
| } | |||
| public WxCpXmlOutVoiceMessage build() { | |||
| WxCpXmlOutVoiceMessage m = new WxCpXmlOutVoiceMessage(); | |||
| setCommon(m); | |||
| m.setMediaId(mediaId); | |||
| return m; | |||
| } | |||
| } | |||
| @@ -2,6 +2,10 @@ | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| @@ -37,7 +37,7 @@ public class WxCpDepartGsonAdapter implements JsonSerializer<WxCpDepart>, JsonDe | |||
| } | |||
| public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | |||
| throws JsonParseException { | |||
| throws JsonParseException { | |||
| WxCpDepart depart = new WxCpDepart(); | |||
| JsonObject departJson = json.getAsJsonObject(); | |||
| if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) { | |||
| @@ -16,9 +16,7 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxCpMessageGsonAdapter implements JsonSerializer<WxCpMessage> { | |||
| @@ -83,7 +81,7 @@ public class WxCpMessageGsonAdapter implements JsonSerializer<WxCpMessage> { | |||
| newsJsonObject.add("articles", articleJsonArray); | |||
| messageJson.add("news", newsJsonObject); | |||
| } | |||
| return messageJson; | |||
| } | |||
| @@ -27,7 +27,7 @@ public class WxCpTagGsonAdapter implements JsonSerializer<WxCpTag>, JsonDeserial | |||
| } | |||
| public WxCpTag deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | |||
| throws JsonParseException { | |||
| throws JsonParseException { | |||
| JsonObject jsonObject = json.getAsJsonObject(); | |||
| return new WxCpTag(GsonHelper.getString(jsonObject, "tagid"), GsonHelper.getString(jsonObject, "tagname")); | |||
| } | |||
| @@ -20,13 +20,13 @@ import java.lang.reflect.Type; | |||
| public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> { | |||
| public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | |||
| throws JsonParseException { | |||
| throws JsonParseException { | |||
| JsonObject o = json.getAsJsonObject(); | |||
| WxCpUser user = new WxCpUser(); | |||
| user.setUserId(GsonHelper.getString(o, "userid")); | |||
| user.setName(GsonHelper.getString(o, "name")); | |||
| if(o.get("department") != null) { | |||
| if (o.get("department") != null) { | |||
| JsonArray departJsonArray = o.get("department").getAsJsonArray(); | |||
| Integer[] departIds = new Integer[departJsonArray.size()]; | |||
| int i = 0; | |||
| @@ -49,8 +49,8 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
| JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray(); | |||
| for (JsonElement attrJsonElement : attrJsonElements) { | |||
| WxCpUser.Attr attr = new WxCpUser.Attr( | |||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"), | |||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value") | |||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"), | |||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value") | |||
| ); | |||
| user.getExtAttrs().add(attr); | |||
| } | |||
| @@ -29,16 +29,16 @@ public class XStreamTransformer { | |||
| /** | |||
| * 注册扩展消息的解析器 | |||
| * @param clz 类型 | |||
| * | |||
| * @param clz 类型 | |||
| * @param xStream xml解析器 | |||
| */ | |||
| public static void register(Class clz,XStream xStream){ | |||
| CLASS_2_XSTREAM_INSTANCE.put(clz,xStream); | |||
| */ | |||
| public static void register(Class clz, XStream xStream) { | |||
| CLASS_2_XSTREAM_INSTANCE.put(clz, xStream); | |||
| } | |||
| /** | |||
| * pojo -> xml | |||
| * | |||
| */ | |||
| public static <T> String toXml(Class<T> clazz, T object) { | |||
| return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); | |||
| @@ -10,17 +10,6 @@ import java.io.InputStream; | |||
| public class ApiTestModule implements Module { | |||
| @Override | |||
| public void configure(Binder binder) { | |||
| InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | |||
| WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1); | |||
| WxCpServiceImpl wxService = new WxCpServiceImpl(); | |||
| wxService.setWxCpConfigStorage(config); | |||
| binder.bind(WxCpServiceImpl.class).toInstance(wxService); | |||
| binder.bind(WxCpConfigStorage.class).toInstance(config); | |||
| } | |||
| public static <T> T fromXml(Class<T> clazz, InputStream is) { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.alias("xml", clazz); | |||
| @@ -28,9 +17,20 @@ public class ApiTestModule implements Module { | |||
| return (T) xstream.fromXML(is); | |||
| } | |||
| @Override | |||
| public void configure(Binder binder) { | |||
| InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | |||
| WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1); | |||
| WxCpServiceImpl wxService = new WxCpServiceImpl(); | |||
| wxService.setWxCpConfigStorage(config); | |||
| binder.bind(WxCpServiceImpl.class).toInstance(wxService); | |||
| binder.bind(WxCpConfigStorage.class).toInstance(config); | |||
| } | |||
| @XStreamAlias("xml") | |||
| public static class WxXmlCpInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | |||
| protected String userId; | |||
| protected String departmentId; | |||
| @@ -40,6 +40,7 @@ public class ApiTestModule implements Module { | |||
| public String getUserId() { | |||
| return userId; | |||
| } | |||
| public void setUserId(String userId) { | |||
| this.userId = userId; | |||
| } | |||
| @@ -63,11 +64,11 @@ public class ApiTestModule implements Module { | |||
| @Override | |||
| public String toString() { | |||
| return super.toString() + " > WxXmlCpConfigStorage{" + | |||
| "userId='" + userId + '\'' + | |||
| ", departmentId='" + departmentId + '\'' + | |||
| ", tagId='" + tagId + '\'' + | |||
| '}'; | |||
| "userId='" + userId + '\'' + | |||
| ", departmentId='" + departmentId + '\'' + | |||
| ", tagId='" + tagId + '\'' + | |||
| '}'; | |||
| } | |||
| } | |||
| } | |||
| @@ -9,8 +9,8 @@ import org.testng.annotations.Test; | |||
| /** | |||
| * 基础API测试 | |||
| * @author Daniel Qian | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| @Test(groups = "baseAPI") | |||
| @Guice(modules = ApiTestModule.class) | |||