点评项目(Redis中间件)第三部分短信登录,查询缓存

可以直接看后面Redis实现功能的部分

基于session实现短信登录

发送短信验证码

前端请求样式

业务层代码

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic Result sendCode(String phone, HttpSession session) {// 1. 校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2. 如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3. 符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4. 保存验证码到sessionsession.setAttribute("code", code);// 5. 发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}
}
关于参数httpsession
  • 来源Spring框架(Servlet容器)自动注入

  • 机制:这是Spring MVC一个非常强大的特性,叫做参数解析(Handler Method Argument Resolution)。当你在一个控制器方法中声明某个类型的参数时,Spring会查看它注册的众多 HandlerMethodArgumentResolver 组件,找到一个能处理该类型的解析器,然后自动为你创建或获取该对象,并传入方法。

    • 对于 HttpSession 类型,Spring使用的是 ServletWebRequest 等相关解析器。这个解析器会:

      1. 检查当前的HTTP请求对象 (HttpServletRequest)。

      2. 调用 request.getSession() 方法来获取当前请求关联的Session。

        • getSession() 方法非常智能:如果Session已经存在,则返回它;如果不存在,则创建一个新的Session并返回

HttpSession 参数不是前端发送来的,而是Spring框架根据你的参数类型,自动从当前HTTP请求中获取并“注入”到方法中的。它的作用是提供一种在服务器端存储用户相关数据并在多次请求间共享的机制。

登录功能

前端请求样式

业务层代码

