org.testng
testng
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
index a7538c6ea..016c880fd 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
@@ -1,12 +1,125 @@
package me.chanjar.weixin.cp.api.impl;
+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.cp.constant.WxCpApiPathConsts;
+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;
+import java.util.concurrent.locks.Lock;
+
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_AGENT_CONFIG_TICKET;
+import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_JSAPI_TICKET;
+
/**
*
* 默认接口实现类,使用apache httpclient实现
* Created by Binary Wang on 2017-5-27.
*
+ *
+ * 增加分布式锁(基于WxCpConfigStorage实现)的支持
+ * Updated by yuanqixun on 2020-05-13
+ *
+ *
*
* @author Binary Wang
*/
public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl {
+ @Override
+ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+ if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) {
+ return getWxCpConfigStorage().getAccessToken();
+ }
+ Lock lock = getWxCpConfigStorage().getAccessTokenLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) {
+ return getWxCpConfigStorage().getAccessToken();
+ }
+ String url = String.format(getWxCpConfigStorage().getApiUrl(WxCpApiPathConsts.GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret());
+ try {
+ HttpGet httpGet = new HttpGet(url);
+ if (getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom()
+ .setProxy(getRequestHttpProxy()).build();
+ httpGet.setConfig(config);
+ }
+ String resultContent;
+ try (CloseableHttpClient httpClient = getRequestHttpClient();
+ CloseableHttpResponse response = httpClient.execute(httpGet)) {
+ resultContent = new BasicResponseHandler().handleResponse(response);
+ } finally {
+ httpGet.releaseConnection();
+ }
+ WxError error = WxError.fromJson(resultContent, WxType.CP);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+
+ WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+ getWxCpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } finally {
+ lock.unlock();
+ }
+ return getWxCpConfigStorage().getAccessToken();
+ }
+
+ @Override
+ public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException {
+ if (forceRefresh) {
+ getWxCpConfigStorage().expireAgentJsapiTicket();
+ }
+ if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) {
+ Lock lock = getWxCpConfigStorage().getAgentJsapiTicketLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) {
+ String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_AGENT_CONFIG_TICKET), null);
+ JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
+ getWxCpConfigStorage().updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(),
+ jsonObject.get("expires_in").getAsInt());
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ return getWxCpConfigStorage().getAgentJsapiTicket();
+ }
+
+ @Override
+ public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
+ if (forceRefresh) {
+ getWxCpConfigStorage().expireJsapiTicket();
+ }
+
+ if (getWxCpConfigStorage().isJsapiTicketExpired()) {
+ Lock lock = getWxCpConfigStorage().getJsapiTicketLock();
+ lock.lock();
+ try {
+ // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
+ if (getWxCpConfigStorage().isJsapiTicketExpired()) {
+ String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_JSAPI_TICKET), null);
+ JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
+ getWxCpConfigStorage().updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(),
+ tmpJsonObject.get("expires_in").getAsInt());
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ return getWxCpConfigStorage().getJsapiTicket();
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
index c6ccf893b..19c04f4be 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
@@ -4,6 +4,7 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import java.io.File;
+import java.util.concurrent.locks.Lock;
/**
* 微信客户端配置存储.
@@ -28,6 +29,8 @@ public interface WxCpConfigStorage {
String getAccessToken();
+ Lock getAccessTokenLock();
+
boolean isAccessTokenExpired();
/**
@@ -41,6 +44,8 @@ public interface WxCpConfigStorage {
String getJsapiTicket();
+ Lock getJsapiTicketLock();
+
boolean isJsapiTicketExpired();
/**
@@ -55,6 +60,8 @@ public interface WxCpConfigStorage {
String getAgentJsapiTicket();
+ Lock getAgentJsapiTicketLock();
+
boolean isAgentJsapiTicketExpired();
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
index 9f182180a..bd407af6d 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
@@ -8,6 +8,8 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.File;
import java.io.Serializable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
/**
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
@@ -22,6 +24,7 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String token;
protected volatile String accessToken;
+ protected Lock accessTokenLock = new ReentrantLock();
private volatile String aesKey;
protected volatile Integer agentId;
private volatile long expiresTime;
@@ -34,9 +37,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String httpProxyPassword;
private volatile String jsapiTicket;
+ protected Lock jsapiTicketLock = new ReentrantLock();
private volatile long jsapiTicketExpiresTime;
private volatile String agentJsapiTicket;
+ protected Lock agentJsapiTicketLock = new ReentrantLock();
private volatile long agentJsapiTicketExpiresTime;
private volatile File tmpDirFile;
@@ -63,6 +68,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
return this.accessToken;
}
+ @Override
+ public Lock getAccessTokenLock() {
+ return this.accessTokenLock;
+ }
+
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
@@ -93,6 +103,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
return this.jsapiTicket;
}
+ @Override
+ public Lock getJsapiTicketLock() {
+ return this.jsapiTicketLock;
+ }
+
public void setJsapiTicket(String jsapiTicket) {
this.jsapiTicket = jsapiTicket;
}
@@ -122,6 +137,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
return this.agentJsapiTicket;
}
+ @Override
+ public Lock getAgentJsapiTicketLock() {
+ return this.agentJsapiTicketLock;
+ }
+
@Override
public boolean isAgentJsapiTicketExpired() {
return System.currentTimeMillis() > this.agentJsapiTicketExpiresTime;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
index 5b48f547f..eb22bc4c7 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
@@ -9,6 +9,8 @@ import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.File;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
/**
*
@@ -89,6 +91,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
}
}
+ @Override
+ public Lock getAccessTokenLock() {
+ return new ReentrantLock();
+ }
+
@Override
public boolean isAccessTokenExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
@@ -132,6 +139,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
}
}
+ @Override
+ public Lock getJsapiTicketLock() {
+ return new ReentrantLock();
+ }
+
@Override
public boolean isJsapiTicketExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
@@ -170,6 +182,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
}
}
+ @Override
+ public Lock getAgentJsapiTicketLock() {
+ return new ReentrantLock();
+ }
+
@Override
public boolean isAgentJsapiTicketExpired() {
try (Jedis jedis = this.jedisPool.getResource()) {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java
new file mode 100644
index 000000000..b17857859
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java
@@ -0,0 +1,154 @@
+package me.chanjar.weixin.cp.config.impl;
+
+import lombok.NonNull;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RedissonClient;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * 基于Redisson的实现
+ *
+ * @author yuanqixun
+ * @date 2020/5/13
+ */
+public class WxCpRedissonConfigImpl extends WxCpDefaultConfigImpl {
+ protected final static String LOCK_KEY = "wechat_cp_lock:";
+ protected final static String CP_ACCESS_TOKEN_KEY = "wechat_cp_access_token_key:";
+ protected final static String CP_JSAPI_TICKET_KEY = "wechat_cp_jsapi_ticket_key:";
+ protected final static String CP_AGENT_JSAPI_TICKET_KEY = "wechat_cp_agent_jsapi_ticket_key:";
+
+ /**
+ * redis 存储的 key 的前缀,可为空
+ */
+ protected String keyPrefix;
+ protected String accessTokenKey;
+ protected String jsapiTicketKey;
+ protected String agentJsapiTicketKey;
+ protected String lockKey;
+
+ private final WxRedisOps redisOps;
+
+ public WxCpRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) {
+ this(new RedissonWxRedisOps(redissonClient), keyPrefix);
+ }
+
+ public WxCpRedissonConfigImpl(@NonNull RedissonClient redissonClient) {
+ this(redissonClient, null);
+ }
+
+ private WxCpRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) {
+ this.redisOps = redisOps;
+ this.keyPrefix = keyPrefix;
+ }
+
+ /**
+ * 设置企业微信自研应用ID(整数),同时初始化相关的redis key,注意要先调用setCorpId,再调用setAgentId
+ *
+ * @param agentId
+ */
+ @Override
+ public void setAgentId(Integer agentId) {
+ super.setAgentId(agentId);
+ String ukey = getCorpId().concat(":").concat(String.valueOf(agentId));
+ String prefix = StringUtils.isBlank(keyPrefix) ? "" :
+ (StringUtils.endsWith(keyPrefix, ":") ? keyPrefix : (keyPrefix + ":"));
+ lockKey = prefix + LOCK_KEY.concat(ukey);
+ accessTokenKey = prefix + CP_ACCESS_TOKEN_KEY.concat(ukey);
+ jsapiTicketKey = prefix + CP_JSAPI_TICKET_KEY.concat(ukey);
+ agentJsapiTicketKey = prefix + CP_AGENT_JSAPI_TICKET_KEY.concat(ukey);
+ }
+
+ protected Lock getLockByKey(String key) {
+ return redisOps.getLock(key);
+ }
+
+ @Override
+ public Lock getAccessTokenLock() {
+ return getLockByKey(this.lockKey.concat(":").concat("accessToken"));
+ }
+
+ @Override
+ public Lock getAgentJsapiTicketLock() {
+ return getLockByKey(this.lockKey.concat(":").concat("agentJsapiTicket"));
+
+ }
+
+ @Override
+ public Lock getJsapiTicketLock() {
+ return getLockByKey(this.lockKey.concat(":").concat("jsapiTicket"));
+ }
+
+ @Override
+ public String getAccessToken() {
+ return redisOps.getValue(this.accessTokenKey);
+ }
+
+ @Override
+ public boolean isAccessTokenExpired() {
+ Long expire = redisOps.getExpire(this.accessTokenKey);
+ return expire == null || expire < 2;
+ }
+
+ @Override
+ public void updateAccessToken(WxAccessToken accessToken) {
+ redisOps.setValue(this.accessTokenKey, accessToken.getAccessToken(), accessToken.getExpiresIn(), TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void updateAccessToken(String accessToken, int expiresInSeconds) {
+ redisOps.setValue(this.accessTokenKey, accessToken, expiresInSeconds, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void expireAccessToken() {
+ redisOps.expire(this.accessTokenKey, 0, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public String getJsapiTicket() {
+ return redisOps.getValue(this.jsapiTicketKey);
+ }
+
+ @Override
+ public boolean isJsapiTicketExpired() {
+ Long expire = redisOps.getExpire(this.jsapiTicketKey);
+ return expire == null || expire < 2;
+ }
+
+ @Override
+ public void expireJsapiTicket() {
+ redisOps.expire(this.jsapiTicketKey, 0, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
+ redisOps.setValue(this.jsapiTicketKey, jsapiTicket, expiresInSeconds, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void expireAgentJsapiTicket() {
+ redisOps.expire(this.agentJsapiTicketKey, 0, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void updateAgentJsapiTicket(String agentJsapiTicket, int expiresInSeconds) {
+ redisOps.setValue(this.agentJsapiTicketKey, agentJsapiTicket, expiresInSeconds, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public String getAgentJsapiTicket() {
+ return redisOps.getValue(this.agentJsapiTicketKey);
+ }
+
+ @Override
+ public boolean isAgentJsapiTicketExpired() {
+ Long expire = redisOps.getExpire(this.agentJsapiTicketKey);
+ return expire == null || expire < 2;
+ }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedissonConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedissonConfigImpl.java
index ef5facb82..c45ad5094 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedissonConfigImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedissonConfigImpl.java
@@ -23,9 +23,6 @@ public class WxMaRedissonConfigImpl extends WxMaDefaultConfigImpl {
protected final static String MA_JSAPI_TICKET_KEY = "wechat_ma_jsapi_ticket_key:";
protected final static String MA_CARD_API_TICKET_KEY = "wechat_ma_card_api_ticket_key:";
- /**
- * redis 存储的 key 的前缀,可为空
- */
/**
* redis 存储的 key 的前缀,可为空
*/