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

黑马点评-短信登录

@Override
public Result sendCode(String phone) {
//        1.检验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
//  这里抛出异常和return fail有什么区别吗?———> 有区别,抛出异常会被全局异常处理器捕获,返回fail不会
throw new RuntimeException("手机号格式不正确");
}
//        2.生成验证码
String code = RandomUtil.randomNumbers(6);
//        3.保存验证码到session  ———> 保存到redis中,redis名字、值、过期时间、时间单位
//        session.setAttribute("code", code);
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//        4.发送验证码到手机
//        注意这里的log是lombok的@Slf4j注解生成的,不然只能写一个参数
log.debug("发送验证码:{},到手机:{}", code, phone);
return Result.ok();
}

这段代码是一个Java方法,用于向指定的手机号发送验证码并处理相关逻辑。下面是对代码中关键部分的解释和你提到的几点疑问的解答:

  1. 关于异常处理

    • 当使用 throw new RuntimeException("手机号格式不正确"); 时,如果手机号格式验证失败,会抛出一个运行时异常。这个异常如果没有被捕获,会直接中断当前方法的执行,并且,如果项目中配置了全局异常处理器(如Spring的@ControllerAdvice),这个异常会被全局异常处理器捕获,然后按照全局异常处理器中定义的方式处理,通常会转换成特定的错误响应给客户端。
    • 而如果直接 return Result.fail("手机号格式不正确"); (假设Result类有这样一个静态方法表示操作失败并附带错误信息),则是一种更直接的错误处理方式,不涉及异常抛出,直接通过返回值告知调用方操作失败及原因。这种方式适用于不需要中断程序执行流,或者不需要全局统一处理该类型错误的场景。
  2. 保存验证码到Redis

    • 代码中使用了Spring Data Redis的StringRedisTemplate来操作Redis数据库,将生成的验证码保存到Redis中,键为LOGIN_CODE_KEY + phone,这样做可以确保每个手机号对应的验证码存储是独立的,并且设置了验证码的有效时间为LOGIN_CODE_TTL分钟,这样可以自动清理过期验证码,避免数据冗余和安全问题。
  3. 日志记录

    • 使用Lombok的@Slf4j注解自动生成的日志对象(如log)可以简化日志记录的代码。log.debug(...)语句会在调试模式下记录消息,帮助开发者追踪验证码的发送过程,包括发送的验证码内容和目标手机号。如果不使用Lombok,则需要手动创建日志对象,例如:private static final Logger log = LoggerFactory.getLogger(YourClassName.class);

总结来说,这段代码实现了手机号格式校验、验证码生成、验证码存储至Redis以及日志记录的功能,同时展示了在遇到错误时使用异常与直接返回错误结果两种不同处理方式的区别。
在代码片段中,使用的Redis数据类型是 字符串(String)。具体体现在这一行代码:

stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);

这里,opsForValue() 方法用于操作Redis中的字符串值。set 方法则用于设置键值对,其中键是LOGIN_CODE_KEY + phone,值是生成的验证码code,并且设置了过期时间LOGIN_CODE_TTL分钟。这种操作非常适合用来存储临时数据,比如短信验证码,因为字符串类型是Redis中最基本且灵活的数据类型,能够存储任何二进制安全的数据,包括数字、文本、甚至是序列化的对象。

@Override
public Result login(LoginFormDTO loginForm) {
//        1.检验手机号  ———>  因为每个请求都是单独的,使用还要再检查一次
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
//        2.检验验证码   --  从redis中获取
//        Object cacheCode = session.getAttribute("code");
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
System.out.println("cacheCode = " + cacheCode);
return Result.fail("验证码错误");
}
//        3.检验用户是否存在
//        法1:最简洁的用法,但是有硬编码
//        User user = query().eq("phone", phone).one();
//        法2:使用lambda表达式,减少硬编码
//        User user = this.lambdaQuery().eq(User::getPhone, loginForm.getPhone()).one();
//        法3:复杂一点,但多了一个isNotBlank的动态查询
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(StringUtils.isNotBlank(phone), User::getPhone, phone);
User user = userMapper.selectOne(queryWrapper);
//        如果不存在则创建用户
if (user == null) {
user = createUserWithPhone(phone);
}//        4.保存用户到session  -- 保存到token中
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 因为user的id是long类型的,但是StringRedisTemplate只支持String类型的key-value,因此要需要⾃定义map映射规将user转成map后进⾏hash存储
// userDTO:要转换为Map的Java对象       new HashMap<>():存储转换后的Map的容器
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
// 忽略userDTO对象中的空值属性,即那些值为null的属性不会被放入userMap中
CopyOptions.create().setIgnoreNullValue(true)
// 将属性值放入userMap前,将属性值转换为其字符串表示形式
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));//        5.存储
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
//        设置过期时间
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
return Result.ok(token);
}

