黑马头条登陆功能详述

news2025/2/25 13:21:27

基于session的短信登陆:

在这里插入图片描述
发送验证码、短信验证码登陆、注册在后端,校验登陆在springmvc的连接器中,根据请求携带cookie来确定找到session

短信验证登陆与注册新用户:

    /**
     * 发送验证码
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、手机号合法,生成验证码,并保存到Session中
        String code = RandomUtil.randomNumbers(6);
        session.setAttribute(SystemConstants.VERIFY_CODE, code);
        // 3、发送验证码
        log.info("验证码:{}", code);
        return Result.ok();
    }

    /**
     * 用户登录
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、判断验证码是否正确
        String sessionCode = (String) session.getAttribute(LOGIN_CODE);
        if (code == null || !code.equals(sessionCode)) {
            return Result.fail("验证码不正确");
        }
        // 3、判断手机号是否是已存在的用户
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPassword, phone));
        if (Objects.isNull(user)) {
            // 用户不存在,需要注册
            user = createUserWithPhone(phone);
        }
        // 4、保存用户信息到Session中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)
        session.setAttribute(LOGIN_USER, user);
        return Result.ok();
    }

    /**
     * 根据手机号创建用户
     */
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

登陆拦截器:

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 前置拦截器,用于判断用户是否登录
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 1、判断用户是否存在
        User user = (User) session.getAttribute(LOGIN_USER);
        if (Objects.isNull(user)){
            // 用户不存在,直接拦截
            response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
            return false;
        }
        // 2、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理
        // 比如:方便获取和使用用户信息,session获取用户信息是具有侵入性的
        ThreadLocalUtls.saveUser(user);

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

存在问题就是,如果是多个服务器都提供了登录和其他服务功能,nigix反向代理会找到不同的主机中,这时候,这个时候就需要用户再次登录了:
解决方法:
1、session共享,利用拷贝功能,但是数据同步需要时间,而且占用内存
2、使用redis缓存来实现共享session问题,问题的关键是怎么获得获取redis中对象时,需要自己手动设置一个key,下次查询也需要携带这个令牌,这个时候前端就是会把返回的token保存在web服务器中,保证每次都是携带了token的

使用redis实现短信验证登陆

在这里插入图片描述
使用redis缓存验证码,key需要时唯一的,可以采用电话号码就可以,值得话直接也是string格式

使用redis缓存对象的数据结构悬着string还是hash结构:

1、采用string形式,相当于把对象序列化为JOSN字符串,但是修改起来需要整体删除了在添加
2、采用的是redis中hash格式,这个格式中有一个feild和一个calue值,相当于一个hashmap,修改起来方面,可以随意添加新的对象属性。相当于对单个字段的CRUD更加灵活。

使用redis缓存对象的key怎么保证唯一性:
自己设置一个业务作为前缀,然后拼接一个UUID来保证key的唯一性。

短信验证登陆:

    /**
     * 发送验证码
     *
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、手机号合法,生成验证码,并保存到Redis中
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code,
                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 3、发送验证码
        log.info("验证码:{}", code);
        return Result.ok();
    }

    /**
     * 用户登录
     *
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判断手机号是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式不正确");
        }
        // 2、判断验证码是否正确
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (code == null || !code.equals(redisCode)) {
            return Result.fail("验证码不正确");
        }
        // 3、判断手机号是否是已存在的用户
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if (Objects.isNull(user)) {
            // 用户不存在,需要注册
            user = createUserWithPhone(phone);
        }
        // 4、保存用户信息到Redis中,便于后面逻辑的判断(比如登录判断、随时取用户信息,减少对数据库的查询)
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 将对象中字段全部转成string类型,StringRedisTemplate只能存字符串类型的数据
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).
                        setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String token = UUID.randomUUID().toString(true);
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

        return Result.ok(token);
    }

    /**
     * 根据手机号创建用户并保存
     *
     * @param phone
     * @return
     */
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

配置登录拦截器,这是在session拦截器基础上的优化

单独配置一个拦截器用户刷新Redis中的token:我们之前设置的登陆拦截器只是简单的对需要用户身份的设置了拦截器。但是在这里,为了防止redis中的用户信息一直存在,我们在上面代码中其实的给token设置了过期时间的,但是如果我们一直没用用到用户信息的功能,登录状态就会没有。因此,我们需要单独设置一个token的刷新拦截器,让每一次请求都会刷新token的有效期。在这里插入图片描述
刷新token的拦截器:

public class RefreshTokenInterceptor implements HandlerInterceptor {

