@@ -5,7 +5,7 @@ | |||
<token>企业号应用Token</token> | |||
<aesKey>企业号应用EncodingAESKey</aesKey> | |||
<accessToken>可以不填写</accessToken> | |||
<expiresIn>可以不填写</expiresIn> | |||
<expiresTime>可以不填写</expiresTime> | |||
<userId>企业号通讯录里的某个userid</userId> | |||
<departmentId>企业号通讯录的某个部门id</departmentId> | |||
<tagId>企业号通讯录里的某个tagid</tagId> | |||
@@ -9,6 +9,15 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||
*/ | |||
public interface WxMpConfigStorage { | |||
public String getAccessToken(); | |||
public boolean isAccessTokenExpired(); | |||
/** | |||
* 强制将access token过期掉 | |||
*/ | |||
public void expireAccessToken(); | |||
/** | |||
* 应该是线程安全的 | |||
* @param accessToken | |||
@@ -22,17 +31,30 @@ public interface WxMpConfigStorage { | |||
*/ | |||
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 getSecret(); | |||
public String getToken(); | |||
public String getAesKey(); | |||
public int getExpiresIn(); | |||
public long getExpiresTime(); | |||
public String getOauth2redirectUri(); | |||
@@ -42,17 +64,7 @@ public interface WxMpConfigStorage { | |||
public String getHttp_proxy_username(); | |||
public String getHttp_proxy_password(); | |||
public String getJsapiTicket(); | |||
public boolean isJsapiTokenExpired(); | |||
/** | |||
* 应该是线程安全的 | |||
* @param jsapiTicket | |||
*/ | |||
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
public String getHttp_proxy_password(); | |||
} |
@@ -16,7 +16,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
protected String token; | |||
protected String accessToken; | |||
protected String aesKey; | |||
protected int expiresIn; | |||
protected long expiresTime; | |||
protected String oauth2redirectUri; | |||
@@ -28,17 +28,43 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
protected String jsapiTicket; | |||
protected long jsapiTicketExpiresTime; | |||
public String getAccessToken() { | |||
return this.accessToken; | |||
} | |||
public boolean isAccessTokenExpired() { | |||
return System.currentTimeMillis() > this.expiresTime; | |||
} | |||
public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
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.expiresIn = expiresIn; | |||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
} | |||
public String getAccessToken() { | |||
return this.accessToken; | |||
public void expireAccessToken() { | |||
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() { | |||
@@ -53,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return this.token; | |||
} | |||
public int getExpiresIn() { | |||
return this.expiresIn; | |||
public long getExpiresTime() { | |||
return this.expiresTime; | |||
} | |||
public void setAppId(String appId) { | |||
@@ -81,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
this.accessToken = accessToken; | |||
} | |||
public void setExpiresIn(int expiresIn) { | |||
this.expiresIn = expiresIn; | |||
public void setExpiresTime(long expiresTime) { | |||
this.expiresTime = expiresTime; | |||
} | |||
@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 | |||
public String toString() { | |||
return "WxMpInMemoryConfigStorage{" + | |||
@@ -149,7 +161,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
", token='" + token + '\'' + | |||
", accessToken='" + accessToken + '\'' + | |||
", aesKey='" + aesKey + '\'' + | |||
", expiresIn=" + expiresIn + | |||
", expiresTime=" + expiresTime + | |||
", http_proxy_host='" + http_proxy_host + '\'' + | |||
", http_proxy_port=" + http_proxy_port + | |||
", http_proxy_username='" + http_proxy_username + '\'' + | |||
@@ -40,9 +40,10 @@ public interface WxMpService { | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | |||
* </pre> | |||
* @return | |||
* @throws me.chanjar.weixin.common.exception.WxErrorException | |||
*/ | |||
public void accessTokenRefresh() throws WxErrorException; | |||
public String getAccessToken() throws WxErrorException; | |||
/** | |||
* <pre> | |||
@@ -44,12 +44,15 @@ import java.util.concurrent.atomic.AtomicBoolean; | |||
public class WxMpServiceImpl implements WxMpService { | |||
/** | |||
* 全局的是否正在刷新Access Token的flag | |||
* true: 正在刷新 | |||
* false: 没有刷新 | |||
* 全局的是否正在刷新access token的锁 | |||
*/ | |||
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 final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
@@ -66,52 +69,45 @@ public class WxMpServiceImpl implements WxMpService { | |||
} | |||
} | |||
public void accessTokenRefresh() throws WxErrorException { | |||
if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | |||
try { | |||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | |||
+ "&appid=" + wxMpConfigStorage.getAppId() | |||
+ "&secret=" + wxMpConfigStorage.getSecret() | |||
; | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (httpProxy != null) { | |||
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); | |||
public String getAccessToken() throws WxErrorException { | |||
if (wxMpConfigStorage.isAccessTokenExpired()) { | |||
synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { | |||
if (wxMpConfigStorage.isAccessTokenExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | |||
+ "&appid=" + wxMpConfigStorage.getAppId() | |||
+ "&secret=" + wxMpConfigStorage.getSecret() | |||
; | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (httpProxy != null) { | |||
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); | |||
} | |||
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 { | |||
if (wxMpConfigStorage.isJsapiTokenExpired()) { | |||
synchronized (wxMpConfigStorage) { | |||
if (wxMpConfigStorage.isJsapiTokenExpired()) { | |||
if (wxMpConfigStorage.isJsapiTicketExpired()) { | |||
synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) { | |||
if (wxMpConfigStorage.isJsapiTicketExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
@@ -440,10 +436,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
* @throws WxErrorException | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) { | |||
accessTokenRefresh(); | |||
} | |||
String accessToken = wxMpConfigStorage.getAccessToken(); | |||
String accessToken = getAccessToken(); | |||
String uriWithAccessToken = uri; | |||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | |||
@@ -458,7 +451,8 @@ public class WxMpServiceImpl implements WxMpService { | |||
* 42001 access_token超时 | |||
*/ | |||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||
accessTokenRefresh(); | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
wxMpConfigStorage.expireAccessToken(); | |||
return execute(executor, uri, data); | |||
} | |||
/** | |||
@@ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken { | |||
public String toString() { | |||
return "WxMpOAuth2AccessToken{" + | |||
"accessToken='" + accessToken + '\'' + | |||
", expiresIn=" + expiresIn + | |||
", expiresTime=" + expiresIn + | |||
", refreshToken='" + refreshToken + '\'' + | |||
", openId='" + openId + '\'' + | |||
", scope='" + scope + '\'' + | |||
@@ -42,7 +42,7 @@ public class ApiTestModule implements Module { | |||
@Override | |||
public String toString() { | |||
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 { | |||
WxMpConfigStorage configStorage = wxService.wxMpConfigStorage; | |||
String before = configStorage.getAccessToken(); | |||
wxService.accessTokenRefresh(); | |||
wxService.getAccessToken(); | |||
String after = configStorage.getAccessToken(); | |||
@@ -16,7 +16,7 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage { | |||
@Override | |||
public String toString() { | |||
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> | |||
<aesKey>公众号EncodingAESKey</aesKey> | |||
<accessToken>可以不填写</accessToken> | |||
<expiresIn>可以不填写</expiresIn> | |||
<expiresTime>可以不填写</expiresTime> | |||
<openId>某个加你公众号的用户的openId</openId> | |||
<oauth2redirectUri>网页授权获取用户信息回调地址</oauth2redirectUri> | |||
</xml> |