当前位置: 首页 > article >正文

黑马Redis详细笔记(实战篇---短信登录)

目录

一.短信登录

1.1 导入项目

1.2 Session 实现短信登录

1.3 集群的 Session 共享问题

1.4 基于 Redis 实现共享 Session 登录


一.短信登录

1.1 导入项目

数据库准备

-- 创建用户表

CREATE TABLE `user` (
    `id` BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    `phone` VARCHAR(20) NOT NULL UNIQUE COMMENT '手机号',
    `nick_name` VARCHAR(50) COMMENT '昵称',
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT='用户表';

导入项目

cd nginx目录
start nginx.exe

1.2 Session 实现短信登录

发送验证码

@Override
public Result sendCode(String phone, HttpSession session) {
    // 校验手机号格式是否正确
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式不正确!");
    }
    // 生成6位随机数字验证码
    String code = RandomUtil.randomNumbers(6);
    // 将验证码保存到session中
    session.setAttribute("code", code);
    // 日志记录验证码(实际开发中应发送短信)
    log.info("验证码为: " + code);
    return Result.ok();
}

登录功能

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 获取手机号
    String phone = loginForm.getPhone();
    // 校验手机号格式
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式错误!");
    }
    // 从session中获取验证码
    Object cacheCode = session.getAttribute("code");
    String code = loginForm.getCode();
    // 校验验证码是否正确
    if (code == null || !cacheCode.toString().equals(code)) {
        return Result.fail("验证码错误!");
    }
    // 根据手机号查询用户
    User user = query().eq("phone", phone).one();
    if (user == null) {
        // 如果用户不存在,则自动注册
        user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        save(user);
    }
    // 将用户信息存入session
    session.setAttribute("user", user);
    return Result.ok();
}

拦截器 LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取session
        HttpSession session = request.getSession();
        // 从session中获取用户信息
        Object user = session.getAttribute("user");
        // 判断用户是否存在
        if (user == null) {
            // 用户未登录,返回401状态码
            response.setStatus(401);
            response.getWriter().write("用户未登录!");
            return false;
        }
        // 用户已登录,放行
        return true;
    }
}

在MvcConfig加上拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",    // 验证码接口
                        "/user/login",   // 登录接口
                        "/blog/hot",     // 热门博客
                        "/shop/**",      // 商户信息
                        "/shop-type/**", // 商户类型
                        "/upload/**",    // 文件上传
                        "/voucher/**"    // 优惠券
                );
    }
}

1.3 集群的 Session 共享问题

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

所以我们把数据存入Redis,集群的Redis可以替代session

1.4 基于 Redis 实现共享 Session 登录

我们应该选择String类型存验证码即可,value:验证码,但是key要区分开来

​ 选择Hash存储用户信息,因为每个字段独立,比较好去DRUD,内存占用少,key用token即可(随机字符串)

之前的session的话,tomcat会自动把session的Id存入Cookie,每次请求都会携带Cookie,所以我们需要手动把token返回给客户端,每次请求客户端都会携带着token

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 校验手机号格式是否正确
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确!");
        }
        // 生成6位随机数字验证码
        String code = RandomUtil.randomNumbers(6);
        // 将验证码保存到Redis中,设置过期时间为2分钟
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 日志记录验证码(实际开发中应发送短信)
        log.info("验证码为: " + code);
        return Result.ok();
    }
}


@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    // 获取手机号
    String phone = loginForm.getPhone();
    // 校验手机号格式
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式错误!");
    }
    // 从Redis中获取验证码
    String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
    String code = loginForm.getCode();
    // 校验验证码是否正确
    if (cacheCode == null || !cacheCode.equals(code)) {
        return Result.fail("验证码错误!");
    }
    // 根据手机号查询用户
    User user = query().eq("phone", phone).one();
    if (user == null) {
        // 如果用户不存在,则自动注册
        user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        save(user);
    }
    // 生成唯一的Token
    String token = UUID.randomUUID().toString(true);
    // 将用户信息转换为UserDTO
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    // 将用户信息以Hash类型存储到Redis中,设置过期时间为36000分钟
    stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, BeanUtil.beanToMap(userDTO));
    stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
    // 返回Token
    return Result.ok(token);
}

