上一篇文章我们讲了如何在负载均衡的项目中使用redis来缓存session数据,戳这里。

我们在项目的进展过程中,不仅需要缓存session数据,有时候还需要缓存一些别的数据,比如说,微信的access_token.

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。

1、建议公众号开发者使用中控服务器统一获取和刷新Access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;
2、目前Access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、Access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。

以上是微信开发文档关于access_token的介绍,从上述的介绍可以知道,access_token是一个很普遍需要用到的,几乎所有微信的接口都需要用到,顾名思义,token就是令牌的意思,这是微信服务器给开发者的令牌,有了这个令牌, 你才能做下一步的工作。
1.笔者之前的做法
我之前的做法很不经济,就是上面说的第一条所反对的做法,每次需要访问微信接口的时候,事先去获取(也就是刷新)access_token。代码如下。
 String tokenStr = CommonUtil
.getTokenByUrl(ConfigUtil.TOKEN_URL);
JSONObject tokeJson = JSONObject.fromObject(tokenStr);
access_token = tokeJson.getString("access_token");

第一行中的ConfigUtil.TOKEN_URL为微信的接口

https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+APPID+"&secret="+APP_SECRECT
getTokenByUrl方法就是一个普通的模拟http请求的方法,通过这个方法我们可以获取到token数据,但是代价也是挺大的,假如我需要频繁的调用微信接口,势必会造成性能损失(每次模拟http请求都要时间,而且对微信服务器一种伤害)。
另外一点就是,微信对access_token的请求是有限制的,当项目的流量的小的时候没关系,但是如果流量多了,还用这种方法就会达到限制次数而被禁止访问。
2.改进的做法
由于项目中使用了nginx,若要做token的缓存的话,则必须做全局缓存,与session缓存的类似。
1)首先编写抽象类AbstractBaseRedisDao,代码如下。
package wonyen.mall.dao;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer; public abstract class AbstractBaseRedisDao<K,V> {
protected RedisTemplate<K, V> redisTemplate; public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
this.redisTemplate = redisTemplate;
}
protected RedisSerializer<String> getRedisSerializer(){
return redisTemplate.getStringSerializer();
} }

2)其次编写实现类redisDao,代码如下,该dao用于处理redis的数据库中的键值数据对。

