首页 > 数据库 >Redis实战之session共享

Redis实战之session共享

时间:2023-02-06 15:00:57浏览次数:67  
标签:code Redis token session user 共享 stringRedisTemplate

当线上集群时候,会出现session共享问题。

虽然Tomcat提供了session copy的功能,但是缺点比较明显:

1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带

2:同一个用户session,需要在多个Tomcat中都存在,浪费内存空间

如果要替换掉Tomcat的session共享,替代方案应该满足:

1:数据共享

2:内存存储

3:key\value结构

基于Redis实现共享session登录

本文由凯哥Java(gz#h:kaigejava),个人blog:www#kaigejava#.com。发布于凯哥Java个人blog

再来回顾下将验证码保存在session中业务流程

Redis实战之session共享_验证码


我们在session中存放的是:session.setAttribute("code", code); 因为session的特点,每次访问都是一个新的sessionId.我们可以直接使用code作为key.思考:那么如果换成了Redis,还能使用code作为可以吗?


将用户信息存放在session中流程:

用户信息在session中存放:session.setAttribute("user", user); 同样思考:那么如果换成了Redis,还能使用user作为可以吗?

将code和user信息存放在Redis中,流程如下:

验证码数据结构是:string类型的

用户对象数据类型是:hash类型的

根据上面分析,我们修改原来代码:

需要考虑的是:Redis的key规则、过期时间

1:发送验证码的时候,将验证码存放到Redis中时候,需要考虑过期时间。其核心代码如下:

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);

2:用户登录的时候,将校验验证码以及用户信息存放到Redis中后,返回token

需要考虑的:

1:token不能重复

2:用户过期时间

3:登录成功后,要将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();
}
}

总结:

在使用Redis替换session的时候,需要考虑的问题:

1:选择合适的数据结构

2:选择合适的key

3:选择合适的存储粒度

​结束语

如操作有问题欢迎去 我的    ​​个人博客()​​留言​​或者  ​ ​​ ​​ ​​ 微信公众号(凯哥Java)​​留言​​交流哦。



标签:code,Redis,token,session,user,共享,stringRedisTemplate
From: https://blog.51cto.com/kaigejava/6039162

相关文章

  • Redis实战之session共享
    当线上集群时候,会出现session共享问题。虽然Tomcat提供了sessioncopy的功能,但是缺点比较明显:1:当Tomcat多的时候,session需要大量同步到多台集群上,占用内网宽带2:同一个用......
  • redis
    数据类型Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sortedset:有序集合)。语法Redis客户端的基本语法为:$redis-cli实例以下实例讲解了如何......
  • 关于NGINX配置来解决CORS跨域资源共享的分析
     跨域主要涉及4个响应头:Access-Control-Allow-Origin用于设置允许跨域请求源地址(预检请求和正式请求在跨域时候都会验证)Access-Control-Allow-Headers跨域允许携带......
  • 解决CentOS缺少共享库:libstdc++.so.6
    当在​​CentOS​​​ 6.2下执行某些命令时,有缺少共享库的报错: errorwhileloadingsharedlibraries:libstdc++.so.6:cannotopensharedobjectfile:Nosuchfil......
  • Redis详解
    Redis配置ymlspring:redis:host:82.157.248.243#host地址port:6379#地址端口号password:#密码database:......
  • 2:redis
    一:redis端口6379,是key-vaule键值对类型数据,数据存储到内存里面,读写速度快,键必须是字符串,值有五种类型二:Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zse......
  • linux安装redis
    linux安装redis1.打开官网找到要安装的版本复制下载连接redis官网wgethttps://github.com/redis/redis/archive/7.0.8.tar.gz#如果提示wget不存在可以使用yuminst......
  • NFS磁盘共享(centos)
    背景:由于生产服务器数据磁盘空间即将满载,找了台磁盘空闲的服务器通过nfs的方式把磁盘共享给生产服务器,用于备份数据或者迁移数据使用。环境:文件服务端:xxx.xxx.xxx.38客......
  • redis哨兵
    节点准备、主从复制配置参考上篇博客一、从解压的redis文件夹下拷贝sentinel.conf哨兵文件至/opt/redis/conf里做公共配置文件cpsentinel.conf/opt/redis/conf/sent......
  • redis进阶
    事务redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列),当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰。一个队列中,一次性,顺序性,排......