Redis实战之session共享
- Redis
- 时间:2023-02-06 14:02
- 5148人已阅读
🔔🔔🔔好消息!好消息!🔔🔔🔔
有需要的朋友👉:联系凯哥
虽然Tomcat提供了session copy的功能,但是缺点比较明显:
1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带
2:同一个用户session,需要在多个Tomcat中都存在,浪费内存空间
如果要替换掉Tomcat的session共享,替代方案应该满足:
基于Redis实现共享session登录
本文由凯哥Java(gz#h:kaigejava),个人blog:www#kaigejava#.com。发布于凯哥Java个人blog
我们在session中存放的是:session.setAttribute("code", code); 因为session的特点,每次访问都是一个新的sessionId.我们可以直接使用code作为key.思考:那么如果换成了Redis,还能使用code作为可以吗?
用户信息在session中存放:session.setAttribute("user", user); 同样思考:那么如果换成了Redis,还能使用user作为可以吗?
验证码数据结构是:string类型的
1:发送验证码的时候,将验证码存放到Redis中时候,需要考虑过期时间。其核心代码如下:
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
2:用户登录的时候,将校验验证码以及用户信息存放到Redis中后,返回token
4:用户只要访问,Redis中的过期时间就要延长-在拦截器中处理的
//2.1:校验验证码是否正确 //String code = (String) session.getAttribute("code"); String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone); if (StringUtils.isEmpty(code) || !code.equals(loginForm.getCode())) { return Result.fail("验证码错误!"); } //2.2:根据手机号查询,如果不存在,创建新用户 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select("id", "phone", "nick_name"); queryWrapper.eq("phone", phone); User user = this.getOne(queryWrapper); if (Objects.isNull(user)) { log.info("新建用户"); //新建用户 user = createUserWithPhone(phone); } //2.3:保存用户到session中 UserDTO userDTO = new UserDTO(); userDTO.setId(user.getId()); userDTO.setIcon(user.getIcon()); userDTO.setNickName(user.getNickName()); //session.setAttribute("user", userDTO); //2.3.1:获取随机的token,作为用户登录的令牌 String token = UUID.randomUUID().toString(true); //2.3.2:将用户以hash类型存放到Redis中==》将user对象转换成map //user对象里有非string类型的字段,用这个方法会报错的 // Map<String,Object> userMap = BeanUtil.beanToMap(userDTO); Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>() , CopyOptions.create() .setIgnoreNullValue(true) .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString())); stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,userMap); //LOGIN_USER_TOKEN_TTL stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,LOGIN_USER_TOKEN_TTL,TimeUnit.MINUTES); //2.3.3: 将token返回 return Result.ok(token);
需要注意:
在使用stringRedisTemplate存放hash对象的时候,对象中所有的key只能是string类型,如果存在非string类型会报错的。所以这里使用了hootool的BeanUtil工具类:
Map<String,Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>() , CopyOptions.create() .setIgnoreNullValue(true) .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
拦截器修改代码:
因为拦截器是我们自定义的,所以不能被spring容器管理的,RedisTemplate就不能自动注入了。我们就使用有参构造器,传递:
public class LoginRedisInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; /** * 因为这个类不能被spring管理,所以不能直接注入RedisTemplate对象。通过构造函数传递 * @param stringRedisTemplate */ public LoginRedisInterceptor(StringRedisTemplate stringRedisTemplate){ this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1:从请求中获取到token String token = request.getHeader("authorization"); if(StringUtils.isEmpty(token)){ response.setStatus(401); return false; } //2:基于token获取redis中用户对象 String key = LOGIN_USER_TOKEN_KEY+token; Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key); //3:判断 if(userMap.isEmpty()){ response.setStatus(401); return false; } //将map转对象 UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); UserHolder.saveUser(user); //刷新token的过期时间 stringRedisTemplate.expire(key,LOGIN_USER_TOKEN_TTL, TimeUnit.MINUTES); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } }
总结: