mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-08-24 16:18:51 +08:00
🎨 #2000 【企业微信】第三方应用增加基于分布式并发锁获取各种token和ticket的版本
This commit is contained in:
parent
3bb918d125
commit
9d3c11f552
@ -2,8 +2,10 @@ package me.chanjar.weixin.cp.config;
|
|||||||
|
|
||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||||
|
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 微信客户端(第三方应用)配置存储
|
* 微信客户端(第三方应用)配置存储
|
||||||
@ -30,6 +32,11 @@ public interface WxCpTpConfigStorage {
|
|||||||
* 第三方应用的suite access token相关
|
* 第三方应用的suite access token相关
|
||||||
*/
|
*/
|
||||||
String getSuiteAccessToken();
|
String getSuiteAccessToken();
|
||||||
|
/**
|
||||||
|
* 获取suite_access_token和剩余过期时间
|
||||||
|
* @return suite access token and the remaining expiration time
|
||||||
|
*/
|
||||||
|
WxAccessToken getSuiteAccessTokenEntity();
|
||||||
boolean isSuiteAccessTokenExpired();
|
boolean isSuiteAccessTokenExpired();
|
||||||
//强制将suite access token过期掉.
|
//强制将suite access token过期掉.
|
||||||
void expireSuiteAccessToken();
|
void expireSuiteAccessToken();
|
||||||
@ -71,7 +78,9 @@ public interface WxCpTpConfigStorage {
|
|||||||
* 授权企业的access token相关
|
* 授权企业的access token相关
|
||||||
*/
|
*/
|
||||||
String getAccessToken(String authCorpId);
|
String getAccessToken(String authCorpId);
|
||||||
|
WxAccessToken getAccessTokenEntity(String authCorpId);
|
||||||
boolean isAccessTokenExpired(String authCorpId);
|
boolean isAccessTokenExpired(String authCorpId);
|
||||||
|
void expireAccessToken(String authCorpId);
|
||||||
void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds);
|
void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +88,7 @@ public interface WxCpTpConfigStorage {
|
|||||||
*/
|
*/
|
||||||
String getAuthCorpJsApiTicket(String authCorpId);
|
String getAuthCorpJsApiTicket(String authCorpId);
|
||||||
boolean isAuthCorpJsApiTicketExpired(String authCorpId);
|
boolean isAuthCorpJsApiTicketExpired(String authCorpId);
|
||||||
|
void expireAuthCorpJsApiTicket(String authCorpId);
|
||||||
void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);
|
void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,12 +96,16 @@ public interface WxCpTpConfigStorage {
|
|||||||
*/
|
*/
|
||||||
String getAuthSuiteJsApiTicket(String authCorpId);
|
String getAuthSuiteJsApiTicket(String authCorpId);
|
||||||
boolean isAuthSuiteJsApiTicketExpired(String authCorpId);
|
boolean isAuthSuiteJsApiTicketExpired(String authCorpId);
|
||||||
|
void expireAuthSuiteJsApiTicket(String authCorpId);
|
||||||
void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);;
|
void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);;
|
||||||
|
|
||||||
boolean isProviderTokenExpired();
|
boolean isProviderTokenExpired();
|
||||||
void updateProviderToken(String providerToken, int expiredInSeconds);
|
void updateProviderToken(String providerToken, int expiredInSeconds);
|
||||||
|
|
||||||
String getProviderToken();
|
String getProviderToken();
|
||||||
|
WxCpProviderToken getProviderTokenEntity();
|
||||||
|
// 强制过期
|
||||||
|
void expireProviderToken();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络代理相关
|
* 网络代理相关
|
||||||
@ -108,4 +122,9 @@ public interface WxCpTpConfigStorage {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
File getTmpDirFile();
|
File getTmpDirFile();
|
||||||
|
|
||||||
|
Lock getProviderAccessTokenLock();
|
||||||
|
Lock getSuiteAccessTokenLock();
|
||||||
|
Lock getAccessTokenLock(String authCorpId);
|
||||||
|
Lock getAuthCorpJsapiTicketLock(String authCorpId);
|
||||||
|
Lock getSuiteJsapiTicketLock(String authCorpId);
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,18 @@ package me.chanjar.weixin.cp.config.impl;
|
|||||||
|
|
||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
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.config.WxCpTpConfigStorage;
|
||||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
|
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
|
||||||
@ -59,6 +64,12 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
|
|
||||||
private volatile String baseApiUrl;
|
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
|
@Override
|
||||||
public void setBaseApiUrl(String baseUrl) {
|
public void setBaseApiUrl(String baseUrl) {
|
||||||
@ -78,6 +89,15 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
return this.suiteAccessToken;
|
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) {
|
public void setSuiteAccessToken(String suiteAccessToken) {
|
||||||
this.suiteAccessToken = suiteAccessToken;
|
this.suiteAccessToken = suiteAccessToken;
|
||||||
}
|
}
|
||||||
@ -218,11 +238,27 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
return authCorpAccessTokenMap.get(authCorpId);
|
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
|
@Override
|
||||||
public boolean isAccessTokenExpired(String authCorpId) {
|
public boolean isAccessTokenExpired(String authCorpId) {
|
||||||
return System.currentTimeMillis() > authCorpAccessTokenExpireTimeMap.get(authCorpId);
|
return System.currentTimeMillis() > authCorpAccessTokenExpireTimeMap.get(authCorpId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAccessToken(String authCorpId) {
|
||||||
|
authCorpAccessTokenMap.remove(authCorpId);
|
||||||
|
authCorpAccessTokenExpireTimeMap.remove(authCorpId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
||||||
authCorpAccessTokenMap.put(authCorpId, accessToken);
|
authCorpAccessTokenMap.put(authCorpId, accessToken);
|
||||||
@ -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
|
@Override
|
||||||
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
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
|
@Override
|
||||||
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
||||||
// 应该根据不同的授权企业做区分
|
// 应该根据不同的授权企业做区分
|
||||||
@ -293,6 +341,16 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
return providerToken;
|
return providerToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpProviderToken getProviderTokenEntity() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireProviderToken() {
|
||||||
|
this.providerTokenExpiresTime = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
public void setOauth2redirectUri(String oauth2redirectUri) {
|
public void setOauth2redirectUri(String oauth2redirectUri) {
|
||||||
this.oauth2redirectUri = oauth2redirectUri;
|
this.oauth2redirectUri = oauth2redirectUri;
|
||||||
}
|
}
|
||||||
@ -343,6 +401,35 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
return this.tmpDirFile;
|
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) {
|
public void setTmpDirFile(File tmpDirFile) {
|
||||||
this.tmpDirFile = tmpDirFile;
|
this.tmpDirFile = tmpDirFile;
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,15 @@ import lombok.NonNull;
|
|||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
import me.chanjar.weixin.common.redis.WxRedisOps;
|
import me.chanjar.weixin.common.redis.WxRedisOps;
|
||||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
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.config.WxCpTpConfigStorage;
|
||||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 企业微信各种固定、授权配置的Redisson存储实现
|
* 企业微信各种固定、授权配置的Redisson存储实现
|
||||||
@ -66,6 +69,14 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|
|||||||
*/
|
*/
|
||||||
private volatile String providerSecret;
|
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
|
@Override
|
||||||
public void setBaseApiUrl(String baseUrl) {
|
public void setBaseApiUrl(String baseUrl) {
|
||||||
this.baseApiUrl = baseUrl;
|
this.baseApiUrl = baseUrl;
|
||||||
@ -88,6 +99,20 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|
|||||||
return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
|
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
|
@Override
|
||||||
public boolean isSuiteAccessTokenExpired() {
|
public boolean isSuiteAccessTokenExpired() {
|
||||||
//remain time to live in seconds, or key not exist
|
//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);
|
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
|
@Override
|
||||||
public boolean isAccessTokenExpired(String authCorpId) {
|
public boolean isAccessTokenExpired(String authCorpId) {
|
||||||
//没有设置或者TTL为0,都是过期
|
//没有设置或者TTL为0,都是过期
|
||||||
@ -192,6 +231,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|
|||||||
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAccessToken(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
|
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;
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAuthCorpJsApiTicket(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
||||||
@ -235,6 +284,11 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|
|||||||
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAuthSuiteJsApiTicket(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
||||||
@ -257,6 +311,25 @@ public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializab
|
|||||||
return wxRedisOps.getValue(keyWithPrefix(providerTokenKey));
|
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;
|
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
|
@Override
|
||||||
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
|
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
|
||||||
return this.apacheHttpClientBuilder;
|
return this.apacheHttpClientBuilder;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package me.chanjar.weixin.cp.tp.service;
|
package me.chanjar.weixin.cp.tp.service;
|
||||||
|
|
||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
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.error.WxErrorException;
|
||||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
|
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
|
||||||
@ -53,6 +54,20 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException;
|
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
|
* 获得suite_ticket,不强制刷新suite_ticket
|
||||||
*
|
*
|
||||||
@ -115,6 +130,15 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
String getSuiteJsApiTicket(String authCorpId) throws WxErrorException;
|
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;
|
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>
|
||||||
* 获取预授权链接,测试环境下使用
|
* 获取预授权链接,测试环境下使用
|
||||||
|
* </pre>
|
||||||
* @param redirectUri 授权完成后的回调网址
|
* @param redirectUri 授权完成后的回调网址
|
||||||
* @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击
|
* @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击
|
||||||
* @param authType 授权类型:0 正式授权, 1 测试授权。
|
* @param authType 授权类型:0 正式授权, 1 测试授权。
|
||||||
* @return pre auth url
|
* @return pre auth url
|
||||||
* @throws WxErrorException the wx error exception
|
* @throws WxErrorException the wx error exception
|
||||||
* @link https ://work.weixin.qq.com/api/doc/90001/90143/90602
|
* @link https ://work.weixin.qq.com/api/doc/90001/90143/90602
|
||||||
* </pre>
|
|
||||||
*/
|
*/
|
||||||
String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException;
|
String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException;
|
||||||
|
|
||||||
@ -202,6 +236,15 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException;
|
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请求.
|
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
|
||||||
*
|
*
|
||||||
@ -335,6 +378,21 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
String getWxCpProviderToken() throws WxErrorException;
|
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
|
* get contact service
|
||||||
*
|
*
|
||||||
@ -415,4 +473,22 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException;
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.google.gson.JsonObject;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
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.enums.WxType;
|
||||||
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
|
import me.chanjar.weixin.common.error.WxCpErrorMsgEnum;
|
||||||
import me.chanjar.weixin.common.error.WxError;
|
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.StandardSessionManager;
|
||||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
import me.chanjar.weixin.common.util.DataUtils;
|
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.crypto.SHA1;
|
||||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||||
import me.chanjar.weixin.common.util.http.RequestHttp;
|
import me.chanjar.weixin.common.util.http.RequestHttp;
|
||||||
@ -97,6 +99,17 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return getSuiteAccessToken(false);
|
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
|
@Override
|
||||||
public String getSuiteTicket() throws WxErrorException {
|
public String getSuiteTicket() throws WxErrorException {
|
||||||
if (this.configStorage.isSuiteTicketExpired()) {
|
if (this.configStorage.isSuiteTicketExpired()) {
|
||||||
@ -150,6 +163,14 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return configStorage.getAuthSuiteJsApiTicket(authCorpId);
|
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
|
@Override
|
||||||
public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException {
|
public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException {
|
||||||
if (this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId)) {
|
if (this.configStorage.isAuthCorpJsApiTicketExpired(authCorpId)) {
|
||||||
@ -169,7 +190,15 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
throw new WxErrorException(WxError.fromJson(resp));
|
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
|
@Override
|
||||||
@ -193,6 +222,16 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return WxAccessToken.fromJson(result);
|
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
|
@Override
|
||||||
public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException {
|
public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException {
|
||||||
JsonObject jsonObject = new JsonObject();
|
JsonObject jsonObject = new JsonObject();
|
||||||
@ -426,6 +465,19 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return configStorage.getProviderToken();
|
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
|
@Override
|
||||||
public WxCpTpContactService getWxCpTpContactService() {
|
public WxCpTpContactService getWxCpTpContactService() {
|
||||||
@ -486,4 +538,30 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return WxCpTpAdmin.fromJson(result);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,169 @@
|
|||||||
package me.chanjar.weixin.cp.tp.service.impl;
|
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>
|
* <pre>
|
||||||
* 默认接口实现类,使用apache httpclient实现
|
* 默认接口实现类,使用apache httpclient实现
|
||||||
* Created by zhenjun cai.
|
* Created by zhenjun cai.
|
||||||
* </pre>
|
* </pre>
|
||||||
|
* <pre>
|
||||||
|
* 实现分布式锁(基于WxCpTpRedissonConfigImpl存储引擎实现类)版本;
|
||||||
|
* 主要封装了suiteAccessToken,corpAccessToken,suiteJsapiTicket,corpJsapiTicket等的获取方法
|
||||||
|
* Updated by zhangq <zhangq002@gmail.com> on 2021-02-13
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author zhenjun cai
|
* @author zhenjun cai
|
||||||
|
* @author zhangq
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class WxCpTpServiceImpl extends WxCpTpServiceApacheHttpClientImpl {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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, "过期时间计算有误");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,9 @@
|
|||||||
<classes>
|
<classes>
|
||||||
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/>
|
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/>
|
||||||
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest"/>
|
<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.api.WxCpMessageRouterTest"/>
|
||||||
|
<class name="me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImplTest"/>
|
||||||
|
<class name="me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImplTest"/>
|
||||||
</classes>
|
</classes>
|
||||||
</test>
|
</test>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user