Redis应用

Posted xiaobingblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis应用相关的知识,希望对你有一定的参考价值。

1. 定义

  redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

2. Redis在用户登录时的应用

  正式接触Redis是做用户登录控制。有个场景,每个用户登录时后台生成唯一Token,保存至Redis中,并将此Token返回给前台。此后,前台每次发送请求时在URL的Header中携带此Token,后台验证此Token是否在Redis中存在,存在即验证通过。大致需求完成后,有一些小细节需要处理。例如,要求用户2小时不操作页面便自动退出;后台日志要记录每位用户登录登出时间;业务方面要求标签信息存储在缓存中,便于标签的交集,并集查询;此时,Redis就派上用场了。

2.1 Redis保存用户登录Token

2.1.1 JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案

  使用JSON Web Token(JWT)根据随机sessionID,用户名ID,用户名Name生成唯一Token。关于JWT详细介绍,请自行百度---;此次大致理解为用一串随机数,ID,Name生成一个密码,此密码就是token,密码可以解码获取原先的sessionID,ID,Name;下面贴一个我自己常用的JWT工具类和简单Redis工具类。

技术图片
  1 package com.boot.util;
  2 
  3 import com.auth0.jwt.JWT;
  4 import com.auth0.jwt.algorithms.Algorithm;
  5 import com.auth0.jwt.exceptions.JWTCreationException;
  6 import com.auth0.jwt.exceptions.JWTDecodeException;
  7 import com.auth0.jwt.interfaces.DecodedJWT;
  8 import org.apache.log4j.Logger;
  9 
 10 import java.io.UnsupportedEncodingException;
 11 import java.util.Date;
 12 
 13 public class JwtParseUtil 
 14 private  static Logger LOGGER = Logger.getLogger(JwtParseUtil.class);
 15 
 16     public static final String createJWT(String userName,long sessionID,int userId)
 17         String token = null;
 18         try
 19             //参数非空判断
 20             Algorithm alg = Algorithm.HMAC256(userName); 
 21             token = JWT.create()
 22                     .withJWTId(String.valueOf(sessionID))
 23                     .withSubject(userName)
 24                     .withClaim("userId", userId)
 25                     .withIssuedAt(new Date())
 26                     .sign(alg);
 27         catch(UnsupportedEncodingException unsuptEncode)
 28             LOGGER.info("不支持UTF-8编码");
 29               //UTF-8 encoding not supported
 30         catch(JWTCreationException jwtCreateExp)
 31             LOGGER.info("签名配置无效,或者是无法转换请求");
 32             //Invalid Signing configuration / Couldn‘t convert Claims.
 33         
 34         return token;
 35         
 36     
 37     /**
 38      * token解析
 39      * */
 40     private static  DecodedJWT parseJWT(String token)
 41         try
 42             DecodedJWT jwt= JWT.decode(token);
 43             return jwt;
 44         catch(JWTDecodeException   exp)
 45             //Invalid token
 46             return null;
 47         
 48     
 49     
 50     /**
 51      * 根据token获取用户名
 52      * */
 53     public static String getUserNameByJWT(String token)
 54         if (token==null) 
 55              throw new MessageException(MessageException.USER_INFO_OUTTIME,null);
 56         
 57         DecodedJWT jwt = parseJWT(token);
 58         if(  jwt != null )
 59             return jwt.getSubject();
 60         else
 61             return null;
 62         
 63      
 64     
 65     /**
 66      * 根据token获取sessionId
 67      */
 68     public static long getSessionIdByJWT(String token)
 69         if(token == null)
 70             return 0;
 71         
 72         DecodedJWT jwt = parseJWT(token);
 73         if(  jwt != null )
 74             return Long.valueOf(jwt.getId());
 75         else
 76             return 0;
 77         
 78         
 79     
 80 
 81     public static int getUserIdByJWT(String token)
 82         if (token==null) 
 83              throw new MessageException(MessageException.USER_INFO_OUTTIME,null);
 84         
 85         DecodedJWT jwt = parseJWT(token);
 86         if(  jwt != null )
 87             return jwt.getClaim("userId").asInt();
 88         else
 89             return 0;
 90         
 91     
 92 
 93     /**
 94      * 生成唯一的sessionID
 95      * @return
 96      */
 97     public static long createSessionId()
 98         
 99         long now = System.currentTimeMillis();//一个13位的时间戳
