Redis修炼 (6.redis代替session实现短信登录和校验)

理解http session

其实曾经在很长的一段时间里面,在前后端的交互中, 我一直很晕 这个 http session 到底是个是个啥. 为什么每次 controller 方法的入参 里面都有request来取前端传过来的参数,这个好理解 但是为什么还会有个httpsession 它里面的数据时哪里来的, 往他里面set数据又是为什么?


情景一
为了方便理解这个 我们来一个最基本的短信登录场景。
假设有个app, 有个用户要登陆它:

登录,然后看一下自己的个人页面 就2步。

你登录的时候 前端发一个请求给后端 带上你的用户名aaa密码123 然后登录成功了

这时候你要点个人页面了,,,,,

停! 这里问题来了 Http协议是没有记忆的 无状态的。这个请求和上一个请求之间什么关系都没有 上一个请求的过程它不会做保存,请求完它就把上一个请求忘记了,就像你谈一个妹子的时候 就忘记了上一个妹子。 既然它忘记了 那对不起 你现在要点击个人页面 我怎么知道 你是刚才的aaa?

没办法 这时候前端 又得传用户名aaa和密码123 告诉后端我是aaa 后端检查过之后,让你访问aaa的个人页面,

ok你看完个人页面了 你又想看看个人钱包, 这时候对不起 因为是http协议 上一个请求老子早就忘了 你现在要看aaa的个人钱包 那你又得传一遍用户名和密码 不然我怎么知道 你是aaa?

很sb对吧? 这就是前后端交互的程序刚兴起的时候遇到的灾难。


情景再现
这时候。。。 cookie和session出现了。 他俩前者属于客户端 比如浏览器 你的手机app。 后者属于服务器。
这俩是怎么解决上面哪个情景的问题的呢:

假设有个app, 有个用户要登陆它:

登录,然后看一下自己的个人页面 就2步。

你登录的时候 前端发一个请求给后端 带上你的用户名aaa密码123 然后登录成功了。

注意这时候 当你的请求第一次发到后端的时候 服务器tomcat 会自动创建一个session 在服务器中, 这个session 你可以把它理解成一个map,里面存各种键值对, 其中一对就是id 每个session都有一个id, 比如aaa用户 第一次请求过来的 给它创建了一个session id 叫123124314, 然后把session id发给前端 存在cookie里面,

然后 在这个整个会话周期里面 这个session 一直都在, 前端访问后端的时候用123124314就可以识别到这个session。 你第一次校验用户名密码之后,就可以把ta 存进id为123124314的session中。

今后登录之后 要继续点其他页面, 这时候每次就可以从 session中取出之前保存的数据 来校验。

http是没有记忆的 但是通过session 会保留一部分记忆。


用redis代替session

session有什么问题呢?
它是在tomcat服务器中产生的, 但是如果现在同时练了多台tomcat服务器。 他们之间各自的session 是不互通的。
这样非常不方便。

这时候redis出现了 ,它提高统一的 一块快速读取的区域。 原来存到session的那部分东西 现在就可以把他存到redis中。

我们现在来大概讲一下 redis 如何实现短信验证登录的功能 的思路:

首先 浏览器 填写了手机号码 点击发送验证码 这是第一步:
找一个代码示例:

   public Result sendCode(String phone, HttpSession session) {
          //校验手机号   //手机号不符合 返回错误信息
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }
          //符合 生成验证码 使用百宝箱hutool
        String code = RandomUtil.randomNumbers(6);

        //保存到session
        // session.setAttribute("code",code);
        //此处重构代码 更改原来的保存code到session中 现在用redis来存储
        //为了更好的做区分 这里的key 前面加一些前缀, 另外要加上有效期
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

         //发送 这里采用伪发送 把验证码打到日志
         log.debug("发送验证码成功 验证码:{}",code);

        //返回结果对象
        return Result.ok();

    }	

首先发送验证码 后端发出验证码 之后 要找个地方存起来 且具有时效性,等前端发过来验证码和手机号的时候 两边验证码一比对 就判断用户是不是登录成功 所以我们把验证码 key为前缀_phone value 为验证码的方式存到redis

用户登录

用户收到验证码之后 他会带着验证码和手机号 登录。
这时候 我们第一步需要验证两边的验证码是不是对的上。

如果对的上 我们还有验证这是不是新用户,如果是就创建一个用户 存到数据库表, 如果不是就从数据库中取出用户的信息。

将这个信息保存到redis中,之后每次要登录校验的时候 就从原来的session中取出user证明你是aaa 变成了从redis当中取。

这里还有一点要注意, 从前的session 服务器自动创建了id 浏览器会带着id来找session, 现在换成了redis中的map, 你需要设置一个token,让前端每次访问时 都把这个token带上 ,比如放到request的header里面是个不错的选择。

在redis中 就是 <token,user对象>的关系。 前端每次token传过来, 你需要用它取出对象 看看对象还在不在。这样就复刻了 sessionid 和session的组合。
来个简单的代码示例

   public Result login(LoginFormDTO loginForm, HttpSession session) {
        //用户已经收到了发送的验证码, 现在开始 提交 手机号和验证码
        //校验手机号
        String phone = loginForm.getPhone();

        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误");
        }

        //对验证码进行校验 是否和redis中的相同
        Object cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            //校验不一致 直接报错
            return Result.fail("验证码错误 或者验证码已经过期");
        }

        //从数据库取出用户对象
        User user = query().eq("phone",loginForm.getPhone()).one();

        //判断用户是否存在
        if (user==null){
            user = createUserWithPhone(loginForm.getPhone());
        }


        //随机生成一个token 作为user对象的key
        String token = UUID.fastUUID().toString(true);

        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //将user对象转换成map
        //注意这个地方有坑 因为用的是 stringRedisTemplate 所有的基础数据类型都需要是string结构。

        Map<String,Object>userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));

        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);

        //单独设置有效期
        stringRedisTemplate.expire(token,LOGIN_USER_TTL,TimeUnit.MINUTES);

        return  Result.ok(token);

    }

最后总结一下 redis在这个功能中最重要的角色是什么?
没错 就是它替代了 原来session的临时存储数据的功能。注意这个临时,持久的数据我们存进mysql 这类临时的数据 生命周期比较短的 我们存进redis 且加上时效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值