黑马点评-短信登录

2024-05-27 00:20
文章标签 黑马 登录 短信 点评

本文主要是介绍黑马点评-短信登录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

@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类型存储用户信息则是为了适应用户信息的结构化特性和提高数据操作的灵活性及效率。

这篇关于黑马点评-短信登录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1006003

相关文章

springboot security验证码的登录实例

《springbootsecurity验证码的登录实例》:本文主要介绍springbootsecurity验证码的登录实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录前言代码示例引入依赖定义验证码生成器定义获取验证码及认证接口测试获取验证码登录总结前言在spring

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

Oracle登录时忘记用户名或密码该如何解决

《Oracle登录时忘记用户名或密码该如何解决》:本文主要介绍如何在Oracle12c中忘记用户名和密码时找回或重置用户账户信息,文中通过代码介绍的非常详细,对同样遇到这个问题的同学具有一定的参... 目录一、忘记账户:二、忘记密码:三、详细情况情况 1:1.1. 登录到数据库1.2. 查看当前用户信息1.

MobaXterm远程登录工具功能与应用小结

《MobaXterm远程登录工具功能与应用小结》MobaXterm是一款功能强大的远程终端软件,主要支持SSH登录,拥有多种远程协议,实现跨平台访问,它包括多会话管理、本地命令行执行、图形化界面集成和... 目录1. 远程终端软件概述1.1 远程终端软件的定义与用途1.2 远程终端软件的关键特性2. 支持的

redis防止短信恶意调用的实现

《redis防止短信恶意调用的实现》本文主要介绍了在场景登录或注册接口中使用短信验证码时遇到的恶意调用问题,并通过使用Redis分布式锁来解决,具有一定的参考价值,感兴趣的可以了解一下... 目录1.场景2.排查3.解决方案3.1 Redis锁实现3.2 方法调用1.场景登录或注册接口中,使用短信验证码场

Oracle数据库如何切换登录用户(system和sys)

《Oracle数据库如何切换登录用户(system和sys)》文章介绍了如何使用SQL*Plus工具登录Oracle数据库的system用户,包括打开登录入口、输入用户名和口令、以及切换到sys用户的... 目录打开登录入口登录system用户总结打开登录入口win+R打开运行对话框,输php入:sqlp

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

Shell脚本实现自动登录服务器

1.登录脚本 login_server.sh #!/bin/bash# ReferenceLink:https://yq.aliyun.com/articles/516347#show all host infos of serverList.txtif [[ -f ./serverList.txt ]]thenhostNum=`cat ./serverList.txt | wc -l`e