Redis之短信登录

news2024/11/19 5:37:13

文章目录

  • 基于 Session 实现
    • 发送验证码
    • 登录校验验证码
    • 登录拦截器
    • 注册拦截器
  • 基于 Redis 实现
    • 发送验证码
    • 登录校验
    • 登录拦截器
    • 登录拦截器优化

基于 Session 实现


发送验证码

/**
 * 发送手机验证码
 */
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
    // 发送短信验证码并保存验证码
    return userService.sendCode(phone, session);

@Override
public Result sendCode(String phone, HttpSession session) {
	// 校验手机号
	if(RegexUtils.isPhoneInvalid(phone)){
		// 不符合 返回错误信息
		return Result.fail("手机号格式不正确");
	}
	// 符合 生成验证码
	String code = RandomUtil.randomNumbers(6);
	// 保存验证码到 session
	session.setAttribute("code", code);
	// 发送验证码
	log.debug("发送验证码: {} 到手机: {}", code, phone);
	// 返回成功信息
	return Result.ok();
}
  1. 发送验证码之前校验手机号是否符合规则
  2. 不符合校验规则就返回错误信息
  3. 符合校验规则就生成验证码并保存到session中
  4. RandomUtil.randomNumbers(6)使用hutool工具生成6位随机验证码
  5. 最后发送验证码并返回成功提示

image.png

登录校验验证码

/**
 * 登录功能
 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
 */
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    // 实现登录功能
    return userService.login(loginForm, session);
}

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
	// 校验手机号
	if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){
		// 不符合 返回错误信息
		return Result.fail("手机号格式不正确");
	}
	// 校验验证码
	Object cacheCode = session.getAttribute("code");
	if(cacheCode == null || !cacheCode.toString().equals(loginForm.getCode())){
		// 验证码不一致
		return Result.fail("验证码不正确");
	}
	// 验证码一致 根据手机号查询用户信息
	User user = lambdaQuery().eq(User::getPhone, loginForm.getPhone()).one();
	// 判断用户是否存在
	if(user == null){
		// 不存在 创建新用户并保存
		User registerUser = new User();
		registerUser.setPhone(loginForm.getPhone());
		registerUser.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(8));
		save(registerUser);
		session.setAttribute("user", registerUser);
	}
	if(user != null){
		session.setAttribute("user", user);
	}
	return Result.ok();
}
  1. 校验手机号是否符合规则
  2. 用户输入的验证码与session中保存的验证码进行比对
  3. 不一致则返回验证码错误的提示信息
  4. 验证码一致则拿用户输入的手机号进行查库看用户是否存在
  5. 存在则将查到的用户信息存入session中, 以备后续使用
  6. 不存在则用输入的手机号自动注册一个新用户存库并将信息保存session

登录拦截器

public class LoginInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 获取session
		HttpSession session = request.getSession();
		// 获取session中的用户
		Object user = session.getAttribute("user");
		// 判断用户是否存在
		if(user == null){
			// 不存在 401拦截
			response.setStatus(401);
			return false;
		}
		// 存在 保存用户到ThreadLocal
		UserHolder.saveUser((UserDTO) user);
		// 放行
		return true;
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		// 移除用户
		UserHolder.removeUser();
	}
}
  1. 从请求中拿到session, 再从session中拿到user信息
  2. 如果user信息为空则返回401响应码并拦截登录请求
  3. 如果存在则将用户信息存入线程ThreadLocal线程中, 以备后续使用
  4. 最后放行

注册拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// 添加拦截器
		registry.addInterceptor(new LoginInterceptor())
				// 放行请求
				.excludePathPatterns(
						"/shop/**",
						"/voucher/**",
						"/shop-type/**",
						"/upload/**",
						"/blog/hot",
						"/user/code",
						"/user/login"
				);
	}
}

基于 Redis 实现


发送验证码

/**
 * 发送手机验证码
 */
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
    // 发送短信验证码并保存验证码
    return userService.sendCode(phone, session);

@Override
public Result sendCode(String phone, HttpSession session) {
	// 校验手机号
	if(RegexUtils.isPhoneInvalid(phone)){
		// 不符合 返回错误信息
		return Result.fail("手机号格式不正确");
	}
    
	// 符合 生成验证码
	String code = RandomUtil.randomNumbers(6);
    
	// 保存验证码到redis
	stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
	
    // 发送验证码
	log.debug("发送验证码: {} 到手机: {}", code, phone);
    
	// 返回成功信息
	return Result.ok();
}
  1. 对比上面的代码就是将保存验证码到session变成保存到redis中

登录校验

/**
 * 登录功能
 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
 */
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    // 实现登录功能
    return userService.login(loginForm, session);
}

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
	String phone = loginForm.getPhone();
	String code = loginForm.getCode();
	// 校验手机号
	if(RegexUtils.isPhoneInvalid(phone)){
		// 不符合 返回错误信息
		return Result.fail("手机号格式不正确");
	}
	// 从redis获取验证码并校验
	String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
	if(cacheCode == null || !cacheCode.equals(code)){
		// 验证码不一致
		return Result.fail("验证码不正确");
	}
	// 验证码一致 根据手机号查询用户信息
	User user = lambdaQuery().eq(User::getPhone, phone).one();
	// 判断用户是否存在
	if(user == null){
		// 不存在 创建新用户并保存
		user = createUser(phone);
	}
	// 存在 保存用户到redis
	// 随机生成token 作为登录令牌
	String token = UUID.randomUUID().toString(true);
	// 将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()));
	// 存储
	String tokenKey = LOGIN_USER_KEY + token;
	stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
	// 设置token有效期
	stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
	return Result.ok();
}

private User createUser(String phone) {
	User registerUser = new User();
	registerUser.setPhone(phone);
	registerUser.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
	save(registerUser);
	return registerUser;
}
  1. 从redis中拿到验证码进行比对
  2. String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone)
  3. UUID生成登录令牌token, String token = UUID.randomUUID().toString(true)
  4. 将User对象转为HashMap存储
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
		CopyOptions.create()
				.setIgnoreNullValue(true)
				.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()))
  • 第一个参数是要转换的对象,即 userDTO
  • 第二个参数是目标 Map 对象,这里传入了一个新的空 HashMap
  • CopyOptions.create() 创建了一个复制选项对象,用于配置复制行为。
  • setIgnoreNullValue(true) 表示忽略源对象中值为 null 的属性。
  • setFieldValueEditor() 设置一个字段值编辑器,使用一个简单的编辑器,将字段值转换为字符串。
  1. 在token前加固定标识, 便于理解, String tokenKey = LOGIN_USER_KEY + token
  2. 将用户信息存入redis, stringRedisTemplate.opsForHash().putAll(tokenKey, userMap)
  3. 设置令牌过期时间stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES)

登录拦截器

public class LoginInterceptor implements HandlerInterceptor {
	private StringRedisTemplate stringRedisTemplate;
	public LoginInterceptor(StringRedisTemplate stringRedisTemplate){
		this.stringRedisTemplate = stringRedisTemplate;
	}
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 获取请求头中的token
		String token = request.getHeader("authorization");
		if(StrUtil.isBlank(token)){
			// 不存在 401拦截
			response.setStatus(401);
			return false;
		}
		String key = RedisConstants.LOGIN_USER_KEY + token;
		// 基于token获取redis中的用户
		Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
				.entries(key);
		// 判断用户是否存在
		if(userMap.isEmpty()){
			// 不存在 401拦截
			response.setStatus(401);
			return false;
		}
		// 将查询到的Hash数据转换为UserDTO对象
		UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
		// 存在 保存用户到ThreadLocal
		UserHolder.saveUser(userDTO);
		// 刷新token有效期
		stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
		
		// 放行
		return true;
	}
  1. 在自定义的拦截器里想使用StringRedisTemplate模板类需要先定义然后用构造函数调用
private StringRedisTemplate stringRedisTemplate;
	public LoginInterceptor(StringRedisTemplate stringRedisTemplate){
		this.stringRedisTemplate = stringRedisTemplate;
	}
  1. 从请求头里拿到token
  • String token = request.getHeader("authorization")
  1. 从redis拿到token对应的user用户信息, entries拿到全部属性
  • Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key)
  1. 将拿到的userMap转换成userDTO, 再存到线程ThreadLocal中, fillBeanWithMap
  • UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false)
  1. 刷新token的有效期
  • stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES)
  1. 思路: request -> token -> userMap -> userDTO -> ThreadLocal -> token.expire

登录拦截器优化

image.png

  1. 第一个拦截器拦截所有路径, 从请求中拿到token, 用token拿到redis中的用户信息存入线程中
  2. 第二个拦截器拦截需要登录的路径, 判断线程中是否存在用户信息, 不存在则判定为该用户未登录, 拦截!

原来的登录拦截器改名为token刷新拦截器即可, 添加一个登录拦截器就行哈哈哈~

public class LoginInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 判断是否需要拦截 ( ThreadLocal中是否存在用户信息 )
		if(UserHolder.getUser() == null){
			// 不存在 401拦截
			response.setStatus(401);
			return false;
		}
		// 存在 放行
		return true;
	}
}

MvcConfig里注册一下这两个拦截器, 给个执行顺序order

@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
	// token 刷新拦截器
	registry.addInterceptor(new RefreshTokenlnterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
	// 登录拦截器
	registry.addInterceptor(new LoginInterceptor())
			// 放行请求
			.excludePathPatterns(
					"/shop/**",
					"/voucher/**",
					"/shop-type/**",
					"/upload/**",
					"/blog/hot",
					"/user/code",
					"/user/login"
			).order(1);
}

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

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

相关文章

BUCK电路布线规则、EMI分析

电源系列文章目录 本系列文章为博主在学习工作过程中的心得记录&#xff0c;欢迎评论区交流讨论。 BUCK电路工作原理、参数计算及工作模式分析BUCK电路布线规则、EMI分析电源电路中肖特基、续流二极管要求 目录 电源系列文章目录一、PCB布线规则1、输入电感与肖特基摆放2、输…

Avalonia 常用控件二 Menu相关

1、Menu 添加代码如下 <Button HorizontalAlignment"Center" Content"Menu/菜单"><Button.Flyout><MenuFlyout><MenuItem Header"打开"/><MenuItem Header"-"/><MenuItem Header"关闭"/&…

降重工具革命:如何使用AI技术优化论文原创性

论文降重一直是困扰各界毕业生的“拦路虎”&#xff0c;还不容易熬过修改的苦&#xff0c;又要迎来降重的痛。 其实想要给论文降重达标&#xff0c;我有一些独家秘诀。话不多说直接上干货&#xff01; 1、同义词改写&#xff08;针对整段整句重复&#xff09; 这是最靠谱也是…

【GD32F303红枫派使用手册】第二十四节 DHT11温湿度传感器检测实验

24.1 实验内容 通过本实验主要学习以下内容&#xff1a; DHT11操作原理 单总线GPIO模拟操作原理 24.2 实验原理 HT11是一款已校准数字信号输出的温湿度一体化数字传感器。该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点信号&#xff0c;传输距离可达20米以…

【人工智能】百度文心一言智能体:AI领域的新里程碑

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

关于正点原子stm32f103精英板v1的stlink通信失败问题解决方案

由于最新的固件不适配&#xff0c;我们要想其工作要下载007的固件。 https://www.st.com/en/development-tools/stsw-link007.html?dlredirect 版本选择最低的。然后选择windows文件夹&#xff0c;更新程序 然后进keil就能正常识别到了

ICMAN液位检测之WS003B管道检测模组

ICMAN液位检测之WS003B管道检测模组 体积小&#xff0c;成本低&#xff0c; 液位检测精度高&#xff0c; 有水输出低电平无水高电平&#xff0c; 适用于饮水机、咖啡机、扫地机器人、洗地机等。 有需要朋友快联系我吧&#xff01;

Flex 布局教程:语法篇

网页布局(layout)是 CSS 的一个重点应用。 布局的传统解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。 20

Redis-在springboot环境下执行lua脚本

文章目录 1、什么lua2、创建SpringBoot工程3、引入相关依赖4、创建LUA脚本5、创建配置类6、创建启动类7、创建测试类 1、什么lua “Lua”的英文全称是“Lightweight Userdata Abstraction Layer”&#xff0c;意思是“轻量级用户数据抽象层”。 2、创建SpringBoot工程 3、引入相…

java基于ssm+jsp 美食推荐管理系统

1前台首页功能模块 美食推荐管理系统&#xff0c;在系统首页可以查看首页、热门美食、美食教程、美食店铺、美食社区、美食资讯、我的、跳转到后台等内容&#xff0c;如图1所示。 图1前台首页功能界面图 用户注册&#xff0c;在注册页面可以填写用户名、密码、姓名、联系电话等…

cpci和ei会议哪个好?

