🎨 #1867【企业微信】优化完善第三方应用的接入代码

This commit is contained in:
xworks
2020-11-12 17:30:27 +08:00
committed by GitHub
parent edc04cb0ae
commit a2074a4d39
16 changed files with 1385 additions and 130 deletions

View File

@@ -0,0 +1,30 @@
package me.chanjar.weixin.common.util.xml;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.thoughtworks.xstream.converters.basic.StringConverter;
/**
* String 数组转换
* @author chily.lin
*/
public class StringArrayConverter extends StringConverter {
@Override
public boolean canConvert(Class type) {
return type == String[].class;
}
@Override
public String toString(Object obj) {
return "<![CDATA[" + Joiner.on(",").join((String[]) obj) + "]]>";
}
@Override
public Object fromString(String str) {
final Iterable<String> iterable = Splitter.on(",").split(str);
String[] results = Iterables.toArray(iterable, String.class);
return results;
}
}

View File

@@ -1,13 +1,17 @@
package me.chanjar.weixin.cp.bean.message;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.converters.basic.IntConverter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.util.XmlUtils;
import me.chanjar.weixin.common.util.xml.IntegerArrayConverter;
import me.chanjar.weixin.common.util.xml.StringArrayConverter;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
@@ -52,6 +56,359 @@ public class WxCpTpXmlMessage implements Serializable {
@XStreamConverter(value = XStreamCDataConverter.class)
protected String authCorpId;
@XStreamAlias("ChangeType")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String changeType;
@XStreamAlias("UserID")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String userID;
@XStreamAlias("Department")
@XStreamConverter(value = IntegerArrayConverter.class)
protected Integer[] department;
@XStreamAlias("MainDepartment")
@XStreamConverter(value = IntConverter.class)
protected Integer mainDepartment;
@XStreamAlias("IsLeaderInDept")
@XStreamConverter(value = IntegerArrayConverter.class)
protected Integer[] isLeaderInDept;
@XStreamAlias("Mobile")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String mobile;
@XStreamAlias("Position")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String position;
@XStreamAlias("Gender")
@XStreamConverter(value = IntConverter.class)
protected Integer gender;
@XStreamAlias("Email")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String email;
@XStreamAlias("Status")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String status;
@XStreamAlias("Avatar")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String avatar;
@XStreamAlias("Alias")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String alias;
@XStreamAlias("Telephone")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String telephone;
@XStreamAlias("Id")
@XStreamConverter(value = IntConverter.class)
protected Integer id;
@XStreamAlias("Name")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String name;
@XStreamAlias("ParentId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String parentId;
@XStreamAlias("Order")
@XStreamConverter(value = XStreamCDataConverter.class)
protected Integer order;
@XStreamAlias("TagId")
@XStreamConverter(value = IntConverter.class)
protected Integer tagId;
@XStreamAlias("AddUserItems")
@XStreamConverter(value = StringArrayConverter.class)
protected String[] addUserItems;
@XStreamAlias("DelUserItems")
@XStreamConverter(value = StringArrayConverter.class)
protected String[] delUserItems;
@XStreamAlias("AddPartyItems")
@XStreamConverter(value = IntegerArrayConverter.class)
protected Integer[] addPartyItems;
@XStreamAlias("DelPartyItems")
@XStreamConverter(value = IntegerArrayConverter.class)
protected Integer[] delPartyItems;
//ref: https://work.weixin.qq.com/api/doc/90001/90143/90585
@XStreamAlias("ServiceCorpId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String serviceCorpId;
@XStreamAlias("RegisterCode")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String registerCode;
@XStreamAlias("ContactSync")
protected ContactSync contactSync;
@XStreamAlias("AuthUserInfo")
protected AuthUserInfo authUserInfo;
@XStreamAlias("TemplateId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String templateId;
@XStreamAlias("CreateTime")
protected Long createTime;
@XStreamAlias("ToUserName")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String toUserName;
@XStreamAlias("FromUserName")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String fromUserName;
@XStreamAlias("MsgType")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String msgType;
@XStreamAlias("Event")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String event;
@XStreamAlias("BatchJob")
protected BatchJob batchJob;
@XStreamAlias("ExternalUserID")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String externalUserID;
@XStreamAlias("WelcomeCode")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String welcomeCode;
@XStreamAlias("FromUser")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String fromUser;
@XStreamAlias("Content")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String content;
@XStreamAlias("MsgId")
protected String msgId;
@XStreamAlias("AgentID")
protected Integer agentID;
@XStreamAlias("PicUrl")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String picUrl;
@XStreamAlias("MediaId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String mediaId;
@XStreamAlias("Format")
@XStreamConverter(value = XStreamCDataConverter.class)
private String format;
@XStreamAlias("ThumbMediaId")
@XStreamConverter(value = XStreamCDataConverter.class)
private String thumbMediaId;
@XStreamAlias("Location_X")
private Double locationX;
@XStreamAlias("Location_Y")
private Double locationY;
@XStreamAlias("Scale")
private Double scale;
@XStreamAlias("Label")
@XStreamConverter(value = XStreamCDataConverter.class)
private String label;
@XStreamAlias("Title")
@XStreamConverter(value = XStreamCDataConverter.class)
private String title;
@XStreamAlias("Description")
@XStreamConverter(value = XStreamCDataConverter.class)
private String description;
@XStreamAlias("Url")
@XStreamConverter(value = XStreamCDataConverter.class)
private String url;
@XStreamAlias("EventKey")
@XStreamConverter(value = XStreamCDataConverter.class)
private String eventKey;
@XStreamAlias("Latitude")
private Double latitude;
@XStreamAlias("Longitude")
private Double longitude;
@XStreamAlias("Precision")
private Double precision;
@XStreamAlias("AppType")
@XStreamConverter(value = XStreamCDataConverter.class)
private String appType;
@XStreamAlias("ScanCodeInfo")
private WxCpXmlMessage.ScanCodeInfo scanCodeInfo = new WxCpXmlMessage.ScanCodeInfo();
@XStreamAlias("SendPicsInfo")
private WxCpXmlMessage.SendPicsInfo sendPicsInfo = new WxCpXmlMessage.SendPicsInfo();
@XStreamAlias("SendLocationInfo")
private WxCpXmlMessage.SendLocationInfo sendLocationInfo = new WxCpXmlMessage.SendLocationInfo();
@XStreamAlias("ApprovalInfo")
private ApprovalInfo approvalInfo = new ApprovalInfo();
@XStreamAlias("TaskId")
@XStreamConverter(value = XStreamCDataConverter.class)
private String taskId;
@Data
@XStreamAlias("ContactSync")
public static class ContactSync {
@XStreamAlias("AccessToken")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String accessToken;
@XStreamAlias("ExpiresIn")
protected Integer expiresIn;
}
@Data
@XStreamAlias("AuthUserInfo")
public static class AuthUserInfo {
@XStreamAlias("UserId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String userId;
}
@Data
@XStreamAlias("BatchJob")
public static class BatchJob {
@XStreamAlias("JobId")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String JobId;
@XStreamAlias("JobType")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String jobType;
@XStreamAlias("ErrCode")
@XStreamConverter(value = IntConverter.class)
protected Integer errCode;
@XStreamAlias("ErrMsg")
@XStreamConverter(value = XStreamCDataConverter.class)
protected String errMsg;
}
@Data
@XStreamAlias("ApprovalInfo")
public static class ApprovalInfo {
@XStreamAlias("ThirdNo")
protected Long thirdNo;
@XStreamAlias("OpenSpName")
protected String openSpName;
@XStreamAlias("OpenTemplateId")
protected Integer openTemplateId;
@XStreamAlias("OpenSpStatus")
protected Integer openSpStatus;
@XStreamAlias("ApplyTime")
protected Long applyTime;
@XStreamAlias("ApplyUserName")
protected String applyUserName;
@XStreamAlias("ApplyUserId")
protected Integer applyUserId;
@XStreamAlias("ApplyUserParty")
protected String applyUserParty;
@XStreamAlias("ApplyUserImage")
protected String applyUserImage;
@XStreamAlias("ApprovalNodes")
protected List<ApprovalNode> approvalNodes;
@XStreamAlias("NotifyNodes")
protected List<NotifyNode> notifyNodes;
@XStreamAlias("approverstep")
protected Integer approverstep;
//自建/第三方应用调用审批流程引擎,状态通知
//ref: https://work.weixin.qq.com/api/doc/90001/90143/90376#审批状态通知事件
//1.自建/第三方应用调用审批流程引擎发起申请之后,审批状态发生变化时
//2.自建/第三方应用调用审批流程引擎发起申请之后,在“审批中”状态,有任意审批人进行审批操作时
@Data
@XStreamAlias("ApprovalNode")
public static class ApprovalNode {
@XStreamAlias("NodeStatus")
protected Integer nodeStatus;
@XStreamAlias("NodeAttr")
protected Integer nodeAttr;
@XStreamAlias("NodeType")
protected Integer nodeType;
@XStreamAlias("Items")
protected List<Item> items;
@Data
@XStreamAlias("Item")
public static class Item {
@XStreamAlias("ItemName")
protected String itemName;
@XStreamAlias("ItemUserId")
protected Integer itemUserId;
@XStreamAlias("ItemImage")
protected String itemImage;
@XStreamAlias("ItemStatus")
protected Integer itemStatus;
@XStreamAlias("ItemSpeech")
protected String itemSpeech;
@XStreamAlias("ItemOpTime")
protected Long itemOpTime;
}
}
@Data
@XStreamAlias("NotifyNode")
public static class NotifyNode {
@XStreamAlias("ItemName")
protected String itemName;
@XStreamAlias("ItemUserId")
protected Integer itemUserId;
@XStreamAlias("ItemImage")
protected String itemImage;
}
}
public static WxCpTpXmlMessage fromXml(String xml) {
//修改微信变态的消息内容格式,方便解析
//xml = xml.replace("</PicList><PicList>", "");

View File

@@ -26,67 +26,76 @@ public interface WxCpTpConfigStorage {
*/
String getApiUrl(String path);
/**
* 第三方应用的suite access token相关
*/
String getSuiteAccessToken();
boolean isSuiteAccessTokenExpired();
/**
* 强制将suite access token过期掉.
*/
//强制将suite access token过期掉.
void expireSuiteAccessToken();
void updateSuiteAccessToken(WxAccessToken suiteAccessToken);
void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds);
void updateSuiteAccessToken(String suiteAccessToken, int expiresIn);
/**
* 第三方应用的suite ticket相关
*/
String getSuiteTicket();
boolean isSuiteTicketExpired();
/**
* 强制将suite ticket过期掉.
*/
//强制将suite ticket过期掉.
void expireSuiteTicket();
/**
* 应该是线程安全的.
*/
//应该是线程安全的
void updateSuiteTicket(String suiteTicket, int expiresInSeconds);
String getCorpId();
String getCorpSecret();
/**
* 第三方应用的其他配置,来自于企微配置
*/
String getSuiteId();
String getSuiteSecret();
// 第三方应用的token用来检查应用的签名
String getToken();
//第三方应用的EncodingAESKey用来检查签名
String getAesKey();
long getExpiresTime();
String getHttpProxyHost();
int getHttpProxyPort();
String getHttpProxyUsername();
String getHttpProxyPassword();
File getTmpDirFile();
/**
* 企微服务商企业ID & 企业secret
*/
String getCorpId();
String getCorpSecret();
/**
* http client builder.
*
* @return ApacheHttpClientBuilder
* 授权企业的access token相关
*/
String getAccessToken(String authCorpId);
boolean isAccessTokenExpired(String authCorpId);
void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds);
/**
* 授权企业的js api ticket相关
*/
String getAuthCorpJsApiTicket(String authCorpId);
boolean isAuthCorpJsApiTicketExpired(String authCorpId);
void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);
/**
* 授权企业的第三方应用js api ticket相关
*/
String getAuthSuiteJsApiTicket(String authCorpId);
boolean isAuthSuiteJsApiTicketExpired(String authCorpId);
void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);;
/**
* 网络代理相关
*/
String getHttpProxyHost();
int getHttpProxyPort();
String getHttpProxyUsername();
String getHttpProxyPassword();
ApacheHttpClientBuilder getApacheHttpClientBuilder();
/**
* 是否自动刷新token
* @return .
*/
boolean autoRefreshToken();
// 毫无相关性的代码
@Deprecated
File getTmpDirFile();
}

View File

@@ -7,6 +7,8 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 基于内存的微信配置provider在实际生产环境中应该将这些配置持久化.
@@ -24,25 +26,34 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
private volatile String token;
private volatile String suiteAccessToken;
private volatile long suiteAccessTokenExpiresTime;
private volatile String aesKey;
private volatile long expiresTime;
private volatile String suiteTicket;
private volatile long suiteTicketExpiresTime;
private volatile String oauth2redirectUri;
private volatile Map<String, String> authCorpAccessTokenMap = new HashMap<>();
private volatile Map<String, Long> authCorpAccessTokenExpireTimeMap = new HashMap<>();
private volatile Map<String, String> authCorpJsApiTicketMap = new HashMap<>();
private volatile Map<String, Long> authCorpJsApiTicketExpireTimeMap = new HashMap<>();
private volatile Map<String, String> authSuiteJsApiTicketMap = new HashMap<>();
private volatile Map<String, Long> authSuiteJsApiTicketExpireTimeMap = new HashMap<>();
private volatile String httpProxyHost;
private volatile int httpProxyPort;
private volatile String httpProxyUsername;
private volatile String httpProxyPassword;
private volatile String suiteTicket;
private volatile long suiteTicketExpiresTime;
private volatile File tmpDirFile;
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
private volatile String baseApiUrl;
@Override
public void setBaseApiUrl(String baseUrl) {
this.baseApiUrl = baseUrl;
@@ -67,12 +78,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
@Override
public boolean isSuiteAccessTokenExpired() {
return System.currentTimeMillis() > this.expiresTime;
return System.currentTimeMillis() > this.suiteAccessTokenExpiresTime;
}
@Override
public void expireSuiteAccessToken() {
this.expiresTime = 0;
this.suiteAccessTokenExpiresTime = 0;
}
@Override
@@ -83,25 +94,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
@Override
public synchronized void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
this.suiteAccessToken = suiteAccessToken;
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
this.suiteAccessTokenExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}
@Override
public String getCorpId() {
return this.corpId;
}
public void setCorpId(String corpId) {
this.corpId = corpId;
}
@Override
public String getCorpSecret() {
return this.corpSecret;
}
public void setCorpSecret(String corpSecret) {
this.corpSecret = corpSecret;
@Deprecated
public void setSuiteAccessTokenExpiresTime(long suiteAccessTokenExpiresTime) {
this.suiteAccessTokenExpiresTime = suiteAccessTokenExpiresTime;
}
@Override
@@ -109,23 +107,16 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.suiteTicket;
}
public void setSuiteTicket(String suiteTicket) {
this.suiteTicket = suiteTicket;
}
public long getSuiteTicketExpiresTime() {
return this.suiteTicketExpiresTime;
}
public void setSuiteTicketExpiresTime(long suiteTicketExpiresTime) {
this.suiteTicketExpiresTime = suiteTicketExpiresTime;
}
@Override
public boolean isSuiteTicketExpired() {
return System.currentTimeMillis() > this.suiteTicketExpiresTime;
}
@Override
public void expireSuiteTicket() {
this.suiteTicketExpiresTime = 0;
}
@Override
public synchronized void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
this.suiteTicket = suiteTicket;
@@ -133,9 +124,20 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
this.suiteTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}
@Override
public void expireSuiteTicket() {
this.suiteTicketExpiresTime = 0;
@Deprecated
public void setSuiteTicket(String suiteTicket) {
this.suiteTicket = suiteTicket;
}
@Deprecated
public long getSuiteTicketExpiresTime() {
return this.suiteTicketExpiresTime;
}
@Deprecated
public void setSuiteTicketExpiresTime(long suiteTicketExpiresTime) {
this.suiteTicketExpiresTime = suiteTicketExpiresTime;
}
@Override
@@ -143,6 +145,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.suiteId;
}
@Deprecated
public void setSuiteId(String corpId) {
this.suiteId = corpId;
}
@@ -152,6 +155,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.suiteSecret;
}
@Deprecated
public void setSuiteSecret(String corpSecret) {
this.suiteSecret = corpSecret;
}
@@ -161,28 +165,109 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.token;
}
@Deprecated
public void setToken(String token) {
this.token = token;
}
@Override
public long getExpiresTime() {
return this.expiresTime;
}
public void setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
}
@Override
public String getAesKey() {
return this.aesKey;
}
@Deprecated
public void setAesKey(String aesKey) {
this.aesKey = aesKey;
}
@Override
public String getCorpId() {
return this.corpId;
}
@Deprecated
public void setCorpId(String corpId) {
this.corpId = corpId;
}
@Override
public String getCorpSecret() {
return this.corpSecret;
}
@Deprecated
public void setCorpSecret(String corpSecret) {
this.corpSecret = corpSecret;
}
@Override
public String getAccessToken(String authCorpId) {
return authCorpAccessTokenMap.get(authCorpId);
}
@Override
public boolean isAccessTokenExpired(String authCorpId) {
return System.currentTimeMillis() > authCorpAccessTokenExpireTimeMap.get(authCorpId);
}
@Override
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
authCorpAccessTokenMap.put(authCorpId, accessToken);
// 预留200秒的时间
authCorpAccessTokenExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId) {
return this.authCorpJsApiTicketMap.get(authCorpId);
}
@Override
public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
Long t = this.authCorpJsApiTicketExpireTimeMap.get(authCorpId);
if (t == null) {
return System.currentTimeMillis() > t;
}
else {
return true;
}
}
@Override
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
// 应该根据不同的授权企业做区分
authCorpJsApiTicketMap.put(authCorpId, jsApiTicket);
// 预留200秒的时间
authCorpJsApiTicketExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
}
@Override
public String getAuthSuiteJsApiTicket(String authCorpId) {
return authSuiteJsApiTicketMap.get(authCorpId);
}
@Override
public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
Long t = authSuiteJsApiTicketExpireTimeMap.get(authCorpId);
if (t == null) {
return System.currentTimeMillis() > t;
}
else {
return true;
}
}
@Override
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
// 应该根据不同的授权企业做区分
authSuiteJsApiTicketMap.put(authCorpId, jsApiTicket);
// 预留200秒的时间
authSuiteJsApiTicketExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
}
public void setOauth2redirectUri(String oauth2redirectUri) {
this.oauth2redirectUri = oauth2redirectUri;
}