    // new出来的对象是无法直接注入IOC容器的(LoginInterceptor是直接new出来的)
    // 所以这里需要再配置类中注入,然后通过构造器传入到当前类中
    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、获取token,并判断token是否存在
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            // token不存在,说明当前用户未登录,不需要刷新直接放行
            return true;
        }
        // 2、判断用户是否存在
        String tokenKey = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        if (userMap.isEmpty()){
            // 用户不存在,说明当前用户未登录,不需要刷新直接放行
            return true;
        }
        // 3、用户存在,则将用户信息保存到ThreadLocal中,方便后续逻辑处理,比如:方便获取和使用用户信息,Redis获取用户信息是具有侵入性的
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.class));
        // 4、刷新token有效期
        stringRedisTemplate.expire(token, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
}

1、先获取token,如果查询到redis中存在,获取redis中的对象,将map结构使用beantil的copyproperties转为实体对象,并保存在threadlocal中。这里使用threadlocal是线程域对象,因此是线程隔离的
3、保存对象数据后,刷星token的过期时间,说明有人用过了
2、如果没有这个key,说明没有登陆,只需要放行就可以以

至此,使用redis共享session成功实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1268625.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark on yarn 模式的安装与部署

任务描述 本关任务&#xff1a; Spark on YARN 模式的安装与部署。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; Spark 部署模式的种类&#xff1b;Spark on YARN 模式的安装。 Spark 部署模式 Spark 部署模式主要分为以下几种&#xff0c;Spark Stand…

基于LangChain实现的知识库问答工具Langchain-Chatchat

基于embeddingLangChainChatGLM2-6B 构建行业知识库 Langchain-Chatchat LangChain 中文文档 langchain 本文使用的Langchain-Chatchat版本是0.2.7 一、构建垂类行业知识库的两种方案 方案一&#xff1a;使用开源LLM本地部署和微调 优点&#xff1a;数据最安全&#xff0c…

Java第二十章总结

一、线程简介 1.什么是进程&#xff1a; 进程是程序的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位。通俗来讲&#xff0c;进程就是在操作系统中运行的程序&#xff0c;例如&#xff1a;电脑中运行的微信、eclipse、idea等。 2.什么是线程 线程是操作系统能…

目标检测——R-CNN算法解读

论文&#xff1a;Rich feature hierarchies for accurate object detection and semantic segmentation 作者&#xff1a;Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik 链接&#xff1a;https://arxiv.org/abs/1311.2524 代码&#xff1a;http://www.cs.berke…

不会PS!超简单的制作产品册方法

​产品册是展示产品的重要工具&#xff0c;对于很多企业来说&#xff0c;制作一本精美的产品册是必不可少的。但是&#xff0c;对于一些不会PS的人来说&#xff0c;制作产品册可能会觉得非常困难。其实&#xff0c;制作产品册并不需要PS等专业工具&#xff0c;只需要一些简单的…

电巢直播|揭秘FCBGA先进封装基板兴力量ze

随着2022年底ChatGPT的问世&#xff0c;我们不仅见证了从互联网时代到AI应用时代的跨越&#xff0c;也迎来了一个数据流量不断攀升的新纪元。在这个以数据为核心的新时代&#xff0c;算力网络成为支撑巨大数字经济的基石&#xff0c;其背后则是对硬件性能持续提升的迫切需求。 …

重点在正负上如何描述?看CHAT有何见解

问CHAT&#xff1a;重点在正负上如何描述&#xff1f; CHAT回复&#xff1a;在描述数据的波动情况时&#xff0c;也可关注其正负方向的变化。通常有以下几种方式&#xff1a; 1. 均值&#xff08;Mean&#xff09;&#xff1a;在一众数值中&#xff0c;如果大部分数值是正值且…

【数值计算方法(黄明游)】常微分方程初值问题的数值积分法:欧拉方法(向后Euler)【理论到程序】

文章目录 一、数值积分法1. 一般步骤2. 数值方法 二、欧拉方法&#xff08;Euler Method&#xff09;1. 向前欧拉法&#xff08;前向欧拉法&#xff09;2. 向后欧拉法&#xff08;后向欧拉法&#xff09;a. 基本理论b. 算法实现 常微分方程初值问题的数值积分法是一种通过数值方…

ISCTF2023 部分wp

