| @@ -53,3 +53,6 @@ sonar-project.properties | |||
| !/.mvn/wrapper/maven-wrapper.jar | |||
| *.versionsBackup | |||
| # STS | |||
| .factorypath | |||
| @@ -8,10 +8,10 @@ | |||
| - (***暂停此种方式,请使用第一种***)另外一种贡献代码的方式就是加入SDK Developers开发组,前提是对自己的代码足够自信就可以申请加入,加入之后可以随时直接提交代码,但要注意对所做的修改或新增的代码进行单元测试,保证提交代码没有明显问题。 | |||
| ### PR方式贡献代码步骤 | |||
| * 在 GitHub 上 `fork` 到自己的仓库,如 `my_user/weixin-java-tools`,然后 `clone` 到本地,并设置用户信息。 | |||
| * 在 GitHub 上 `fork` 到自己的仓库,如 `my_user/WxJava`,然后 `clone` 到本地,并设置用户信息。 | |||
| ```bash | |||
| $ git clone git@github.com:my_user/weixin-java-tools.git | |||
| $ git clone git@github.com:my_user/WxJava.git | |||
| $ cd weixin-java-tools | |||
| $ git config user.name "yourname" | |||
| $ git config user.email "your email" | |||
| @@ -27,7 +27,7 @@ $ git push | |||
| * 定期使用项目仓库内容更新自己仓库内容。 | |||
| ```bash | |||
| $ git remote add upstream https://github.com/wechat-group/weixin-java-tools | |||
| $ git remote add upstream https://github.com/Wechat-Group/WxJava | |||
| $ git fetch upstream | |||
| $ git checkout develop | |||
| $ git rebase upstream/develop | |||
| @@ -5,12 +5,12 @@ | |||
| xmlns="http://maven.apache.org/POM/4.0.0"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>3.2.0</version> | |||
| <artifactId>wx-java</artifactId> | |||
| <version>3.3.0</version> | |||
| <packaging>pom</packaging> | |||
| <name>Weixin Java Tools - Parent</name> | |||
| <name>WxJava - Weixin/Wechat Java SDK</name> | |||
| <description>微信开发Java SDK</description> | |||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||
| <url>https://github.com/Wechat-Group/WxJava</url> | |||
| <licenses> | |||
| <license> | |||
| @@ -85,6 +85,11 @@ | |||
| <email>007gzs@gmail.com</email> | |||
| <url>https://github.com/007gzs</url> | |||
| </developer> | |||
| <developer> | |||
| <name>Howard Liu</name> | |||
| <email>liuxinghao1988@gmail.com</email> | |||
| <url>https://github.com/howardliu-cn</url> | |||
| </developer> | |||
| </developers> | |||
| <scm> | |||
| @@ -166,7 +171,7 @@ | |||
| <dependency> | |||
| <groupId>com.thoughtworks.xstream</groupId> | |||
| <artifactId>xstream</artifactId> | |||
| <version>1.4.9</version> | |||
| <version>1.4.10</version> | |||
| </dependency> | |||
| <!-- 由于guava较新的21.0版本需要jdk8,故而此处采用较低版本 --> | |||
| <dependency> | |||
| @@ -140,14 +140,6 @@ | |||
| </module> | |||
| <module name="OverloadMethodsDeclarationOrder"/> | |||
| <module name="VariableDeclarationUsageDistance"/> | |||
| <module name="CustomImportOrder"> | |||
| <property name="customImportOrderRules" | |||
| value="SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE###STATIC"/> | |||
| <property name="specialImportsRegExp" value="^javax\."/> | |||
| <property name="standardPackageRegExp" value="^java\."/> | |||
| <property name="sortImportsInGroupAlphabetically" value="true"/> | |||
| <property name="separateLineBetweenGroups" value="false"/> | |||
| </module> | |||
| <module name="MethodParamPad"/> | |||
| <module name="ParenPad"/> | |||
| <module name="OperatorWrap"> | |||
| @@ -3,9 +3,9 @@ | |||
| [](https://gitee.com/binary/weixin-java-tools) | |||
| [](https://github.com/Wechat-Group/WxJava) | |||
| [](https://github.com/Wechat-Group/WxJava/releases) | |||
| [](http://mvnrepository.com/artifact/com.github.binarywang/weixin-java-parent) | |||
| [](http://mvnrepository.com/artifact/com.github.binarywang/wx-java) | |||
| [](https://travis-ci.org/Wechat-Group/WxJava) | |||
| [](https://www.jetbrains.com/?from=weixin-java-tools) | |||
| [](https://www.jetbrains.com/?from=WxJava-weixin-java-tools) | |||
| [](https://opensource.org/licenses/Apache-2.0) | |||
| --------------------------------- | |||
| @@ -13,8 +13,7 @@ | |||
| --------------------------------- | |||
| ### 重要信息 | |||
| 1. **2018-09-24 发布 [【3.2.0正式版】](https://github.com/Wechat-Group/WxJava/releases)**! | |||
| 1. **2018-12-23 项目更名为WxJava,并发布 [【3.3.0正式版】](https://github.com/Wechat-Group/WxJava/releases)**! | |||
| 1. 新手重要提示:本项目仅是一个SDK开发工具包,未提供Web实现,建议使用maven或gradle引用本项目即可使用本SDK提供的各种功能,详情可参考 **[【Demo项目】](demo.md)** 或本项目中的部分单元测试代码;另外微信开发新手请务必阅读[【开发文档 Wiki 首页】](https://github.com/Wechat-Group/WxJava/wiki)的常见问题部分,可以少走很多弯路,节省不少时间。 | |||
| --------------------------------- | |||
| @@ -152,3 +151,4 @@ | |||
| 1. [qsjia (QSJia)](http://github.com/qsjia) | |||
| 1. [webcreazy (webcreazy)](http://github.com/webcreazy) | |||
| 1. [cwivan (鱼丸Cwivan)](http://github.com/cwivan) | |||
| 1. [howardliu-cn (看山)](https://github.com/howardliu-cn) | |||
| @@ -6,12 +6,12 @@ | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>3.2.0</version> | |||
| <artifactId>wx-java</artifactId> | |||
| <version>3.3.0</version> | |||
| </parent> | |||
| <artifactId>weixin-java-common</artifactId> | |||
| <name>Weixin Java Tools - Common</name> | |||
| <name>WxJava - Common</name> | |||
| <description>微信开发Java SDK公共模块</description> | |||
| <dependencies> | |||
| @@ -114,6 +114,11 @@ | |||
| <artifactId>assertj-guava</artifactId> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.dom4j</groupId> | |||
| <artifactId>dom4j</artifactId> | |||
| <version>2.0.0</version> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| @@ -250,6 +250,15 @@ public class WxConsts { | |||
| */ | |||
| public static final String CARD_PAY_ORDER = "card_pay_order"; | |||
| /** | |||
| * 小程序审核事件:审核通过 | |||
| */ | |||
| public static final String WEAPP_AUDIT_SUCCESS = "weapp_audit_success"; | |||
| /** | |||
| * 小程序审核事件:审核不通过 | |||
| */ | |||
| public static final String WEAPP_AUDIT_FAIL = "weapp_audit_fail"; | |||
| } | |||
| @@ -76,7 +76,7 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec | |||
| } | |||
| } | |||
| } catch (InterruptedException e) { | |||
| e.printStackTrace(); | |||
| Thread.currentThread().interrupt(); | |||
| } | |||
| } | |||
| }); | |||
| @@ -2,10 +2,8 @@ package me.chanjar.weixin.common.bean; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| /** | |||
| * 卡券Api签名. | |||
| @@ -37,6 +35,6 @@ public class WxCardApiSignature implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -7,9 +7,6 @@ import java.nio.charset.StandardCharsets; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| @@ -49,7 +46,7 @@ public class WxMenu implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return this.toJson(); | |||
| } | |||
| } | |||
| @@ -4,11 +4,9 @@ import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| /** | |||
| * menu button. | |||
| @@ -83,7 +81,7 @@ public class WxMenuButton implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -2,11 +2,9 @@ package me.chanjar.weixin.common.bean.menu; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| /** | |||
| * menu rule. | |||
| @@ -31,6 +29,6 @@ public class WxMenuRule implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -2,9 +2,6 @@ package me.chanjar.weixin.common.bean.result; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| @@ -28,7 +25,7 @@ public class WxMediaUploadResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -794,8 +794,7 @@ public enum WxCpErrorMsgEnum { | |||
| * 通过错误代码查找其中文含义. | |||
| */ | |||
| public static String findMsgByCode(int code) { | |||
| WxCpErrorMsgEnum[] values = WxCpErrorMsgEnum.values(); | |||
| for (WxCpErrorMsgEnum value : values) { | |||
| for (WxCpErrorMsgEnum value : WxCpErrorMsgEnum.values()) { | |||
| if (value.code == code) { | |||
| return value.msg; | |||
| } | |||
| @@ -64,6 +64,11 @@ public class WxError implements Serializable { | |||
| if (msg != null) { | |||
| wxError.setErrorMsg(msg); | |||
| } | |||
| } else if (type == WxType.MiniApp) { | |||
| final String msg = WxMaErrorMsgEnum.findMsgByCode(wxError.getErrorCode()); | |||
| if (msg != null) { | |||
| wxError.setErrorMsg(msg); | |||
| } | |||
| } | |||
| return wxError; | |||
| @@ -0,0 +1,253 @@ | |||
| package me.chanjar.weixin.common.error; | |||
| import lombok.Getter; | |||
| /** | |||
| * 微信小程序错误码 | |||
| * | |||
| * @author <a href="https://github.com/biggates">biggates</a> | |||
| */ | |||
| @Getter | |||
| public enum WxMaErrorMsgEnum { | |||
| /** | |||
| * <pre> | |||
| * 获取 access_token 时 AppSecret 错误, | |||
| * 或者 access_token 无效。请开发者认真比对 AppSecret 的正确性,或查看是否正在为恰当的小程序调用接口 | |||
| * 对应操作:<code>sendCustomerMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_40001(40001, "access_token 无效或 AppSecret 错误"), | |||
| /** | |||
| * <pre> | |||
| * 不合法的凭证类型 | |||
| * 对应操作:<code>sendCustomerMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_40002(40002, "不合法的凭证类型"), | |||
| /** | |||
| * <pre> | |||
| * touser不是正确的openid. | |||
| * 对应操作:<code>sendCustomerMessage</code>, <code>sendUniformMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_40003(40003, "openid 不正确"), | |||
| /** | |||
| * <pre> | |||
| * 无效媒体文件类型 | |||
| * 对应操作:<code>uploadTempMedia</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/uploadTempMedia.html | |||
| * </pre> | |||
| */ | |||
| CODE_40004(40004, "无效媒体文件类型"), | |||
| /** | |||
| * <pre> | |||
| * 无效媒体文件 ID. | |||
| * 对应操作:<code>getTempMedia</code> | |||
| * 对应地址: | |||
| * GET https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/getTempMedia.html | |||
| * </pre> | |||
| */ | |||
| CODE_40007(40007, "无效媒体文件 ID"), | |||
| /** | |||
| * <pre> | |||
| * appid不正确,或者不符合绑定关系要求. | |||
| * 对应操作:<code>sendUniformMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_40013(40013, "appid不正确,或者不符合绑定关系要求"), | |||
| /** | |||
| * <pre> | |||
| * template_id 不正确. | |||
| * 对应操作:<code>sendUniformMessage</code>, <code>sendTemplateMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_40037(40037, "template_id 不正确"), | |||
| /** | |||
| * <pre> | |||
| * form_id不正确,或者过期. | |||
| * 对应操作:<code>sendUniformMessage</code>, <code>sendTemplateMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_41028(41028, "form_id 不正确,或者过期"), | |||
| /** | |||
| * <pre> | |||
| * code 或 template_id 不正确. | |||
| * 对应操作:<code>code2Session</code>, <code>sendUniformMessage</code>, <code>sendTemplateMessage</code> | |||
| * 对应地址: | |||
| * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_41029(41029, "请求的参数不正确"), | |||
| /** | |||
| * <pre> | |||
| * form_id 已被使用,或者所传page页面不存在,或者小程序没有发布 | |||
| * 对应操作:<code>sendUniformMessage</coce>, <code>getWXACodeUnlimit</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html | |||
| * </pre> | |||
| */ | |||
| CODE_41030(41030, "请求的参数不正确"), | |||
| /** | |||
| * <pre> | |||
| * 调用分钟频率受限. | |||
| * 对应操作:<code>getWXACodeUnlimit</code>, <code>sendUniformMessage</code>, <code>sendTemplateMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACodeUnlimit.html | |||
| * </pre> | |||
| */ | |||
| CODE_45009(45009, "调用分钟频率受限"), | |||
| /** | |||
| * <pre> | |||
| * 频率限制,每个用户每分钟100次. | |||
| * 对应操作:<code>code2Session</code> | |||
| * 对应地址: | |||
| * GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html | |||
| * </pre> | |||
| */ | |||
| CODE_45011(45011, "频率限制,每个用户每分钟100次"), | |||
| /** | |||
| * <pre> | |||
| * 回复时间超过限制. | |||
| * 对应操作:<code>sendCustomerMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_45015(45015, "回复时间超过限制"), | |||
| /** | |||
| * <pre> | |||
| * 接口调用超过限额, 或生成码个数总和到达最大个数限制. | |||
| * 对应操作:<code>createWXAQRCode</code>, <code>sendTemplateMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/qr-code/getWXACode.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/template-message/sendTemplateMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_45029(45029, "接口调用超过限额"), | |||
| /** | |||
| * <pre> | |||
| * 客服接口下行条数超过上限. | |||
| * 对应操作:<code>sendCustomerMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_45047(45047, "客服接口下行条数超过上限"), | |||
| /** | |||
| * <pre> | |||
| * command字段取值不对 | |||
| * 对应操作:<code>customerTyping</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html | |||
| * </pre> | |||
| */ | |||
| CODE_45072(45072, "command字段取值不对"), | |||
| /** | |||
| * <pre> | |||
| * 下发输入状态,需要之前30秒内跟用户有过消息交互. | |||
| * 对应操作:<code>customerTyping</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html | |||
| */ | |||
| CODE_45080(45080, "下发输入状态,需要之前30秒内跟用户有过消息交互"), | |||
| /** | |||
| * <pre> | |||
| * 已经在输入状态,不可重复下发. | |||
| * 对应操作:<code>customerTyping</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/typing?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/customerTyping.html | |||
| * </pre> | |||
| */ | |||
| CODE_45081(45081, "已经在输入状态,不可重复下发"), | |||
| /** | |||
| * <pre> | |||
| * API 功能未授权,请确认小程序已获得该接口. | |||
| * 对应操作:<code>sendCustomerMessage</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/customer-message/sendCustomerMessage.html | |||
| * </pre> | |||
| */ | |||
| CODE_48001(48001, "API 功能未授权"), | |||
| /** | |||
| * <pre> | |||
| * 内容含有违法违规内容. | |||
| * 对应操作:<code>imgSecCheck</code>, <code>msgSecCheck</code> | |||
| * 对应地址: | |||
| * POST https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN | |||
| * POST https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN | |||
| * 参考文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/imgSecCheck.html | |||
| * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/msgSecCheck.html | |||
| * </pre> | |||
| */ | |||
| CODE_87014(87014, "内容含有违法违规内容"); | |||
| private int code; | |||
| private String msg; | |||
| WxMaErrorMsgEnum(int code, String msg) { | |||
| this.code = code; | |||
| this.msg = msg; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 通过错误代码查找其中文含义. | |||
| */ | |||
| public static String findMsgByCode(int code) { | |||
| for (WxMaErrorMsgEnum value : WxMaErrorMsgEnum.values()) { | |||
| if (value.code == code) { | |||
| return value.msg; | |||
| } | |||
| } | |||
| return null; | |||
| } | |||
| } | |||
| @@ -642,8 +642,7 @@ public enum WxMpErrorMsgEnum { | |||
| * 通过错误代码查找其中文含义. | |||
| */ | |||
| public static String findMsgByCode(int code) { | |||
| WxMpErrorMsgEnum[] values = WxMpErrorMsgEnum.values(); | |||
| for (WxMpErrorMsgEnum value : values) { | |||
| for (WxMpErrorMsgEnum value : WxMpErrorMsgEnum.values()) { | |||
| if (value.code == code) { | |||
| return value.msg; | |||
| } | |||
| @@ -196,6 +196,7 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||
| Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L); | |||
| backgroundProcess(); | |||
| } catch (InterruptedException e) { | |||
| Thread.currentThread().interrupt(); | |||
| StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e); | |||
| } | |||
| } | |||
| @@ -1,12 +1,14 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import org.apache.commons.codec.binary.Hex; | |||
| import javax.crypto.Mac; | |||
| import javax.crypto.spec.SecretKeySpec; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.security.InvalidKeyException; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import javax.crypto.Mac; | |||
| import javax.crypto.spec.SecretKeySpec; | |||
| import org.apache.commons.codec.binary.Hex; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| /** | |||
| * <pre> | |||
| @@ -27,9 +29,9 @@ public class SignUtils { | |||
| public static String createHmacSha256Sign(String message, String key) { | |||
| try { | |||
| Mac sha256 = Mac.getInstance("HmacSHA256"); | |||
| SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256"); | |||
| SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); | |||
| sha256.init(secretKeySpec); | |||
| byte[] bytes = sha256.doFinal(message.getBytes()); | |||
| byte[] bytes = sha256.doFinal(message.getBytes(StandardCharsets.UTF_8)); | |||
| return Hex.encodeHexString(bytes).toUpperCase(); | |||
| } catch (NoSuchAlgorithmException | InvalidKeyException e) { | |||
| SignUtils.log.error(e.getMessage(), e); | |||
| @@ -0,0 +1,96 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import java.io.StringReader; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.Set; | |||
| import org.dom4j.Document; | |||
| import org.dom4j.DocumentException; | |||
| import org.dom4j.Element; | |||
| import org.dom4j.Node; | |||
| import org.dom4j.io.SAXReader; | |||
| import org.dom4j.tree.DefaultText; | |||
| import com.google.common.collect.Lists; | |||
| import com.google.common.collect.Maps; | |||
| import com.google.common.collect.Sets; | |||
| /** | |||
| * <pre> | |||
| * XML转换工具类. | |||
| * Created by Binary Wang on 2018/11/4. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class XmlUtils { | |||
| public static Map<String, Object> xml2Map(String xmlString) { | |||
| Map<String, Object> map = new HashMap<>(16); | |||
| try { | |||
| SAXReader saxReader = new SAXReader(); | |||
| Document doc = saxReader.read(new StringReader(xmlString)); | |||
| Element root = doc.getRootElement(); | |||
| List<Element> elements = root.elements(); | |||
| for (Element element : elements) { | |||
| map.put(element.getName(), element2MapOrString(element)); | |||
| } | |||
| } catch (DocumentException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| return map; | |||
| } | |||
| private static Object element2MapOrString(Element element) { | |||
| Map<String, Object> result = Maps.newHashMap(); | |||
| final List<Node> content = element.content(); | |||
| if (content.size() <= 1) { | |||
| return element.getText(); | |||
| } | |||
| final Set<String> names = names(content); | |||
| if (names.size() == 1) { | |||
| // 说明是个列表,各个子对象是相同的name | |||
| List<Object> list = Lists.newArrayList(); | |||
| for (Node node : content) { | |||
| if (node instanceof DefaultText) { | |||
| continue; | |||
| } | |||
| if (node instanceof Element) { | |||
| list.add(element2MapOrString((Element) node)); | |||
| } | |||
| } | |||
| result.put(names.iterator().next(), list); | |||
| } else { | |||
| for (Node node : content) { | |||
| if (node instanceof DefaultText) { | |||
| continue; | |||
| } | |||
| if (node instanceof Element) { | |||
| result.put(node.getName(), element2MapOrString((Element) node)); | |||
| } | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| private static Set<String> names(List<Node> nodes) { | |||
| Set<String> names = Sets.newHashSet(); | |||
| for (Node node : nodes) { | |||
| if (node instanceof DefaultText) { | |||
| continue; | |||
| } | |||
| names.add(node.getName()); | |||
| } | |||
| return names; | |||
| } | |||
| } | |||
| @@ -1,22 +1,22 @@ | |||
| /** | |||
| /* | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import java.nio.charset.Charset; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.Arrays; | |||
| /** | |||
| * 提供基于PKCS7算法的加解 | |||
| * 提供基于PKCS7算法的加解. | |||
| * | |||
| * @author tencent | |||
| */ | |||
| public class PKCS7Encoder { | |||
| private static final Charset CHARSET = Charset.forName("utf-8"); | |||
| private static final Charset CHARSET = StandardCharsets.UTF_8; | |||
| private static final int BLOCK_SIZE = 32; | |||
| /** | |||
| @@ -28,20 +28,17 @@ public class PKCS7Encoder { | |||
| 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(); | |||
| StringBuilder tmp = new StringBuilder(); | |||
| for (int index = 0; index < amountToPad; index++) { | |||
| tmp += padChr; | |||
| tmp.append(padChr); | |||
| } | |||
| return tmp.getBytes(CHARSET); | |||
| return tmp.toString().getBytes(CHARSET); | |||
| } | |||
| /** | |||
| * 删除解密后明文的补位字符 | |||
| * 删除解密后明文的补位字符. | |||
| * | |||
| * @param decrypted 解密后的明文 | |||
| * @return 删除补位字符后的明文 | |||
| @@ -55,12 +52,12 @@ public class PKCS7Encoder { | |||
| } | |||
| /** | |||
| * 将数字转化成ASCII码对应的字符,用于对明文进行补码 | |||
| * 将数字转化成ASCII码对应的字符,用于对明文进行补码. | |||
| * | |||
| * @param a 需要转化的数字 | |||
| * @return 转化得到的字符 | |||
| */ | |||
| public static char chr(int a) { | |||
| private static char chr(int a) { | |||
| byte target = (byte) (a & 0xFF); | |||
| return (char) target; | |||
| } | |||
| @@ -1,21 +1,21 @@ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import org.apache.commons.codec.binary.Base64; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.xml.sax.InputSource; | |||
| import java.io.StringReader; | |||
| import java.nio.charset.Charset; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.Arrays; | |||
| import java.util.Random; | |||
| import javax.crypto.Cipher; | |||
| import javax.crypto.spec.IvParameterSpec; | |||
| import javax.crypto.spec.SecretKeySpec; | |||
| import javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import java.io.StringReader; | |||
| import java.nio.charset.Charset; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.Arrays; | |||
| import java.util.Random; | |||
| import org.apache.commons.codec.binary.Base64; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.xml.sax.InputSource; | |||
| /** | |||
| * <pre> | |||
| @@ -25,6 +25,8 @@ import java.util.Random; | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| * </pre> | |||
| * | |||
| * @author Tencent | |||
| */ | |||
| public class WxCryptUtil { | |||
| @@ -35,7 +37,9 @@ public class WxCryptUtil { | |||
| @Override | |||
| protected DocumentBuilder initialValue() { | |||
| try { | |||
| return DocumentBuilderFactory.newInstance().newDocumentBuilder(); | |||
| final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |||
| factory.setExpandEntityReferences(false); | |||
| return factory.newDocumentBuilder(); | |||
| } catch (ParserConfigurationException exc) { | |||
| throw new IllegalArgumentException(exc); | |||
| } | |||
| @@ -164,8 +168,7 @@ public class WxCryptUtil { | |||
| ByteGroup byteCollector = new ByteGroup(); | |||
| byte[] randomStringBytes = randomStr.getBytes(CHARSET); | |||
| byte[] plainTextBytes = plainText.getBytes(CHARSET); | |||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | |||
| plainTextBytes.length); | |||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(plainTextBytes.length); | |||
| byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET); | |||
| // randomStr + networkBytesOrder + text + appid | |||
| @@ -252,7 +255,7 @@ public class WxCryptUtil { | |||
| throw new RuntimeException(e); | |||
| } | |||
| String xmlContent, from_appid; | |||
| String xmlContent, fromAppid; | |||
| try { | |||
| // 去除补位字符 | |||
| byte[] bytes = PKCS7Encoder.decode(original); | |||
| @@ -264,14 +267,14 @@ public class WxCryptUtil { | |||
| xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | |||
| CHARSET); | |||
| from_appid = new String( | |||
| fromAppid = new String( | |||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| // appid不相同的情况 | |||
| if (!from_appid.equals(this.appidOrCorpid)) { | |||
| if (!fromAppid.equals(this.appidOrCorpid)) { | |||
| throw new RuntimeException("AppID不正确"); | |||
| } | |||
| @@ -1,13 +1,16 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheMediaDownloadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaDownloadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaDownloadRequestExecutor; | |||
| import java.io.File; | |||
| /** | |||
| * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File | |||
| * 下载媒体文件请求执行器. | |||
| * 请求的参数是String, 返回的结果是File | |||
| * 视频文件不支持下载 | |||
| * | |||
| * @author Daniel Qian | |||
| @@ -21,6 +24,11 @@ public abstract class BaseMediaDownloadRequestExecutor<H, P> implements RequestE | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| @Override | |||
| public void execute(String uri, String data, ResponseHandler<File> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| public static RequestExecutor<File, String> create(RequestHttp requestHttp, File tmpDirFile) { | |||
| switch (requestHttp.getRequestType()) { | |||
| case APACHE_HTTP: | |||
| @@ -58,7 +58,7 @@ public class HttpResponseProxy { | |||
| private String getFileName(CloseableHttpResponse response) throws WxErrorException { | |||
| Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); | |||
| if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").build()); | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); | |||
| } | |||
| return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue()); | |||
| @@ -76,7 +76,7 @@ public class HttpResponseProxy { | |||
| private String extractFileNameFromContentString(String content) throws WxErrorException { | |||
| if (content == null || content.length() == 0) { | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").build()); | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); | |||
| } | |||
| Matcher m = PATTERN.matcher(content); | |||
| @@ -84,7 +84,7 @@ public class HttpResponseProxy { | |||
| return m.group(1); | |||
| } | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").build()); | |||
| throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build()); | |||
| } | |||
| } | |||
| @@ -1,14 +1,17 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheMediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaUploadRequestExecutor; | |||
| import java.io.File; | |||
| /** | |||
| * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | |||
| * 上传媒体文件请求执行器. | |||
| * 请求的参数是File, 返回的结果是String | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| @@ -19,6 +22,11 @@ public abstract class MediaUploadRequestExecutor<H, P> implements RequestExecuto | |||
| this.requestHttp = requestHttp; | |||
| } | |||
| @Override | |||
| public void execute(String uri, File data, ResponseHandler<WxMediaUploadResult> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| public static RequestExecutor<WxMediaUploadResult, File> create(RequestHttp requestHttp) { | |||
| switch (requestHttp.getRequestType()) { | |||
| case APACHE_HTTP: | |||
| @@ -1,11 +1,11 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| /** | |||
| * http请求执行器 | |||
| * http请求执行器. | |||
| * | |||
| * @param <T> 返回值类型 | |||
| * @param <E> 请求参数类型 | |||
| @@ -14,8 +14,25 @@ import java.io.IOException; | |||
| public interface RequestExecutor<T, E> { | |||
| /** | |||
| * 执行http请求. | |||
| * | |||
| * @param uri uri | |||
| * @param data 数据 | |||
| * @return 响应结果 | |||
| * @throws WxErrorException 自定义异常 | |||
| * @throws IOException io异常 | |||
| */ | |||
| T execute(String uri, E data) throws WxErrorException, IOException; | |||
| /** | |||
| * 执行http请求. | |||
| * | |||
| * @param uri uri | |||
| * @param data 数据 | |||
| * @param handler http响应处理器 | |||
| * @throws WxErrorException 自定义异常 | |||
| * @throws IOException io异常 | |||
| */ | |||
| void execute(String uri, E data, ResponseHandler<T> handler) throws WxErrorException, IOException; | |||
| } | |||
| @@ -2,21 +2,30 @@ package me.chanjar.weixin.common.util.http; | |||
| /** | |||
| * Created by ecoolper on 2017/4/22. | |||
| * | |||
| * @author ecoolper | |||
| */ | |||
| public interface RequestHttp<H, P> { | |||
| /** | |||
| * 返回httpClient | |||
| * 返回httpClient. | |||
| * | |||
| * @return 返回httpClient | |||
| */ | |||
| H getRequestHttpClient(); | |||
| /** | |||
| * 返回httpProxy | |||
| * 返回httpProxy. | |||
| * | |||
| * @return 返回httpProxy | |||
| */ | |||
| P getRequestHttpProxy(); | |||
| /** | |||
| * 返回HttpType. | |||
| * | |||
| * @return HttpType | |||
| */ | |||
| HttpType getRequestType(); | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| /** | |||
| * <pre> | |||
| * http请求响应回调处理接口. | |||
| * Created by Binary Wang on 2018/12/8. | |||
| * </pre> | |||
| * | |||
| * @param <T> 返回值类型 | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public interface ResponseHandler<T> { | |||
| /** | |||
| * 响应结果处理. | |||
| * | |||
| * @param t 要处理的对象 | |||
| */ | |||
| void handle(T t); | |||
| } | |||
| @@ -1,11 +1,15 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientSimpleGetRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor; | |||
| /** | |||
| * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String | |||
| * 简单的GET请求执行器. | |||
| * 请求的参数是String, 返回的结果也是String | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| @@ -16,6 +20,11 @@ public abstract class SimpleGetRequestExecutor<H, P> implements RequestExecutor< | |||
| this.requestHttp = requestHttp; | |||
| } | |||
| @Override | |||
| public void execute(String uri, String data, ResponseHandler<String> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| public static RequestExecutor<String, String> create(RequestHttp requestHttp) { | |||
| switch (requestHttp.getRequestType()) { | |||
| case APACHE_HTTP: | |||
| @@ -1,5 +1,8 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor; | |||
| @@ -17,6 +20,11 @@ public abstract class SimplePostRequestExecutor<H, P> implements RequestExecutor | |||
| this.requestHttp = requestHttp; | |||
| } | |||
| @Override | |||
| public void execute(String uri, String data, ResponseHandler<String> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| public static RequestExecutor<String, String> create(RequestHttp requestHttp) { | |||
| switch (requestHttp.getRequestType()) { | |||
| case APACHE_HTTP: | |||
| @@ -1,9 +1,9 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.io.UnsupportedEncodingException; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| public class URIUtil { | |||
| @@ -16,27 +16,22 @@ public class URIUtil { | |||
| int l = input.length(); | |||
| StringBuilder o = new StringBuilder(l * 3); | |||
| try { | |||
| for (int i = 0; i < l; i++) { | |||
| String e = input.substring(i, i + 1); | |||
| if (ALLOWED_CHARS.indexOf(e) == -1) { | |||
| byte[] b = e.getBytes("utf-8"); | |||
| o.append(getHex(b)); | |||
| continue; | |||
| } | |||
| o.append(e); | |||
| for (int i = 0; i < l; i++) { | |||
| String e = input.substring(i, i + 1); | |||
| if (!ALLOWED_CHARS.contains(e)) { | |||
| byte[] b = e.getBytes(StandardCharsets.UTF_8); | |||
| o.append(getHex(b)); | |||
| continue; | |||
| } | |||
| return o.toString(); | |||
| } catch (UnsupportedEncodingException e) { | |||
| e.printStackTrace(); | |||
| o.append(e); | |||
| } | |||
| return input; | |||
| return o.toString(); | |||
| } | |||
| private static String getHex(byte buf[]) { | |||
| private static String getHex(byte[] buf) { | |||
| StringBuilder o = new StringBuilder(buf.length * 3); | |||
| for (int i = 0; i < buf.length; i++) { | |||
| int n = buf[i] & 0xff; | |||
| for (byte aBuf : buf) { | |||
| int n = aBuf & 0xff; | |||
| o.append("%"); | |||
| if (n < 0x10) { | |||
| o.append("0"); | |||
| @@ -1,16 +1,17 @@ | |||
| package me.chanjar.weixin.common.util.http.apache; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
| import java.io.IOException; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpGet; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import java.io.IOException; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
| /** | |||
| * Created by ecoolper on 2017/5/4. | |||
| @@ -1,5 +1,9 @@ | |||
| package me.chanjar.weixin.common.util.http.apache; | |||
| import java.io.IOException; | |||
| import java.util.concurrent.TimeUnit; | |||
| import java.util.concurrent.atomic.AtomicBoolean; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.annotation.NotThreadSafe; | |||
| @@ -25,10 +29,6 @@ import org.apache.http.protocol.HttpContext; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import java.io.IOException; | |||
| import java.util.concurrent.TimeUnit; | |||
| import java.util.concurrent.atomic.AtomicBoolean; | |||
| /** | |||
| * httpclient 连接管理器 自带DNS解析. | |||
| * <p>大部分代码拷贝自:DefaultApacheHttpClientBuilder</p> | |||
| @@ -292,6 +292,7 @@ public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder { | |||
| } | |||
| } | |||
| } catch (InterruptedException ignore) { | |||
| Thread.currentThread().interrupt(); | |||
| } | |||
| } | |||
| @@ -25,7 +25,6 @@ import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| * Created by ecoolper on 2017/5/5. | |||
| */ | |||
| public class ApacheMediaDownloadRequestExecutor extends BaseMediaDownloadRequestExecutor<CloseableHttpClient, HttpHost> { | |||
| public ApacheMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { | |||
| super(requestHttp, tmpDirFile); | |||
| } | |||
| @@ -1,5 +1,8 @@ | |||
| package me.chanjar.weixin.common.util.http.apache; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import org.apache.http.HttpEntity; | |||
| import org.apache.http.HttpResponse; | |||
| import org.apache.http.StatusLine; | |||
| @@ -7,18 +10,20 @@ 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; | |||
| /** | |||
| * 输入流响应处理器. | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class InputStreamResponseHandler implements ResponseHandler<InputStream> { | |||
| public static final ResponseHandler<InputStream> INSTANCE = new InputStreamResponseHandler(); | |||
| private static final int STATUS_CODE_300 = 300; | |||
| @Override | |||
| public InputStream handleResponse(final HttpResponse response) throws IOException { | |||
| final StatusLine statusLine = response.getStatusLine(); | |||
| final HttpEntity entity = response.getEntity(); | |||
| if (statusLine.getStatusCode() >= 300) { | |||
| if (statusLine.getStatusCode() >= STATUS_CODE_300) { | |||
| EntityUtils.consume(entity); | |||
| throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); | |||
| } | |||
| @@ -7,9 +7,13 @@ import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| /** | |||
| * . | |||
| * @author chanjarster | |||
| */ | |||
| public class WxGsonBuilder { | |||
| public static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
| private static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
| static { | |||
| INSTANCE.disableHtmlEscaping(); | |||
| @@ -8,47 +8,47 @@ import com.thoughtworks.xstream.core.util.QuickWriter; | |||
| import com.thoughtworks.xstream.io.HierarchicalStreamWriter; | |||
| import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; | |||
| import com.thoughtworks.xstream.io.xml.XppDriver; | |||
| import com.thoughtworks.xstream.security.NullPermission; | |||
| import com.thoughtworks.xstream.security.PrimitiveTypePermission; | |||
| public class XStreamInitializer { | |||
| public static XStream getInstance() { | |||
| XStream xstream = new XStream(new PureJavaReflectionProvider(), new XppDriver() { | |||
| @Override | |||
| public HierarchicalStreamWriter createWriter(Writer out) { | |||
| return new PrettyPrintWriter(out, getNameCoder()) { | |||
| protected String PREFIX_CDATA = "<![CDATA["; | |||
| 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(this.PREFIX_CDATA) && text.endsWith(this.SUFFIX_CDATA)) { | |||
| writer.write(text); | |||
| } else if (text.startsWith(this.PREFIX_MEDIA_ID) && text.endsWith(this.SUFFIX_MEDIA_ID)) { | |||
| writer.write(text); | |||
| } else { | |||
| super.writeText(writer, text); | |||
| } | |||
| private static final XppDriver XPP_DRIVER = new XppDriver() { | |||
| @Override | |||
| public HierarchicalStreamWriter createWriter(Writer out) { | |||
| return new PrettyPrintWriter(out, getNameCoder()) { | |||
| private static final String PREFIX_CDATA = "<![CDATA["; | |||
| private static final String SUFFIX_CDATA = "]]>"; | |||
| private static final String PREFIX_MEDIA_ID = "<MediaId>"; | |||
| private static final String SUFFIX_MEDIA_ID = "</MediaId>"; | |||
| @Override | |||
| protected void writeText(QuickWriter writer, String text) { | |||
| if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) { | |||
| writer.write(text); | |||
| } else if (text.startsWith(PREFIX_MEDIA_ID) && text.endsWith(SUFFIX_MEDIA_ID)) { | |||
| writer.write(text); | |||
| } else { | |||
| super.writeText(writer, text); | |||
| } | |||
| @Override | |||
| public String encodeNode(String name) { | |||
| //防止将_转换成__ | |||
| return name; | |||
| } | |||
| }; | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public String encodeNode(String name) { | |||
| //防止将_转换成__ | |||
| return name; | |||
| } | |||
| }; | |||
| } | |||
| }; | |||
| public static XStream getInstance() { | |||
| XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER); | |||
| xstream.ignoreUnknownElements(); | |||
| xstream.setMode(XStream.NO_REFERENCES); | |||
| xstream.addPermission(NullPermission.NULL); | |||
| xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); | |||
| XStream.setupDefaultSecurity(xstream); | |||
| xstream.allowTypesByWildcard(new String[]{ | |||
| "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" | |||
| }); | |||
| xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); | |||
| return xstream; | |||
| } | |||
| @@ -1,8 +1,7 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import org.testng.annotations.Test; | |||
| import org.testng.annotations.*; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| import static org.testng.Assert.*; | |||
| /** | |||
| @@ -18,6 +17,6 @@ public class DataUtilsTest { | |||
| public void testHandleDataWithSecret() { | |||
| String data = "js_code=001tZveq0SMoiq1AEXeq0ECJeq0tZveZ&secret=5681022fa1643845392367ea88888888&grant_type=authorization_code&appid=wxe156d4848d999999"; | |||
| final String s = DataUtils.handleDataWithSecret(data); | |||
| assertThat(s).contains("&secret=******&"); | |||
| assertTrue(s.contains("&secret=******&")); | |||
| } | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.testng.annotations.*; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| /** | |||
| * <pre> | |||
| * | |||
| * Created by Binary Wang on 2018/11/4. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class XmlUtilsTest { | |||
| @Test | |||
| public void testXml2Map() { | |||
| String xml = "<xml>\n" + | |||
| "<CopyrightCheckResult>\n" + | |||
| "<Count>2</Count>\n" + | |||
| "<ResultList>\n" + | |||
| "<item>\n" + | |||
| "<ArticleIdx>1</ArticleIdx>\n" + | |||
| "<UserDeclareState>0</UserDeclareState>\n" + | |||
| "<AuditState>2</AuditState>\n" + | |||
| "<OriginalArticleUrl><![CDATA[Url_1]]></OriginalArticleUrl>\n" + | |||
| "<OriginalArticleType>1</OriginalArticleType>\n" + | |||
| "<CanReprint>1</CanReprint>\n" + | |||
| "<NeedReplaceContent>1</NeedReplaceContent>\n" + | |||
| "<NeedShowReprintSource>1</NeedShowReprintSource>\n" + | |||
| "</item>\n" + | |||
| "<item>\n" + | |||
| "<ArticleIdx>2</ArticleIdx>\n" + | |||
| "<UserDeclareState>0</UserDeclareState>\n" + | |||
| "<AuditState>2</AuditState>\n" + | |||
| "<OriginalArticleUrl><![CDATA[Url_2]]></OriginalArticleUrl>\n" + | |||
| "<OriginalArticleType>1</OriginalArticleType>\n" + | |||
| "<CanReprint>1</CanReprint>\n" + | |||
| "<NeedReplaceContent>1</NeedReplaceContent>\n" + | |||
| "<NeedShowReprintSource>1</NeedShowReprintSource>\n" + | |||
| "</item>\n" + | |||
| "</ResultList>\n" + | |||
| "<CheckState>2</CheckState>\n" + | |||
| "</CopyrightCheckResult>\n" + | |||
| "</xml>"; | |||
| final Map<String, Object> map = XmlUtils.xml2Map(xml); | |||
| assertThat(map).isNotNull(); | |||
| final Map<String, Object> copyrightCheckResult = (Map<String, Object>) map.get("CopyrightCheckResult"); | |||
| List<Map<String, Object>> resultList = (List<Map<String, Object>>) ((Map<String, Object>) copyrightCheckResult.get("ResultList")).get("item"); | |||
| assertThat(copyrightCheckResult).isNotNull(); | |||
| assertThat(copyrightCheckResult.get("Count")).isEqualTo("2"); | |||
| assertThat(copyrightCheckResult.get("CheckState")).isEqualTo("2"); | |||
| assertThat(resultList.get(0).get("ArticleIdx")).isEqualTo("1"); | |||
| assertThat(resultList.get(0).get("UserDeclareState")).isEqualTo("0"); | |||
| assertThat(resultList.get(0).get("AuditState")).isEqualTo("2"); | |||
| assertThat(resultList.get(0).get("OriginalArticleUrl")).isEqualTo("Url_1"); | |||
| assertThat(resultList.get(0).get("OriginalArticleType")).isEqualTo("1"); | |||
| assertThat(resultList.get(0).get("CanReprint")).isEqualTo("1"); | |||
| assertThat(resultList.get(0).get("NeedReplaceContent")).isEqualTo("1"); | |||
| assertThat(resultList.get(0).get("NeedShowReprintSource")).isEqualTo("1"); | |||
| assertThat(resultList.get(1).get("ArticleIdx")).isEqualTo("2"); | |||
| assertThat(resultList.get(1).get("UserDeclareState")).isEqualTo("0"); | |||
| assertThat(resultList.get(1).get("AuditState")).isEqualTo("2"); | |||
| assertThat(resultList.get(1).get("OriginalArticleUrl")).isEqualTo("Url_2"); | |||
| assertThat(resultList.get(1).get("OriginalArticleType")).isEqualTo("1"); | |||
| assertThat(resultList.get(1).get("CanReprint")).isEqualTo("1"); | |||
| assertThat(resultList.get(1).get("NeedReplaceContent")).isEqualTo("1"); | |||
| assertThat(resultList.get(1).get("NeedShowReprintSource")).isEqualTo("1"); | |||
| } | |||
| } | |||
| @@ -1,5 +1,11 @@ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import java.io.IOException; | |||
| import java.io.StringReader; | |||
| import javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import org.testng.annotations.*; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| @@ -7,12 +13,6 @@ import org.w3c.dom.NodeList; | |||
| import org.xml.sax.InputSource; | |||
| import org.xml.sax.SAXException; | |||
| import javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import java.io.IOException; | |||
| import java.io.StringReader; | |||
| import static org.testng.Assert.*; | |||
| @Test | |||
| @@ -39,6 +39,7 @@ public class WxCryptUtilTest { | |||
| System.out.println(encryptedXml); | |||
| DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||
| documentBuilderFactory.setExpandEntityReferences(false); | |||
| DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||
| Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | |||
| @@ -81,6 +82,7 @@ public class WxCryptUtilTest { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| String afterEncrpt = pc.encrypt(this.replyMsg); | |||
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |||
| dbf.setExpandEntityReferences(false); | |||
| DocumentBuilder db = dbf.newDocumentBuilder(); | |||
| StringReader sr = new StringReader(afterEncrpt); | |||
| InputSource is = new InputSource(sr); | |||
| @@ -6,13 +6,13 @@ | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>3.2.0</version> | |||
| <artifactId>wx-java</artifactId> | |||
| <version>3.3.0</version> | |||
| </parent> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| <name>Weixin Java Tools - CP</name> | |||
| <description>微信企业号Java SDK</description> | |||
| <name>WxJava - CP</name> | |||
| <description>微信企业号/企业微信 Java SDK</description> | |||
| <dependencies> | |||
| <dependency> | |||
| @@ -0,0 +1,48 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||
| /** | |||
| * 群聊服务 | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public interface WxCpChatService { | |||
| /** | |||
| * 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群。 | |||
| * | |||
| * @param name 群聊名,最多50个utf8字符,超过将截断 | |||
| * @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主 | |||
| * @param users 群成员id列表。至少2人,至多500人 | |||
| * @param chatId 群聊的唯一标志,不能与已有的群重复;字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id | |||
| * @return 创建群聊会话的结果,群聊的唯一标志 | |||
| * @throws WxErrorException 发生异常 | |||
| */ | |||
| String chatCreate(String name, String owner, List<String> users, String chatId) throws WxErrorException; | |||
| /** | |||
| * 修改群聊会话 | |||
| * | |||
| * @param chatId 群聊id | |||
| * @param name 新的群聊名。若不需更新,请忽略此参数(null or empty)。最多50个utf8字符,超过将截断 | |||
| * @param owner 新群主的id。若不需更新,请忽略此参数(null or empty) | |||
| * @param usersToAdd 添加成员的id列表,若不需要更新,则传递空对象或者空集合 | |||
| * @param usersToDelete 踢出成员的id列表,若不需要更新,则传递空对象或者空集合 | |||
| * @throws WxErrorException 发生异常 | |||
| */ | |||
| void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException; | |||
| /** | |||
| * 获取群聊会话 | |||
| * | |||
| * @param chatId 群聊编号 | |||
| * @return 群聊会话 | |||
| * @throws WxErrorException 发生异常 | |||
| */ | |||
| WxCpChat chatGet(String chatId) throws WxErrorException; | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 | |||
| @@ -17,42 +17,47 @@ public interface WxCpDepartmentService { | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 创建部门 | |||
| * 部门管理接口 - 创建部门. | |||
| * 最多支持创建500个部门 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||
| * </pre> | |||
| * | |||
| * @param depart 部门 | |||
| * @return 部门id | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| Integer create(WxCpDepart depart) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 查询部门 | |||
| * 部门管理接口 - 查询部门. | |||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E9%83%A8%E9%97%A8#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E5.88.97.E8.A1.A8 | |||
| * </pre> | |||
| * | |||
| * @param id 部门id。获取指定部门及其下的子部门。非必需,可为null | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| List<WxCpDepart> list(Integer id) throws WxErrorException; | |||
| List<WxCpDepart> list(Long id) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 修改部门名 | |||
| * 部门管理接口 - 修改部门名. | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||
| * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误 | |||
| * </pre> | |||
| * | |||
| * @param group 要更新的group,group的id,name必须设置 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| void update(WxCpDepart group) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 删除部门 | |||
| * 部门管理接口 - 删除部门. | |||
| * </pre> | |||
| * | |||
| * @param departId 部门id | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| void delete(Integer departId) throws WxErrorException; | |||
| void delete(Long departId) throws WxErrorException; | |||
| } | |||
| @@ -19,6 +19,7 @@ public interface WxCpMediaService { | |||
| String MEDIA_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||
| String MEDIA_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type="; | |||
| String IMG_UPLOAD_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg"; | |||
| String JSSDK_MEDIA_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk"; | |||
| /** | |||
| * <pre> | |||
| @@ -59,6 +60,21 @@ public interface WxCpMediaService { | |||
| */ | |||
| File download(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取高清语音素材. | |||
| * 可以使用本接口获取从JSSDK的uploadVoice接口上传的临时语音素材,格式为speex,16K采样率。该音频比上文的临时素材获取接口(格式为amr,8K采样率)更加清晰,适合用作语音识别等对音质要求较高的业务。 | |||
| * 请求方式:GET(HTTPS) | |||
| * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=ACCESS_TOKEN&media_id=MEDIA_ID | |||
| * 仅企业微信2.4及以上版本支持。 | |||
| * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90255 | |||
| * </pre> | |||
| * | |||
| * @param mediaId 媒体id | |||
| * @return 保存到本地的临时文件 | |||
| */ | |||
| File getJssdkFile(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 上传图片. | |||
| @@ -69,7 +85,7 @@ public interface WxCpMediaService { | |||
| * </pre> | |||
| * | |||
| * @param file 上传的文件对象 | |||
| * @return 返回图片url | |||
| * @return 返回图片url | |||
| */ | |||
| String uploadImg(File file) throws WxErrorException; | |||
| } | |||
| @@ -7,7 +7,8 @@ import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.cp.bean.*; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| /** | |||
| @@ -241,13 +242,20 @@ public interface WxCpService { | |||
| * 获取用户相关接口的服务类对象 | |||
| */ | |||
| WxCpUserService getUserService(); | |||
| /** | |||
| * 获取群聊服务 | |||
| * | |||
| * @return 群聊服务 | |||
| */ | |||
| WxCpChatService getChatService(); | |||
| WxCpAgentService getAgentService(); | |||
| /** | |||
| * http请求对象 | |||
| */ | |||
| RequestHttp getRequestHttp(); | |||
| RequestHttp<?, ?> getRequestHttp(); | |||
| void setUserService(WxCpUserService userService); | |||
| @@ -38,7 +38,7 @@ public interface WxCpUserService { | |||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||
| */ | |||
| List<WxCpUser> listByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
| List<WxCpUser> listByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -51,7 +51,7 @@ public interface WxCpUserService { | |||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||
| */ | |||
| List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
| List<WxCpUser> listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||
| /** | |||
| * 新建用户. | |||
| @@ -1,9 +1,11 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import com.google.gson.Gson; | |||
| import java.util.List; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.cp.api.WxCpAgentService; | |||
| @@ -11,8 +13,6 @@ import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpAgent; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| @@ -0,0 +1,83 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import com.google.gson.JsonParser; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| import me.chanjar.weixin.cp.api.WxCpChatService; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| /** | |||
| * 群聊服务实现 | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public class WxCpChatServiceImpl implements WxCpChatService { | |||
| private final WxCpService internalService; | |||
| /** | |||
| * 创建群聊服务实现的实例 | |||
| * | |||
| * @param internalService 企业微信的服务 | |||
| */ | |||
| public WxCpChatServiceImpl(WxCpService internalService) { | |||
| this.internalService = internalService; | |||
| } | |||
| @Override | |||
| public String chatCreate(String name, String owner, List<String> users, String chatId) throws WxErrorException { | |||
| Map<String, Object> data = new HashMap<>(4); | |||
| if (StringUtils.isNotBlank(name)) { | |||
| data.put("name", name); | |||
| } | |||
| if (StringUtils.isNotBlank(owner)) { | |||
| data.put("owner", owner); | |||
| } | |||
| if (users != null) { | |||
| data.put("userlist", users); | |||
| } | |||
| if (StringUtils.isNotBlank(chatId)) { | |||
| data.put("chatid", chatId); | |||
| } | |||
| String result = internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/create", WxGsonBuilder.create().toJson(data)); | |||
| return new JsonParser().parse(result).getAsJsonObject().get("chatid").getAsString(); | |||
| } | |||
| @Override | |||
| public void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException { | |||
| Map<String, Object> data = new HashMap<>(5); | |||
| if (StringUtils.isNotBlank(chatId)) { | |||
| data.put("chatid", chatId); | |||
| } | |||
| if (StringUtils.isNotBlank(name)) { | |||
| data.put("name", name); | |||
| } | |||
| if (StringUtils.isNotBlank(owner)) { | |||
| data.put("owner", owner); | |||
| } | |||
| if (usersToAdd != null && !usersToAdd.isEmpty()) { | |||
| data.put("add_user_list", usersToAdd); | |||
| } | |||
| if (usersToDelete != null && !usersToDelete.isEmpty()) { | |||
| data.put("del_user_list", usersToDelete); | |||
| } | |||
| internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/update", WxGsonBuilder.create().toJson(data)); | |||
| } | |||
| @Override | |||
| public WxCpChat chatGet(String chatId) throws WxErrorException { | |||
| String result = internalService.get("https://qyapi.weixin.qq.com/cgi-bin/appchat/get?chatid=" + chatId, null); | |||
| return WxCpGsonBuilder.create().fromJson( | |||
| new JsonParser().parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(), WxCpChat.class); | |||
| } | |||
| } | |||
| @@ -1,5 +1,7 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.util.List; | |||
| import com.google.gson.JsonElement; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| @@ -10,8 +12,6 @@ import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 | |||
| @@ -42,13 +42,13 @@ public class WxCpDepartmentServiceImpl implements WxCpDepartmentService { | |||
| } | |||
| @Override | |||
| public void delete(Integer departId) throws WxErrorException { | |||
| public void delete(Long departId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId; | |||
| this.mainService.get(url, null); | |||
| } | |||
| @Override | |||
| public List<WxCpDepart> list(Integer id) throws WxErrorException { | |||
| public List<WxCpDepart> list(Long id) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list"; | |||
| if (id != null) { | |||
| url += "?id=" + id; | |||
| @@ -56,7 +56,7 @@ public class WxCpDepartmentServiceImpl implements WxCpDepartmentService { | |||
| String responseContent = this.mainService.get(url, null); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| return WxCpGsonBuilder.create() | |||
| .fromJson(tmpJsonElement.getAsJsonObject().get("department"), | |||
| new TypeToken<List<WxCpDepart>>() { | |||
| }.getType() | |||
| @@ -48,6 +48,14 @@ public class WxCpMediaServiceImpl implements WxCpMediaService { | |||
| MEDIA_GET_URL, "media_id=" + mediaId); | |||
| } | |||
| @Override | |||
| public File getJssdkFile(String mediaId) throws WxErrorException { | |||
| return this.mainService.execute( | |||
| BaseMediaDownloadRequestExecutor.create(this.mainService.getRequestHttp(), | |||
| this.mainService.getWxCpConfigStorage().getTmpDirFile()), | |||
| JSSDK_MEDIA_GET_URL, "media_id=" + mediaId); | |||
| } | |||
| @Override | |||
| public String uploadImg(File file) throws WxErrorException { | |||
| final WxMediaUploadResult result = this.mainService | |||
| @@ -1,6 +1,5 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import com.google.gson.GsonBuilder; | |||
| import com.google.gson.JsonElement; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| @@ -11,6 +10,7 @@ import me.chanjar.weixin.common.util.json.GsonHelper; | |||
| import me.chanjar.weixin.cp.api.WxCpOAuth2Service; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpUserDetail; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| /** | |||
| * <pre> | |||
| @@ -87,6 +87,6 @@ public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service { | |||
| JsonObject param = new JsonObject(); | |||
| param.addProperty("user_ticket", userTicket); | |||
| String responseText = this.mainService.post(url, param.toString()); | |||
| return new GsonBuilder().create().fromJson(responseText, WxCpUserDetail.class); | |||
| return WxCpGsonBuilder.create().fromJson(responseText, WxCpUserDetail.class); | |||
| } | |||
| } | |||
| @@ -1,9 +1,16 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import com.google.gson.JsonArray; | |||
| import com.google.gson.JsonElement; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| @@ -17,19 +24,24 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||
| import me.chanjar.weixin.cp.api.*; | |||
| import me.chanjar.weixin.cp.bean.*; | |||
| import me.chanjar.weixin.cp.api.WxCpAgentService; | |||
| import me.chanjar.weixin.cp.api.WxCpChatService; | |||
| import me.chanjar.weixin.cp.api.WxCpDepartmentService; | |||
| import me.chanjar.weixin.cp.api.WxCpMediaService; | |||
| import me.chanjar.weixin.cp.api.WxCpMenuService; | |||
| import me.chanjar.weixin.cp.api.WxCpOAuth2Service; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.api.WxCpTagService; | |||
| import me.chanjar.weixin.cp.api.WxCpUserService; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, RequestHttp<H, P> { | |||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
| private WxCpUserService userService = new WxCpUserServiceImpl(this); | |||
| private WxCpChatService chatService = new WxCpChatServiceImpl(this); | |||
| private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this); | |||
| private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this); | |||
| private WxCpMenuService menuService = new WxCpMenuServiceImpl(this); | |||
| @@ -184,7 +196,7 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
| this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||
| Thread.sleep(sleepMillis); | |||
| } catch (InterruptedException e1) { | |||
| throw new RuntimeException(e1); | |||
| Thread.currentThread().interrupt(); | |||
| } | |||
| } else { | |||
| throw e; | |||
| @@ -334,7 +346,12 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
| } | |||
| @Override | |||
| public RequestHttp getRequestHttp() { | |||
| public WxCpChatService getChatService() { | |||
| return chatService; | |||
| } | |||
| @Override | |||
| public RequestHttp<?, ?> getRequestHttp() { | |||
| return this; | |||
| } | |||
| @@ -1,6 +1,12 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import com.google.gson.*; | |||
| import java.util.List; | |||
| import com.google.gson.JsonArray; | |||
| import com.google.gson.JsonElement; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.JsonPrimitive; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| @@ -11,8 +17,6 @@ import me.chanjar.weixin.cp.bean.WxCpTagGetResult; | |||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 标签管理接口 | |||
| @@ -57,7 +61,7 @@ public class WxCpTagServiceImpl implements WxCpTagService { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list"; | |||
| String responseContent = this.mainService.get(url, null); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| return WxCpGsonBuilder.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||
| new TypeToken<List<WxCpTag>>() { | |||
| @@ -70,7 +74,7 @@ public class WxCpTagServiceImpl implements WxCpTagService { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId; | |||
| String responseContent = this.mainService.get(url, null); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| return WxCpGsonBuilder.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| @@ -76,7 +76,7 @@ public class WxCpUserServiceImpl implements WxCpUserService { | |||
| } | |||
| @Override | |||
| public List<WxCpUser> listByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||
| public List<WxCpUser> listByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/list?department_id=" + departId; | |||
| String params = ""; | |||
| if (fetchChild != null) { | |||
| @@ -90,7 +90,7 @@ public class WxCpUserServiceImpl implements WxCpUserService { | |||
| String responseContent = this.mainService.get(url, params); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| return WxCpGsonBuilder.create() | |||
| .fromJson(tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| @@ -98,7 +98,7 @@ public class WxCpUserServiceImpl implements WxCpUserService { | |||
| } | |||
| @Override | |||
| public List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||
| public List<WxCpUser> listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?department_id=" + departId; | |||
| String params = ""; | |||
| if (fetchChild != null) { | |||
| @@ -112,7 +112,7 @@ public class WxCpUserServiceImpl implements WxCpUserService { | |||
| String responseContent = this.mainService.get(url, params); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| return WxCpGsonBuilder.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| @@ -1,5 +1,8 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Builder; | |||
| @@ -7,9 +10,6 @@ import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 企业号应用信息. | |||
| @@ -80,12 +80,14 @@ public class WxCpAgent implements Serializable { | |||
| @Data | |||
| public static class Users implements Serializable { | |||
| private static final long serialVersionUID = 8801100463558788565L; | |||
| @SerializedName("user") | |||
| private List<User> users; | |||
| } | |||
| @Data | |||
| public class User implements Serializable { | |||
| private static final long serialVersionUID = 7287632514385508024L; | |||
| @SerializedName("userid") | |||
| private String userId; | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.util.List; | |||
| import lombok.Data; | |||
| /** | |||
| * 群聊 | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| @Data | |||
| public class WxCpChat { | |||
| private String id; | |||
| private String name; | |||
| private String owner; | |||
| private List<String> users; | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 微信部门. | |||
| * | |||
| @@ -14,9 +14,9 @@ import java.io.Serializable; | |||
| public class WxCpDepart implements Serializable { | |||
| private static final long serialVersionUID = -5028321625140879571L; | |||
| private Integer id; | |||
| private Long id; | |||
| private String name; | |||
| private Integer parentId; | |||
| private Long parentId; | |||
| private Long order; | |||
| public static WxCpDepart fromJson(String json) { | |||
| @@ -5,8 +5,6 @@ import java.util.Collections; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.common.base.Splitter; | |||
| import com.google.gson.annotations.SerializedName; | |||
| @@ -25,11 +23,11 @@ public class WxCpInviteResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| public static WxCpInviteResult fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpInviteResult.class); | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpInviteResult.class); | |||
| } | |||
| @SerializedName("errcode") | |||
| @@ -1,16 +1,23 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.api.WxConsts; | |||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | |||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.*; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.FileBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.ImageBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.MpnewsBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.NewsBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.TextBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.TextCardBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.VideoBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.VoiceBuilder; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| /** | |||
| * 消息. | |||
| * | |||
| @@ -114,7 +121,7 @@ public class WxCpMessage implements Serializable { | |||
| } | |||
| public String toJson() { | |||
| return WxCpGsonBuilder.INSTANCE.create().toJson(this); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -5,8 +5,6 @@ import java.util.Collections; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.common.base.Splitter; | |||
| import com.google.gson.annotations.SerializedName; | |||
| @@ -25,11 +23,11 @@ public class WxCpMessageSendResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| public static WxCpMessageSendResult fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpMessageSendResult.class); | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpMessageSendResult.class); | |||
| } | |||
| @SerializedName("errcode") | |||
| @@ -1,14 +1,15 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Data; | |||
| import lombok.NoArgsConstructor; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.Serializable; | |||
| /** | |||
| * Created by Daniel Qian. | |||
| * @author Daniel Qian | |||
| */ | |||
| @Data | |||
| @AllArgsConstructor | |||
| @@ -5,8 +5,6 @@ import java.util.Collections; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.common.base.Splitter; | |||
| import com.google.gson.annotations.SerializedName; | |||
| @@ -25,11 +23,11 @@ public class WxCpTagAddOrRemoveUsersResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| public static WxCpTagAddOrRemoveUsersResult fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpTagAddOrRemoveUsersResult.class); | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpTagAddOrRemoveUsersResult.class); | |||
| } | |||
| @SerializedName("errcode") | |||
| @@ -50,11 +50,11 @@ public class WxCpUser implements Serializable { | |||
| } | |||
| public static WxCpUser fromJson(String json) { | |||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpUser.class); | |||
| } | |||
| public String toJson() { | |||
| return WxCpGsonBuilder.INSTANCE.create().toJson(this); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @Data | |||
| @@ -3,12 +3,11 @@ package me.chanjar.weixin.cp.bean; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.Serializable; | |||
| import java.nio.charset.StandardCharsets; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.io.IOUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -18,6 +17,7 @@ import me.chanjar.weixin.common.api.WxConsts; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
| /** | |||
| @@ -370,7 +370,7 @@ public class WxCpXmlMessage implements Serializable { | |||
| 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); | |||
| return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxCpConfigStorage, timestamp, nonce, msgSignature); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| @@ -378,7 +378,7 @@ public class WxCpXmlMessage implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @Data | |||
| @@ -2,11 +2,9 @@ package me.chanjar.weixin.cp.config; | |||
| import java.io.File; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| /** | |||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
| @@ -203,7 +201,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @Override | |||
| @@ -1,5 +1,18 @@ | |||
| package me.chanjar.weixin.cp.message; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ExecutionException; | |||
| import java.util.concurrent.ExecutorService; | |||
| import java.util.concurrent.Executors; | |||
| import java.util.concurrent.Future; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
| @@ -11,18 +24,6 @@ import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ExecutionException; | |||
| import java.util.concurrent.ExecutorService; | |||
| import java.util.concurrent.Executors; | |||
| import java.util.concurrent.Future; | |||
| /** | |||
| * <pre> | |||
| @@ -195,6 +196,7 @@ public class WxCpMessageRouter { | |||
| sessionEndAccess(wxMessage); | |||
| } catch (InterruptedException e) { | |||
| WxCpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| Thread.currentThread().interrupt(); | |||
| } catch (ExecutionException e) { | |||
| WxCpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| } | |||
| @@ -207,12 +209,11 @@ public class WxCpMessageRouter { | |||
| /** | |||
| * 处理微信消息 | |||
| * 处理微信消息. | |||
| * | |||
| * @param wxMessage | |||
| */ | |||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | |||
| return this.route(wxMessage, new HashMap<String, Object>()); | |||
| return this.route(wxMessage, new HashMap<String, Object>(2)); | |||
| } | |||
| protected boolean isDuplicateMessage(WxCpXmlMessage wxMessage) { | |||
| @@ -0,0 +1,77 @@ | |||
| /* | |||
| * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||
| * | |||
| * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||
| * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||
| * arose from modification of the original source, or other redistribution of this source | |||
| * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
| */ | |||
| package me.chanjar.weixin.cp.util.json; | |||
| import java.lang.reflect.Type; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import com.google.gson.JsonArray; | |||
| import com.google.gson.JsonDeserializationContext; | |||
| import com.google.gson.JsonDeserializer; | |||
| import com.google.gson.JsonElement; | |||
| 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.util.json.GsonHelper; | |||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||
| /** | |||
| * 群聊适配器 | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public class WxCpChatGsonAdapter implements JsonSerializer<WxCpChat>, JsonDeserializer<WxCpChat> { | |||
| @Override | |||
| public JsonElement serialize(WxCpChat chat, Type typeOfSrc, JsonSerializationContext context) { | |||
| JsonObject json = new JsonObject(); | |||
| if (chat.getId() != null) { | |||
| json.addProperty("chatid", chat.getId()); | |||
| } | |||
| if (chat.getName() != null) { | |||
| json.addProperty("name", chat.getName()); | |||
| } | |||
| if (chat.getOwner() != null) { | |||
| json.addProperty("owner", chat.getOwner()); | |||
| } | |||
| if (chat.getUsers() != null) { | |||
| JsonArray users = new JsonArray(); | |||
| for (String user : chat.getUsers()) { | |||
| users.add(user); | |||
| } | |||
| json.add("userlist", users); | |||
| } | |||
| return json; | |||
| } | |||
| @Override | |||
| public WxCpChat deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
| JsonObject chatJson = json.getAsJsonObject(); | |||
| WxCpChat chat = new WxCpChat(); | |||
| chat.setId(GsonHelper.getAsString(chatJson.get("chatid"))); | |||
| chat.setName(GsonHelper.getAsString(chatJson.get("name"))); | |||
| chat.setOwner(GsonHelper.getAsString(chatJson.get("owner"))); | |||
| JsonArray usersJson = chatJson.getAsJsonArray("userlist"); | |||
| if (usersJson != null) { | |||
| List<String> users = new ArrayList<>(usersJson.size()); | |||
| chat.setUsers(users); | |||
| for (JsonElement userJson : usersJson) { | |||
| users.add(userJson.getAsString()); | |||
| } | |||
| } | |||
| return chat; | |||
| } | |||
| } | |||
| @@ -8,31 +8,43 @@ | |||
| */ | |||
| package me.chanjar.weixin.cp.util.json; | |||
| import com.google.gson.*; | |||
| import java.lang.reflect.Type; | |||
| import com.google.gson.JsonDeserializationContext; | |||
| import com.google.gson.JsonDeserializer; | |||
| import com.google.gson.JsonElement; | |||
| 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.util.json.GsonHelper; | |||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * WxCpDepart的gson适配器. | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxCpDepartGsonAdapter implements JsonSerializer<WxCpDepart>, JsonDeserializer<WxCpDepart> { | |||
| private static final String ID = "id"; | |||
| private static final String NAME = "name"; | |||
| private static final String PARENT_ID = "parentid"; | |||
| private static final String ORDER = "order"; | |||
| @Override | |||
| public JsonElement serialize(WxCpDepart group, Type typeOfSrc, JsonSerializationContext context) { | |||
| JsonObject json = new JsonObject(); | |||
| if (group.getId() != null) { | |||
| json.addProperty("id", group.getId()); | |||
| json.addProperty(ID, group.getId()); | |||
| } | |||
| if (group.getName() != null) { | |||
| json.addProperty("name", group.getName()); | |||
| json.addProperty(NAME, group.getName()); | |||
| } | |||
| if (group.getParentId() != null) { | |||
| json.addProperty("parentid", group.getParentId()); | |||
| json.addProperty(PARENT_ID, group.getParentId()); | |||
| } | |||
| if (group.getOrder() != null) { | |||
| json.addProperty("order", String.valueOf(group.getOrder())); | |||
| json.addProperty(ORDER, String.valueOf(group.getOrder())); | |||
| } | |||
| return json; | |||
| } | |||
| @@ -42,17 +54,17 @@ public class WxCpDepartGsonAdapter implements JsonSerializer<WxCpDepart>, JsonDe | |||
| throws JsonParseException { | |||
| WxCpDepart depart = new WxCpDepart(); | |||
| JsonObject departJson = json.getAsJsonObject(); | |||
| if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) { | |||
| depart.setId(GsonHelper.getAsInteger(departJson.get("id"))); | |||
| if (departJson.get(ID) != null && !departJson.get(ID).isJsonNull()) { | |||
| depart.setId(GsonHelper.getAsLong(departJson.get(ID))); | |||
| } | |||
| if (departJson.get("name") != null && !departJson.get("name").isJsonNull()) { | |||
| depart.setName(GsonHelper.getAsString(departJson.get("name"))); | |||
| if (departJson.get(NAME) != null && !departJson.get(NAME).isJsonNull()) { | |||
| depart.setName(GsonHelper.getAsString(departJson.get(NAME))); | |||
| } | |||
| if (departJson.get("order") != null && !departJson.get("order").isJsonNull()) { | |||
| depart.setOrder(GsonHelper.getAsLong(departJson.get("order"))); | |||
| if (departJson.get(ORDER) != null && !departJson.get(ORDER).isJsonNull()) { | |||
| depart.setOrder(GsonHelper.getAsLong(departJson.get(ORDER))); | |||
| } | |||
| if (departJson.get("parentid") != null && !departJson.get("parentid").isJsonNull()) { | |||
| depart.setParentId(GsonHelper.getAsInteger(departJson.get("parentid"))); | |||
| if (departJson.get(PARENT_ID) != null && !departJson.get(PARENT_ID).isJsonNull()) { | |||
| depart.setParentId(GsonHelper.getAsLong(departJson.get(PARENT_ID))); | |||
| } | |||
| return depart; | |||
| } | |||
| @@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder; | |||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.util.json.WxErrorAdapter; | |||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||
| @@ -15,11 +16,12 @@ import me.chanjar.weixin.cp.bean.WxCpUser; | |||
| */ | |||
| public class WxCpGsonBuilder { | |||
| public static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
| private static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
| static { | |||
| INSTANCE.disableHtmlEscaping(); | |||
| INSTANCE.registerTypeAdapter(WxCpMessage.class, new WxCpMessageGsonAdapter()); | |||
| INSTANCE.registerTypeAdapter(WxCpChat.class, new WxCpChatGsonAdapter()); | |||
| INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | |||
| INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); | |||
| INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); | |||
| @@ -0,0 +1,46 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.util.Arrays; | |||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||
| import org.testng.Assert; | |||
| import org.testng.annotations.Guice; | |||
| import org.testng.annotations.Test; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.cp.api.ApiTestModule; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| /** | |||
| * 测试群聊服务 | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| @Guice(modules = ApiTestModule.class) | |||
| public class WxCpChatServiceImplTest { | |||
| @Inject | |||
| private WxCpService wxCpService; | |||
| @Test | |||
| public void create() throws Exception { | |||
| wxCpService.getChatService().chatCreate("测试群聊", "gaige_shen", Arrays.asList("gaige_shen", "ZhangXiaoMing"), "mychatid"); | |||
| } | |||
| @Test | |||
| public void get() throws Exception { | |||
| WxCpChat chat = wxCpService.getChatService().chatGet("mychatid"); | |||
| System.out.println(chat); | |||
| Assert.assertEquals(chat.getName(), "测试群聊"); | |||
| } | |||
| @Test | |||
| public void update() throws Exception { | |||
| wxCpService.getChatService().chatUpdate("mychatid", "", "", Arrays.asList("ZhengWuYao"), null); | |||
| WxCpChat chat = wxCpService.getChatService().chatGet("mychatid"); | |||
| System.out.println(chat); | |||
| Assert.assertEquals(chat.getUsers().size(), 3); | |||
| } | |||
| } | |||
| @@ -1,15 +1,15 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.util.List; | |||
| import org.testng.annotations.*; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.cp.api.ApiTestModule; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
| import org.testng.annotations.*; | |||
| import java.util.List; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| import static org.testng.Assert.*; | |||
| /** | |||
| * <pre> | |||
| @@ -29,7 +29,7 @@ public class WxCpDepartmentServiceImplTest { | |||
| public void testCreate() throws Exception { | |||
| WxCpDepart cpDepart = new WxCpDepart(); | |||
| cpDepart.setName("子部门" + System.currentTimeMillis()); | |||
| cpDepart.setParentId(1); | |||
| cpDepart.setParentId(1L); | |||
| cpDepart.setOrder(1L); | |||
| Integer departId = this.wxCpService.getDepartmentService().create(cpDepart); | |||
| System.out.println(departId); | |||
| @@ -45,7 +45,7 @@ public class WxCpDepartmentServiceImplTest { | |||
| } | |||
| @Test(dataProvider = "departIds") | |||
| public void testList(Integer id) throws Exception { | |||
| public void testList(Long id) throws Exception { | |||
| System.out.println("=================获取部门"); | |||
| List<WxCpDepart> departList = this.wxCpService.getDepartmentService().list(id); | |||
| assertThat(departList).isNotEmpty(); | |||
| @@ -36,7 +36,8 @@ public class WxCpMediaServiceImplTest { | |||
| public Object[][] mediaData() { | |||
| return new Object[][]{ | |||
| new Object[]{WxConsts.MediaFileType.IMAGE, TestConstants.FILE_JPG, "mm.jpeg"}, | |||
| new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"},//{"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"} | |||
| //new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_MP3, "mm.mp3"}, | |||
| // {"errcode":301017,"errmsg":"voice file only support amr like myvoice.amr"} | |||
| new Object[]{WxConsts.MediaFileType.VOICE, TestConstants.FILE_AMR, "mm.amr"}, | |||
| new Object[]{WxConsts.MediaFileType.VIDEO, TestConstants.FILE_MP4, "mm.mp4"}, | |||
| new Object[]{WxConsts.MediaFileType.FILE, TestConstants.FILE_JPG, "mm.jpeg"} | |||
| @@ -47,8 +48,9 @@ public class WxCpMediaServiceImplTest { | |||
| public void testUploadMedia(String mediaType, String fileType, String fileName) throws WxErrorException, IOException { | |||
| try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName)) { | |||
| WxMediaUploadResult res = this.wxService.getMediaService().upload(mediaType, fileType, inputStream); | |||
| assertNotNull(res.getType()); | |||
| assertNotNull(res.getCreatedAt()); | |||
| assertThat(res).isNotNull(); | |||
| assertThat(res.getType()).isNotEmpty(); | |||
| assertThat(res.getCreatedAt()).isGreaterThan(0); | |||
| assertTrue(res.getMediaId() != null || res.getThumbMediaId() != null); | |||
| if (res.getMediaId() != null) { | |||
| @@ -70,9 +72,9 @@ public class WxCpMediaServiceImplTest { | |||
| } | |||
| @Test(dependsOnMethods = {"testUploadMedia"}, dataProvider = "downloadMedia") | |||
| public void testDownloadMedia(String media_id) throws WxErrorException { | |||
| File file = this.wxService.getMediaService().download(media_id); | |||
| assertNotNull(file); | |||
| public void testDownload(String mediaId) throws WxErrorException { | |||
| File file = this.wxService.getMediaService().download(mediaId); | |||
| assertThat(file).isNotNull(); | |||
| System.out.println(file); | |||
| } | |||
| @@ -82,4 +84,11 @@ public class WxCpMediaServiceImplTest { | |||
| String res = this.wxService.getMediaService().uploadImg(new File(url.getFile())); | |||
| assertThat(res).isNotEmpty(); | |||
| } | |||
| @Test | |||
| public void testGetJssdkFile() throws WxErrorException { | |||
| File file = this.wxService.getMediaService().getJssdkFile("...."); | |||
| assertThat(file).isNotNull(); | |||
| System.out.println(file); | |||
| } | |||
| } | |||
| @@ -74,7 +74,7 @@ public class WxCpUserServiceImplTest { | |||
| @Test | |||
| public void testListByDepartment() throws Exception { | |||
| List<WxCpUser> users = this.wxCpService.getUserService().listByDepartment(1, true, 0); | |||
| List<WxCpUser> users = this.wxCpService.getUserService().listByDepartment(1L, true, 0); | |||
| assertNotEquals(users.size(), 0); | |||
| for (WxCpUser user : users) { | |||
| System.out.println(ToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE)); | |||
| @@ -83,7 +83,7 @@ public class WxCpUserServiceImplTest { | |||
| @Test | |||
| public void testListSimpleByDepartment() throws Exception { | |||
| List<WxCpUser> users = this.wxCpService.getUserService().listSimpleByDepartment(1, true, 0); | |||
| List<WxCpUser> users = this.wxCpService.getUserService().listSimpleByDepartment(1L, true, 0); | |||
| assertNotEquals(users.size(), 0); | |||
| for (WxCpUser user : users) { | |||
| System.out.println(ToStringBuilder.reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE)); | |||
| @@ -6,11 +6,11 @@ | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>3.2.0</version> | |||
| <artifactId>wx-java</artifactId> | |||
| <version>3.3.0</version> | |||
| </parent> | |||
| <artifactId>weixin-java-miniapp</artifactId> | |||
| <name>Weixin Java Tools - MiniApp</name> | |||
| <name>WxJava - MiniApp</name> | |||
| <description>微信小程序Java SDK</description> | |||
| <dependencies> | |||
| @@ -15,7 +15,26 @@ public interface WxMaJsapiService { | |||
| /** | |||
| * 获得jsapi_ticket的url | |||
| */ | |||
| String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||
| String GET_JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"; | |||
| /** | |||
| * 获得卡券api_ticket,不强制刷新api_ticket | |||
| * | |||
| * @see #getJsapiTicket(boolean) | |||
| */ | |||
| String getCardApiTicket() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获得卡券api_ticket | |||
| * 获得时会检查apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||
| * | |||
| * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @param forceRefresh 强制刷新 | |||
| */ | |||
| String getCardApiTicket(boolean forceRefresh) throws WxErrorException; | |||
| /** | |||
| * 获得jsapi_ticket,不强制刷新jsapi_ticket | |||
| @@ -1,10 +1,10 @@ | |||
| package cn.binarywang.wx.miniapp.api; | |||
| import java.io.File; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import java.io.File; | |||
| /** | |||
| * <pre> | |||
| * 二维码相关操作接口. | |||
| @@ -32,24 +32,87 @@ public interface WxMaQrcodeService { | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @return 文件内容字节数组 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| byte[] createQrcodeBytes(String path, int width) throws WxErrorException; | |||
| /** | |||
| * 接口C: 获取小程序页面二维码. | |||
| * <pre> | |||
| * 适用于需要的码数量较少的业务场景 | |||
| * 通过该接口,仅能生成已发布的小程序的二维码。 | |||
| * 可以在开发者工具预览时生成开发版的带参二维码。 | |||
| * 带参二维码只有 100000 个,请谨慎调用。 | |||
| * </pre> | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createQrcode(String path, int width) throws WxErrorException; | |||
| /** | |||
| * 接口C: 获取小程序页面二维码. | |||
| * <pre> | |||
| * 适用于需要的码数量较少的业务场景 | |||
| * 通过该接口,仅能生成已发布的小程序的二维码。 | |||
| * 可以在开发者工具预览时生成开发版的带参二维码。 | |||
| * 带参二维码只有 100000 个,请谨慎调用。 | |||
| * </pre> | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createQrcode(String path) throws WxErrorException; | |||
| /** | |||
| * 接口A: 获取小程序码. | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} | |||
| * @param is_hyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} | |||
| * @param isHyaline 是否需要透明底色, isHyaline 为true时,生成透明底色的小程序码 | |||
| * @return 文件内容字节数组 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| byte[] createWxaCodeBytes(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) | |||
| throws WxErrorException; | |||
| /** | |||
| * 接口A: 获取小程序码. | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} | |||
| * @param isHyaline 是否需要透明底色, isHyaline 为true时,生成透明底色的小程序码 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean is_hyaline) throws WxErrorException; | |||
| File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) | |||
| throws WxErrorException; | |||
| /** | |||
| * 接口A: 获取小程序码. | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @param width 默认430 二维码的宽度 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createWxaCode(String path, int width) throws WxErrorException; | |||
| /** | |||
| * 接口A: 获取小程序码. | |||
| * | |||
| * @param path 不能为空,最大长度 128 字节 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createWxaCode(String path) throws WxErrorException; | |||
| /** | |||
| @@ -61,15 +124,56 @@ public interface WxMaQrcodeService { | |||
| * 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 urlencode | |||
| * </pre> | |||
| * | |||
| * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) | |||
| * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, | |||
| * 其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) | |||
| * @param page 必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面 | |||
| * @param width 默认false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} | |||
| * @param isHyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 | |||
| * @return 文件内容字节数组 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| byte[] createWxaCodeUnlimitBytes(String scene, String page, int width, boolean autoColor, | |||
| WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException; | |||
| /** | |||
| * 接口B: 获取小程序码(永久有效、数量暂无限制). | |||
| * <pre> | |||
| * 通过该接口生成的小程序码,永久有效,数量暂无限制。 | |||
| * 用户扫描该码进入小程序后,将统一打开首页,开发者需在对应页面根据获取的码中 scene 字段的值,再做处理逻辑。 | |||
| * 使用如下代码可以获取到二维码中的 scene 字段的值。 | |||
| * 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 urlencode | |||
| * </pre> | |||
| * | |||
| * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, | |||
| * 其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) | |||
| * @param page 必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面 | |||
| * @param width 默认false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 | |||
| * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} | |||
| * @param isHyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createWxaCodeUnlimit(String scene, String page, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException; | |||
| File createWxaCodeUnlimit(String scene, String page, int width, boolean autoColor, | |||
| WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException; | |||
| /** | |||
| * 接口B: 获取小程序码(永久有效、数量暂无限制). | |||
| * <pre> | |||
| * 通过该接口生成的小程序码,永久有效,数量暂无限制。 | |||
| * 用户扫描该码进入小程序后,将统一打开首页,开发者需在对应页面根据获取的码中 scene 字段的值,再做处理逻辑。 | |||
| * 使用如下代码可以获取到二维码中的 scene 字段的值。 | |||
| * 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 urlencode | |||
| * </pre> | |||
| * | |||
| * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, | |||
| * 其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) | |||
| * @param page 必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面 | |||
| * @return 文件对象 | |||
| * @throws WxErrorException 异常 | |||
| */ | |||
| File createWxaCodeUnlimit(String scene, String page) throws WxErrorException; | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package cn.binarywang.wx.miniapp.api; | |||
| import java.util.List; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaRunStepInfo; | |||
| /** | |||
| * 微信运动相关操作接口. | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public interface WxMaRunService { | |||
| /** | |||
| * 解密分享敏感数据. | |||
| * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/werun/wx.getWeRunData.html | |||
| * | |||
| * @param sessionKey 会话密钥 | |||
| * @param encryptedData 消息密文 | |||
| * @param ivStr 加密算法的初始向量 | |||
| */ | |||
| List<WxMaRunStepInfo> getRunStepInfo(String sessionKey, String encryptedData, String ivStr); | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| package cn.binarywang.wx.miniapp.api; | |||
| import java.io.File; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 内容安全相关接口. | |||
| * Created by Binary Wang on 2018/11/24. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public interface WxMaSecCheckService { | |||
| String IMG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/img_sec_check"; | |||
| String MSG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/msg_sec_check"; | |||
| /** | |||
| * <pre> | |||
| * 校验一张图片是否含有违法违规内容. | |||
| * 应用场景举例: | |||
| * 1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等; | |||
| * 2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天 | |||
| * 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/imgSecCheck.html | |||
| * </pre> | |||
| */ | |||
| boolean checkImage(File file) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 检查一段文本是否含有违法违规内容。 | |||
| * 应用场景举例: | |||
| * 用户个人资料违规文字检测; | |||
| * 媒体新闻类用户发表文章,评论内容检测; | |||
| * 游戏类用户编辑上传的素材(如答题类小游戏用户上传的问题及答案)检测等。 频率限制:单个 appId 调用上限为 4000 次/分钟,2,000,000 次/天* | |||
| * 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/sec-check/msgSecCheck.html | |||
| * </pre> | |||
| */ | |||
| boolean checkMessage(String msgString); | |||
| } | |||
| @@ -1,7 +1,5 @@ | |||
| package cn.binarywang.wx.miniapp.api; | |||
| import java.io.File; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| @@ -20,17 +18,6 @@ public interface WxMaService { | |||
| String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | |||
| String IMG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/img_sec_check"; | |||
| /** | |||
| * <pre> | |||
| * 校验一张图片是否含有违法违规内容. | |||
| * 应用场景举例:1)图片智能鉴黄:涉及拍照的工具类应用(如美拍,识图类应用)用户拍照上传检测;电商类商品上架图片检测;媒体类用户文章里的图片检测等;2)敏感人脸识别:用户头像;媒体类用户文章里的图片检测;社交类用户上传的图片检测等。频率限制:单个 appId 调用上限为 1000 次/分钟,100,000 次/天 | |||
| * 详情请见: https://developers.weixin.qq.com/miniprogram/dev/api/imgSecCheck.html | |||
| * </pre> | |||
| */ | |||
| boolean imgSecCheck(File file) throws WxErrorException; | |||
| /** | |||
| * 获取登录后的session信息. | |||
| * | |||
| @@ -152,33 +139,51 @@ public interface WxMaService { | |||
| WxMaTemplateService getTemplateService(); | |||
| /** | |||
| * 数据分析相关查询服务 | |||
| * 数据分析相关查询服务. | |||
| * | |||
| * @return WxMaAnalysisService | |||
| */ | |||
| WxMaAnalysisService getAnalysisService(); | |||
| /** | |||
| * 返回代码操作相关的 API | |||
| * 返回代码操作相关的 API. | |||
| * | |||
| * @return WxMaCodeService | |||
| */ | |||
| WxMaCodeService getCodeService(); | |||
| /** | |||
| * 返回jsapi操作相关的 API服务类对象 | |||
| * 返回jsapi操作相关的 API服务类对象. | |||
| * | |||
| * @return WxMaJsapiService | |||
| */ | |||
| WxMaJsapiService getJsapiService(); | |||
| /** | |||
| * 小程序修改服务器地址、成员管理 API | |||
| * 小程序修改服务器地址、成员管理 API. | |||
| * | |||
| * @return WxMaSettingService | |||
| */ | |||
| WxMaSettingService getSettingService(); | |||
| /** | |||
| * 返回分享相关查询服务. | |||
| * @return WxMaShareService | |||
| */ | |||
| WxMaShareService getShareService(); | |||
| /** | |||
| * 返回微信运动相关接口服务对象. | |||
| * @return WxMaShareService | |||
| */ | |||
| WxMaRunService getRunService(); | |||
| /** | |||
| * 返回内容安全相关接口服务对象. | |||
| * @return WxMaShareService | |||
| */ | |||
| WxMaSecCheckService getSecCheckService(); | |||
| /** | |||
| * 初始化http请求对象. | |||
| */ | |||
| @@ -0,0 +1,21 @@ | |||
| package cn.binarywang.wx.miniapp.api; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaShareInfo; | |||
| /** | |||
| * 分享信息相关操作接口. | |||
| * | |||
| * @author zhfish | |||
| */ | |||
| public interface WxMaShareService { | |||
| /** | |||
| * 解密分享敏感数据. | |||
| * | |||
| * @param sessionKey 会话密钥 | |||
| * @param encryptedData 消息密文 | |||
| * @param ivStr 加密算法的初始向量 | |||
| */ | |||
| WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, String ivStr); | |||
| } | |||
| @@ -28,6 +28,32 @@ public class WxMaJsapiServiceImpl implements WxMaJsapiService { | |||
| this.wxMaService = wxMaService; | |||
| } | |||
| public String getCardApiTicket() throws WxErrorException { | |||
| return getCardApiTicket(false); | |||
| } | |||
| public String getCardApiTicket(boolean forceRefresh) throws WxErrorException { | |||
| Lock lock = this.wxMaService.getWxMaConfig().getCardApiTicketLock(); | |||
| try { | |||
| lock.lock(); | |||
| if (forceRefresh) { | |||
| this.wxMaService.getWxMaConfig().expireCardApiTicket(); | |||
| } | |||
| if (this.wxMaService.getWxMaConfig().isCardApiTicketExpired()) { | |||
| String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL + "?type=wx_card", null); | |||
| JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
| this.wxMaService.getWxMaConfig().updateCardApiTicket(jsapiTicket, expiresInSeconds); | |||
| } | |||
| } finally { | |||
| lock.unlock(); | |||
| } | |||
| return this.wxMaService.getWxMaConfig().getJsapiTicket(); | |||
| } | |||
| @Override | |||
| public String getJsapiTicket() throws WxErrorException { | |||
| return getJsapiTicket(false); | |||
| @@ -43,7 +69,7 @@ public class WxMaJsapiServiceImpl implements WxMaJsapiService { | |||
| } | |||
| if (this.wxMaService.getWxMaConfig().isJsapiTicketExpired()) { | |||
| String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL, null); | |||
| String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL + "?type=jsapi", null); | |||
| JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
| @@ -1,16 +1,17 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.io.File; | |||
| import cn.binarywang.wx.miniapp.api.WxMaQrcodeService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaQrcode; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaWxcode; | |||
| import cn.binarywang.wx.miniapp.bean.WxaCode; | |||
| import cn.binarywang.wx.miniapp.bean.WxaCodeUnlimit; | |||
| import cn.binarywang.wx.miniapp.util.http.QrCodeRequestExecutor; | |||
| import cn.binarywang.wx.miniapp.util.QrcodeBytesRequestExecutor; | |||
| import cn.binarywang.wx.miniapp.util.QrcodeRequestExecutor; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import java.io.File; | |||
| /** | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @@ -21,10 +22,16 @@ public class WxMaQrcodeServiceImpl implements WxMaQrcodeService { | |||
| this.wxMaService = wxMaService; | |||
| } | |||
| @Override | |||
| public byte[] createQrcodeBytes(String path, int width) throws WxErrorException { | |||
| final QrcodeBytesRequestExecutor executor = new QrcodeBytesRequestExecutor(this.wxMaService.getRequestHttp()); | |||
| return this.wxMaService.execute(executor, CREATE_QRCODE_URL, new WxMaQrcode(path, width)); | |||
| } | |||
| @Override | |||
| public File createQrcode(String path, int width) throws WxErrorException { | |||
| return this.wxMaService.execute(new QrCodeRequestExecutor(this.wxMaService.getRequestHttp()), | |||
| CREATE_QRCODE_URL, new WxMaQrcode(path, width)); | |||
| final QrcodeRequestExecutor executor = new QrcodeRequestExecutor(this.wxMaService.getRequestHttp()); | |||
| return this.wxMaService.execute(executor, CREATE_QRCODE_URL, new WxMaQrcode(path, width)); | |||
| } | |||
| @Override | |||
| @@ -33,15 +40,29 @@ public class WxMaQrcodeServiceImpl implements WxMaQrcodeService { | |||
| } | |||
| @Override | |||
| public File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException { | |||
| WxMaWxcode wxMaWxcode = new WxMaWxcode(); | |||
| wxMaWxcode.setPath(path); | |||
| wxMaWxcode.setWidth(width); | |||
| wxMaWxcode.setAutoColor(autoColor); | |||
| wxMaWxcode.setLineColor(lineColor); | |||
| wxMaWxcode.setHyaline(isHyaline); | |||
| return this.wxMaService.execute(new QrCodeRequestExecutor(this.wxMaService.getRequestHttp()), | |||
| GET_WXACODE_URL, wxMaWxcode); | |||
| public byte[] createWxaCodeBytes(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) | |||
| throws WxErrorException { | |||
| final QrcodeBytesRequestExecutor executor = new QrcodeBytesRequestExecutor(this.wxMaService.getRequestHttp()); | |||
| return this.wxMaService.execute(executor, GET_WXACODE_URL, WxaCode.builder() | |||
| .path(path) | |||
| .width(width) | |||
| .autoColor(autoColor) | |||
| .lineColor(lineColor) | |||
| .isHyaline(isHyaline) | |||
| .build()); | |||
| } | |||
| @Override | |||
| public File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) | |||
| throws WxErrorException { | |||
| final QrcodeRequestExecutor executor = new QrcodeRequestExecutor(this.wxMaService.getRequestHttp()); | |||
| return this.wxMaService.execute(executor, GET_WXACODE_URL, WxaCode.builder() | |||
| .path(path) | |||
| .width(width) | |||
| .autoColor(autoColor) | |||
| .lineColor(lineColor) | |||
| .isHyaline(isHyaline) | |||
| .build()); | |||
| } | |||
| @Override | |||
| @@ -51,12 +72,27 @@ public class WxMaQrcodeServiceImpl implements WxMaQrcodeService { | |||
| @Override | |||
| public File createWxaCode(String path) throws WxErrorException { | |||
| return this.createWxaCode(path, 430, true, null, false); | |||
| return this.createWxaCode(path, 430); | |||
| } | |||
| @Override | |||
| public File createWxaCodeUnlimit(String scene, String page, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline) | |||
| throws WxErrorException { | |||
| public byte[] createWxaCodeUnlimitBytes(String scene, String page, int width, boolean autoColor, | |||
| WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException { | |||
| return this.wxMaService.execute(new QrcodeBytesRequestExecutor(this.wxMaService.getRequestHttp()), | |||
| GET_WXACODE_UNLIMIT_URL, | |||
| this.buildWxaCodeUnlimit(scene, page, width, autoColor, lineColor, isHyaline)); | |||
| } | |||
| @Override | |||
| public File createWxaCodeUnlimit(String scene, String page, int width, boolean autoColor, | |||
| WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException { | |||
| return this.wxMaService.execute(new QrcodeRequestExecutor(this.wxMaService.getRequestHttp()), | |||
| GET_WXACODE_UNLIMIT_URL, | |||
| this.buildWxaCodeUnlimit(scene, page, width, autoColor, lineColor, isHyaline)); | |||
| } | |||
| private WxaCodeUnlimit buildWxaCodeUnlimit(String scene, String page, int width, boolean autoColor, | |||
| WxMaCodeLineColor lineColor, boolean isHyaline) { | |||
| WxaCodeUnlimit wxaCodeUnlimit = new WxaCodeUnlimit(); | |||
| wxaCodeUnlimit.setScene(scene); | |||
| wxaCodeUnlimit.setPage(page); | |||
| @@ -64,8 +100,8 @@ public class WxMaQrcodeServiceImpl implements WxMaQrcodeService { | |||
| wxaCodeUnlimit.setAutoColor(autoColor); | |||
| wxaCodeUnlimit.setLineColor(lineColor); | |||
| wxaCodeUnlimit.setHyaline(isHyaline); | |||
| return this.wxMaService.execute(new QrCodeRequestExecutor(this.wxMaService.getRequestHttp()), | |||
| GET_WXACODE_UNLIMIT_URL, wxaCodeUnlimit); | |||
| return wxaCodeUnlimit; | |||
| } | |||
| @Override | |||
| @@ -0,0 +1,29 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.util.List; | |||
| import cn.binarywang.wx.miniapp.api.WxMaRunService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaRunStepInfo; | |||
| import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
| /** | |||
| * <pre> | |||
| * | |||
| * Created by Binary Wang on 2018/11/4. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class WxMaRunServiceImpl implements WxMaRunService { | |||
| private WxMaService service; | |||
| public WxMaRunServiceImpl(WxMaService service) { | |||
| this.service = service; | |||
| } | |||
| @Override | |||
| public List<WxMaRunStepInfo> getRunStepInfo(String sessionKey, String encryptedData, String ivStr) { | |||
| return WxMaRunStepInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.io.File; | |||
| import cn.binarywang.wx.miniapp.api.WxMaSecCheckService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import com.google.gson.JsonObject; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| /** | |||
| * <pre> | |||
| * | |||
| * Created by Binary Wang on 2018/11/24. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class WxMaSecCheckServiceImpl implements WxMaSecCheckService { | |||
| private WxMaService service; | |||
| public WxMaSecCheckServiceImpl(WxMaService service) { | |||
| this.service = service; | |||
| } | |||
| @Override | |||
| public boolean checkImage(File file) throws WxErrorException { | |||
| //这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult | |||
| WxMediaUploadResult result = this.service.execute(MediaUploadRequestExecutor | |||
| .create(this.service.getRequestHttp()), IMG_SEC_CHECK_URL, file); | |||
| return result != null; | |||
| } | |||
| @Override | |||
| public boolean checkMessage(String msgString) { | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("content", msgString); | |||
| try { | |||
| this.service.post(MSG_SEC_CHECK_URL, jsonObject.toString()); | |||
| } catch (WxErrorException e) { | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @@ -1,6 +1,5 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| @@ -12,8 +11,6 @@ import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpGet; | |||
| import org.apache.http.impl.client.BasicResponseHandler; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import cn.binarywang.wx.miniapp.api.WxMaAnalysisService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||
| @@ -21,22 +18,24 @@ import cn.binarywang.wx.miniapp.api.WxMaJsapiService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaMediaService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaMsgService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaQrcodeService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaRunService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaSecCheckService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaSettingService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaShareService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaTemplateService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaUserService; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
| import com.google.common.base.Joiner; | |||
| import com.google.gson.Gson; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.DataUtils; | |||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||
| import me.chanjar.weixin.common.util.http.HttpType; | |||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
| @@ -49,9 +48,8 @@ import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | |||
| /** | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @Slf4j | |||
| public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> { | |||
| private final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
| private CloseableHttpClient httpClient; | |||
| private HttpHost httpProxy; | |||
| private WxMaConfig wxMaConfig; | |||
| @@ -65,6 +63,9 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| private WxMaCodeService codeService = new WxMaCodeServiceImpl(this); | |||
| private WxMaSettingService settingService = new WxMaSettingServiceImpl(this); | |||
| private WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this); | |||
| private WxMaShareService shareService = new WxMaShareServiceImpl(this); | |||
| private WxMaRunService runService = new WxMaRunServiceImpl(this); | |||
| private WxMaSecCheckService secCheckService = new WxMaSecCheckServiceImpl(this); | |||
| private int retrySleepMillis = 1000; | |||
| private int maxRetryTimes = 5; | |||
| @@ -149,13 +150,6 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| return this.getWxMaConfig().getAccessToken(); | |||
| } | |||
| @Override | |||
| public boolean imgSecCheck(File file) throws WxErrorException { | |||
| //这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult | |||
| WxMediaUploadResult result = this.execute(MediaUploadRequestExecutor.create(this.getRequestHttp()), IMG_SEC_CHECK_URL, file); | |||
| return result != null; | |||
| } | |||
| @Override | |||
| public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException { | |||
| final WxMaConfig config = getWxMaConfig(); | |||
| @@ -205,7 +199,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| return this.executeInternal(executor, uri, data); | |||
| } catch (WxErrorException e) { | |||
| if (retryTimes + 1 > this.maxRetryTimes) { | |||
| this.log.warn("重试达到最大次数【{}】", maxRetryTimes); | |||
| log.warn("重试达到最大次数【{}】", maxRetryTimes); | |||
| //最后一次重试失败后,直接抛出异常,不再等待 | |||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
| } | |||
| @@ -215,10 +209,10 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| if (error.getErrorCode() == -1) { | |||
| int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||
| try { | |||
| this.log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||
| log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||
| Thread.sleep(sleepMillis); | |||
| } catch (InterruptedException e1) { | |||
| throw new RuntimeException(e1); | |||
| Thread.currentThread().interrupt(); | |||
| } | |||
| } else { | |||
| throw e; | |||
| @@ -226,7 +220,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| } | |||
| } while (retryTimes++ < this.maxRetryTimes); | |||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||
| log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
| } | |||
| @@ -242,7 +236,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| try { | |||
| T result = executor.execute(uriWithAccessToken, data); | |||
| this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result); | |||
| log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result); | |||
| return result; | |||
| } catch (WxErrorException e) { | |||
| WxError error = e.getError(); | |||
| @@ -260,12 +254,12 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| } | |||
| if (error.getErrorCode() != 0) { | |||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); | |||
| log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); | |||
| throw new WxErrorException(error, e); | |||
| } | |||
| return null; | |||
| } catch (IOException e) { | |||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); | |||
| log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| @@ -335,4 +329,19 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
| public WxMaSettingService getSettingService() { | |||
| return this.settingService; | |||
| } | |||
| @Override | |||
| public WxMaShareService getShareService() { | |||
| return this.shareService; | |||
| } | |||
| @Override | |||
| public WxMaRunService getRunService() { | |||
| return this.runService; | |||
| } | |||
| @Override | |||
| public WxMaSecCheckService getSecCheckService() { | |||
| return this.secCheckService; | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaShareService; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaShareInfo; | |||
| import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
| /** | |||
| * @author zhfish | |||
| */ | |||
| public class WxMaShareServiceImpl implements WxMaShareService { | |||
| private WxMaService service; | |||
| public WxMaShareServiceImpl(WxMaService service) { | |||
| this.service = service; | |||
| } | |||
| @Override | |||
| public WxMaShareInfo getShareInfo(String sessionKey, String encryptedData, String ivStr) { | |||
| return WxMaShareInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
| } | |||
| } | |||
| @@ -1,5 +1,9 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.api.WxMaTemplateService; | |||
| import cn.binarywang.wx.miniapp.bean.template.WxMaTemplateAddResult; | |||
| @@ -10,15 +14,11 @@ import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| private WxMaService wxMaService; | |||
| public WxMaTemplateServiceImpl(WxMaService wxMaService){ | |||
| public WxMaTemplateServiceImpl(WxMaService wxMaService) { | |||
| this.wxMaService = wxMaService; | |||
| } | |||
| @@ -31,9 +31,9 @@ public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| String responseText = this.wxMaService.post(TEMPLATE_LIBRARY_LIST_URL, WxGsonBuilder.create().toJson(params)); | |||
| WxError wxError = WxError.fromJson(responseText); | |||
| if(wxError.getErrorCode() == 0){ | |||
| if (wxError.getErrorCode() == 0) { | |||
| return WxMaTemplateLibraryListResult.fromJson(responseText); | |||
| }else { | |||
| } else { | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| } | |||
| @@ -46,9 +46,9 @@ public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| String responseText = this.wxMaService.post(TEMPLATE_LIBRARY_KEYWORD_URL, WxGsonBuilder.create().toJson(params)); | |||
| WxError wxError = WxError.fromJson(responseText); | |||
| if(wxError.getErrorCode() == 0){ | |||
| if (wxError.getErrorCode() == 0) { | |||
| return WxMaTemplateLibraryGetResult.fromJson(responseText); | |||
| }else { | |||
| } else { | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| } | |||
| @@ -62,9 +62,9 @@ public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| String responseText = this.wxMaService.post(TEMPLATE_ADD_URL, WxGsonBuilder.create().toJson(params)); | |||
| WxError wxError = WxError.fromJson(responseText); | |||
| if(wxError.getErrorCode() == 0){ | |||
| if (wxError.getErrorCode() == 0) { | |||
| return WxMaTemplateAddResult.fromJson(responseText); | |||
| }else { | |||
| } else { | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| } | |||
| @@ -78,9 +78,9 @@ public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| String responseText = this.wxMaService.post(TEMPLATE_LIST_URL, WxGsonBuilder.create().toJson(params)); | |||
| WxError wxError = WxError.fromJson(responseText); | |||
| if(wxError.getErrorCode() == 0){ | |||
| if (wxError.getErrorCode() == 0) { | |||
| return WxMaTemplateListResult.fromJson(responseText); | |||
| }else { | |||
| } else { | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| } | |||
| @@ -92,9 +92,9 @@ public class WxMaTemplateServiceImpl implements WxMaTemplateService { | |||
| String responseText = this.wxMaService.post(TEMPLATE_DEL_URL, WxGsonBuilder.create().toJson(params)); | |||
| WxError wxError = WxError.fromJson(responseText); | |||
| if(wxError.getErrorCode() == 0){ | |||
| if (wxError.getErrorCode() == 0) { | |||
| return true; | |||
| }else { | |||
| } else { | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| } | |||
| @@ -6,8 +6,6 @@ import java.io.Serializable; | |||
| import java.nio.charset.StandardCharsets; | |||
| import org.apache.commons.io.IOUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
| import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
| @@ -168,7 +166,7 @@ public class WxMaMessage implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return this.toJson(); | |||
| } | |||
| public String toJson() { | |||
| @@ -0,0 +1,38 @@ | |||
| package cn.binarywang.wx.miniapp.bean; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import lombok.Data; | |||
| /** | |||
| * 微信运动步数信息. | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @Data | |||
| public class WxMaRunStepInfo implements Serializable { | |||
| private static final JsonParser JSON_PARSER = new JsonParser(); | |||
| private static final long serialVersionUID = -7496372171398607044L; | |||
| /** | |||
| * 时间戳,表示数据对应的时间. | |||
| */ | |||
| private Long timestamp; | |||
| /** | |||
| * 微信运动步数. | |||
| */ | |||
| private Integer step; | |||
| public static List<WxMaRunStepInfo> fromJson(String json) { | |||
| JsonObject jsonObject = JSON_PARSER.parse(json).getAsJsonObject(); | |||
| return WxMaGsonBuilder.create().fromJson(jsonObject.get("stepInfoList").toString(), | |||
| new TypeToken<List<WxMaRunStepInfo>>() { | |||
| }.getType()); | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| package cn.binarywang.wx.miniapp.bean; | |||
| import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
| import lombok.Data; | |||
| import java.io.Serializable; | |||
| /** | |||
| * @author zhfish | |||
| */ | |||
| @Data | |||
| public class WxMaShareInfo implements Serializable { | |||
| private static final long serialVersionUID = -8053613683499632226L; | |||
| private String openGId; | |||
| public static WxMaShareInfo fromJson(String json) { | |||
| return WxMaGsonBuilder.create().fromJson(json, WxMaShareInfo.class); | |||
| } | |||
| } | |||
| @@ -1,36 +1,48 @@ | |||
| package cn.binarywang.wx.miniapp.bean; | |||
| import java.io.Serializable; | |||
| import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import lombok.AllArgsConstructor; | |||
| import lombok.Builder; | |||
| import lombok.Data; | |||
| import lombok.EqualsAndHashCode; | |||
| import java.io.Serializable; | |||
| import lombok.NoArgsConstructor; | |||
| /** | |||
| * 小程序码. | |||
| * | |||
| * @author Element | |||
| * @date 2017/7/27 | |||
| */ | |||
| @Data | |||
| @EqualsAndHashCode(callSuper = false) | |||
| public class WxMaWxcode extends AbstractWxMaQrcodeWrapper implements Serializable { | |||
| @Builder | |||
| @NoArgsConstructor | |||
| @AllArgsConstructor | |||
| public class WxaCode extends AbstractWxMaQrcodeWrapper implements Serializable { | |||
| private static final long serialVersionUID = 1287399621649210322L; | |||
| private String path; | |||
| @Builder.Default | |||
| private int width = 430; | |||
| @SerializedName("auto_color") | |||
| @Builder.Default | |||
| private boolean autoColor = true; | |||
| @SerializedName("is_hyaline") | |||
| @Builder.Default | |||
| private boolean isHyaline = false; | |||
| @SerializedName("line_color") | |||
| @Builder.Default | |||
| private WxMaCodeLineColor lineColor = new WxMaCodeLineColor("0", "0", "0"); | |||
| public static WxMaWxcode fromJson(String json) { | |||
| return WxMaGsonBuilder.create().fromJson(json, WxMaWxcode.class); | |||
| public static WxaCode fromJson(String json) { | |||
| return WxMaGsonBuilder.create().fromJson(json, WxaCode.class); | |||
| } | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| package cn.binarywang.wx.miniapp.config; | |||
| import java.util.concurrent.locks.Lock; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| import java.util.concurrent.locks.Lock; | |||
| /** | |||
| * 小程序配置 | |||
| * | |||
| @@ -57,6 +57,28 @@ public interface WxMaConfig { | |||
| */ | |||
| void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
| /** | |||
| * 卡券api_ticket. | |||
| */ | |||
| String getCardApiTicket(); | |||
| Lock getCardApiTicketLock(); | |||
| boolean isCardApiTicketExpired(); | |||
| /** | |||
| * 强制将卡券api ticket过期掉. | |||
| */ | |||
| void expireCardApiTicket(); | |||
| /** | |||
| * 应该是线程安全的. | |||
| * | |||
| * @param apiTicket 新的卡券api ticket值 | |||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||
| */ | |||
| void updateCardApiTicket(String apiTicket, int expiresInSeconds); | |||
| String getAppid(); | |||
| String getSecret(); | |||
| @@ -4,9 +4,7 @@ import java.io.File; | |||
| import java.util.concurrent.locks.Lock; | |||
| import java.util.concurrent.locks.ReentrantLock; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| @@ -32,8 +30,14 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
| protected volatile String jsapiTicket; | |||
| protected volatile long jsapiTicketExpiresTime; | |||
| //微信卡券的ticket单独缓存 | |||
| protected volatile String cardApiTicket; | |||
| protected volatile long cardApiTicketExpiresTime; | |||
| protected Lock accessTokenLock = new ReentrantLock(); | |||
| protected Lock jsapiTicketLock = new ReentrantLock(); | |||
| protected Lock cardApiTicketLock = new ReentrantLock(); | |||
| /** | |||
| * 临时文件目录 | |||
| @@ -103,6 +107,34 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
| this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| public String getCardApiTicket() { | |||
| return this.cardApiTicket; | |||
| } | |||
| @Override | |||
| public Lock getCardApiTicketLock() { | |||
| return this.cardApiTicketLock; | |||
| } | |||
| @Override | |||
| public boolean isCardApiTicketExpired() { | |||
| return System.currentTimeMillis() > this.cardApiTicketExpiresTime; | |||
| } | |||
| @Override | |||
| public void expireCardApiTicket() { | |||
| this.cardApiTicketExpiresTime = 0; | |||
| } | |||
| @Override | |||
| public void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { | |||
| this.cardApiTicket = cardApiTicket; | |||
| // 预留200秒的时间 | |||
| this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| public void expireAccessToken() { | |||
| this.expiresTime = 0; | |||
| @@ -191,7 +223,7 @@ public class WxMaInMemoryConfig implements WxMaConfig { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||
| return WxMaGsonBuilder.create().toJson(this); | |||
| } | |||
| @Override | |||
| @@ -0,0 +1,66 @@ | |||
| package cn.binarywang.wx.miniapp.util; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import org.apache.commons.io.IOUtils; | |||
| import org.apache.http.Header; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpPost; | |||
| import org.apache.http.entity.ContentType; | |||
| import org.apache.http.entity.StringEntity; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.ResponseHandler; | |||
| import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; | |||
| import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; | |||
| /** | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class QrcodeBytesRequestExecutor implements RequestExecutor<byte[], AbstractWxMaQrcodeWrapper> { | |||
| protected RequestHttp<CloseableHttpClient, HttpHost> requestHttp; | |||
| public QrcodeBytesRequestExecutor(RequestHttp requestHttp) { | |||
| this.requestHttp = requestHttp; | |||
| } | |||
| @Override | |||
| public void execute(String uri, AbstractWxMaQrcodeWrapper data, ResponseHandler<byte[]> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| @Override | |||
| public byte[] execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper) throws WxErrorException, IOException { | |||
| HttpPost httpPost = new HttpPost(uri); | |||
| if (requestHttp.getRequestHttpProxy() != null) { | |||
| httpPost.setConfig( | |||
| RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() | |||
| ); | |||
| } | |||
| httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson())); | |||
| try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); | |||
| final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { | |||
| Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||
| if (contentTypeHeader != null && contentTypeHeader.length > 0 | |||
| && ContentType.APPLICATION_JSON.getMimeType() | |||
| .equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) { | |||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||
| throw new WxErrorException(WxError.fromJson(responseContent)); | |||
| } | |||
| return IOUtils.toByteArray(inputStream); | |||
| } finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,13 +1,10 @@ | |||
| package cn.binarywang.wx.miniapp.util.http; | |||
| package cn.binarywang.wx.miniapp.util; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.util.UUID; | |||
| import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; | |||
| import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; | |||
| import org.apache.http.Header; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| @@ -17,33 +14,44 @@ import org.apache.http.entity.ContentType; | |||
| import org.apache.http.entity.StringEntity; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.util.UUID; | |||
| import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.ResponseHandler; | |||
| import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; | |||
| import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; | |||
| /** | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class QrCodeRequestExecutor implements RequestExecutor<File, AbstractWxMaQrcodeWrapper> { | |||
| public class QrcodeRequestExecutor implements RequestExecutor<File, AbstractWxMaQrcodeWrapper> { | |||
| protected RequestHttp<CloseableHttpClient, HttpHost> requestHttp; | |||
| public QrCodeRequestExecutor(RequestHttp requestHttp) { | |||
| public QrcodeRequestExecutor(RequestHttp requestHttp) { | |||
| this.requestHttp = requestHttp; | |||
| } | |||
| @Override | |||
| public File execute(String uri, AbstractWxMaQrcodeWrapper ticket) throws WxErrorException, IOException { | |||
| public void execute(String uri, AbstractWxMaQrcodeWrapper data, ResponseHandler<File> handler) throws WxErrorException, IOException { | |||
| handler.handle(this.execute(uri, data)); | |||
| } | |||
| @Override | |||
| public File execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper) throws WxErrorException, IOException { | |||
| HttpPost httpPost = new HttpPost(uri); | |||
| if (requestHttp.getRequestHttpProxy() != null) { | |||
| httpPost.setConfig( | |||
| RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() | |||
| ); | |||
| } | |||
| httpPost.setEntity(new StringEntity(ticket.toJson())); | |||
| try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); | |||
| InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);) { | |||
| httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson())); | |||
| try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); | |||
| final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { | |||
| Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||
| if (contentTypeHeader != null && contentTypeHeader.length > 0 | |||
| && ContentType.APPLICATION_JSON.getMimeType() | |||
| @@ -7,6 +7,9 @@ import org.testng.annotations.*; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| /** | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| @@ -18,21 +21,38 @@ public class WxMaQrcodeServiceImplTest { | |||
| private WxMaService wxService; | |||
| @Test | |||
| public void testCreateQrCode() throws Exception { | |||
| public void testCreateQrcode() throws Exception { | |||
| final File qrCode = this.wxService.getQrcodeService().createQrcode("111", 122); | |||
| System.out.println(qrCode); | |||
| assertThat(qrCode).isNotNull(); | |||
| } | |||
| @Test | |||
| public void testCreateWxaCode() throws Exception { | |||
| final File wxCode = this.wxService.getQrcodeService().createWxaCode("111", 122); | |||
| System.out.println(wxCode); | |||
| assertThat(wxCode).isNotNull(); | |||
| } | |||
| @Test | |||
| public void testCreateWxaCodeUnlimit() throws Exception { | |||
| final File wxCode = this.wxService.getQrcodeService().createWxaCodeUnlimit("111", null); | |||
| System.out.println(wxCode); | |||
| assertThat(wxCode).isNotNull(); | |||
| } | |||
| @Test | |||
| public void testCreateQrcodeBytes() throws WxErrorException { | |||
| final byte[] qrCode = this.wxService.getQrcodeService().createQrcodeBytes("111", 122); | |||
| assertThat(qrCode).isNotNull(); | |||
| } | |||
| @Test | |||
| public void testCreateWxaCodeBytes() throws WxErrorException { | |||
| final byte[] wxCode = this.wxService.getQrcodeService().createWxaCodeBytes("111", 122, true, null, false); | |||
| assertThat(wxCode).isNotNull(); | |||
| } | |||
| @Test | |||
| public void testCreateWxaCodeUnlimitBytes() throws WxErrorException { | |||
| final byte[] wxCode = this.wxService.getQrcodeService().createWxaCodeUnlimitBytes("111", null, 122, true, null, false); | |||
| assertThat(wxCode).isNotNull(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import java.io.File; | |||
| import org.testng.annotations.*; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| import static org.testng.Assert.*; | |||
| /** | |||
| * <pre> | |||
| * | |||
| * Created by Binary Wang on 2018/11/24. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| @Test | |||
| @Guice(modules = ApiTestModule.class) | |||
| public class WxMaSecCheckServiceImplTest { | |||
| @Inject | |||
| private WxMaService wxService; | |||
| @Test | |||
| public void testCheckImage() throws WxErrorException { | |||
| boolean result = this.wxService.getSecCheckService() | |||
| .checkImage(new File(ClassLoader.getSystemResource("tmp.png").getFile())); | |||
| assertTrue(result); | |||
| } | |||
| @DataProvider | |||
| public Object[][] secData() { | |||
| return new Object[][]{ | |||
| {"特3456书yuuo莞6543李zxcz蒜7782法fgnv级", false}, | |||
| {"完2347全dfji试3726测asad感3847知qwez到", false}, | |||
| {"hello world!", true} | |||
| }; | |||
| } | |||
| @Test(dataProvider = "secData") | |||
| public void testCheckMessage(String msg, boolean result) { | |||
| assertThat(this.wxService.getSecCheckService() | |||
| .checkMessage(msg)) | |||
| .isEqualTo(result); | |||
| } | |||
| } | |||
| @@ -33,9 +33,4 @@ public class WxMaServiceImplTest { | |||
| assertTrue(StringUtils.isNotBlank(after)); | |||
| } | |||
| @Test | |||
| public void testImgSecCheck() throws WxErrorException { | |||
| boolean result = this.wxService.imgSecCheck(new File(ClassLoader.getSystemResource("tmp.png").getFile())); | |||
| assertTrue(result); | |||
| } | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| package cn.binarywang.wx.miniapp.api.impl; | |||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaShareInfo; | |||
| import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
| import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
| import cn.binarywang.wx.miniapp.test.TestConfig; | |||
| import com.google.common.collect.ImmutableMap; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import org.testng.annotations.Guice; | |||
| import org.testng.annotations.Test; | |||
| import static org.testng.Assert.assertNotNull; | |||
| import static org.testng.Assert.assertTrue; | |||
| /** | |||
| * 测试分享相关的接口 | |||
| * | |||
| * @author zhfish | |||
| */ | |||
| @Test | |||
| @Guice(modules = ApiTestModule.class) | |||
| public class WxMaShareServiceImplTest { | |||
| @Inject | |||
| private WxMaService wxService; | |||
| @Test | |||
| public void testGetSessionKey() throws Exception { | |||
| assertNotNull(this.wxService.getUserService().getSessionInfo("aaa")); | |||
| } | |||
| /** | |||
| * TODO 测试数据有问题,需要替换为正确的数据 | |||
| */ | |||
| @Test | |||
| public void testGetShareInfo() { | |||
| WxMaShareInfo shareInfo = this.wxService.getShareService().getShareInfo("tiihtNczf5v6AKRyjwEUhQ==", | |||
| "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==", | |||
| "r7BXXKkLb8qrSNn05n0qiA=="); | |||
| assertNotNull(shareInfo); | |||
| System.out.println(shareInfo.toString()); | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| package cn.binarywang.wx.miniapp.bean; | |||
| import java.util.List; | |||
| import org.testng.annotations.*; | |||
| import static org.assertj.core.api.Assertions.assertThat; | |||
| import static org.testng.Assert.*; | |||
| /** | |||
| * <pre> | |||
| * | |||
| * Created by Binary Wang on 2018/11/4. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class WxMaRunStepInfoTest { | |||
| @Test | |||
| public void testFromJson() { | |||
| // 数据来源:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/werun/wx.getWeRunData.html | |||
| String json = "{\n" + | |||
| " \"stepInfoList\": [\n" + | |||
| " {\n" + | |||
| " \"timestamp\": 1445866601,\n" + | |||
| " \"step\": 100\n" + | |||
| " },\n" + | |||
| " {\n" + | |||
| " \"timestamp\": 1445876601,\n" + | |||
| " \"step\": 120\n" + | |||
| " }\n" + | |||
| " ]\n" + | |||
| "}"; | |||
| final List<WxMaRunStepInfo> stepInfoList = WxMaRunStepInfo.fromJson(json); | |||
| assertThat(stepInfoList).isNotEmpty(); | |||
| assertThat(stepInfoList.get(0).getStep()).isEqualTo(100); | |||
| assertThat(stepInfoList.get(0).getTimestamp()).isEqualTo(1445866601); | |||
| assertThat(stepInfoList.get(1).getStep()).isEqualTo(120); | |||
| assertThat(stepInfoList.get(1).getTimestamp()).isEqualTo(1445876601); | |||
| } | |||
| } | |||