黑马给的代码里面,工具类的相关注解有一处错误

 /*** 是否是无效手机格式* @param phone 要校验的手机号* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}

返回ture是无效验证码

之所以一直使用反向验证是因为正向验证会使得代码有很多if的嵌套

业务层代码

@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,报错return Result.fail("验证码错误");}// 4.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判断用户是否存在if (user == null) {// 6.不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到 redis中// 7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将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()));// 7.3.存储String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);}

如果保存可能会导致一些泄露问题,而且我们也不需要那么多信息,只需要唯一标识,所以我们把user对象转换成userdto对象,转换代码上面有,还是使用的BeanUtil工具类来实现。

验证拦截功能

这个功能放到拦截器里面,方便所有的可控制器都可以实现用户校验。然后将拦截到的用户信息传递到控制器里面去。不过这里有线程安全问题,所以我们需要使用threadlocal保证安全。

线程安全问题

简单来说,线程安全的根源在于:多个线程同时读写共享的可变数据(状态)时,如果没有正确的同步,就会导致不确定的结果。

特征线程安全线程不安全
数据来源局部变量、方法参数、不可变对象共享的成员变量、非线程安全容器
操作类型单纯的读操作、无状态的计算复合操作(如 i++)、"检查-然后-执行"
代码示例return a + b; String s = "hello";count++; if (map.contains(key)) { map.put(key, value); }
核心原因没有共享 或 共享不可变共享且可变,且未正确同步
变量类型存储位置共享范围线程安全问题来源
实例变量
(非 static)
堆内存 (Heap)
(每个对象内)
同一个对象实例被多个线程引用高发区。因为容易被忽略,人们常常以为“非static就是安全的”,却忘了单个实例被多线程共享这一常见场景。
类变量
(static)
方法区 (Method Area)整个JVM内的所有线程和所有实例显而易见。因为大家都知道
拦截器代码(实现Redis最终版)

在工具类里面写一个拦截器,需要实现一个接口,以及三个接口方法

  1. preHandle:在Controller方法执行之前被调用。通常用于身份验证、日志记录、权限检查等。如果返回 false,则中断请求流程。

  2. postHandle:在Controller方法执行之后,但视图渲染之前被调用。通常用于向模型添加公共数据、记录执行时间等。

  3. afterCompletion:在整个请求完成之后(即视图渲染完毕)被调用。通常用于资源清理、记录最终日志等。

package com.hmdp.utils;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}
}
配置拦截器

需要一个mvc config类(实现Redis最终版)

package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);// token刷新的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

里面有些控制器不需要被拦截所以我们需要排除在外。

共享问题

替代办法

Redis

使用Redis实现session登录

获取并保存验证码

public Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到 sessionstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}

验证码发送的功能很复杂,这里直接用日志代替。

TimeUnit.MINUTES - 时间单位

短信登录以及注册功能

登录之后用户的信息还要存储到redis里面,key就用一个随机的token,存储对象转换成哈希,这个token会返回给前端。

public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,报错return Result.fail("验证码错误");}// 4.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判断用户是否存在if (user == null) {// 6.不存在,创建新用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到 redis中// 7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将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()));// 7.3.存储String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);}
  • 先检查 cacheCode 是否为 null(防止空指针异常)

  • 如果是 null,直接进入if块,不会执行后面的equals

  • 如果不是 null,再比较内容是否相等

token来源

UUID是一个全球唯一的标识符,就像每个人的身份证号一样,几乎不可能重复。

  1. 唯一性 - 几乎不可能重复

  2. 安全性 - 难以猜测,适合做token

  3. 分布式 - 不同服务器生成也不会冲突

  4. 无需协调 - 不需要中央机构分配

下一步就是redis的语法应用了,将信息存储到redis里面。

// 7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 7.2.将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()));// 7.3.存储String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);

这里面用beantil工具把bean转换成了一个map对象,然后使用

设置token有效期,

String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

另外只要用户在不断访问我们就要不断的更新redis的时间限制,在拦截器里面添加更新逻辑。

null - 不存在

  • 对象根本不存在

  • 没有分配内存空间

  • 相当于"无中生有"

isEmpty() - 存在但为空

  • 对象存在,但内容为空

  • 已经分配了内存空间

  • 相当于"空盒子"

刷新token时间的拦截器

因为有一些功能是不需要校验的,所以为了这些功能也有刷新token的功能所以我们重新创建一个新的拦截器。

package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2.基于TOKEN获取redis中的用户String key  = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

拦截器的代码我们在前面就是放的最终版的,两个拦截器需要设置先后顺序,我们使用order设置大小,越小的越先执行。

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

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

相关文章

线性方程求解器的矩阵分裂

大概思路是对的&#xff0c;但是查老师可能会出现幻觉&#xff0c;小心食用 &#x1f603;这段代码是在初始化迭代法求解器&#xff0c;构建迭代矩阵和分裂矩阵。以下是详细解释&#xff1a; if init_from_func or init_from_input:# 1. 存储刚度矩阵self.stiff_p stiff_p# 2.…

【Beetle RP2350】雷达模块 CEM5861G-M11 开发使用指南

一、硬件介绍 1、产品特点 Beetle RP2350【RP2350A_QFN60】是一款基于RP2350微控制器的高性能迷你开发板&#xff0c;双核双架构设计&#xff08;支持 Arm Cortex-M33或Hazard3 RISC-V内核&#xff09;为开发者提供灵活的性能配置。 双核双架构&#xff0c;性能自由切换 采…

高通Android 13 开机黑屏问题深度剖析与解决方案

1. 问题概述 在 Android 13 系统定制化开发过程中&#xff0c;开机流程的调试与优化颇具挑战性。一个典型问题是&#xff1a;当开机动画播放完毕后&#xff0c;设备会先出现数秒黑屏&#xff0c;然后才进入锁屏界面。本文基于开机日志分析&#xff0c;结合实际项目经验&#x…

腾讯推出AI CLI工具CodeBuddy,国内首家同时支持插件、IDE和CLI三种形态的AI编程工具厂商

2025年9月9日&#xff0c;腾讯正式推出自研AI CLI工具CodeBuddy code&#xff0c;成为国内首家同时支持插件、IDE和CLI三种形态的AI编程工具厂商。这一创新不仅填补了国内市场在全形态AI编程工具领域的空白&#xff0c;更以编码时间缩短40%、AI生成代码占比超50%的硬核数据&…

零基础学习QT的第二天-组件基础知识

组件声明以及设置属性 所有的组件的基类为&#xff1a;QtObject&#xff0c;在c中名称为&#xff1a;QObject。 在qml和c名称有所区别&#xff0c;例如在Qml中QtObject&#xff0c;在C会省略一个t(QObject) 声明组件的方式&#xff1a; 组件名 {属性名:值}在实际应用中&#xf…

像素图生成小程序开发全解析:从图片上传到Excel图纸

像素图生成小程序开发全解析&#xff1a;从图片上传到Excel图纸 前言 在数字化创作和工艺设计领域&#xff0c;像素图生成工具具有广泛的应用价值&#xff0c;无论是十字绣设计、LED灯阵布置还是复古游戏美术创作。本文将详细解析一个功能完整的像素图生成小程序的开发过程&…

mac-intel操作系统go-stock项目(股票分析工具)安装与配置指南

1. 项目基础介绍 go-stock 是一个基于Wails和NaiveUI开发的AI赋能股票分析工具。旨在为用户提供自选股行情获取、成本盈亏展示、涨跌报警推送等功能。它支持A股、港股、美股等市场&#xff0c;能够进行市场整体或个股的情绪分析、K线技术指标分析等功能。所有数据均保存在本地…

spring-单例bean是线程安全的吗

其中可修改的成员变量有线程不安全问题&#xff0c;不可修改的无状态的 userService是没有线程安全问题的 spring框架中有一个 Scope注解&#xff0c;默认的值就是singleton&#xff0c;单例的。 不是线程安全的&#xff0c;一般来说&#xff0c;我们在bean中注入的对象都是无状…

CM1033系列 3串锂电池保护IC - 高精度±25mV 内置延时 多型号可选(含铁锂)

1. 核心亮点 高精度多重保护&#xff1a;专为3串电池组设计&#xff0c;提供过充、过放、三级过流&#xff08;含短路&#xff09;、充电过流及断线检测等全方位保护&#xff0c;电压检测精度高达25mV。超低功耗&#xff1a;工作电流典型值仅7μA&#xff0c;休眠电流低至4μA&…

【第23话:定位建图】SLAM后端优化方法详解

SLAM 后端优化方法详解 SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;后端优化是SLAM系统中的关键环节&#xff0c;负责对前端输出的传感器数据进行全局一致性优化&#xff0c;消除累积误差。后端通常基于图优化框架&#xff08;如g2o、GTSAM&#xff09…

MongoDB 备份与恢复终极指南:mongodump 和 mongorestore 深度实战

MongoDB 备份与恢复终极指南&#xff1a;mongodump 和 mongorestore 深度实战引言&#xff1a;数据守护者的使命第一部分&#xff1a;基础概念与核心原理1.1 逻辑备份 vs. 物理备份&#xff1a;根本性的区别1.2 核心工具介绍第二部分&#xff1a;mongodump 备份实战详解2.1 基础…

鸿蒙的“分布式架构”理念:未来操作系统的关键突破

一、引言&#xff1a;为什么需要分布式架构&#xff1f; 随着移动互联网的发展&#xff0c;智能设备不断普及。用户身边可能同时拥有 手机、平板、PC、电视、手表、耳机、智能音箱、车机 等多种终端设备。 但现实中&#xff0c;我们常遇到以下问题&#xff1a; 不同设备系统割…

MySQL 事务管理与锁机制:解决并发场景下的数据一致性问题

前言在电商下单、金融转账、库存扣减等并发业务场景中&#xff0c;若不控制数据操作的原子性与隔离性&#xff0c;极易出现 “超卖”“重复扣款”“脏读数据” 等问题。MySQL 的事务管理与锁机制是解决这些问题的核心技术&#xff0c;也是后端开发者必须掌握的生产环境能力。本…

MySQL集群高可用架构

一、MySQL高可用之组复制&#xff08;MGR&#xff09;1.1 组复制核心特性与优势MySQL Group Replication&#xff08;MGR&#xff09;是基于分布式一致性协议&#xff08;Paxos&#xff09;实现的高可用集群方案&#xff0c;核心特性包括&#xff1a;自动故障检测与恢复&#x…

判别模型 VS 生成模型

1. 判别模型&#xff08;Discriminative Models&#xff09;判别模型直接学习输入特征&#xff08;X&#xff09;与输出标签&#xff08;Y&#xff09;之间的映射关系&#xff0c;即直接对条件概率P(Y|X)进行建模。判别模型关注于如何区分不同类别的数据。特点&#xff1a;直接…

代码随想录算法训练营第三十一天 | 合并区间、单调递增的数字

合并区间&#xff1a; 这里还是先对左区间进行排序&#xff0c;判断重叠区间&#xff0c;首先判断是否存在元素&#xff0c;存在那么就将元素的第一个放到结果中&#xff0c;那么判断重叠就是当前元素的左区间和结果集里的最后元素的右区间进行判断&#xff0c;如果重叠&#x…

EXCEL VBA 清空Excel工作表(Sheet)的方法

1. 删除所有内容&#xff0c;但保留格式和对象 这种方法只会清除单元格的内容&#xff0c;不会影响格式和嵌入的图表或对象。 Sub ClearSheetContents()Worksheets("Sheet1").Cells.ClearContents End Sub2. 删除所有内容和格式&#xff0c;但保留对象 这种方法会删除…

智能客户服务支持智能体

超越传统客服机器人。智能体可以深度查询知识库、调用订单系统API、甚至根据客户情绪灵活处理退货、退款、升级投诉等复杂流程。 案例&#xff1a; 客户说&#xff1a;“我上周买的鞋子尺码不对&#xff0c;想换货但是找不到订单页面了。” 智能体行动&#xff1a; ① 通过用户…

【MySQL|第四篇】DQL语句(二)——数据查询语言

4、排序分页&#xff1a;&#xff08;1&#xff09;排序&#xff1a;查询数据的时候进行排序&#xff0c;就是根据某个字段的值&#xff0c;按照升序或者降序的情况将记录显示出来语法&#xff1a; select col_name,... from tb_name order by col_name [asc|desc]注意事项&…

百度文心X1.1发布!实测深度思考能力!

文章目录背景模型实测效果事实性指令跟随智能体模型技术解读基准测试文心飞桨携手共进总结背景 9月9日&#xff0c;WAVE SUMMIT深度学习开发者大会上&#xff0c;百度首席技术官、深度学习技术及应用国家工程研究中心主任王海峰正式发布了文心大模型X1.1深度思考模型&#xff…