黑马程序员项目-黑马点评

news2024/11/27 4:12:22

黑马点评1

短信登录

基于Session实现登录流程

发送验证码:
用户在提交手机号后,会校验手机号是否合法,如果不合法,则要求用户重新输入手机号
如果手机号合法,后台此时生成对应的验证码,同时将验证码进行保存,然后再通过短信的方式将验证码发送给用户
短信验证码登录、注册:
用户将验证码和手机号进行输入,后台从session中拿到当前验证码,然后和用户输入的验证码进行校验,如果不一致,则无法通过校验,如果一致,则后台根据手机号查询用户,如果用户不存在,则为用户创建账号信息,保存到数据库,无论是否存在,都会将用户信息保存到session中,方便后续获得当前登录信息
校验登录状态:
用户在请求时候,会从cookie中携带者JsessionId到后台,后台通过JsessionId从session中拿到用户信息,如果没有session信息,则进行拦截,如果有session信息,则将用户信息保存到threadLocal中,并且放行
1653066208144.png

  • 发送验证码
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //校验 手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            //不符合
            return Result.fail("手机号格式不正确");
        }
        //生成验证码
        String code = RandomUtil.randomNumbers(6);
        //保存到session
        session.setAttribute("code", code);
        //发送验证码
        log.debug("手机号:{},验证码:{}", phone, code);
        return Result.ok();
    }
  • 用户登录
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            //不符合
            return Result.fail("手机号格式不正确");
        }
        //校验验证码
        Object hasSendCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if (hasSendCode == null || !hasSendCode.toString().equals(code)) {
            return Result.fail("验证码不正确");
        }
        //校验通过,查询用户
        User user = this.query().eq("phone", phone).one();
        if (user == null) {
            user = createUserWithPhone(phone);
        }
        //保存用户信息到session
        session.setAttribute("user", user);
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

此时可以正常登录

手机号格式:

  • 手机号以 “1” 开头。
  • 第二位是一个特定的数字,可以是 3、4、5、6、7、8、9 中的一个。
  • 接下来的 9 位数字可以是 0 到 9 的任意数字。

登录拦截

  • 添加拦截器设置
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取Session
        HttpSession session = request.getSession();
        //获取用户信息
        Object user = session.getAttribute("user");
        if (user == null) {
            //不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        //存在,保存到ThreadLocal中
        UserHolder.saveUser((User) user);
        return true;
    }
}
  • 配置拦截器生效
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns("/shop/**",
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                );
    }
}

用户信息脱敏

登录的时候修改:

        //保存用户信息到session
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        session.setAttribute("user", userDTO);
        return Result.ok();

拦截器修改:

        //存在,保存到ThreadLocal中
        UserHolder.saveUser((UserDTO) user);

UserHolder修改 :

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

Session共享

session会存入到tomcat服务器 中,但是后端如果有多个tomcat服务器,就不好 实现数据共享,早期的办法是将 session拷贝到不同 的tomcat服务器上面,现在有了redis,可以直接使用redis来解决session共享的问题,两种存储方式

  • 使用string存储
  • 使用哈希存储

1653319261433.png
这里选择使用哈希存储

设计key的时候可以随机 生成一个token给 前端,让前端带着token来访问后端
整体流程如下:
1653319474181.png

  • 修改发送验证码逻辑,保存到redis
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //校验 手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            //不符合
            return Result.fail("手机号格式不正确");
        }
        //生成验证码
        String code = RandomUtil.randomNumbers(6);
        //保存到session
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //发送验证码
        log.debug("手机号:{},验证码:{}", phone, code);
        return Result.ok();
    }

修改登录逻辑,从redis读数据:

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            //不符合
            return Result.fail("手机号格式不正确");
        }
        //从redis中获取验证码
        String hasSendCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if (hasSendCode == null || !hasSendCode.toString().equals(code)) {
            return Result.fail("验证码不正确");
        }
        //校验通过,查询用户
        User user = this.query().eq("phone", phone).one();
        if (user == null) {
            user = createUserWithPhone(phone);
        }
        //保存用户信息到redis中
        //生成token
        String token = UUID.randomUUID().toString();
        //将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())
        );
        //存储
        String toKenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(toKenKey, userMap);
        //设置有效期
        stringRedisTemplate.expire(toKenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return Result.ok(token);
    }

解决登录状态刷新问题

目前的拦截机制:
1653320822964.png

