🎨 #2000 【企业微信】第三方应用增加基于分布式并发锁获取各种token和ticket的版本

This commit is contained in:
Coding And Loving 2021-02-13 23:22:49 +08:00 committed by GitHub
parent 3bb918d125
commit 9d3c11f552
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 664 additions and 4 deletions

View File

@ -2,8 +2,10 @@ package me.chanjar.weixin.cp.config;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
import java.io.File;
import java.util.concurrent.locks.Lock;
/**
* 微信客户端第三方应用配置存储
@ -30,6 +32,11 @@ public interface WxCpTpConfigStorage {
* 第三方应用的suite access token相关
*/
String getSuiteAccessToken();
/**
* 获取suite_access_token和剩余过期时间
* @return suite access token and the remaining expiration time
*/
WxAccessToken getSuiteAccessTokenEntity();
boolean isSuiteAccessTokenExpired();
//强制将suite access token过期掉.
void expireSuiteAccessToken();
@ -71,7 +78,9 @@ public interface WxCpTpConfigStorage {
* 授权企业的access token相关
*/
String getAccessToken(String authCorpId);
WxAccessToken getAccessTokenEntity(String authCorpId);
boolean isAccessTokenExpired(String authCorpId);
void expireAccessToken(String authCorpId);
void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds);
/**
@ -79,6 +88,7 @@ public interface WxCpTpConfigStorage {
*/
String getAuthCorpJsApiTicket(String authCorpId);
boolean isAuthCorpJsApiTicketExpired(String authCorpId);
void expireAuthCorpJsApiTicket(String authCorpId);
void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);
/**
@ -86,12 +96,16 @@ public interface WxCpTpConfigStorage {
*/
String getAuthSuiteJsApiTicket(String authCorpId);
boolean isAuthSuiteJsApiTicketExpired(String authCorpId);
void expireAuthSuiteJsApiTicket(String authCorpId);
void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);;
boolean isProviderTokenExpired();
void updateProviderToken(String providerToken, int expiredInSeconds);
String getProviderToken();
WxCpProviderToken getProviderTokenEntity();
// 强制过期
void expireProviderToken();
/**
* 网络代理相关
@ -108,4 +122,9 @@ public interface WxCpTpConfigStorage {
@Deprecated
File getTmpDirFile();
Lock getProviderAccessTokenLock();
Lock getSuiteAccessTokenLock();
Lock getAccessTokenLock(String authCorpId);
Lock getAuthCorpJsapiTicketLock(String authCorpId);
Lock getSuiteJsapiTicketLock(String authCorpId);
}

View File

@ -2,13 +2,18 @@ package me.chanjar.weixin.cp.config.impl;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 基于内存的微信配置provider在实际生产环境中应该将这些配置持久化.
@ -59,6 +64,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
private volatile String baseApiUrl;
// locker
private final transient Map<String, Lock> providerAccessTokenLocker = new ConcurrentHashMap<>();
private final transient Map<String, Lock> suiteAccessTokenLocker = new ConcurrentHashMap<>();
private final transient Map<String, Lock> accessTokenLocker = new ConcurrentHashMap<>();
private final transient Map<String, Lock> authCorpJsapiTicketLocker = new ConcurrentHashMap<>();
private final transient Map<String, Lock> authSuiteJsapiTicketLocker = new ConcurrentHashMap<>();
@Override
public void setBaseApiUrl(String baseUrl) {
@ -78,6 +89,15 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.suiteAccessToken;
}
@Override
public WxAccessToken getSuiteAccessTokenEntity() {
WxAccessToken accessToken = new WxAccessToken();
int expiresIn = Math.toIntExact((this.suiteAccessTokenExpiresTime - System.currentTimeMillis()) / 1000L);
accessToken.setExpiresIn(expiresIn <= 0 ? -1 : expiresIn);
accessToken.setAccessToken(this.suiteAccessToken);
return accessToken;
}
public void setSuiteAccessToken(String suiteAccessToken) {
this.suiteAccessToken = suiteAccessToken;
}
@ -218,12 +238,28 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return authCorpAccessTokenMap.get(authCorpId);
}
@Override
public WxAccessToken getAccessTokenEntity(String authCorpId) {
String accessToken = authCorpAccessTokenMap.getOrDefault(authCorpId, StringUtils.EMPTY);
Long expire = authCorpAccessTokenExpireTimeMap.getOrDefault(authCorpId, 0L);
WxAccessToken accessTokenEntity = new WxAccessToken();
accessTokenEntity.setAccessToken(accessToken);
accessTokenEntity.setExpiresIn(Math.toIntExact(expire));
return accessTokenEntity;
}
@Override
public boolean isAccessTokenExpired(String authCorpId) {
return System.currentTimeMillis() > authCorpAccessTokenExpireTimeMap.get(authCorpId);
}
@Override
@Override
public void expireAccessToken(String authCorpId) {
authCorpAccessTokenMap.remove(authCorpId);
authCorpAccessTokenExpireTimeMap.remove(authCorpId);
}
@Override
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
authCorpAccessTokenMap.put(authCorpId, accessToken);
// 预留200秒的时间
@ -246,6 +282,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
}
}
@Override
public void expireAuthCorpJsApiTicket(String authCorpId) {
this.authCorpJsApiTicketMap.remove(authCorpId);
this.authCorpJsApiTicketExpireTimeMap.remove(authCorpId);
}
@Override
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
// 应该根据不同的授权企业做区分
@ -269,6 +311,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
}
}
@Override
public void expireAuthSuiteJsApiTicket(String authCorpId) {
this.authSuiteJsApiTicketMap.remove(authCorpId);
this.authSuiteJsApiTicketExpireTimeMap.remove(authCorpId);
}
@Override
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
// 应该根据不同的授权企业做区分
@ -293,6 +341,16 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return providerToken;
}
@Override
public WxCpProviderToken getProviderTokenEntity() {
return null;
}
@Override
public void expireProviderToken() {
this.providerTokenExpiresTime = 0L;
}
public void setOauth2redirectUri(String oauth2redirectUri) {
this.oauth2redirectUri = oauth2redirectUri;
}
@ -343,6 +401,35 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
return this.tmpDirFile;
}
@Override
public Lock getProviderAccessTokenLock() {
return this.providerAccessTokenLocker
.computeIfAbsent(String.join(":", this.suiteId, this.corpId), key -> new ReentrantLock());
}
@Override
public Lock getSuiteAccessTokenLock() {
return this.suiteAccessTokenLocker.computeIfAbsent(this.suiteId, key -> new ReentrantLock());
}
@Override
public Lock getAccessTokenLock(String authCorpId) {
return this.accessTokenLocker
.computeIfAbsent(String.join(":", this.suiteId, authCorpId), key -> new ReentrantLock());
}
@Override
public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
return this.authCorpJsapiTicketLocker
.computeIfAbsent(String.join(":", this.suiteId, authCorpId), key -> new ReentrantLock());
}
@Override
public Lock getSuiteJsapiTicketLock(String authCorpId) {
return this.authSuiteJsapiTicketLocker
.computeIfAbsent(String.join(":", this.suiteId, authCorpId), key -> new ReentrantLock());
}
public void setTmpDirFile(File tmpDirFile) {
this.tmpDirFile = tmpDirFile;
}

View File

@ -6,12 +6,15 @@ 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.bean.WxCpProviderToken;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* 企业微信各种固定授权配置的Redisson存储实现
@ -66,6 +69,14 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
*/
private volatile String providerSecret;
// lock key
protected static final String LOCK_KEY = "wechat_tp_lock:";
protected static final String LOCKER_PROVIDER_ACCESS_TOKEN = "providerAccessTokenLock";
protected static final String LOCKER_SUITE_ACCESS_TOKEN = "suiteAccessTokenLock";
protected static final String LOCKER_ACCESS_TOKEN = "accessTokenLock";
protected static final String LOCKER_CORP_JSAPI_TICKET = "corpJsapiTicketLock";
protected static final String LOCKER_SUITE_JSAPI_TICKET = "suiteJsapiTicketLock";
@Override
public void setBaseApiUrl(String baseUrl) {
this.baseApiUrl = baseUrl;
@ -88,6 +99,20 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
}
@Override
public WxAccessToken getSuiteAccessTokenEntity() {
String suiteAccessToken = wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
Long expireIn = wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey));
if (StringUtils.isBlank(suiteAccessToken) || expireIn == null || expireIn == 0 || expireIn == -2) {
return new WxAccessToken();
}
WxAccessToken suiteAccessTokenEntity = new WxAccessToken();
suiteAccessTokenEntity.setAccessToken(suiteAccessToken);
suiteAccessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expireIn), 0));
return suiteAccessTokenEntity;
}
@Override
public boolean isSuiteAccessTokenExpired() {
//remain time to live in seconds, or key not exist
@ -185,6 +210,20 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
}
@Override
public WxAccessToken getAccessTokenEntity(String authCorpId) {
String accessToken = wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
Long expire = wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey);
if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
return new WxAccessToken();
}
WxAccessToken accessTokenEntity = new WxAccessToken();
accessTokenEntity.setAccessToken(accessToken);
accessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
return accessTokenEntity;
}
@Override
public boolean isAccessTokenExpired(String authCorpId) {
//没有设置或者TTL为0都是过期
@ -192,6 +231,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
}
@Override
public void expireAccessToken(String authCorpId) {
wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
}
@Override
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
@ -213,6 +257,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
}
@Override
public void expireAuthCorpJsApiTicket(String authCorpId) {
wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
}
@Override
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
@ -235,6 +284,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
}
@Override
public void expireAuthSuiteJsApiTicket(String authCorpId) {
wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
}
@Override
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
@ -257,6 +311,25 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
return wxRedisOps.getValue(keyWithPrefix(providerTokenKey));
}
@Override
public WxCpProviderToken getProviderTokenEntity() {
String providerToken = wxRedisOps.getValue(keyWithPrefix(providerTokenKey));
Long expire = wxRedisOps.getExpire(keyWithPrefix(providerTokenKey));
if (StringUtils.isBlank(providerToken) || expire == null || expire == 0 || expire == -2) {
return new WxCpProviderToken();
}
WxCpProviderToken wxCpProviderToken = new WxCpProviderToken();
wxCpProviderToken.setProviderAccessToken(providerToken);
wxCpProviderToken.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
return wxCpProviderToken;
}
@Override
public void expireProviderToken() {
wxRedisOps.expire(keyWithPrefix(providerTokenKey), 0, TimeUnit.SECONDS);
}
/**
* 网络代理相关
@ -286,6 +359,37 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
return tmpDirFile;
}
@Override
public Lock getProviderAccessTokenLock() {
return getLockByKey(String.join(":", this.corpId, LOCKER_PROVIDER_ACCESS_TOKEN));
}
@Override
public Lock getSuiteAccessTokenLock() {
return getLockByKey(LOCKER_SUITE_ACCESS_TOKEN);
}
@Override
public Lock getAccessTokenLock(String authCorpId) {
return getLockByKey(String.join(":", authCorpId, LOCKER_ACCESS_TOKEN));
}
@Override
public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
return getLockByKey(String.join(":", authCorpId, LOCKER_CORP_JSAPI_TICKET));
}
@Override
public Lock getSuiteJsapiTicketLock(String authCorpId) {
return getLockByKey(String.join(":", authCorpId, LOCKER_SUITE_JSAPI_TICKET));
}
private Lock getLockByKey(String key) {
// 最终key的模式(keyPrefix:)wechat_tp_lock:suiteId:(authCorpId):lockKey
// 其中keyPrefix目前不支持外部配置authCorpId只有涉及到corpAccessToken, suiteJsapiTicket, authCorpJsapiTicket时才会拼上
return this.wxRedisOps.getLock(String.join(":", keyWithPrefix(LOCK_KEY + this.suiteId), key));
}
@Override
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
return this.apacheHttpClientBuilder;

View File

@ -1,6 +1,7 @@
package me.chanjar.weixin.cp.tp.service;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
@ -53,6 +54,20 @@ public interface WxCpTpService {
*/
String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException;
/**
* 获取suite_access_token和剩余过期时间, 不强制刷新suite_access_token
* @return suite access token and the remaining expiration time
*/
WxAccessToken getSuiteAccessTokenEntity() throws WxErrorException;
/**
* 获取suite_access_token和剩余过期时间, 支持强制刷新suite_access_token
* @param forceRefresh 是否调用微信服务器强制刷新token
* @return suite access token and the remaining expiration time
* @throws WxErrorException
*/
WxAccessToken getSuiteAccessTokenEntity(boolean forceRefresh) throws WxErrorException;
/**
* 获得suite_ticket,不强制刷新suite_ticket
*
@ -115,6 +130,15 @@ public interface WxCpTpService {
*/
String getSuiteJsApiTicket(String authCorpId) throws WxErrorException;
/**
* 获取应用的 jsapi ticket 支持强制刷新
* @param authCorpId
* @param forceRefresh
* @return
* @throws WxErrorException
*/
String getSuiteJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException;
/**
* 小程序登录凭证校验
*
@ -134,6 +158,16 @@ public interface WxCpTpService {
*/
WxAccessToken getCorpToken(String authCorpId, String permanentCode) throws WxErrorException;
/**
* 获取企业凭证, 支持强制刷新
* @param authCorpId
* @param permanentCode
* @param forceRefresh
* @return
* @throws WxErrorException
*/
WxAccessToken getCorpToken(String authCorpId, String permanentCode, boolean forceRefresh) throws WxErrorException;
/**
* 获取企业永久授权码 .
*
@ -173,13 +207,13 @@ public interface WxCpTpService {
/**
* <pre>
* 获取预授权链接测试环境下使用
* </pre>
* @param redirectUri 授权完成后的回调网址
* @param state a-zA-Z0-9的参数值不超过128个字节用于第三方自行校验session防止跨域攻击
* @param authType 授权类型0 正式授权 1 测试授权
* @return pre auth url
* @throws WxErrorException the wx error exception
* @link https ://work.weixin.qq.com/api/doc/90001/90143/90602
* </pre>
*/
String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException;
@ -202,6 +236,15 @@ public interface WxCpTpService {
*/
String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException;
/**
* 获取授权企业的 jsapi ticket, 支持强制刷新
* @param authCorpId
* @param forceRefresh
* @return
* @throws WxErrorException
*/
String getAuthCorpJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException;
/**
* 当本Service没有实现某个API的时候可以用这个针对所有微信API中的GET请求.
*
@ -335,6 +378,21 @@ public interface WxCpTpService {
*/
String getWxCpProviderToken() throws WxErrorException;
/**
* 获取服务商providerToken和剩余过期时间
* @return
* @throws WxErrorException
*/
WxCpProviderToken getWxCpProviderTokenEntity() throws WxErrorException;
/**
* 获取服务商providerToken和剩余过期时间支持强制刷新
* @param forceRefresh
* @return
* @throws WxErrorException
*/
WxCpProviderToken getWxCpProviderTokenEntity(boolean forceRefresh) throws WxErrorException;
/**
* get contact service
*
@ -415,4 +473,22 @@ public interface WxCpTpService {
*/
WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException;
/**
* 创建机构级jsApiTicket签名
* 详情参见企业微信第三方应用开发文档https://work.weixin.qq.com/api/doc/90001/90144/90539
* @param url 调用JS接口页面的完整URL
* @param authCorpId
* @return
*/
WxJsapiSignature createAuthCorpJsApiTicketSignature(String url, String authCorpId) throws WxErrorException;
/**
* 创建应用级jsapiTicket签名
* 详情参见企业微信第三方应用开发文档https://work.weixin.qq.com/api/doc/90001/90144/90539
* @param url 调用JS接口页面的完整URL
* @param authCorpId
* @return
*/
WxJsapiSignature createSuiteJsApiTicketSignature(String url, String authCorpId) throws WxErrorException;
}

