点评项目——短信登陆模块
2023.12.6
短信登陆如果基于session来实现,会存在session共享问题:多台Tomcat不能共享session存储空间,这会导致当请求切换到不同服务器时出现数据丢失的问题。
早期的解决办法是让session提供一个数据拷贝的功能,即让各个Tomcat的数据实现一个互相的拷贝,但是这种方式容易造成内存空间的浪费,并且拷贝是需要时间的,这段时间内依然存在数据不一致的问题。
于是我们可以基于Redis来完成,由于Redis数据本身就是共享的,就可以避免session共享的问题了,并且redis是基于内存的,读写性能高。
发送短信验证码
之前是将验证码保存到session中,现在是将验证码保存到redis中,此时需要考虑redis采用什么数据结构来保存验证码:key需要保证唯一性,所以使用用户的手机号作为key;由于验证码就是一个六位数的字符串,所以value直接采用String保存就好。
该模块的代码如下:
public Result sendCode(String phone, HttpSession session) {//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2.不符合则返回错误信息return Result.fail("手机号格式错误!");}//3.符合,生成验证码String code = RandomUtil.randomNumbers(6);//保存验证码到session//session.setAttribute("code",code);//4.保存验证码到redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//设置过期时间定期清理redis中的验证码//5.发送验证码log.debug("发送验证码成功,验证码:{}",code);return Result.ok();}
这里不真的发验证码,就模拟一下就好了,验证码直接输入到控制台上。
短信验证码登陆和注册模块
这里的redis要存的是用户对象信息,于是我们采用HashMap数据结构去存,该结构可以将对象的每个字段独立存储,方便针对单个字段进行CRUD。
redis的key的选择这里不选择手机号,而是采用一个随机token作为key,此处的token最终是要返回给前端页面的,类似于之前的session id(登陆凭证),所以key填手机号的话不安全;并且为了后续能够通过这个token得到用户信息,需要将此token返回给客户端。
该模块代码如下:
@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)){//3.验证码不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户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);// 返回tokenreturn Result.ok(token);}private User createUserWithPhone(String phone) {//1.创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));//2.保存用户save(user);return user;}
此处有个细节,虽然我们设置了token有效期为30分钟,但是不管我们登陆后做什么操作,token始终都是30分钟后被消除。 而正常情况应该是我们登陆后如果有操作行为,token的有效期应该被重置为30分钟。
于是我们可以使用一个拦截器对请求进行拦截,每当有请求发生,就将token的有效期重置。
拦截器的代码为:
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 = RedisConstants.LOGIN_USER_KEY+token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//3.判断用户是否存在if(userMap.isEmpty()){return true;}//5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);//7.刷新token有效期stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
写完拦截器的代码之后,还需要在MvcConfig中进行配置:
@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)).order(0);}
}
这里虽然是在第二行配置的刷新有效期拦截器,但是它会先进行拦截,因为我们order里填的数字是0,数字越小优先级越高。
第一行的拦截器是用来进行登陆校验的,即如果你访问了除代码中路径以外的路径的话,需要判断你有没有登陆,那如何判断你有没有登陆呢?只需要查看当前ThreadLocal中有无用户信息就知道了,该拦截器代码如下:
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断是否需要拦截(即ThreadLocal中是否有用户)if(UserHolder.getUser() == null){response.setStatus(401);return false;}return true;}}
拦截器的整体架构图如下:
相关文章:

点评项目——短信登陆模块
2023.12.6 短信登陆如果基于session来实现,会存在session共享问题:多台Tomcat不能共享session存储空间,这会导致当请求切换到不同服务器时出现数据丢失的问题。 早期的解决办法是让session提供一个数据拷贝的功能,即让各个Tomcat的…...

2023亚太地区五岳杯量子计算挑战赛
计算电源网 (CPN)布局优化 1. 介绍 计算能力网络 (CPN)是一种基于业务需求分配和调度计算资源的新型信息基础设施,计算资源通常由终端用户、边缘服务器和云服务器组成。该网络旨在满足各种计算任务的需求。根据计算需求的空间分…...

Python 模块的使用方法
Python 模块是一种组织和封装代码的方式,允许你将相关的功能和变量放在一个单独的文件中,以便在其他程序中重复使用。在Python中,模块是一种可执行的Python脚本,其文件扩展名为 .py。这里,我将详细讲解Python模块的使用…...

【知识】稀疏矩阵是否比密集矩阵更高效?
转载请注明出处:小锋学长生活大爆炸[xfxuezhang.cn] 问题提出 有些地方说,稀疏图比密集图的计算效率更高,真的吗? 原因猜想 这里的效率高,应该是有前提的:当使用稀疏矩阵的存储格式(如CSR)时,计…...

代码随想Day24 | 回溯法模板、77. 组合
理论基础 回溯法和递归不可分割,回溯法是一种穷举的方法,通常需要剪枝来降低复杂度。回溯法有一个选择并退回的过程,可以抽象为树结构,回溯法的模板如下: void backtracking(参数) {if (终止条件) {存放结果;return;}…...

搜索与回溯算法②
求0-9的数字可以组成的所有k 位数。 def backtrack(start, path, k, n, results):"""核心函数。:param start: 下一个添加的数字的起始位置:param path: 当前构建的路径,代表一个组合:param k: 组合中所需的数字个数:param n: 可选数字的最大值:par…...

Centos图形化界面封装OpenStack Ubuntu镜像
目录 背景 环境 搭建kvm环境 安装ubuntu虚机 虚机设置 系统安装 登录虚机 安装cloud-init 安装cloud-utils-growpart 关闭实例 删除细节信息 删除网卡细节 使虚机脱离libvirt纳管 结束与验证 压缩与转移 验证是否能够正常运行 背景 一般的镜像文件在上传OpenSt…...

使用Jmeter进行http接口测试怎么做?
前言: 本文主要针对http接口进行测试,使用Jmeter工具实现。 Jmter工具设计之初是用于做性能测试的,它在实现对各种接口的调用方面已经做的比较成熟,因此,本次直接使用Jmeter工具来完成对Http接口的测试。 一、开发接…...

创建腾讯云存储桶---上传图片--使用cos-sdk完成上传
创建腾讯云存储桶—上传图片 注册腾讯云账号https://cloud.tencent.com/login 登录成功,选择右边的控制台 点击云产品,选择对象存储 创建存储桶 填写名称,选择公有读,私有写一直下一步,到创建 选择安全管理&#…...

12.3_黑马MybatisPlus笔记(上)
目录 02 03 04 05 06 07 编辑 thinking:system.out::println?编辑 thinking:list.of? 08 thinking:RequestParam和 ApiParam注解使用? thinking:RequestParam 和PathVariable的区别? 编辑 编…...

智能优化算法应用:基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于寄生捕食算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.寄生捕食算法4.实验参数设定5.算法结果6.参考…...

全息图着色器插件:Hologram Shaders Pro for URP, HDRP Built-in
8个新的Unity全息图着色器,具有故障效果,扫描线,网格线,和更多其他效果!与所有渲染管线兼容。 软件包添加了一系列的全息图着色器到Unity。从基本的全息图与菲涅耳亮点,先进的全息图与两种故障效应,扫描线,文体点阵和网格线全息图! 特色全息效果 Basic-支持菲涅耳发光照…...

Python Opencv实践 - 简单的AR项目
这个简单的AR项目效果是,通过给定一张静态图片作为要视频中要替换的目标物品,当在视频中检测到图片中的物体时,通过单应矩阵做投影,将视频中的物体替换成一段视频播放。这个项目的所有素材来自自己的手机拍的视频。 静态图片&…...

Java不可变集合
Java不可变集合 不可变集合:也就是不可以被修改的集合 创建不可变集合的应用场景 ●如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。 ●当集合对象被不可信的库调用时,不可变形式是安全的。 简单理解࿱…...

openGauss学习笔记-146 openGauss 数据库运维-备份与恢复-配置文件的备份与恢复
文章目录 openGauss学习笔记-146 openGauss 数据库运维-备份与恢复-配置文件的备份与恢复146.1 背景信息146.2 前置条件146.3 操作步骤146.4 示例 openGauss学习笔记-146 openGauss 数据库运维-备份与恢复-配置文件的备份与恢复 146.1 背景信息 在openGauss使用过程中&#x…...

一文读懂中间件
前言:在程序猿的日常工作中, 经常会提到中间件,然而大家对中间件的理解并不一致,导致了一些不必要的分歧和误解。“中间件”一词被用来描述各种各样的软件产品,在不同文献中有着许多不同的中间件定义,包括操…...

【编程基础心法】「设计模式系列」让我们一起来学编程界的“兵法”设计模式(序章)
一起来学编程界的“兵法”设计模式(序章) 设计模式是什么设计模式的概念设计模式的分类创建型模式(5种)结构型模式(7种)行为型模式(11种) 设计模式应用场景工厂模式的实现及应用单例…...

技术阅读周刊第第8️⃣期
技术阅读周刊,每周更新。 历史更新 20231103:第四期20231107:第五期20231117:第六期20231124:第七期 Prometheus vs. VictoriaMetrics (VM) | Last9 URL: https://last9.io/blog/prometheus-vs-victoriametrics/?refd…...

HTML程序大全(2):通用注册模版
一、正常情况效果 二、某项没有填写的效果 三、没有勾选同意项的效果 四、代码 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>注册</title><style>body {font-family: Arial, sans-serif;background-color…...

【循环结构 for、break、continue高级用法】
在 C++ 中,for 循环是一种常用的循环结构,它用于重复执行代码块直到满足指定的条件。for 循环的基础用法相对简单,而高级用法则涉及更复杂的控制结构和技术。让我们探讨这些用法,并通过一些示例来加深理解。 文章目录 基础用法高级用法实战示例注意事项结合 break 和 conti…...

JAVA网络编程——BIO、NIO、AIO深度解析
I/O 一直是很多Java同学难以理解的一个知识点,这篇帖子将会从底层原理上带你理解I/O,让你看清I/O相关问题的本质。 1、I/O的概念 I/O 的全称是Input/Output。虽常谈及I/O,但想必你也一时不能给出一个完整的定义。搜索了谷哥欠,发…...

Linux高级系统编程-3 进程
概念 进程与程序的区别 程序:一个可执行文件, 占磁盘空间,是静态的 进程:一个程序运行的过程, 占内存,动态的。 单道程序和多道程序 单道程序设计: 所有进程一个一个排队执行。若 A 阻塞, B 只能等待࿰…...

ES-ELSER 如何在内网中离线导入ES官方的稀疏向量模型(国内网络环境下操作方法)
ES官方训练了稀疏向量模型,用来支持语义检索。(目前该模型只支持英文) 最好是以离线的方式安装。在线的方式,在国内下载也麻烦,下载速度也慢。还不如用离线的方式。对于一般的生产环境,基本上也是网络隔离的…...

Excel 使用技巧
Excel 使用技巧 注意: excel 中设计计算的字符尽量使用英文。 拼接两段文字(字符串拼接) 方法一 在需要计算的单元格上,键入 点击 A1(点击需要拼接的单元格) & C1(点击需要拼接的单元格) 举例: 姓名栏想要拼接 姓 和 名 两列点击姓名这一…...

Hadoop学习笔记(HDP)-Part.03 资源规划
目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …...

一个最新国内可用的免费GPT4,Midjourney绘画网站+使用教程
一、前言 ChatGPT GPT4.0,Midjourney绘画,相信对大家应该不感到陌生吧?简单来说,GPT-4技术比之前的GPT-3.5相对来说更加智能,会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而,GPT-4对普…...

深入了解Java8新特性-日期时间API之ZonedDateTime类
阅读建议 嗨,伙计!刷到这篇文章咱们就是有缘人,在阅读这篇文章前我有一些建议: 本篇文章大概19000多字,预计阅读时间长需要10分钟以上。本篇文章的实战性、理论性较强,是一篇质量分数较高的技术干货文章&…...

使用Vue写一个日期选择器
在 Vue 中实现日期选择器的方法有很多,下面提供一个简单的实现方法。 首先,在需要使用日期选择器的组件中引用 Vue 和 date-fns 库,date-fns 库是一个轻量级的 JavaScript 时间日期工具库,可以方便地处理日期的格式化和计算。 &…...

19、pytest通过mark标记测试函数
官方实例 [pytest] markers slow:marks tests as slow(deselect with -m "not slow")serial# content of test_mark.py import pytestpytest.mark.slow def test_mark_function():print("test_mark_function was invoked")assert 0解读与实操 通过使用p…...

Linux环境变量与命令行参数
Linux环境变量与命令行参数 一.命令行参数1.语法2.应用1:简易计算器 二.环境变量1.环境变量的概念2.环境变量的作用3.进一步理解环境变量的作用4.常见环境变量5.导出环境变量(添加环境变量)6.环境变量的特性7.另一种获取环境变量的方式8.小功能:用于身份验证的代码9.补充:第三种…...