* 三方平台支持redis分布式锁;getComponentAccessToken 加锁 * getAuthorizerAccessToken 加锁dev1
| @@ -243,6 +243,12 @@ | |||||
| <version>2.9.0</version> | <version>2.9.0</version> | ||||
| <scope>provided</scope> | <scope>provided</scope> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>com.github.jedis-lock</groupId> | |||||
| <artifactId>jedis-lock</artifactId> | |||||
| <version>1.0.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>org.redisson</groupId> | <groupId>org.redisson</groupId> | ||||
| <artifactId>redisson</artifactId> | <artifactId>redisson</artifactId> | ||||
| @@ -118,6 +118,15 @@ | |||||
| <artifactId>dom4j</artifactId> | <artifactId>dom4j</artifactId> | ||||
| <version>2.1.1</version> | <version>2.1.1</version> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>redis.clients</groupId> | |||||
| <artifactId>jedis</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.github.jedis-lock</groupId> | |||||
| <artifactId>jedis-lock</artifactId> | |||||
| </dependency> | |||||
| </dependencies> | </dependencies> | ||||
| <build> | <build> | ||||
| @@ -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 <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class JedisDistributedLock implements Lock { | |||||
| private final Pool<Jedis> jedisPool; | |||||
| private final JedisLock lock; | |||||
| public JedisDistributedLock(Pool<Jedis> 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"); | |||||
| } | |||||
| } | |||||
| @@ -6,6 +6,8 @@ import me.chanjar.weixin.mp.config.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | ||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * @author <a href="https://github.com/007gzs">007</a> | * @author <a href="https://github.com/007gzs">007</a> | ||||
| */ | */ | ||||
| @@ -53,6 +55,10 @@ public interface WxOpenConfigStorage { | |||||
| WxMaConfig getWxMaConfig(String appId); | WxMaConfig getWxMaConfig(String appId); | ||||
| Lock getComponentAccessTokenLock(); | |||||
| Lock getLockByKey(String key); | |||||
| /** | /** | ||||
| * 应该是线程安全的 | * 应该是线程安全的 | ||||
| * | * | ||||
| @@ -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_REFRESH_TOKEN_KEY = "wechat_authorizer_refresh_token:"; | ||||
| protected final static String AUTHORIZER_ACCESS_TOKEN_KEY = "wechat_authorizer_access_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 JSAPI_TICKET_KEY = "wechat_jsapi_ticket:"; | ||||
| protected final static String CARD_API_TICKET_KEY = "wechat_card_api_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 authorizerAccessTokenKey; | ||||
| protected String jsapiTicketKey; | protected String jsapiTicketKey; | ||||
| protected String cardApiTicket; | protected String cardApiTicket; | ||||
| protected String lockKey; | |||||
| @Override | @Override | ||||
| public void setComponentAppId(String componentAppId) { | public void setComponentAppId(String componentAppId) { | ||||
| @@ -36,8 +40,9 @@ public abstract class AbstractWxOpenInRedisConfigStorage extends WxOpenInMemoryC | |||||
| componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); | componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); | ||||
| authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); | authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); | ||||
| authorizerAccessTokenKey = prefix + AUTHORIZER_ACCESS_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) { | protected String getKey(String prefix, String appId) { | ||||
| @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory; | |||||
| import java.util.List; | import java.util.List; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||
| /** | /** | ||||
| @@ -109,8 +110,16 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { | |||||
| @Override | @Override | ||||
| public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { | 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 jsonObject = new JsonObject(); | ||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | ||||
| jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); | 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()); | String responseContent = this.getWxOpenService().post(API_COMPONENT_TOKEN_URL, jsonObject.toString()); | ||||
| WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); | WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); | ||||
| getWxOpenConfigStorage().updateComponentAccessToken(componentAccessToken); | |||||
| config.updateComponentAccessToken(componentAccessToken); | |||||
| return config.getComponentAccessToken(); | |||||
| } finally { | |||||
| lock.unlock(); | |||||
| } | } | ||||
| return this.getWxOpenConfigStorage().getComponentAccessToken(); | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -346,8 +357,16 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { | |||||
| @Override | @Override | ||||
| public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { | 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 jsonObject = new JsonObject(); | ||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | ||||
| jsonObject.addProperty("authorizer_appid", appId); | jsonObject.addProperty("authorizer_appid", appId); | ||||
| @@ -355,9 +374,11 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { | |||||
| String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); | String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); | ||||
| WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); | 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 | @Override | ||||
| @@ -44,6 +44,9 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { | |||||
| private Map<String, Token> authorizerAccessTokens = new ConcurrentHashMap<>(); | private Map<String, Token> authorizerAccessTokens = new ConcurrentHashMap<>(); | ||||
| private Map<String, Token> jsapiTickets = new ConcurrentHashMap<>(); | private Map<String, Token> jsapiTickets = new ConcurrentHashMap<>(); | ||||
| private Map<String, Token> cardApiTickets = new ConcurrentHashMap<>(); | private Map<String, Token> cardApiTickets = new ConcurrentHashMap<>(); | ||||
| private Map<String, Lock> locks = new ConcurrentHashMap<>(); | |||||
| private Lock componentAccessTokenLock = getLockByKey("componentAccessTokenLock"); | |||||
| @Override | @Override | ||||
| @@ -61,6 +64,21 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { | |||||
| updateComponentAccessToken(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); | 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 | @Override | ||||
| public WxMpConfigStorage getWxMpConfigStorage(String appId) { | public WxMpConfigStorage getWxMpConfigStorage(String appId) { | ||||
| return new WxOpenInnerConfigStorage(this, appId); | return new WxOpenInnerConfigStorage(this, appId); | ||||
| @@ -211,13 +229,16 @@ public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { | |||||
| * 云环境ID | * 云环境ID | ||||
| */ | */ | ||||
| private volatile String cloudEnv; | 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) { | private WxOpenInnerConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String appId) { | ||||
| this.wxOpenConfigStorage = wxOpenConfigStorage; | this.wxOpenConfigStorage = wxOpenConfigStorage; | ||||
| this.appId = appId; | this.appId = appId; | ||||
| this.accessTokenLock = wxOpenConfigStorage.getLockByKey(appId + ":accessTokenLock"); | |||||
| this.jsapiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":jsapiTicketLock"); | |||||
| this.cardApiTicketLock = wxOpenConfigStorage.getLockByKey(appId + ":cardApiTicketLock"); | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -1,9 +1,12 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | package me.chanjar.weixin.open.api.impl; | ||||
| import me.chanjar.weixin.common.util.locks.JedisDistributedLock; | |||||
| import redis.clients.jedis.Jedis; | import redis.clients.jedis.Jedis; | ||||
| import redis.clients.jedis.JedisPool; | import redis.clients.jedis.JedisPool; | ||||
| import redis.clients.util.Pool; | import redis.clients.util.Pool; | ||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * @author <a href="https://github.com/007gzs">007</a> | * @author <a href="https://github.com/007gzs">007</a> | ||||
| */ | */ | ||||
| @@ -163,4 +166,9 @@ public class WxOpenInRedisConfigStorage extends AbstractWxOpenInRedisConfigStora | |||||
| jedis.setex(this.getKey(this.cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); | jedis.setex(this.getKey(this.cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); | ||||
| } | } | ||||
| } | } | ||||
| @Override | |||||
| public Lock getLockByKey(String key) { | |||||
| return new JedisDistributedLock(jedisPool, getKey(lockKey, key)); | |||||
| } | |||||
| } | } | ||||