基于redis实现登录校验

实现流程

  1. 发送短信验证码,并将验证码保存在redis。

  2. 根据用户登录数据完成登录。

    1. 如果是新用户还要创建用户。
    2. 如果查询到则转化为DTO保存。
      将用户数据被保存到redis,并将token保存到redis,同时返回给前端。
  3. 创建登录校验拦截器。

    注册登录校验拦截器。

发送验证码接口

请求参数:手机号phone

无结果对象

service层实现

java 复制代码
@Override  
public Result sendCode(String phone) {  
    //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();  
}

接口介绍

  1. 根据正则表达式检验手机号格式。
  2. 通过hutool生成6位随机数作为验证码。
  3. 将验证码保存在redis中。
  4. 将验证码发送给客户端(此处应使用相关云服务完成,笔者使用日志只为记录数据)。

登录接口

请求参数:已经封装好的LoginFormDTO对象

结果对象:token

service层实现

java 复制代码
@Override  
public Result login(LoginFormDTO loginForm) {  
    //1.校验手机号格式  
    String phone = loginForm.getPhone();  
    if (RegexUtils.isPhoneInvalid(phone)) {  
        return Result.fail("手机号格式错误!");  
    }  
    //2.校验验证码  
    String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);  
    String code = loginForm.getCode();  
    if (cacheCode == null || !cacheCode.equals(code)) {  
        return Result.fail("验证码错误!");  
    }  
    //3.根据手机号查询用户  
    User user = query().eq("phone", phone).one();  
    //4.判断用户是否存在  
    if (user == null) {  
        //5.没有用户则创建新用户  
        user = createUserWithPhone(phone);  
    }  
    //6.保存用户信息到redis,(以哈希储存)  
        //1.通过UUID拼装tokenKey  
    String token = UUID.randomUUID().toString(true);  
    String tokenKey = LOGIN_USER_KEY + token;  
        //2.将UserDTO对象转为HashMap存储, 由于使用的是stringRedisTemplate,要保证所有的值都是字符串类型,要把lang转换为string  
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);  
  
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),  
            CopyOptions  
                    .create()//创建默认数据拷贝选项  
                    .setIgnoreNullValue(true) //忽略null值  
                    /*字段值修改器,接收实现修改的方法*/  
                    .setFieldValueEditor(  
                            (fieldName, fieldValue) -> fieldValue.toString()  
                    )  
            );  
  
    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);  
        //3.设置token的有效期  
    stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.SECONDS);  
    //7.返回token  
    return Result.ok(tokenKey);  
}

接口介绍

  1. 校验手机号格式。
  2. 校验验证码。
  3. 根据手机号查询用户信息(使用MybatisPlus)。
  4. 通过UUID生成随机token,所为key,将查询后得到的UserDTO(将User转换为UserDTO,避免暴漏敏感数据 ),封装为hash(使用hutool相关API),存入redis,并设置有效期。
  5. 返回token。

手动创建拦截器

在实现了HandlerInterceptor的类中重写preHandle和afterCompletion来实现对每一次请求的处理。

java 复制代码
public class LoginInInterceptor implements HandlerInterceptor {  
  
    private StringRedisTemplate stringRedisTemplate;  
  
    public LoginInInterceptor(StringRedisTemplate stringRedisTemplate) {  
        this.stringRedisTemplate = stringRedisTemplate;  
    }  
      
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
  
        //获取请求头中的token  
        String token = request.getHeader("authorization");  
        if (token == null) {  
            response.setStatus(401);  
            return false;  
        }  
        //根据token从redis中获取用户信息  
        String key = LOGIN_CODE_KEY + token;  
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);  
        if (userMap.isEmpty()) {  
            response.setStatus(401);  
            return false;  
        }  
  
        //将map转换为UserDTO对象  
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);  
  
        //保存用户信息到ThreadLocal  
        UserHolder.saveUser(userDTO);  
        //刷新token有效期  
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.SECONDS);  
        return true;  
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {  
        UserHolder.removeUser();  
    }  
}

用前注意:

该自定义拦截器未交给Spring容器管理,不能使用@Autowired或者@Resource自动注入依赖,此处使用构造器注入stringRedisTemplate。

执行逻辑:
preHandle执行拦截校验

  1. getHeader获取请求头中的token,并进行非空判断。
  2. 根据token从redis中获取userMap,再次使用hutool包的API将map集合转换为实体类。
  3. 使用工具类将userDTO保存到ThreadLocal,实现跨层级数据共享。
  4. 刷新token有效期,提升用户体验。
    afterCompletion执行清理ThreadLocal
    请求完成后清理ThreadLocal中的数据,防止内存泄漏和数据污染。

注册拦截器

在含有@Configuration 且实现了WebMvcConfigurer 的配置类中添加自定义拦截器

java 复制代码
@Configuration  
public class MvcConfig implements WebMvcConfigurer {  
  
    @Resource  
    private StringRedisTemplate stringRedisTemplate;  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(new LoginInInterceptor(stringRedisTemplate))  
                .excludePathPatterns(  
                        "/user/code",  
                        "/user/login",  
                        "/blog/hot",  
                        "/shop/**",  
                        "/shop-type/**",  
                        "/voucher/**",  
                        "/upload/**"  
                );  
    }  
}

执行逻辑:

  1. 使用@Resource注解注入stringRedisTemplate
  2. 重写addInterceptors方法
  3. 调用registry的addInterceptor方法添加拦截器(形参中new出LoginInInterceptor,并将正确注入的stringRedisTemplate传递给LoginInInterceptor,完成自定义拦截器中的依赖注入)。
  4. 调用excludePathPatterns指明不拦截的请求
相关推荐
葫芦和十三2 小时前
图解 MongoDB 19|Oplog:复制的真正载体,不是文档是操作
后端·mongodb·agent
葫芦和十三2 小时前
图解 MongoDB 20|复制延迟与 catch up:Secondary 为什么跟不上
后端·mongodb·agent
IT_陈寒7 小时前
SpringBoot自动配置的坑,我的API突然就404了
前端·人工智能·后端
ServBay7 小时前
为什么说 MCP 是 2026 年开发者必须掌握的黄金协议?
后端·mcp
用户3074596982078 小时前
Redis 延时队列详解
redis
程序员夏洛8 小时前
Spring Boot 多模块项目中 IDEA 提示 Cannot resolve symbol 的一次排查记录
后端
子兮曰8 小时前
OpenMontage 深度解剖:你的 AI 编程助手,其实是个视频工作室
前端·后端·ai编程
子兮曰8 小时前
前端工具链的「Rust 化」:一场没有赢家的军备竞赛?
前端·后端·rust
爱勇宝9 小时前
从 Ctrl+CV 到 Enter:程序员正在失去什么
前端·后端·程序员