mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-10-30 18:19:04 +08:00
refactor: mp 调整access token刷新策略
This commit is contained in:
@ -5,7 +5,7 @@
|
|||||||
<token>企业号应用Token</token>
|
<token>企业号应用Token</token>
|
||||||
<aesKey>企业号应用EncodingAESKey</aesKey>
|
<aesKey>企业号应用EncodingAESKey</aesKey>
|
||||||
<accessToken>可以不填写</accessToken>
|
<accessToken>可以不填写</accessToken>
|
||||||
<expiresIn>可以不填写</expiresIn>
|
<expiresTime>可以不填写</expiresTime>
|
||||||
<userId>企业号通讯录里的某个userid</userId>
|
<userId>企业号通讯录里的某个userid</userId>
|
||||||
<departmentId>企业号通讯录的某个部门id</departmentId>
|
<departmentId>企业号通讯录的某个部门id</departmentId>
|
||||||
<tagId>企业号通讯录里的某个tagid</tagId>
|
<tagId>企业号通讯录里的某个tagid</tagId>
|
||||||
|
|||||||
@ -9,6 +9,15 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
|
|||||||
*/
|
*/
|
||||||
public interface WxMpConfigStorage {
|
public interface WxMpConfigStorage {
|
||||||
|
|
||||||
|
public String getAccessToken();
|
||||||
|
|
||||||
|
public boolean isAccessTokenExpired();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制将access token过期掉
|
||||||
|
*/
|
||||||
|
public void expireAccessToken();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应该是线程安全的
|
* 应该是线程安全的
|
||||||
* @param accessToken
|
* @param accessToken
|
||||||
@ -22,17 +31,30 @@ public interface WxMpConfigStorage {
|
|||||||
*/
|
*/
|
||||||
public void updateAccessToken(String accessToken, int expiresIn);
|
public void updateAccessToken(String accessToken, int expiresIn);
|
||||||
|
|
||||||
public String getAccessToken();
|
public String getJsapiTicket();
|
||||||
|
|
||||||
|
public boolean isJsapiTicketExpired();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制将jsapi ticket过期掉
|
||||||
|
*/
|
||||||
|
public void expireJsapiTicket();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应该是线程安全的
|
||||||
|
* @param jsapiTicket
|
||||||
|
*/
|
||||||
|
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);
|
||||||
|
|
||||||
public String getAppId();
|
public String getAppId();
|
||||||
|
|
||||||
public String getSecret();
|
public String getSecret();
|
||||||
|
|
||||||
public String getToken();
|
public String getToken();
|
||||||
|
|
||||||
public String getAesKey();
|
public String getAesKey();
|
||||||
|
|
||||||
public int getExpiresIn();
|
public long getExpiresTime();
|
||||||
|
|
||||||
public String getOauth2redirectUri();
|
public String getOauth2redirectUri();
|
||||||
|
|
||||||
@ -42,17 +64,7 @@ public interface WxMpConfigStorage {
|
|||||||
|
|
||||||
public String getHttp_proxy_username();
|
public String getHttp_proxy_username();
|
||||||
|
|
||||||
|
|
||||||
public String getHttp_proxy_password();
|
public String getHttp_proxy_password();
|
||||||
|
|
||||||
|
|
||||||
public String getJsapiTicket();
|
|
||||||
|
|
||||||
public boolean isJsapiTokenExpired();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应该是线程安全的
|
|
||||||
* @param jsapiTicket
|
|
||||||
*/
|
|
||||||
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
protected String token;
|
protected String token;
|
||||||
protected String accessToken;
|
protected String accessToken;
|
||||||
protected String aesKey;
|
protected String aesKey;
|
||||||
protected int expiresIn;
|
protected long expiresTime;
|
||||||
|
|
||||||
protected String oauth2redirectUri;
|
protected String oauth2redirectUri;
|
||||||
|
|
||||||
@ -28,17 +28,43 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
protected String jsapiTicket;
|
protected String jsapiTicket;
|
||||||
protected long jsapiTicketExpiresTime;
|
protected long jsapiTicketExpiresTime;
|
||||||
|
|
||||||
|
public String getAccessToken() {
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccessTokenExpired() {
|
||||||
|
return System.currentTimeMillis() > this.expiresTime;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void updateAccessToken(WxAccessToken accessToken) {
|
public synchronized void updateAccessToken(WxAccessToken accessToken) {
|
||||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void updateAccessToken(String accessToken, int expiresIn) {
|
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.expiresIn = expiresIn;
|
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessToken() {
|
public void expireAccessToken() {
|
||||||
return this.accessToken;
|
this.expiresTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJsapiTicket() {
|
||||||
|
return jsapiTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isJsapiTicketExpired() {
|
||||||
|
return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
|
||||||
|
this.jsapiTicket = jsapiTicket;
|
||||||
|
// 预留200秒的时间
|
||||||
|
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expireJsapiTicket() {
|
||||||
|
this.jsapiTicketExpiresTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAppId() {
|
public String getAppId() {
|
||||||
@ -53,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
return this.token;
|
return this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getExpiresIn() {
|
public long getExpiresTime() {
|
||||||
return this.expiresIn;
|
return this.expiresTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppId(String appId) {
|
public void setAppId(String appId) {
|
||||||
@ -81,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExpiresIn(int expiresIn) {
|
public void setExpiresTime(long expiresTime) {
|
||||||
this.expiresIn = expiresIn;
|
this.expiresTime = expiresTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -127,20 +153,6 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getJsapiTicket() {
|
|
||||||
return jsapiTicket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isJsapiTokenExpired() {
|
|
||||||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
|
|
||||||
this.jsapiTicket = jsapiTicket;
|
|
||||||
// 预留200秒的时间
|
|
||||||
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "WxMpInMemoryConfigStorage{" +
|
return "WxMpInMemoryConfigStorage{" +
|
||||||
@ -149,7 +161,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
|
|||||||
", token='" + token + '\'' +
|
", token='" + token + '\'' +
|
||||||
", accessToken='" + accessToken + '\'' +
|
", accessToken='" + accessToken + '\'' +
|
||||||
", aesKey='" + aesKey + '\'' +
|
", aesKey='" + aesKey + '\'' +
|
||||||
", expiresIn=" + expiresIn +
|
", expiresTime=" + expiresTime +
|
||||||
", http_proxy_host='" + http_proxy_host + '\'' +
|
", http_proxy_host='" + http_proxy_host + '\'' +
|
||||||
", http_proxy_port=" + http_proxy_port +
|
", http_proxy_port=" + http_proxy_port +
|
||||||
", http_proxy_username='" + http_proxy_username + '\'' +
|
", http_proxy_username='" + http_proxy_username + '\'' +
|
||||||
|
|||||||
@ -40,9 +40,10 @@ public interface WxMpService {
|
|||||||
|
|
||||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
|
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
|
||||||
* </pre>
|
* </pre>
|
||||||
|
* @return
|
||||||
* @throws me.chanjar.weixin.common.exception.WxErrorException
|
* @throws me.chanjar.weixin.common.exception.WxErrorException
|
||||||
*/
|
*/
|
||||||
public void accessTokenRefresh() throws WxErrorException;
|
public String getAccessToken() throws WxErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
|
|||||||
@ -44,12 +44,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
public class WxMpServiceImpl implements WxMpService {
|
public class WxMpServiceImpl implements WxMpService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局的是否正在刷新Access Token的flag
|
* 全局的是否正在刷新access token的锁
|
||||||
* true: 正在刷新
|
|
||||||
* false: 没有刷新
|
|
||||||
*/
|
*/
|
||||||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false);
|
protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局的是否正在刷新jsapi_ticket的锁
|
||||||
|
*/
|
||||||
|
protected static final Object GLOBAL_JSAPI_TICKET_REFRESH_LOCK = new Object();
|
||||||
|
|
||||||
protected WxMpConfigStorage wxMpConfigStorage;
|
protected WxMpConfigStorage wxMpConfigStorage;
|
||||||
|
|
||||||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();
|
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();
|
||||||
@ -66,52 +69,45 @@ public class WxMpServiceImpl implements WxMpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void accessTokenRefresh() throws WxErrorException {
|
public String getAccessToken() throws WxErrorException {
|
||||||
if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) {
|
if (wxMpConfigStorage.isAccessTokenExpired()) {
|
||||||
try {
|
synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
|
||||||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
|
if (wxMpConfigStorage.isAccessTokenExpired()) {
|
||||||
+ "&appid=" + wxMpConfigStorage.getAppId()
|
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
|
||||||
+ "&secret=" + wxMpConfigStorage.getSecret()
|
+ "&appid=" + wxMpConfigStorage.getAppId()
|
||||||
;
|
+ "&secret=" + wxMpConfigStorage.getSecret()
|
||||||
try {
|
;
|
||||||
HttpGet httpGet = new HttpGet(url);
|
try {
|
||||||
if (httpProxy != null) {
|
HttpGet httpGet = new HttpGet(url);
|
||||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
|
if (httpProxy != null) {
|
||||||
httpGet.setConfig(config);
|
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
|
||||||
|
httpGet.setConfig(config);
|
||||||
|
}
|
||||||
|
CloseableHttpClient httpclient = getHttpclient();
|
||||||
|
CloseableHttpResponse response = httpclient.execute(httpGet);
|
||||||
|
String resultContent = new BasicResponseHandler().handleResponse(response);
|
||||||
|
WxError error = WxError.fromJson(resultContent);
|
||||||
|
if (error.getErrorCode() != 0) {
|
||||||
|
throw new WxErrorException(error);
|
||||||
|
}
|
||||||
|
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
||||||
|
wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||||
|
} catch (ClientProtocolException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
CloseableHttpClient httpclient = getHttpclient();
|
|
||||||
CloseableHttpResponse response = httpclient.execute(httpGet);
|
|
||||||
String resultContent = new BasicResponseHandler().handleResponse(response);
|
|
||||||
WxError error = WxError.fromJson(resultContent);
|
|
||||||
if (error.getErrorCode() != 0) {
|
|
||||||
throw new WxErrorException(error);
|
|
||||||
}
|
|
||||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
|
||||||
wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
|
||||||
} catch (ClientProtocolException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 每隔100ms检查一下是否刷新完毕了
|
|
||||||
while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 刷新完毕了,就没他什么事儿了
|
|
||||||
}
|
}
|
||||||
|
return wxMpConfigStorage.getAccessToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getJsapiTicket() throws WxErrorException {
|
public String getJsapiTicket() throws WxErrorException {
|
||||||
if (wxMpConfigStorage.isJsapiTokenExpired()) {
|
if (wxMpConfigStorage.isJsapiTicketExpired()) {
|
||||||
synchronized (wxMpConfigStorage) {
|
synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) {
|
||||||
if (wxMpConfigStorage.isJsapiTokenExpired()) {
|
if (wxMpConfigStorage.isJsapiTicketExpired()) {
|
||||||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
|
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
|
||||||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
|
String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
|
||||||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
|
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
|
||||||
@ -440,10 +436,7 @@ public class WxMpServiceImpl implements WxMpService {
|
|||||||
* @throws WxErrorException
|
* @throws WxErrorException
|
||||||
*/
|
*/
|
||||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||||
if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) {
|
String accessToken = getAccessToken();
|
||||||
accessTokenRefresh();
|
|
||||||
}
|
|
||||||
String accessToken = wxMpConfigStorage.getAccessToken();
|
|
||||||
|
|
||||||
String uriWithAccessToken = uri;
|
String uriWithAccessToken = uri;
|
||||||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
|
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
|
||||||
@ -458,7 +451,8 @@ public class WxMpServiceImpl implements WxMpService {
|
|||||||
* 42001 access_token超时
|
* 42001 access_token超时
|
||||||
*/
|
*/
|
||||||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
|
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
|
||||||
accessTokenRefresh();
|
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
|
||||||
|
wxMpConfigStorage.expireAccessToken();
|
||||||
return execute(executor, uri, data);
|
return execute(executor, uri, data);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "WxMpOAuth2AccessToken{" +
|
return "WxMpOAuth2AccessToken{" +
|
||||||
"accessToken='" + accessToken + '\'' +
|
"accessToken='" + accessToken + '\'' +
|
||||||
", expiresIn=" + expiresIn +
|
", expiresTime=" + expiresIn +
|
||||||
", refreshToken='" + refreshToken + '\'' +
|
", refreshToken='" + refreshToken + '\'' +
|
||||||
", openId='" + openId + '\'' +
|
", openId='" + openId + '\'' +
|
||||||
", scope='" + scope + '\'' +
|
", scope='" + scope + '\'' +
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public class ApiTestModule implements Module {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
|
return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
|
||||||
+ ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]";
|
+ ", expiresTime=" + expiresTime + ", token=" + token + ", openId=" + openId + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public class WxMpBaseAPITest {
|
|||||||
public void testRefreshAccessToken() throws WxErrorException {
|
public void testRefreshAccessToken() throws WxErrorException {
|
||||||
WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
|
WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
|
||||||
String before = configStorage.getAccessToken();
|
String before = configStorage.getAccessToken();
|
||||||
wxService.accessTokenRefresh();
|
wxService.getAccessToken();
|
||||||
|
|
||||||
String after = configStorage.getAccessToken();
|
String after = configStorage.getAccessToken();
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
|
return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken
|
||||||
+ ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]";
|
+ ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<token>公众号Token</token>
|
<token>公众号Token</token>
|
||||||
<aesKey>公众号EncodingAESKey</aesKey>
|
<aesKey>公众号EncodingAESKey</aesKey>
|
||||||
<accessToken>可以不填写</accessToken>
|
<accessToken>可以不填写</accessToken>
|
||||||
<expiresIn>可以不填写</expiresIn>
|
<expiresTime>可以不填写</expiresTime>
|
||||||
<openId>某个加你公众号的用户的openId</openId>
|
<openId>某个加你公众号的用户的openId</openId>
|
||||||
<oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri>
|
<oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri>
|
||||||
</xml>
|
</xml>
|
||||||
|
|||||||
Reference in New Issue
Block a user