天津百度网站排名优化,seo建站教程,家居网站建设素材,广州文化网站模板目录 IDEA集成git
传统session存在的问题
redis方案
业务流程
选用的数据结构
整体访问流程
发送短信验证码 获取校验验证码
配置登录拦截器
拦截器注册配置类
拦截器
用户状态刷新问题
刷新问题解决方案 IDEA集成git
远程仓库采用码云#xff0c;创建好仓库创建好仓库复制仓库的url 在idea中点击出现git选项点击ok 之后右击项目点击remotes 填写url即可集成git
传统session存在的问题
传统的登录认证会采用session进行登录认证将登录的验证码用户信息都存放到session中我们通过session来进行操作数据这有什么问题呢 每个tomcat服务器中都有一份属于自己的session,假设用户第一次访问第一台tomcat并且把自己的信息存放到第一台服务器的session中但是第二次这个用户访问到了第二台tomcat那么在第二台服务器上肯定没有第一台服务器存放的session即session在各个服务器之间是不共通的所以此时整个登录拦截功能就会出现问题而redis数据本身就是共享的就可以避免session共享的问题了 redis方案
业务流程
将验证码存储到redis中将用户数据存储到redis中
选用的数据结构
存储验证码时采用String类型key采用业务代码手机号
存储user数据时采用hashkey采用业务代码随机的tonke hash可以将对象中的每个字段独立存储可以针对单个字段做CRUD并且内存占用更少实际上也可以采用String类型但hash类型消耗内存较少故选用String类型 整体访问流程 当注册完成后用户去登录会去校验用户提交的手机号和验证码是否一致如果一致则根据手机号查询用户信息不存在则新建最后将用户数据保存到redis并且生成token作为redis的key当我们校验用户是否登录时会去携带着token进行访问从redis中取出token对应的value判断是否存在这个数据如果没有则拦截如果存在则将其保存到threadLocal中简化后续业务获取用户信息的代码后续业务需要登录用户的信息只要在threadLocal中取即可无需从redis中取从而增加复杂度最后放行。 redis业务代码常量后续继续补充
public class RedisConstants {//发送验证码业务标识public static final String LOGIN_CODE_KEY login:code:;public static final Long LOGIN_CODE_TTL 2L;//用户登录业务标识public static final String LOGIN_USER_KEY login:token:;public static final Long LOGIN_USER_TTL 30L;}
发送短信验证码
采用日志打印形式将验证码打印在控制台 public Result sendCode(String phone) {//校验手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号码格式不正确);}//手机号格式正确生成验证码String code RandomUtil.randomNumbers(6);//保存到redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);//打印日志log.debug(手机验证码为: code);return Result.ok();} 获取校验验证码
我们需要对用户敏感信息进行筛选只给前端返回必要的用户信息数据需要封装dto对象同时在uuid生成token作为redis取数据的key最后要设置过期时间防止reids内存爆炸设置为30分钟因为session的过期时间也是30分钟 public Result login(LoginFormDTO loginForm) {String phone loginForm.getPhone();String code loginForm.getCode();//校验手机号if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号码格式不正确);}//从redis取验证码进行比对String cacheCode stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY phone);if (cacheCode null || !cacheCode.equals(code)) {return Result.fail(验证码错误);}//根据电话号码从数据库查询用户是否存在LambdaQueryWrapperUser queryWrapper new LambdaQueryWrapper();queryWrapper.eq(User::getPhone, phone);User user userMapper.selectOne(queryWrapper);//不存在则创建if (user null) {user createUserWithPhone(phone);}String token UUID.randomUUID().toString(true);//只返回不敏感的信息使用dto进行封装UserDTO userDTO new UserDTO();BeanUtils.copyProperties(user, userDTO);//将dto对象转化为map结构MapString, String userMap new HashMap();userMap.put(id, userDTO.getId().toString());userMap.put(icon, userDTO.getIcon());userMap.put(nickName, userDTO.getNickName());
//业务代码token形成redis中的keyString tokenKey LOGIN_USER_KEY token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}
配置登录拦截器
拦截器注册配置类
Configuration
public class MvcConfig implements WebMvcConfigurer {Autowiredprivate StringRedisTemplate stringRedisTemplate;Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(/shop/**,/voucher/**,/shop-type/**,/upload/**,/blog/hot,/user/code,/user/login);}
}
拦截器
拦截器需要的stringRedisTemplate对象不能通过Autowired直接注入 因为LoginInterceptor 对象是我们手动创建的不受spring管控不在spring容器中故不能注入spring容器中的bean只能通过在配置类中通过构造方法注入stringRedisTemplate对象 public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token request.getHeader(authorization);if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}String tokenKey LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if (userMap.isEmpty()) {//4.不存在拦截返回401状态码response.setStatus(401);return false;}UserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//5.存在保存用户信息到ThreadlocalUserHolder.saveUser(userDTO);//刷新登录状态stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);//6.放行return true;}
}
用户状态刷新问题 那就是登录认证和刷新用户状态绑定在一个拦截器中而需要登录认证的路径并不是全部路径如果用户不访问需要登录认证的路径那就刷新不了用户状态即30分钟之后登录就会退出这明显不符合我们的常识 刷新问题解决方案
我们可以再加一个拦截器形成一个拦截器链第一个拦截器对所有路径进行拦截而它的功能就是刷新登录状态登录认证则由第二个拦截器完成 刷新状态拦截器
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token request.getHeader(authorization);if (StrUtil.isBlank(token)) {return true;}// 2.基于TOKEN获取redis中的用户String key LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
登录认证拦截器
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截ThreadLocal中是否有用户if (UserHolder.getUser() null) {// 没有需要拦截设置状态码response.setStatus(401);// 拦截return false;}// 有用户则放行return true;}
}
为了保证拦截器的执行先后顺序配置类需设置order的大小设定拦截器执行的先后的顺序如若不设置就按照注册顺序的先后来执行
Configuration
public class MvcConfig implements WebMvcConfigurer {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(/shop/**,/voucher/**,/shop-type/**,/upload/**,/blog/hot,/user/code,/user/login).order(1);// token刷新的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns(/**).order(0);}
}