nginx+redis缓存微信的token数据

上一篇文章我们讲了如何在负载均衡的项目中使用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。代码如下。
1 String tokenStr = CommonUtil
2                         .getTokenByUrl(ConfigUtil.TOKEN_URL);
3                 JSONObject tokeJson = JSONObject.fromObject(tokenStr);
4                 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);//3600000
            } 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中的数据。

虽然看不懂,但是他确实已经存进去了。
时间: 2024-10-14 10:46:16

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

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

目录 1 准备工作 2 具体实施   1 准备工作 什么是Redis? Redis:一个高性能的key-value数据库.支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用:提供string.list.set.zset.hash等数据结构的存储,并支持数据的备份. 本文适合使用的场景:当一个项目中Redis缓存的数据量逐渐增大,Redis缓存的数据占用内存也会越来越大,而且其中有很多很可能是价值不大的数据.由于Redis是一个key-value数据库,所以对其中的数

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

1:使用Redis缓存的优化思路 redis的使用场景很多,仅说下本人所用的一个场景: 1.1对于大量的数据读取,为了缓解数据库的压力将一些不经常变化的而又读取频繁的数据存入redis缓存 大致思路如下:执行一个查询 1.2首先判断缓存中是否存在,如存在直接从Redis缓存中获取. 1.3如果Redis缓存中不存在,实时读取数据库数据,同时写入缓存(并设定缓存失效的时间). 1.4缺点,如果直接修改了数据库的数据而又没有更新缓存,在缓存失效的时间内将导致读取的Redis缓存是错误的数据. 2:R

redis缓存服务器

redis 缓存数据库 1.1 redis 的简单介绍 Redis是一个开源(BSD许可)的,ANSI C语言编写的,高级键值(key-value)缓存和支持永久存储NoSql数据库产品. 内存中的数据结构存储系统,他可以用作数据库.缓存和消息中间件. 它支持多种数据类型.字符串(string).字典(hash).列表(list).集合(set).有序集合(sorted set) 运行于大多数POSIX系统,如Linux.*BSD.OS X等. 基本配合后端数据库使用,存放的只是用户当前频繁调去

redis缓存服务器(Nginx+Tomcat+redis+MySQL实现session会话共享)

一.redis介绍 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现master-slave(主从)同步. Redis是一个高性能的key-valu

redis缓存mysql数据

redis (Remote Dictionary Server)是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件.它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).Redis支持主从同步.数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器.这使得Redis可执行单层树复制. MySQL和Redis,自身都

图文并茂超详细搭建redis缓存服务器(nginx+tomcat+redis+mysql实现session会话共享)

博主QQ:819594300 博客地址:http://zpf666.blog.51cto.com/ 有什么疑问的朋友可以联系博主,博主会帮你们解答,谢谢支持! 一.redis介绍 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会

Nginx+Redis+Ehcache:大型高并发与高可用的三层缓存架构总结

摘要: 对于高并发架构,毫无疑问缓存是最重要的一环,对于大量的高并发,可以采用三层缓存架构来实现,nginx+redis+ehcache Nginx 对于中间件nginx常用来做流量的分发,同时nginx本身也有自己的缓存(容量有限),我们可以用来缓存热点数据,让用户的请求直接走缓存并返回,减少流向服务器的流量 一.模板引擎 通常我们可以配合使用freemaker/velocity等模板引擎来抗住大量的请求 小型系统可能直接在服务器端渲染出所有的页面并放入缓存,之后的相同页面请求就可以直接返回,

【转】Nginx学习---Nginx&amp;&amp;Redis&amp;&amp;hcache三层缓存架构总结

[原文]https://www.toutiao.com/i6594307974817120782/ 摘要: 对于高并发架构,毫无疑问缓存是最重要的一环,对于大量的高并发,可以采用三层缓存架构来实现,nginx+redis+ehcache Nginx 对于中间件nginx常用来做流量的分发,同时nginx本身也有自己的缓存(容量有限),我们可以用来缓存热点数据,让用户的请求直接走缓存并返回,减少流向服务器的流量 一.模板引擎 通常我们可以配合使用freemaker/velocity等模板引擎来抗住

nginx+redis 实现 jsp页面缓存,提升系统吞吐率

最近在开发的时候,发现之前APP客户端的一部分页面用的是webview交互,这些页面请求很多,打开一套试卷,将会产生100+的请求量,导致系统性能下降.于是考虑在最靠近客户端的Nginx服务器上做Redis缓存.综合了下网上对于php缓存的资料,经过一番改动,终于搭建成功.由于网上的是针对php的,而且没有说明,对于我这种完全不动运维的人来说,研究下来还是挺痛苦的.所以整理一份比较完整的,供大家参考. 以下的配置中,可能有不适合或者写的有问题的.请留言指出,谢谢! 最终缓存以后,整个项目结构图如