MvcConfig注入stringRedisTemplate,然后传给LoginInterceptor,因为LoginInterceptor不是bean不能用spring注入其他bean

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor{

    private final StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(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 (StrUtil.isBlank(token)){
            //不存在,拦截 设置响应状态吗为401(未授权)
            response.setStatus(401);
            return false;
        }
        //2.基于token获取redis中用户
        String key=RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        //3.判断用户是否存在
        if (userMap.isEmpty()){
            //4.不存在则拦截,设置响应状态吗为401(未授权)
            response.setStatus(401);
            return false;
        }
        //5.将查询到的Hash数据转化为UserDTO对象
        UserDTO userDTO=new UserDTO();
        BeanUtil.fillBeanWithMap(userMap,userDTO, false);
        //6.保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //7.更新token的有效时间,只要用户还在访问我们就需要更新token的存活时间
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
        //8.放行
        return true;
    }


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //销毁,以免内存泄漏
        UserHolder.removeUser();
    }
}

用户请求进去拦截器,我们试着去获取请求头内的token,根据token去查询用户信息,判断是否拦截,保存在ThreadLocal,刷新token的有效期

但是,这个拦截器是拦截需要登录之后才需要进行请求的路径,那我如果一直在访问的是不需要拦截的页面的话,我还是会过期?这就不合理。所以我们需要在这个拦截器前面再加个拦截器,然后在新增拦截器上进行保存ThreadLocal和刷新有效期,不理解其他

其实就是对之前的拦截器进行功能拆分

MvcConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                ).order(1);//RefreshTokenInterceptor 先于 LoginInterceptor 执行
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);//默认拦截所有请求
    }

}

RefreshTokenInterceptor

public class RefreshTokenInterceptor implements HandlerInterceptor {
    private final 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");
        if (StrUtil.isBlank(token)) {
            // 如果Token为空,直接放行
            return true;
        }
        // 构造Redis中的Key
        String key = RedisConstants.LOGIN_USER_KEY + token;
        // 从Redis中获取用户信息
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        if (userMap.isEmpty()) {
            // 如果用户信息为空,直接放行
            return true;
        }
        // 将用户信息转换为UserDTO对象
        UserDTO userDTO = new UserDTO();
        BeanUtil.fillBeanWithMap(userMap, userDTO, false);
        // 将用户信息保存到ThreadLocal中
        UserHolder.saveUser(userDTO);
        // 刷新Token的有效期
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
}

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从ThreadLocal中获取用户信息
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 如果用户未登录,返回401状态码
            response.setStatus(401);
            response.getWriter().write

​```

http://www.kler.cn/a/542916.html

相关文章:

  • JSON是什么
  • 100.15 AI量化面试题:PPO与GPPO策略优化算法的异同点
  • 02.06、回文链表
  • 关于arm
  • 计算机毕业设计SpringBoot校园二手交易小程序 校园二手交易平台(websocket消息推送+云存储+双端+数据统计)(源码+文档+运行视频+讲解视频)
  • C++20 新特性解析
  • 电脑出现蓝屏英文怎么办?查看修复过程
  • 【物联网】电子电路基础知识
  • RocketMQ、RabbitMQ、Kafka 的底层实现、功能异同、应用场景及技术选型分析
  • 华为openEuler部署docker
  • CCFCSP第34次认证第一题——矩阵重塑(其一)
  • DeepSeek应用——与word的配套使用
  • 活动预告 | 解锁 Excel 新境界 —— AI 技术赋能下的数据分析超级引擎!
  • 【C++高并发服务器WebServer】-17:阻塞/非阻塞和同步/异步、五种IO模型、Web服务器
  • 【Windows/C++/yolo开发部署04】使用CLI 和 python进行推理
  • 理解UML中的四种关系:依赖、关联、泛化和实现
  • 美团智能外呼机器人意图训练全流程
  • docker发布自己的镜像
  • VSCode Cline 插件快速接入 DeepSeek API,提升本地深度学习效率
  • 讲解一下SpringBoot的RPC连接
  • Redis常见数据结构
  • 利用Python和SQLite进行数据处理与优化——从数据库操作到高级数据压缩
  • 除了try...catch,还有其他处理异步错误的方法吗?
  • clickhouse replicatedmergetree 恢复
  • 自制游戏——斗罗大陆
  • Unity使用iTextSharp导出PDF-02基础结构及设置中文字体