当前位置: 首页 > 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 …...

Python新手必看:从安装到第一个GUI程序的全流程指南(含IDLE使用技巧)

Python新手必看&#xff1a;从安装到第一个GUI程序的全流程指南&#xff08;含IDLE使用技巧&#xff09; 引言 对于刚接触编程的新手来说&#xff0c;Python无疑是最友好的入门语言之一。它简洁的语法、丰富的库支持以及活跃的社区&#xff0c;都让学习过程变得轻松愉快。本文将…...

Burp Suite实战:如何用Base64编码爆破网站登录(附完整配置流程)

Burp Suite高级实战&#xff1a;Base64编码爆破攻击的深度解析与防御策略 在渗透测试领域&#xff0c;认证机制的安全性评估始终是核心环节。Base64编码作为一种常见的数据表示方式&#xff0c;常被误认为具有加密功能而用于认证传输。本文将深入剖析如何利用Burp Suite对采用B…...

从修车铺到世界冠军,从废塑料到再生资源:一场关于坚持与价值的时代对话

最近&#xff0c;张雪的故事刷屏了。这个14岁辍学、睡在修车铺阁楼、月薪300元的湖南山村少年&#xff0c;用了整整二十年&#xff0c;将自己亲手打造的摩托车送上了世界超级摩托车锦标赛&#xff08;WSBK&#xff09;的冠军领奖台。当五星红旗在葡萄牙阿尔加维国际赛道升起时&…...

[具身智能-230]:大模型编程的一个最佳实践:先通过自然语言让大模型编写Python语言代码,功能和性能调通后,再让大模型把python程序转换成C++或其他语言的程序

这种“Python 原型验证 C 性能落地”的开发模式&#xff0c;完美契合了大模型&#xff08;LLM&#xff09;的能力特点以及现代软件工程的需求。结合最新的行业实践和技术原理&#xff0c;我为你深度解析为什么这种工作流如此有效&#xff0c;以及在实际操作中需要注意的关键点…...

告别繁琐手工操作:工资条生成器使用指南

对于许多财务人员来说&#xff0c;每月制作工资条都是一项让人头疼的工作。 手工制作不仅要花费大量时间&#xff0c;还容易出现各种错误&#xff0c;影响工作效率和准确性。 今天&#xff0c;我们就来详细介绍一款能够彻底改变这种状况的工具——工资条生成器。 工资条生成…...

四场景下的两阶段鲁棒优化模型构建与实施——列与约束生成算法及其数据处理机制探究

两阶段鲁棒优化模型 多场景 采用matlab编程两阶段鲁棒优化程序&#xff0c;考虑四个场景&#xff0c;模型采用列与约束生成&#xff08;CCG&#xff09;算法进行求解&#xff0c;场景分布的概率置信区间由 1-范数和∞-范数约束&#xff0c;程序含拉丁超立方抽样kmeans数据处理程…...

别只盯着TCP!拆解大疆源码里MQTT协议的双通道设计:BASIC与DRC到底有啥区别?

大疆源码中的MQTT双通道设计&#xff1a;BASIC与DRC的工程哲学 在分析大疆无人机开源项目的通信架构时&#xff0c;一个有趣的设计选择跃然眼前——MQTT协议同时运行在TCP和WebSocket两种传输层上。这种看似冗余的配置背后&#xff0c;隐藏着对物联网通信场景的深刻理解。本文将…...

电赛赛题深度解析:从五大类别到实战备赛策略

1. 电赛赛题五大类别全解析 全国大学生电子设计竞赛&#xff08;简称电赛&#xff09;作为电子类专业最具影响力的赛事&#xff0c;其赛题设置直接反映了行业技术发展趋势。经过对近十年赛题的统计分析&#xff0c;所有题目可明确划分为五大类别&#xff0c;每类都有独特的考察…...

OpenBMC实战:phosphor-bmc-code-mgmt仓库代码逻辑全解析(附避坑指南)

OpenBMC实战&#xff1a;phosphor-bmc-code-mgmt仓库代码逻辑全解析&#xff08;附避坑指南&#xff09; 在嵌入式系统开发领域&#xff0c;BMC&#xff08;Baseboard Management Controller&#xff09;固件的可靠更新机制是确保服务器稳定运行的关键环节。作为OpenBMC项目的核…...

Claude Code源码分析之提示词工程

每天免费领 1亿 Token&#xff0c;白嫖DeepSeek、GLM、MiniMax、Kimi等大模型&#xff01; 在开发大模型应用的时候&#xff0c;管理系统提示词&#xff08;System Prompt&#xff09;往往是个让人头大的工程难题。要是只用简单的字符串拼接&#xff0c;随着活儿越接越多&#…...