| @@ -1,11 +1,12 @@ | |||||
| <?xml version="1.0"?> | <?xml version="1.0"?> | ||||
| <project | <project | ||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"> | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
| xmlns="http://maven.apache.org/POM/4.0.0"> | |||||
| <modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>weixin-java-parent</artifactId> | <artifactId>weixin-java-parent</artifactId> | ||||
| <version>2.6.0</version> | |||||
| <version>2.7.0</version> | |||||
| <packaging>pom</packaging> | <packaging>pom</packaging> | ||||
| <name>WeiXin Java Tools - Parent</name> | <name>WeiXin Java Tools - Parent</name> | ||||
| <description>微信公众号、企业号上级POM</description> | <description>微信公众号、企业号上级POM</description> | ||||
| @@ -74,6 +75,11 @@ | |||||
| <email>aimilin@yeah.net</email> | <email>aimilin@yeah.net</email> | ||||
| <url>https://github.com/aimilin6688</url> | <url>https://github.com/aimilin6688</url> | ||||
| </developer> | </developer> | ||||
| <developer> | |||||
| <name>ecoolper</name> | |||||
| <email>crskyp@gmail.com</email> | |||||
| <url>https://github.com/crskyp</url> | |||||
| </developer> | |||||
| </developers> | </developers> | ||||
| <scm> | <scm> | ||||
| @@ -87,6 +93,7 @@ | |||||
| <module>weixin-java-cp</module> | <module>weixin-java-cp</module> | ||||
| <module>weixin-java-mp</module> | <module>weixin-java-mp</module> | ||||
| <module>weixin-java-pay</module> | <module>weixin-java-pay</module> | ||||
| <module>weixin-java-miniapp</module> | |||||
| <!--module>weixin-java-osgi</module--> | <!--module>weixin-java-osgi</module--> | ||||
| </modules> | </modules> | ||||
| @@ -98,73 +105,90 @@ | |||||
| <downloadJavadocs>true</downloadJavadocs> | <downloadJavadocs>true</downloadJavadocs> | ||||
| <downloadSources>true</downloadSources> | <downloadSources>true</downloadSources> | ||||
| <httpclient.version>4.5</httpclient.version> | <httpclient.version>4.5</httpclient.version> | ||||
| <slf4j.version>1.7.10</slf4j.version> | |||||
| <logback.version>1.1.2</logback.version> | |||||
| <gson.version>2.7</gson.version> | |||||
| <guava.version>19.0</guava.version> | |||||
| <commons-lang3.version>3.5</commons-lang3.version> | |||||
| <commons-io.version>2.5</commons-io.version> | |||||
| <commons-codec.version>1.10</commons-codec.version> | |||||
| <jetty.version>9.3.0.RC0</jetty.version> | <jetty.version>9.3.0.RC0</jetty.version> | ||||
| <jedis.version>2.9.0</jedis.version> | |||||
| </properties> | </properties> | ||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| <version>${slf4j.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.thoughtworks.xstream</groupId> | |||||
| <artifactId>xstream</artifactId> | |||||
| <version>1.4.9</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <version>${logback.version}</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpclient</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpmime</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.code.gson</groupId> | |||||
| <artifactId>gson</artifactId> | |||||
| <version>${gson.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| <version>${commons-codec.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| <version>${commons-io.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.commons</groupId> | |||||
| <artifactId>commons-lang3</artifactId> | |||||
| <version>${commons-lang3.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.guava</groupId> | |||||
| <artifactId>guava</artifactId> | |||||
| <version>${guava.version}</version> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| <dependencyManagement> | <dependencyManagement> | ||||
| <dependencies> | <dependencies> | ||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>qrcode-utils</artifactId> | |||||
| <version>1.1</version> | |||||
| </dependency> | |||||
| <!-- 由于jodd-http较新的3.8版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <version>3.7.1</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <version>3.7.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpclient</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpmime</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| <version>1.10</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| <version>2.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.commons</groupId> | |||||
| <artifactId>commons-lang3</artifactId> | |||||
| <version>3.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| <version>1.7.24</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.thoughtworks.xstream</groupId> | |||||
| <artifactId>xstream</artifactId> | |||||
| <version>1.4.9</version> | |||||
| </dependency> | |||||
| <!-- 由于guava较新的21.0版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>com.google.guava</groupId> | |||||
| <artifactId>guava</artifactId> | |||||
| <version>20.0</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.code.gson</groupId> | |||||
| <artifactId>gson</artifactId> | |||||
| <version>2.8.0</version> | |||||
| </dependency> | |||||
| <!-- 测试所用依赖 --> | |||||
| <dependency> | |||||
| <groupId>joda-time</groupId> | |||||
| <artifactId>joda-time</artifactId> | |||||
| <version>2.9.7</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <version>1.1.11</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>com.google.inject</groupId> | <groupId>com.google.inject</groupId> | ||||
| <artifactId>guice</artifactId> | <artifactId>guice</artifactId> | ||||
| @@ -174,7 +198,7 @@ | |||||
| <dependency> | <dependency> | ||||
| <groupId>org.testng</groupId> | <groupId>org.testng</groupId> | ||||
| <artifactId>testng</artifactId> | <artifactId>testng</artifactId> | ||||
| <version>6.8.7</version> | |||||
| <version>6.10</version> | |||||
| <scope>test</scope> | <scope>test</scope> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | <dependency> | ||||
| @@ -198,7 +222,8 @@ | |||||
| <dependency> | <dependency> | ||||
| <groupId>redis.clients</groupId> | <groupId>redis.clients</groupId> | ||||
| <artifactId>jedis</artifactId> | <artifactId>jedis</artifactId> | ||||
| <version>${jedis.version}</version> | |||||
| <version>2.9.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | </dependency> | ||||
| </dependencies> | </dependencies> | ||||
| </dependencyManagement> | </dependencyManagement> | ||||
| @@ -1,12 +0,0 @@ | |||||
| description = 'WeiXin Java Tools - Common' | |||||
| dependencies { | |||||
| compile group: 'com.thoughtworks.xstream', name: 'xstream', version:'1.4.7' | |||||
| testCompile group: 'junit', name: 'junit', version:'4.11' | |||||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0' | |||||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0' | |||||
| } | |||||
| test.useTestNG() | |||||
| @@ -1,12 +1,13 @@ | |||||
| <?xml version="1.0"?> | <?xml version="1.0"?> | ||||
| <project | <project | ||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
| xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
| xmlns="http://maven.apache.org/POM/4.0.0"> | |||||
| <modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>weixin-java-parent</artifactId> | <artifactId>weixin-java-parent</artifactId> | ||||
| <version>2.6.0</version> | |||||
| <version>2.7.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-common</artifactId> | <artifactId>weixin-java-common</artifactId> | ||||
| @@ -14,6 +15,59 @@ | |||||
| <description>微信公众号、企业号Java SDK Common</description> | <description>微信公众号、企业号Java SDK Common</description> | ||||
| <dependencies> | <dependencies> | ||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.thoughtworks.xstream</groupId> | |||||
| <artifactId>xstream</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpclient</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpmime</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.code.gson</groupId> | |||||
| <artifactId>gson</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.commons</groupId> | |||||
| <artifactId>commons-lang3</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.guava</groupId> | |||||
| <artifactId>guava</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>org.testng</groupId> | <groupId>org.testng</groupId> | ||||
| <artifactId>testng</artifactId> | <artifactId>testng</artifactId> | ||||
| @@ -7,13 +7,13 @@ import java.lang.annotation.Target; | |||||
| /** | /** | ||||
| * 标识某个字段是否是必填的 | * 标识某个字段是否是必填的 | ||||
| * | |||||
| * <p> | |||||
| * Created by Binary Wang on 2016/9/25. | * Created by Binary Wang on 2016/9/25. | ||||
| * @author binarywang (https://github.com/binarywang) | |||||
| * | * | ||||
| * @author binarywang (https://github.com/binarywang) | |||||
| */ | */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target(ElementType.FIELD) | @Target(ElementType.FIELD) | ||||
| public @interface Required { | public @interface Required { | ||||
| } | |||||
| } | |||||
| @@ -24,7 +24,6 @@ public class WxConsts { | |||||
| public static final String XML_MSG_HARDWARE = "hardware"; | public static final String XML_MSG_HARDWARE = "hardware"; | ||||
| public static final String XML_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; | public static final String XML_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; | ||||
| /////////////////////// | /////////////////////// | ||||
| // 主动发送消息(即客服消息)的消息类型 | // 主动发送消息(即客服消息)的消息类型 | ||||
| /////////////////////// | /////////////////////// | ||||
| @@ -88,45 +87,6 @@ public class WxConsts { | |||||
| public static final String EVT_LOCATION_SELECT = "location_select"; | public static final String EVT_LOCATION_SELECT = "location_select"; | ||||
| public static final String EVT_TEMPLATESENDJOBFINISH = "TEMPLATESENDJOBFINISH"; | public static final String EVT_TEMPLATESENDJOBFINISH = "TEMPLATESENDJOBFINISH"; | ||||
| public static final String EVT_ENTER_AGENT = "enter_agent"; | public static final String EVT_ENTER_AGENT = "enter_agent"; | ||||
| public static final String EVT_CARD_PASS_CHECK = "card_pass_check"; | |||||
| public static final String EVT_CARD_NOT_PASS_CHECK = "card_not_pass_check"; | |||||
| public static final String EVT_USER_GET_CARD = "user_get_card"; | |||||
| public static final String EVT_USER_DEL_CARD = "user_del_card"; | |||||
| public static final String EVT_USER_CONSUME_CARD = "user_consume_card"; | |||||
| public static final String EVT_USER_PAY_FROM_PAY_CELL = "user_pay_from_pay_cell"; | |||||
| public static final String EVT_USER_VIEW_CARD = "user_view_card"; | |||||
| public static final String EVT_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card"; | |||||
| public static final String EVT_CARD_SKU_REMIND = "card_sku_remind"; // 库存报警 | |||||
| public static final String EVT_KF_CREATE_SESSION = "kf_create_session"; // 客服接入会话 | |||||
| public static final String EVT_KF_CLOSE_SESSION = "kf_close_session"; // 客服关闭会话 | |||||
| public static final String EVT_KF_SWITCH_SESSION = "kf_switch_session"; // 客服转接会话 | |||||
| public static final String EVT_POI_CHECK_NOTIFY = "poi_check_notify"; //门店审核事件推送 | |||||
| public static final String EVN_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info"; //接收会员信息事件推送 | |||||
| //以下为微信认证事件 | |||||
| /** | |||||
| * 资质认证成功 | |||||
| */ | |||||
| public static final String EVT_QUALIFICATION_VERIFY_SUCCESS = "qualification_verify_success"; | |||||
| /** | |||||
| * 资质认证失败 | |||||
| */ | |||||
| public static final String EVT_QUALIFICATION_VERIFY_FAIL = "qualification_verify_fail"; | |||||
| /** | |||||
| * 名称认证成功 | |||||
| */ | |||||
| public static final String EVT_NAMING_VERIFY_SUCCESS = "naming_verify_success"; | |||||
| /** | |||||
| * 名称认证失败 | |||||
| */ | |||||
| public static final String EVT_NAMING_VERIFY_FAIL = "naming_verify_fail"; | |||||
| /** | |||||
| * 年审通知 | |||||
| */ | |||||
| public static final String EVT_ANNUAL_RENEW = "annual_renew"; | |||||
| /** | |||||
| * 认证过期失效通知 | |||||
| */ | |||||
| public static final String EVT_VERIFY_EXPIRED = "verify_expired"; | |||||
| /////////////////////// | /////////////////////// | ||||
| // 上传多媒体文件的类型 | // 上传多媒体文件的类型 | ||||
| @@ -185,7 +145,7 @@ public class WxConsts { | |||||
| * 跳转图文消息URL | * 跳转图文消息URL | ||||
| */ | */ | ||||
| public static final String BUTTON_VIEW_LIMITED = "view_limited"; | public static final String BUTTON_VIEW_LIMITED = "view_limited"; | ||||
| /** | /** | ||||
| * 不弹出授权页面,直接跳转,只能获取用户openid | * 不弹出授权页面,直接跳转,只能获取用户openid | ||||
| */ | */ | ||||
| @@ -1,6 +1,8 @@ | |||||
| package me.chanjar.weixin.common.bean.result; | package me.chanjar.weixin.common.bean.result; | ||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | import me.chanjar.weixin.common.util.json.WxGsonBuilder; | ||||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| @@ -50,8 +52,7 @@ public class WxMediaUploadResult implements Serializable { | |||||
| @Override | @Override | ||||
| public String toString() { | public String toString() { | ||||
| return "WxUploadResult [type=" + this.type + ", media_id=" + this.mediaId + ", thumb_media_id=" + this.thumbMediaId | |||||
| + ", created_at=" + this.createdAt + "]"; | |||||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); | |||||
| } | } | ||||
| } | } | ||||
| @@ -14,7 +14,7 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||||
| public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | ||||
| protected static final StringManager sm = | protected static final StringManager sm = | ||||
| StringManager.getManager(Constants.Package); | |||||
| StringManager.getManager(Constants.Package); | |||||
| /** | /** | ||||
| * The descriptive name of this Manager implementation (for logging). | * The descriptive name of this Manager implementation (for logging). | ||||
| */ | */ | ||||
| @@ -82,7 +82,7 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||||
| public WxSession getSession(String sessionId, boolean create) { | public WxSession getSession(String sessionId, boolean create) { | ||||
| if (sessionId == null) { | if (sessionId == null) { | ||||
| throw new IllegalStateException | throw new IllegalStateException | ||||
| (sm.getString("sessionManagerImpl.getSession.ise")); | |||||
| (sm.getString("sessionManagerImpl.getSession.ise")); | |||||
| } | } | ||||
| InternalSession session = findSession(sessionId); | InternalSession session = findSession(sessionId); | ||||
| @@ -135,15 +135,15 @@ public class StandardSessionManager implements WxSessionManager, InternalSession | |||||
| public InternalSession createSession(String sessionId) { | public InternalSession createSession(String sessionId) { | ||||
| if (sessionId == null) { | if (sessionId == null) { | ||||
| throw new IllegalStateException | throw new IllegalStateException | ||||
| (sm.getString("sessionManagerImpl.createSession.ise")); | |||||
| (sm.getString("sessionManagerImpl.createSession.ise")); | |||||
| } | } | ||||
| if ((this.maxActiveSessions >= 0) && | if ((this.maxActiveSessions >= 0) && | ||||
| (getActiveSessions() >= this.maxActiveSessions)) { | |||||
| (getActiveSessions() >= this.maxActiveSessions)) { | |||||
| this.rejectedSessions++; | this.rejectedSessions++; | ||||
| throw new TooManyActiveSessionsException( | throw new TooManyActiveSessionsException( | ||||
| sm.getString("sessionManagerImpl.createSession.tmase"), | |||||
| this.maxActiveSessions); | |||||
| sm.getString("sessionManagerImpl.createSession.tmase"), | |||||
| this.maxActiveSessions); | |||||
| } | } | ||||
| // Recycle or create a Session instance | // Recycle or create a Session instance | ||||
| @@ -21,7 +21,7 @@ package me.chanjar.weixin.common.session; | |||||
| * reached and the server is refusing to create any new sessions. | * reached and the server is refusing to create any new sessions. | ||||
| */ | */ | ||||
| public class TooManyActiveSessionsException | public class TooManyActiveSessionsException | ||||
| extends IllegalStateException { | |||||
| extends IllegalStateException { | |||||
| private static final long serialVersionUID = 1L; | private static final long serialVersionUID = 1L; | ||||
| /** | /** | ||||
| @@ -13,25 +13,10 @@ import org.apache.commons.lang3.builder.ToStringStyle; | |||||
| */ | */ | ||||
| public class ToStringUtils { | public class ToStringUtils { | ||||
| public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle(); | public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle(); | ||||
| private static class SimpleMultiLineToStringStyle extends ToStringStyle { | |||||
| private static final long serialVersionUID = 4645306494220335355L; | |||||
| private static final String LINE_SEPARATOR = "\n"; | |||||
| private static final String NULL_TEXT = "<null>"; | |||||
| public SimpleMultiLineToStringStyle() { | |||||
| super(); | |||||
| this.setContentStart("["); | |||||
| this.setFieldSeparator(LINE_SEPARATOR + " "); | |||||
| this.setFieldSeparatorAtStart(true); | |||||
| this.setContentEnd(LINE_SEPARATOR + "]"); | |||||
| this.setNullText(NULL_TEXT); | |||||
| this.setUseShortClassName(true); | |||||
| this.setUseIdentityHashCode(false); | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串 | * 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串 | ||||
| * | |||||
| * @param obj | * @param obj | ||||
| */ | */ | ||||
| public static String toSimpleString(Object obj) { | public static String toSimpleString(Object obj) { | ||||
| @@ -58,4 +43,21 @@ public class ToStringUtils { | |||||
| return result.deleteCharAt(result.length() - 1).toString(); | return result.deleteCharAt(result.length() - 1).toString(); | ||||
| } | } | ||||
| private static class SimpleMultiLineToStringStyle extends ToStringStyle { | |||||
| private static final long serialVersionUID = 4645306494220335355L; | |||||
| private static final String LINE_SEPARATOR = "\n"; | |||||
| private static final String NULL_TEXT = "<null>"; | |||||
| public SimpleMultiLineToStringStyle() { | |||||
| super(); | |||||
| this.setContentStart("["); | |||||
| this.setFieldSeparator(LINE_SEPARATOR + " "); | |||||
| this.setFieldSeparatorAtStart(true); | |||||
| this.setContentEnd(LINE_SEPARATOR + "]"); | |||||
| this.setNullText(NULL_TEXT); | |||||
| this.setUseShortClassName(true); | |||||
| this.setUseIdentityHashCode(false); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -1,8 +1,9 @@ | |||||
| package me.chanjar.weixin.common.util.crypto; | package me.chanjar.weixin.common.util.crypto; | ||||
| import java.util.Arrays; | |||||
| import org.apache.commons.codec.digest.DigestUtils; | import org.apache.commons.codec.digest.DigestUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.util.Arrays; | |||||
| /** | /** | ||||
| * Created by Daniel Qian on 14/10/19. | * Created by Daniel Qian on 14/10/19. | ||||
| @@ -13,6 +14,10 @@ public class SHA1 { | |||||
| * 串接arr参数,生成sha1 digest | * 串接arr参数,生成sha1 digest | ||||
| */ | */ | ||||
| public static String gen(String... arr) { | public static String gen(String... arr) { | ||||
| if (StringUtils.isAnyEmpty(arr)) { | |||||
| throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " + Arrays.toString(arr)); | |||||
| } | |||||
| Arrays.sort(arr); | Arrays.sort(arr); | ||||
| StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
| for (String a : arr) { | for (String a : arr) { | ||||
| @@ -25,6 +30,10 @@ public class SHA1 { | |||||
| * 用&串接arr参数,生成sha1 digest | * 用&串接arr参数,生成sha1 digest | ||||
| */ | */ | ||||
| public static String genWithAmple(String... arr) { | public static String genWithAmple(String... arr) { | ||||
| if (StringUtils.isAnyEmpty(arr)) { | |||||
| throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " + Arrays.toString(arr)); | |||||
| } | |||||
| Arrays.sort(arr); | Arrays.sort(arr); | ||||
| StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
| for (int i = 0; i < arr.length; i++) { | for (int i = 0; i < arr.length; i++) { | ||||
| @@ -1,26 +1,9 @@ | |||||
| /** | |||||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||||
| * | |||||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||||
| * <p> | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| // ------------------------------------------------------------------------ | |||||
| /** | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| package me.chanjar.weixin.common.util.crypto; | package me.chanjar.weixin.common.util.crypto; | ||||
| import java.io.StringReader; | |||||
| import java.nio.charset.Charset; | |||||
| 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; | |||||
| import javax.crypto.Cipher; | import javax.crypto.Cipher; | ||||
| import javax.crypto.spec.IvParameterSpec; | import javax.crypto.spec.IvParameterSpec; | ||||
| @@ -28,12 +11,19 @@ import javax.crypto.spec.SecretKeySpec; | |||||
| import javax.xml.parsers.DocumentBuilder; | import javax.xml.parsers.DocumentBuilder; | ||||
| import javax.xml.parsers.DocumentBuilderFactory; | import javax.xml.parsers.DocumentBuilderFactory; | ||||
| import javax.xml.parsers.ParserConfigurationException; | import javax.xml.parsers.ParserConfigurationException; | ||||
| import java.io.StringReader; | |||||
| import java.nio.charset.Charset; | |||||
| 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; | |||||
| /** | |||||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||||
| * | |||||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| public class WxCryptUtil { | public class WxCryptUtil { | ||||
| private static final Base64 base64 = new Base64(); | private static final Base64 base64 = new Base64(); | ||||
| @@ -61,9 +51,9 @@ public class WxCryptUtil { | |||||
| /** | /** | ||||
| * 构造函数 | * 构造函数 | ||||
| * | * | ||||
| * @param token 公众平台上,开发者设置的token | |||||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
| * @param appidOrCorpid 公众平台appid/corpid | |||||
| * @param token 公众平台上,开发者设置的token | |||||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
| * @param appidOrCorpid 公众平台appid/corpid | |||||
| */ | */ | ||||
| public WxCryptUtil(String token, String encodingAesKey, | public WxCryptUtil(String token, String encodingAesKey, | ||||
| String appidOrCorpid) { | String appidOrCorpid) { | ||||
| @@ -84,6 +74,66 @@ public class WxCryptUtil { | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||||
| * | |||||
| * @param number | |||||
| */ | |||||
| private static byte[] number2BytesInNetworkOrder(int number) { | |||||
| byte[] orderBytes = new byte[4]; | |||||
| orderBytes[3] = (byte) (number & 0xFF); | |||||
| orderBytes[2] = (byte) (number >> 8 & 0xFF); | |||||
| orderBytes[1] = (byte) (number >> 16 & 0xFF); | |||||
| orderBytes[0] = (byte) (number >> 24 & 0xFF); | |||||
| return orderBytes; | |||||
| } | |||||
| /** | |||||
| * 4个字节的网络字节序bytes数组还原成一个数字 | |||||
| * | |||||
| * @param bytesInNetworkOrder | |||||
| */ | |||||
| private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { | |||||
| int sourceNumber = 0; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| sourceNumber <<= 8; | |||||
| sourceNumber |= bytesInNetworkOrder[i] & 0xff; | |||||
| } | |||||
| return sourceNumber; | |||||
| } | |||||
| /** | |||||
| * 随机生成16位字符串 | |||||
| */ | |||||
| private static String genRandomStr() { | |||||
| String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |||||
| Random random = new Random(); | |||||
| StringBuffer sb = new StringBuffer(); | |||||
| for (int i = 0; i < 16; i++) { | |||||
| int number = random.nextInt(base.length()); | |||||
| sb.append(base.charAt(number)); | |||||
| } | |||||
| return sb.toString(); | |||||
| } | |||||
| /** | |||||
| * 生成xml消息 | |||||
| * | |||||
| * @param encrypt 加密后的消息密文 | |||||
| * @param signature 安全签名 | |||||
| * @param timestamp 时间戳 | |||||
| * @param nonce 随机字符串 | |||||
| * @return 生成的xml字符串 | |||||
| */ | |||||
| private static String generateXml(String encrypt, String signature, | |||||
| String timestamp, String nonce) { | |||||
| String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||||
| + "</xml>"; | |||||
| return String.format(format, encrypt, signature, timestamp, nonce); | |||||
| } | |||||
| /** | /** | ||||
| * 将公众平台回复用户的消息加密打包. | * 将公众平台回复用户的消息加密打包. | ||||
| * <ol> | * <ol> | ||||
| @@ -119,7 +169,7 @@ public class WxCryptUtil { | |||||
| byte[] randomStringBytes = randomStr.getBytes(CHARSET); | byte[] randomStringBytes = randomStr.getBytes(CHARSET); | ||||
| byte[] plainTextBytes = plainText.getBytes(CHARSET); | byte[] plainTextBytes = plainText.getBytes(CHARSET); | ||||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | ||||
| plainTextBytes.length); | |||||
| plainTextBytes.length); | |||||
| byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET); | byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET); | ||||
| // randomStr + networkBytesOrder + text + appid | // randomStr + networkBytesOrder + text + appid | ||||
| @@ -168,8 +218,7 @@ public class WxCryptUtil { | |||||
| * @param encryptedXml 密文,对应POST请求的数据 | * @param encryptedXml 密文,对应POST请求的数据 | ||||
| * @return 解密后的原文 | * @return 解密后的原文 | ||||
| */ | */ | ||||
| public String decrypt(String msgSignature, String timeStamp, String nonce, | |||||
| String encryptedXml) { | |||||
| public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { | |||||
| // 密钥,公众账号的app corpSecret | // 密钥,公众账号的app corpSecret | ||||
| // 提取密文 | // 提取密文 | ||||
| String cipherText = extractEncryptPart(encryptedXml); | String cipherText = extractEncryptPart(encryptedXml); | ||||
| @@ -198,7 +247,7 @@ public class WxCryptUtil { | |||||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | ||||
| SecretKeySpec key_spec = new SecretKeySpec(this.aesKey, "AES"); | SecretKeySpec key_spec = new SecretKeySpec(this.aesKey, "AES"); | ||||
| IvParameterSpec iv = new IvParameterSpec( | IvParameterSpec iv = new IvParameterSpec( | ||||
| Arrays.copyOfRange(this.aesKey, 0, 16)); | |||||
| Arrays.copyOfRange(this.aesKey, 0, 16)); | |||||
| cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | ||||
| // 使用BASE64对密文进行解码 | // 使用BASE64对密文进行解码 | ||||
| @@ -221,9 +270,9 @@ public class WxCryptUtil { | |||||
| int xmlLength = bytesNetworkOrder2Number(networkOrder); | int xmlLength = bytesNetworkOrder2Number(networkOrder); | ||||
| xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | ||||
| CHARSET); | |||||
| CHARSET); | |||||
| from_appid = new String( | from_appid = new String( | ||||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| throw new RuntimeException(e); | throw new RuntimeException(e); | ||||
| } | } | ||||
| @@ -237,64 +286,4 @@ public class WxCryptUtil { | |||||
| } | } | ||||
| /** | |||||
| * 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||||
| * | |||||
| * @param number | |||||
| */ | |||||
| private static byte[] number2BytesInNetworkOrder(int number) { | |||||
| byte[] orderBytes = new byte[4]; | |||||
| orderBytes[3] = (byte) (number & 0xFF); | |||||
| orderBytes[2] = (byte) (number >> 8 & 0xFF); | |||||
| orderBytes[1] = (byte) (number >> 16 & 0xFF); | |||||
| orderBytes[0] = (byte) (number >> 24 & 0xFF); | |||||
| return orderBytes; | |||||
| } | |||||
| /** | |||||
| * 4个字节的网络字节序bytes数组还原成一个数字 | |||||
| * | |||||
| * @param bytesInNetworkOrder | |||||
| */ | |||||
| private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { | |||||
| int sourceNumber = 0; | |||||
| for (int i = 0; i < 4; i++) { | |||||
| sourceNumber <<= 8; | |||||
| sourceNumber |= bytesInNetworkOrder[i] & 0xff; | |||||
| } | |||||
| return sourceNumber; | |||||
| } | |||||
| /** | |||||
| * 随机生成16位字符串 | |||||
| */ | |||||
| private static String genRandomStr() { | |||||
| String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |||||
| Random random = new Random(); | |||||
| StringBuffer sb = new StringBuffer(); | |||||
| for (int i = 0; i < 16; i++) { | |||||
| int number = random.nextInt(base.length()); | |||||
| sb.append(base.charAt(number)); | |||||
| } | |||||
| return sb.toString(); | |||||
| } | |||||
| /** | |||||
| * 生成xml消息 | |||||
| * | |||||
| * @param encrypt 加密后的消息密文 | |||||
| * @param signature 安全签名 | |||||
| * @param timestamp 时间戳 | |||||
| * @param nonce 随机字符串 | |||||
| * @return 生成的xml字符串 | |||||
| */ | |||||
| private static String generateXml(String encrypt, String signature, | |||||
| String timestamp, String nonce) { | |||||
| String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||||
| + "</xml>"; | |||||
| return String.format(format, encrypt, signature, timestamp, nonce); | |||||
| } | |||||
| } | } | ||||
| @@ -25,7 +25,7 @@ public class FileUtils { | |||||
| } | } | ||||
| tmpFile.deleteOnExit(); | tmpFile.deleteOnExit(); | ||||
| try (FileOutputStream fos = new FileOutputStream(tmpFile)) { | try (FileOutputStream fos = new FileOutputStream(tmpFile)) { | ||||
| int read = 0; | int read = 0; | ||||
| byte[] bytes = new byte[1024 * 100]; | byte[] bytes = new byte[1024 * 100]; | ||||
| @@ -35,7 +35,7 @@ public class FileUtils { | |||||
| fos.flush(); | fos.flush(); | ||||
| return tmpFile; | return tmpFile; | ||||
| } | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -0,0 +1,8 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| /** | |||||
| * Created by ecoolper on 2017/4/28. | |||||
| */ | |||||
| public enum HttpType { | |||||
| JODD_HTTP, APACHE_HTTP, OK_HTTP; | |||||
| } | |||||
| @@ -1,93 +1,37 @@ | |||||
| package me.chanjar.weixin.common.util.http; | package me.chanjar.weixin.common.util.http; | ||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| 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.HttpGet; | |||||
| import org.apache.http.entity.ContentType; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| 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; | import java.io.File; | ||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | |||||
| /** | /** | ||||
| * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File | * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File | ||||
| * 视频文件不支持下载 | * 视频文件不支持下载 | ||||
| * | |||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| */ | */ | ||||
| public class MediaDownloadRequestExecutor implements RequestExecutor<File, String> { | |||||
| public abstract class MediaDownloadRequestExecutor<H, P> implements RequestExecutor<File, String> { | |||||
| private File tmpDirFile; | |||||
| public MediaDownloadRequestExecutor() { | |||||
| } | |||||
| public MediaDownloadRequestExecutor(File tmpDirFile) { | |||||
| protected RequestHttp<H, P> requestHttp; | |||||
| protected File tmpDirFile; | |||||
| public MediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { | |||||
| this.requestHttp = requestHttp; | |||||
| this.tmpDirFile = tmpDirFile; | this.tmpDirFile = tmpDirFile; | ||||
| } | } | ||||
| @Override | |||||
| public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpGet httpGet = new HttpGet(uri); | |||||
| if (httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| try (CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
| InputStream inputStream = InputStreamResponseHandler.INSTANCE | |||||
| .handleResponse(response)) { | |||||
| Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||||
| if (contentTypeHeader != null && contentTypeHeader.length > 0) { | |||||
| if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) { | |||||
| // application/json; encoding=utf-8 下载媒体文件出错 | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| throw new WxErrorException(WxError.fromJson(responseContent)); | |||||
| } | |||||
| } | |||||
| String fileName = getFileName(response); | |||||
| if (StringUtils.isBlank(fileName)) { | |||||
| public static RequestExecutor<File, String> create(RequestHttp requestHttp, File tmpDirFile) { | |||||
| switch (requestHttp.getRequestType()) { | |||||
| case APACHE_HTTP: | |||||
| return new ApacheMediaDownloadRequestExecutor(requestHttp, tmpDirFile); | |||||
| case JODD_HTTP: | |||||
| return new JoddHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); | |||||
| case OK_HTTP: | |||||
| return new OkHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile); | |||||
| default: | |||||
| return null; | return null; | ||||
| } | |||||
| String[] nameAndExt = fileName.split("\\."); | |||||
| return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], this.tmpDirFile); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| } | |||||
| private String getFileName(CloseableHttpResponse response) throws WxErrorException { | |||||
| Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); | |||||
| if(contentDispositionHeader == null || contentDispositionHeader.length == 0){ | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| Pattern p = Pattern.compile(".*filename=\"(.*)\""); | |||||
| Matcher m = p.matcher(contentDispositionHeader[0].getValue()); | |||||
| if(m.matches()){ | |||||
| return m.group(1); | |||||
| } | } | ||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,53 +1,34 @@ | |||||
| package me.chanjar.weixin.common.util.http; | package me.chanjar.weixin.common.util.http; | ||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import org.apache.http.HttpEntity; | |||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.client.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.mime.HttpMultipartMode; | |||||
| import org.apache.http.entity.mime.MultipartEntityBuilder; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| 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; | import java.io.File; | ||||
| import java.io.IOException; | |||||
| /** | /** | ||||
| * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | ||||
| * | * | ||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| */ | */ | ||||
| public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUploadResult, File> { | |||||
| public abstract class MediaUploadRequestExecutor<H, P> implements RequestExecutor<WxMediaUploadResult, File> { | |||||
| protected RequestHttp<H, P> requestHttp; | |||||
| @Override | |||||
| public WxMediaUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File file) throws WxErrorException, IOException { | |||||
| HttpPost httpPost = new HttpPost(uri); | |||||
| if (httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
| httpPost.setConfig(config); | |||||
| } | |||||
| if (file != null) { | |||||
| HttpEntity entity = MultipartEntityBuilder | |||||
| .create() | |||||
| .addBinaryBody("media", file) | |||||
| .setMode(HttpMultipartMode.RFC6532) | |||||
| .build(); | |||||
| httpPost.setEntity(entity); | |||||
| httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString()); | |||||
| } | |||||
| try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return WxMediaUploadResult.fromJson(responseContent); | |||||
| } finally { | |||||
| httpPost.releaseConnection(); | |||||
| public MediaUploadRequestExecutor(RequestHttp requestHttp) { | |||||
| this.requestHttp = requestHttp; | |||||
| } | |||||
| public static RequestExecutor<WxMediaUploadResult, File> create(RequestHttp requestHttp) { | |||||
| switch (requestHttp.getRequestType()) { | |||||
| case APACHE_HTTP: | |||||
| return new ApacheMediaUploadRequestExecutor(requestHttp); | |||||
| case JODD_HTTP: | |||||
| return new JoddHttpMediaUploadRequestExecutor(requestHttp); | |||||
| case OK_HTTP: | |||||
| return new OkHttpMediaUploadRequestExecutor(requestHttp); | |||||
| default: | |||||
| return null; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,8 +1,6 @@ | |||||
| package me.chanjar.weixin.common.util.http; | package me.chanjar.weixin.common.util.http; | ||||
| import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| @@ -15,13 +13,47 @@ import java.io.IOException; | |||||
| public interface RequestExecutor<T, E> { | public interface RequestExecutor<T, E> { | ||||
| /** | /** | ||||
| * @param httpclient 传入的httpClient | |||||
| * @param httpProxy http代理对象,如果没有配置代理则为空 | |||||
| * @param uri uri | |||||
| * @param data 数据 | |||||
| * @param uri uri | |||||
| * @param data 数据 | |||||
| * @throws WxErrorException | * @throws WxErrorException | ||||
| * @throws IOException | * @throws IOException | ||||
| */ | */ | ||||
| T execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException; | |||||
| T execute(String uri, E data) throws WxErrorException, IOException; | |||||
| /** | |||||
| * apache-http实现方式 | |||||
| * @param httpclient | |||||
| * @param httpProxy | |||||
| * @param uri | |||||
| * @param data | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| * @throws IOException | |||||
| *//* | |||||
| T executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException; | |||||
| *//** | |||||
| * jodd-http实现方式 | |||||
| * @param provider | |||||
| * @param proxyInfo | |||||
| * @param uri | |||||
| * @param data | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| * @throws IOException | |||||
| *//* | |||||
| T executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, E data) throws WxErrorException, IOException; | |||||
| *//** okhttp实现方式 | |||||
| * @param pool | |||||
| * @param proxyInfo | |||||
| * @param uri | |||||
| * @param data | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| * @throws IOException | |||||
| *//* | |||||
| T executeOkhttp(ConnectionPool pool, final OkHttpProxyInfo proxyInfo, String uri, E data) throws WxErrorException, IOException; | |||||
| */ | |||||
| } | } | ||||
| @@ -0,0 +1,22 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| /** | |||||
| * Created by ecoolper on 2017/4/22. | |||||
| */ | |||||
| public interface RequestHttp<H, P> { | |||||
| /** | |||||
| * 返回httpClient | |||||
| * | |||||
| */ | |||||
| H getRequestHttpClient(); | |||||
| /** | |||||
| * 返回httpProxy | |||||
| * | |||||
| */ | |||||
| P getRequestHttpProxy(); | |||||
| HttpType getRequestType(); | |||||
| } | |||||
| @@ -1,45 +1,31 @@ | |||||
| package me.chanjar.weixin.common.util.http; | package me.chanjar.weixin.common.util.http; | ||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.client.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.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 | * @author Daniel Qian | ||||
| */ | */ | ||||
| public class SimpleGetRequestExecutor implements RequestExecutor<String, String> { | |||||
| public abstract class SimpleGetRequestExecutor<H, P> implements RequestExecutor<String, String> { | |||||
| protected RequestHttp<H, P> requestHttp; | |||||
| @Override | |||||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpGet httpGet = new HttpGet(uri); | |||||
| if (httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| public SimpleGetRequestExecutor(RequestHttp<H, P> requestHttp) { | |||||
| this.requestHttp = requestHttp; | |||||
| } | |||||
| try (CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| public static RequestExecutor<String, String> create(RequestHttp requestHttp) { | |||||
| switch (requestHttp.getRequestType()) { | |||||
| case APACHE_HTTP: | |||||
| return new ApacheHttpClientSimpleGetRequestExecutor(requestHttp); | |||||
| case JODD_HTTP: | |||||
| return new JoddHttpSimpleGetRequestExecutor(requestHttp); | |||||
| case OK_HTTP: | |||||
| return new OkHttpSimpleGetRequestExecutor(requestHttp); | |||||
| default: | |||||
| throw new IllegalArgumentException("非法请求参数"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,58 +1,32 @@ | |||||
| package me.chanjar.weixin.common.util.http; | package me.chanjar.weixin.common.util.http; | ||||
| import java.io.IOException; | |||||
| import org.apache.http.Consts; | |||||
| 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.StringEntity; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.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; | |||||
| /** | /** | ||||
| * 用装饰模式实现 | |||||
| * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | ||||
| * | * | ||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| */ | */ | ||||
| public class SimplePostRequestExecutor implements RequestExecutor<String, String> { | |||||
| @Override | |||||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String postEntity) throws WxErrorException, IOException { | |||||
| HttpPost httpPost = new HttpPost(uri); | |||||
| if (httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
| httpPost.setConfig(config); | |||||
| } | |||||
| public abstract class SimplePostRequestExecutor<H, P> implements RequestExecutor<String, String> { | |||||
| protected RequestHttp<H, P> requestHttp; | |||||
| if (postEntity != null) { | |||||
| StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); | |||||
| httpPost.setEntity(entity); | |||||
| } | |||||
| try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| if (responseContent.isEmpty()) { | |||||
| throw new WxErrorException( | |||||
| WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容") | |||||
| .build()); | |||||
| } | |||||
| if (responseContent.startsWith("<xml>")) { | |||||
| //xml格式输出直接返回 | |||||
| return responseContent; | |||||
| } | |||||
| public SimplePostRequestExecutor(RequestHttp requestHttp) { | |||||
| this.requestHttp = requestHttp; | |||||
| } | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } finally { | |||||
| httpPost.releaseConnection(); | |||||
| public static RequestExecutor<String, String> create(RequestHttp requestHttp) { | |||||
| switch (requestHttp.getRequestType()) { | |||||
| case APACHE_HTTP: | |||||
| return new ApacheSimplePostRequestExecutor(requestHttp); | |||||
| case JODD_HTTP: | |||||
| return new JoddHttpSimplePostRequestExecutor(requestHttp); | |||||
| case OK_HTTP: | |||||
| return new OkHttpSimplePostRequestExecutor(requestHttp); | |||||
| default: | |||||
| return null; | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,61 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| import org.apache.http.conn.DnsResolver; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.net.InetAddress; | |||||
| import java.net.UnknownHostException; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * 微信DNS域名解析器,将微信域名绑定到指定IP | |||||
| * -------------------------------------------- | |||||
| * 适用于服务器端调用微信服务器需要开通出口防火墙情况 | |||||
| * <p> | |||||
| * Created by Andy Huo on 17/03/28. | |||||
| */ | |||||
| public class WxDnsResolver implements DnsResolver { | |||||
| private final static String WECHAT_API_URL = "api.weixin.qq.com"; | |||||
| private static Map<String, InetAddress[]> MAPPINGS = new HashMap<String, InetAddress[]>(); | |||||
| protected final Logger log = LoggerFactory.getLogger(WxDnsResolver.class); | |||||
| private String wxApiIp; | |||||
| public WxDnsResolver(String ip) { | |||||
| this.wxApiIp = ip; | |||||
| this.init(); | |||||
| } | |||||
| private void init() { | |||||
| if (log.isDebugEnabled()) { | |||||
| log.debug("init wechat dns config with ip {}", wxApiIp); | |||||
| } | |||||
| try { | |||||
| MAPPINGS.put(WECHAT_API_URL, new InetAddress[]{InetAddress.getByName(wxApiIp)}); | |||||
| } catch (UnknownHostException e) { | |||||
| //如果初始化DNS配置失败则使用默认配置,不影响服务的启动 | |||||
| log.error("init WxDnsResolver error", e); | |||||
| MAPPINGS = new HashMap<String, InetAddress[]>(); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public InetAddress[] resolve(String host) throws UnknownHostException { | |||||
| return MAPPINGS.containsKey(host) ? MAPPINGS.get(host) : new InetAddress[0]; | |||||
| } | |||||
| public String getWxApiIp() { | |||||
| return wxApiIp; | |||||
| } | |||||
| public void setWxApiIp(String wxApiIp) { | |||||
| this.wxApiIp = wxApiIp; | |||||
| this.init(); | |||||
| } | |||||
| } | |||||
| @@ -1,10 +1,11 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | ||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
| /** | /** | ||||
| * httpclient build interface | * httpclient build interface | ||||
| * | |||||
| * @author kakotor | * @author kakotor | ||||
| */ | */ | ||||
| public interface ApacheHttpClientBuilder { | public interface ApacheHttpClientBuilder { | ||||
| @@ -0,0 +1,50 @@ | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| 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; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class ApacheHttpClientSimpleGetRequestExecutor extends SimpleGetRequestExecutor<CloseableHttpClient, HttpHost> { | |||||
| public ApacheHttpClientSimpleGetRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpGet httpGet = new HttpGet(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,306 @@ | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.apache.http.annotation.NotThreadSafe; | |||||
| import org.apache.http.auth.AuthScope; | |||||
| import org.apache.http.auth.UsernamePasswordCredentials; | |||||
| import org.apache.http.client.CredentialsProvider; | |||||
| import org.apache.http.client.HttpRequestRetryHandler; | |||||
| import org.apache.http.client.config.RequestConfig; | |||||
| import org.apache.http.config.Registry; | |||||
| import org.apache.http.config.RegistryBuilder; | |||||
| import org.apache.http.config.SocketConfig; | |||||
| import org.apache.http.conn.DnsResolver; | |||||
| import org.apache.http.conn.HttpClientConnectionManager; | |||||
| import org.apache.http.conn.socket.ConnectionSocketFactory; | |||||
| import org.apache.http.conn.socket.PlainConnectionSocketFactory; | |||||
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||||
| import org.apache.http.impl.client.BasicCredentialsProvider; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import org.apache.http.impl.client.HttpClientBuilder; | |||||
| import org.apache.http.impl.client.HttpClients; | |||||
| import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | |||||
| 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 | |||||
| * | |||||
| * @author Andy.Huo | |||||
| */ | |||||
| @NotThreadSafe | |||||
| public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder { | |||||
| protected final Logger log = LoggerFactory.getLogger(ApacheHttpDnsClientBuilder.class); | |||||
| private final AtomicBoolean prepared = new AtomicBoolean(false); | |||||
| private int connectionRequestTimeout = 3000; | |||||
| private int connectionTimeout = 5000; | |||||
| private int soTimeout = 5000; | |||||
| private int idleConnTimeout = 60000; | |||||
| private int checkWaitTime = 60000; | |||||
| private int maxConnPerHost = 10; | |||||
| private int maxTotalConn = 50; | |||||
| private String userAgent; | |||||
| private DnsResolver dnsResover; | |||||
| private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { | |||||
| @Override | |||||
| public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); | |||||
| private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); | |||||
| private String httpProxyHost; | |||||
| private int httpProxyPort; | |||||
| private String httpProxyUsername; | |||||
| private String httpProxyPassword; | |||||
| /** | |||||
| * 闲置连接监控线程 | |||||
| */ | |||||
| private IdleConnectionMonitorThread idleConnectionMonitorThread; | |||||
| private HttpClientBuilder httpClientBuilder; | |||||
| private ApacheHttpDnsClientBuilder() { | |||||
| } | |||||
| public static ApacheHttpDnsClientBuilder get() { | |||||
| return new ApacheHttpDnsClientBuilder(); | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) { | |||||
| this.httpProxyHost = httpProxyHost; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) { | |||||
| this.httpProxyPort = httpProxyPort; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) { | |||||
| this.httpProxyUsername = httpProxyUsername; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) { | |||||
| this.httpProxyPassword = httpProxyPassword; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { | |||||
| this.sslConnectionSocketFactory = sslConnectionSocketFactory; | |||||
| return this; | |||||
| } | |||||
| /** | |||||
| * 获取链接的超时时间设置,默认3000ms | |||||
| * <p> | |||||
| * 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置). | |||||
| * </p> | |||||
| * | |||||
| * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms | |||||
| */ | |||||
| public void setConnectionRequestTimeout(int connectionRequestTimeout) { | |||||
| this.connectionRequestTimeout = connectionRequestTimeout; | |||||
| } | |||||
| /** | |||||
| * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 | |||||
| * <p> | |||||
| * 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). | |||||
| * </p> | |||||
| * | |||||
| * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms | |||||
| */ | |||||
| public void setConnectionTimeout(int connectionTimeout) { | |||||
| this.connectionTimeout = connectionTimeout; | |||||
| } | |||||
| /** | |||||
| * 默认NIO的socket超时设置,默认5000ms. | |||||
| * | |||||
| * @param soTimeout 默认NIO的socket超时设置,默认5000ms. | |||||
| * @see java.net.SocketOptions#SO_TIMEOUT | |||||
| */ | |||||
| public void setSoTimeout(int soTimeout) { | |||||
| this.soTimeout = soTimeout; | |||||
| } | |||||
| /** | |||||
| * 空闲链接的超时时间,默认60000ms. | |||||
| * <p> | |||||
| * 超时的链接将在下一次空闲链接检查是被销毁 | |||||
| * </p> | |||||
| * | |||||
| * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. | |||||
| */ | |||||
| public void setIdleConnTimeout(int idleConnTimeout) { | |||||
| this.idleConnTimeout = idleConnTimeout; | |||||
| } | |||||
| /** | |||||
| * 检查空间链接的间隔周期,默认60000ms. | |||||
| * | |||||
| * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. | |||||
| */ | |||||
| public void setCheckWaitTime(int checkWaitTime) { | |||||
| this.checkWaitTime = checkWaitTime; | |||||
| } | |||||
| /** | |||||
| * 每路的最大链接数,默认10 | |||||
| * | |||||
| * @param maxConnPerHost 每路的最大链接数,默认10 | |||||
| */ | |||||
| public void setMaxConnPerHost(int maxConnPerHost) { | |||||
| this.maxConnPerHost = maxConnPerHost; | |||||
| } | |||||
| /** | |||||
| * 最大总连接数,默认50 | |||||
| * | |||||
| * @param maxTotalConn 最大总连接数,默认50 | |||||
| */ | |||||
| public void setMaxTotalConn(int maxTotalConn) { | |||||
| this.maxTotalConn = maxTotalConn; | |||||
| } | |||||
| /** | |||||
| * 自定义httpclient的User Agent | |||||
| * | |||||
| * @param userAgent User Agent | |||||
| */ | |||||
| public void setUserAgent(String userAgent) { | |||||
| this.userAgent = userAgent; | |||||
| } | |||||
| public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { | |||||
| return this.idleConnectionMonitorThread; | |||||
| } | |||||
| private synchronized void prepare() { | |||||
| if (prepared.get()) { | |||||
| return; | |||||
| } | |||||
| Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | |||||
| .register("http", this.plainConnectionSocketFactory).register("https", this.sslConnectionSocketFactory) | |||||
| .build(); | |||||
| @SuppressWarnings("resource") | |||||
| PoolingHttpClientConnectionManager connectionManager; | |||||
| if (dnsResover != null) { | |||||
| if (log.isDebugEnabled()) { | |||||
| log.debug("specified dns resolver."); | |||||
| } | |||||
| connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResover); | |||||
| } else { | |||||
| if (log.isDebugEnabled()) { | |||||
| log.debug("Not specified dns resolver."); | |||||
| } | |||||
| connectionManager = new PoolingHttpClientConnectionManager(registry); | |||||
| } | |||||
| connectionManager.setMaxTotal(this.maxTotalConn); | |||||
| connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost); | |||||
| connectionManager | |||||
| .setDefaultSocketConfig(SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(this.soTimeout).build()); | |||||
| this.idleConnectionMonitorThread = new IdleConnectionMonitorThread(connectionManager, this.idleConnTimeout, | |||||
| this.checkWaitTime); | |||||
| this.idleConnectionMonitorThread.setDaemon(true); | |||||
| this.idleConnectionMonitorThread.start(); | |||||
| this.httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager) | |||||
| .setConnectionManagerShared(true) | |||||
| .setDefaultRequestConfig(RequestConfig.custom().setSocketTimeout(this.soTimeout) | |||||
| .setConnectTimeout(this.connectionTimeout) | |||||
| .setConnectionRequestTimeout(this.connectionRequestTimeout).build()) | |||||
| .setRetryHandler(this.httpRequestRetryHandler); | |||||
| if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { | |||||
| // 使用代理服务器 需要用户认证的代理服务器 | |||||
| CredentialsProvider provider = new BasicCredentialsProvider(); | |||||
| provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), | |||||
| new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); | |||||
| this.httpClientBuilder.setDefaultCredentialsProvider(provider); | |||||
| } | |||||
| if (StringUtils.isNotBlank(this.userAgent)) { | |||||
| this.httpClientBuilder.setUserAgent(this.userAgent); | |||||
| } | |||||
| prepared.set(true); | |||||
| } | |||||
| @Override | |||||
| public CloseableHttpClient build() { | |||||
| if (!prepared.get()) { | |||||
| prepare(); | |||||
| } | |||||
| return this.httpClientBuilder.build(); | |||||
| } | |||||
| public DnsResolver getDnsResover() { | |||||
| return dnsResover; | |||||
| } | |||||
| public void setDnsResover(DnsResolver dnsResover) { | |||||
| this.dnsResover = dnsResover; | |||||
| } | |||||
| public static class IdleConnectionMonitorThread extends Thread { | |||||
| private final HttpClientConnectionManager connMgr; | |||||
| private final int idleConnTimeout; | |||||
| private final int checkWaitTime; | |||||
| private volatile boolean shutdown; | |||||
| public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout, | |||||
| int checkWaitTime) { | |||||
| super("IdleConnectionMonitorThread"); | |||||
| this.connMgr = connMgr; | |||||
| this.idleConnTimeout = idleConnTimeout; | |||||
| this.checkWaitTime = checkWaitTime; | |||||
| } | |||||
| @Override | |||||
| public void run() { | |||||
| try { | |||||
| while (!this.shutdown) { | |||||
| synchronized (this) { | |||||
| wait(this.checkWaitTime); | |||||
| this.connMgr.closeExpiredConnections(); | |||||
| this.connMgr.closeIdleConnections(this.idleConnTimeout, TimeUnit.MILLISECONDS); | |||||
| } | |||||
| } | |||||
| } catch (InterruptedException ignore) { | |||||
| } | |||||
| } | |||||
| public void trigger() { | |||||
| synchronized (this) { | |||||
| notifyAll(); | |||||
| } | |||||
| } | |||||
| public void shutdown() { | |||||
| this.shutdown = true; | |||||
| synchronized (this) { | |||||
| notifyAll(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,87 @@ | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| 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.HttpGet; | |||||
| import org.apache.http.entity.ContentType; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class ApacheMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<CloseableHttpClient, HttpHost> { | |||||
| public ApacheMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { | |||||
| super(requestHttp, tmpDirFile); | |||||
| } | |||||
| @Override | |||||
| public File execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpGet httpGet = new HttpGet(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet); | |||||
| InputStream inputStream = InputStreamResponseHandler.INSTANCE | |||||
| .handleResponse(response)) { | |||||
| Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||||
| if (contentTypeHeader != null && contentTypeHeader.length > 0) { | |||||
| if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) { | |||||
| // application/json; encoding=utf-8 下载媒体文件出错 | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| throw new WxErrorException(WxError.fromJson(responseContent)); | |||||
| } | |||||
| } | |||||
| String fileName = getFileName(response); | |||||
| if (StringUtils.isBlank(fileName)) { | |||||
| return null; | |||||
| } | |||||
| String[] nameAndExt = fileName.split("\\."); | |||||
| return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| } | |||||
| private String getFileName(CloseableHttpResponse response) throws WxErrorException { | |||||
| Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); | |||||
| if (contentDispositionHeader == null || contentDispositionHeader.length == 0) { | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| Pattern p = Pattern.compile(".*filename=\"(.*)\""); | |||||
| Matcher m = p.matcher(contentDispositionHeader[0].getValue()); | |||||
| if (m.matches()) { | |||||
| return m.group(1); | |||||
| } | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import org.apache.http.HttpEntity; | |||||
| 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.mime.HttpMultipartMode; | |||||
| import org.apache.http.entity.mime.MultipartEntityBuilder; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class ApacheMediaUploadRequestExecutor extends MediaUploadRequestExecutor<CloseableHttpClient, HttpHost> { | |||||
| public ApacheMediaUploadRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException { | |||||
| HttpPost httpPost = new HttpPost(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); | |||||
| httpPost.setConfig(config); | |||||
| } | |||||
| if (file != null) { | |||||
| HttpEntity entity = MultipartEntityBuilder | |||||
| .create() | |||||
| .addBinaryBody("media", file) | |||||
| .setMode(HttpMultipartMode.RFC6532) | |||||
| .build(); | |||||
| httpPost.setEntity(entity); | |||||
| } | |||||
| try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return WxMediaUploadResult.fromJson(responseContent); | |||||
| } finally { | |||||
| httpPost.releaseConnection(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import org.apache.http.Consts; | |||||
| 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.StringEntity; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class ApacheSimplePostRequestExecutor extends SimplePostRequestExecutor<CloseableHttpClient, HttpHost> { | |||||
| public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String postEntity) throws WxErrorException, IOException { | |||||
| HttpPost httpPost = new HttpPost(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); | |||||
| httpPost.setConfig(config); | |||||
| } | |||||
| if (postEntity != null) { | |||||
| StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); | |||||
| httpPost.setEntity(entity); | |||||
| } | |||||
| try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { | |||||
| String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
| if (responseContent.isEmpty()) { | |||||
| throw new WxErrorException( | |||||
| WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容") | |||||
| .build()); | |||||
| } | |||||
| if (responseContent.startsWith("<xml>")) { | |||||
| //xml格式输出直接返回 | |||||
| return responseContent; | |||||
| } | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } finally { | |||||
| httpPost.releaseConnection(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.apache.http.annotation.NotThreadSafe; | import org.apache.http.annotation.NotThreadSafe; | ||||
| @@ -14,16 +14,24 @@ import org.apache.http.conn.HttpClientConnectionManager; | |||||
| import org.apache.http.conn.socket.ConnectionSocketFactory; | import org.apache.http.conn.socket.ConnectionSocketFactory; | ||||
| import org.apache.http.conn.socket.PlainConnectionSocketFactory; | import org.apache.http.conn.socket.PlainConnectionSocketFactory; | ||||
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | ||||
| import org.apache.http.conn.ssl.TrustStrategy; | |||||
| import org.apache.http.impl.client.BasicCredentialsProvider; | import org.apache.http.impl.client.BasicCredentialsProvider; | ||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
| import org.apache.http.impl.client.HttpClientBuilder; | import org.apache.http.impl.client.HttpClientBuilder; | ||||
| import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
| import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | ||||
| import org.apache.http.protocol.HttpContext; | import org.apache.http.protocol.HttpContext; | ||||
| import org.apache.http.ssl.SSLContexts; | |||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| import javax.net.ssl.SSLContext; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.security.KeyManagementException; | |||||
| import java.security.KeyStoreException; | |||||
| import java.security.NoSuchAlgorithmException; | |||||
| import java.security.cert.CertificateException; | |||||
| import java.security.cert.X509Certificate; | |||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||
| @@ -188,7 +196,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||||
| } | } | ||||
| private synchronized void prepare() { | private synchronized void prepare() { | ||||
| if(prepared.get()){ | |||||
| if (prepared.get()) { | |||||
| return; | return; | ||||
| } | } | ||||
| Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | ||||
| @@ -214,6 +222,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||||
| this.httpClientBuilder = HttpClients.custom() | this.httpClientBuilder = HttpClients.custom() | ||||
| .setConnectionManager(connectionManager) | .setConnectionManager(connectionManager) | ||||
| .setConnectionManagerShared(true) | .setConnectionManagerShared(true) | ||||
| .setSSLSocketFactory(this.buildSSLConnectionSocketFactory()) | |||||
| .setDefaultRequestConfig( | .setDefaultRequestConfig( | ||||
| RequestConfig.custom() | RequestConfig.custom() | ||||
| .setSocketTimeout(this.soTimeout) | .setSocketTimeout(this.soTimeout) | ||||
| @@ -240,9 +249,32 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||||
| prepared.set(true); | prepared.set(true); | ||||
| } | } | ||||
| private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() { | |||||
| try { | |||||
| SSLContext sslcontext = SSLContexts.custom() | |||||
| //忽略掉对服务器端证书的校验 | |||||
| .loadTrustMaterial(new TrustStrategy() { | |||||
| @Override | |||||
| public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { | |||||
| return true; | |||||
| } | |||||
| }).build(); | |||||
| return new SSLConnectionSocketFactory( | |||||
| sslcontext, | |||||
| new String[]{"TLSv1"}, | |||||
| null, | |||||
| SSLConnectionSocketFactory.getDefaultHostnameVerifier()); | |||||
| } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { | |||||
| this.log.error(e.getMessage(), e); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| @Override | @Override | ||||
| public CloseableHttpClient build() { | public CloseableHttpClient build() { | ||||
| if(!prepared.get()){ | |||||
| if (!prepared.get()) { | |||||
| prepare(); | prepare(); | ||||
| } | } | ||||
| return this.httpClientBuilder.build(); | return this.httpClientBuilder.build(); | ||||
| @@ -1,4 +1,4 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import org.apache.http.HttpEntity; | import org.apache.http.HttpEntity; | ||||
| import org.apache.http.HttpResponse; | import org.apache.http.HttpResponse; | ||||
| @@ -1,4 +1,4 @@ | |||||
| package me.chanjar.weixin.common.util.http; | |||||
| package me.chanjar.weixin.common.util.http.apache; | |||||
| import org.apache.http.Consts; | import org.apache.http.Consts; | ||||
| import org.apache.http.HttpEntity; | import org.apache.http.HttpEntity; | ||||
| @@ -0,0 +1,81 @@ | |||||
| package me.chanjar.weixin.common.util.http.jodd; | |||||
| import jodd.http.HttpConnectionProvider; | |||||
| import jodd.http.HttpRequest; | |||||
| import jodd.http.HttpResponse; | |||||
| import jodd.http.ProxyInfo; | |||||
| import jodd.util.StringPool; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.io.ByteArrayInputStream; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class JoddHttpMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<HttpConnectionProvider, ProxyInfo> { | |||||
| public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { | |||||
| super(requestHttp, tmpDirFile); | |||||
| } | |||||
| @Override | |||||
| public File execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpRequest request = HttpRequest.get(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); | |||||
| } | |||||
| request.withConnectionProvider(requestHttp.getRequestHttpClient()); | |||||
| HttpResponse response = request.send(); | |||||
| response.charset(StringPool.UTF_8); | |||||
| String contentType = response.header("Content-Type"); | |||||
| if (contentType != null && contentType.startsWith("application/json")) { | |||||
| // application/json; encoding=utf-8 下载媒体文件出错 | |||||
| throw new WxErrorException(WxError.fromJson(response.bodyText())); | |||||
| } | |||||
| String fileName = getFileName(response); | |||||
| if (StringUtils.isBlank(fileName)) { | |||||
| return null; | |||||
| } | |||||
| InputStream inputStream = new ByteArrayInputStream(response.bodyBytes()); | |||||
| String[] nameAndExt = fileName.split("\\."); | |||||
| return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile); | |||||
| } | |||||
| private String getFileName(HttpResponse response) throws WxErrorException { | |||||
| String content = response.header("Content-disposition"); | |||||
| if (content == null || content.length() == 0) { | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| Pattern p = Pattern.compile(".*filename=\"(.*)\""); | |||||
| Matcher m = p.matcher(content); | |||||
| if (m.matches()) { | |||||
| return m.group(1); | |||||
| } | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| package me.chanjar.weixin.common.util.http.jodd; | |||||
| import jodd.http.HttpConnectionProvider; | |||||
| import jodd.http.HttpRequest; | |||||
| import jodd.http.HttpResponse; | |||||
| import jodd.http.ProxyInfo; | |||||
| import jodd.util.StringPool; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class JoddHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor<HttpConnectionProvider, ProxyInfo> { | |||||
| public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException { | |||||
| HttpRequest request = HttpRequest.post(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); | |||||
| } | |||||
| request.withConnectionProvider(requestHttp.getRequestHttpClient()); | |||||
| request.form("media", file); | |||||
| HttpResponse response = request.send(); | |||||
| response.charset(StringPool.UTF_8); | |||||
| String responseContent = response.bodyText(); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return WxMediaUploadResult.fromJson(responseContent); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| package me.chanjar.weixin.common.util.http.jodd; | |||||
| import jodd.http.*; | |||||
| import jodd.util.StringPool; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class JoddHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor<HttpConnectionProvider, ProxyInfo> { | |||||
| public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| HttpRequest request = HttpRequest.get(uri); | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); | |||||
| } | |||||
| request.withConnectionProvider(requestHttp.getRequestHttpClient()); | |||||
| HttpResponse response = request.send(); | |||||
| response.charset(StringPool.UTF_8); | |||||
| String responseContent = response.bodyText(); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| package me.chanjar.weixin.common.util.http.jodd; | |||||
| import jodd.http.HttpConnectionProvider; | |||||
| import jodd.http.HttpRequest; | |||||
| import jodd.http.HttpResponse; | |||||
| import jodd.http.ProxyInfo; | |||||
| import jodd.util.StringPool; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class JoddHttpSimplePostRequestExecutor extends SimplePostRequestExecutor<HttpConnectionProvider, ProxyInfo> { | |||||
| public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String postEntity) throws WxErrorException, IOException { | |||||
| HttpConnectionProvider provider = requestHttp.getRequestHttpClient(); | |||||
| ProxyInfo proxyInfo = requestHttp.getRequestHttpProxy(); | |||||
| HttpRequest request = HttpRequest.post(uri); | |||||
| if (proxyInfo != null) { | |||||
| provider.useProxy(proxyInfo); | |||||
| } | |||||
| request.withConnectionProvider(provider); | |||||
| if (postEntity != null) { | |||||
| request.bodyText(postEntity); | |||||
| } | |||||
| HttpResponse response = request.send(); | |||||
| response.charset(StringPool.UTF_8); | |||||
| String responseContent = response.bodyText(); | |||||
| if (responseContent.isEmpty()) { | |||||
| throw new WxErrorException( | |||||
| WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容") | |||||
| .build()); | |||||
| } | |||||
| if (responseContent.startsWith("<xml>")) { | |||||
| //xml格式输出直接返回 | |||||
| return responseContent; | |||||
| } | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,89 @@ | |||||
| package me.chanjar.weixin.common.util.http.okhttp; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import okhttp3.*; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.io.ByteArrayInputStream; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.regex.Matcher; | |||||
| import java.util.regex.Pattern; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class OkHttpMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<ConnectionPool, OkHttpProxyInfo> { | |||||
| public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) { | |||||
| super(requestHttp, tmpDirFile); | |||||
| } | |||||
| @Override | |||||
| public File execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient()); | |||||
| //设置代理 | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy()); | |||||
| } | |||||
| //设置授权 | |||||
| clientBuilder.authenticator(new Authenticator() { | |||||
| @Override | |||||
| public Request authenticate(Route route, Response response) throws IOException { | |||||
| String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword()); | |||||
| return response.request().newBuilder() | |||||
| .header("Authorization", credential) | |||||
| .build(); | |||||
| } | |||||
| }); | |||||
| //得到httpClient | |||||
| OkHttpClient client = clientBuilder.build(); | |||||
| Request request = new Request.Builder().url(uri).get().build(); | |||||
| Response response = client.newCall(request).execute(); | |||||
| String contentType = response.header("Content-Type"); | |||||
| if (contentType != null && contentType.startsWith("application/json")) { | |||||
| // application/json; encoding=utf-8 下载媒体文件出错 | |||||
| throw new WxErrorException(WxError.fromJson(response.body().string())); | |||||
| } | |||||
| String fileName = getFileName(response); | |||||
| if (StringUtils.isBlank(fileName)) { | |||||
| return null; | |||||
| } | |||||
| InputStream inputStream = new ByteArrayInputStream(response.body().bytes()); | |||||
| String[] nameAndExt = fileName.split("\\."); | |||||
| return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile); | |||||
| } | |||||
| private String getFileName(Response response) throws WxErrorException { | |||||
| String content = response.header("Content-disposition"); | |||||
| if (content == null || content.length() == 0) { | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| Pattern p = Pattern.compile(".*filename=\"(.*)\""); | |||||
| Matcher m = p.matcher(content); | |||||
| if (m.matches()) { | |||||
| return m.group(1); | |||||
| } | |||||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| package me.chanjar.weixin.common.util.http.okhttp; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import okhttp3.*; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/5. | |||||
| */ | |||||
| public class OkHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor<ConnectionPool, OkHttpProxyInfo> { | |||||
| public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException { | |||||
| OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient()); | |||||
| //设置代理 | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy()); | |||||
| } | |||||
| //设置授权 | |||||
| clientBuilder.authenticator(new Authenticator() { | |||||
| @Override | |||||
| public Request authenticate(Route route, Response response) throws IOException { | |||||
| String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword()); | |||||
| return response.request().newBuilder() | |||||
| .header("Authorization", credential) | |||||
| .build(); | |||||
| } | |||||
| }); | |||||
| //得到httpClient | |||||
| OkHttpClient client = clientBuilder.build(); | |||||
| RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); | |||||
| RequestBody body = new MultipartBody.Builder().addFormDataPart("media", null, fileBody).build(); | |||||
| Request request = new Request.Builder().url(uri).post(body).build(); | |||||
| Response response = client.newCall(request).execute(); | |||||
| String responseContent = response.body().string(); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return WxMediaUploadResult.fromJson(responseContent); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,117 @@ | |||||
| package me.chanjar.weixin.common.util.http.okhttp; | |||||
| import java.net.InetSocketAddress; | |||||
| import java.net.Proxy; | |||||
| /** | |||||
| * Created by ecoolper on 2017/4/26. | |||||
| * Proxy information. | |||||
| */ | |||||
| public class OkHttpProxyInfo { | |||||
| private final String proxyAddress; | |||||
| private final int proxyPort; | |||||
| private final String proxyUsername; | |||||
| private final String proxyPassword; | |||||
| private final ProxyType proxyType; | |||||
| public OkHttpProxyInfo(ProxyType proxyType, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) { | |||||
| this.proxyType = proxyType; | |||||
| this.proxyAddress = proxyHost; | |||||
| this.proxyPort = proxyPort; | |||||
| this.proxyUsername = proxyUser; | |||||
| this.proxyPassword = proxyPassword; | |||||
| } | |||||
| /** | |||||
| * Creates directProxy. | |||||
| */ | |||||
| public static OkHttpProxyInfo directProxy() { | |||||
| return new OkHttpProxyInfo(ProxyType.NONE, null, 0, null, null); | |||||
| } | |||||
| // ---------------------------------------------------------------- factory | |||||
| /** | |||||
| * Creates SOCKS4 proxy. | |||||
| */ | |||||
| public static OkHttpProxyInfo socks4Proxy(String proxyAddress, int proxyPort, String proxyUser) { | |||||
| return new OkHttpProxyInfo(ProxyType.SOCKS4, proxyAddress, proxyPort, proxyUser, null); | |||||
| } | |||||
| /** | |||||
| * Creates SOCKS5 proxy. | |||||
| */ | |||||
| public static OkHttpProxyInfo socks5Proxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) { | |||||
| return new OkHttpProxyInfo(ProxyType.SOCKS5, proxyAddress, proxyPort, proxyUser, proxyPassword); | |||||
| } | |||||
| /** | |||||
| * Creates HTTP proxy. | |||||
| */ | |||||
| public static OkHttpProxyInfo httpProxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) { | |||||
| return new OkHttpProxyInfo(ProxyType.HTTP, proxyAddress, proxyPort, proxyUser, proxyPassword); | |||||
| } | |||||
| /** | |||||
| * Returns proxy type. | |||||
| */ | |||||
| public ProxyType getProxyType() { | |||||
| return proxyType; | |||||
| } | |||||
| // ---------------------------------------------------------------- getter | |||||
| /** | |||||
| * Returns proxy address. | |||||
| */ | |||||
| public String getProxyAddress() { | |||||
| return proxyAddress; | |||||
| } | |||||
| /** | |||||
| * Returns proxy port. | |||||
| */ | |||||
| public int getProxyPort() { | |||||
| return proxyPort; | |||||
| } | |||||
| /** | |||||
| * Returns proxy user name or <code>null</code> if | |||||
| * no authentication required. | |||||
| */ | |||||
| public String getProxyUsername() { | |||||
| return proxyUsername; | |||||
| } | |||||
| /** | |||||
| * Returns proxy password or <code>null</code>. | |||||
| */ | |||||
| public String getProxyPassword() { | |||||
| return proxyPassword; | |||||
| } | |||||
| /** | |||||
| * 返回 java.net.Proxy | |||||
| * | |||||
| * @return | |||||
| */ | |||||
| public Proxy getProxy() { | |||||
| Proxy proxy = null; | |||||
| if (getProxyType().equals(ProxyType.SOCKS5)) { | |||||
| proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); | |||||
| } else if (getProxyType().equals(ProxyType.SOCKS4)) { | |||||
| proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); | |||||
| } else if (getProxyType().equals(ProxyType.HTTP)) { | |||||
| proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getProxyAddress(), getProxyPort())); | |||||
| } else if (getProxyType().equals(ProxyType.NONE)) { | |||||
| proxy = new Proxy(Proxy.Type.DIRECT, new InetSocketAddress(getProxyAddress(), getProxyPort())); | |||||
| } | |||||
| return proxy; | |||||
| } | |||||
| /** | |||||
| * Proxy types. | |||||
| */ | |||||
| public enum ProxyType { | |||||
| NONE, HTTP, SOCKS4, SOCKS5 | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,58 @@ | |||||
| package me.chanjar.weixin.common.util.http.okhttp; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| import okhttp3.*; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class OkHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor<ConnectionPool, OkHttpProxyInfo> { | |||||
| public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String queryParam) throws WxErrorException, IOException { | |||||
| if (queryParam != null) { | |||||
| if (uri.indexOf('?') == -1) { | |||||
| uri += '?'; | |||||
| } | |||||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
| } | |||||
| OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient()); | |||||
| //设置代理 | |||||
| if (requestHttp.getRequestHttpProxy() != null) { | |||||
| clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy()); | |||||
| } | |||||
| //设置授权 | |||||
| clientBuilder.authenticator(new Authenticator() { | |||||
| @Override | |||||
| public Request authenticate(Route route, Response response) throws IOException { | |||||
| String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword()); | |||||
| return response.request().newBuilder() | |||||
| .header("Authorization", credential) | |||||
| .build(); | |||||
| } | |||||
| }); | |||||
| //得到httpClient | |||||
| OkHttpClient client = clientBuilder.build(); | |||||
| Request request = new Request.Builder().url(uri).build(); | |||||
| Response response = client.newCall(request).execute(); | |||||
| String responseContent = response.body().string(); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,58 @@ | |||||
| package me.chanjar.weixin.common.util.http.okhttp; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import okhttp3.*; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * Created by ecoolper on 2017/5/4. | |||||
| */ | |||||
| public class OkHttpSimplePostRequestExecutor extends SimplePostRequestExecutor<ConnectionPool, OkHttpProxyInfo> { | |||||
| public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { | |||||
| super(requestHttp); | |||||
| } | |||||
| @Override | |||||
| public String execute(String uri, String postEntity) throws WxErrorException, IOException { | |||||
| ConnectionPool pool = requestHttp.getRequestHttpClient(); | |||||
| final OkHttpProxyInfo proxyInfo = requestHttp.getRequestHttpProxy(); | |||||
| OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(pool); | |||||
| //设置代理 | |||||
| if (proxyInfo != null) { | |||||
| clientBuilder.proxy(proxyInfo.getProxy()); | |||||
| } | |||||
| //设置授权 | |||||
| clientBuilder.authenticator(new Authenticator() { | |||||
| @Override | |||||
| public Request authenticate(Route route, Response response) throws IOException { | |||||
| String credential = Credentials.basic(proxyInfo.getProxyUsername(), proxyInfo.getProxyPassword()); | |||||
| return response.request().newBuilder() | |||||
| .header("Authorization", credential) | |||||
| .build(); | |||||
| } | |||||
| }); | |||||
| //得到httpClient | |||||
| OkHttpClient client = clientBuilder.build(); | |||||
| MediaType mediaType = MediaType.parse("text/plain; charset=utf-8"); | |||||
| RequestBody body = RequestBody.create(mediaType, postEntity); | |||||
| Request request = new Request.Builder().url(uri).post(body).build(); | |||||
| Response response = client.newCall(request).execute(); | |||||
| String responseContent = response.body().string(); | |||||
| WxError error = WxError.fromJson(responseContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return responseContent; | |||||
| } | |||||
| } | |||||
| @@ -2,7 +2,6 @@ package me.chanjar.weixin.common.util.json; | |||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | import me.chanjar.weixin.common.bean.menu.WxMenu; | ||||
| import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
| @@ -90,9 +90,14 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ | |||||
| * 操蛋的微信 | * 操蛋的微信 | ||||
| * 创建菜单时是 { button : ... } | * 创建菜单时是 { button : ... } | ||||
| * 查询菜单时是 { menu : { button : ... } } | * 查询菜单时是 { menu : { button : ... } } | ||||
| * 现在企业号升级为企业微信后,没有此问题,因此需要单独处理 | |||||
| */ | */ | ||||
| WxMenu menu = new WxMenu(); | |||||
| JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray(); | JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray(); | ||||
| return this.buildMenuFromJson(buttonsJson); | |||||
| } | |||||
| protected WxMenu buildMenuFromJson(JsonArray buttonsJson) { | |||||
| WxMenu menu = new WxMenu(); | |||||
| for (int i = 0; i < buttonsJson.size(); i++) { | for (int i = 0; i < buttonsJson.size(); i++) { | ||||
| JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | ||||
| WxMenuButton button = convertFromJson(buttonJson); | WxMenuButton button = convertFromJson(buttonJson); | ||||
| @@ -47,7 +47,7 @@ import java.util.*; | |||||
| public class StringManager { | public class StringManager { | ||||
| private static final Map<String, Map<Locale, StringManager>> managers = | private static final Map<String, Map<Locale, StringManager>> managers = | ||||
| new Hashtable<>(); | |||||
| new Hashtable<>(); | |||||
| private static int LOCALE_CACHE_SIZE = 10; | private static int LOCALE_CACHE_SIZE = 10; | ||||
| /** | /** | ||||
| * The ResourceBundle for this StringManager. | * The ResourceBundle for this StringManager. | ||||
| @@ -103,7 +103,7 @@ public class StringManager { | |||||
| * @param packageName The package name | * @param packageName The package name | ||||
| */ | */ | ||||
| public static final synchronized StringManager getManager( | public static final synchronized StringManager getManager( | ||||
| String packageName) { | |||||
| String packageName) { | |||||
| return getManager(packageName, Locale.getDefault()); | return getManager(packageName, Locale.getDefault()); | ||||
| } | } | ||||
| @@ -116,7 +116,7 @@ public class StringManager { | |||||
| * @param locale The Locale | * @param locale The Locale | ||||
| */ | */ | ||||
| public static final synchronized StringManager getManager( | public static final synchronized StringManager getManager( | ||||
| String packageName, Locale locale) { | |||||
| String packageName, Locale locale) { | |||||
| Map<Locale, StringManager> map = managers.get(packageName); | Map<Locale, StringManager> map = managers.get(packageName); | ||||
| if (map == null) { | if (map == null) { | ||||
| @@ -133,7 +133,7 @@ public class StringManager { | |||||
| @Override | @Override | ||||
| protected boolean removeEldestEntry( | protected boolean removeEldestEntry( | ||||
| Map.Entry<Locale, StringManager> eldest) { | |||||
| Map.Entry<Locale, StringManager> eldest) { | |||||
| return size() > (LOCALE_CACHE_SIZE - 1); | return size() > (LOCALE_CACHE_SIZE - 1); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -1,7 +1,5 @@ | |||||
| package me.chanjar.weixin.common.util.xml; | package me.chanjar.weixin.common.util.xml; | ||||
| import java.io.Writer; | |||||
| import com.thoughtworks.xstream.XStream; | import com.thoughtworks.xstream.XStream; | ||||
| import com.thoughtworks.xstream.core.util.QuickWriter; | import com.thoughtworks.xstream.core.util.QuickWriter; | ||||
| import com.thoughtworks.xstream.io.HierarchicalStreamWriter; | import com.thoughtworks.xstream.io.HierarchicalStreamWriter; | ||||
| @@ -10,6 +8,8 @@ import com.thoughtworks.xstream.io.xml.XppDriver; | |||||
| import com.thoughtworks.xstream.security.NullPermission; | import com.thoughtworks.xstream.security.NullPermission; | ||||
| import com.thoughtworks.xstream.security.PrimitiveTypePermission; | import com.thoughtworks.xstream.security.PrimitiveTypePermission; | ||||
| import java.io.Writer; | |||||
| public class XStreamInitializer { | public class XStreamInitializer { | ||||
| public static XStream getInstance() { | public static XStream getInstance() { | ||||
| @@ -1,34 +1,36 @@ | |||||
| package me.chanjar.weixin.common.util; | |||||
| package me.chanjar.weixin.common.api; | |||||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||||
| import org.testng.Assert; | import org.testng.Assert; | ||||
| import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
| import static org.testng.Assert.assertFalse; | |||||
| import static org.testng.Assert.assertTrue; | |||||
| @Test | @Test | ||||
| public class WxMessageInMemoryDuplicateCheckerTest { | public class WxMessageInMemoryDuplicateCheckerTest { | ||||
| private WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l); | |||||
| public void test() throws InterruptedException { | public void test() throws InterruptedException { | ||||
| Long[] msgIds = new Long[]{1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l}; | Long[] msgIds = new Long[]{1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l}; | ||||
| WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l); | |||||
| // 第一次检查 | // 第一次检查 | ||||
| for (Long msgId : msgIds) { | for (Long msgId : msgIds) { | ||||
| boolean result = checker.isDuplicate(String.valueOf(msgId)); | boolean result = checker.isDuplicate(String.valueOf(msgId)); | ||||
| Assert.assertFalse(result); | |||||
| assertFalse(result); | |||||
| } | } | ||||
| // 过1秒再检查 | // 过1秒再检查 | ||||
| Thread.sleep(1000l); | Thread.sleep(1000l); | ||||
| for (Long msgId : msgIds) { | for (Long msgId : msgIds) { | ||||
| boolean result = checker.isDuplicate(String.valueOf(msgId)); | boolean result = checker.isDuplicate(String.valueOf(msgId)); | ||||
| Assert.assertTrue(result); | |||||
| assertTrue(result); | |||||
| } | } | ||||
| // 过1.5秒再检查 | // 过1.5秒再检查 | ||||
| Thread.sleep(1500l); | Thread.sleep(1500l); | ||||
| for (Long msgId : msgIds) { | for (Long msgId : msgIds) { | ||||
| boolean result = checker.isDuplicate(String.valueOf(msgId)); | boolean result = checker.isDuplicate(String.valueOf(msgId)); | ||||
| Assert.assertFalse(result); | |||||
| assertFalse(result); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| package me.chanjar.weixin.common.bean; | package me.chanjar.weixin.common.bean; | ||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Test; | |||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| @Test | @Test | ||||
| public class WxAccessTokenTest { | public class WxAccessTokenTest { | ||||
| @@ -1,8 +1,8 @@ | |||||
| package me.chanjar.weixin.common.bean; | package me.chanjar.weixin.common.bean; | ||||
| import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Test; | |||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| @Test | @Test | ||||
| public class WxErrorTest { | public class WxErrorTest { | ||||
| @@ -1,12 +1,10 @@ | |||||
| package me.chanjar.weixin.common.bean; | package me.chanjar.weixin.common.bean; | ||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.DataProvider; | |||||
| import org.testng.annotations.Test; | |||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | import me.chanjar.weixin.common.bean.menu.WxMenu; | ||||
| import me.chanjar.weixin.common.bean.menu.WxMenuButton; | import me.chanjar.weixin.common.bean.menu.WxMenuButton; | ||||
| import me.chanjar.weixin.common.bean.menu.WxMenuRule; | import me.chanjar.weixin.common.bean.menu.WxMenuRule; | ||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| @Test | @Test | ||||
| public class WxMenuTest { | public class WxMenuTest { | ||||
| @@ -87,75 +85,75 @@ public class WxMenuTest { | |||||
| Object[][] res = menuJson(); | Object[][] res = menuJson(); | ||||
| String json = "{ \"menu\" : " + res[0][0] + " }"; | String json = "{ \"menu\" : " + res[0][0] + " }"; | ||||
| return new Object[][]{ | return new Object[][]{ | ||||
| new Object[]{json} | |||||
| new Object[]{json} | |||||
| }; | }; | ||||
| } | } | ||||
| @DataProvider(name = "wxPushMenu") | @DataProvider(name = "wxPushMenu") | ||||
| public Object[][] menuJson() { | public Object[][] menuJson() { | ||||
| String json = | String json = | ||||
| "{" | |||||
| + "\"button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"今日歌曲\"," | |||||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"歌手简介\"," | |||||
| + "\"key\":\"V1001_TODAY_SINGER\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"name\":\"菜单\"," | |||||
| + "\"sub_button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"view\"," | |||||
| + "\"name\":\"搜索\"," | |||||
| + "\"url\":\"http://www.soso.com/\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"view\"," | |||||
| + "\"name\":\"视频\"," | |||||
| + "\"url\":\"http://v.qq.com/\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"赞一下我们\"," | |||||
| + "\"key\":\"V1001_GOOD\"" | |||||
| + "}" | |||||
| + "]" | |||||
| + "}" | |||||
| + "]" | |||||
| + "}"; | |||||
| "{" | |||||
| + "\"button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"今日歌曲\"," | |||||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"歌手简介\"," | |||||
| + "\"key\":\"V1001_TODAY_SINGER\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"name\":\"菜单\"," | |||||
| + "\"sub_button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"view\"," | |||||
| + "\"name\":\"搜索\"," | |||||
| + "\"url\":\"http://www.soso.com/\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"view\"," | |||||
| + "\"name\":\"视频\"," | |||||
| + "\"url\":\"http://v.qq.com/\"" | |||||
| + "}," | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"赞一下我们\"," | |||||
| + "\"key\":\"V1001_GOOD\"" | |||||
| + "}" | |||||
| + "]" | |||||
| + "}" | |||||
| + "]" | |||||
| + "}"; | |||||
| return new Object[][]{ | return new Object[][]{ | ||||
| new Object[]{json} | |||||
| new Object[]{json} | |||||
| }; | }; | ||||
| } | } | ||||
| @DataProvider(name = "wxAddConditionalMenu") | @DataProvider(name = "wxAddConditionalMenu") | ||||
| public Object[][] addConditionalMenuJson() { | public Object[][] addConditionalMenuJson() { | ||||
| String json = | String json = | ||||
| "{" | |||||
| + "\"button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"今日歌曲\"," | |||||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||||
| + "}" | |||||
| + "]," | |||||
| + "\"matchrule\":{" | |||||
| + "\"group_id\":\"2\"," | |||||
| + "\"sex\":\"1\"," | |||||
| + "\"country\":\"中国\"," | |||||
| + "\"province\":\"广东\"," | |||||
| + "\"city\":\"广州\"," | |||||
| + "\"client_platform_type\":\"2\"," | |||||
| + "\"language\":\"zh_CN\"" | |||||
| + "}" | |||||
| + "}"; | |||||
| "{" | |||||
| + "\"button\":[" | |||||
| + "{" | |||||
| + "\"type\":\"click\"," | |||||
| + "\"name\":\"今日歌曲\"," | |||||
| + "\"key\":\"V1001_TODAY_MUSIC\"" | |||||
| + "}" | |||||
| + "]," | |||||
| + "\"matchrule\":{" | |||||
| + "\"group_id\":\"2\"," | |||||
| + "\"sex\":\"1\"," | |||||
| + "\"country\":\"中国\"," | |||||
| + "\"province\":\"广东\"," | |||||
| + "\"city\":\"广州\"," | |||||
| + "\"client_platform_type\":\"2\"," | |||||
| + "\"language\":\"zh_CN\"" | |||||
| + "}" | |||||
| + "}"; | |||||
| return new Object[][]{ | return new Object[][]{ | ||||
| new Object[]{json} | |||||
| new Object[]{json} | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -1,8 +1,7 @@ | |||||
| package me.chanjar.weixin.common.session; | package me.chanjar.weixin.common.session; | ||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.DataProvider; | |||||
| import org.testng.annotations.Test; | |||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| @Test | @Test | ||||
| public class SessionTest { | public class SessionTest { | ||||
| @@ -11,7 +10,7 @@ public class SessionTest { | |||||
| public Object[][] getSessionManager() { | public Object[][] getSessionManager() { | ||||
| return new Object[][]{ | return new Object[][]{ | ||||
| new Object[]{new StandardSessionManager()} | |||||
| new Object[]{new StandardSessionManager()} | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -0,0 +1,36 @@ | |||||
| package me.chanjar.weixin.common.util.crypto; | |||||
| import org.testng.annotations.Test; | |||||
| import static org.testng.Assert.assertEquals; | |||||
| import static org.testng.Assert.assertNotNull; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2017/6/10. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||||
| */ | |||||
| public class SHA1Test { | |||||
| @Test | |||||
| public void testGen() throws Exception { | |||||
| final String result = SHA1.gen("123", "345"); | |||||
| assertNotNull(result); | |||||
| assertEquals(result,"9f537aeb751ec72605f57f94a2f6dc3e3958e1dd"); | |||||
| } | |||||
| @Test(expectedExceptions = {IllegalArgumentException.class}) | |||||
| public void testGen_illegalArguments() { | |||||
| final String result = SHA1.gen(null, "", "345"); | |||||
| assertNotNull(result); | |||||
| } | |||||
| @Test | |||||
| public void testGenWithAmple() throws Exception { | |||||
| final String result = SHA1.genWithAmple("123", "345"); | |||||
| assertNotNull(result); | |||||
| assertEquals(result,"20b896ccbd5a72dde5dbe0878ff985e4069771c6"); | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| package me.chanjar.weixin.common.util.crypto; | package me.chanjar.weixin.common.util.crypto; | ||||
| import org.testng.annotations.Test; | |||||
| import org.testng.annotations.*; | |||||
| import org.w3c.dom.Document; | import org.w3c.dom.Document; | ||||
| import org.w3c.dom.Element; | import org.w3c.dom.Element; | ||||
| import org.w3c.dom.NodeList; | import org.w3c.dom.NodeList; | ||||
| @@ -13,8 +13,7 @@ import javax.xml.parsers.ParserConfigurationException; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.StringReader; | import java.io.StringReader; | ||||
| import static org.testng.Assert.assertEquals; | |||||
| import static org.testng.Assert.fail; | |||||
| import static org.testng.Assert.*; | |||||
| @Test | @Test | ||||
| public class WxCryptUtilTest { | public class WxCryptUtilTest { | ||||
| @@ -77,7 +76,7 @@ public class WxCryptUtilTest { | |||||
| } | } | ||||
| public void testValidateSignatureError() throws ParserConfigurationException, SAXException, | public void testValidateSignatureError() throws ParserConfigurationException, SAXException, | ||||
| IOException { | |||||
| IOException { | |||||
| try { | try { | ||||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | ||||
| String afterEncrpt = pc.encrypt(this.replyMsg); | String afterEncrpt = pc.encrypt(this.replyMsg); | ||||
| @@ -1,17 +1,13 @@ | |||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/2002/xmlspec/dtd/2.10/xmlspec.dtd"> | |||||
| <configuration> | <configuration> | ||||
| <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||
| <!-- encoders are assigned the type | |||||
| ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | |||||
| <encoder> | <encoder> | ||||
| <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | |||||
| <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %replace(%caller{1}){'Caller', ''} - %msg%n</pattern> | |||||
| </encoder> | </encoder> | ||||
| </appender> | </appender> | ||||
| <root level="info"> | <root level="info"> | ||||
| <appender-ref ref="STDOUT" /> | |||||
| <appender-ref ref="STDOUT"/> | |||||
| </root> | </root> | ||||
| <logger name="me.chanjar.weixin.common" level="debug" /> | |||||
| <logger name="me.chanjar.weixin.common" level="debug"/> | |||||
| </configuration> | </configuration> | ||||
| @@ -7,8 +7,8 @@ | |||||
| <class name="me.chanjar.weixin.common.bean.WxErrorTest"/> | <class name="me.chanjar.weixin.common.bean.WxErrorTest"/> | ||||
| <class name="me.chanjar.weixin.common.bean.WxMenuTest"/> | <class name="me.chanjar.weixin.common.bean.WxMenuTest"/> | ||||
| <class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest"/> | <class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest"/> | ||||
| <class name="me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateCheckerTest"/> | |||||
| <class name="me.chanjar.weixin.common.session.SessionTest" /> | |||||
| <class name="me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateCheckerTest"/> | |||||
| <class name="me.chanjar.weixin.common.session.SessionTest"/> | |||||
| </classes> | </classes> | ||||
| </test> | </test> | ||||
| </suite> | </suite> | ||||
| @@ -1,12 +0,0 @@ | |||||
| description = 'WeiXin Java Tools - CP' | |||||
| dependencies { | |||||
| compile project(':weixin-java-common') | |||||
| testCompile group: 'junit', name: 'junit', version:'4.11' | |||||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0' | |||||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0' | |||||
| } | |||||
| test.useTestNG() | |||||
| @@ -1,12 +1,13 @@ | |||||
| <?xml version="1.0"?> | <?xml version="1.0"?> | ||||
| <project | <project | ||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"> | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
| xmlns="http://maven.apache.org/POM/4.0.0"> | |||||
| <modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>weixin-java-parent</artifactId> | <artifactId>weixin-java-parent</artifactId> | ||||
| <version>2.6.0</version> | |||||
| <version>2.7.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-cp</artifactId> | <artifactId>weixin-java-cp</artifactId> | ||||
| @@ -19,10 +20,25 @@ | |||||
| <artifactId>weixin-java-common</artifactId> | <artifactId>weixin-java-common</artifactId> | ||||
| <version>${project.version}</version> | <version>${project.version}</version> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>redis.clients</groupId> | <groupId>redis.clients</groupId> | ||||
| <artifactId>jedis</artifactId> | <artifactId>jedis</artifactId> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>org.testng</groupId> | <groupId>org.testng</groupId> | ||||
| <artifactId>testng</artifactId> | <artifactId>testng</artifactId> | ||||
| @@ -48,6 +64,11 @@ | |||||
| <artifactId>jetty-servlet</artifactId> | <artifactId>jetty-servlet</artifactId> | ||||
| <scope>test</scope> | <scope>test</scope> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| </dependencies> | </dependencies> | ||||
| <build> | <build> | ||||
| @@ -0,0 +1,57 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 部门管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpDepartmentService { | |||||
| /** | |||||
| * <pre> | |||||
| * 部门管理接口 - 创建部门 | |||||
| * 最多支持创建500个部门 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * </pre> | |||||
| * | |||||
| * @param depart 部门 | |||||
| * @return 部门id | |||||
| */ | |||||
| Integer create(WxCpDepart depart) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 部门管理接口 - 查询所有部门 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * </pre> | |||||
| */ | |||||
| List<WxCpDepart> listAll() throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 部门管理接口 - 修改部门名 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误 | |||||
| * </pre> | |||||
| * | |||||
| * @param group 要更新的group,group的id,name必须设置 | |||||
| */ | |||||
| void update(WxCpDepart group) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 部门管理接口 - 删除部门 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 部门id | |||||
| */ | |||||
| void delete(Integer departId) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,57 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| /** | |||||
| * <pre> | |||||
| * 媒体管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpMediaService { | |||||
| /** | |||||
| * <pre> | |||||
| * 上传多媒体文件 | |||||
| * 上传的多媒体文件有格式和大小限制,如下: | |||||
| * 图片(image): 1M,支持JPG格式 | |||||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||||
| * 视频(video):10MB,支持MP4格式 | |||||
| * 缩略图(thumb):64KB,支持JPG格式 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
| * </pre> | |||||
| * | |||||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||||
| * @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} | |||||
| * @param inputStream 输入流 | |||||
| */ | |||||
| WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException; | |||||
| /** | |||||
| * @param mediaType 媒体类型 | |||||
| * @param file 文件对象 | |||||
| * @see #upload(String, String, InputStream) | |||||
| */ | |||||
| WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 下载多媒体文件 | |||||
| * 根据微信文档,视频文件下载不了,会返回null | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
| * </pre> | |||||
| * | |||||
| * @param mediaId 媒体id | |||||
| * @return 保存到本地的临时文件 | |||||
| */ | |||||
| File download(String mediaId) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,91 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 菜单管理相关接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpMenuService { | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单创建接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @param menu 菜单对象 | |||||
| * @see #create(Integer, WxMenu) | |||||
| */ | |||||
| void create(WxMenu menu) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单创建接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @param menu 菜单对象 | |||||
| * @see #create(me.chanjar.weixin.common.bean.menu.WxMenu) | |||||
| */ | |||||
| void create(Integer agentId, WxMenu menu) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单删除接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @see #delete(Integer) | |||||
| */ | |||||
| void delete() throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单删除接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @see #delete() | |||||
| */ | |||||
| void delete(Integer agentId) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单查询接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @see #get(Integer) | |||||
| */ | |||||
| WxMenu get() throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 自定义菜单查询接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @see #get() | |||||
| */ | |||||
| WxMenu get(Integer agentId) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * OAuth2相关管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpOAuth2Service { | |||||
| /** | |||||
| * <pre> | |||||
| * 构造oauth2授权的url连接 | |||||
| * </pre> | |||||
| * | |||||
| * @param state 状态码 | |||||
| * @return url | |||||
| */ | |||||
| String buildAuthorizationUrl(String state); | |||||
| /** | |||||
| * <pre> | |||||
| * 构造oauth2授权的url连接 | |||||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code | |||||
| * </pre> | |||||
| * | |||||
| * @param redirectUri 跳转链接地址 | |||||
| * @param state 状态码 | |||||
| * @return url | |||||
| */ | |||||
| String buildAuthorizationUrl(String redirectUri, String state); | |||||
| /** | |||||
| * <pre> | |||||
| * 用oauth2获取用户信息 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息 | |||||
| * 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @param code 微信oauth授权返回的代码 | |||||
| * @return [userid, deviceid] | |||||
| * @see #getUserInfo(Integer, String) | |||||
| */ | |||||
| String[] getUserInfo(String code) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 用oauth2获取用户信息 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息 | |||||
| * 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @param code 微信oauth授权返回的代码 | |||||
| * @return [userid, deviceid] | |||||
| * @see #getUserInfo(String) | |||||
| */ | |||||
| String[] getUserInfo(Integer agentId, String code) throws WxErrorException; | |||||
| } | |||||
| @@ -6,11 +6,11 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
| import me.chanjar.weixin.common.session.WxSession; | import me.chanjar.weixin.common.session.WxSession; | ||||
| import me.chanjar.weixin.common.session.WxSessionManager; | 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.RequestExecutor; | ||||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.cp.bean.*; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| import java.io.File; | import java.io.File; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
| @@ -35,16 +35,6 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); | boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); | ||||
| /** | |||||
| * <pre> | |||||
| * 用在二次验证的时候 | |||||
| * 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。 | |||||
| * </pre> | |||||
| * | |||||
| * @param userId 用户id | |||||
| */ | |||||
| void userAuthenticated(String userId) throws WxErrorException; | |||||
| /** | /** | ||||
| * 获取access_token, 不强制刷新access_token | * 获取access_token, 不强制刷新access_token | ||||
| * | * | ||||
| @@ -96,337 +86,208 @@ public interface WxCpService { | |||||
| WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 上传多媒体文件 | |||||
| * 上传的多媒体文件有格式和大小限制,如下: | |||||
| * 图片(image): 1M,支持JPG格式 | |||||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||||
| * 视频(video):10MB,支持MP4格式 | |||||
| * 缩略图(thumb):64KB,支持JPG格式 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
| * </pre> | |||||
| * | |||||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||||
| * @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} | |||||
| * @param inputStream 输入流 | |||||
| * @deprecated 请使用 {@link WxCpMenuService#create(WxMenu)} | |||||
| */ | */ | ||||
| WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException; | |||||
| @Deprecated | |||||
| void menuCreate(WxMenu menu) throws WxErrorException; | |||||
| /** | /** | ||||
| * @param mediaType 媒体类型 | |||||
| * @param file 文件对象 | |||||
| * @see #mediaUpload(String, String, InputStream) | |||||
| * @deprecated 请使用 {@link WxCpMenuService#create(Integer, WxMenu)} | |||||
| */ | */ | ||||
| WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 下载多媒体文件 | |||||
| * 根据微信文档,视频文件下载不了,会返回null | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
| * </pre> | |||||
| * | |||||
| * @param mediaId 媒体id | |||||
| * @return 保存到本地的临时文件 | |||||
| */ | |||||
| File mediaDownload(String mediaId) throws WxErrorException; | |||||
| @Deprecated | |||||
| void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 发送消息 | |||||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |||||
| * </pre> | |||||
| * | |||||
| * @param message 要发送的消息对象 | |||||
| * @deprecated 请使用 {@link WxCpMenuService#delete()} } | |||||
| */ | */ | ||||
| void messageSend(WxCpMessage message) throws WxErrorException; | |||||
| @Deprecated | |||||
| void menuDelete() throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 自定义菜单创建接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @param menu 菜单对象 | |||||
| * @see #menuCreate(Integer, WxMenu) | |||||
| * @deprecated 请使用 {@link WxCpMenuService#delete(Integer)} | |||||
| */ | */ | ||||
| void menuCreate(WxMenu menu) throws WxErrorException; | |||||
| @Deprecated | |||||
| void menuDelete(Integer agentId) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 自定义菜单创建接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @param menu 菜单对象 | |||||
| * @see #menuCreate(me.chanjar.weixin.common.bean.menu.WxMenu) | |||||
| * @deprecated 请使用 {@link WxCpMenuService#get() } | |||||
| */ | */ | ||||
| void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException; | |||||
| @Deprecated | |||||
| WxMenu menuGet() throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 自定义菜单删除接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @see #menuDelete(Integer) | |||||
| * @deprecated 请使用 {@link WxCpMenuService#get(Integer)} | |||||
| */ | */ | ||||
| void menuDelete() throws WxErrorException; | |||||
| @Deprecated | |||||
| WxMenu menuGet(Integer agentId) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 自定义菜单删除接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * 发送消息 | |||||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param agentId 企业号应用的id | |||||
| * @see #menuDelete() | |||||
| * @param message 要发送的消息对象 | |||||
| */ | */ | ||||
| void menuDelete(Integer agentId) throws WxErrorException; | |||||
| WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 自定义菜单查询接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @see #menuGet(Integer) | |||||
| * @deprecated 请使用 {@link WxCpDepartmentService#create(WxCpDepart)} | |||||
| */ | */ | ||||
| WxMenu menuGet() throws WxErrorException; | |||||
| @Deprecated | |||||
| Integer departCreate(WxCpDepart depart) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 自定义菜单查询接口 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @see #menuGet() | |||||
| * @deprecated 请使用 {@link WxCpDepartmentService#update(WxCpDepart)} | |||||
| */ | */ | ||||
| WxMenu menuGet(Integer agentId) throws WxErrorException; | |||||
| @Deprecated | |||||
| void departUpdate(WxCpDepart group) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 部门管理接口 - 创建部门 | |||||
| * 最多支持创建500个部门 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * </pre> | |||||
| * | |||||
| * @param depart 部门 | |||||
| * @return 部门id | |||||
| * @deprecated 请使用 {@link WxCpDepartmentService#delete(Integer)} | |||||
| */ | */ | ||||
| Integer departCreate(WxCpDepart depart) throws WxErrorException; | |||||
| @Deprecated | |||||
| void departDelete(Integer departId) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 部门管理接口 - 查询所有部门 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * </pre> | |||||
| * @deprecated 请使用 {@link WxCpDepartmentService#listAll() } | |||||
| */ | */ | ||||
| @Deprecated | |||||
| List<WxCpDepart> departGet() throws WxErrorException; | List<WxCpDepart> departGet() throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 部门管理接口 - 修改部门名 | |||||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||||
| * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误 | |||||
| * </pre> | |||||
| * | |||||
| * @param group 要更新的group,group的id,name必须设置 | |||||
| * @deprecated 请使用 {@link WxCpMediaService#upload(String, String, InputStream)} | |||||
| */ | */ | ||||
| void departUpdate(WxCpDepart group) throws WxErrorException; | |||||
| @Deprecated | |||||
| WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 部门管理接口 - 删除部门 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 部门id | |||||
| * @deprecated 请使用 {@link WxCpMediaService#upload(String, File)} | |||||
| */ | */ | ||||
| void departDelete(Integer departId) throws WxErrorException; | |||||
| @Deprecated | |||||
| WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 获取部门成员(详情) | |||||
| * | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 必填。部门id | |||||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||||
| * @deprecated 请使用 {@link WxCpMediaService#download(String)} | |||||
| */ | */ | ||||
| List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| @Deprecated | |||||
| File mediaDownload(String mediaId) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | |||||
| * 获取部门成员 | |||||
| * | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 必填。部门id | |||||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||||
| * @deprecated 请使用 {@link WxCpUserService#authenticate(String)} | |||||
| */ | */ | ||||
| List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| @Deprecated | |||||
| void userAuthenticated(String userId) throws WxErrorException; | |||||
| /** | /** | ||||
| * 新建用户 | |||||
| * | |||||
| * @param user 用户对象 | |||||
| * @deprecated 请使用 {@link WxCpUserService#create(WxCpUser)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void userCreate(WxCpUser user) throws WxErrorException; | void userCreate(WxCpUser user) throws WxErrorException; | ||||
| /** | /** | ||||
| * 更新用户 | |||||
| * | |||||
| * @param user 用户对象 | |||||
| * @deprecated 请使用 {@link WxCpUserService#update(WxCpUser)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void userUpdate(WxCpUser user) throws WxErrorException; | void userUpdate(WxCpUser user) throws WxErrorException; | ||||
| /** | /** | ||||
| * 删除用户 | |||||
| * | |||||
| * @param userid 用户id | |||||
| * @deprecated 请使用 {@link WxCpUserService#delete(String...)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void userDelete(String userid) throws WxErrorException; | void userDelete(String userid) throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 批量删除成员 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98 | |||||
| * </pre> | |||||
| * | |||||
| * @param userids 员工UserID列表。对应管理端的帐号 | |||||
| * @deprecated 请使用 {@link WxCpUserService#delete(String...)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void userDelete(String[] userids) throws WxErrorException; | void userDelete(String[] userids) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取用户 | |||||
| * | |||||
| * @param userid 用户id | |||||
| * @deprecated 请使用 {@link WxCpUserService#getById(String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| WxCpUser userGet(String userid) throws WxErrorException; | WxCpUser userGet(String userid) throws WxErrorException; | ||||
| /** | /** | ||||
| * 创建标签 | |||||
| * | |||||
| * @param tagName 标签名 | |||||
| * @deprecated 请使用 {@link WxCpUserService#listByDepartment(Integer, Boolean, Integer)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| /** | |||||
| * @deprecated 请使用 {@link WxCpUserService#listSimpleByDepartment(Integer, Boolean, Integer)} | |||||
| */ | |||||
| @Deprecated | |||||
| List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| /** | |||||
| * @deprecated 请使用 {@link WxCpTagService#create(String)} | |||||
| */ | |||||
| @Deprecated | |||||
| String tagCreate(String tagName) throws WxErrorException; | String tagCreate(String tagName) throws WxErrorException; | ||||
| /** | /** | ||||
| * 更新标签 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| * @param tagName 标签名 | |||||
| * @deprecated 请使用 {@link WxCpTagService#update(String, String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void tagUpdate(String tagId, String tagName) throws WxErrorException; | void tagUpdate(String tagId, String tagName) throws WxErrorException; | ||||
| /** | /** | ||||
| * 删除标签 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| * @deprecated 请使用 {@link WxCpTagService#delete(String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void tagDelete(String tagId) throws WxErrorException; | void tagDelete(String tagId) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获得标签列表 | |||||
| * @deprecated 请使用 {@link WxCpTagService#listAll()} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| List<WxCpTag> tagGet() throws WxErrorException; | List<WxCpTag> tagGet() throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取标签成员 | |||||
| * | |||||
| * @param tagId 标签ID | |||||
| * @deprecated 请使用 {@link WxCpTagService#listUsersByTagId(String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException; | List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException; | ||||
| /** | /** | ||||
| * 增加标签成员 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| * @param userIds 用户ID 列表 | |||||
| * @deprecated 请使用 {@link WxCpTagService#addUsers2Tag(String, List, List)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException; | void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 构造oauth2授权的url连接 | |||||
| * </pre> | |||||
| * | |||||
| * @param state 状态码 | |||||
| * @return url | |||||
| * @deprecated 请使用 {@link WxCpTagService#removeUsersFromTag(String, List)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException; | |||||
| /** | |||||
| * @deprecated 请使用 {@link WxCpOAuth2Service#buildAuthorizationUrl(String)} | |||||
| */ | |||||
| @Deprecated | |||||
| String oauth2buildAuthorizationUrl(String state); | String oauth2buildAuthorizationUrl(String state); | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 构造oauth2授权的url连接 | |||||
| * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code | |||||
| * </pre> | |||||
| * | |||||
| * @param redirectUri 跳转链接地址 | |||||
| * @param state 状态码 | |||||
| * @return url | |||||
| * @deprecated 请使用 {@link WxCpOAuth2Service#buildAuthorizationUrl(String, String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| String oauth2buildAuthorizationUrl(String redirectUri, String state); | String oauth2buildAuthorizationUrl(String redirectUri, String state); | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 用oauth2获取用户信息 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息 | |||||
| * 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @param code 微信oauth授权返回的代码 | |||||
| * @return [userid, deviceid] | |||||
| * @see #oauth2getUserInfo(Integer, String) | |||||
| * @deprecated 请使用 {@link WxCpOAuth2Service#getUserInfo(String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| String[] oauth2getUserInfo(String code) throws WxErrorException; | String[] oauth2getUserInfo(String code) throws WxErrorException; | ||||
| /** | /** | ||||
| * <pre> | |||||
| * 用oauth2获取用户信息 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息 | |||||
| * 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。 | |||||
| * | |||||
| * 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出 | |||||
| * </pre> | |||||
| * | |||||
| * @param agentId 企业号应用的id | |||||
| * @param code 微信oauth授权返回的代码 | |||||
| * @return [userid, deviceid] | |||||
| * @see #oauth2getUserInfo(String) | |||||
| * @deprecated 请使用 {@link WxCpOAuth2Service#getUserInfo(Integer, String)} | |||||
| */ | */ | ||||
| @Deprecated | |||||
| String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException; | String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException; | ||||
| /** | |||||
| * 移除标签成员 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| * @param userIds 用户id列表 | |||||
| */ | |||||
| void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 邀请成员关注 | * 邀请成员关注 | ||||
| @@ -469,7 +330,7 @@ public interface WxCpService { | |||||
| * <pre> | * <pre> | ||||
| * Service没有实现某个API的时候,可以用这个, | * Service没有实现某个API的时候,可以用这个, | ||||
| * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | ||||
| * 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法 | |||||
| * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param executor 执行器 | * @param executor 执行器 | ||||
| @@ -480,13 +341,6 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | ||||
| /** | |||||
| * 注入 {@link WxCpConfigStorage} 的实现 | |||||
| * | |||||
| * @param wxConfigProvider 配置对象 | |||||
| */ | |||||
| void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | ||||
| @@ -550,4 +404,58 @@ public interface WxCpService { | |||||
| * 获取异步任务结果 | * 获取异步任务结果 | ||||
| */ | */ | ||||
| String getTaskResult(String joinId) throws WxErrorException; | String getTaskResult(String joinId) throws WxErrorException; | ||||
| /** | |||||
| * 初始化http请求对象 | |||||
| */ | |||||
| void initHttp(); | |||||
| /** | |||||
| * 获取WxMpConfigStorage 对象 | |||||
| * | |||||
| * @return WxMpConfigStorage | |||||
| */ | |||||
| WxCpConfigStorage getWxCpConfigStorage(); | |||||
| /** | |||||
| * 注入 {@link WxCpConfigStorage} 的实现 | |||||
| * | |||||
| * @param wxConfigProvider 配置对象 | |||||
| */ | |||||
| void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | |||||
| /** | |||||
| * 获取部门相关接口的服务类对象 | |||||
| */ | |||||
| WxCpDepartmentService getDepartmentService(); | |||||
| /** | |||||
| * 获取媒体相关接口的服务类对象 | |||||
| */ | |||||
| WxCpMediaService getMediaService(); | |||||
| /** | |||||
| * 获取菜单相关接口的服务类对象 | |||||
| */ | |||||
| WxCpMenuService getMenuService(); | |||||
| /** | |||||
| * 获取Oauth2相关接口的服务类对象 | |||||
| */ | |||||
| WxCpOAuth2Service getOauth2Service(); | |||||
| /** | |||||
| * 获取标签相关接口的服务类对象 | |||||
| */ | |||||
| WxCpTagService getTagService(); | |||||
| /** | |||||
| * 获取用户相关接口的服务类对象 | |||||
| */ | |||||
| WxCpUserService getUserService(); | |||||
| /** | |||||
| * http请求对象 | |||||
| */ | |||||
| RequestHttp getRequestHttp(); | |||||
| } | } | ||||
| @@ -1,697 +0,0 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.session.StandardSessionManager; | |||||
| import me.chanjar.weixin.common.session.WxSession; | |||||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||||
| import me.chanjar.weixin.common.util.RandomUtils; | |||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import me.chanjar.weixin.common.util.http.*; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | |||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| 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.BasicResponseHandler; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.List; | |||||
| import java.util.UUID; | |||||
| public class WxCpServiceImpl implements WxCpService { | |||||
| protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class); | |||||
| /** | |||||
| * 全局的是否正在刷新access token的锁 | |||||
| */ | |||||
| protected final Object globalAccessTokenRefreshLock = new Object(); | |||||
| /** | |||||
| * 全局的是否正在刷新jsapi_ticket的锁 | |||||
| */ | |||||
| protected final Object globalJsapiTicketRefreshLock = new Object(); | |||||
| protected WxCpConfigStorage configStorage; | |||||
| protected CloseableHttpClient httpClient; | |||||
| protected HttpHost httpProxy; | |||||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | |||||
| /** | |||||
| * 临时文件目录 | |||||
| */ | |||||
| protected File tmpDirFile; | |||||
| private int retrySleepMillis = 1000; | |||||
| private int maxRetryTimes = 5; | |||||
| @Override | |||||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||||
| try { | |||||
| return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) | |||||
| .equals(msgSignature); | |||||
| } catch (Exception e) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void userAuthenticated(String userId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId; | |||||
| get(url, null); | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken() throws WxErrorException { | |||||
| return getAccessToken(false); | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireAccessToken(); | |||||
| } | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom() | |||||
| .setProxy(this.httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| String resultContent = null; | |||||
| try (CloseableHttpClient httpclient = getHttpclient(); | |||||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken( | |||||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket() throws WxErrorException { | |||||
| return getJsapiTicket(false); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireJsapiTicket(); | |||||
| } | |||||
| if (this.configStorage.isJsapiTicketExpired()) { | |||||
| synchronized (this.globalJsapiTicketRefreshLock) { | |||||
| if (this.configStorage.isJsapiTicketExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||||
| String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||||
| this.configStorage.updateJsapiTicket(jsapiTicket, | |||||
| expiresInSeconds); | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getJsapiTicket(); | |||||
| } | |||||
| @Override | |||||
| public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||||
| long timestamp = System.currentTimeMillis() / 1000; | |||||
| String noncestr = RandomUtils.getRandomStr(); | |||||
| String jsapiTicket = getJsapiTicket(false); | |||||
| String signature = SHA1.genWithAmple( | |||||
| "jsapi_ticket=" + jsapiTicket, | |||||
| "noncestr=" + noncestr, | |||||
| "timestamp=" + timestamp, | |||||
| "url=" + url | |||||
| ); | |||||
| WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||||
| jsapiSignature.setTimestamp(timestamp); | |||||
| jsapiSignature.setNonceStr(noncestr); | |||||
| jsapiSignature.setUrl(url); | |||||
| jsapiSignature.setSignature(signature); | |||||
| // Fixed bug | |||||
| jsapiSignature.setAppId(this.configStorage.getCorpId()); | |||||
| return jsapiSignature; | |||||
| } | |||||
| @Override | |||||
| public void messageSend(WxCpMessage message) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||||
| post(url, message.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void menuCreate(WxMenu menu) throws WxErrorException { | |||||
| menuCreate(this.configStorage.getAgentId(), menu); | |||||
| } | |||||
| @Override | |||||
| public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" | |||||
| + this.configStorage.getAgentId(); | |||||
| post(url, menu.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void menuDelete() throws WxErrorException { | |||||
| menuDelete(this.configStorage.getAgentId()); | |||||
| } | |||||
| @Override | |||||
| public void menuDelete(Integer agentId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId; | |||||
| get(url, null); | |||||
| } | |||||
| @Override | |||||
| public WxMenu menuGet() throws WxErrorException { | |||||
| return menuGet(this.configStorage.getAgentId()); | |||||
| } | |||||
| @Override | |||||
| public WxMenu menuGet(Integer agentId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId; | |||||
| try { | |||||
| String resultContent = get(url, null); | |||||
| return WxMenu.fromJson(resultContent); | |||||
| } catch (WxErrorException e) { | |||||
| // 46003 不存在的菜单数据 | |||||
| if (e.getError().getErrorCode() == 46003) { | |||||
| return null; | |||||
| } | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException { | |||||
| return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; | |||||
| return execute(new MediaUploadRequestExecutor(), url, file); | |||||
| } | |||||
| @Override | |||||
| public File mediaDownload(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||||
| return execute( | |||||
| new MediaDownloadRequestExecutor( | |||||
| this.configStorage.getTmpDirFile()), | |||||
| url, "media_id=" + mediaId); | |||||
| } | |||||
| @Override | |||||
| public Integer departCreate(WxCpDepart depart) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | |||||
| String responseContent = execute( | |||||
| new SimplePostRequestExecutor(), | |||||
| url, | |||||
| depart.toJson()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||||
| } | |||||
| @Override | |||||
| public void departUpdate(WxCpDepart group) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update"; | |||||
| post(url, group.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void departDelete(Integer departId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId; | |||||
| get(url, null); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpDepart> departGet() throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list"; | |||||
| String responseContent = get(url, null); | |||||
| /* | |||||
| * 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} } | |||||
| * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] } | |||||
| */ | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("department"), | |||||
| new TypeToken<List<WxCpDepart>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public void userCreate(WxCpUser user) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create"; | |||||
| post(url, user.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void userUpdate(WxCpUser user) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update"; | |||||
| post(url, user.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void userDelete(String userid) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userid; | |||||
| get(url, null); | |||||
| } | |||||
| @Override | |||||
| public void userDelete(String[] userids) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userid : userids) { | |||||
| jsonArray.add(new JsonPrimitive(userid)); | |||||
| } | |||||
| jsonObject.add("useridlist", jsonArray); | |||||
| post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public WxCpUser userGet(String userid) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid; | |||||
| String responseContent = get(url, null); | |||||
| return WxCpUser.fromJson(responseContent); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> userList(Integer 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) { | |||||
| params += "&fetch_child=" + (fetchChild ? "1" : "0"); | |||||
| } | |||||
| if (status != null) { | |||||
| params += "&status=" + status; | |||||
| } else { | |||||
| params += "&status=0"; | |||||
| } | |||||
| String responseContent = get(url, params); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> departGetUsers(Integer 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) { | |||||
| params += "&fetch_child=" + (fetchChild ? "1" : "0"); | |||||
| } | |||||
| if (status != null) { | |||||
| params += "&status=" + status; | |||||
| } else { | |||||
| params += "&status=0"; | |||||
| } | |||||
| String responseContent = get(url, params); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public String tagCreate(String tagName) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create"; | |||||
| JsonObject o = new JsonObject(); | |||||
| o.addProperty("tagname", tagName); | |||||
| String responseContent = post(url, o.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return tmpJsonElement.getAsJsonObject().get("tagid").getAsString(); | |||||
| } | |||||
| @Override | |||||
| public void tagUpdate(String tagId, String tagName) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update"; | |||||
| JsonObject o = new JsonObject(); | |||||
| o.addProperty("tagid", tagId); | |||||
| o.addProperty("tagname", tagName); | |||||
| post(url, o.toString()); | |||||
| } | |||||
| @Override | |||||
| public void tagDelete(String tagId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId; | |||||
| get(url, null); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpTag> tagGet() throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list"; | |||||
| String responseContent = get(url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||||
| new TypeToken<List<WxCpTag>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId; | |||||
| String responseContent = get(url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("tagid", tagId); | |||||
| if (userIds != null) { | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : userIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("userlist", jsonArray); | |||||
| } | |||||
| if (partyIds != null) { | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : partyIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("partylist", jsonArray); | |||||
| } | |||||
| post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("tagid", tagId); | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : userIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("userlist", jsonArray); | |||||
| post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public String oauth2buildAuthorizationUrl(String state) { | |||||
| return this.oauth2buildAuthorizationUrl( | |||||
| this.configStorage.getOauth2redirectUri(), | |||||
| state | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public String oauth2buildAuthorizationUrl(String redirectUri, String state) { | |||||
| String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; | |||||
| url += "appid=" + this.configStorage.getCorpId(); | |||||
| url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); | |||||
| url += "&response_type=code"; | |||||
| url += "&scope=snsapi_base"; | |||||
| if (state != null) { | |||||
| url += "&state=" + state; | |||||
| } | |||||
| url += "#wechat_redirect"; | |||||
| return url; | |||||
| } | |||||
| @Override | |||||
| public String[] oauth2getUserInfo(String code) throws WxErrorException { | |||||
| return oauth2getUserInfo(this.configStorage.getAgentId(), code); | |||||
| } | |||||
| @Override | |||||
| public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" | |||||
| + "code=" + code | |||||
| + "&agentid=" + agentId; | |||||
| String responseText = get(url, null); | |||||
| JsonElement je = new JsonParser().parse(responseText); | |||||
| JsonObject jo = je.getAsJsonObject(); | |||||
| return new String[]{GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId"), GsonHelper.getString(jo, "OpenId")}; | |||||
| } | |||||
| @Override | |||||
| public int invite(String userId, String inviteTips) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/invite/send"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("userid", userId); | |||||
| if (StringUtils.isNotEmpty(inviteTips)) { | |||||
| jsonObject.addProperty("invite_tips", inviteTips); | |||||
| } | |||||
| String responseContent = post(url, jsonObject.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return tmpJsonElement.getAsJsonObject().get("type").getAsInt(); | |||||
| } | |||||
| @Override | |||||
| public String[] getCallbackIp() throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; | |||||
| String responseContent = get(url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | |||||
| String[] ips = new String[jsonArray.size()]; | |||||
| for (int i = 0; i < jsonArray.size(); i++) { | |||||
| ips[i] = jsonArray.get(i).getAsString(); | |||||
| } | |||||
| return ips; | |||||
| } | |||||
| @Override | |||||
| public String get(String url, String queryParam) throws WxErrorException { | |||||
| return execute(new SimpleGetRequestExecutor(), url, queryParam); | |||||
| } | |||||
| @Override | |||||
| public String post(String url, String postData) throws WxErrorException { | |||||
| return execute(new SimplePostRequestExecutor(), url, postData); | |||||
| } | |||||
| /** | |||||
| * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||||
| */ | |||||
| @Override | |||||
| public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
| int retryTimes = 0; | |||||
| do { | |||||
| try { | |||||
| T result = this.executeInternal(executor, uri, data); | |||||
| this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",uri, data, result); | |||||
| return result; | |||||
| } catch (WxErrorException e) { | |||||
| if (retryTimes + 1 > this.maxRetryTimes) { | |||||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||||
| //最后一次重试失败后,直接抛出异常,不再等待 | |||||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
| } | |||||
| WxError error = e.getError(); | |||||
| /* | |||||
| * -1 系统繁忙, 1000ms后重试 | |||||
| */ | |||||
| if (error.getErrorCode() == -1) { | |||||
| int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||||
| try { | |||||
| this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||||
| Thread.sleep(sleepMillis); | |||||
| } catch (InterruptedException e1) { | |||||
| throw new RuntimeException(e1); | |||||
| } | |||||
| } else { | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| } while (retryTimes++ < this.maxRetryTimes); | |||||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
| } | |||||
| protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
| if (uri.contains("access_token=")) { | |||||
| throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); | |||||
| } | |||||
| String accessToken = getAccessToken(false); | |||||
| String uriWithAccessToken = uri; | |||||
| uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | |||||
| try { | |||||
| return executor.execute(getHttpclient(), this.httpProxy, uriWithAccessToken, data); | |||||
| } catch (WxErrorException e) { | |||||
| WxError error = e.getError(); | |||||
| /* | |||||
| * 发生以下情况时尝试刷新access_token | |||||
| * 40001 获取access_token时AppSecret错误,或者access_token无效 | |||||
| * 42001 access_token超时 | |||||
| */ | |||||
| if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||||
| // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
| this.configStorage.expireAccessToken(); | |||||
| return execute(executor, uri, data); | |||||
| } | |||||
| if (error.getErrorCode() != 0) { | |||||
| this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, error); | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return null; | |||||
| } catch (IOException e) { | |||||
| this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", uri, data, e.getMessage()); | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| protected CloseableHttpClient getHttpclient() { | |||||
| return this.httpClient; | |||||
| } | |||||
| @Override | |||||
| public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { | |||||
| this.configStorage = wxConfigProvider; | |||||
| ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage | |||||
| .getApacheHttpClientBuilder(); | |||||
| if (null == apacheHttpClientBuilder) { | |||||
| apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||||
| } | |||||
| apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost()) | |||||
| .httpProxyPort(this.configStorage.getHttpProxyPort()) | |||||
| .httpProxyUsername(this.configStorage.getHttpProxyUsername()) | |||||
| .httpProxyPassword(this.configStorage.getHttpProxyPassword()); | |||||
| if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { | |||||
| this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort()); | |||||
| } | |||||
| this.httpClient = apacheHttpClientBuilder.build(); | |||||
| } | |||||
| @Override | |||||
| public void setRetrySleepMillis(int retrySleepMillis) { | |||||
| this.retrySleepMillis = retrySleepMillis; | |||||
| } | |||||
| @Override | |||||
| public void setMaxRetryTimes(int maxRetryTimes) { | |||||
| this.maxRetryTimes = maxRetryTimes; | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession(String id) { | |||||
| if (this.sessionManager == null) { | |||||
| return null; | |||||
| } | |||||
| return this.sessionManager.getSession(id); | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession(String id, boolean create) { | |||||
| if (this.sessionManager == null) { | |||||
| return null; | |||||
| } | |||||
| return this.sessionManager.getSession(id, create); | |||||
| } | |||||
| @Override | |||||
| public void setSessionManager(WxSessionManager sessionManager) { | |||||
| this.sessionManager = sessionManager; | |||||
| } | |||||
| @Override | |||||
| public String replaceParty(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("media_id", mediaId); | |||||
| return post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public String replaceUser(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("media_id", mediaId); | |||||
| return post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public String getTaskResult(String joinId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId; | |||||
| return get(url, null); | |||||
| } | |||||
| public File getTmpDirFile() { | |||||
| return this.tmpDirFile; | |||||
| } | |||||
| public void setTmpDirFile(File tmpDirFile) { | |||||
| this.tmpDirFile = tmpDirFile; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTagAddOrRemoveUsersResult; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 标签管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpTagService { | |||||
| /** | |||||
| * 创建标签 | |||||
| * | |||||
| * @param tagName 标签名 | |||||
| */ | |||||
| String create(String tagName) throws WxErrorException; | |||||
| /** | |||||
| * 更新标签 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| * @param tagName 标签名 | |||||
| */ | |||||
| void update(String tagId, String tagName) throws WxErrorException; | |||||
| /** | |||||
| * 删除标签 | |||||
| * | |||||
| * @param tagId 标签id | |||||
| */ | |||||
| void delete(String tagId) throws WxErrorException; | |||||
| /** | |||||
| * 获得标签列表 | |||||
| */ | |||||
| List<WxCpTag> listAll() throws WxErrorException; | |||||
| /** | |||||
| * 获取标签成员 | |||||
| * | |||||
| * @param tagId 标签ID | |||||
| */ | |||||
| List<WxCpUser> listUsersByTagId(String tagId) throws WxErrorException; | |||||
| /** | |||||
| * 增加标签成员 | |||||
| * @param tagId 标签id | |||||
| * @param userIds 用户ID 列表 | |||||
| */ | |||||
| WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException; | |||||
| /** | |||||
| * 移除标签成员 | |||||
| * @param tagId 标签id | |||||
| * @param userIds 用户id列表 | |||||
| */ | |||||
| WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List<String> userIds) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,83 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 用户管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public interface WxCpUserService { | |||||
| /** | |||||
| * <pre> | |||||
| * 用在二次验证的时候 | |||||
| * 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。 | |||||
| * </pre> | |||||
| * | |||||
| * @param userId 用户id | |||||
| */ | |||||
| void authenticate(String userId) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取部门成员(详情) | |||||
| * | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 必填。部门id | |||||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||||
| */ | |||||
| List<WxCpUser> listByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取部门成员 | |||||
| * | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98 | |||||
| * </pre> | |||||
| * | |||||
| * @param departId 必填。部门id | |||||
| * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 | |||||
| * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 | |||||
| */ | |||||
| List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; | |||||
| /** | |||||
| * 新建用户 | |||||
| * | |||||
| * @param user 用户对象 | |||||
| */ | |||||
| void create(WxCpUser user) throws WxErrorException; | |||||
| /** | |||||
| * 更新用户 | |||||
| * | |||||
| * @param user 用户对象 | |||||
| */ | |||||
| void update(WxCpUser user) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 删除用户/批量删除成员 | |||||
| * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98 | |||||
| * </pre> | |||||
| * | |||||
| * @param userIds 员工UserID列表。对应管理端的帐号 | |||||
| */ | |||||
| void delete(String... userIds) throws WxErrorException; | |||||
| /** | |||||
| * 获取用户 | |||||
| * | |||||
| * @param userid 用户id | |||||
| */ | |||||
| WxCpUser getById(String userid) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,543 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| 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.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.session.StandardSessionManager; | |||||
| import me.chanjar.weixin.common.session.WxSession; | |||||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||||
| import me.chanjar.weixin.common.util.RandomUtils; | |||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||||
| 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.config.WxCpConfigStorage; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.List; | |||||
| public abstract class AbstractWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||||
| private WxCpUserService userService = new WxCpUserServiceImpl(this); | |||||
| private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this); | |||||
| private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this); | |||||
| private WxCpMenuService menuService = new WxCpMenuServiceImpl(this); | |||||
| private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this); | |||||
| private WxCpTagService tagService = new WxCpTagServiceImpl(this); | |||||
| /** | |||||
| * 全局的是否正在刷新access token的锁 | |||||
| */ | |||||
| protected final Object globalAccessTokenRefreshLock = new Object(); | |||||
| /** | |||||
| * 全局的是否正在刷新jsapi_ticket的锁 | |||||
| */ | |||||
| protected final Object globalJsapiTicketRefreshLock = new Object(); | |||||
| protected WxCpConfigStorage configStorage; | |||||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | |||||
| /** | |||||
| * 临时文件目录 | |||||
| */ | |||||
| protected File tmpDirFile; | |||||
| private int retrySleepMillis = 1000; | |||||
| private int maxRetryTimes = 5; | |||||
| @Override | |||||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||||
| try { | |||||
| return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) | |||||
| .equals(msgSignature); | |||||
| } catch (Exception e) { | |||||
| this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken() throws WxErrorException { | |||||
| return getAccessToken(false); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket() throws WxErrorException { | |||||
| return getJsapiTicket(false); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireJsapiTicket(); | |||||
| } | |||||
| if (this.configStorage.isJsapiTicketExpired()) { | |||||
| synchronized (this.globalJsapiTicketRefreshLock) { | |||||
| if (this.configStorage.isJsapiTicketExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||||
| String responseContent = execute(SimpleGetRequestExecutor.create(this), url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||||
| this.configStorage.updateJsapiTicket(jsapiTicket, | |||||
| expiresInSeconds); | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getJsapiTicket(); | |||||
| } | |||||
| @Override | |||||
| public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||||
| long timestamp = System.currentTimeMillis() / 1000; | |||||
| String noncestr = RandomUtils.getRandomStr(); | |||||
| String jsapiTicket = getJsapiTicket(false); | |||||
| String signature = SHA1.genWithAmple( | |||||
| "jsapi_ticket=" + jsapiTicket, | |||||
| "noncestr=" + noncestr, | |||||
| "timestamp=" + timestamp, | |||||
| "url=" + url | |||||
| ); | |||||
| WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||||
| jsapiSignature.setTimestamp(timestamp); | |||||
| jsapiSignature.setNonceStr(noncestr); | |||||
| jsapiSignature.setUrl(url); | |||||
| jsapiSignature.setSignature(signature); | |||||
| // Fixed bug | |||||
| jsapiSignature.setAppId(this.configStorage.getCorpId()); | |||||
| return jsapiSignature; | |||||
| } | |||||
| @Override | |||||
| public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||||
| return WxCpMessageSendResult.fromJson(this.post(url, message.toJson())); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void menuCreate(WxMenu menu) throws WxErrorException { | |||||
| this.getMenuService().create(menu); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException { | |||||
| this.getMenuService().create(agentId, menu); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void menuDelete() throws WxErrorException { | |||||
| this.getMenuService().delete(); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void menuDelete(Integer agentId) throws WxErrorException { | |||||
| this.getMenuService().delete(agentId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public WxMenu menuGet() throws WxErrorException { | |||||
| return this.getMenuService().get(); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public WxMenu menuGet(Integer agentId) throws WxErrorException { | |||||
| return this.getMenuService().get(agentId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException { | |||||
| return this.getMediaService().upload(mediaType, fileType, inputStream); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException { | |||||
| return this.getMediaService().upload(mediaType, file); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public File mediaDownload(String mediaId) throws WxErrorException { | |||||
| return this.getMediaService().download(mediaId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void userAuthenticated(String userId) throws WxErrorException { | |||||
| this.getUserService().authenticate(userId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void userCreate(WxCpUser user) throws WxErrorException { | |||||
| this.getUserService().create(user); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void userUpdate(WxCpUser user) throws WxErrorException { | |||||
| this.getUserService().update(user); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void userDelete(String userid) throws WxErrorException { | |||||
| this.getUserService().delete(userid); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void userDelete(String[] userids) throws WxErrorException { | |||||
| this.getUserService().delete(userids); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public WxCpUser userGet(String userid) throws WxErrorException { | |||||
| return this.getUserService().getById(userid); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||||
| return this.getUserService().listByDepartment(departId, fetchChild, status); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { | |||||
| return this.getUserService().listSimpleByDepartment(departId, fetchChild, status); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public String tagCreate(String tagName) throws WxErrorException { | |||||
| return this.getTagService().create(tagName); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void tagUpdate(String tagId, String tagName) throws WxErrorException { | |||||
| this.getTagService().update(tagId, tagName); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void tagDelete(String tagId) throws WxErrorException { | |||||
| this.getTagService().delete(tagId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public List<WxCpTag> tagGet() throws WxErrorException { | |||||
| return this.getTagService().listAll(); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException { | |||||
| return this.getTagService().listUsersByTagId(tagId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException { | |||||
| this.getTagService().addUsers2Tag(tagId, userIds, partyIds); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException { | |||||
| this.getTagService().removeUsersFromTag(tagId, userIds); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public String oauth2buildAuthorizationUrl(String state) { | |||||
| return this.getOauth2Service().buildAuthorizationUrl(state); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public String oauth2buildAuthorizationUrl(String redirectUri, String state) { | |||||
| return this.getOauth2Service().buildAuthorizationUrl(redirectUri, state); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public String[] oauth2getUserInfo(String code) throws WxErrorException { | |||||
| return this.getOauth2Service().getUserInfo(code); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException { | |||||
| return this.getOauth2Service().getUserInfo(agentId, code); | |||||
| } | |||||
| @Override | |||||
| public int invite(String userId, String inviteTips) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/invite/send"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("userid", userId); | |||||
| if (StringUtils.isNotEmpty(inviteTips)) { | |||||
| jsonObject.addProperty("invite_tips", inviteTips); | |||||
| } | |||||
| String responseContent = post(url, jsonObject.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return tmpJsonElement.getAsJsonObject().get("type").getAsInt(); | |||||
| } | |||||
| @Override | |||||
| public String[] getCallbackIp() throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; | |||||
| String responseContent = get(url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | |||||
| String[] ips = new String[jsonArray.size()]; | |||||
| for (int i = 0; i < jsonArray.size(); i++) { | |||||
| ips[i] = jsonArray.get(i).getAsString(); | |||||
| } | |||||
| return ips; | |||||
| } | |||||
| @Override | |||||
| public String get(String url, String queryParam) throws WxErrorException { | |||||
| return execute(SimpleGetRequestExecutor.create(this), url, queryParam); | |||||
| } | |||||
| @Override | |||||
| public String post(String url, String postData) throws WxErrorException { | |||||
| return execute(SimplePostRequestExecutor.create(this), url, postData); | |||||
| } | |||||
| /** | |||||
| * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||||
| */ | |||||
| @Override | |||||
| public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
| int retryTimes = 0; | |||||
| do { | |||||
| try { | |||||
| return this.executeInternal(executor, uri, data); | |||||
| } catch (WxErrorException e) { | |||||
| if (retryTimes + 1 > this.maxRetryTimes) { | |||||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||||
| //最后一次重试失败后,直接抛出异常,不再等待 | |||||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
| } | |||||
| WxError error = e.getError(); | |||||
| /* | |||||
| * -1 系统繁忙, 1000ms后重试 | |||||
| */ | |||||
| if (error.getErrorCode() == -1) { | |||||
| int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||||
| try { | |||||
| this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||||
| Thread.sleep(sleepMillis); | |||||
| } catch (InterruptedException e1) { | |||||
| throw new RuntimeException(e1); | |||||
| } | |||||
| } else { | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| } while (retryTimes++ < this.maxRetryTimes); | |||||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
| } | |||||
| protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
| if (uri.contains("access_token=")) { | |||||
| throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); | |||||
| } | |||||
| String accessToken = getAccessToken(false); | |||||
| String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken; | |||||
| try { | |||||
| T result = executor.execute(uriWithAccessToken, data); | |||||
| this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, data, result); | |||||
| return result; | |||||
| } catch (WxErrorException e) { | |||||
| WxError error = e.getError(); | |||||
| /* | |||||
| * 发生以下情况时尝试刷新access_token | |||||
| * 40001 获取access_token时AppSecret错误,或者access_token无效 | |||||
| * 42001 access_token超时 | |||||
| * 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 | |||||
| */ | |||||
| if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { | |||||
| // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
| this.configStorage.expireAccessToken(); | |||||
| return execute(executor, uri, data); | |||||
| } | |||||
| if (error.getErrorCode() != 0) { | |||||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, data, error); | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return null; | |||||
| } catch (IOException e) { | |||||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, data, e.getMessage()); | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { | |||||
| this.configStorage = wxConfigProvider; | |||||
| this.initHttp(); | |||||
| } | |||||
| @Override | |||||
| public void setRetrySleepMillis(int retrySleepMillis) { | |||||
| this.retrySleepMillis = retrySleepMillis; | |||||
| } | |||||
| @Override | |||||
| public void setMaxRetryTimes(int maxRetryTimes) { | |||||
| this.maxRetryTimes = maxRetryTimes; | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession(String id) { | |||||
| if (this.sessionManager == null) { | |||||
| return null; | |||||
| } | |||||
| return this.sessionManager.getSession(id); | |||||
| } | |||||
| @Override | |||||
| public WxSession getSession(String id, boolean create) { | |||||
| if (this.sessionManager == null) { | |||||
| return null; | |||||
| } | |||||
| return this.sessionManager.getSession(id, create); | |||||
| } | |||||
| @Override | |||||
| public void setSessionManager(WxSessionManager sessionManager) { | |||||
| this.sessionManager = sessionManager; | |||||
| } | |||||
| @Override | |||||
| public String replaceParty(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("media_id", mediaId); | |||||
| return post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public String replaceUser(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("media_id", mediaId); | |||||
| return post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public String getTaskResult(String joinId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId; | |||||
| return get(url, null); | |||||
| } | |||||
| public File getTmpDirFile() { | |||||
| return this.tmpDirFile; | |||||
| } | |||||
| public void setTmpDirFile(File tmpDirFile) { | |||||
| this.tmpDirFile = tmpDirFile; | |||||
| } | |||||
| @Override | |||||
| public WxCpDepartmentService getDepartmentService() { | |||||
| return departmentService; | |||||
| } | |||||
| @Override | |||||
| public WxCpMediaService getMediaService() { | |||||
| return mediaService; | |||||
| } | |||||
| @Override | |||||
| public WxCpMenuService getMenuService() { | |||||
| return menuService; | |||||
| } | |||||
| @Override | |||||
| public WxCpOAuth2Service getOauth2Service() { | |||||
| return oauth2Service; | |||||
| } | |||||
| @Override | |||||
| public WxCpTagService getTagService() { | |||||
| return tagService; | |||||
| } | |||||
| @Override | |||||
| public WxCpUserService getUserService() { | |||||
| return userService; | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public Integer departCreate(WxCpDepart depart) throws WxErrorException { | |||||
| return this.getDepartmentService().create(depart); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void departUpdate(WxCpDepart depart) throws WxErrorException { | |||||
| this.getDepartmentService().update(depart); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public void departDelete(Integer departId) throws WxErrorException { | |||||
| this.getDepartmentService().delete(departId); | |||||
| } | |||||
| @Override | |||||
| @Deprecated | |||||
| public List<WxCpDepart> departGet() throws WxErrorException { | |||||
| return this.getDepartmentService().listAll(); | |||||
| } | |||||
| @Override | |||||
| public RequestHttp getRequestHttp() { | |||||
| return this; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,65 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonParser; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.cp.api.WxCpDepartmentService; | |||||
| 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> | |||||
| * 部门管理接口 | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class WxCpDepartmentServiceImpl implements WxCpDepartmentService { | |||||
| private WxCpService mainService; | |||||
| public WxCpDepartmentServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public Integer create(WxCpDepart depart) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | |||||
| String responseContent = this.mainService.post(url, depart.toJson()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||||
| } | |||||
| @Override | |||||
| public void update(WxCpDepart group) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update"; | |||||
| this.mainService.post(url, group.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void delete(Integer 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> listAll() throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list"; | |||||
| String responseContent = this.mainService.get(url, null); | |||||
| /* | |||||
| * 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} } | |||||
| * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] } | |||||
| */ | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson(tmpJsonElement.getAsJsonObject().get("department"), | |||||
| new TypeToken<List<WxCpDepart>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||||
| import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||||
| import me.chanjar.weixin.cp.api.WxCpMediaService; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.util.UUID; | |||||
| /** | |||||
| * <pre> | |||||
| * 媒体管理接口 | |||||
| * Created by Binary Wang on 2017-6-25. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpMediaServiceImpl implements WxCpMediaService { | |||||
| private WxCpService mainService; | |||||
| public WxCpMediaServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream) | |||||
| throws WxErrorException, IOException { | |||||
| return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||||
| } | |||||
| @Override | |||||
| public WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; | |||||
| return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file); | |||||
| } | |||||
| @Override | |||||
| public File download(String mediaId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||||
| return this.mainService.execute( | |||||
| MediaDownloadRequestExecutor.create(this.mainService.getRequestHttp(), | |||||
| this.mainService.getWxCpConfigStorage().getTmpDirFile()), | |||||
| url, "media_id=" + mediaId); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.api.WxCpMenuService; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| /** | |||||
| * <pre> | |||||
| * 菜单管理相关接口 | |||||
| * Created by Binary Wang on 2017-6-25. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpMenuServiceImpl implements WxCpMenuService { | |||||
| private WxCpService mainService; | |||||
| public WxCpMenuServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public void create(WxMenu menu) throws WxErrorException { | |||||
| this.create(this.mainService.getWxCpConfigStorage().getAgentId(), menu); | |||||
| } | |||||
| @Override | |||||
| public void create(Integer agentId, WxMenu menu) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" + agentId; | |||||
| this.mainService.post(url, menu.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void delete() throws WxErrorException { | |||||
| this.delete(this.mainService.getWxCpConfigStorage().getAgentId()); | |||||
| } | |||||
| @Override | |||||
| public void delete(Integer agentId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId; | |||||
| this.mainService.get(url, null); | |||||
| } | |||||
| @Override | |||||
| public WxMenu get() throws WxErrorException { | |||||
| return this.get(this.mainService.getWxCpConfigStorage().getAgentId()); | |||||
| } | |||||
| @Override | |||||
| public WxMenu get(Integer agentId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId; | |||||
| try { | |||||
| String resultContent = this.mainService.get(url, null); | |||||
| return WxCpGsonBuilder.create().fromJson(resultContent, WxMenu.class); | |||||
| } catch (WxErrorException e) { | |||||
| // 46003 不存在的菜单数据 | |||||
| if (e.getError().getErrorCode() == 46003) { | |||||
| return null; | |||||
| } | |||||
| throw e; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.google.gson.JsonParser; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.URIUtil; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.cp.api.WxCpOAuth2Service; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| /** | |||||
| * <pre> | |||||
| * | |||||
| * Created by Binary Wang on 2017-6-25. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service { | |||||
| private WxCpService mainService; | |||||
| public WxCpOAuth2ServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public String buildAuthorizationUrl(String state) { | |||||
| return this.buildAuthorizationUrl( | |||||
| this.mainService.getWxCpConfigStorage().getOauth2redirectUri(), | |||||
| state | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public String buildAuthorizationUrl(String redirectUri, String state) { | |||||
| String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; | |||||
| url += "appid=" + this.mainService.getWxCpConfigStorage().getCorpId(); | |||||
| url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); | |||||
| url += "&response_type=code"; | |||||
| url += "&scope=snsapi_base"; | |||||
| if (state != null) { | |||||
| url += "&state=" + state; | |||||
| } | |||||
| url += "#wechat_redirect"; | |||||
| return url; | |||||
| } | |||||
| @Override | |||||
| public String[] getUserInfo(String code) throws WxErrorException { | |||||
| return getUserInfo(this.mainService.getWxCpConfigStorage().getAgentId(), code); | |||||
| } | |||||
| @Override | |||||
| public String[] getUserInfo(Integer agentId, String code) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" | |||||
| + "code=" + code | |||||
| + "&agentid=" + agentId; | |||||
| String responseText = this.mainService.get(url, null); | |||||
| JsonElement je = new JsonParser().parse(responseText); | |||||
| JsonObject jo = je.getAsJsonObject(); | |||||
| return new String[]{GsonHelper.getString(jo, "UserId"), | |||||
| GsonHelper.getString(jo, "DeviceId"), | |||||
| GsonHelper.getString(jo, "OpenId")}; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| 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.BasicResponseHandler; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.IOException; | |||||
| public class WxCpServiceApacheHttpClientImpl extends AbstractWxCpServiceImpl<CloseableHttpClient, HttpHost> { | |||||
| protected CloseableHttpClient httpClient; | |||||
| protected HttpHost httpProxy; | |||||
| @Override | |||||
| public CloseableHttpClient getRequestHttpClient() { | |||||
| return httpClient; | |||||
| } | |||||
| @Override | |||||
| public HttpHost getRequestHttpProxy() { | |||||
| return httpProxy; | |||||
| } | |||||
| @Override | |||||
| public HttpType getRequestType() { | |||||
| return HttpType.APACHE_HTTP; | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireAccessToken(); | |||||
| } | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom() | |||||
| .setProxy(this.httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| String resultContent = null; | |||||
| try (CloseableHttpClient httpclient = getRequestHttpClient(); | |||||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken( | |||||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| @Override | |||||
| public void initHttp() { | |||||
| ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage | |||||
| .getApacheHttpClientBuilder(); | |||||
| if (null == apacheHttpClientBuilder) { | |||||
| apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||||
| } | |||||
| apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost()) | |||||
| .httpProxyPort(this.configStorage.getHttpProxyPort()) | |||||
| .httpProxyUsername(this.configStorage.getHttpProxyUsername()) | |||||
| .httpProxyPassword(this.configStorage.getHttpProxyPassword()); | |||||
| if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { | |||||
| this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort()); | |||||
| } | |||||
| this.httpClient = apacheHttpClientBuilder.build(); | |||||
| } | |||||
| @Override | |||||
| public WxCpConfigStorage getWxCpConfigStorage() { | |||||
| return this.configStorage; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| /** | |||||
| * <pre> | |||||
| * 默认接口实现类,使用apache httpclient实现 | |||||
| * Created by Binary Wang on 2017-5-27. | |||||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl { | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import jodd.http.*; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| public class WxCpServiceJoddHttpImpl extends AbstractWxCpServiceImpl<HttpConnectionProvider, ProxyInfo> { | |||||
| protected HttpConnectionProvider httpClient; | |||||
| protected ProxyInfo httpProxy; | |||||
| @Override | |||||
| public HttpConnectionProvider getRequestHttpClient() { | |||||
| return httpClient; | |||||
| } | |||||
| @Override | |||||
| public ProxyInfo getRequestHttpProxy() { | |||||
| return httpProxy; | |||||
| } | |||||
| @Override | |||||
| public HttpType getRequestType() { | |||||
| return HttpType.JODD_HTTP; | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireAccessToken(); | |||||
| } | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| HttpRequest request = HttpRequest.get(url); | |||||
| if (this.httpProxy != null) { | |||||
| httpClient.useProxy(this.httpProxy); | |||||
| } | |||||
| request.withConnectionProvider(httpClient); | |||||
| HttpResponse response = request.send(); | |||||
| String resultContent = response.bodyText(); | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken( | |||||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| @Override | |||||
| public void initHttp() { | |||||
| if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { | |||||
| httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); | |||||
| } | |||||
| httpClient = JoddHttp.httpConnectionProvider; | |||||
| } | |||||
| @Override | |||||
| public WxCpConfigStorage getWxCpConfigStorage() { | |||||
| return this.configStorage; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,104 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | |||||
| import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| import okhttp3.*; | |||||
| import java.io.IOException; | |||||
| public class WxCpServiceOkHttpImpl extends AbstractWxCpServiceImpl<ConnectionPool, OkHttpProxyInfo> { | |||||
| protected ConnectionPool httpClient; | |||||
| protected OkHttpProxyInfo httpProxy; | |||||
| @Override | |||||
| public ConnectionPool getRequestHttpClient() { | |||||
| return httpClient; | |||||
| } | |||||
| @Override | |||||
| public OkHttpProxyInfo getRequestHttpProxy() { | |||||
| return httpProxy; | |||||
| } | |||||
| @Override | |||||
| public HttpType getRequestType() { | |||||
| return HttpType.OK_HTTP; | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireAccessToken(); | |||||
| } | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(httpClient); | |||||
| //设置代理 | |||||
| if (httpProxy != null) { | |||||
| clientBuilder.proxy(getRequestHttpProxy().getProxy()); | |||||
| } | |||||
| //设置授权 | |||||
| clientBuilder.authenticator(new Authenticator() { | |||||
| @Override | |||||
| public Request authenticate(Route route, Response response) throws IOException { | |||||
| String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); | |||||
| return response.request().newBuilder() | |||||
| .header("Authorization", credential) | |||||
| .build(); | |||||
| } | |||||
| }); | |||||
| //得到httpClient | |||||
| OkHttpClient client = clientBuilder.build(); | |||||
| //请求的request | |||||
| Request request = new Request.Builder().url(url).get().build(); | |||||
| Response response = null; | |||||
| try { | |||||
| response = client.newCall(request).execute(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| String resultContent = null; | |||||
| try { | |||||
| resultContent = response.body().string(); | |||||
| } catch (IOException e) { | |||||
| e.printStackTrace(); | |||||
| } | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), | |||||
| accessToken.getExpiresIn()); | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| @Override | |||||
| public void initHttp() { | |||||
| WxCpConfigStorage configStorage = this.configStorage; | |||||
| if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { | |||||
| httpProxy = new OkHttpProxyInfo(OkHttpProxyInfo.ProxyType.SOCKS5, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); | |||||
| } | |||||
| httpClient = new ConnectionPool(); | |||||
| } | |||||
| @Override | |||||
| public WxCpConfigStorage getWxCpConfigStorage() { | |||||
| return this.configStorage; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,116 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.api.WxCpTagService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTagAddOrRemoveUsersResult; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 标签管理接口 | |||||
| * Created by Binary Wang on 2017-6-25. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpTagServiceImpl implements WxCpTagService { | |||||
| private WxCpService mainService; | |||||
| public WxCpTagServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public String create(String tagName) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create"; | |||||
| JsonObject o = new JsonObject(); | |||||
| o.addProperty("tagname", tagName); | |||||
| String responseContent = this.mainService.post(url, o.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return tmpJsonElement.getAsJsonObject().get("tagid").getAsString(); | |||||
| } | |||||
| @Override | |||||
| public void update(String tagId, String tagName) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update"; | |||||
| JsonObject o = new JsonObject(); | |||||
| o.addProperty("tagid", tagId); | |||||
| o.addProperty("tagname", tagName); | |||||
| this.mainService.post(url, o.toString()); | |||||
| } | |||||
| @Override | |||||
| public void delete(String tagId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId; | |||||
| this.mainService.get(url, null); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpTag> listAll() throws WxErrorException { | |||||
| 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() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||||
| new TypeToken<List<WxCpTag>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> listUsersByTagId(String tagId) throws WxErrorException { | |||||
| 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() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("tagid", tagId); | |||||
| if (userIds != null) { | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : userIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("userlist", jsonArray); | |||||
| } | |||||
| if (partyIds != null) { | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : partyIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("partylist", jsonArray); | |||||
| } | |||||
| return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString())); | |||||
| } | |||||
| @Override | |||||
| public WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List<String> userIds) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("tagid", tagId); | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userId : userIds) { | |||||
| jsonArray.add(new JsonPrimitive(userId)); | |||||
| } | |||||
| jsonObject.add("userlist", jsonArray); | |||||
| return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString())); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,118 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.api.WxCpUserService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2017/6/24. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class WxCpUserServiceImpl implements WxCpUserService { | |||||
| private WxCpService mainService; | |||||
| public WxCpUserServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public void authenticate(String userId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId; | |||||
| this.mainService.get(url, null); | |||||
| } | |||||
| @Override | |||||
| public void create(WxCpUser user) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create"; | |||||
| this.mainService.post(url, user.toJson()); | |||||
| } | |||||
| @Override | |||||
| public void update(WxCpUser user) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update"; | |||||
| this.mainService.post(url, user.toJson()); | |||||
| } | |||||
| public void deleteOne(String userId) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userId; | |||||
| this.mainService.get(url, null); | |||||
| } | |||||
| @Override | |||||
| public void delete(String... userIds) throws WxErrorException { | |||||
| if (userIds.length == 1) { | |||||
| this.deleteOne(userIds[0]); | |||||
| } | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userid : userIds) { | |||||
| jsonArray.add(new JsonPrimitive(userid)); | |||||
| } | |||||
| jsonObject.add("useridlist", jsonArray); | |||||
| this.mainService.post(url, jsonObject.toString()); | |||||
| } | |||||
| @Override | |||||
| public WxCpUser getById(String userid) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid; | |||||
| String responseContent = this.mainService.get(url, null); | |||||
| return WxCpUser.fromJson(responseContent); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> listByDepartment(Integer 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) { | |||||
| params += "&fetch_child=" + (fetchChild ? "1" : "0"); | |||||
| } | |||||
| if (status != null) { | |||||
| params += "&status=" + status; | |||||
| } else { | |||||
| params += "&status=0"; | |||||
| } | |||||
| String responseContent = this.mainService.get(url, params); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson(tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpUser> listSimpleByDepartment(Integer 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) { | |||||
| params += "&fetch_child=" + (fetchChild ? "1" : "0"); | |||||
| } | |||||
| if (status != null) { | |||||
| params += "&status=" + status; | |||||
| } else { | |||||
| params += "&status=0"; | |||||
| } | |||||
| String responseContent = this.mainService.get(url, params); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.INSTANCE.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||||
| new TypeToken<List<WxCpUser>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,9 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.io.Serializable; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
| import java.io.Serializable; | |||||
| /** | /** | ||||
| * 微信部门 | * 微信部门 | ||||
| * | * | ||||
| @@ -60,10 +60,10 @@ public class WxCpDepart implements Serializable { | |||||
| @Override | @Override | ||||
| public String toString() { | public String toString() { | ||||
| return "WxCpDepart{" + | return "WxCpDepart{" + | ||||
| "id=" + this.id + | |||||
| ", name='" + this.name + '\'' + | |||||
| ", parentId=" + this.parentId + | |||||
| ", order=" + this.order + | |||||
| '}'; | |||||
| "id=" + this.id + | |||||
| ", name='" + this.name + '\'' + | |||||
| ", parentId=" + this.parentId + | |||||
| ", order=" + this.order + | |||||
| '}'; | |||||
| } | } | ||||
| } | } | ||||
| @@ -33,14 +33,6 @@ public class WxCpMessage implements Serializable { | |||||
| private List<NewArticle> articles = new ArrayList<>(); | private List<NewArticle> articles = new ArrayList<>(); | ||||
| private List<MpnewsArticle> mpnewsArticles = new ArrayList<>(); | private List<MpnewsArticle> mpnewsArticles = new ArrayList<>(); | ||||
| public List<MpnewsArticle> getMpnewsArticles() { | |||||
| return mpnewsArticles; | |||||
| } | |||||
| public void setMpnewsArticles(List<MpnewsArticle> mpnewsArticles) { | |||||
| this.mpnewsArticles = mpnewsArticles; | |||||
| } | |||||
| /** | /** | ||||
| * 获得文本消息builder | * 获得文本消息builder | ||||
| */ | */ | ||||
| @@ -90,6 +82,14 @@ public class WxCpMessage implements Serializable { | |||||
| return new FileBuilder(); | return new FileBuilder(); | ||||
| } | } | ||||
| public List<MpnewsArticle> getMpnewsArticles() { | |||||
| return mpnewsArticles; | |||||
| } | |||||
| public void setMpnewsArticles(List<MpnewsArticle> mpnewsArticles) { | |||||
| this.mpnewsArticles = mpnewsArticles; | |||||
| } | |||||
| public String getToUser() { | public String getToUser() { | ||||
| return this.toUser; | return this.toUser; | ||||
| } | } | ||||
| @@ -0,0 +1,103 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.common.base.Splitter; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.util.Collections; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 消息发送结果对象类 | |||||
| * Created by Binary Wang on 2017-6-22. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpMessageSendResult { | |||||
| @Override | |||||
| public String toString() { | |||||
| return ToStringUtils.toSimpleString(this); | |||||
| } | |||||
| public static WxCpMessageSendResult fromJson(String json) { | |||||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpMessageSendResult.class); | |||||
| } | |||||
| @SerializedName("errcode") | |||||
| private Integer errCode; | |||||
| @SerializedName("errmsg") | |||||
| private String errMsg; | |||||
| @SerializedName("invaliduser") | |||||
| private String invalidUser; | |||||
| @SerializedName("invalidparty") | |||||
| private String invalidParty; | |||||
| @SerializedName("invalidtag") | |||||
| private String invalidTag; | |||||
| public Integer getErrCode() { | |||||
| return this.errCode; | |||||
| } | |||||
| public void setErrCode(Integer errCode) { | |||||
| this.errCode = errCode; | |||||
| } | |||||
| public String getErrMsg() { | |||||
| return this.errMsg; | |||||
| } | |||||
| public void setErrMsg(String errMsg) { | |||||
| this.errMsg = errMsg; | |||||
| } | |||||
| public String getInvalidUser() { | |||||
| return this.invalidUser; | |||||
| } | |||||
| public void setInvalidUser(String invalidUser) { | |||||
| this.invalidUser = invalidUser; | |||||
| } | |||||
| public String getInvalidParty() { | |||||
| return this.invalidParty; | |||||
| } | |||||
| public void setInvalidParty(String invalidParty) { | |||||
| this.invalidParty = invalidParty; | |||||
| } | |||||
| public String getInvalidTag() { | |||||
| return this.invalidTag; | |||||
| } | |||||
| public void setInvalidTag(String invalidTag) { | |||||
| this.invalidTag = invalidTag; | |||||
| } | |||||
| public List<String> getInvalidUserList() { | |||||
| return this.content2List(this.invalidUser); | |||||
| } | |||||
| private List<String> content2List(String content) { | |||||
| if(StringUtils.isBlank(content)){ | |||||
| return Collections.emptyList(); | |||||
| } | |||||
| return Splitter.on("|").splitToList(content); | |||||
| } | |||||
| public List<String> getInvalidPartyList() { | |||||
| return this.content2List(this.invalidParty); | |||||
| } | |||||
| public List<String> getInvalidTagList() { | |||||
| return this.content2List(this.invalidTag); | |||||
| } | |||||
| } | |||||
| @@ -1,9 +1,9 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.io.Serializable; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
| import java.io.Serializable; | |||||
| /** | /** | ||||
| * Created by Daniel Qian | * Created by Daniel Qian | ||||
| */ | */ | ||||
| @@ -0,0 +1,85 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.common.base.Splitter; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.util.Collections; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 为标签添加或移除用户结果对象类 | |||||
| * Created by Binary Wang on 2017-6-22. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpTagAddOrRemoveUsersResult { | |||||
| @Override | |||||
| public String toString() { | |||||
| return ToStringUtils.toSimpleString(this); | |||||
| } | |||||
| public static WxCpTagAddOrRemoveUsersResult fromJson(String json) { | |||||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpTagAddOrRemoveUsersResult.class); | |||||
| } | |||||
| @SerializedName("errcode") | |||||
| private Integer errCode; | |||||
| @SerializedName("errmsg") | |||||
| private String errMsg; | |||||
| @SerializedName("invalidlist") | |||||
| private String invalidUsers; | |||||
| @SerializedName("invalidparty") | |||||
| private String[] invalidParty; | |||||
| public Integer getErrCode() { | |||||
| return this.errCode; | |||||
| } | |||||
| public void setErrCode(Integer errCode) { | |||||
| this.errCode = errCode; | |||||
| } | |||||
| public String getErrMsg() { | |||||
| return this.errMsg; | |||||
| } | |||||
| public void setErrMsg(String errMsg) { | |||||
| this.errMsg = errMsg; | |||||
| } | |||||
| public String getInvalidUser() { | |||||
| return this.invalidUsers; | |||||
| } | |||||
| public void setInvalidUser(String invalidUser) { | |||||
| this.invalidUsers = invalidUser; | |||||
| } | |||||
| public String[] getInvalidParty() { | |||||
| return this.invalidParty; | |||||
| } | |||||
| public void setInvalidParty(String[] invalidParty) { | |||||
| this.invalidParty = invalidParty; | |||||
| } | |||||
| public List<String> getInvalidUserList() { | |||||
| return this.content2List(this.invalidUsers); | |||||
| } | |||||
| private List<String> content2List(String content) { | |||||
| if(StringUtils.isBlank(content)){ | |||||
| return Collections.emptyList(); | |||||
| } | |||||
| return Splitter.on("|").splitToList(content); | |||||
| } | |||||
| } | |||||
| @@ -1,11 +1,11 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.List; | import java.util.List; | ||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| /** | /** | ||||
| * 微信用户信息 | * 微信用户信息 | ||||
| * | * | ||||
| @@ -13,20 +13,54 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| */ | */ | ||||
| public class WxCpUser implements Serializable { | public class WxCpUser implements Serializable { | ||||
| public enum Gender { | |||||
| MALE("男", "1"), | |||||
| FEMAIL("女", "2"); | |||||
| private String genderName; | |||||
| private String code; | |||||
| Gender(String genderName, String code) { | |||||
| this.genderName = genderName; | |||||
| this.code = code; | |||||
| } | |||||
| public String getGenderName() { | |||||
| return this.genderName; | |||||
| } | |||||
| public String getCode() { | |||||
| return this.code; | |||||
| } | |||||
| public static Gender fromCode(String code) { | |||||
| if ("1".equals(code)) { | |||||
| return Gender.MALE; | |||||
| } | |||||
| if ("2".equals(code)) { | |||||
| return Gender.FEMAIL; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| } | |||||
| private static final long serialVersionUID = -5696099236344075582L; | private static final long serialVersionUID = -5696099236344075582L; | ||||
| private final List<Attr> extAttrs = new ArrayList<>(); | |||||
| private String userId; | private String userId; | ||||
| private String name; | private String name; | ||||
| private Integer[] departIds; | private Integer[] departIds; | ||||
| private String position; | private String position; | ||||
| private String mobile; | private String mobile; | ||||
| private String gender; | |||||
| private String tel; | |||||
| private Gender gender; | |||||
| private String email; | private String email; | ||||
| private String weiXinId; | |||||
| private String avatar; | private String avatar; | ||||
| private Integer status; | private Integer status; | ||||
| private Integer enable; | private Integer enable; | ||||
| private Integer isLeader; | |||||
| private final List<Attr> extAttrs = new ArrayList<>(); | |||||
| private Integer hideMobile; | |||||
| private String englishName; | |||||
| private String telephone; | |||||
| public static WxCpUser fromJson(String json) { | public static WxCpUser fromJson(String json) { | ||||
| return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); | return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); | ||||
| @@ -56,11 +90,11 @@ public class WxCpUser implements Serializable { | |||||
| this.departIds = departIds; | this.departIds = departIds; | ||||
| } | } | ||||
| public String getGender() { | |||||
| public Gender getGender() { | |||||
| return this.gender; | return this.gender; | ||||
| } | } | ||||
| public void setGender(String gender) { | |||||
| public void setGender(Gender gender) { | |||||
| this.gender = gender; | this.gender = gender; | ||||
| } | } | ||||
| @@ -80,12 +114,12 @@ public class WxCpUser implements Serializable { | |||||
| this.mobile = mobile; | this.mobile = mobile; | ||||
| } | } | ||||
| public String getTel() { | |||||
| return this.tel; | |||||
| public String getTelephone() { | |||||
| return this.telephone; | |||||
| } | } | ||||
| public void setTel(String tel) { | |||||
| this.tel = tel; | |||||
| public void setTelephone(String telephone) { | |||||
| this.telephone = telephone; | |||||
| } | } | ||||
| public String getEmail() { | public String getEmail() { | ||||
| @@ -96,14 +130,6 @@ public class WxCpUser implements Serializable { | |||||
| this.email = email; | this.email = email; | ||||
| } | } | ||||
| public String getWeiXinId() { | |||||
| return this.weiXinId; | |||||
| } | |||||
| public void setWeiXinId(String weiXinId) { | |||||
| this.weiXinId = weiXinId; | |||||
| } | |||||
| public String getAvatar() { | public String getAvatar() { | ||||
| return this.avatar; | return this.avatar; | ||||
| } | } | ||||
| @@ -136,12 +162,35 @@ public class WxCpUser implements Serializable { | |||||
| return this.extAttrs; | return this.extAttrs; | ||||
| } | } | ||||
| public Integer getIsLeader() { | |||||
| return isLeader; | |||||
| } | |||||
| public void setIsLeader(Integer isLeader) { | |||||
| this.isLeader = isLeader; | |||||
| } | |||||
| public Integer getHideMobile() { | |||||
| return hideMobile; | |||||
| } | |||||
| public void setHideMobile(Integer hideMobile) { | |||||
| this.hideMobile = hideMobile; | |||||
| } | |||||
| public String getEnglishName() { | |||||
| return englishName; | |||||
| } | |||||
| public void setEnglishName(String englishName) { | |||||
| this.englishName = englishName; | |||||
| } | |||||
| public String toJson() { | public String toJson() { | ||||
| return WxCpGsonBuilder.INSTANCE.create().toJson(this); | return WxCpGsonBuilder.INSTANCE.create().toJson(this); | ||||
| } | } | ||||
| public static class Attr { | public static class Attr { | ||||
| private String name; | private String name; | ||||
| private String value; | private String value; | ||||
| @@ -1,20 +1,18 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.Serializable; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import org.apache.commons.io.IOUtils; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| import me.chanjar.weixin.cp.api.WxCpConfigStorage; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | ||||
| import org.apache.commons.io.IOUtils; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.Serializable; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| @@ -183,18 +181,18 @@ public class WxCpXmlMessage implements Serializable { | |||||
| * @param msgSignature | * @param msgSignature | ||||
| */ | */ | ||||
| public static WxCpXmlMessage fromEncryptedXml( | public static WxCpXmlMessage fromEncryptedXml( | ||||
| String encryptedXml, | |||||
| WxCpConfigStorage wxCpConfigStorage, | |||||
| String timestamp, String nonce, String msgSignature) { | |||||
| String encryptedXml, | |||||
| WxCpConfigStorage wxCpConfigStorage, | |||||
| String timestamp, String nonce, String msgSignature) { | |||||
| WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); | WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); | ||||
| String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); | String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); | ||||
| return fromXml(plainText); | return fromXml(plainText); | ||||
| } | } | ||||
| public static WxCpXmlMessage fromEncryptedXml( | public static WxCpXmlMessage fromEncryptedXml( | ||||
| InputStream is, | |||||
| WxCpConfigStorage wxCpConfigStorage, | |||||
| String timestamp, String nonce, String msgSignature) { | |||||
| InputStream is, | |||||
| WxCpConfigStorage wxCpConfigStorage, | |||||
| String timestamp, String nonce, String msgSignature) { | |||||
| try { | try { | ||||
| return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); | return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); | ||||
| } catch (IOException e) { | } catch (IOException e) { | ||||
| @@ -493,40 +491,40 @@ public class WxCpXmlMessage implements Serializable { | |||||
| @Override | @Override | ||||
| public String toString() { | public String toString() { | ||||
| return "WxCpXmlMessage{" + | return "WxCpXmlMessage{" + | ||||
| "agentId=" + this.agentId + | |||||
| ", toUserName='" + this.toUserName + '\'' + | |||||
| ", fromUserName='" + this.fromUserName + '\'' + | |||||
| ", createTime=" + this.createTime + | |||||
| ", msgType='" + this.msgType + '\'' + | |||||
| ", content='" + this.content + '\'' + | |||||
| ", msgId=" + this.msgId + | |||||
| ", picUrl='" + this.picUrl + '\'' + | |||||
| ", mediaId='" + this.mediaId + '\'' + | |||||
| ", format='" + this.format + '\'' + | |||||
| ", thumbMediaId='" + this.thumbMediaId + '\'' + | |||||
| ", locationX=" + this.locationX + | |||||
| ", locationY=" + this.locationY + | |||||
| ", scale=" + this.scale + | |||||
| ", label='" + this.label + '\'' + | |||||
| ", title='" + this.title + '\'' + | |||||
| ", description='" + this.description + '\'' + | |||||
| ", url='" + this.url + '\'' + | |||||
| ", event='" + this.event + '\'' + | |||||
| ", eventKey='" + this.eventKey + '\'' + | |||||
| ", ticket='" + this.ticket + '\'' + | |||||
| ", latitude=" + this.latitude + | |||||
| ", longitude=" + this.longitude + | |||||
| ", precision=" + this.precision + | |||||
| ", recognition='" + this.recognition + '\'' + | |||||
| ", status='" + this.status + '\'' + | |||||
| ", totalCount=" + this.totalCount + | |||||
| ", filterCount=" + this.filterCount + | |||||
| ", sentCount=" + this.sentCount + | |||||
| ", errorCount=" + this.errorCount + | |||||
| ", scanCodeInfo=" + this.scanCodeInfo + | |||||
| ", sendPicsInfo=" + this.sendPicsInfo + | |||||
| ", sendLocationInfo=" + this.sendLocationInfo + | |||||
| '}'; | |||||
| "agentId=" + this.agentId + | |||||
| ", toUserName='" + this.toUserName + '\'' + | |||||
| ", fromUserName='" + this.fromUserName + '\'' + | |||||
| ", createTime=" + this.createTime + | |||||
| ", msgType='" + this.msgType + '\'' + | |||||
| ", content='" + this.content + '\'' + | |||||
| ", msgId=" + this.msgId + | |||||
| ", picUrl='" + this.picUrl + '\'' + | |||||
| ", mediaId='" + this.mediaId + '\'' + | |||||
| ", format='" + this.format + '\'' + | |||||
| ", thumbMediaId='" + this.thumbMediaId + '\'' + | |||||
| ", locationX=" + this.locationX + | |||||
| ", locationY=" + this.locationY + | |||||
| ", scale=" + this.scale + | |||||
| ", label='" + this.label + '\'' + | |||||
| ", title='" + this.title + '\'' + | |||||
| ", description='" + this.description + '\'' + | |||||
| ", url='" + this.url + '\'' + | |||||
| ", event='" + this.event + '\'' + | |||||
| ", eventKey='" + this.eventKey + '\'' + | |||||
| ", ticket='" + this.ticket + '\'' + | |||||
| ", latitude=" + this.latitude + | |||||
| ", longitude=" + this.longitude + | |||||
| ", precision=" + this.precision + | |||||
| ", recognition='" + this.recognition + '\'' + | |||||
| ", status='" + this.status + '\'' + | |||||
| ", totalCount=" + this.totalCount + | |||||
| ", filterCount=" + this.filterCount + | |||||
| ", sentCount=" + this.sentCount + | |||||
| ", errorCount=" + this.errorCount + | |||||
| ", scanCodeInfo=" + this.scanCodeInfo + | |||||
| ", sendPicsInfo=" + this.sendPicsInfo + | |||||
| ", sendLocationInfo=" + this.sendLocationInfo + | |||||
| '}'; | |||||
| } | } | ||||
| @XStreamAlias("ScanCodeInfo") | @XStreamAlias("ScanCodeInfo") | ||||
| @@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | ||||
| @@ -3,7 +3,7 @@ package me.chanjar.weixin.cp.bean; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| import me.chanjar.weixin.cp.api.WxCpConfigStorage; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.*; | import me.chanjar.weixin.cp.bean.outxmlbuilder.*; | ||||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | ||||
| @@ -1,14 +1,13 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| @XStreamAlias("xml") | @XStreamAlias("xml") | ||||
| public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | ||||
| private static final long serialVersionUID = -5796178637883178826L; | private static final long serialVersionUID = -5796178637883178826L; | ||||
| @@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| @@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| @@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | ||||
| @@ -4,6 +4,7 @@ package me.chanjar.weixin.cp.bean.article; | |||||
| * <pre> | * <pre> | ||||
| * Created by BinaryWang on 2017/3/27. | * Created by BinaryWang on 2017/3/27. | ||||
| * </pre> | * </pre> | ||||
| * | |||||
| * @author Binary Wang | * @author Binary Wang | ||||
| */ | */ | ||||
| public class MpnewsArticle { | public class MpnewsArticle { | ||||
| @@ -44,7 +44,7 @@ public class BaseBuilder<T> { | |||||
| m.setToParty(this.toParty); | m.setToParty(this.toParty); | ||||
| m.setToTag(this.toTag); | m.setToTag(this.toTag); | ||||
| m.setSafe( | m.setSafe( | ||||
| (this.safe == null || "".equals(this.safe)) ? WxConsts.CUSTOM_MSG_SAFE_NO : this.safe); | |||||
| (this.safe == null || "".equals(this.safe)) ? WxConsts.CUSTOM_MSG_SAFE_NO : this.safe); | |||||
| return m; | return m; | ||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.config; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||||
| import java.io.File; | import java.io.File; | ||||
| @@ -1,8 +1,8 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.config; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.util.ToStringUtils; | import me.chanjar.weixin.common.util.ToStringUtils; | ||||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||||
| import java.io.File; | import java.io.File; | ||||
| @@ -1,9 +1,10 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.config; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||||
| import redis.clients.jedis.Jedis; | import redis.clients.jedis.Jedis; | ||||
| import redis.clients.jedis.JedisPool; | import redis.clients.jedis.JedisPool; | ||||
| import redis.clients.jedis.JedisPoolConfig; | |||||
| import java.io.File; | import java.io.File; | ||||
| @@ -38,6 +39,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||||
| this.jedisPool = new JedisPool(host, port); | this.jedisPool = new JedisPool(host, port); | ||||
| } | } | ||||
| public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port) { | |||||
| this.jedisPool = new JedisPool(poolConfig, host, port); | |||||
| } | |||||
| public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, final String password) { | |||||
| this.jedisPool = new JedisPool(poolConfig, host, port, timeout, password); | |||||
| } | |||||
| /** | /** | ||||
| * This method will be destroy jedis pool | * This method will be destroy jedis pool | ||||
| */ | */ | ||||
| @@ -1,7 +1,8 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.message; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
| import me.chanjar.weixin.common.session.WxSessionManager; | import me.chanjar.weixin.common.session.WxSessionManager; | ||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
| @@ -1,7 +1,8 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.message; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
| import me.chanjar.weixin.common.session.WxSessionManager; | import me.chanjar.weixin.common.session.WxSessionManager; | ||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| @@ -1,4 +1,4 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.message; | |||||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
| @@ -1,4 +1,4 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.message; | |||||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | ||||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | ||||
| @@ -8,13 +8,16 @@ import me.chanjar.weixin.common.session.InternalSessionManager; | |||||
| import me.chanjar.weixin.common.session.StandardSessionManager; | import me.chanjar.weixin.common.session.StandardSessionManager; | ||||
| import me.chanjar.weixin.common.session.WxSessionManager; | import me.chanjar.weixin.common.session.WxSessionManager; | ||||
| import me.chanjar.weixin.common.util.LogExceptionHandler; | 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.WxCpXmlMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||
| import java.util.HashMap; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | |||||
| import java.util.concurrent.ExecutionException; | import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.ExecutorService; | import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Executors; | import java.util.concurrent.Executors; | ||||
| @@ -135,8 +138,9 @@ public class WxCpMessageRouter { | |||||
| * 处理微信消息 | * 处理微信消息 | ||||
| * | * | ||||
| * @param wxMessage | * @param wxMessage | ||||
| * @param context | |||||
| */ | */ | ||||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | |||||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage, final Map<String, Object> context) { | |||||
| if (isDuplicateMessage(wxMessage)) { | if (isDuplicateMessage(wxMessage)) { | ||||
| // 如果是重复消息,那么就不做处理 | // 如果是重复消息,那么就不做处理 | ||||
| return null; | return null; | ||||
| @@ -166,12 +170,12 @@ public class WxCpMessageRouter { | |||||
| this.executorService.submit(new Runnable() { | this.executorService.submit(new Runnable() { | ||||
| @Override | @Override | ||||
| public void run() { | public void run() { | ||||
| rule.service(wxMessage, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler); | |||||
| rule.service(wxMessage, context, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler); | |||||
| } | } | ||||
| }) | }) | ||||
| ); | ); | ||||
| } else { | } else { | ||||
| res = rule.service(wxMessage, this.wxCpService, this.sessionManager, this.exceptionHandler); | |||||
| res = rule.service(wxMessage, context, this.wxCpService, this.sessionManager, this.exceptionHandler); | |||||
| // 在同步操作结束,session访问结束 | // 在同步操作结束,session访问结束 | ||||
| this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); | this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); | ||||
| sessionEndAccess(wxMessage); | sessionEndAccess(wxMessage); | ||||
| @@ -200,6 +204,16 @@ public class WxCpMessageRouter { | |||||
| return res; | return res; | ||||
| } | } | ||||
| /** | |||||
| * 处理微信消息 | |||||
| * | |||||
| * @param wxMessage | |||||
| */ | |||||
| public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | |||||
| return this.route(wxMessage, new HashMap<String, Object>()); | |||||
| } | |||||
| protected boolean isDuplicateMessage(WxCpXmlMessage wxMessage) { | protected boolean isDuplicateMessage(WxCpXmlMessage wxMessage) { | ||||
| String messageId = ""; | String messageId = ""; | ||||
| @@ -1,8 +1,9 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| package me.chanjar.weixin.cp.message; | |||||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | ||||
| import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
| import me.chanjar.weixin.common.session.WxSessionManager; | import me.chanjar.weixin.common.session.WxSessionManager; | ||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
| @@ -229,13 +230,16 @@ public class WxCpMessageRouterRule { | |||||
| * @return true 代表继续执行别的router,false 代表停止执行别的router | * @return true 代表继续执行别的router,false 代表停止执行别的router | ||||
| */ | */ | ||||
| protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage, | protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage, | ||||
| Map<String, Object> context, | |||||
| WxCpService wxCpService, | WxCpService wxCpService, | ||||
| WxSessionManager sessionManager, | WxSessionManager sessionManager, | ||||
| WxErrorExceptionHandler exceptionHandler) { | WxErrorExceptionHandler exceptionHandler) { | ||||
| try { | |||||
| if (context == null) { | |||||
| context = new HashMap<>(); | |||||
| } | |||||
| Map<String, Object> context = new HashMap<>(); | |||||
| try { | |||||
| // 如果拦截器不通过 | // 如果拦截器不通过 | ||||
| for (WxCpMessageInterceptor interceptor : this.interceptors) { | for (WxCpMessageInterceptor interceptor : this.interceptors) { | ||||
| if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) { | if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) { | ||||
| @@ -1,47 +1,51 @@ | |||||
| /** | |||||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||||
| * | |||||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||||
| * <p> | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| // ------------------------------------------------------------------------ | |||||
| /** | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| package me.chanjar.weixin.cp.util.crypto; | |||||
| import me.chanjar.weixin.common.util.crypto.WxCryptUtil; | |||||
| import me.chanjar.weixin.cp.api.WxCpConfigStorage; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| public class WxCpCryptUtil extends WxCryptUtil { | |||||
| /** | |||||
| * 构造函数 | |||||
| * | |||||
| * @param wxCpConfigStorage | |||||
| */ | |||||
| public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) { | |||||
| /* | |||||
| * @param token 公众平台上,开发者设置的token | |||||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
| * @param appidOrCorpid 公众平台appid | |||||
| */ | |||||
| String encodingAesKey = wxCpConfigStorage.getAesKey(); | |||||
| String token = wxCpConfigStorage.getToken(); | |||||
| String corpId = wxCpConfigStorage.getCorpId(); | |||||
| this.token = token; | |||||
| this.appidOrCorpid = corpId; | |||||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||||
| * | |||||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||||
| * <p> | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| * <p> | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| // ------------------------------------------------------------------------ | |||||
| /** | |||||
| * 针对org.apache.commons.codec.binary.Base64, | |||||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||||
| */ | |||||
| package me.chanjar.weixin.cp.util.crypto; | |||||
| import me.chanjar.weixin.common.util.crypto.WxCryptUtil; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| public class WxCpCryptUtil extends WxCryptUtil { | |||||
| /** | |||||
| * 构造函数 | |||||
| * | |||||
| * @param wxCpConfigStorage | |||||
| */ | |||||
| public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) { | |||||
| /* | |||||
| * @param token 公众平台上,开发者设置的token | |||||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
| * @param appidOrCorpid 公众平台appid | |||||
| */ | |||||
| String encodingAesKey = wxCpConfigStorage.getAesKey(); | |||||
| String token = wxCpConfigStorage.getToken(); | |||||
| String corpId = wxCpConfigStorage.getCorpId(); | |||||
| this.token = token; | |||||
| this.appidOrCorpid = corpId; | |||||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||||
| } | |||||
| } | |||||
| @@ -39,7 +39,7 @@ public class WxCpDepartGsonAdapter implements JsonSerializer<WxCpDepart>, JsonDe | |||||
| @Override | @Override | ||||
| public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | ||||
| throws JsonParseException { | |||||
| throws JsonParseException { | |||||
| WxCpDepart depart = new WxCpDepart(); | WxCpDepart depart = new WxCpDepart(); | ||||
| JsonObject departJson = json.getAsJsonObject(); | JsonObject departJson = json.getAsJsonObject(); | ||||
| if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) { | if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) { | ||||
| @@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.util.json; | |||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
| import me.chanjar.weixin.common.util.json.WxErrorAdapter; | import me.chanjar.weixin.common.util.json.WxErrorAdapter; | ||||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
| @@ -19,6 +20,7 @@ public class WxCpGsonBuilder { | |||||
| INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | ||||
| INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); | ||||
| INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); | INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); | ||||
| INSTANCE.registerTypeAdapter(WxMenu.class, new WxCpMenuGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter()); | ||||
| } | } | ||||
| @@ -0,0 +1,24 @@ | |||||
| package me.chanjar.weixin.cp.util.json; | |||||
| import com.google.gson.JsonDeserializationContext; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonParseException; | |||||
| import me.chanjar.weixin.common.bean.menu.WxMenu; | |||||
| import me.chanjar.weixin.common.util.json.WxMenuGsonAdapter; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * <pre> | |||||
| * 企业号菜单json转换适配器 | |||||
| * Created by Binary Wang on 2017-6-25. | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| * </pre> | |||||
| */ | |||||
| public class WxCpMenuGsonAdapter extends WxMenuGsonAdapter { | |||||
| @Override | |||||
| public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
| return this.buildMenuFromJson(json.getAsJsonObject().get("button").getAsJsonArray()); | |||||
| } | |||||
| } | |||||