redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
正式接触Redis是做用户登录控制。有个场景,每个用户登录时后台生成唯一Token,保存至Redis中,并将此Token返回给前台。此后,前台每次发送请求时在URL的Header中携带此Token,后台验证此Token是否在Redis中存在,存在即验证通过。大致需求完成后,有一些小细节需要处理。例如,要求用户2小时不操作页面便自动退出;后台日志要记录每位用户登录登出时间;业务方面要求标签信息存储在缓存中,便于标签的交集,并集查询;此时,Redis就派上用场了。
使用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 }
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。保存至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 }
后台日志系统要记录用户登录退出操作。对于正常退出用户可以在退出时写日志到数据库,非正常退出用户就无法记录日志。使用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); } } }
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 }
此次操作基本设计业务操作,此处便不做详细介绍。下面贴一个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 }
关于Redis集群在liunx环境中配置及Java配置Redis,大家请自行百度--- 由于此次是自己第一次写Blog,有很多写的不好的地方,愿见谅。本文章全是手打,项目来自自己的小Demo。期待自己下次写会有进步-----
原文:https://www.cnblogs.com/xiaobingblog/p/11417579.html