学一年了还在入门( web where_is_the_flag ISCTF{41631519-1c64-40f6-8dbb-27877a184e74} 圣杯战争 <?php // highlight_file(__FILE__); // error_reporting(0);class artifact{public $excalibuer;public $arrow;public function __toString(){echo "为Saber选择…

实施工程师运维工程师面试题

Linux 1.请使用命令行拉取SFTP服务器/data/20221108/123.csv 文件&#xff0c;到本机一/data/20221108目录中。 使用命令行拉取SFTP服务器文件到本机指定目录&#xff0c;可以使用sftp命令。假设SFTP服务器的IP地址为192.168.1.100&#xff0c;用户名为username&#xff0c;密…

【爬虫实战】最新python豆瓣热榜Top250

一.最终效果 豆瓣是大多数新手练习爬虫的 二.数据定位过程 对于一个目标网站&#xff0c;该如何快速判定页面上的数据来源&#xff1f;首先你需要简单web调试能力&#xff0c;对大多数开发者来说都chrome浏览器应该是不二选择&#xff0c;当然我选中的也是。F12打开调试面板&…

Vite 了解

1、vite 与 create-vite 的区别 2、vite 解决的部分问题 3、vite配置文件的细节 3.1、vite语法提示配置 3.2、环境的处理 3.3、环境变量 上图补充 使用 3.4、vite 识别&#xff0c;vue文件的原理 简单概括就是&#xff0c;我们在运行 npm润dev 的时候&#xff0c;vite 会搭起…

springboot+mysql实现就业信息管理系统

springbootmysql实现的就业信息管理系统,有普通用户和管理员两种角色,演示地址:yanshi.ym4j.com:8091 普通用户账号&#xff1a;test 密码&#xff1a;123456管理员账号&#xff1a;admin 密码&#xff1a;123456 共包含就业信息管理、就业统计、用户管理等功能。 登录 就业信…

网络通信概述

文章目录 IP地址端口号协议三要素作用 五元组协议分层OSI七层模型TCP/IP 五层模型应用层传输层网络层数据链路层物理层 封装和分用发送方 - 封装中间转发接收方 - 分用 一般认为计算机网络就是利用通信线路和通信设备将地理上分散的、具有独立功能的多个计算机系统按不同的形式…

Qt 天气预报项目

参考引用 QT开发专题-天气预报 1. JSON 数据格式 1.1 什么是 JSON JSON (JavaScript Object Notation)&#xff0c;中文名 JS 对象表示法&#xff0c;因为它和 JS 中对象的写法很类似 通常说的 JSON&#xff0c;其实就是 JSON 字符串&#xff0c;本质上是一种特殊格式的字符串…

销门!销售秘籍都在这了

很多刚进销售行业&#xff0c;或者初入职场的小白&#xff0c;肯定会有这一段迷茫/茫然期&#xff1a;为什么同事每天都有客户打电话/实地拜访呢&#xff1f; 而自己却无所事事&#xff0c;也没有客户找我&#xff0c;也不知道去哪里拜访客户。 这个阶段对于销售小白来说是很…

联想SR660 V2服务器使用默认用户登录BMC失败

新到了一台服务器&#xff0c;使用默认用户登录BMC失败 登录失败提示&#xff1a;账号或密码错误 解决方案&#xff1a; 1、重置BMC 2、新增用户 开机后在出现 ThinkServer 界面按 F1&#xff0c;进入 BIOS 界面 进入 System Settings-BMC Configuration 菜单相关&#xf…

聊聊VMware vSphere

VMware vSphere是一种虚拟化平台和云计算基础设施解决方案&#xff0c;由VMware公司开发。它为企业提供了一种强大的虚拟化和云计算管理平台&#xff0c;能够在数据中心中运行、管理和保护应用程序和数据。vSphere平台与VMware ESXi虚拟化操作系统相结合&#xff0c;提供了完整…

1992-2021年区县经过矫正的夜间灯光数据(GNLD、VIIRS)

1992-2021年区县经过矫正的夜间灯光数据&#xff08;GNLD、VIIRS&#xff09; 1、时间&#xff1a;1992-2021年3月&#xff0c;其中1992-2013年为年度数据&#xff0c;2013-2021年3月为月度数据 2、来源&#xff1a;DMSP、VIIRS 3、范围&#xff1a;区县数据 4、指标解释&a…

从零开始:PHP实现阿里云直播的简单方法!

1. 配置阿里云直播的推流地址和播放地址 使用阿里云直播功能前&#xff0c;首先需要在阿里云控制台中创建直播应用&#xff0c;然后获取推流地址和播放地址。 推流地址一般格式为&#xff1a; rtmp://{Domain}/{AppName}/{StreamName}?auth_key{AuthKey}-{Timestamp}-{Rand…