View File

@@ -0,0 +1,278 @@
package me.chanjar.weixin.cp.config.impl;
import lombok.Builder;
import lombok.NonNull;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.redis.WxRedisOps;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.File;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
/**
* 企业微信各种固定、授权配置的Redisson存储实现
*/
@Builder
public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializable {
@NonNull
private final WxRedisOps wxRedisOps;
//redis里面key的统一前缀
private final String keyPrefix = "";
private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
private final String suiteTicketKey = ":suiteTicketKey:";
private final String accessTokenKey = ":accessTokenKey:";
private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
private volatile String baseApiUrl;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
private volatile String httpProxyUsername;
private volatile String httpProxyPassword;
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
private volatile File tmpDirFile;
/**
* 第三方应用的其他配置,来自于企微配置
*/
private volatile String suiteId;
private volatile String suiteSecret;
// 第三方应用的token用来检查应用的签名
private volatile String token;
//第三方应用的EncodingAESKey用来检查签名
private volatile String aesKey;
/**
* 企微服务商企业ID & 企业secret来自于企微配置
*/
private volatile String corpId;
private volatile String corpSecret;
@Override
public void setBaseApiUrl(String baseUrl) {
this.baseApiUrl = baseUrl;
}
@Override
public String getApiUrl(String path) {
if (baseApiUrl == null) {
baseApiUrl = "https://qyapi.weixin.qq.com";
}
return baseApiUrl + path; }
/**
* 第三方应用的suite access token相关
*/
@Override
public String getSuiteAccessToken() {
return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
}
@Override
public boolean isSuiteAccessTokenExpired() {
//remain time to live in seconds, or key not exist
return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
}
@Override
public void expireSuiteAccessToken() {
wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
}
@Override
public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
}
@Override
public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
}
/**
* 第三方应用的suite ticket相关
*/
@Override
public String getSuiteTicket() {
return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
}
@Override
public boolean isSuiteTicketExpired() {
//remain time to live in seconds, or key not exist
return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
}
@Override
public void expireSuiteTicket() {
wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
}
@Override
public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
}
/**
* 第三方应用的其他配置,来自于企微配置
*/
@Override
public String getSuiteId() {
return suiteId;
}
@Override
public String getSuiteSecret() {
return suiteSecret;
}
// 第三方应用的token用来检查应用的签名
@Override
public String getToken() {
return token;
}
//第三方应用的EncodingAESKey用来检查签名
@Override
public String getAesKey() {
return aesKey;
}
/**
* 企微服务商企业ID & 企业secret, 来自于企微配置
*/
@Override
public String getCorpId() {
return corpId;
}
@Override
public String getCorpSecret() {
return corpSecret;
}
/**
* 授权企业的access token相关
*/
@Override
public String getAccessToken(String authCorpId) {
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
}
@Override
public boolean isAccessTokenExpired(String authCorpId) {
//没有设置或者TTL为0都是过期
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
}
@Override
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
}
/**
* 授权企业的js api ticket相关
*/
@Override
public String getAuthCorpJsApiTicket(String authCorpId) {
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
}
@Override
public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
//没有设置或TTL为0,都是过期
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
}
@Override
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS);
}
/**
* 授权企业的第三方应用js api ticket相关
*/
@Override
public String getAuthSuiteJsApiTicket(String authCorpId) {
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
}
@Override
public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
//没有设置或者TTL为0都是过期
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
}
@Override
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS);
}
/**
* 网络代理相关
*/
@Override
public String getHttpProxyHost() {
return this.httpProxyHost;
}
@Override
public int getHttpProxyPort() {
return this.httpProxyPort;
}
@Override
public String getHttpProxyUsername() {
return this.httpProxyUsername;
}
@Override
public String getHttpProxyPassword() {
return this.httpProxyPassword;
}
@Override
public File getTmpDirFile() {
return tmpDirFile;
}
@Override
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
return this.apacheHttpClientBuilder;
}
@Override
public boolean autoRefreshToken() {
return false;
}
@Override
public String toString() {
//TODO:
return WxCpGsonBuilder.create().toJson(this);
}
private String keyWithPrefix(String key) {
return keyPrefix + key;
}
}