package wonyen.mall.dao;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set; import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.serializer.RedisSerializer; import redis.clients.jedis.Jedis;
import wonyen.mall.constant.SystemConstant; public class RedisDao extends
AbstractBaseRedisDao<String, HashMap<String, Object>> {
/**
* 新增键值对
*
* @param key
* @param value
* @return
*/
public boolean addString(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] jkey = serializer.serialize(key);
byte[] jvalue = serializer.serialize(value);
return connection.setNX(jkey, jvalue);
}
});
return result;
} /**
* 新增(拼接字符串)
*
* @param key
* @param value
* @return
*/
public boolean appendString(final String key, final String value) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] jkey = serializer.serialize(key);
byte[] jvalue = serializer.serialize(value);
if (connection.exists(jkey)) {
connection.append(jkey, jvalue);
return true;
} else {
return false;
}
}
});
return result;
} /**
* 新增(存储Map)
*
* @param key
* @param value
* @return
*/
public String addMap(String key, Map<String, String> map) {
Jedis jedis = getJedis();
String result = jedis.hmset(key, map);
jedis.close();
return result;
} /**
* 获取map
*
* @param key
* @return
*/
public Map<String, String> getMap(String key) {
Jedis jedis = getJedis();
Map<String, String> map = new HashMap<String, String>();
Iterator<String> iter = jedis.hkeys(key).iterator();
while (iter.hasNext()) {
String ikey = iter.next();
map.put(ikey, jedis.hmget(key, ikey).get(0));
}
jedis.close();
return map;
} /**
* 新增(存储List)
*
* @param key
* @param pd
* @return
*/
public void addList(String key, List<String> list) {
Jedis jedis = getJedis();
jedis.del(key); // 开始前,先移除所有的内容
for (String value : list) {
jedis.rpush(key, value);
}
jedis.close();
} /**
* 获取List
*
* @param key
* @return
*/
public List<String> getList(String key) {
Jedis jedis = getJedis();
List<String> list = jedis.lrange(key, 0, -1);
jedis.close();
return list;
} /**
* 新增(存储set)
*
* @param key
* @param set
*/
public void addSet(String key, Set<String> set) {
Jedis jedis = getJedis();
jedis.del(key);
for (String value : set) {
jedis.sadd(key, value);
}
jedis.close();
} /**
* 获取Set
*
* @param key
* @return
*/
public Set<String> getSet(String key) {
Jedis jedis = getJedis();
Set<String> set = jedis.smembers(key);
jedis.close();
return set;
} /**
* 删除 (non-Javadoc)
*
* @see com.fh.dao.redis.RedisDao#delete(java.lang.String)
*/
public boolean delete(final String key) {
boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] jkey = serializer.serialize(key);
if (connection.exists(jkey)) {
connection.del(jkey);
return true;
} else {
return false;
}
}
});
return result;
} /**
* 删除多个 (non-Javadoc)
*
* @see com.fh.dao.redis.RedisDao#delete(java.util.List)
*/
public void delete(List<String> keys) {
redisTemplate.delete(keys);
} /**
* 修改 (non-Javadoc)
*
* @see com.fh.dao.redis.RedisDao#eidt(java.lang.String, java.lang.String)
*/
public boolean eidt(String key, String value) {
if (delete(key)) {
addString(key, value);
return true;
}
return false;
}
/**
* 先删除后添加
* @param key
* @param value
*/
public void del_add(String key, String value){
delete(key);
addString(key, value);
} /**
* 通过key获取值 (non-Javadoc)
*
*
*/
public String get(final String keyId) {
String result = redisTemplate.execute(new RedisCallback<String>() {
public String doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] jkey = serializer.serialize(keyId);
byte[] jvalue = connection.get(jkey);
if (jvalue == null) {
return null;
}
return serializer.deserialize(jvalue);
}
});
return result;
} /**
* 获取Jedis
*
* @return
*/
public Jedis getJedis() {
Properties pros = getPprVue();
String isopen = pros.getProperty("redis_isopen"); // 地址
String host = pros.getProperty("redis_hostName"); // 地址
String port = pros.getProperty("redis_port"); // 端口
String pass = pros.getProperty("redis_password"); // 密码
if ("yes".equals(isopen)) {
Jedis jedis = new Jedis(host, Integer.parseInt(port));
jedis.auth(pass);
return jedis;
} else {
return null;
}
} /**
* 读取redis.properties 配置文件
*
* @return
* @throws IOException
*/
public Properties getPprVue() {
InputStream inputStream = SystemConstant.class.getClassLoader()
.getResourceAsStream("redis.properties");
Properties p = new Properties();
try {
p.load(inputStream);
inputStream.close();
} catch (IOException e) {
// 读取配置文件出错
e.printStackTrace();
}
return p;
} }

3)redis的配置文件,redis.properties

redis_isopen:yes
#主机地址
redis_hostName=xxx.xxx.xxx.xxx
#端口
redis_port=6379
#密码
redis_password=xxxxx
#连接超时时间
redis_timeout=200000
redis_maxIdle=300
redis_maxActive=600
redis_maxWait=100000
redis_testOnBorrow=true

4)spring-redis配置文件

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ">
<!-- session设置 -->
<bean
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="3600"></property>
</bean>
<!-- redis连接池 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis_maxIdle}" />
<property name="testOnBorrow" value="${redis_testOnBorrow}" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- redis连接工厂 -->
<bean id="connectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis_hostName}" />
<property name="port" value="${redis_port}" />
<property name="password" value="${redis_password}" />
<property name="timeout" value="${redis_timeout}" />
<property name="poolConfig" ref="poolConfig"></property>
</bean>
<!-- redisDao -->
<bean id="redisDao" class="wonyen.mall.dao.RedisDao">
<property name="redisTemplate" ref="redisTemplate" />
</bean>
<!-- redisAction -->
<bean id="redisAction" class="wonyen.mall.action.RedisAction"
scope="prototype">
<property name="redisDao" ref="redisDao" />
</bean>
<!-- tokenScan -->
<bean id="tokenScan" class="wonyen.mall.scan.TokenScan">
<property name="redisDao" ref="redisDao" />
</bean>
</beans>

5)现在我们必须开启一个线程,让它每隔一段时间(少于两个小时)去从微信接口获取新的token,然后存储在redis的服务器中,线程类即是上述配置的rokenScan,如下所示。

package wonyen.mall.scan;

