一、背景
微信公众号调用接口的accesstoken,失效期为7200秒,所以我们需要把它缓存起来,不用每次都去获取新的。
二、方案
将accesstoken保存再redis中,设置失效时间,并在代码中检测是否有缓存值,没有则去获取并更新redis。但会出现一个问题,在多进程的模式下,会出现并发去获取accesstoken的场景,这样会导致前边的值失效,但前边的值已经缓存到redis中,后边的进程发现redis中有值,则不去更新redis,会造成之后的请求accesstoken一直失效的问题。解决方案采用的是redis的分布式锁来实现进程间并发的问题。
三、代码
package com.wuage.wechat.service.center.utils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationObjectSupport;
import com.alibaba.fastjson.JSON;
import com.wuage.wechat.model.WechatConfig;
import com.wuage.wechat.model.WechatMessageResult;
import com.wuage.wechat.service.center.redis.MessageJedisPool;
import redis.clients.jedis.Jedis;
/**
* 类SingleWechatAccessToken.java的实现描述:TODO 类实现描述
*
* @author wangpeng 2017年1月18日 下午3:39:47
*/
@Component
public class WechatAccessToken extends WebApplicationObjectSupport implements InitializingBean {
private Logger logger = LoggerFactory.getLogger(WechatAccessToken.class);
private MessageJedisPool messageJedisPool;
private volatile String accessToken = "";
private final String WECHAT_ACCESSTOKEN_KEY = PropertiesUtil.getParameter("wechat.accesstoken.key");
private final int WECHAT_ACCESSTOKEN_EXPIRE = 6000; // key超时时间(秒)
private static final long LOCK_TIMEOUT = 3000;
private static final long SLEEP_TIME = 1000;
private long lock_timeout = 0;
private String LOCK_KEY = PropertiesUtil.getParameter("wechat.accesstoken.lock");
/**
* @return the accessToken
*/
public String getAccessToken() {
checkAndResetAccessToken();
return accessToken;
}
/**
* @Description:检查并重新获取accesstoken
* @Author:wangpeng
* @Date:2017年1月19日
* @Return:void
*/
private void checkAndResetAccessToken() {
Jedis jedis = messageJedisPool.getJedis();
try {
if (jedis != null) {
String token = jedis.get(WECHAT_ACCESSTOKEN_KEY);
if (StringUtils.isBlank(token)) {
if (getLock().longValue() == 1) {
resetAccessToken();
releaseLock();
}
} else {
accessToken = token;
}
}
} catch (Exception e) {
logger.error("get wechat access token throw exception :{}", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* @Description:重新获取accesstoken
* @Author:wangpeng
* @Date:2017年1月19日
* @Return:void
*/
public void resetAccessToken() {
logger.info("get wechat access token before,token is " + accessToken);
StringBuilder sb = new StringBuilder();
sb.append(WechatConfig.ACCESS_TOKEN_URL);
sb.append("&appid=");
sb.append(PropertiesUtil.getParameter("WX_APPID"));
sb.append("&secret=");
sb.append(PropertiesUtil.getParameter("WX_APPSECRET"));
// sb.append("546c1511fa8182c6c65db70197f2ebb6");
String result = HttpClientUtil.HttpGetService(sb.toString());
WechatMessageResult wmr = JSON.parseObject(result, WechatMessageResult.class);
if (wmr != null) {
accessToken = wmr.getAccess_token();
setAccessTokenOnRedis(wmr.getAccess_token());
logger.info("get wechat access token after,token is " + accessToken);
}
}
/**
* @Description:缓存accesstoken
* @Author:wangpeng
* @Date:2017年1月19日
* @Return:void
*/
private void setAccessTokenOnRedis(String token) {
Jedis jedis = messageJedisPool.getJedis();
try {
if (jedis != null) {
String state = jedis.set(WECHAT_ACCESSTOKEN_KEY, token);
Long expire = jedis.expire(WECHAT_ACCESSTOKEN_KEY, WECHAT_ACCESSTOKEN_EXPIRE);
logger.info("set access token state is " + state);
logger.info("access token expire time is " + expire);
}
} catch (Exception e) {
logger.error("set access token on redis throw excpetion :{}", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* @Description:获取锁
* @Author:wangpeng
* @Date:2017年2月10日
* @Return:Long
*/
private Long getLock() {
Long lock = 0L;
Jedis jedis = messageJedisPool.getJedis();
try {
if (jedis != null) {
while (lock != 1) {
long now = System.currentTimeMillis();
lock_timeout = now + LOCK_TIMEOUT + 1000;
lock = jedis.setnx(LOCK_KEY, lock_timeout + "");
if (lock.longValue() == 1 || (now > Long.parseLong(jedis.get(LOCK_KEY))
&& now > Long.parseLong(jedis.getSet(LOCK_KEY, lock_timeout + "")))) {
break;
} else {
Thread.sleep(SLEEP_TIME);
}
}
}
} catch (Exception e) {
logger.error("getLock failed throw excpetion :{}", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
return lock;
}
/**
* @Description:释放锁
* @Author:wangpeng
* @Date:2017年2月10日
* @Return:void
*/
private void releaseLock() {
Jedis jedis = messageJedisPool.getJedis();
try {
if (jedis != null) {
long now = System.currentTimeMillis();
if (now < lock_timeout) {
Long result = jedis.del(LOCK_KEY);
logger.info("release lock result:" + result);
}
}
} catch (Exception e) {
logger.error("releaseLock failed throw excpetion :{}", e);
} finally {
if (jedis != null) {
jedis.close();
}
}
}
/**
* @Description:
* @Author:wangpeng
* @Date:2017年1月22日
*/
@Override
public void afterPropertiesSet() throws Exception {
WebApplicationContext wac = getWebApplicationContext();
messageJedisPool = (MessageJedisPool) wac.getBean("messageJedisPool");
}
}