View File

@@ -140,6 +140,8 @@ public final class WxCpApiPathConsts {
public static final String GET_PROVIDER_TOKEN = "/cgi-bin/service/get_provider_token";
public static final String GET_PREAUTH_CODE = "/cgi-bin/service/get_pre_auth_code";
public static final String GET_AUTH_INFO = "/cgi-bin/service/get_auth_info";
public static final String GET_AUTH_CORP_JSAPI_TICKET = "/cgi-bin/get_jsapi_ticket";
public static final String GET_SUITE_JSAPI_TICKET = "/cgi-bin/ticket/get";
}
@UtilityClass

View File

@@ -0,0 +1,52 @@
package me.chanjar.weixin.cp.constant;
import lombok.experimental.UtilityClass;
public class WxCpTpConsts {
@UtilityClass
public static class InfoType {
/**
* 推送更新suite_ticket
*/
public static final String SUITE_TICKET = "suite_ticket";
/**
* 从企业微信应用市场发起授权时,授权成功通知
*/
public static final String CREATE_AUTH = "create_auth";
/**
* 从企业微信应用市场发起授权时,变更授权通知
*/
public static final String CHANGE_AUTH = "change_auth";
/**
* 从企业微信应用市场发起授权时,取消授权通知
*/
public static final String CANCEL_AUTH = "cancel_auth";
/**
* 通讯录变更通知
*/
public static final String CHANGE_CONTACT = "change_contact";
/**
* 用户进行企业微信的注册,注册完成回调通知
*/
public static final String REGISTER_CORP = "register_corp";
/**
* 异步任务回调通知
*/
public static final String BATCH_JOB_RESULT = "batch_job_result";
/**
* 外部联系人变更通知
*/
public static final String CHANGE_EXTERNAL_CONTACT = "change_external_contact";
}
}