如果访问不需要拦截的路径,这个拦截器不会生效,此时不会刷新令牌

优化方案:
再添加一个拦截器,拦截所有请求
1653320764547.png

具体代码以及解释 如下 :

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    //这里的构造方法是为了注入StringRedisTemplate,
    // 因为 RefreshTokenInterceptor 是自己 new 出来的,不是 Spring 管理的
    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)) {
            return true;
        }
        //获取redis中的用户
        String key = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        if (userMap.isEmpty()) {
            //用户不存在
            return true;
        }
        //将hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //保存到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, java.util.concurrent.TimeUnit.DAYS);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清空ThreadLocal中的数据  防止内存泄漏
        UserHolder.removeUser();
    }
}

配置类里面注入StringRedisTemplate:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

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

登录拦截器修改如下 :

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (UserHolder.getUser() == null) {
            //不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        //有用户
        return true;
    }
}

至此,黑马点评的登录逻辑完成。

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

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

相关文章

从MFC初始化过程看rc文件的行为,并剖析关联控件变量的实质

以MFC对话框程序为例: 当我们打开资源编辑器时就可以很容易的添加各种控件窗口,资源编辑器实际上操作的是rc文件;那么这些控件窗口是何时被MFC创建与管理的 没有关联控件变量前,在资源编辑器中依然可以容易拖动控件,并显示出来;这个控件窗口是如何被创建和管理的: 资源编…

【C语法学习】13 - fscanf()函数

文章目录 1 函数原型2 参数3 返回值4 比较5 示例5.1 示例15.2 示例2 1 函数原型 fscanf()&#xff1a;从指定流stream读取格式化输入&#xff0c;函数原型如下&#xff1a; int fscanf(FILE *stream, const char *format, ...)2 参数 fscanf()函数参数包括三部分&#xff1a…

Node.js 中解析 HTML 的方法介绍

在 Web 开发中&#xff0c;解析 HTML 是一个常见的任务&#xff0c;特别是当我们需要从网页中提取数据或操作 DOM 时。掌握 Node.js 中解析 HTML 的各种方式&#xff0c;可以大大提高我们提取和处理网页数据的效率。本文将介绍如何在 Node.js 中解析 HTML。 基本概念 HTML 解析…

【广州华锐互动】VR野外求生技能学习,让你感受真实的冒险之旅!

随着科技的迅速发展&#xff0c;虚拟现实(VR)技术为人们提供了一个全新的、身临其境的探险体验。通过将用户带入一个仿真的、沉浸式的虚拟环境&#xff0c;VR互动体验让人们在安全的氛围中感受到野外探险的乐趣。本文将从视觉呈现、沉浸式体验、交互性和应用范围四个方面&#…

MATLAB颜色索引表---持续更新中--各个平台都可使用

MATLAB颜色索引表—持续更新中–各个平台都可使用

探索ChatGPT在学术写作中的应用与心得

随着人工智能的迅猛发展&#xff0c;ChatGPT作为一种强大的自然语言处理模型&#xff0c;逐渐在学术界引起了广泛的关注。本文将探讨ChatGPT在学术写作中的应用&#xff0c;并分享使用ChatGPT进行学术写作时的一些经验和心得。 01 — ChatGPT在学术写作中的应用 1.文献综述和…

【ChatOCR】OCR+LLM定制化关键信息抽取(附开源大语言模型汇总整理)

目录 背景技术方案存在的问题及解决思路关键信息提取结果其他解决方案替换文心一言LangChain大型多模态模型&#xff08;Large Multimodal Model, LMM&#xff09; 开源大模型汇总LLaMA —— Meta 大语言模型Stanford Alpaca —— 指令调优的 LLaMA 模型Lit-LLaMA —— 基于 na…

基于单片机的语音存储与回放系统设计

博主主页&#xff1a;单片机辅导设计 博主简介&#xff1a;专注单片机技术领域和毕业设计项目。 主要内容&#xff1a;毕业设计、简历模板、学习资料、技术咨询。 文章目录 主要介绍一、控制系统设计1.1 系统方案设计1.2 系统工作原理 二、硬件电路设计总电路设计图 三、 软件设…

shufflenet v2 yolo

设计理念 1x1卷积进行平衡输入和输出的通道大小&#xff1b;组卷积要谨慎使用&#xff0c;注意分组数&#xff1b;避免网络的碎片化&#xff1b;减少元素级运算。 ShuffleNet v2中弃用了1x1的group convolution操作&#xff0c;而直接使用了input/output channels数目相同的…