View File

@ -6,6 +6,7 @@ import com.google.gson.JsonObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
import me.chanjar.weixin.common.error.WxError;
@ -14,6 +15,7 @@ import me.chanjar.weixin.common.error.WxRuntimeException;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.DataUtils;
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;
@ -97,6 +99,17 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return getSuiteAccessToken(false);
}
@Override
public WxAccessToken getSuiteAccessTokenEntity() throws WxErrorException {
return this.getSuiteAccessTokenEntity(false);
}
@Override
public WxAccessToken getSuiteAccessTokenEntity(boolean forceRefresh) throws WxErrorException {
getSuiteAccessToken(forceRefresh);
return this.configStorage.getSuiteAccessTokenEntity();
}
@Override
public String getSuiteTicket() throws WxErrorException {
if (this.configStorage.isSuiteTicketExpired()) {
@ -150,6 +163,14 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return configStorage.getAuthSuiteJsApiTicket(authCorpId);
}
@Override
public String getSuiteJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAuthSuiteJsApiTicket(authCorpId);
}
return this.getSuiteJsApiTicket(authCorpId);
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException {
if (this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId)) {
@ -169,7 +190,15 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
throw new WxErrorException(WxError.fromJson(resp));
}
}
return configStorage.getProviderToken();
return configStorage.getAuthCorpJsApiTicket(authCorpId);
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAuthCorpJsApiTicket(authCorpId);
}
return this.getAuthCorpJsApiTicket(authCorpId);
}
@Override
@ -193,6 +222,16 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return WxAccessToken.fromJson(result);
}
@Override
public WxAccessToken getCorpToken(String authCorpId, String permanentCode, boolean forceRefresh)
throws WxErrorException {
if (this.configStorage.isAccessTokenExpired(authCorpId) || forceRefresh) {
WxAccessToken corpToken = this.getCorpToken(authCorpId, permanentCode);
this.configStorage.updateAccessToken(authCorpId, corpToken.getAccessToken(), corpToken.getExpiresIn());
}
return this.configStorage.getAccessTokenEntity(authCorpId);
}
@Override
public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
@ -426,6 +465,19 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return configStorage.getProviderToken();
}
@Override
public WxCpProviderToken getWxCpProviderTokenEntity() throws WxErrorException {
return this.getWxCpProviderTokenEntity(false);
}
@Override
public WxCpProviderToken getWxCpProviderTokenEntity(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireProviderToken();
}
this.getWxCpProviderToken();
return this.configStorage.getProviderTokenEntity();
}
@Override
public WxCpTpContactService getWxCpTpContactService() {
@ -486,4 +538,30 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
return WxCpTpAdmin.fromJson(result);
}
@Override
public WxJsapiSignature createAuthCorpJsApiTicketSignature(String url, String authCorpId) throws WxErrorException {
return doCreateWxJsapiSignature(url, authCorpId, this.getAuthCorpJsApiTicket(authCorpId));
}
@Override
public WxJsapiSignature createSuiteJsApiTicketSignature(String url, String authCorpId) throws WxErrorException {
return doCreateWxJsapiSignature(url, authCorpId, this.getSuiteJsApiTicket(authCorpId));
}
private WxJsapiSignature doCreateWxJsapiSignature(String url, String authCorpId, String jsapiTicket) {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = RandomUtils.getRandomStr();
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);
jsapiSignature.setAppId(authCorpId);
return jsapiSignature;
}
}

