diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java new file mode 100644 index 00000000..741d27de --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java @@ -0,0 +1,169 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; +import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; + +/** + * 微信第三方应用API的Service + * @author zhenjun cai + */ +public interface WxCpTpService { + String JSCODE_TO_SESSION_URL = "https://qyapi.weixin.qq.com/cgi-bin/service/miniprogram/jscode2session"; + String GET_CORP_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token"; + String GET_PERMANENT_CODE = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code"; + /** + *
+ * 验证推送过来的消息的正确性 + * 详情请见: https://work.weixin.qq.com/api/doc#90000/90139/90968/消息体签名校验 + *+ * + * @param msgSignature 消息签名 + * @param timestamp 时间戳 + * @param nonce 随机数 + * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 + */ + boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); + + /** + * 获取suite_access_token, 不强制刷新suite_access_token + * + * @see #getSuiteAccessToken(boolean) + */ + String getSuiteAccessToken() throws WxErrorException; + + /** + *
+ * 获取suite_access_token,本方法线程安全 + * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 + * 另:本service的所有方法都会在suite_access_token过期是调用此方法 + * 程序员在非必要情况下尽量不要主动调用此方法 + * 详情请见: https://work.weixin.qq.com/api/doc#90001/90143/90600 + *+ * + * @param forceRefresh 强制刷新 + */ + String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException; + + /** + * 获得suite_ticket,不强制刷新suite_ticket + * + * @see #getSuiteTicket(boolean) + */ + String getSuiteTicket() throws WxErrorException; + + /** + *
+ * 获得suite_ticket + * 由于suite_ticket是微信服务器定时推送(每10分钟),不能主动获取,如果碰到过期只能抛异常 + * + * 详情请见:https://work.weixin.qq.com/api/doc#90001/90143/90628 + *+ * + * @param forceRefresh 强制刷新 + */ + String getSuiteTicket(boolean forceRefresh) throws WxErrorException; + + /** + * 小程序登录凭证校验 + * + * @param jsCode 登录时获取的 code + */ + WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; + + /** + * 获取企业凭证 + * @param authCorpid 授权方corpid + * @param permanentCode 永久授权码,通过get_permanent_code获取 + */ + WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException; + + /** + * 获取企业永久授权码 + * @param authCode + * @return + */ + WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 + * + * @param url 接口地址 + * @param queryParam 请求参数 + */ + String get(String url, String queryParam) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 + * + * @param url 接口地址 + * @param postData 请求body字符串 + */ + String post(String url, String postData) throws WxErrorException; + + /** + *
+ * Service没有实现某个API的时候,可以用这个, + * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 + * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 + *+ * + * @param executor 执行器 + * @param uri 请求地址 + * @param data 参数 + * @param
+ * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 + * 默认:1000ms + *+ * + * @param retrySleepMillis 重试休息时间 + */ + void setRetrySleepMillis(int retrySleepMillis); + + /** + *
+ * 设置当微信系统响应系统繁忙时,最大重试次数 + * 默认:5次 + *+ * + * @param maxRetryTimes 最大重试次数 + */ + void setMaxRetryTimes(int maxRetryTimes); + + /** + * 初始化http请求对象 + */ + void initHttp(); + + /** + * 获取WxMpConfigStorage 对象 + * + * @return WxMpConfigStorage + */ + WxCpTpConfigStorage getWxCpTpConfigStorage(); + + /** + * 注入 {@link WxCpTpConfigStorage} 的实现 + * + * @param wxConfigProvider 配置对象 + */ + void setWxCpTpConfigStorage(WxCpTpConfigStorage wxConfigProvider); + + /** + * http请求对象 + */ + RequestHttp, ?> getRequestHttp(); + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java new file mode 100644 index 00000000..837c355e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java @@ -0,0 +1,244 @@ +package me.chanjar.weixin.cp.api.impl; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Joiner; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.DataUtils; +import me.chanjar.weixin.common.util.crypto.SHA1; +import me.chanjar.weixin.common.util.http.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.WxCpService; +import me.chanjar.weixin.cp.api.WxCpTpService; +import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; +import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; + +/** + * @author zhenjun cai + */ +public abstract class BaseWxCpTpServiceImpl
+ * 默认接口实现类,使用apache httpclient实现,配合第三方应用service使用 + * Created by zhenjun cai. + *+ * + * @author Binary Wang + */ +public class WxCpServiceOnTpImpl extends WxCpServiceApacheHttpClientImpl { + //第三方应用service + WxCpTpService wxCpTpService; + + public void setWxCpTpService(WxCpTpService wxCpTpService) { + this.wxCpTpService = wxCpTpService; + } + + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) { + return this.configStorage.getAccessToken(); + } + //access token通过第三方应用service获取 + //corpSecret对应企业永久授权码 + WxAccessToken accessToken = wxCpTpService.getCorpToken(this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + + this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + return this.configStorage.getAccessToken(); + } + + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java new file mode 100644 index 00000000..0b8a134d --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java @@ -0,0 +1,114 @@ +package me.chanjar.weixin.cp.api.impl; + + +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.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.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.WxCpTpConfigStorage; + +public class WxCpTpServiceApacheHttpClientImpl extends BaseWxCpTpServiceImpl
+ * 默认接口实现类,使用apache httpclient实现 + * Created by zhenjun cai. + *+ * + * @author Binary Wang + */ +public class WxCpTpServiceImpl extends WxCpTpServiceApacheHttpClientImpl { +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java new file mode 100644 index 00000000..9ca59b84 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 微信部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpCorp implements Serializable { + + private static final long serialVersionUID = -5028321625140879571L; + @SerializedName("corpid") + private String corpId; + @SerializedName("corp_name") + private String corpName; + @SerializedName("corp_full_name") + private String corpFullName; + @SerializedName("corp_type") + private String corpType; + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + @SerializedName("corp_user_max") + private String corpUserMax; + @SerializedName("permanent_code") + private String permanentCode; + + public static WxCpTpCorp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java new file mode 100644 index 00000000..53e068db --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; +import java.util.Map; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.XmlUtils; +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; +import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import me.chanjar.weixin.cp.util.xml.XStreamTransformer; + +/** + * 回调推送的message + * https://work.weixin.qq.com/api/doc#90001/90143/90612 + * + * @author zhenjun cai + */ +@XStreamAlias("xml") +@Slf4j +@Data +public class WxCpTpXmlMessage implements Serializable { + + private static final long serialVersionUID = 6031833682211475786L; + /** + * 使用dom4j解析的存放所有xml属性和值的map. + */ + private Map
+ * 针对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
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * 针对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 org.apache.commons.codec.binary.Base64;
+
+import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+
+public class WxCpTpCryptUtil extends WxCryptUtil {
+
+ /**
+ * 构造函数
+ *
+ * @param wxCpConfigStorage
+ */
+ public WxCpTpCryptUtil(WxCpTpConfigStorage wxCpTpConfigStorage) {
+ /*
+ * @param token 公众平台上,开发者设置的token
+ * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
+ * @param appidOrCorpid 公众平台corpId
+ */
+ String encodingAesKey = wxCpTpConfigStorage.getAesKey();
+ String token = wxCpTpConfigStorage.getToken();
+ String corpId = wxCpTpConfigStorage.getCorpId();
+
+ this.token = token;
+ this.appidOrCorpid = corpId;
+ this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
+ }
+
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
index 5ef1503d..3c6174c9 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
@@ -1,117 +1,135 @@
-package me.chanjar.weixin.cp.util.xml;
-
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.thoughtworks.xstream.XStream;
-import me.chanjar.weixin.common.util.xml.XStreamInitializer;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage;
-
-public class XStreamTransformer {
-
- protected static final Map