• 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中的数据。

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

     

  • 相关阅读:
    反编译工具
    3.25Java变量
    标识符
    OMS系统
    java打印方法区别
    注释
    写代码时候要注意的两个点
    python_pracyoce_day1
    SKU和SPU
    文档注释
  • 原文地址:https://www.cnblogs.com/roy-blog/p/7201383.html
Copyright © 2020-2023  润新知