diff --git a/pom.xml b/pom.xml index f826bb76..6b282968 100644 --- a/pom.xml +++ b/pom.xml @@ -246,7 +246,7 @@ org.projectlombok lombok - 1.16.18 + 1.18.8 provided diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml index d6fc851e..3253a2e1 100644 --- a/weixin-java-miniapp/pom.xml +++ b/weixin-java-miniapp/pom.xml @@ -80,6 +80,12 @@ org.projectlombok lombok + + com.github.jedis-lock + jedis-lock + 1.0.0 + true + diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaInRedisConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaInRedisConfig.java new file mode 100644 index 00000000..c90be60a --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/WxMaInRedisConfig.java @@ -0,0 +1,404 @@ +package cn.binarywang.wx.miniapp.config; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import com.github.jedis.lock.JedisLock; + +import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +/** + * 基于Redis的微信配置provider. + * + *
+ * 需要引入依赖jedis-lock,才能使用该类。
+ * 
+ * + * @author winter + */ +public class WxMaInRedisConfig implements WxMaConfig { + + private static final String ACCESS_TOKEN = "accessToken"; + private static final String JSAPI_TICKET = "jsapiTicket"; + private static final String CARD_API_TICKET = "cardApiTicket"; + + private static final String HASH_VALUE_FIELD = "value"; + private static final String HASH_EXPIRE_FIELD = "expire"; + + protected JedisPool jedisPool; + /** + * 微信小程序唯一id,用于拼接存储到redis时的key,防止key重复 + */ + protected String maId; + + protected volatile String msgDataFormat; + protected volatile String appid; + protected volatile String secret; + protected volatile String token; + protected volatile String aesKey; + + protected volatile String httpProxyHost; + protected volatile int httpProxyPort; + protected volatile String httpProxyUsername; + protected volatile String httpProxyPassword; + + protected Lock accessTokenLock; + protected Lock jsapiTicketLock; + protected Lock cardApiTicketLock; + + /** + * 临时文件目录 + */ + protected volatile File tmpDirFile; + + protected volatile ApacheHttpClientBuilder apacheHttpClientBuilder; + + private String getRedisKey(String key) { + StringBuilder redisKey = new StringBuilder("maConfig:"); + if(maId == null) { + return redisKey.append(key).toString(); + } else { + return redisKey.append(maId).append(":").append(key).toString(); + } + } + + private String getValueFromRedis(String key) { + Jedis jedis = jedisPool.getResource(); + try { + return jedis.hget(getRedisKey(key), HASH_VALUE_FIELD); + } finally { + jedis.close(); + } + } + + private void setValueToRedis(String key, long expiresTime, String value) { + Jedis jedis = jedisPool.getResource(); + try { + Map hash = new HashMap(); + hash.put(HASH_VALUE_FIELD, value); + hash.put(HASH_EXPIRE_FIELD, String.valueOf(expiresTime)); + jedis.hmset(getRedisKey(key), hash); + } finally { + jedis.close(); + } + } + + private long getExpireFromRedis(String key) { + Jedis jedis = jedisPool.getResource(); + try { + String expire = jedis.hget(getRedisKey(key), HASH_EXPIRE_FIELD); + return expire == null ? 0 : Long.valueOf(expire); + } finally { + jedis.close(); + } + } + + private void setExpire(String key, long expiresTime) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.hset(getRedisKey(key), HASH_EXPIRE_FIELD, String.valueOf(expiresTime)); + } finally { + jedis.close(); + } + } + + public void setJedisPool(JedisPool jedisPool) { + this.jedisPool = jedisPool; + } + + public void setMaId(String maId) { + this.maId = maId; + } + + @Override + public String getAccessToken() { + return getValueFromRedis(ACCESS_TOKEN); + } + + @Override + public Lock getAccessTokenLock() { + if(accessTokenLock == null) { + synchronized (this) { + if(accessTokenLock == null) { + accessTokenLock = new DistributedLock(getRedisKey("accessTokenLock")); + } + } + } + return accessTokenLock; + } + + @Override + public boolean isAccessTokenExpired() { + return System.currentTimeMillis() > getExpireFromRedis(ACCESS_TOKEN); + } + + @Override + public synchronized void updateAccessToken(WxAccessToken accessToken) { + updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } + + @Override + public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { + setValueToRedis(ACCESS_TOKEN, System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L, accessToken); + } + + @Override + public String getJsapiTicket() { + return getValueFromRedis(JSAPI_TICKET); + } + + @Override + public Lock getJsapiTicketLock() { + if(jsapiTicketLock == null) { + synchronized (this) { + if(jsapiTicketLock == null) { + jsapiTicketLock = new DistributedLock(getRedisKey("jsapiTicketLock")); + } + } + } + return jsapiTicketLock; + } + + @Override + public boolean isJsapiTicketExpired() { + return System.currentTimeMillis() > getExpireFromRedis(JSAPI_TICKET); + } + + @Override + public void expireJsapiTicket() { + setExpire(JSAPI_TICKET, 0); + } + + @Override + public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { + // 预留200秒的时间 + setValueToRedis(JSAPI_TICKET, System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L, jsapiTicket); + } + + + @Override + public String getCardApiTicket() { + return getValueFromRedis(CARD_API_TICKET); + } + + @Override + public Lock getCardApiTicketLock() { + if(cardApiTicketLock == null) { + synchronized (this) { + if(cardApiTicketLock == null) { + cardApiTicketLock = new DistributedLock(getRedisKey("cardApiTicketLock")); + } + } + } + return cardApiTicketLock; + } + + @Override + public boolean isCardApiTicketExpired() { + return System.currentTimeMillis() > getExpireFromRedis(CARD_API_TICKET); + } + + @Override + public void expireCardApiTicket() { + setExpire(CARD_API_TICKET, 0); + } + + @Override + public void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { + setValueToRedis(CARD_API_TICKET, System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L, cardApiTicket); + } + + @Override + public void expireAccessToken() { + setExpire(ACCESS_TOKEN, 0); + } + + @Override + public String getSecret() { + return this.secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + @Override + public String getToken() { + return this.token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public long getExpiresTime() { + return getExpireFromRedis(ACCESS_TOKEN); + } + + @Override + public String getAesKey() { + return this.aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + @Override + public String getMsgDataFormat() { + return this.msgDataFormat; + } + + public void setMsgDataFormat(String msgDataFormat) { + this.msgDataFormat = msgDataFormat; + } + + @Override + public String getHttpProxyHost() { + return this.httpProxyHost; + } + + public void setHttpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + } + + @Override + public int getHttpProxyPort() { + return this.httpProxyPort; + } + + public void setHttpProxyPort(int httpProxyPort) { + this.httpProxyPort = httpProxyPort; + } + + @Override + public String getHttpProxyUsername() { + return this.httpProxyUsername; + } + + public void setHttpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + } + + @Override + public String getHttpProxyPassword() { + return this.httpProxyPassword; + } + + public void setHttpProxyPassword(String httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + } + + @Override + public String toString() { + return WxMaGsonBuilder.create().toJson(this); + } + + @Override + public ApacheHttpClientBuilder getApacheHttpClientBuilder() { + return this.apacheHttpClientBuilder; + } + + public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { + this.apacheHttpClientBuilder = apacheHttpClientBuilder; + } + + @Override + public boolean autoRefreshToken() { + return true; + } + + @Override + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + /** + * 基于redis的简单分布式锁 + */ + private class DistributedLock implements Lock { + + private JedisLock lock; + + private DistributedLock(String key) { + this.lock = new JedisLock(getRedisKey(key)); + } + + @Override + public void lock() { + Jedis jedis = jedisPool.getResource(); + try { + if(!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } catch (InterruptedException e) { + throw new RuntimeException("lock failed",e); + } finally { + jedis.close(); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + Jedis jedis = jedisPool.getResource(); + try { + if(!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } finally { + jedis.close(); + } + } + + @Override + public boolean tryLock() { + Jedis jedis = jedisPool.getResource(); + try { + return lock.acquire(jedis); + } catch (InterruptedException e) { + throw new RuntimeException("lock failed",e); + } finally { + jedis.close(); + } + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + Jedis jedis = jedisPool.getResource(); + try { + return lock.acquire(jedis); + } finally { + jedis.close(); + } + } + + @Override + public void unlock() { + Jedis jedis = jedisPool.getResource(); + try { + lock.release(jedis); + } finally { + jedis.close(); + } + } + + @Override + public Condition newCondition() { + throw new RuntimeException("unsupported method"); + } + + } +}