黑马点评项目总结
- 0. 整体架构
- 1. 短信登录模块
- 1.1 基于session
- (1)后台发送验证码Code
- (2)登录、注册
- (3)校验登录状态
- 1.2 基于Redis
- (1)后台发送验证码Code
- (2)登录、注册
- (3)校验登录状态(更新Token有效时间)
- 1.3 补充
- 2. 商户缓存
- 报错:
- 1. 给
0. 整体架构
1. 短信登录模块
1.1 基于session
(1)后台发送验证码Code
- 客户端提交手机号
- 后台检验手机号是否合法,不合法就返回错误信息
- 合法就用工具类随机生成验证码吗,并将验证码保存到
session
中,用于第二个环节的验证; - 向用户发送验证码
(2)登录、注册
- 用户提交手机号和验证码
- 后台再次验证手机号是否合法(两次请求,都要验证),如果不合法就报错
- 取出
session
中的验证码Code,和用户传的Code比较,不一样则报错 - 一致则通过手机号从数据库查询User对象,如果User为空就用Mybatis添加数据库;
- 不管有没有user,最后都把user对象放到
session
中,用于后序验证;
(3)校验登录状态
前端跳转 /user/me页面,先在拦截器进行校验登录状态
- 定义一个拦截器类,实现
HandlerInterceptor
接口,重写preHandle
方法;然后在SpringMvcConfig中配置拦截器,排除不需要被拦截的地址; - 在拦截器的preHandle方法中,获取session中的user对象
- 如果user对象不存在则表明没有通过第二个环节,报错401(未授权)
- 如果存在,这里为了从缓存快速读取用户信息,一方面为了安全性,就把user对象存入
Threadlocal
,后序用户从ThreadLocal直接读取信息更安全; - return true 放行请求,访问个人信息页面;
使用Session来完成登录时,登录凭证就是sessionID,Tomcat服务器会自动维护SessionID;
问题:
当Tocmat服务器扩张成一个服务器集群,而Tomcat之间不共享session存储空间;
当浏览器发送请求到 Nginx,由Nginx进行 负载均衡 时,同一个浏览器的不同请求就可能发往不同的Tomcat服务器,这样数据读取就出问题;
解决:
Tomcat使用session互相拷贝;
①多台服务器拷贝浪费空间 ②拷贝需要时间,有延迟(服务器是进程,进程通信效率低)
所以使用缓存服务器专门用来存数据,让Tomcat都可以去访问;
1.2 基于Redis
(1)后台发送验证码Code
- 客户端提交手机号
- 后台检验手机号是否合法,不合法就返回错误信息
- 合法就用工具类随机生成验证码吗,使用
StringRedisTemplate.opsForValue()
将验证码保存到Redis
中,用于第二个环节的验证; - 向用户发送验证码
Redis中存的第一类数据(验证码):key
为手机号+“前缀”(保证唯一性),value
就是验证码;
(2)登录、注册
- 用户提交手机号和验证码
- 后台再次验证手机号是否合法(两次请求,都要验证),如果不合法就报错
- 用手机号取出之前存在
Redis
中的验证码Code,和用户提交的Code对比,不一样则返回错误; - 一致则通过手机号从数据库查询User对象,如果User为空就用Mybatis添加数据库;
- 不管有没有user,都需要将user对象存到Redis中;
使用UUID工具类生成一串Token
,将Token作为key
;将user对象转换为Map格式作为value
,使用StringRedisTemplate.opsForHash().putAll()
把user对象存入Redis
;
此时Token就是登陆凭证; - 使用
StringRedisTemplate.expire()
方法设置Token的有效时间; - 将登陆凭证Token返回给前端,存储到浏览器中,以后每次访问都放在【请求头】中;
前端会用一个sessionStorage()
将Token存到浏览器中,以后每次访问都在请求头中带着Token;
(3)校验登录状态(更新Token有效时间)
前端跳转 /user/me 页面,先在拦截器进行校验登录状态
- 定义一个拦截器类,实现
HandlerInterceptor
接口,重写preHandle
方法;然后在SpringMvcConfig中配置拦截器,排除不需要被拦截的地址; - 在拦截器的
preHandle
方法中,从request中getHeader()读取请求头中的**Token
**,如果不存即第二个环节没通过,返回错误401(未授权); - 通过Token从Redis中取出user的Map,如果Map不存在,也返回错误401;
- 存在则使用BeanUtil 将Map转换为对象格式,存入
Threadlocal
(方便和安全性); - 用
StringRedisTemplate.expire()
更新Token的有效期; - return true 放行请求,访问个人信息页面;
注意Redis存的是两类数据,一个是 手机号–Code ,另一个是Token–User
1.3 补充
问题:用户登录状态的保持主要靠的是拦截器中更新Token有效时间,但是访问 shop店铺、blog博客时没有更新Tokend有效时间 !
解决:再增加一个拦截器 !
让第一个拦截器对所有请求操作,获取Token,保存User用户到ThreadLocal
第二个拦截器来实现拦截功能;
- 第一个拦截器:(对所有请求页面拦截)
如果发现Token为空,则直接return true放行到第二个拦截器;
通过Token获取User,不存在则放行,
将User存储放在第一个拦截器; - 第二个拦截器:(对部分请求页面拦截)
判断ThreadLocal中是否有用户,没有则拦截,有则放行;
配置拦截器:
通过设置拦截器的 order
,来控制拦截器的执行顺序!
oder越小,优先级越高;