refactor: mp 调整access token刷新策略

This commit is contained in:
Daniel Qian
2015-01-20 13:52:40 +08:00
parent cef8ac01e8
commit 1bca854b52
10 changed files with 115 additions and 96 deletions

View File

@ -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>

View File

@ -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);
} }

View File

@ -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 + '\'' +

View File

@ -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>

View File

@ -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);
} }
/** /**

View File

@ -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 + '\'' +

View File

@ -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 + "]";
} }
} }

View File

@ -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();

View File

@ -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 + "]";
} }

View File

@ -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>