Redis实战案例及问题分析之redis实现短信登陆

本文介绍了在多台Tomcat集群环境中,如何利用Redis解决Session共享问题,实现短信验证码登录功能。详细阐述了验证码的生成与存储、用户登录注册过程,以及拦截器的优化,确保用户信息的安全和有效会话管理。同时,展示了如何通过拦截器刷新登录令牌的有效期。
摘要由CSDN通过智能技术生成

短信登陆

基于session短信的登陆存在的问题:

集群的session共享问题:多台tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。

session的替代方案应该满足:

  • 数据共享
  • 内存数据:读写效率高
  • key、value结构:方便存储方案

使用redis代替session:

  • redis是tomcat以外的存储方案,所以任意一台tomcat都能访问到redis拿到数据实现数据共享
  • redis是内存存储
  • redis是key、value结构

 基于redis实现共享session登录

发送验证码功能:

生成验证码之后,以手机号为key,验证码为value,类型为string存入redis。

@Resource
    private StringRedisTemplate stringRedisTemplate;

    public Result sendcode(String phone, HttpSession session){
        //1.判断手机是否正确
        if (RegexUtils.isPhoneInvalid(phone)) {
            //验证失败,返回错误信息
            return Result.fail("手机格式输入错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);
        //3.把验证码保存到redis,设置短信验证码有效期
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //4.发送验证码
        log.debug("发送短信验证码成功,验证码{}",code);
        return Result.ok();
    }

短信验证码登陆注册:

以手机号码为key去redis读取验证码,校验是否一致,如果一致以随机生成的token为key,用户信息以hash类型为value存入redis数据库,并返回给客户端(一般是浏览器)

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            //验证失败,返回错误信息
            return Result.fail("手机格式输入错误!");
        }
        //TODO 2.从redis获取验证码并校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (cacheCode == null || !cacheCode.equals(loginForm.getCode())){
            return Result.fail("验证码错误请重试!");
        }
        // 3.查询手机是否在数据库里
        User user = query().eq("phone", phone).one();
        //4.如果不在就把该用户注册进数据库
        if (user == null){
            user = creatUserByPhone(phone);

        }
        // 5.把用户放入redis
        //5.1随机生成token,作为登录令牌
        String token = UUID.fastUUID().toString(true);
        // 5.2将user对象转换为Hashmap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().
                setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        // 5.3存储到redis
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);
        //5.4设置token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);

        // 6返回token给客户端
        return Result.ok(token);


    }

 校验登录功能:

因为在短信验证码登陆注册功能中把用户的token保存在了客户端(一般是浏览器),那么用户每次访问都会携带这个token,就可以根据这个token去redis里面查询用户信息。

拦截器优化:

一开始的拦截器设置是要用户有操作才会刷新用户的token保存时间,优化拦截器之后无论访问的是不是需要用户登录的页面都可以刷新token有效期

public class RefreshTokenInterceptor implements HandlerInterceptor {

    /*
    因为这个类RefreshTokenInterceptor对象是我们自己在需要使用到它的时候手动New出来的,
    不是由spring创建的,所以没办法用@Resource/@Autowired这些注解来注入StringRedisTemplate属性
    那谁来注入这个StringRedisTemplate呢?就看是谁用到了RefreshTokenInterceptor类的对象,
    就在用到的那里注入那谁来注入这个StringRedisTemplate,比如这里是在MvcConfig类中
     */
    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的token,根据前端代码中定义的变量名来取
        String token = request.getHeader("authorization");
        //判断token是否为空
        if (StrUtil.isBlank(token)) {

            return true;

        }
        //基于token获取redis用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
        //判断用户是否存在
        //如果不存在
        if (userMap.isEmpty()) {

            return true;
        }
        //将查询到的Hash数据转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //用户存在,保存到threadlocal
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户
        UserHolder.removeUser();
    }
}
public class LoginInterceptor implements HandlerInterceptor {



    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //判断是否需要拦截(ThreadLocal中是否有用户)
        if(UserHolder.getUser() == null){
            response.setStatus(401);
            return false;
        }
        return true;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值