View File

@ -1,12 +1,169 @@
package me.chanjar.weixin.cp.tp.service.impl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
import java.util.concurrent.locks.Lock;
/**
* <pre>
* 默认接口实现类使用apache httpclient实现
* Created by zhenjun cai.
* </pre>
* <pre>
* 实现分布式锁基于WxCpTpRedissonConfigImpl存储引擎实现类版本
* 主要封装了suiteAccessTokencorpAccessTokensuiteJsapiTicketcorpJsapiTicket等的获取方法
* Updated by zhangq <zhangq002@gmail.com> on 2021-02-13
* </pre>
*
* @author zhenjun cai
* @author zhangq
*/
@Slf4j
public class WxCpTpServiceImpl extends WxCpTpServiceApacheHttpClientImpl {
@Override
public WxAccessToken getSuiteAccessTokenEntity() throws WxErrorException {
return this.getSuiteAccessTokenEntity(false);
}
@Override
public WxAccessToken getSuiteAccessTokenEntity(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getSuiteAccessTokenEntity();
}
// 此处configStorage推荐使用WxCpTpRedissonConfigImpl实现类
// 它底层采用了redisson提供的并发锁会自动续期无需担心异常中断导致的死锁问题以及锁提前释放导致的并发问题
Lock lock = this.configStorage.getSuiteAccessTokenLock();
lock.lock();
try {
if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getSuiteAccessTokenEntity();
}
super.getSuiteAccessToken(forceRefresh);
return this.configStorage.getSuiteAccessTokenEntity();
} finally {
lock.unlock();
}
}
/**
* 复写父类方法使其支持并发锁模式
* @param forceRefresh
* @return
* @throws WxErrorException
*/
@Override
public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
WxAccessToken suiteToken = this.getSuiteAccessTokenEntity(forceRefresh);
return suiteToken.getAccessToken();
}
@Override
public WxCpProviderToken getWxCpProviderTokenEntity() throws WxErrorException {
return this.getWxCpProviderTokenEntity(false);
}
@Override
public WxCpProviderToken getWxCpProviderTokenEntity(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isProviderTokenExpired() && !forceRefresh) {
return this.configStorage.getProviderTokenEntity();
}
Lock lock = this.configStorage.getProviderAccessTokenLock();
lock.lock();
try {
if (!this.configStorage.isProviderTokenExpired() && !forceRefresh) {
return this.configStorage.getProviderTokenEntity();
}
return super.getWxCpProviderTokenEntity(forceRefresh);
} finally {
lock.unlock();
}
}
@Override
public WxAccessToken getCorpToken(String authCorpId, String permanentCode) throws WxErrorException {
return this.getCorpToken(authCorpId, permanentCode, false);
}
@Override
public WxAccessToken getCorpToken(String authCorpId, String permanentCode, boolean forceRefresh)
throws WxErrorException {
if (!this.configStorage.isAccessTokenExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAccessTokenEntity(authCorpId);
}
Lock lock = this.configStorage.getAccessTokenLock(authCorpId);
lock.lock();
try {
if (!this.configStorage.isAccessTokenExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAccessTokenEntity(authCorpId);
}
WxAccessToken accessToken = super.getCorpToken(authCorpId, permanentCode);
this.configStorage.updateAccessToken(authCorpId, accessToken.getAccessToken(), accessToken.getExpiresIn());
return accessToken;
} finally {
lock.unlock();
}
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException {
return this.getAuthCorpJsApiTicket(authCorpId, false);
}
@Override
public String getAuthCorpJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAuthCorpJsApiTicket(authCorpId);
}
Lock lock = this.configStorage.getAuthCorpJsapiTicketLock(authCorpId);
lock.lock();
try {
if (!this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAuthCorpJsApiTicket(authCorpId);
}
if (forceRefresh) {
this.configStorage.expireAuthCorpJsApiTicket(authCorpId);
}
return super.getAuthCorpJsApiTicket(authCorpId);
} finally {
lock.unlock();
}
}
@Override
public String getSuiteJsApiTicket(String authCorpId) throws WxErrorException {
return this.getSuiteJsApiTicket(authCorpId, false);
}
@Override
public String getSuiteJsApiTicket(String authCorpId, boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isAuthSuiteJsApiTicketExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAuthSuiteJsApiTicket(authCorpId);
}
Lock lock = this.configStorage.getSuiteJsapiTicketLock(authCorpId);
lock.lock();
try {
if (!this.configStorage.isAuthSuiteJsApiTicketExpired(authCorpId) && !forceRefresh) {
return this.configStorage.getAuthSuiteJsApiTicket(authCorpId);
}
if (forceRefresh) {
this.configStorage.expireAuthSuiteJsApiTicket(authCorpId);
}
return super.getSuiteJsApiTicket(authCorpId);
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,25 @@
package me.chanjar.weixin.cp.config.impl;
import me.chanjar.weixin.common.bean.WxAccessToken;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.concurrent.TimeUnit;
public class WxCpTpDefaultConfigImplTest {
@Test
public void testGetSuiteAccessTokenEntity() throws InterruptedException {
final String testAccessToken = "5O_32IEDOib99RliaF301vzGiZaAJw3CsaNb4QXyQ-07KJ0UDQ8nxq9vs66jNLIZ4TvYs3QFlYZag1WfG8i4gNu_dYQj2Ff89xznZPquv7EFMAZha_faYZrE0uCFRqkV";
final long testExpireTime = 7200L;
final long restTime = 10L;
WxCpTpDefaultConfigImpl storage = new WxCpTpDefaultConfigImpl();
storage.setSuiteAccessToken(testAccessToken);
storage.setSuiteAccessTokenExpiresTime(System.currentTimeMillis() + (testExpireTime - 200) * 1000L);
TimeUnit.SECONDS.sleep(restTime);
WxAccessToken accessToken = storage.getSuiteAccessTokenEntity();
Assert.assertEquals(accessToken.getAccessToken(), testAccessToken, "accessToken不一致");
Assert.assertTrue(accessToken.getExpiresIn() <= testExpireTime - restTime, "过期时间计算有误");
}
}

View File

@ -0,0 +1,113 @@
package me.chanjar.weixin.cp.tp.service.impl;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* 测试用参数请在自己的企业微信第三方开发者后台查找匹配
* 如果测试不过请检查redis中是否存在微信定期推送的suite_ticket
*
* @author zhangq <zhangq002@gmail.com>
*/
public class WxCpTpServiceImplTest {
public static final String API_URL = "https://qyapi.weixin.qq.com";
public static final String SUITE_ID = "xxxxxx";
public static final String SUITE_SECRET = "xxxxxx";
public static final String TOKEN = "xxxxxx";
public static final String AES_KEY = "xxxxxx";
public static final String PROVIDER_CORP_ID = "xxxxxx";
public static final String CORP_SECRET = "xxxxxx";
public static final String PROVIDER_SECRET = CORP_SECRET;
public static final String REDIS_ADDR = "redis://xxx.xxx.xxx.xxx:6379";
public static final String REDIS_PASSWD = "xxxxxx";
private static final String AUTH_CORP_ID = "xxxxxx";
private static final String PERMANENT_CODE = "xxxxxx";
private WxCpTpService wxCpTpService;
@BeforeMethod
public void setUp() {
wxCpTpService = new WxCpTpServiceImpl();
wxCpTpService.setWxCpTpConfigStorage(wxCpTpConfigStorage());
}
public WxCpTpConfigStorage wxCpTpConfigStorage() {
return WxCpTpRedissonConfigImpl.builder().baseApiUrl(API_URL).suiteId(SUITE_ID).suiteSecret(SUITE_SECRET)
.token(TOKEN).aesKey(AES_KEY).corpId(PROVIDER_CORP_ID).corpSecret(CORP_SECRET).providerSecret(PROVIDER_SECRET)
.wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
}
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress(REDIS_ADDR).setConnectTimeout(10 * 1000).setDatabase(6)
.setPassword(REDIS_PASSWD).setConnectionMinimumIdleSize(2).setConnectionPoolSize(2);
return Redisson.create(config);
}
@Test
public void testGetSuiteAccessTokenEntity() throws WxErrorException {
wxCpTpService.getWxCpTpConfigStorage().expireSuiteAccessToken();
WxAccessToken suiteAccessTokenEntity = wxCpTpService.getSuiteAccessTokenEntity(true);
System.out.println("suiteAccessTokenEntity:" + suiteAccessTokenEntity);
Assert.assertTrue(
StringUtils.isNotBlank(suiteAccessTokenEntity.getAccessToken()) && suiteAccessTokenEntity.getExpiresIn() > 0);
suiteAccessTokenEntity = wxCpTpService.getSuiteAccessTokenEntity();
System.out.println("suiteAccessTokenEntity:" + suiteAccessTokenEntity);
Assert.assertTrue(
StringUtils.isNotBlank(suiteAccessTokenEntity.getAccessToken()) && suiteAccessTokenEntity.getExpiresIn() > 0);
}
@Test
public void testGetWxCpProviderTokenEntity() throws WxErrorException {
wxCpTpService.getWxCpTpConfigStorage().expireProviderToken();
WxCpProviderToken providerToken = wxCpTpService.getWxCpProviderTokenEntity(true);
System.out.println("providerToken:" + providerToken);
Assert
.assertTrue(StringUtils.isNotBlank(providerToken.getProviderAccessToken()) && providerToken.getExpiresIn() > 0);
providerToken = wxCpTpService.getWxCpProviderTokenEntity();
System.out.println("providerToken:" + providerToken);
Assert
.assertTrue(StringUtils.isNotBlank(providerToken.getProviderAccessToken()) && providerToken.getExpiresIn() > 0);
}
@Test
public void testGetCorpToken() throws WxErrorException {
wxCpTpService.getWxCpTpConfigStorage().expireAccessToken(AUTH_CORP_ID);
WxAccessToken accessToken = wxCpTpService.getCorpToken(AUTH_CORP_ID, PERMANENT_CODE, true);
System.out.println("accessToken:" + accessToken);
accessToken = wxCpTpService.getCorpToken(AUTH_CORP_ID, PERMANENT_CODE);
System.out.println("accessToken:" + accessToken);
}
@Test
public void testGetAuthCorpJsApiTicket() throws WxErrorException {
wxCpTpService.getWxCpTpConfigStorage().expireAuthCorpJsApiTicket(AUTH_CORP_ID);
String authCorpJsApiTicket = wxCpTpService.getAuthCorpJsApiTicket(AUTH_CORP_ID, true);
System.out.println("authCorpJsApiTicket:" + authCorpJsApiTicket);
authCorpJsApiTicket = wxCpTpService.getAuthCorpJsApiTicket(AUTH_CORP_ID);
System.out.println("authCorpJsApiTicket:" + authCorpJsApiTicket);
}
@Test
public void testGetSuiteJsApiTicket() throws WxErrorException {
wxCpTpService.getWxCpTpConfigStorage().expireAuthSuiteJsApiTicket(AUTH_CORP_ID);
String suiteJsApiTicket = wxCpTpService.getSuiteJsApiTicket(AUTH_CORP_ID, true);
System.out.println("suiteJsApiTicket:" + suiteJsApiTicket);
suiteJsApiTicket = wxCpTpService.getSuiteJsApiTicket(AUTH_CORP_ID);
System.out.println("suiteJsApiTicket:" + suiteJsApiTicket);
}
}

View File

@ -5,8 +5,9 @@
<classes>
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/>
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest"/>
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest"/>
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest"/>
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImplTest"/>
<class name="me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImplTest"/>
</classes>
</test>