100         
101         int r=(int)((Math.random()*9+1)*10000);
102     
103         String paymentID =String.valueOf(now)+String.valueOf(r);// sessionID
104         return Long.valueOf(paymentID);
105         
106     
107     
108 
Jwt生成Token工具类
技术图片
  1 package com.boot.util;
  2 
  3 import com.auth0.jwt.JWT;
  4 import com.auth0.jwt.algorithms.Algorithm;
  5 import com.auth0.jwt.exceptions.JWTCreationException;
  6 import com.auth0.jwt.exceptions.JWTDecodeException;
  7 import com.auth0.jwt.interfaces.DecodedJWT;
  8 import org.apache.log4j.Logger;
  9 
 10 import java.io.UnsupportedEncodingException;
 11 import java.util.Date;
 12 
 13 public class JwtParseUtil 
 14 private  static Logger LOGGER = Logger.getLogger(JwtParseUtil.class);
 15 
 16     public static final String createJWT(String userName,long sessionID,int userId)
 17         String token = null;
 18         try
 19             //参数非空判断
 20             Algorithm alg = Algorithm.HMAC256(userName); 
 21             token = JWT.create()
 22                     .withJWTId(String.valueOf(sessionID))
 23                     .withSubject(userName)
 24                     .withClaim("userId", userId)
 25                     .withIssuedAt(new Date())
 26                     .sign(alg);
 27         catch(UnsupportedEncodingException unsuptEncode)
 28             LOGGER.info("不支持UTF-8编码");
 29               //UTF-8 encoding not supported
 30         catch(JWTCreationException jwtCreateExp)
 31             LOGGER.info("签名配置无效,或者是无法转换请求");
 32             //Invalid Signing configuration / Couldn‘t convert Claims.
 33         
 34         return token;
 35         
 36     
 37     /**
 38      * token解析
 39      * */
 40     private static  DecodedJWT parseJWT(String token)
 41         try
 42             DecodedJWT jwt= JWT.decode(token);
 43             return jwt;
 44         catch(JWTDecodeException   exp)
 45             //Invalid token
 46             return null;
 47         
 48     
 49     
 50     /**
 51      * 根据token获取用户名
 52      * */
 53     public static String getUserNameByJWT(String token)
 54         if (token==null) 
 55              throw new MessageException(MessageException.USER_INFO_OUTTIME,null);
 56         
 57         DecodedJWT jwt = parseJWT(token);
 58         if(  jwt != null )
 59             return jwt.getSubject();
 60         else
 61             return null;
 62         
 63      
 64     
 65     /**
 66      * 根据token获取sessionId
 67      */
 68     public static long getSessionIdByJWT(String token)
 69         if(token == null)
 70             return 0;
 71         
 72         DecodedJWT jwt = parseJWT(token);
 73         if(  jwt != null )
 74             return Long.valueOf(jwt.getId());
 75         else
 76             return 0;
 77         
 78         
 79     
 80 
 81     public static int getUserIdByJWT(String token)
 82         if (token==null) 
 83              throw new MessageException(MessageException.USER_INFO_OUTTIME,null);
 84         
 85         DecodedJWT jwt = parseJWT(token);
 86         if(  jwt != null )
 87             return jwt.getClaim("userId").asInt();
 88         else
 89             return 0;
 90         
 91     
 92 
 93     /**
 94      * 生成唯一的sessionID
 95      * @return
 96      */
 97     public static long createSessionId()
 98         
 99         long now = System.currentTimeMillis();//一个13位的时间戳
100         
101         int r=(int)((Math.random()*9+1)*10000);
102     
103         String paymentID =String.valueOf(now)+String.valueOf(r);// sessionID
104         return Long.valueOf(paymentID);
105         
106     
107     
108 
RedisUtil工具类

2.1.2 SpringBoot请求拦截控制 

  用户登录时,验证用户名密码通过后。使用JWT生成唯一Token。保存至Redis中(key->生成的sessionID,value->生成的Token),设置Redis过期时间为2小时。以后每次用户发送请求时,都会拦截此Token。若Redis中不存在此Token,返回用户登录过期错误。若存在Token,会更新此Token过期时间为2小时。这样,解决了第一个问题,要求用户2小时不操作页面便自动退出。下面贴一个用户登录及请求拦截代码。