win10系统nodejs的安装npm教程

1.在官网下载nodejs&#xff0c;https://nodejs.org/en 2&#xff0c;双击nodejs的安装包 3&#xff0c;点击 next 4&#xff0c;勾选I accpet the terms in…… 5&#xff0c;第4步点击next进入配置安装路径界面 6,点击next&#xff0c;选中Add to PATH &#xff0c;旁边…

高压检测设备

比如&#xff1a;高压数字表、高压差分探头、指针式高压表、电流探枪、高压探棒 这些设备都是用来测量高压的&#xff0c;有的测电压&#xff0c;有的测电流。 高压数字表&#xff1a; 单独使用&#xff0c;功能很简单&#xff0c;有2个正负极探爪&#xff0c;把2个探爪连接到…

Linux 安装 RocketMq

RocketMq是阿里出品&#xff08;基于MetaQ&#xff09;的开源中间件&#xff0c;已捐赠给Apache基金会并成为Apache的顶级项目。基于java语言实现&#xff0c;十万级数据吞吐量&#xff0c;ms级处理速度&#xff0c;分布式架构&#xff0c;功能强大&#xff0c;扩展性强。 官网…

大厂面试题-IO和NIO区别

从下面几个方面来回答&#xff1a; 首先&#xff0c;I/O&#xff0c;指的是IO流&#xff0c;它可以实现数据从磁盘中的读取以及写入。 实际上&#xff0c;除了磁盘以外&#xff0c;内存、网络都可以作为I/O流的数据来源和目的地。 在Java里面&#xff0c;提供了字符流和字节…

http协议 - 常见渗透测试姿势

文章目录 HTTP是什么GET 请求POST请求HEAD请求 HTTP绕过练习靶场修改添加请求头部字段内容绕过referer User-Agent x-forwarded-for进入题目内容&#xff08;1&#xff09; 修改添加cookie字段内容绕过cookie进入题目内容&#xff08;2&#xff09; 工具&#xff1a; Burp Sui…

NodeJS 安装及环境配置

下载地址&#xff1a;https://nodejs.org/zh-cn/download/ 安装 NodeJS 根据自己电脑系统及位数选择&#xff0c;一般都选择 windows 64位 .msi 格式安装包。 所用命令&#xff1a; node -v npm -v PS&#xff1a;如果以上两条命令都能执行成功&#xff0c;表示安装完成&#…

Compose-Multiplatform在Android和iOS上的实践

本文字数&#xff1a;4680字 预计阅读时间&#xff1a;30分钟 01 简介 之前我们探讨过KMM&#xff0c;即Kotlin Multiplatform Mobile&#xff0c;是Kotlin发布的移动端跨平台框架。当时的结论是KMM提倡将共有的逻辑部分抽出&#xff0c;由KMM封装成Android(Kotlin/JVM)的aar和…

NUUO网络摄像头(NVR)RCE漏洞复现

简介 NUUO Network Video Recorder&#xff08;NVR&#xff09;是中国台湾NUUO公司的一款网络视频记录器。 NUUO NVR视频存储管理设备的__debugging_center_utils___.php文件存在未授权远程命令执行漏洞&#xff0c;攻击者可在没有任何权限的情况下通过log参数执行任意命令。…

【优选算法系列】【专题九链表】第一节.链表常用技巧和操作总结(2. 两数相加)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、链表常用技巧和操作总结二、两数相加 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、链表常…

Python:字符串格式化

文章目录 %用法使用format方法进行格式化 %用法 格式字符说明%s字符串%c单个字符%d十进制整数%o八进制整数%x十六进制整数%e指数(基底写为e)%E指数(基底写为E) x 1235 print(%o % x) print(%d % x) print(%x % x) print(%e % x) print(%s % 65) print(%c % a)使用format方法…

mac M2 出现 zsh: command not found: brew问题解决方案

mac M2 出现 zsh: command not found: brew问题解决方案 各操作系统安装第三方工具指令 1 . mac os 安装第三方工具命令brew 2. RedHat系列&#xff1a;Redhat、Centos、Fedora等 安装第三方工具命令 yum 3. Debian系列&#xff1a;Debian、Ubuntu 安装第三方工具命令 apt-get…