目前国际学术会议出版的会议论文集有两种收录方式&#xff0c;一是cpci数据库收录&#xff0c;另一个是ei数据库收录&#xff0c;那么两者都是会议论文&#xff0c;哪个好一些?下面学术顾问在这里做出全面的分析&#xff0c;帮助作者能够选择到适合自己的会议论文。 EI(CA)与…

【数据分析实战】—预测宠物收养状况数据分析

文章目录 数据集数据集描述特征用途注意 宠物收养预测环境准备探索数据帧数据预处理机器学习数据预处理&#xff1a;模型培训和评估&#xff1a;合奏学习&#xff1a; 添加底部名片获取数据集吧&#xff01; 数据集 数据集描述 宠物收养数据集提供了对各种因素的全面调查&…

工程文档CAD转换必备!在 Java 中将 DWG 转换为 JPG

Aspose.CAD 是一个独立的类库&#xff0c;以加强Java应用程序处理和渲染CAD图纸&#xff0c;而不需要AutoCAD或任何其他渲染工作流程。该CAD类库允许将DWG, DWT, DWF, DWFX, IFC, PLT, DGN, OBJ, STL, IGES, CFF2文件、布局和图层高质量地转换为PDF和光栅图像格式。 Aspose AP…

众所周知,配置即代码≠基础设置即代码

​前段时间翻到几条留言&#xff0c;问&#xff1a; “配置即代码和基础设施即代码一样吗&#xff1f;” “配置即代码是什么&#xff1f;怎么都是基础设施即代码&#xff1f;” 我们都是知道&#xff0c;DevOp的快速发展&#xff0c;让服务器管理与配置的时间大大减少&#x…

AI入门系列:工具篇之ChatGPT的优秀的国内替代品

文章目录 一&#xff0c;智谱清言(ChatGLM)1&#xff0c;智谱清言简介2&#xff0c;[智谱清言地址&#xff0c;点我开始用吧](https://chatglm.cn/) 二&#xff0c;Kimi智能助手1&#xff0c;Kimi简介2&#xff0c;[Kimi地址&#xff0c;点我开始用吧](https://kimi.moonshot.c…

VPDBVE16E、VPDBVE32A、VPDBVE32B比例控制阀放大器

VPDBPC06A、VPDBPC06B、VPDBPC06C、VPDBPC06D、VPDBPC06E、VPDB08PC06100、VPDB08PC06200、VPDB08PC06315、VPDBVE16A、VPDBVE16B、VPDBVE16C、VPDBVE16D、VPDBVE16E、VPDBVE32A、VPDBVE32B、VPDBVE32C、VPDBVE32D、VPDBVE32E、VPDM2VE16A、VPDM2VE16B、VPDM2VE16C、VPDM2VE16D…

HTTP3(QUIC)详解

文章目录 一、HTTP3简述二、为什么不升级改造TCP而使用UDP&#xff1f;三、QUIC的实现四、HTTP3改进详解1. 快速连接建立(1-RTT初次建立&#xff0c;0-RTT恢复&#xff09;2. 无队头阻塞&#xff08;Head-of-Line Blocking&#xff09;重传机制HTTP/2 中的流HTTP/3 中的流 3. 移…

2024年6月24日v1.0.3优雅草超级站长工具开发进度

https://doc.youyacao.com/9/2151 v1.0.3优雅草超级站长工具开发进度 2024年6月24日v1.0.3优雅草超级站长工具开发进度- 演示地址-可测试 https://test2.youyacao.com 介绍 本产品是一款针对站长使用的工具&#xff0c;针对网站域名的多维信息查询工具&#xff0c;本产品…

【自动控制原理课程设计】

利用频率法设计控制器&#xff0c;对象模型采用二阶传递函数&#xff0c;使得校正后的系统的性能指标满足&#xff1a; 1&#xff09;位置误差系统kp10; 2)相角裕度y45&#xff1b; 3&#xff09;幅值裕度h>10dB&#xff1b; 绘制出校正前后的Bode图&#xff0c;并进行闭环…

特征工程与数据预处理全解析:基础技术和代码示例

在机器学习和数据科学的世界里&#xff0c;数据的质量是建模成功与否的关键所在。这就是特征工程和数据预处理发挥作用的地方。本文总结的这些关键步骤可以显著提高模型的性能&#xff0c;获得更准确的预测&#xff0c;我们将深入研究处理异常值、缺失值、编码、特征缩放和特征…