技术图片
 1 public String userLogin(UserInfo userInfo) 
 2 
 3         String oaName = userInfo.getOaName();
 4         String pswd = userInfo.getPswd();
 5         //判断用户名密码不为空
 6         if(oaName == null || "".equals(oaName) || pswd == null || "".equals(pswd))
 7             throw new MessageException(MessageException.NO_EMPTY,MessageException.NO_EMPTY_MESSAGE);
 8         
 9         //数据库验证
10         List<UserInfo> userInfos = userInfoMapper.getUserByLogin(oaName,pswd);
11         if(userInfos.size() == 0) 
12             // 抛出自定义异常
13             throw new MessageException(MessageException.LOGGING_ERROR,MessageException.LOGGING_ERROR_MESSAGE);
14         
15         //生成session,token
16         Long sessionID = JwtParseUtil.createSessionId();
17         System.out.println("####登录用户session:"+sessionID+"####");
18         String token = JwtParseUtil.createJWT(userInfos.get(0).getOaName(),sessionID,userInfos.get(0).getUserId());
19 
20         // 获取登录IP(自己写的登录日志记录功能,可忽略)
21         String ipAddress = IpUtil.getIpAddress(getHttpServletRequest());
22 
23         // 插入登录日志中
24         UserLoginStatistics userLoginStatistics = new UserLoginStatistics(sessionID,userInfos.get(0).getUserId(),new Date().getTime(),ipAddress,null);
25         userLoginStatisticsMapper.insert(userLoginStatistics);
26 
27         // 用户Token存放在Redis中,Constant.TOKEN_EXPIRE_TIME为7200L,即2小时
28 
29         redisUtil.set(sessionID+"_UTOKEN",token, Constant.TOKEN_EXPIRE_TIME);
30 
31         
32         return token;
33     
登录代码
技术图片
 1 package com.boot.beanConfig;
 2 
 3 import com.boot.util.IpUtil;
 4 import com.boot.util.JwtParseUtil;
 5 import com.boot.util.MessageException;
 6 import com.boot.util.RedisUtil;
 7 import org.springframework.beans.factory.BeanFactory;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.context.annotation.Configuration;
10 import org.springframework.web.context.support.WebApplicationContextUtils;
11 import org.springframework.web.servlet.HandlerInterceptor;
12 import org.springframework.web.servlet.ModelAndView;
13 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
14 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
15 
16 import javax.annotation.Resource;
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 
20 import static com.boot.util.MessageException.SYSTEM_UPDATE;
21 import static com.boot.util.MessageException.SYSTEM_UPDATE_MESSAGE;
22 
23 /**
24  * @Author  xiabing5
25  * @Create  2019/7/11 19:04
26  * @Desc 访问拦截器配置
27  **/
28 @Configuration
29 public class WebInterceptorConfig extends WebMvcConfigurerAdapter 
30     @Resource
31     private RedisUtil redisUtil;
32 
33     @Override
34     public void addInterceptors(InterceptorRegistry registry) 
35 
36         //注册自定义拦截器,添加拦截路径和排除拦截路径
37         registry.addInterceptor(new RequestInterceptorConfig()).addPathPatterns("/RestService/**").excludePathPatterns("/RestService/userRestService/userLogin","/RestService/userRestService/forgetPassword","/RestService/userRestService/updateOwnPswd");
38     
39 
40     /**
41      * @Author  xiabing5
42      * @Create  2019/7/11 19:33
43      * @Desc token验证
44      **/
45     public class RequestInterceptorConfig implements HandlerInterceptor 
46 
47         @Override
48         public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception 
49             // 防止复杂请求先发的options请求被拦截
50             if("OPTIONS".equals(httpServletRequest.getMethod())) 
51                 return true;
52             
53 
54             System.out.println("#######进入权限验证#######");
55             String token = httpServletRequest.getHeader("token");
56 
57             Long sessionID = JwtParseUtil.getSessionIdByJWT(token);
58             if( !redisUtil.exists(sessionID+"_UTOKEN"))
59                 throw new MessageException(MessageException.USER_INFO_OUTTIME,MessageException.USER_INFO_OUTTIME_MESSAGE);
60             
61 
62             if(token == null || "".equals(token)) 
63                 System.out.println("#######被拦截#######");
64                 throw new MessageException(MessageException.NO_LOGIN,MessageException.NO_LOGIN_MESSAGE);
65             
66             return true;
67         
68 
69         @Override
70         public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception 
71 
72         
73 
74         @Override
75         public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception 
76 
77         
78     
79 
80 
SpringBoot请求拦截

