修改Token/Ticket的同步机制以支持集群master
@@ -5,6 +5,7 @@ import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
import javax.net.ssl.SSLContext; | |||
import java.io.File; | |||
import java.util.concurrent.locks.Lock; | |||
/** | |||
* 微信客户端配置存储 | |||
@@ -15,6 +16,8 @@ public interface WxMpConfigStorage { | |||
String getAccessToken(); | |||
Lock getAccessTokenLock(); | |||
boolean isAccessTokenExpired(); | |||
/** | |||
@@ -37,6 +40,8 @@ public interface WxMpConfigStorage { | |||
String getJsapiTicket(); | |||
Lock getJsapiTicketLock(); | |||
boolean isJsapiTicketExpired(); | |||
/** | |||
@@ -53,6 +58,8 @@ public interface WxMpConfigStorage { | |||
String getCardApiTicket(); | |||
Lock getCardApiTicketLock(); | |||
boolean isCardApiTicketExpired(); | |||
/** | |||
@@ -6,6 +6,8 @@ import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
import javax.net.ssl.SSLContext; | |||
import java.io.File; | |||
import java.util.concurrent.locks.Lock; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
/** | |||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
@@ -36,6 +38,10 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
protected volatile String cardApiTicket; | |||
protected volatile long cardApiTicketExpiresTime; | |||
protected Lock accessTokenLock = new ReentrantLock(); | |||
protected Lock jsapiTicketLock = new ReentrantLock(); | |||
protected Lock cardApiTicketLock = new ReentrantLock(); | |||
/** | |||
* 临时文件目录 | |||
*/ | |||
@@ -50,6 +56,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return this.accessToken; | |||
} | |||
@Override | |||
public Lock getAccessTokenLock() { | |||
return this.accessTokenLock; | |||
} | |||
@Override | |||
public boolean isAccessTokenExpired() { | |||
return System.currentTimeMillis() > this.expiresTime; | |||
@@ -76,6 +87,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return this.jsapiTicket; | |||
} | |||
@Override | |||
public Lock getJsapiTicketLock() { | |||
return this.jsapiTicketLock; | |||
} | |||
public void setJsapiTicket(String jsapiTicket) { | |||
this.jsapiTicket = jsapiTicket; | |||
} | |||
@@ -113,6 +129,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return this.cardApiTicket; | |||
} | |||
@Override | |||
public Lock getCardApiTicketLock() { | |||
return this.cardApiTicketLock; | |||
} | |||
@Override | |||
public boolean isCardApiTicketExpired() { | |||
return System.currentTimeMillis() > this.cardApiTicketExpiresTime; | |||
@@ -1,6 +1,7 @@ | |||
package me.chanjar.weixin.mp.api.impl; | |||
import java.util.Arrays; | |||
import java.util.concurrent.locks.Lock; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -29,11 +30,6 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class); | |||
/** | |||
* 全局的是否正在刷新卡券api_ticket的锁 | |||
*/ | |||
private final Object globalCardApiTicketRefreshLock = new Object(); | |||
private WxMpService wxMpService; | |||
public WxMpCardServiceImpl(WxMpService wxMpService) { | |||
@@ -66,21 +62,25 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
*/ | |||
@Override | |||
public String getCardApiTicket(boolean forceRefresh) throws WxErrorException { | |||
if (forceRefresh) { | |||
this.wxMpService.getWxMpConfigStorage().expireCardApiTicket(); | |||
} | |||
if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) { | |||
synchronized (this.globalCardApiTicketRefreshLock) { | |||
if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card"; | |||
String responseContent = this.wxMpService.execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String cardApiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.wxMpService.getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds); | |||
} | |||
Lock lock = wxMpService.getWxMpConfigStorage().getCardApiTicketLock(); | |||
try { | |||
lock.lock(); | |||
if (forceRefresh) { | |||
this.wxMpService.getWxMpConfigStorage().expireCardApiTicket(); | |||
} | |||
if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card"; | |||
String responseContent = this.wxMpService.execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String cardApiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.wxMpService.getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds); | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
return this.wxMpService.getWxMpConfigStorage().getCardApiTicket(); | |||
} | |||
@@ -28,6 +28,7 @@ import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
import java.util.concurrent.locks.Lock; | |||
public class WxMpServiceImpl implements WxMpService { | |||
@@ -35,16 +36,6 @@ public class WxMpServiceImpl implements WxMpService { | |||
protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
/** | |||
* 全局的是否正在刷新access token的锁 | |||
*/ | |||
private final Object globalAccessTokenRefreshLock = new Object(); | |||
/** | |||
* 全局的是否正在刷新jsapi_ticket的锁 | |||
*/ | |||
private final Object globalJsapiTicketRefreshLock = new Object(); | |||
private WxMpConfigStorage configStorage; | |||
private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | |||
@@ -98,39 +89,42 @@ public class WxMpServiceImpl implements WxMpService { | |||
@Override | |||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||
if (forceRefresh) { | |||
this.configStorage.expireAccessToken(); | |||
} | |||
Lock lock = configStorage.getAccessTokenLock(); | |||
try { | |||
lock.lock(); | |||
if (this.configStorage.isAccessTokenExpired()) { | |||
synchronized (this.globalAccessTokenRefreshLock) { | |||
if (this.configStorage.isAccessTokenExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + | |||
"&appid=" + this.configStorage.getAppId() + "&secret=" | |||
+ this.configStorage.getSecret(); | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (this.httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
try (CloseableHttpResponse response = getHttpclient().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); | |||
this.configStorage.updateAccessToken(accessToken.getAccessToken(), | |||
accessToken.getExpiresIn()); | |||
}finally { | |||
httpGet.releaseConnection(); | |||
if (forceRefresh) { | |||
this.configStorage.expireAccessToken(); | |||
} | |||
if (this.configStorage.isAccessTokenExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + | |||
"&appid=" + this.configStorage.getAppId() + "&secret=" | |||
+ this.configStorage.getSecret(); | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (this.httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
try (CloseableHttpResponse response = getHttpclient().execute(httpGet)) { | |||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||
WxError error = WxError.fromJson(resultContent); | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||
this.configStorage.updateAccessToken(accessToken.getAccessToken(), | |||
accessToken.getExpiresIn()); | |||
}finally { | |||
httpGet.releaseConnection(); | |||
} | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
return this.configStorage.getAccessToken(); | |||
} | |||
@@ -142,22 +136,25 @@ public class WxMpServiceImpl implements WxMpService { | |||
@Override | |||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||
if (forceRefresh) { | |||
this.configStorage.expireJsapiTicket(); | |||
} | |||
Lock lock = configStorage.getJsapiTicketLock(); | |||
try { | |||
lock.lock(); | |||
if (this.configStorage.isJsapiTicketExpired()) { | |||
synchronized (this.globalJsapiTicketRefreshLock) { | |||
if (this.configStorage.isJsapiTicketExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.configStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||
} | |||
if (forceRefresh) { | |||
this.configStorage.expireJsapiTicket(); | |||
} | |||
if (this.configStorage.isJsapiTicketExpired()) { | |||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.configStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
return this.configStorage.getJsapiTicket(); | |||
} | |||