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");
+ }
+
+ }
+}