1、Redis短信登录场景
1.1、整体流程
1.1、发送短信验证码
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、校验手机号是否正常if (RegexUtils.isPhoneInvalid(phone)) {// 校验失败返回错误信息return Result.fail("手机号不符合规则");}// 2、生成验证码String code = RandomUtil.randomNumbers(6);// 3、存储到session中session.setAttribute("code", code);// 4、发送验证码log.debug("发送验证码成功,验证码,{}", code);// 5、返回请求return Result.ok();}
}
1.2、短信和手机登录验证注册
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1、校验手机号是否正确if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {// 校验失败返回错误信息return Result.fail("手机号不符合规则");}// 2、校验验证码是否一致是否正确Object cacheCode = session.getAttribute("code");if (cacheCode == null || !cacheCode.equals(loginForm.getCode())) {// 校验失败返回错误信息return Result.fail("验证码错误");}// 3、根据手机号查询用户信息是否存在User user = query().eq("phone", loginForm.getPhone()).one();if (user == null) {// 4、不存在则注册该用户信息user = new User();user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));user.setPhone(loginForm.getPhone());save(user);}// 5、保存用户信息到session中session.setAttribute("user", user);return Result.ok();}
1.3、登录校验-注册配置拦截器
1.1、设置拦截器的逻辑
public class LoginInteception implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1、获取sessionHttpSession session = request.getSession();// 2、获取session中的userObject user = session.getAttribute("user");// 3、判断user是否存在if (user == null) {// 4、不存在就拦截 -> 401 权限问题response.setStatus(401);return false;}// 5、存在就存在到ThreadLocal中UserHolder.saveUser((User) user);// 6、放行return true;}
}
1.2、配置总拦截器
@Configuration
public class MvaConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInteception()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
1.2、优化流程
1.1、存在的问题
1.2、优化操作
存储code:使用手机号作为存储符合唯一性和业务逻辑
存储用户信息:使用唯一随机字符而不是继续使用手机号信息,需要返回前台防止信息泄露
1.3、redis存储的选择
1.4、代码修改
拦截器代码
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate redisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInteception()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);registry.addInterceptor(new RefresTokenInteception(redisTemplate)).addPathPatterns("/**").order(0); // order 设置优先级,数字越小优先级越高}
}
public class LoginInteception implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1、判断ThreadLocal是否存在用户,不存在则拦截if (UserHolder.getUser() == null) {response.setStatus(401);return false;}// 2、存在放行return true;}
}
public class RefresTokenInteception implements HandlerInterceptor {private StringRedisTemplate redisTemplate;public RefresTokenInteception(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1、获取请求头中的tokenString token = request.getHeader("authorization");if (token == null) {return true;}// 2、使用token从redis中获取用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = redisTemplate.opsForHash().entries(key);// 3、判断user是否存在if (userMap.isEmpty()) {return true;}// 5、将查询到的Hash数据转化成UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6、 存在就存在到ThreadLocal中UserHolder.saveUser(userDTO);// 7、刷新token有效期redisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);// 8、放行return true;}
}
用户登录注册代码
@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1、校验手机号是否正确if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {// 校验失败返回错误信息return Result.fail("手机号不符合规则");}
// // 2、校验验证码是否一致是否正确
// Object cacheCode = session.getAttribute("code");// 2、从redis中获取手机号的验证码String cacheCode = redisTemplate.opsForValue().get(LOGIN_CODE_KEY + loginForm.getPhone());if (cacheCode == null || !cacheCode.equals(loginForm.getCode())) {// 校验失败返回错误信息return Result.fail("验证码错误");}// 3、根据手机号查询用户信息是否存在User user = query().eq("phone", loginForm.getPhone()).one();if (user == null) {// 4、不存在则注册该用户信息user = new User();user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));user.setPhone(loginForm.getPhone());save(user);}
// UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// // 5、保存用户信息到session中
// session.setAttribute("user", userDTO);// 5、保存用户信息到redis中// 5.1、随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);// 5.2、将user独享转化成Hash存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));String tokenKey = LOGIN_USER_KEY + token;redisTemplate.opsForHash().putAll(tokenKey, userMap);// 5.3、设置token有效期redisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8、返回tokenreturn Result.ok(token);}
发送验证码
@Overridepublic Result sendCode(String phone, HttpSession session) {// 1、校验手机号是否正常if (RegexUtils.isPhoneInvalid(phone)) {// 校验失败返回错误信息return Result.fail("手机号不符合规则");}// 2、生成验证码String code = RandomUtil.randomNumbers(6);
// // 3、存储到session中
// session.setAttribute("code", code);// 3、存储到redis中redisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 4、发送验证码log.debug("发送验证码成功,验证码,{}", code);// 5、返回请求return Result.ok();}