黑马点评项目

点评项目——用户登录注册

这部分主要实现发送验证码,用户登录注册。
技术点:使用token+redis的方式实现前后端分离的单点登录问题。

登录实现

1、基于session来实现(单体应用下)

黑马点评项目_第1张图片

2、基于session实现登录的问题(单点登录问题的解决方式)

首先出现的问题是:
上述是单体会话信息保存在seesion中,session存在服务器端(session的工作原理图见:),如果是仅仅后端只是针对一个web服务器,上述的方法就可以实现了。但是如果处于web服务器集群的环境下(Nginx做负载均衡,后端部署了两个web服务器,使用轮询等这种请求分配策略),就会出现用户A在服务器1登陆了,但是session存在服务器1中,但是第二次请求被分配到了服务器2中,服务器2内存中没有用户1的session会话导致用户A需要再次登录,这也就设计到解决的单点登录(SSO)的问题:
解决这个问题目前学习到的有两个方法(no session设计):

2.1、jwt方法:

生成并发给客户端之后,后台是不用存储,客户端访问时会验证其签名、过期时间等再取出里面的信息(如username),再使用该信息直接查询用户信息完成登录验证。jwt自带签名、过期等校验,后台不用存储,缺陷是一旦下发,服务后台无法拒绝携带该jwt的请求(如踢除用户)

2.2、token+redis token+redis:

token是自己生成个10位的key,value为用户信息,访问时判断redis里是否有该token,如果有,则加载该用户信息完成登录。服务需要存储下发的每个token及对应的value,维持其过期时间,好处是随时可以删除某个token,阻断该token继续使用.
这两种方式都有自己的优缺点,在这里选择的是redis+token这种方法。
(redis是部署在独立的服务器上,各个web服务器都可以访问到)

2.2.1基于redis+token实现集群下登录注册的功能。

1、功能流程图
黑马点评项目_第2张图片
使用电话号码作为key,验证码作为value,使用redis中string结构进行存储
随机生成十位字符串作为token,作为key进行存储,value是用户信息。
实现过程中注意的地方
1、选用什么样的redis中的数据结构进行存储:
黑马点评项目_第3张图片

2、存储粒度问题
为什么要进行一个redis存储粒度的划分
(1)、安全性
因为User中有手机号,密码等等涉及用户数据安全的数据,然后如果全部存在redis中,内存存储可能导致数据安全性的问题
(2)、内存问题
因为如果User中全部属性要进行存储的话,会增加内存的压力,这里redis存储用户信息,是为了知道有这个用户,以及之后登录验证的时候明确是哪一个用户在登录操作,所以存储的就是UserId(只要有UserId就可以在后面需要获取用户更多信息进行查询),昵称,头像信息即可。(UserDTO类)
实现过程:
1、User属性赋值到UserDTO对象
使用hutool工具类,来进行
2、存储过程:
使用redis中hash结构来进行

//void putAll(H key, Map m);
//第二参数需要的是map,所以要要把对象转换成hashmap
stringRedisTemplate.opsForHash().putAll()

对象转换成hashmap
注意StringRedisTemplateredis需要转换成的map的各个字段都是String类型,而bean的每个属性的类型各不相同(所以要把每个字段的值来进行转换一下,都转换成了string)
使用hutool工具类进行一个值字段类型的转换的转换

Map<String,Object> usermap = BeanUtil.beanToMap(userDTO1,new HashMap<>(),
                CopyOptions.
                        create().
                        setIgnoreNullValue(true)//忽略空值
                        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

因为我是第一次接触使用这个hutool工具类,在写进这个项目之前进行了一下测试,发现这个setIgnoreNullValue(true)这个并没有起作用,就是传出的对象中的某个属性,我偷懒设置成了null之后,发现,就报了空指针的错误,debug之后,发现是它并没有忽略掉,导致后面的toString()就报错了。然后我上网查了一下,找到作者在gitee上的回答
作者的回答gitee

1、setFieldValueEditor优先级要高于ignoreNullValue导致前者首先被触发,因此出现空指针问题。你在setFieldValueEditor中也需要判空。

2、这么设计的原因主要是,如果原值确实是null,但是你想给一个默认值,在此前过滤掉就不合理了,而你的值编辑后转换为null,后置的判断就会过滤掉。
然后作者提供了思路就是,在setFieldValueEditor上也要判空,改了一下代码。

转换成hashmap之后,进行存储,之后还要设置过期时间

2.2.2基于redis+token实现集群下登录验证的功能。

    上面已经实现了登录验证功能,然后现在要实现登录验证(会话跟踪:HTTP是无状态的,要记录知道是那一个用户登录,然后之后的页面的跳转)
    以及因为上面还设置了过期时间(过期之后就会从redis中删掉,如果一个用户在app的访问时间长,就会出现会查数据库次数多,避免这个问题,就设计如果该用户一直有访问请求就刷新token的过期时间,所以可以在拦截器中实现这个功能。

黑马点评项目_第4张图片
创建了两个拦截器
第一个拦截器(拦截所有路径):获取前端请求携带的token,从redis中查询出来用户的信息,保存到Threadlocal,并且刷新token的过期时间。无论查出来没有都进行一个放行的操作。
在实现这个拦截器的时候,一定要在afterCompletion,进行Threadlocal的移除否则会造成内存泄漏(这个Threadlocal去补课一下,之前学的时候没认证)
第二拦截器:是针对需要登录的路径进行拦截。
最后就是把拦截器注册到mvc的配置中即可(注意顺序)

你可能感兴趣的:(服务器,数据库,java,redis)