Redis实现短信登录功能
发送验证码
第一步:检验手机号是否合法
第二步:生成验证码 可以使用hutool工具RandomUtil.randomNumbers(6)
第三步:将验证码存入redis,设定生存周期
stringRedisTemplate.opsForValue().set()方法可以直接设定生存周期
第四步:模拟发送验证码
@Overridepublic Result sendCode(String phone, HttpSession session) {//1.检查手机号是否合法if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号格式有误!");}//2.生成验证码String code = RandomUtil.randomNumbers(6);//3.将验证码存到redis中stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+code,code,LOGIN_CODE_TTL,TimeUnit.MINUTES);//模拟发送验证码log.debug("验证码为{"+code+"}");//4.发送验证码return Result.ok();}
登录
第一步:判断手机号是否合法
因为登录和发送验证码是两个业务,这中间用户可能修改了手机号,所以还需要判断手机号是否合法
第二步:获取验证码
第三步:判断验证码是否正确
第四步:如果正确,判断用户是否存在
如果不存在,根据手机号创建用户
第五步:将用户保存至redis
redis---key:创建token
reids---value:将User转为UserToken存储,注意因为使用的是StringRedisTemplate,所有的键值对都是String类型。而User的Id是Long类型,需要做特殊的转换
第六步:设置token生存周期
第七步:返回token
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.判断手机号是否合法String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号格式有误!");}//2.获取验证码String code = loginForm.getCode();String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + code);//3.比对验证码是否一致if(cacheCode==null || !cacheCode.equals(code)){return Result.fail("验证码有误");}//4.判断用户是否存在User tmpUser = new User();tmpUser.setPhone(phone);User user = userMapper.selectOne(new QueryWrapper<>(tmpUser));//5.不存在,则创建用户if(user==null){user = createUserToRedis(phone);}//6.创建tokenString token = UUID.randomUUID().toString(true);//7.将user转为userDTO存储UserDTO userDTO= BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));String key=LOGIN_USER_KEY+token;stringRedisTemplate.opsForHash().putAll(key,map);//8.设置token有效期stringRedisTemplate.expire(key,LOGIN_USER_TTL, TimeUnit.MINUTES);//7.返回return Result.ok(token);}
获取用户
直接获取当前用户并返回
@GetMapping("/me")public Result me(){// TODO 获取当前登录的用户并返回UserDTO user = UserHolder.getUser();return Result.ok(user);}
对于每次请求,都应该判读用户是否登录(部分请求可以直接放行)
RefreshTokenInterceptor:
该拦截器不做拦截处理,只是对将已经登录的用户存入LocalThread
具体实现:
第一步:根据请求头获取token
第二步:如果token存在,则根据token获取UserMap用户
第三步:判断UserMap是否为空
第四步:如果UserMap不为空,则转换为UserDTO,并存入LocalThread
第五步:刷新该token生存时间
第六步:放行
private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate=stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("authorization");if(StrUtil.isBlank(token)){return true;}String tokenKey=LOGIN_USER_KEY+token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);if(userMap.isEmpty()){return true;}UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap,new UserDTO(), false);UserHolder.saveUser(userDTO);stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
LoginInterceptor:
该拦截器对未登录的用户进行拦截
具体实现:
通过LocalThread获取用户,如果用户不存在则拦截,存在则放行
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {UserDTO user = UserHolder.getUser();if(user==null){response.setStatus(401);return false;}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
注册拦截器:
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/login","/user/code","/blog/hot","/shop/","/shop-type/","/voucher/","/upload/").order(1);}
}
备注:UserHolder的实现
public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user){tl.set(user);}public static UserDTO getUser(){return tl.get();}public static void removeUser(){tl.remove();}
}