该段Java代码是一个登录方法的实现,主要功能是验证用户通过手机号和验证码登录。以下是关键步骤的解析:

  1. 校验手机号格式:首先使用正则表达式验证传入的手机号是否合法。

  2. 校验验证码:从Redis中根据手机号取出之前保存的验证码(使用stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone)),并与用户提交的验证码进行比较。如果Redis中没有验证码或者不匹配,则登录失败。

  3. 查询用户是否存在

    • 使用Lambda表达式构建查询条件,动态地检查手机号是否非空,并据此查询数据库中的用户记录。这样做可以避免硬编码查询条件,增加代码的灵活性和可维护性。
    • 如果用户不存在,则通过createUserWithPhone(phone)方法创建新用户并保存到数据库。
  4. 用户认证成功处理

    • 生成一个随机的UUID作为token。
    • 将查询到的用户实体转化为UserDTO对象,然后进一步转化为一个Map(这一步是为了方便以HashMap的形式存储到Redis中,因为Redis的Hash数据结构适合存储对象)。
    • 将用户信息以HashMap的形式存入Redis,键为LOGIN_USER_KEY + token,并且设置相应的过期时间,这样就实现了基于token的会话管理,而不是传统的session。
  5. 返回结果:最后返回一个包含生成的token的结果对象,前端可以使用这个token来进行后续的认证请求。

此登录逻辑结合了安全性(如验证码校验)、灵活性(动态查询构造)以及扩展性(使用token而非session管理用户状态),是现代Web应用中常见的认证流程实现方式。

在上述登录方法中,使用的Redis数据类型是Hash

当用户登录成功后,代码片段通过以下命令将用户信息存储到Redis中:

stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);

这里,opsForHash() 方法表明操作的是Redis的Hash数据结构。Hash允许我们将一个键关联到多个字段-值对,非常适合用来存储对象,其中键(tokenKey)代表了这个存储单元,而userMap中的每个键值对则代表了用户的一个属性及其值。这种方式使得可以通过一个键快速访问到用户的所有信息,并且便于单独修改或删除某个属性,非常适合用于会话管理和用户信息的临时存储场景。此外,通过设置过期时间,还可以自动清理不再需要的会话信息,保持Redis内存的有效利用。

为什么保存验证码用String类型,保存用户信息用hash类型

在Redis中选择使用String类型存储验证码和Hash类型存储用户信息,主要是基于以下考虑:

  1. 验证码的特性

    • 简单性:验证码一般只是一个简短的字符串,比如6位数字或字母组合,其数据结构相对简单,不需要复杂的字段关联。
    • 读写频繁:验证码的生成和验证是高频率的操作,每次验证后可能还需要清除或标记已使用的验证码。String类型提供了快速的读写能力,适合这种简单且高频的访问模式。
    • 生命周期短:验证码的有效期较短,通常几分钟到几十分钟,使用String类型可以直接设置键值对的过期时间,实现自动清理过期数据,简单高效。
  2. 用户信息的特性

    • 结构化数据:用户信息通常包含多个属性,如用户名、密码、邮箱、电话等,这些信息之间存在逻辑关联,形成一个结构化的数据对象。
    • 更新灵活性:用户信息可能需要部分更新,例如修改密码或邮箱,而不会影响其他信息。Hash类型允许对单个字段进行操作(如HSET, HGET等),提供了针对字段级别的操作能力,非常适用于需要频繁更新或查询部分属性的场景。
    • 存储效率:虽然理论上String也可以通过序列化整个对象来存储用户信息,但这样做会导致每次读写都需要序列化和反序列化整个对象,效率较低。而Hash类型仅需操作相关的字段,提高了存储和访问的效率。

综上所述,选择String类型存储验证码是因为其简单、高效且生命周期短的特点,而选择Hash类型存储用户信息则是为了适应用户信息的结构化特性和提高数据操作的灵活性及效率。

相关文章:

黑马点评-短信登录

Override public Result sendCode(String phone) { // 1.检验手机号 if (RegexUtils.isPhoneInvalid(phone)) { // 这里抛出异常和return fail有什么区别吗&#xff1f;———> 有区别&#xff0c;抛出异常会被全局异常处理器捕获&#xff0c;返回fail不会 throw ne…...

CentOS7 部署单机版 elasticsearch

一、环境准备 1、准备一台系统为CentOS7的服务器 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 2、创建新用户&#xff0c;用于elasticsearch服务 # elastic不允许使用root账号启动服务 [rootlocalhost ~]# useradd elastic [rootlo…...

Mujoco仿真【xml文件的学习 4】

在学习Mujoco仿真的过程中&#xff0c;mujoco的版本要选择合适。先前我将mujoco的版本升级到了mujoco-3.1.4&#xff0c;在运行act的仿真代码时遇到了问题&#xff0c;撰写了博客&#xff1a; Aloha机械臂的mujoco仿真问题记录-CSDN博客 下面在进行mujoco仿真时&#xff0c;统…...

vue数据持久化仓库

本文章是一篇记录实用性vue数据持久化仓的使用&#xff01; 首先在src中创建store文件夹&#xff0c;并创建一个根据本页面相关的名称&#xff0c; 在终端导入&#xff1a;npm i pinia 和 npm i pinia-plugin-persistedstate 接下来引入代码&#xff1a; import { defineSt…...

OrangePi AIpro评测 - 基础操作篇

0. 环境 ●OrangePi AIpro ●win10笔记本 ●路由器 准备下win10电脑、路由器&#xff0c;这些板卡通常是在网络正常的环境下才方便测试。 还要准备OrangePi AIpro的官方资料&#xff1a; http://www.orangepi.cn/html/hardWare/computerAndMicrocontrollers/service-and-suppo…...

不含一阶导数项的线性二阶微分方程的通解

假设这里有一个线性二阶微分等式&#xff0c;形式如下&#xff1a; &#xff08;1&#xff09; 其中是连续的&#xff0c;是在实闭区间是连续的,如果有人倾向于推广&#xff0c;在相对假弱的假设下&#xff0c;这个结果能够被发现。如果是下列其次线性方程的任意两个线性无关的…...

Redis篇 String

String概念和set,get扩充 一. String类型的基本介绍二. String中set,get方法扩充 一. String类型的基本介绍 redis中所有的key都是字符串类型的,但是value的类型差异很大. redis中的字符串,直接就是二进制方式存储的,可以存储整数,二进制数据 文本数据,Json,xml还有音频等. 二.…...

【vue-2】v-on、v-show、v-if及按键修饰符

目录 1、v-on事件 2、按键修饰符 3、显示和隐藏v-show 4、条件渲染v-if 1、v-on事件 创建button按钮有以下两种方式&#xff1a; <button v-on:click"edit">修改</button> <button click"edit">修改</button> 完整示例代码…...

华为交换机基础实验----VLAN基础

交换机篇实验&#xff1a; 给交换机创建VLAN 1.单个VLAN的创建 [S]vlan 10 查看的方法&#xff1a;dis vlan 2.批量创建vlan的方法 Vlan b 20 30 40 连续创建三个vlan&#xff0c;分别为vlan20 vlan30和vlan40 [SONY-S1-vlan10]vlan b 20 30 40 3.批量创建连续的vlan&#xf…...

Vue3学习使用axios和qs进行POST请求和响应处理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、前言1.准备工作2.发送POST请求3.处理响应数据4.总结 一、前言 在前端开发中&#xff0c;经常需要与后端进行数据交互&#xff0c;其中包括发送POST请求并处理响…...

张大哥笔记:赚钱高手养成计划---如何将一份时间产生N份收入?

我们常说的赚钱的四种境界有哪些&#xff1f; 1.靠体力挣钱 2.靠技能挣钱 3.靠知识挣钱 4.靠平台钱生钱 所以对应的收入的模式就会是下面4种模式&#xff1a; 1.一份时间卖1次 2.一份时间卖N次 3.一份时间溢价卖N次 4.购买他人时间为自己所用 时间对于每个人都是相同的…...

excel里如何将数据分组转置?

这个表格怎样转换为下表&#xff1f;按照国家来分组&#xff0c;把不同年份对应的不同序列值进行转置&#xff1f;&#xff1f; 这演示用数据透视表就完成这个数据转换。 1.创建数据透视表 选中数据中任意单元格&#xff0c;点击插入选项卡&#xff0c;数据透视表&#xff0c;…...

WHAT - 前端安全性测试和常见攻击手段

目录 一、安全性测试二、前端安全性测试三、跨站脚本&#xff08;XSS&#xff09;攻击1. 介绍2. 三大类型反射型 XSS&#xff08;Reflected XSS&#xff09;存储型 XSS&#xff08;Stored XSS&#xff09;DOM 型 XSS&#xff08;DOM-based XSS&#xff09; 3. xss 盲打4. xss 水…...

重量and体积,不要在傻傻的花冤枉钱寄快递了!

寄快递时有没有遇到过明明不重却被按体积收费的情况&#xff1f;别急&#xff0c;今天就来给大家揭秘快递收费的奥秘&#xff01; 实际重量和体积重量&#xff01; 首先&#xff0c;我们要明白两个概念&#xff1a;实际重量和体积重量。实际重量就是你看到的物品重量&#xf…...

docker ps显示的参数具体是什么意思

1&#xff0c;运行一个容器 docker run -d ubuntu:15.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"这段命令的作用是使用 docker run 命令运行一个基于 ubuntu:15.10 镜像的 Docker 容器&#xff0c;并在容器中执行一个无限循环的命令。 具体解…...

【C++】多态:编程中的“一人千面”艺术

目录 一、多态的概念二、多态的定义及实现1.多态的构成条件2.虚函数的重写2.1 什么是虚函数&#xff1f;2.2 虚函数的重写是什么&#xff1f;2.3 虚函数重写的两个例外2.4 C11 override 和 final2.5 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类3.1 概念3.2 接口继承和实现…...

【必备工具】gitee上传-保姆级教程

目录 1.gitee是什么 2.gitee怎么注册 ​编辑 3.gitee怎么提交代码 4.gitee的三板斧 Clone仓库 Q&A 1. Gitee 只有三板斧吗&#xff1f; 2. Git 教了&#xff0c;Gitee 上没有绿点怎么办&#xff1f; 3. 用户名和密码输入错误怎么办&#xff1f; 4. 操作时不小心…...

P1115 最长子段和

题目描述 给出一个长度为 &#x1d45b;n 的序列 &#x1d44e;a&#xff0c;选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数&#xff0c;表示序列的长度 &#x1d45b;。 第二行有 &#x1d45b;n 个整数&#xff0c;第 &#x1d456; 个整数表示序列的…...

02 FreeRTOS 任务

1、创建任务函数 1.1 动态内存的使用 在之前我们如果要创建一个与学生有关的任务&#xff0c;我们会定义&#xff1a; //打印50个学生的信息 char name[50][100]; int age[50]; int sex[50]; //1表示男&#xff0c;0表示女 int score[50]; 如果之后要对其进行修改会非常麻烦&…...

NSS题目练习4

[LitCTF 2023]1zjs 打开后是一个游戏&#xff0c;用dirsearch扫描&#xff0c;什么都没发现 查看源代码搜索flag&#xff0c;发现没有什么用 搜索php&#xff0c;访问 出现一堆符号&#xff0c;看样子像是jother编码 解码得到flag&#xff0c;要删掉[] [LitCTF 2023]Http pro …...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)

目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 ​编辑​编辑 UDP的特征 socke函数 bind函数 recvfrom函数&#xff08;接收函数&#xff09; sendto函数&#xff08;发送函数&#xff09; 五、网络编程之 UDP 用…...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

UE5 音效系统

一.音效管理 音乐一般都是WAV,创建一个背景音乐类SoudClass,一个音效类SoundClass。所有的音乐都分为这两个类。再创建一个总音乐类&#xff0c;将上述两个作为它的子类。 接着我们创建一个音乐混合类SoundMix&#xff0c;将上述三个类翻入其中&#xff0c;通过它管理每个音乐…...