View File

@@ -3,7 +3,6 @@ package me.chanjar.weixin.cp.tp.message;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;

View File

@@ -3,7 +3,6 @@ package me.chanjar.weixin.cp.tp.message;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import java.util.Map;

View File

@@ -1,5 +1,6 @@
package me.chanjar.weixin.cp.tp.message;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
/**
@@ -15,6 +16,6 @@ public interface WxCpTpMessageMatcher {
* @param message the message
* @return the boolean
*/
boolean match(WxCpXmlMessage message);
boolean match(WxCpTpXmlMessage message);
}

View File

@@ -10,9 +10,7 @@ import me.chanjar.weixin.common.session.InternalSessionManager;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.LogExceptionHandler;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.message.WxCpMessageRouterRule;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import org.apache.commons.lang3.StringUtils;
@@ -25,20 +23,22 @@ import java.util.concurrent.*;
/**
* <pre>
* 微信消息路由器通过代码化的配置把来自微信的消息交给handler处理
* 和WxCpMessageRouter的rule相比多了infoType和changeType维度的匹配
*
* 说明:
* 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
* 2. 默认情况下消息只会被处理一次,除非使用 {@link WxCpMessageRouterRule#next()}
* 3. 规则的结束必须用{@link WxCpMessageRouterRule#end()}或者{@link WxCpMessageRouterRule#next()},否则不会生效
* 2. 默认情况下消息只会被处理一次,除非使用 {@link WxCpTpMessageRouterRule#next()}
* 3. 规则的结束必须用{@link WxCpTpMessageRouterRule#end()}或者{@link WxCpTpMessageRouterRule#next()},否则不会生效
*
* 使用方法:
* WxCpMessageRouter router = new WxCpMessageRouter();
* WxCpTpMessageRouter router = new WxCpTpMessageRouter();
* router
* .rule()
* .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
* .interceptor(interceptor, ...).handler(handler, ...)
* .end()
* .rule()
* .infoType("INFO_TYPE").changeType("CHANGE_TYPE")
* // 另外一个匹配规则
* .end()
* ;
@@ -55,7 +55,7 @@ public class WxCpTpMessageRouter {
private static final int DEFAULT_THREAD_POOL_SIZE = 100;
private final List<WxCpTpMessageRouterRule> rules = new ArrayList<>();
private final WxCpTpService wxCpService;
private final WxCpTpService wxCpTpService;
private ExecutorService executorService;
@@ -68,13 +68,13 @@ public class WxCpTpMessageRouter {
/**
* 构造方法.
*/
public WxCpTpMessageRouter(WxCpTpService wxCpService) {
this.wxCpService = wxCpService;
public WxCpTpMessageRouter(WxCpTpService wxCpTpService) {
this.wxCpTpService = wxCpTpService;
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxCpTpMessageRouter-pool-%d").build();
this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory);
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
this.sessionManager = wxCpService.getSessionManager();
this.sessionManager = wxCpTpService.getSessionManager();
this.exceptionHandler = new LogExceptionHandler();
}
@@ -160,11 +160,11 @@ public class WxCpTpMessageRouter {
if (rule.isAsync()) {
futures.add(
this.executorService.submit(() -> {
rule.service(wxMessage, context, WxCpTpMessageRouter.this.wxCpService, WxCpTpMessageRouter.this.sessionManager, WxCpTpMessageRouter.this.exceptionHandler);
rule.service(wxMessage, context, WxCpTpMessageRouter.this.wxCpTpService, WxCpTpMessageRouter.this.sessionManager, WxCpTpMessageRouter.this.exceptionHandler);
})
);
} else {
res = rule.service(wxMessage, context, this.wxCpService, this.sessionManager, this.exceptionHandler);
res = rule.service(wxMessage, context, this.wxCpTpService, this.sessionManager, this.exceptionHandler);
// 在同步操作结束session访问结束
log.debug("End session access: async=false, sessionId={}", wxMessage.getSuiteId());
sessionEndAccess(wxMessage);
@@ -200,17 +200,30 @@ public class WxCpTpMessageRouter {
private boolean isMsgDuplicated(WxCpTpXmlMessage wxMessage) {
StringBuilder messageId = new StringBuilder();
if (StringUtils.isNotEmpty(wxMessage.getSuiteId())) {
messageId.append("-").append(wxMessage.getSuiteId());
}
if (wxMessage.getInfoType() != null) {
messageId.append(wxMessage.getInfoType())
.append("-").append(StringUtils.trimToEmpty(wxMessage.getSuiteId()))
.append("-").append(wxMessage.getTimeStamp())
.append("-").append(StringUtils.trimToEmpty(wxMessage.getAuthCorpId()))
.append("-").append(StringUtils.trimToEmpty(wxMessage.getUserID()))
.append("-").append(StringUtils.trimToEmpty(wxMessage.getChangeType()))
.append("-").append(StringUtils.trimToEmpty(wxMessage.getServiceCorpId()));
}
if (StringUtils.isNotEmpty(wxMessage.getInfoType())) {
messageId.append("-").append(wxMessage.getInfoType());
}
if (StringUtils.isNotEmpty(wxMessage.getTimeStamp())) {
messageId.append("-").append(wxMessage.getTimeStamp());
}
if (wxMessage.getMsgType() != null) {
if (wxMessage.getMsgId() != null) {
messageId.append(wxMessage.getMsgId())
.append("-").append(wxMessage.getCreateTime())
.append("-").append(wxMessage.getFromUserName());
}
else {
messageId.append(wxMessage.getMsgType())
.append("-").append(wxMessage.getCreateTime())
.append("-").append(wxMessage.getFromUserName())
.append("-").append(StringUtils.trimToEmpty(wxMessage.getEvent()))
.append("-").append(StringUtils.trimToEmpty(wxMessage.getEventKey()));
}
}
return this.messageDuplicateChecker.isDuplicate(messageId.toString());
}

View File

@@ -6,10 +6,11 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.message.WxCpMessageMatcher;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.regex.Pattern;
/**
* The type Wx cp message router rule.
@@ -22,15 +23,34 @@ public class WxCpTpMessageRouterRule {
private boolean async = true;
private WxCpMessageMatcher matcher;
private String fromUser;
private String msgType;
private String event;
private String eventKey;
private String eventKeyRegex;
private String content;
private String rContent;
private WxCpTpMessageMatcher matcher;
private boolean reEnter = false;
private Integer agentId;
private String infoType;
private String changeType;
private List<WxCpTpMessageHandler> handlers = new ArrayList<>();
private List<WxCpTpMessageInterceptor> interceptors = new ArrayList<>();
private String suiteId;
private String infoType;
private String authCode;
private String suiteTicket;
@@ -64,13 +84,24 @@ public class WxCpTpMessageRouterRule {
return this;
}
/**
* 如果changeType等于这个type符合rule的条件之一
* @param changeType
* @return
*/
public WxCpTpMessageRouterRule changeType(String changeType) {
this.changeType = changeType;
return this;
}
/**
* 如果消息匹配某个matcher用在用户需要自定义更复杂的匹配规则的时候
*
* @param matcher the matcher
* @return the wx cp message router rule
*/
public WxCpTpMessageRouterRule matcher(WxCpMessageMatcher matcher) {
public WxCpTpMessageRouterRule matcher(WxCpTpMessageMatcher matcher) {
this.matcher = matcher;
return this;
}
@@ -154,13 +185,30 @@ public class WxCpTpMessageRouterRule {
protected boolean test(WxCpTpXmlMessage wxMessage) {
return
(this.suiteId == null || this.suiteId.equals(wxMessage.getSuiteId()))
&&
(this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName()))
&&
(this.agentId == null || this.agentId.equals(wxMessage.getAgentID()))
&&
(this.msgType == null || this.msgType.equalsIgnoreCase(wxMessage.getMsgType()))
&&
(this.infoType == null || this.infoType.equals(wxMessage.getInfoType()))
&&
(this.suiteTicket == null || this.suiteTicket.equalsIgnoreCase(wxMessage.getSuiteTicket()))
&&
(this.authCode == null || this.authCode.equalsIgnoreCase(wxMessage.getAuthCode()))
;
(this.eventKeyRegex == null || Pattern.matches(this.eventKeyRegex, StringUtils.trimToEmpty(wxMessage.getEventKey())))
&&
(this.content == null || this.content.equals(StringUtils.trimToNull(wxMessage.getContent())))
&&
(this.rContent == null || Pattern.matches(this.rContent, StringUtils.trimToEmpty(wxMessage.getContent())))
&&
(this.infoType == null || this.infoType.equals(wxMessage.getInfoType()))
&&
(this.changeType == null || this.changeType.equals(wxMessage.getChangeType()))
&&
(this.matcher == null || this.matcher.match(wxMessage))
&&
(this.authCode == null || this.authCode.equalsIgnoreCase(wxMessage.getAuthCode()));
}
/**

View File

@@ -73,12 +73,35 @@ public interface WxCpTpService {
* 详情请见https://work.weixin.qq.com/api/doc#90001/90143/90628
* </pre>
*
* @Deprecated 由于无法主动刷新所以这个接口实际已经没有意义需要在接收企业微信的主动推送后保存这个ticket
* @see #setSuiteTicket(String)
*
* @param forceRefresh 强制刷新
* @return the suite ticket
* @throws WxErrorException the wx error exception
*/
@Deprecated
String getSuiteTicket(boolean forceRefresh) throws WxErrorException;
/**
* <pre>
* 保存企业微信定时推送的suite_ticket,每10分钟
* 详情请见https://work.weixin.qq.com/api/doc#90001/90143/90628
* </pre>
*
* @param suiteTicket
* @throws WxErrorException
*/
void setSuiteTicket(String suiteTicket) throws WxErrorException;
/**
* 获取应用的 jsapi ticket
*
* @param authCorpId 授权企业的cropId
* @return jsapi ticket
*/
String getSuiteJsApiTicket(String authCorpId) throws WxErrorException;
/**
* 小程序登录凭证校验
*
@@ -144,6 +167,14 @@ public interface WxCpTpService {
*/
WxCpTpAuthInfo getAuthInfo(String authCorpId, String permanentCode) throws WxErrorException;
/**
* 获取授权企业的 jsapi ticket
*
* @param authCorpId 授权企业的cropId
* @return jsapi ticket
*/
String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException;
/**
* 当本Service没有实现某个API的时候可以用这个针对所有微信API中的GET请求.
*
@@ -209,8 +240,10 @@ public interface WxCpTpService {
/**
* 获取WxMpConfigStorage 对象.
*
* @Deprecated storage应该在service内部使用提供这个接口容易破坏这个封装
* @return WxMpConfigStorage wx cp tp config storage
*/
@Deprecated
WxCpTpConfigStorage getWxCpTpConfigStorage();
/**

View File

@@ -45,10 +45,16 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
*/
protected final Object globalSuiteAccessTokenRefreshLock = new Object();
/**
* 全局刷新suite ticket的锁
*/
protected final Object globalSuiteTicketRefreshLock = new Object();
/**
* 全局的是否正在刷新jsapi_ticket的锁.
*/
protected final Object globalSuiteTicketRefreshLock = new Object();
protected final Object globalJsApiTicketRefreshLock = new Object();
protected WxCpTpConfigStorage configStorage;
@@ -79,16 +85,6 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
@Override
public String getSuiteTicket() throws WxErrorException {
return getSuiteTicket(false);
}
@Override
public String getSuiteTicket(boolean forceRefresh) throws WxErrorException {
// suite ticket由微信服务器推送不能强制刷新
// if (forceRefresh) {
// this.configStorage.expireSuiteTicket();
// }
if (this.configStorage.isSuiteTicketExpired()) {
// 本地suite ticket 不存在或者过期
WxError wxError = WxError.fromJson("{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}", WxType.CP);
@@ -97,6 +93,68 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return this.configStorage.getSuiteTicket();
}
@Override
public String getSuiteTicket(boolean forceRefresh) throws WxErrorException {
// suite ticket由微信服务器推送不能强制刷新
// if (forceRefresh) {
// this.configStorage.expireSuiteTicket();
// }
return getSuiteTicket();
}
@Override
public void setSuiteTicket(String suiteTicket) throws WxErrorException {
synchronized (globalSuiteTicketRefreshLock) {
this.configStorage.updateSuiteTicket(suiteTicket, 10 * 60);
}
}
@Override
public String getSuiteJsApiTicket(String authCorpId) throws WxErrorException {
if (this.configStorage.isSuiteAccessTokenExpired()) {
String resp = get(configStorage.getApiUrl(GET_SUITE_JSAPI_TICKET),
"type=agent_config&access_token=" + this.configStorage.getAccessToken(authCorpId));
JsonObject jsonObject = GsonParser.parse(resp);
if (jsonObject.get("errcode").getAsInt() == 0) {
String jsApiTicket = jsonObject.get("ticket").getAsString();
int expiredInSeconds = jsonObject.get("expires_in").getAsInt();
synchronized (globalJsApiTicketRefreshLock) {
configStorage.updateAuthSuiteJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds);
}
}
else {
throw new WxErrorException(WxError.fromJson(resp));
}
}
return configStorage.getSuiteAccessToken();
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException {
if (this.configStorage.isSuiteAccessTokenExpired()) {
String resp = get(configStorage.getApiUrl(GET_AUTH_CORP_JSAPI_TICKET),
"access_token=" + this.configStorage.getAccessToken(authCorpId));
JsonObject jsonObject = GsonParser.parse(resp);
if (jsonObject.get("errcode").getAsInt() == 0) {
String jsApiTicket = jsonObject.get("ticket").getAsString();
int expiredInSeconds = jsonObject.get("expires_in").getAsInt();
synchronized (globalJsApiTicketRefreshLock) {
configStorage.updateAuthCorpJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds);
}
}
else {
throw new WxErrorException(WxError.fromJson(resp));
}
}
return configStorage.getSuiteAccessToken();
}
@Override
public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException {

View File

@@ -0,0 +1,57 @@
package me.chanjar.weixin.cp.api;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.tp.message.WxCpTpMessageHandler;
import me.chanjar.weixin.cp.tp.message.WxCpTpMessageRouter;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImpl;
import org.testng.annotations.Test;
import java.util.Map;
import static org.testng.Assert.assertNotNull;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNull;
public class WxCpTpMessageRouterTest {
@Test
public void testMessageRouter() {
WxCpTpService service = new WxCpTpServiceImpl();
WxCpTpMessageRouter router = new WxCpTpMessageRouter(service);
String xml = "<xml>\n" +
" <SuiteId><![CDATA[ww4asffe99e54cxxxx]]></SuiteId>\n" +
" <AuthCorpId><![CDATA[wxf8b4f85f3a79xxxx]]></AuthCorpId>\n" +
" <InfoType><![CDATA[change_contact]]></InfoType>\n" +
" <TimeStamp>1403610513</TimeStamp>\n" +
" <ChangeType><![CDATA[update_tag]]></ChangeType>\n" +
" <TagId>1</TagId>\n" +
" <AddUserItems><![CDATA[zhangsan,lisi]]></AddUserItems>\n" +
" <DelUserItems><![CDATA[zhangsan1,lisi1]]></DelUserItems>\n" +
" <AddPartyItems><![CDATA[1,2]]></AddPartyItems>\n" +
" <DelPartyItems><![CDATA[3,4]]></DelPartyItems>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
router.rule().infoType("change_contact").changeType("update_tag").handler(new WxCpTpMessageHandler() {
@Override
public WxCpXmlOutMessage handle(WxCpTpXmlMessage wxMessage, Map<String, Object> context, WxCpTpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
System.out.println("handler enter");
assertNotNull(wxCpService);
return null;
}
}).end();
assertNull(router.route(wxXmlMessage));
System.out.println("over");
}
}

View File

@@ -0,0 +1,234 @@
package me.chanjar.weixin.cp.bean.message;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
public class WxCpTpXmlMessageTest {
@Test
public void testUserNotifyXML() {
String xml = "<xml>\n" +
" <SuiteId><![CDATA[ww4asffe99e54c0f4c]]></SuiteId>\n" +
" <AuthCorpId><![CDATA[wxf8b4f85f3axxxxxx]]></AuthCorpId>\n" +
" <InfoType><![CDATA[change_contact]]></InfoType>\n" +
" <TimeStamp>1403610513</TimeStamp>\n" +
" <ChangeType><![CDATA[create_user]]></ChangeType>\n" +
" <UserID><![CDATA[zhangsan]]></UserID>\n" +
" <Name><![CDATA[张三]]></Name>\n" +
" <Department><![CDATA[1,2,3]]></Department>\n" +
" <MainDepartment>1</MainDepartment>\n" +
" <IsLeaderInDept><![CDATA[1,0,0]]></IsLeaderInDept>\n" +
" <Mobile><![CDATA[11111111111]]></Mobile>\n" +
" <Position><![CDATA[产品经理]]></Position>\n" +
" <Gender>1</Gender>\n" +
" <Email><![CDATA[zhangsan@xxx.com]]></Email>\n" +
" <Avatar><![CDATA[http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0]]></Avatar>\n" +
" <Alias><![CDATA[zhangsan]]></Alias>\n" +
" <Telephone><![CDATA[020-111111]]></Telephone>\n" +
" <ExtAttr>\n" +
" <Item>\n" +
" <Name><![CDATA[爱好]]></Name>\n" +
" <Type>0</Type>\n" +
" <Text>\n" +
" <Value><![CDATA[旅游]]></Value>\n" +
" </Text>\n" +
" </Item>\n" +
" <Item>\n" +
" <Name><![CDATA[卡号]]></Name>\n" +
" <Type>1</Type>\n" +
" <Web>\n" +
" <Title><![CDATA[企业微信]]></Title>\n" +
" <Url><![CDATA[https://work.weixin.qq.com]]></Url>\n" +
" </Web>\n" +
" </Item>\n" +
" </ExtAttr>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getSuiteId(), "ww4asffe99e54c0f4c");
assertEquals(wxXmlMessage.getPosition(), "产品经理");
assertEquals(wxXmlMessage.getGender(), Integer.valueOf(1));
assertEquals(wxXmlMessage.getTelephone(), "020-111111");
}
@Test
public void testRegisterCorp() {
String xml = "<xml>\n" +
" <ServiceCorpId><![CDATA[wwddddccc7775555aab]]></ServiceCorpId>\n" +
" <InfoType><![CDATA[register_corp]]></InfoType>\n" +
" <TimeStamp>1502682173</TimeStamp>\n" +
" <RegisterCode><![CDATA[pIKi3wRPNWCGF-pyP-YU5KWjDDD]]></RegisterCode>\n" +
" <AuthCorpId><![CDATA[wwddddccc7775555aaa]]></AuthCorpId>\n" +
" <ContactSync>\n" +
" <AccessToken><![CDATA[accesstoken000001]]></AccessToken>\n" +
" <ExpiresIn>1800</ExpiresIn>\n" +
" </ContactSync>\n" +
" <AuthUserInfo>\n" +
" <UserId><![CDATA[zhangshan]]></UserId>\n" +
" </AuthUserInfo>\n" +
" <State><![CDATA[state1]]></State>\n" +
" <TemplateId><![CDATA[tpl1test]]></TemplateId>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getServiceCorpId(), "wwddddccc7775555aab");
assertEquals(wxXmlMessage.getInfoType(), "register_corp");
assertEquals(wxXmlMessage.getRegisterCode(), "pIKi3wRPNWCGF-pyP-YU5KWjDDD");
assertNotNull(wxXmlMessage.getContactSync());
assertEquals(wxXmlMessage.getContactSync().getAccessToken(), "accesstoken000001");
assertEquals(wxXmlMessage.getContactSync().getExpiresIn(), Integer.valueOf(1800));
assertNotNull(wxXmlMessage.getAuthUserInfo());
assertEquals(wxXmlMessage.getAuthUserInfo().getUserId(), "zhangshan");
assertEquals(wxXmlMessage.getTemplateId(), "tpl1test");
}
@Test
public void tagNotifyTest() {
String xml = "<xml>\n" +
" <SuiteId><![CDATA[ww4asffe99e54cxxxx]]></SuiteId>\n" +
" <AuthCorpId><![CDATA[wxf8b4f85f3a79xxxx]]></AuthCorpId>\n" +
" <InfoType><![CDATA[change_contact]]></InfoType>\n" +
" <TimeStamp>1403610513</TimeStamp>\n" +
" <ChangeType><![CDATA[update_tag]]></ChangeType>\n" +
" <TagId>1</TagId>\n" +
" <AddUserItems><![CDATA[zhangsan,lisi]]></AddUserItems>\n" +
" <DelUserItems><![CDATA[zhangsan1,lisi1]]></DelUserItems>\n" +
" <AddPartyItems><![CDATA[1,2]]></AddPartyItems>\n" +
" <DelPartyItems><![CDATA[3,4]]></DelPartyItems>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getTagId(), Integer.valueOf(1));
assertNotNull(wxXmlMessage.getAddUserItems());
assertEquals(wxXmlMessage.getAddUserItems()[0], "zhangsan");
assertEquals(wxXmlMessage.getAddUserItems()[1], "lisi");
assertNotNull(wxXmlMessage.getDelUserItems());
assertNotNull(wxXmlMessage.getDelUserItems()[0], "zhangsan1");
assertNotNull(wxXmlMessage.getDelUserItems()[0], "lisi1");
assertNotNull(wxXmlMessage.getAddPartyItems());
assertEquals(wxXmlMessage.getAddPartyItems()[0], Integer.valueOf(1));
assertEquals(wxXmlMessage.getAddPartyItems()[1], Integer.valueOf(2));
}
@Test
public void enterAppTest() {
String xml = "<xml><ToUserName><![CDATA[toUser]]></ToUserName>\n" +
"<FromUserName><![CDATA[FromUser]]></FromUserName>\n" +
"<CreateTime>1408091189</CreateTime>\n" +
"<MsgType><![CDATA[event]]></MsgType>\n" +
"<Event><![CDATA[enter_agent]]></Event>\n" +
"<EventKey><![CDATA[]]></EventKey>\n" +
"<AgentID>1</AgentID>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getToUserName(), "toUser");
assertEquals(wxXmlMessage.getFromUserName(), "FromUser");
assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1408091189));
assertEquals(wxXmlMessage.getEvent(), "enter_agent");
assertEquals(wxXmlMessage.getEventKey(), "");
assertEquals(wxXmlMessage.getAgentID(), Integer.valueOf(1));
}
@Test
public void textMessageTest() {
String xml = "<xml>\n" +
" <ToUserName><![CDATA[toUser]]></ToUserName>\n" +
" <FromUserName><![CDATA[fromUser]]></FromUserName> \n" +
" <CreateTime>1348831860</CreateTime>\n" +
" <MsgType><![CDATA[text]]></MsgType>\n" +
" <Content><![CDATA[this is a test]]></Content>\n" +
" <MsgId>1234567890123456</MsgId>\n" +
" <AgentID>1</AgentID>\n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getToUserName(), "toUser");
assertEquals(wxXmlMessage.getFromUserName(), "fromUser");
assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1348831860));
assertEquals(wxXmlMessage.getMsgType(), "text");
assertEquals(wxXmlMessage.getMsgId(), "1234567890123456");
}
@Test
public void ApprovalInfoTest() {
String xml = "<xml>\n" +
" <ToUserName>wwddddccc7775555aaa</ToUserName> \n" +
" <FromUserName>sys</FromUserName> \n" +
" <CreateTime>1527838022</CreateTime> \n" +
" <MsgType>event</MsgType> \n" +
" <Event>open_approval_change</Event>\n" +
" <AgentID>1</AgentID>\n" +
" <ApprovalInfo> \n" +
" <ThirdNo>201806010001</ThirdNo> \n" +
" <OpenSpName>付款</OpenSpName> \n" +
" <OpenTemplateId>1234567890</OpenTemplateId> \n" +
" <OpenSpStatus>1</OpenSpStatus> \n" +
" <ApplyTime>1527837645</ApplyTime> \n" +
" <ApplyUserName>xiaoming</ApplyUserName> \n" +
" <ApplyUserId>1</ApplyUserId> \n" +
" <ApplyUserParty>产品部</ApplyUserParty> \n" +
" <ApplyUserImage>http://www.qq.com/xxx.png</ApplyUserImage> \n" +
" <ApprovalNodes> \n" +
" <ApprovalNode> \n" +
" <NodeStatus>1</NodeStatus> \n" +
" <NodeAttr>1</NodeAttr> \n" +
" <NodeType>1</NodeType> \n" +
" <Items> \n" +
" <Item> \n" +
" <ItemName>xiaohong</ItemName> \n" +
" <ItemUserId>2</ItemUserId> \n" +
" <ItemImage>http://www.qq.com/xxx.png</ItemImage> \n" +
" <ItemStatus>1</ItemStatus> \n" +
" <ItemSpeech></ItemSpeech> \n" +
" <ItemOpTime>0</ItemOpTime> \n" +
" </Item> \n" +
" </Items> \n" +
" </ApprovalNode> \n" +
" </ApprovalNodes> \n" +
" <NotifyNodes> \n" +
" <NotifyNode> \n" +
" <ItemName>xiaogang</ItemName> \n" +
" <ItemUserId>3</ItemUserId> \n" +
" <ItemImage>http://www.qq.com/xxx.png</ItemImage> \n" +
" </NotifyNode> \n" +
" </NotifyNodes> \n" +
" <approverstep>0</approverstep> \n" +
" </ApprovalInfo> \n" +
"</xml>";
WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
assertEquals(wxXmlMessage.getToUserName(), "wwddddccc7775555aaa");
assertEquals(wxXmlMessage.getFromUserName(), "sys");
assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1527838022));
assertEquals(wxXmlMessage.getEvent(), "open_approval_change");
assertNotNull(wxXmlMessage.getApprovalInfo());
assertEquals(wxXmlMessage.getApprovalInfo().getThirdNo(), Long.valueOf(201806010001L));
assertEquals(wxXmlMessage.getApprovalInfo().getOpenSpName(), "付款");
assertEquals(wxXmlMessage.getApprovalInfo().getThirdNo(), Long.valueOf(201806010001L));
assertEquals(wxXmlMessage.getApprovalInfo().getApplyTime(), Long.valueOf(1527837645));
assertEquals(wxXmlMessage.getApprovalInfo().getApplyUserName(), "xiaoming");
assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes());
assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0));
assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getNodeAttr(), Integer.valueOf(1));
assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getNodeType(), Integer.valueOf(1));
assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems());
assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemName(), "xiaohong");
assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemOpTime(), Long.valueOf(0));
assertNotNull(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0));
assertEquals(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemImage(), "http://www.qq.com/xxx.png");
assertEquals(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemUserId(), Integer.valueOf(3));
}
}