「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
之前我们已经实现了自定义注解校验入参->springboot开发银行秒杀后端系统——用户篇(上篇)
接下来我打算通过redis简单实现一下分布式session,因为如果后端要部署集群的话肯定不能把只把session存在本机上,不然前端的请求打过来然后nginx分流以后不能有效保持连接,比如登陆时请求发到了后台1,所以session存在了后台1,过一会另外一个请求发到了后台2,但是后台2没有存这个用户的session所以会要求重新登陆,非常影响用户的体验,简单来说就是要实现session的一致性,这里我采用redis存储代替本机存储实现。
跟之前一样redis我也是用docker部署的,学习docker的可以看一下我的这篇文章:Docker使用指南
docker run -p 6369:6379 --name myredis -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes
redis.conf文件是从官网下载的然后需要修改配置
- bind 127.0.0.1 #注释掉这部分,这是限制redis只能本地访问
- protected-mode no #默认yes,开启保护模式,限制为本地访问
- daemonize no#默认no,改为yes意为以守护进程方式启动,可后台运行,除非kill进程,改为yes会使以配置文件方式启动redis失败
- databases 16 #数据库个数(可选),
- dir ./ #输入本地redis数据库存放文件夹(可选)
- appendonly yes #redis持久化(可选)
因为没有设密码所以我把端口映射改成了6369,之前是6379但是会被黑掉,基本上晚上睡一觉第二天数据库里数据就都成乱码了,因为是测试用的我也懒得去搞密码干脆换个端口,到现在还没被黑过。然后我们需要实现一下redis的配置类,就是很简单的设置一下序列化
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//对redis进行相应的配置,使得redis存储时可以直观显示
//key序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//hash类型key序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//hash类型value序列化
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//注入连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
配置完redis后就是实现session的一致性,我的做法是登陆的时候校验完用户名密码后将用户的id配合上当天的日期MD5加密后当作key,把用户类当作value存到redis中去,然后配置一个AOP切面监控全局的登陆状态。
public Map login(ParamUserDto entity, HttpServletRequest request, HttpServletResponse response) {
Map map = new HashMap();
if (sysUserMapper.selectByTel(entity.getTel()) != 1)
throw new RuntimeException("账号或密码错误");
SysUser sysUser = sysUserMapper.selectAllByTel(entity.getTel());
if (!MD5Utils.passwordIsTrue(entity.getPassWord(), sysUser.getPassWord()))
throw new RuntimeException("账号或密码错误");
//生成token——日期加用户id再进行MD5加密
String token = string2MD5(TimeUtils.getCuitPreTime()+sysUser.getUserId());
redisTemplate.opsForValue().set(token, sysUser, 60 * 60 * 12, TimeUnit.SECONDS);
//setCookie
CookieUtils.setCookie(request, response, "userTicket", token);
map.put("token", token);
map.put("msg", "登录成功");
return map;
}
这样我们登陆以后就会把信息放到redis的数据库里再加上一个过期时间,我这里设置的是12小时,判断状态的时候就去cookie里获取我们的userTicket进行判断即可,有很多种方式可以判断登录状态,比如用过滤器或者其他等等做判断,我这里尝试了一下@Aspect注解的使用,简单来说就是通过面向切面来校验登陆的状态,当然也可以把其他的需求加进去,不过这里只是简单实现一下,后期我应该会整合shiro进去替换掉这个。
配置这个切面先新建一个类然后用上@Aspect即可,@Pointcut表示了我这个切面的范围,具体的语法有点复杂可以查看一些相关的文章,我这里写的就是包含了TGoodsController下的所有接口,相当于在访问这个类下的接口时都会先进行一遍doVerify()的操作,如果有多个类的话可以用&&连接。doVerify()就是通过cookie工具类拿到用户的userTicket然后去数据库里查询用户是否存在,如果不存在就说明还未登录或者已经过期需要重新登陆。
@Aspect
@Component
@Slf4j
public class LoginAspect {
@Autowired
RedisTemplate redisTemplate;
//定义一个切面
@Pointcut("execution(public * com.yt.seckill.controller.TGoodsController.*(..))")
public void verify() {
}
@Before("verify()")
public void doVerify() {
//获取request
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
//查询cookie,使用封装好的工具类
// System.out.println("CookieUtils.getCookieValue(request,"userTicket") = " + CookieUtils.getCookieValue(request, "userTicket"));
String userTicket = CookieUtils.getCookieValue(request, "userTicket");
if (StringUtils.isEmpty(userTicket)) {
throw new RuntimeException("登陆状态已失效,请重新登录");
}
SysUser user = (SysUser) redisTemplate.opsForValue().get(userTicket);
if (null == user) {
throw new RuntimeException("登陆状态已失效,请重新登录");
}
}
}
然后我们不去登陆直接访问切面下的接口可以看到未登录的报错信息
登陆完以后再我们的headers中设置好cookie里的userTicket就可以成功访问接口了