import net.sf.json.JSONObject;
import wonyen.mall.dao.RedisDao;
import wonyen.yipin.wechat.CommonUtil;
import wonyen.yipin.wechat.ConfigUtil; /**
* 扫描微信token的线程
* 启动时候获取token,然后每隔45分钟刷新一次token
* @author xdx
*
*/
public class TokenScan implements Runnable{
public boolean run = true;// 线程开关
private static final int cycle=45;//刷新周期,45min刷新一次
private RedisDao redisDao;
public void setRedisDao(RedisDao redisDao) {
this.redisDao = redisDao;
} @Override
public void run() {
while(run){
String tokenStr = CommonUtil.getTokenByUrl(ConfigUtil.TOKEN_URL);
JSONObject tokeJson = JSONObject.fromObject(tokenStr);
if (tokeJson.containsKey("access_token")) {
String access_token=tokeJson.getString("access_token");
redisDao.del_add("access_token", access_token);
String ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="
+ access_token + "&type=jsapi";
String ticketStr = CommonUtil.getTokenByUrl(ticketUrl);
JSONObject ticketJson = JSONObject.fromObject(ticketStr);
if(ticketJson.containsKey("ticket")){
String jsapi_ticket=ticketJson.getString("ticket");
redisDao.del_add("jsapi_ticket", jsapi_ticket);
}
//api_ticket
String apiTicketUrl="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+access_token+"&type=wx_card";
String apiTickerStr=CommonUtil.getTokenByUrl(apiTicketUrl);
JSONObject apiTicketJson=JSONObject.fromObject(apiTickerStr);
if(apiTicketJson.containsKey("ticket")){
String api_ticket=apiTicketJson.getString("ticket");
redisDao.del_add("api_ticket", api_ticket);
}
}
try {
Thread.sleep(cycle*60*1000);//
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } }

在这个线程中,我不仅仅把access_token存入redis,还将jsapi_ticket和apit_ticket也都存入了redis中,每隔45分钟更新一次。我们可以专门做一个线程(与主项目分开)来执行这段代码,这样比较不会影响主项目的性能。

6)读取redis中的数据,我们既然已经把token等数据放入了redis,接下来就是将他们取出来,很简单,同样是调用redisDao里面的方法。

package wonyen.yipin.service;

import net.sf.json.JSONObject;
import wonyen.mall.dao.RedisDao;
import wonyen.yipin.wechat.CommonUtil;
import wonyen.yipin.wechat.ConfigUtil; public class WxService {
private RedisDao redisDao; public void setRedisDao(RedisDao redisDao) {
this.redisDao = redisDao;
} /**
* 获取微信jsapi_ticket
*
* @return
*/
public String getJsapiTicket() {
String jsapi_ticket = redisDao.get("jsapi_ticket");
if (jsapi_ticket == null) {
String access_token;
if (redisDao.get("access_token") != null) {
access_token = redisDao.get("access_token");
} else {
String tokenStr = CommonUtil
.getTokenByUrl(ConfigUtil.TOKEN_URL);
JSONObject tokeJson = JSONObject.fromObject(tokenStr);
access_token = tokeJson.getString("access_token");
}
String ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="
+ access_token + "&type=jsapi";
String ticketStr = CommonUtil.getTokenByUrl(ticketUrl);
JSONObject ticketJson = JSONObject.fromObject(ticketStr);
if (ticketJson.containsKey("ticket")) {
jsapi_ticket = ticketJson.getString("ticket");
}
}
return jsapi_ticket;
}
public String getApiTicket(){
String api_ticket = redisDao.get("api_ticket");
if(api_ticket == null){
String access_token;
if(redisDao.get("access_token")!=null){
access_token = redisDao.get("access_token");
}else{
String tokenStr = CommonUtil
.getTokenByUrl(ConfigUtil.TOKEN_URL);
JSONObject tokeJson = JSONObject.fromObject(tokenStr);
access_token = tokeJson.getString("access_token");
}
String ticketUrl="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+access_token+"&type=wx_card";
String ticketStr=CommonUtil.getTokenByUrl(ticketUrl);
JSONObject ticketJson = JSONObject.fromObject(ticketStr);
if (ticketJson.containsKey("ticket")) {
api_ticket = ticketJson.getString("ticket");
}
}
return api_ticket;
} }

上述两个方法分别是从redis中获取jsapi_ticket和api_ticket的方法,我们先从redis中直接去取,当取不到的时候我们才调用原始的微信接口去取,这样就不用频繁的去请求微信接口了。更重要的一点,因为我们用了redis,是全局的缓冲,在每个负载均衡的分支上都是同步的。

我们可以看看redis的IDE中的数据。

虽然看不懂,但是他确实已经存进去了。

nginx+redis缓存微信的token数据的更多相关文章

  1. 基于Python项目的Redis缓存消耗内存数据简单分析(附详细操作步骤)

    目录 1 准备工作 2 具体实施   1 准备工作 什么是Redis? Redis:一个高性能的key-value数据库.支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使 ...

  2. 如何使用Senparc.Weixin SDK 底层的Redis缓存并设置过期时间

