diff --git a/pom.xml b/pom.xml index f3d6b3da..a173a078 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,12 @@ 2.9.0 provided + + com.github.jedis-lock + jedis-lock + 1.0.0 + provided + org.redisson redisson diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 25b4d4fe..f0b09943 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -118,6 +118,15 @@ dom4j 2.1.1 + + redis.clients + jedis + + + + com.github.jedis-lock + jedis-lock + diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java new file mode 100644 index 00000000..636e7e24 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java @@ -0,0 +1,71 @@ +package me.chanjar.weixin.common.util.locks; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import redis.clients.jedis.Jedis; +import redis.clients.util.Pool; + +/** + * JedisPool 分布式锁 + * + * @author 007 + */ +public class JedisDistributedLock implements Lock { + private final Pool jedisPool; + private final JedisLock lock; + + public JedisDistributedLock(Pool jedisPool, String key){ + this.jedisPool = jedisPool; + this.lock = new JedisLock(key); + } + + @Override + public void lock() { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } + } + + @Override + public boolean tryLock() { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } + } + + @Override + public void unlock() { + try (Jedis jedis = jedisPool.getResource()) { + lock.release(jedis); + } + } + + @Override + public Condition newCondition() { + throw new RuntimeException("unsupported method"); + } + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java index 79969228..5a51c3bf 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java @@ -6,6 +6,8 @@ import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; +import java.util.concurrent.locks.Lock; + /** * @author 007 */ @@ -53,6 +55,10 @@ public interface WxOpenConfigStorage { WxMaConfig getWxMaConfig(String appId); + Lock getComponentAccessTokenLock(); + + Lock getLockByKey(String key); + /** * 应该是线程安全的 * diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java index 0d0b1bf2..52799da5 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/AbstractWxOpenInRedisConfigStorage.java @@ -13,6 +13,9 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC protected final static String AUTHORIZER_REFRESH_TOKEN_KEY = "wechat_authorizer_refresh_token:"; protected final static String AUTHORIZER_ACCESS_TOKEN_KEY = "wechat_authorizer_access_token:"; + + protected final static String LOCK_KEY = "wechat_lock:"; + protected final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket:"; protected final static String CARD_API_TICKET_KEY = "wechat_card_api_ticket:"; @@ -26,6 +29,7 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC protected String authorizerAccessTokenKey; protected String jsapiTicketKey; protected String cardApiTicket; + protected String lockKey; @Override public void setComponentAppId(String componentAppId) { @@ -36,8 +40,9 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); authorizerAccessTokenKey = prefix + AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId); - this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId); - this.cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId); + lockKey = prefix + LOCK_KEY.concat(componentAppId); + jsapiTicketKey = prefix + JSAPI_TICKET_KEY.concat(componentAppId); + cardApiTicket = prefix + CARD_API_TICKET_KEY.concat(componentAppId); } protected String getKey(String prefix, String appId) { diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java index 27e76569..4f1fc900 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; +import java.util.concurrent.locks.Lock; import java.util.concurrent.ConcurrentHashMap; /** @@ -109,8 +110,16 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { @Override public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { - - if (this.getWxOpenConfigStorage().isComponentAccessTokenExpired() || forceRefresh) { + final WxOpenConfigStorage config = this.getWxOpenConfigStorage(); + if (!config.isComponentAccessTokenExpired() && !forceRefresh) { + return config.getComponentAccessToken(); + } + Lock lock = config.getComponentAccessTokenLock(); + lock.lock(); + try { + if (!config.isComponentAccessTokenExpired() && !forceRefresh) { + return config.getComponentAccessToken(); + } JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); @@ -118,9 +127,11 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { String responseContent = this.getWxOpenService().post(API_COMPONENT_TOKEN_URL, jsonObject.toString()); WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); - getWxOpenConfigStorage().updateComponentAccessToken(componentAccessToken); + config.updateComponentAccessToken(componentAccessToken); + return config.getComponentAccessToken(); + } finally { + lock.unlock(); } - return this.getWxOpenConfigStorage().getComponentAccessToken(); } @Override @@ -346,8 +357,16 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { @Override public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { - - if (this.getWxOpenConfigStorage().isAuthorizerAccessTokenExpired(appId) || forceRefresh) { + WxOpenConfigStorage config = getWxOpenConfigStorage(); + if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh){ + return config.getAuthorizerAccessToken(appId); + } + Lock lock = config.getWxMpConfigStorage(appId).getAccessTokenLock(); + lock.lock(); + try{ + if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh){ + return config.getAuthorizerAccessToken(appId); + } JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); jsonObject.addProperty("authorizer_appid", appId); @@ -355,9 +374,11 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); - getWxOpenConfigStorage().updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); + config.updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); + return config.getAuthorizerAccessToken(appId); + }finally { + lock.unlock(); } - return this.getWxOpenConfigStorage().getAuthorizerAccessToken(appId); } @Override diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java index 33678ea8..966db061 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java @@ -44,6 +44,9 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { private Map authorizerAccessTokens = new ConcurrentHashMap<>(); private Map jsapiTickets = new ConcurrentHashMap<>(); private Map cardApiTickets = new ConcurrentHashMap<>(); + private Map locks = new ConcurrentHashMap<>(); + + private Lock componentAccessTokenLock = getLockByKey("componentAccessTokenLock"); @Override @@ -61,6 +64,21 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { updateComponentAccessToken(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); } + @Override + public Lock getLockByKey(String key){ + Lock lock = locks.get(key); + if (lock == null) { + synchronized (WxOpenInMemoryConfigStorage.class){ + lock = locks.get(key); + if (lock == null) { + lock = new ReentrantLock(); + locks.put(key, lock); + } + } + } + return lock; + } + @Override public WxMpConfigStorage getWxMpConfigStorage(String appId) { return new WxOpenInnerConfigStorage(this, appId); @@ -211,13 +229,16 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { * 云环境ID */ private volatile String cloudEnv; - private Lock accessTokenLock = new ReentrantLock(); - private Lock jsapiTicketLock = new ReentrantLock(); - private Lock cardApiTicketLock = new ReentrantLock(); + private final Lock accessTokenLock; + private final Lock jsapiTicketLock; + private final Lock cardApiTicketLock; private WxOpenInnerConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String appId) { this.wxOpenConfigStorage = wxOpenConfigStorage; this.appId = appId; + this.accessTokenLock = wxOpenConfigStorage.getLockByKey(appId + ":accessTokenLock"); + this.jsapiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":jsapiTicketLock"); + this.cardApiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":cardApiTicketLock"); } @Override diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java index f1b903d4..a681e293 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java @@ -1,9 +1,12 @@ package me.chanjar.weixin.open.api.impl; +import me.chanjar.weixin.common.util.locks.JedisDistributedLock; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.util.Pool; +import java.util.concurrent.locks.Lock; + /** * @author 007 */ @@ -163,4 +166,9 @@ public class WxOpenInRedisConfigStorage extends AbstractWxOpenInRedisConfigStora jedis.setex(this.getKey(this.cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); } } + + @Override + public Lock getLockByKey(String key) { + return new JedisDistributedLock(jedisPool, getKey(lockKey, key)); + } }