| @@ -5,6 +5,7 @@ import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||||
| import javax.net.ssl.SSLContext; | import javax.net.ssl.SSLContext; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * 微信客户端配置存储 | * 微信客户端配置存储 | ||||
| @@ -15,6 +16,8 @@ public interface WxMpConfigStorage { | |||||
| String getAccessToken(); | String getAccessToken(); | ||||
| Lock getAccessTokenLock(); | |||||
| boolean isAccessTokenExpired(); | boolean isAccessTokenExpired(); | ||||
| /** | /** | ||||
| @@ -37,6 +40,8 @@ public interface WxMpConfigStorage { | |||||
| String getJsapiTicket(); | String getJsapiTicket(); | ||||
| Lock getJsapiTicketLock(); | |||||
| boolean isJsapiTicketExpired(); | boolean isJsapiTicketExpired(); | ||||
| /** | /** | ||||
| @@ -53,6 +58,8 @@ public interface WxMpConfigStorage { | |||||
| String getCardApiTicket(); | String getCardApiTicket(); | ||||
| Lock getCardApiTicketLock(); | |||||
| boolean isCardApiTicketExpired(); | boolean isCardApiTicketExpired(); | ||||
| /** | /** | ||||
| @@ -6,6 +6,8 @@ import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||||
| import javax.net.ssl.SSLContext; | import javax.net.ssl.SSLContext; | ||||
| import java.io.File; | import java.io.File; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| import java.util.concurrent.locks.ReentrantLock; | |||||
| /** | /** | ||||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | ||||
| @@ -36,6 +38,10 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
| protected volatile String cardApiTicket; | protected volatile String cardApiTicket; | ||||
| protected volatile long cardApiTicketExpiresTime; | 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; | return this.accessToken; | ||||
| } | } | ||||
| @Override | |||||
| public Lock getAccessTokenLock() { | |||||
| return this.accessTokenLock; | |||||
| } | |||||
| @Override | @Override | ||||
| public boolean isAccessTokenExpired() { | public boolean isAccessTokenExpired() { | ||||
| return System.currentTimeMillis() > this.expiresTime; | return System.currentTimeMillis() > this.expiresTime; | ||||
| @@ -76,6 +87,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
| return this.jsapiTicket; | return this.jsapiTicket; | ||||
| } | } | ||||
| @Override | |||||
| public Lock getJsapiTicketLock() { | |||||
| return this.jsapiTicketLock; | |||||
| } | |||||
| public void setJsapiTicket(String jsapiTicket) { | public void setJsapiTicket(String jsapiTicket) { | ||||
| this.jsapiTicket = jsapiTicket; | this.jsapiTicket = jsapiTicket; | ||||
| } | } | ||||
| @@ -113,6 +129,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
| return this.cardApiTicket; | return this.cardApiTicket; | ||||
| } | } | ||||
| @Override | |||||
| public Lock getCardApiTicketLock() { | |||||
| return this.cardApiTicketLock; | |||||
| } | |||||
| @Override | @Override | ||||
| public boolean isCardApiTicketExpired() { | public boolean isCardApiTicketExpired() { | ||||
| return System.currentTimeMillis() > this.cardApiTicketExpiresTime; | return System.currentTimeMillis() > this.cardApiTicketExpiresTime; | ||||
| @@ -1,6 +1,7 @@ | |||||
| package me.chanjar.weixin.mp.api.impl; | package me.chanjar.weixin.mp.api.impl; | ||||
| import java.util.Arrays; | import java.util.Arrays; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| @@ -29,11 +30,6 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||||
| private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class); | private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class); | ||||
| /** | |||||
| * 全局的是否正在刷新卡券api_ticket的锁 | |||||
| */ | |||||
| private final Object globalCardApiTicketRefreshLock = new Object(); | |||||
| private WxMpService wxMpService; | private WxMpService wxMpService; | ||||
| public WxMpCardServiceImpl(WxMpService wxMpService) { | public WxMpCardServiceImpl(WxMpService wxMpService) { | ||||
| @@ -66,21 +62,25 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public String getCardApiTicket(boolean forceRefresh) throws WxErrorException { | 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(); | return this.wxMpService.getWxMpConfigStorage().getCardApiTicket(); | ||||
| } | } | ||||
| @@ -28,6 +28,7 @@ import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| public class WxMpServiceImpl implements WxMpService { | public class WxMpServiceImpl implements WxMpService { | ||||
| @@ -35,16 +36,6 @@ public class WxMpServiceImpl implements WxMpService { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | 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 WxMpConfigStorage configStorage; | ||||
| private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | ||||
| @@ -98,39 +89,42 @@ public class WxMpServiceImpl implements WxMpService { | |||||
| @Override | @Override | ||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | 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(); | return this.configStorage.getAccessToken(); | ||||
| } | } | ||||
| @@ -142,22 +136,25 @@ public class WxMpServiceImpl implements WxMpService { | |||||
| @Override | @Override | ||||
| public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | 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(); | return this.configStorage.getJsapiTicket(); | ||||
| } | } | ||||