redis 实践(一)实现登录 这段代码值得借鉴,搬完就是我的。 背景 有些项目登录的逻辑都是如上图所示,但是实际访问量上来了,不可避免需要tomcat集群,但是多台Tomcat并不共享
背景这段代码值得借鉴,搬完就是我的。
有些项目登录的逻辑都是如上图所示,但是实际访问量上来了,不可避免需要tomcat集群,但是多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时会导致数据丢失的问题。
所以需要数据共享,内存存储,key、value结构。
基于Redis的登录流程
登录代码实现 发送验证码@Override
public Result sendCode(String phone, HttpSession session) {
//1. 校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
//2. 如果不符合,返回错误信息
return Result.fail("手机号格式错误");
}
//3. 符合生成验证码
String code = RandomUtil.randomNumbers(6);
//4. 保存验证码到redis
// set key value ex 120
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code, LOGIN_CODE_TTL,TimeUnit.MINUTES);
//5. 模拟发送验证码
log.debug("发送验证码成功,验证码为{}",code);
//返回ok
return Result.ok();
}
登录的实现
-
先检验验证码,如果验证码正确,则进行用户的校验,如果用户不存在,则进行添加数据库的操作。
//1.检验手机号 String phone = loginForm.getPhone(); if(RegexUtils.isPhoneInvalid(phone)){ return Result.fail("手机号错误"); } //2.从Redis获取验证码并且检验验证码 String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone); String code = loginForm.getCode(); if(cacheCode == null || !cacheCode.equals(code)){ return Result.fail("验证码错误"); } //查询数据库 User user = query().eq("phone",phone).one(); //用户不存在就创建新用户并且保存到数据库 if(user == null){ //createUserWithPhone 创建用户的函数 user = createUserWithPhone(phone); }
-
然后把用户信息添加到redis,具体思路就是先根据自己的规则生成一个token,作为用户的凭证,这个凭证需要返回前端,并且需要和前端约定,每次的异步请求都需要携带这个token。这里向redis存放的键是这个token,值是用户的基本信息。
//保存信息到redis中 //随机生成token,作为登录令牌 //hutool 中 的方法,为true就不带下划线 String token = UUID.randomUUID().toString(true); //将User对象转为Hash存储,函数用法为user对象转成UserDTO(存在用户的基本信息) UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); //hutool:将userDTO对象转成map //这个方法可以自定义key和value Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); //存储 String tokenKey = LOGIN_USER_KEY + token; stringRedisTemplate.opsForHash().putAll(tokenKey, userMap); //设置有效期 stringRedisTemplate.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES); return Result.ok(token);
登录校验这里交给拦截器,但是这里需要考虑Redis的token的过期问题,每次的异步请求,都需要进行一次token过期时间的刷新。这里可以用两个拦截器。
实现代码-
第一个拦截器,先获取请求头里面的token,再将用户信息放到ThreadLocal里面
//1. 获取请求头中的token String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { //未登录,也不进行拦截 // response.setStatus(401); return true; } //2. 基于token获取redis中的用户 Map<Object, Object> userMap = stringRedisTemplate.opsForHash() .entries(RedisConstants.LOGIN_USER_KEY + token); //3. 判断用户是否存在 if(userMap.isEmpty()){ //4. 不存在,不拦截,不返回 401 状态码 //response.setStatus(401); return true; } // 5. 将查询到的Hash数据转为UserDTO对象 UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false); // 6. 存在,保存用户信息到ThreadLocal UserHolder.saveUser(userDTO); // 刷新token stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); return true;
-
第二个拦截器
//判断是否需要拦截(ThreadLocal中是否有该用户) if(UserHolder.getUser() == null){ //没有,拦截 response.setStatus(401); //拦截 return false; } //有用户,放行 return true;