2.2 Redis中key过期监听

  后台日志系统要记录用户登录退出操作。对于正常退出用户可以在退出时写日志到数据库,非正常退出用户就无法记录日志。使用Redis的key过期监听功能,后台订阅Redis的过期消息通知,Token过期时,后台就会监听此token,通过JWT解析出用户ID和sessionID,写入日志。这样就解决了第二个问题,后台日志要记录每位用户登录登出时间。下面贴一个Redis的Key过期监听配置。

技术图片
package com.boot.listen;

import com.boot.mapper.UserLoginStatisticsMapper;
import com.boot.pojo.UserLoginStatistics;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author  xiabing5
 * @Create  2019/8/6 14:53
 * @Desc    过期监听操作
 **/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener 

    @Autowired
    private UserLoginStatisticsMapper userLoginStatisticsMapper;

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) 
        super(listenerContainer);
    

    @Override
    public void onMessage(Message message, byte[] pattern) 
        // 用户做自己的业务处理即可,message.toString()可以获取失效的key
        String expiredKey = message.toString();
        // 捕获超时退出的token
        if(expiredKey.contains("_UTOKEN")) 
            String session = expiredKey.substring(expiredKey.indexOf("1"),expiredKey.indexOf("_"));
            if(session != null) 
                try
                    Long sessionId = Long.valueOf(session);
                    UserLoginStatistics userLoginStatistics = new UserLoginStatistics(sessionId,null,null,null,new Date().getTime());
                    userLoginStatisticsMapper.update(userLoginStatistics);
                catch (Exception e) 
                    e.printStackTrace();
                
            
            System.out.println("#########清除你#########"+session);
        
    
Redis过期监听
技术图片
 1 package com.boot.beanConfig;
 2 
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.context.annotation.Configuration;
 5 import org.springframework.data.redis.connection.RedisConnectionFactory;
 6 import org.springframework.data.redis.listener.PatternTopic;
 7 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
 8 
 9 /**
10  * @Author  xiabing5
11  * @Create  2019/8/6 14:46
12  * @Desc    监听redis中Key过期事件
13  **/
14 @Configuration
15 public class RedisListenerConfig 
16     @Bean
17     RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) 
18 
19         RedisMessageListenerContainer container = new RedisMessageListenerContainer();
20         container.setConnectionFactory(connectionFactory);
21         //container.addMessageListener(new RedisKeyExpirationListener(container), new PatternTopic("__keyevent@0__:expired"));
22         return container;
23     
24 
Redis过期监听

2.3  项目初始化时将数据库指定数据返回Redis中

  此次操作基本设计业务操作,此处便不做详细介绍。下面贴一个SpringBoot初始化执行业务的接口。

技术图片
 1 package com.boot.beanConfig;
 2 
 3 import org.springframework.boot.CommandLineRunner;
 4 import org.springframework.stereotype.Component;
 5 
 6 /**
 7  * @Author  xiabing5
 8  * @Create  2019/8/27 10:04
 9  * @Desc    容器启动时执行方法
10  **/
11 @Component
12 public class InitRunner implements CommandLineRunner
13 
14     @Override
15     public void run(String... strings) throws Exception 
16         System.out.println("###执行方法###");
17     
18 
19     // 存放lable的key-value键值对
20     private static void putInfoToRedis() 
21 
22     
23 
SpringBoot初始化

3. 总结

  关于Redis集群在liunx环境中配置及Java配置Redis,大家请自行百度--- 由于此次是自己第一次写Blog,有很多写的不好的地方,愿见谅。本文章全是手打,项目来自自己的小Demo。期待自己下次写会有进步-----

 

以上是关于Redis应用的主要内容,如果未能解决你的问题,请参考以下文章

Redis工业生产应用场景

应用部署到服务器redis无法读取数据?

Redis应用场景

电商中mq和redis应用场景是啥样的

redis常见应用场景

redis之管道应用场景及源码分析