网站销售怎么样的,wordpress替换插件,wordpress导入数据库依然无法链接,深圳网站制作建设哪家专业目录
1 基于Session实现短信验证码登录
2 配置登录拦截器
3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效
4 Session集群共享问题
5 基于Redis实现短信验证码登录
6 Hash 结构与 String 结构类型的比较
7 Redis替代Session需要考虑的问题
8 …目录
1 基于Session实现短信验证码登录
2 配置登录拦截器
3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效
4 Session集群共享问题
5 基于Redis实现短信验证码登录
6 Hash 结构与 String 结构类型的比较
7 Redis替代Session需要考虑的问题
8 基于redis短信验证登录
9 配置登录拦截器 1 基于Session实现短信验证码登录 /*** 发送验证码*/Overridepublic Result sendCode(String phone, HttpSession session) {// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号格式不正确);}// 2、手机号合法生成验证码并保存到Session中String code RandomUtil.randomNumbers(6);session.setAttribute(SystemConstants.VERIFY_CODE, code);// 3、发送验证码log.info(验证码:{}, code);return Result.ok();}/*** 用户登录*/Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone loginForm.getPhone();String code loginForm.getCode();// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号格式不正确);}// 2、判断验证码是否正确String sessionCode (String) session.getAttribute(LOGIN_CODE);if (code null || !code.equals(sessionCode)) {return Result.fail(验证码不正确);}// 3、判断手机号是否是已存在的用户User user this.getOne(new LambdaQueryWrapperUser().eq(User::getPassword, phone));if (Objects.isNull(user)) {// 用户不存在需要注册user createUserWithPhone(phone);}// 4、保存用户信息到Session中便于后面逻辑的判断比如登录判断、随时取用户信息减少对数据库的查询session.setAttribute(LOGIN_USER, user);return Result.ok();}/*** 根据手机号创建用户*/private User createUserWithPhone(String phone) {User user new User();user.setPhone(phone);user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX RandomUtil.randomString(10));this.save(user);return user;}2 配置登录拦截器 public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器用于判断用户是否登录*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session request.getSession();// 1、判断用户是否存在User user (User) session.getAttribute(LOGIN_USER);if (Objects.isNull(user)){// 用户不存在直接拦截response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);return false;}// 2、用户存在则将用户信息保存到ThreadLocal中方便后续逻辑处理// 比如方便获取和使用用户信息session获取用户信息是具有侵入性的ThreadLocalUtls.saveUser(user);return HandlerInterceptor.super.preHandle(request, response, handler);}
}3 配置完拦截器还需将自定义拦截器添加到SpringMVC的拦截器列表中 才能生效
Configuration
public class WebMvcConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {// 添加登录拦截器registry.addInterceptor(new LoginInterceptor())// 设置放行请求.excludePathPatterns(/user/code,/user/login,/blog/hot,/shop/**,/shop-type/**,/upload/**,/voucher/**);}
}
4 Session集群共享问题
1什么是Session集群共享问题 在分布式集群环境中会话Session共享是一个常见的挑战。默认情况下Web 应用程序的会话是保存在单个服务器上的当请求不经过该服务器时会话信息无法被访问。 2 Session集群共享问题造成哪些问题 服务器之间无法实现会话状态的共享。比如在当前这个服务器上用户已经完成了登录Session中存储了用户的信息能够判断用户已登录但是在另一个服务器的Session中没有用户信息无法调用显示没有登录的服务器上的服务 3如何解决Session集群共享问题
方案一Session拷贝不推荐
Tomcat提供了Session拷贝功能通过配置Tomcat可以实现Session的拷贝但是这会增加服务器的额外内存开销同时会带来数据一致性问题
方案二Redis缓存推荐
Redis缓存具有Session存储一样的特点基于内存、存储结构可以是key-value结构、数据共享
4Redis缓存相较于传统Session存储的优点
1 高性能和可伸缩性Redis 是一个内存数据库具有快速的读写能力。相比于传统的 Session 存储方式将会话数据存储在 Redis 中可以大大提高读写速度和处理能力。此外Redis 还支持集群和分片技术可以实现水平扩展处理大规模的并发请求。 2 可靠性和持久性Redis 提供了持久化机制可以将内存中的数据定期或异步地写入磁盘以保证数据的持久性。这样即使发生服务器崩溃或重启会话数据也可以被恢复。 3 丰富的数据结构Redis 不仅仅是一个键值存储数据库它还支持多种数据结构如字符串、列表、哈希、集合和有序集合等。这些数据结构的灵活性使得可以更方便地存储和操作复杂的会话数据。 4 分布式缓存功能Redis 作为一个高效的缓存解决方案可以用于缓存会话数据减轻后端服务器的负载。与传统的 Session 存储方式相比使用 Redis 缓存会话数据可以大幅提高系统的性能和可扩展性。 5 可用性和可部署性Redis 是一个强大而成熟的开源工具有丰富的社区支持和活跃的开发者社区。它可以轻松地与各种编程语言和框架集成并且可以在多个操作系统上运行。 PS但是Redis费钱而且增加了系统的复杂度
5 基于Redis实现短信验证码登录 6 Hash 结构与 String 结构类型的比较
String 数据结构是以 JSON 字符串的形式保存更加直观操作也更加简单但是 JSON 结构会有很多非必须的内存开销比如双引号、大括号内存占用比 Hash 更高Hash 数据结构是以 Hash 表的形式保存可以对单个字段进行CRUD更加灵活 7 Redis替代Session需要考虑的问题
1选择合适的数据结构了解 Hash 比 String 的区别 2选择合适的key为key设置一个业务前缀方便区分和分组为key拼接一个UUID避免key冲突防止数据覆盖 3选择合适的存储粒度对于验证码这类数据一般设置TTL为3min即可防止大量缓存数据的堆积而对于用户信息这类数据可以稍微设置长一点比如30min防止频繁对Redis进行IO操作 8 基于redis短信验证登录 /*** 发送验证码** param phone* param session* return*/Overridepublic Result sendCode(String phone, HttpSession session) {// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号格式不正确);}// 2、手机号合法生成验证码并保存到Redis中String code RandomUtil.randomNumbers(6);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY phone, code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);// 3、发送验证码log.info(验证码:{}, code);return Result.ok();}/*** 用户登录** param loginForm* param session* return*/Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone loginForm.getPhone();String code loginForm.getCode();// 1、判断手机号是否合法if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号格式不正确);}// 2、判断验证码是否正确String redisCode stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY phone);if (code null || !code.equals(redisCode)) {return Result.fail(验证码不正确);}// 3、判断手机号是否是已存在的用户User user this.getOne(new LambdaQueryWrapperUser().eq(User::getPhone, phone));if (Objects.isNull(user)) {// 用户不存在需要注册user createUserWithPhone(phone);}// 4、保存用户信息到Redis中便于后面逻辑的判断比如登录判断、随时取用户信息减少对数据库的查询UserDTO userDTO BeanUtil.copyProperties(user, UserDTO.class);// 将对象中字段全部转成string类型StringRedisTemplate只能存字符串类型的数据MapString, Object userMap BeanUtil.beanToMap(userDTO, new HashMap(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) - fieldValue.toString()));String token UUID.randomUUID().toString(true);String tokenKey LOGIN_USER_KEY token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);return Result.ok(token);}/*** 根据手机号创建用户并保存** param phone* return*/private User createUserWithPhone(String phone) {User user new User();user.setPhone(phone);user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX RandomUtil.randomString(10));this.save(user);return user;}9 配置登录拦截器 单独配置一个拦截器用户刷新Redis中的token在基于Session实现短信验证码登录时我们只配置了一个拦截器这里需要另外再配置一个拦截器专门用于刷新存入Redis中的 token因为我们现在改用Redis了为了防止用户在操作网站时突然由于Redis中的 token 过期导致直接退出网站严重影响用户体验。那为什么不把刷新的操作放到一个拦截器中呢因为之前的那个拦截器只是用来拦截一些需要进行登录校验的请求对于哪些不需要登录校验的请求是不会走拦截器的刷新操作显然是要针对所有请求比较合理所以单独创建一个拦截器拦截一切请求刷新Redis中的Key 登录拦截器
public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器用于判断用户是否登录*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 判断当前用户是否已登录if (ThreadLocalUtls.getUser() null){// 当前用户未登录直接拦截response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);return false;}// 用户存在直接放行return true;}
}刷新token的拦截器
public class RefreshTokenInterceptor implements HandlerInterceptor {// new出来的对象是无法直接注入IOC容器的LoginInterceptor是直接new出来的// 所以这里需要再配置类中注入然后通过构造器传入到当前类中private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1、获取token并判断token是否存在String token request.getHeader(authorization);if (StrUtil.isBlank(token)){// token不存在说明当前用户未登录不需要刷新直接放行return true;}// 2、判断用户是否存在String tokenKey LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(tokenKey);if (userMap.isEmpty()){// 用户不存在说明当前用户未登录不需要刷新直接放行return true;}// 3、用户存在则将用户信息保存到ThreadLocal中方便后续逻辑处理比如方便获取和使用用户信息Redis获取用户信息是具有侵入性的UserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.class));// 4、刷新token有效期stringRedisTemplate.expire(token, LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}
}将自定义的拦截器添加到SpringMVC的拦截器表中使其生效
Configuration
public class WebMvcConfig implements WebMvcConfigurer {// new出来的对象是无法直接注入IOC容器的LoginInterceptor是直接new出来的// 所以这里需要再配置类中注入然后通过构造器传入到当前类中Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic void addInterceptors(InterceptorRegistry registry) {// 添加登录拦截器registry.addInterceptor(new LoginInterceptor())// 设置放行请求.excludePathPatterns(/user/code,/user/login,/blog/hot,/shop/**,/shop-type/**,/upload/**,/voucher/**).order(1); // 优先级默认都是0值越大优先级越低// 添加刷新token的拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns(/**).order(0);}
}RefreshTokenInterceptor 先执行主要用来检查 token 的有效性并刷新 token 的有效期同时将用户信息存入 ThreadLocal。LoginInterceptor 后执行验证 ThreadLocal 中是否有用户信息以确认用户是否登录。