Redis应用
2019-08-27 07:13:45来源:博客园 阅读 ()
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。期待自己下次写会有进步-----
原文链接:https://www.cnblogs.com/xiaobingblog/p/11417579.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- redis缓存 2020-06-12
- Spring Boot 2.3.0 新特性Redis 拓扑动态感应 2020-06-11
- 作为一个面试官,我想问问你Redis分布式锁怎么搞? 2020-06-10
- 如何优雅地停止 Spring Boot 应用? 2020-06-08
- 分布式锁没那么难,手把手教你实现 Redis 分布锁!|保姆级教 2020-06-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash