摘要:redis学习
redis实战课程
短信登录
- 导入黑马点评项目
- 基于Session实现登录
- 集群的session共享问题
- 基于Redis实现共享session登录
1. 导入黑马点评项目
导入SQL文件
1 | server: |
2.基于Session实现登录
基本框架
1)发送短信验证码
提交手机号,校验手机号,生成验证码,保存验证码到session中,发送验证码
2)短信验证码登录和注册流程
在校验验证码和手机号成功之后保存用户到session中,
3)校验登录状态
session是基于cookie的,每一个session都一个唯一的sessionId保存在浏览器中,当用户来访问我们的时候,都会带上cookie,cookie中带有sessionId到服务端获取session判断用户是否存在,如果存在则保存在ThreadLocal中,ThreadLocal是线程域对象,当请求来的时候,都会保存在对应线程的一个独立的空间中,鲜线程之间没有干扰,最后放行
2.1发送验证码
1)校验手机号
一般是使用正则表达式来校验,使用黑马点评写好的工具类
黑马点评中实现的正则表达式校验工具类使用的是抽象类的实现
1 |
|
1 | // 正则表达式工具类 |
在什么情况下使用抽象类,什么情况下使用接口?
1)是否提供具体的实现 —— 抽象类
2)是否需要支持多重继承 —— 接口
3)设计的具体需求
2)生成验证码
使用hutool自动生成6为验证码
3)保存验证码
调用session的setAttribute(“code”,code)
4)发送验证码
一般需要使用公司提供的服务来进行验证码短信的发送,比如阿里云的短信验证功能
或者公司会有这种服务
这里我们进行假设模拟短信验证码
使用日志注解@Slf4j 在后台输出随机生成的验证码
5)返回我们设定好的返回结果类中的静态方法 ok()
1 | // 发送验证码 全部代码 |
🎯session和request的区别和关系
web中,会话session和请求request是两个完全不同的概念
请求request
- 定义:请求指的是客户端(通常是浏览器)向Web服务器发送的一个具体信息单元,以获取资源或触发某个动作。(每次用户点击链接、提交表单或通过其他方式与服务器交互时,都会产生一个新的HTTP请求。这个请求包含了诸如请求方法(GET、POST等)、请求URI、请求头以及可能的请求体(比如表单数据)等信息。
- 生命周期:请求的生命周期相对较短,从客户端发起请求开始,到服务器处理完毕并返回响应给客户端后结束。
会话session
- 定义:会话则是指一个用户与Web应用之间的一系列连续请求与响应过程。从用户打开浏览器访问Web应用开始,到用户关闭浏览器离开该应用为止,这一段时间内发生的所有请求和响应构成了一个会话。会话的主要目的是在多次请求之间维护用户的上下文信息,比如登录状态、购物车内容等。
- 生命周期:会话的生命周期通常较长,覆盖了用户与Web应用交互的整个时间段,可以包含多个请求-响应周期。
- 实现机制:为了在无状态的HTTP协议上实现会话跟踪,Web应用通常使用Cookie或URL重写技术来传递一个会话标识符(Session ID),服务器根据这个标识符来识别不同的会话,并在服务器端为每个会话保存相应的状态信息。
两者关系
- 关联性:在一个会话期间,可能会发生多次请求。每个请求都是独立的,但通过会话机制,服务器能够识别出这些请求属于同一个用户会话,从而提供个性化的响应或维持用户状态。简而言之,请求是构成会话的基本单位,而会话则是请求的集合,提供了一种跨请求的数据共享机制。
- 作用:请求负责传输即时的数据和指令,而会话则负责维护这些请求之间的连续性和状态,确保用户在多次交互过程中的体验是一致且连贯的。
2.2 实现短信验证码登录和注册
功能实现流程
实现验证码的登录和注册功能
一、Controller层中,接受前端传来的请求体body中的json格式内容,通过Spring注解@RequestBody注解绑定后端创建DTO对象接收
1 | package com.hmdp.dto; |
1 | /** |
二、在UserServiceImpl层中实现短信登录注册功能
1 |
|
2.3 登录验证功能(获取当前登录状态)
若后续出现controller都需要验证登录状态,为了提高效率
使用Spring中自带的拦截器
将登录校验功能统一放在拦截器中,并且将拦截器中获取到的用户信息通过 ThreadLocal 传递到拦截器后面的需要当前登录用户信息的controller中
ThreadLocal
每一个进入服务器的请求都是一个独立的线程保证线程的安全,ThreadLocal就会在线程中开辟一个独立的空间保存用户信息
一、创建拦截器
1 | package com.hmdp.utils; |
二、配置拦截器
1 | package com.hmdp.config; |
三、完成登录校验接口
1 | /** |
将保存在通过拦截器保存在UserHolder中的ThreadLocal的用户信息返回给前端即可
集群的Session共享问题
session共享问题:
多台Tomcat并不共享session存储空间,当请求切换到不同的tomcat服务时,导致数据丢失的问题太
每一台tomcat都有独立的session空间
当使用负载均衡的思想,将多个服务分配到不同的服务器上时,出现session不共享的问题
解决的方法:
session数据拷贝,缺点:浪费内存空间
session的替代方案应该满足
- 数据共享
- 内存共享
- key、value结构
基于Redis实现共享Session登录
Redis是共享的内存空间
一、保存验证码
将手机号作为key,考虑唯一性,考虑后续能够获取到value
value存储验证码
二、保存用户
方式一:
Json字符串的方式保存数据比较直观
方式二:⭐
Hash结构
每个字段独立存储,可以针对单个字段进行crud,占用内存少
使用Hash结构保存用户
key:以随机token为key存储用户数据,也就是登录凭证,相当于sessionId,但是不会像Tomcat一样,将Sessionid自动写在服务器的cookie上
value:用户数据
为什么key不能使用手机号,因为会保存在服务器中,不安全
token是随机生成的uuid
三、Redis重新实现验证码,登录注册功能
发送验证码
将魔法值定义起来
Redis实现短信验证码登录和注册功能
保存用户信息到redis中
步骤:
- 随机生成token,作为登录令牌
- 将User对象转为HashMap存储
- 存储到redis中
- 设置redis过期时间,session的有效期是30分钟,使用expire
- 返回token
注意:
设置过期时间的时候,要保证每一次用户进行每一次操作都在更新Redis的有效期,保证Redis中的用户信息一直保持,只有当用户不操作超过30分钟,才会自动移除用户信息
拦截器设置,每一次请求都会经过拦截器,确保请求都是携带用户信息
步骤
- 获取请求头中的token
- 基于token获取redis中的用户
- 判断用户是否存在
- 将查询到的Hash数据转为UserDTO对象
- 存在,保存用户信息到ThreadLocal中
- 刷新有效期
注意:
运行如果出现报错,类型转换的错误
1 | stringRedisTemplate.opsForHash().putAll(tokenKey,userMap); |
userMap来自UserDTO,UserDTO中之后id 是long 类型,
出现的原因是我们使用的redis是string类型的stringRedisTemplate,他的类型都是<String,String>,
要保证存入redis的所有类型都是String
在bean转map的时候出现类型转换失败
解决方法:
修改为
该Java函数的主要目的是将一个名为userDTO的Java对象转换成一个类型为Map<String, Object>的数据结构。这个转换过程通过调用BeanUtil.beanToMap方法实现,该方法属于某个工具类(未直接显示类名,但根据方法名推测可能是Apache Commons BeanUtils或Hutool等库中的工具方法)。下面是该函数行为的详细分解:
函数输入参数:
userDTO: 这是一个Java对象,其具体的类没有直接给出,但可以假设它包含多个属性(字段)。
new HashMap<>(): 作为转换结果的初始容器,一个新的空HashMap实例被创建。转换后的键值对将存储在这个Map中。
CopyOptions.create()…: 创建并配置了一个CopyOptions实例,用来定制转换过程的行为。具体配置了两项:
setIgnoreNullValue(true): 指定在转换过程中,如果userDTO对象中有属性值为null,那么这些键值对将不会被复制到目标Map中。这有助于减少不必要的数据传输和存储开销。
setFieldValueEditor(…): 定义了一个字段值编辑器,它是一个函数,对每个属性值执行操作。这里使用Lambda表达式(fieldName, fieldValue) -> fieldValue.toString(),意味着无论原属性值是什么类型,都会被转换成字符串类型后存入Map中。这简化了Map中值的处理,确保所有值都是统一的字符串格式。
函数输出/效果:
函数最终返回的是一个Map<String, Object>,其中键为userDTO对象中每个属性的名称(字符串形式),值为相应属性值转换后的字符串形式。如果原属性值为null,则该属性不会出现在结果Map中。
应用场景:
这种转换常见于需要将复杂的Java对象模型转换为更通用的数据结构(如Map),以便于序列化、传输、存储或与其他系统交互,尤其是在JSON转换、数据库操作、API接口数据准备等场景下非常有用。
总结而言,此函数实现了从特定Java对象到键值对映射的转换,同时进行了数据清洗(忽略null值)和类型统一(所有值转为字符串),提高了数据处理的灵活性和兼容性。
Redis代替session需要考虑的问题:
- 选择合适的数据结构
- 选择合适的key
- 选择合适的存储粒度