    最近在微信第三方平台项目开发中,有一个需求,所有绑定的公众号的回复规则按照主公众号的关键词配置来处理,我的处理思路是获取主公众号配置的关键词回复规则,缓存10分钟,由于需要使用Redis缓存来存储一些 ...

  3. Redis缓存击穿、缓存穿透、缓存雪崩

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 上篇文章谈到了Redis分布式锁,实际上就是为了解释为什么做缓存采用Redis而不使用map/guava.缓存 ...

  4. 高可用Redis(十三):Redis缓存的使用和设计

    1.缓存的受益和成本 1.1 受益 1.可以加速读写:Redis是基于内存的数据源,通过缓存加速数据读取速度 2.降低后端负载:后端服务器通过前端缓存降低负载,业务端使用Redis降低后端数据源的负载 ...

  5. asp.net性能优化之使用Redis缓存(入门)

    1:使用Redis缓存的优化思路 redis的使用场景很多,仅说下本人所用的一个场景: 1.1对于大量的数据读取,为了缓解数据库的压力将一些不经常变化的而又读取频繁的数据存入redis缓存 大致思路如 ...

  6. SpringBoot缓存管理(三) 自定义Redis缓存序列化机制

    前言 在上一篇文章中,我们完成了SpringBoot整合Redis进行数据缓存管理的工作,但缓存管理的实体类数据使用的是JDK序列化方式(如下图所示),不便于使用可视化管理工具进行查看和管理. 接下来 ...

  7. 结合场景使用Redis缓存与数据库同步

    Redis缓存与MySQL数据库与同步 什么场景用到了Redis缓存? 1.广告数据 2.搜索时,分类品牌名称,分类名称和规格数据 3.购物车 4.支付 问题:如何实现? 1.广告数据 先查询Redi ...

  8. [转]在nodejs使用Redis缓存和查询数据及Session持久化(Express)

    本文转自:https://blog.csdn.net/wellway/article/details/76176760 在之前的这篇文章 在ExpressJS(NodeJS)中设置二级域名跨域共享Co ...

  9. 接入层高性能缓存技术nginx+redis利器OpenResty

    一. OpenRestyOpenResty是一个基于 Nginx与 Lua的高性能 Web平台,其内部集成了大量精良的 Lua库.第三方模块以及大多数的依赖项.用于方便地搭建能够处理超高并发.扩展性极 ...

随机推荐

  1. C语言 第三章 关系、逻辑运算与分支流程控制

    目录 一.关系运算 二.逻辑运算 三.运算优先级 四.if语句 4.0.代码块 4.1.单if语句 4.2.if else 4.3.多重if 4.4.?号:号表达式 五.switch语句 一.关系运算 ...

  2. 如何将FastReportOnlineDesign 灵活的应用到C/S B/S 程序当中?

    一.好久没有写博客了,主要是停在这里太久了,有些事情让自己尽量不在去想,忘记不了一段难以忘怀的记忆,就让这一段美好的记忆沉没在无锡的太湖中吧!不在去想了.难以忘怀..... 二.废话不多说了,不如正题 ...

  3. [转载] epoll详解

    转载自http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句 ...

  4. 【转】jQuery代码片段备用

    在CSDN看到的,记下备用.原文:http://www.csdn.net/article/2013-07-16/2816238-15-jquery-code-snippets-for-develope ...

  5. ASP.NET Core缓存静态资源

    背景 缓存样式表,JavaScript或图像文件等静态资源可以提高您网站的性能.在客户端,总是从缓存中加载一个静态文件,这样可以减少对服务器的请求数量,从而减少获取页面及其资源的时间.在服务器端,由于 ...

  6. Java并发之线程管理(线程基础知识)

    因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...

  7. Python网络编程篇之select和epoll

    1. select 原理 在多路复⽤的模型中, ⽐较常⽤的有select模型和epoll模型. 这两个都是系统接⼝, 由操作系统提供. 当然, Python的select模块进⾏了更⾼级的封装. ⽹络 ...

  8. postgresql的psql常用命令-4

    psql是PostgreSQL的一个命令行交互式客户端工具 1. 查看postgresql账号 [root@localhost ~]#cat /etc/passwdroot:x:0:0:root:/r ...

  9. Makefile Android.mk 引发的思索

    在我们编写 Android 平台 cocos2d-x 游戏的时候,我们除了编写 Classes 之内的源代码文件之外,我们还需要维护其编译文件 Android.mk,如我们在 Classes 添加新的 ...

  10. propertychange 不起作用

    兼容性问题.将$("#systemLogSettings td[name='nMaxFileSize'] input").on("input propertychange ...