黑马点评-短信登录

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

相关文章

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

SpringBoot登录退出|苍穹外卖登录退出分析

文章目录 概要整体流程注意事项一、拦截路径二、token三、注册防止用户重复提交 苍穹外卖登录退出分析注意解决JWT退出后依然有效的问题 概要 结合Spring Boot和Vue3实现安全的用户登录和退出功能,并使用拦截器、JWT和Redis缓存来提高系统的安全性和性能。 整体流程 注意事项 一、拦截路径 像登录页面的路径就不要拦截了,否则都不能登录了 例如:

Node.js和vue3实现GitHub OAuth第三方登录

Node.js和vue3实现GitHub OAuth第三方登录 前言 第三方登入太常见了,微信,微博,QQ…总有一个你用过。 在开发中,我们希望用户可以通过GitHub账号登录我们的网站,这样用户就不需要注册账号,直接通过GitHub账号登录即可。 效果演示 注册配置 GitHub 应用 1.首先登录你的GitHub然后点击右上角的头像->点击进入Settings页面 2.在

三方登录 - 华为登录

1.1. 开发准备 当应用需要使用以下开放能力的一种或多种时,为正常调试运行应用,需要预先添加公钥指纹 Account Kit(华为帐号服务)Call Kit(通话服务)Game Service Kit(游戏服务)Health Service Kit(运动健康服务)IAP Kit(应用内支付服务)Live View Kit(实况窗服务,当需要使用Push Kit时必须执行此步骤)Map Kit

web登录校验

基础登录功能 LoginController @PostMapping("/login")Result login(@RequestBody Emp emp) {log.info("前端,发送了一个登录请求");Emp e = empService.login(emp);return e!=null?Result.success():Result.error("用户" +"名或密码错误");

mysql导出导入数据和修改登录密码

导出表结构: mysqldump -uroot -ppassword -d dbname tablename>db.sql; 导出表数据: mysqldump -t dbname -uroot -ppassword > db.sql 导出表结构和数据(不加-d): mysqldump -uroot -ppassword dbname tablename > db.sql;

【中国国际航空-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 所以大部分网站及App 都采取图形验证码或滑动验证码等交互解决方案, 但在机器学习能力提高的当下,连百度这样的大厂都遭受攻击导致点名批评, 图形验证及交互验证方式的安全性到底如