【从零开始学习Redis】项目实战-黑马点评D1

项目实战-黑马点评

项目架构

短信登录

发送短信验证码

实现思路就是按照上图左一部分,

实现类如下

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {/*** 验证手机号发送验证码** @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,可以返回错误信息return Result.fail("手机号格式错误");}//3.生成验证码String code = RandomUtil.randomNumbers(6);//4.保存验证码到sessionsession.setAttribute("code", code);//5.发送验证码log.debug("发送验证码成功,验证码为:{}",code);//返回okreturn Result.ok();}
}

这里封装了RegexUtils工具类,调用了反向验证手机号方法,我们可以学习一下这个工具类的实现

public class RegexUtils {/*** 是否是无效手机格式* @param phone 要校验的手机号* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}/*** 是否是无效邮箱格式* @param email 要校验的邮箱* @return true:符合,false:不符合*/public static boolean isEmailInvalid(String email){return mismatch(email, RegexPatterns.EMAIL_REGEX);}/*** 是否是无效验证码格式* @param code 要校验的验证码* @return true:符合,false:不符合*/public static boolean isCodeInvalid(String code){return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);}// 校验是否不符合正则格式private static boolean mismatch(String str, String regex){if (StrUtil.isBlank(str)) {return true;}return !str.matches(regex);}
}

其实就是先检查手机号是否为空,如果不为空再把当前手机号字符串按照正则表达式匹配。

短信验证码登录

校验手机号,校验验证码,如果不一致直接返回错误信息。

如果一致,需要查询用户,如果根据当前手机号,用户不存在,那么创建新用户,其实就是insert

这里没有MapperMyBatis-PlusMyBatis的增强工具,内置了大量的方法,无需XML就能完成CRUD

/*** 实现登录功能* @param loginForm* @param session* @return*/
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//手机号不符合,返回错误信息return Result.fail("手机号格式错误");}//2.校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode == null || !cacheCode.equals(code)){//3.不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();//5.判断用户是否存在if(user == null){//6.不存在,创建新用户并保存user = createUserWithPhone(phone);}//7.保存用户信息到sessionsession.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));return Result.ok();
}private User createUserWithPhone(String phone){
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
save(user);
return user;
}
登录校验

在访问后端的多个接口的时候,不可能每次访问都得登陆,保证只要登陆一次即可。于是可以使用拦截器统一拦截,获取session信息,如果存在那么放行,并把信息保存到ThreadLocal,保证可以随时调用。如果不存在,那么拦截。

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session的用户Object user = session.getAttribute("user");//3.判断用户是否存在if(user == null){//4.不存在,拦截,返回401状态码response.setStatus(401);return false;}//5.存在,保存用户信息到ThreadLocalUserHolder.saveUser((UserDTO) user);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}
集群的session共享问题

由于多台Tomcat并不共享session的共享空间,请求切换到不同的Tomcat时就会导致数据丢失。

最开始考虑的是,只要把数据拷贝一份到每个Tomcat即可,但会导致空间浪费问题,因为保存的都是相同数据。

替代方案应满足:

  • 数据共享
  • 内存存储
  • key、value结构

显然可以借助Redis

基于Redis实现共享session登录

发送验证码

改动的地方就是本来储存在session中,现在把验证码保存到Redis中,使用的key是业务+手机号,从而保证唯一性。

@Resource
private StringRedisTemplate stringRedisTemplate;
/*** 验证手机号发送验证码** @param phone* @param session* @return*/
@Override
public Result sendCode(String phone, HttpSession session) {//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,可以返回错误信息return Result.fail("手机号格式错误");}//3.生成验证码String code = RandomUtil.randomNumbers(6);//4.保存验证码到session set key value ex 120stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);//5.发送验证码log.debug("发送验证码成功,验证码为:{}",code);//返回okreturn Result.ok();
}
实现登录功能

更新部分在于,取数据从redis中获取,生成的随机token作为令牌和储存用户信息的key。

/*** 实现登录功能* @param loginForm* @param session* @return*/
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//手机号不符合,返回错误信息return Result.fail("手机号格式错误");}//2.校验验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {//3.不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();//5.判断用户是否存在if (user == null) {//6.不存在,创建新用户并保存user = createUserWithPhone(phone);}//7.保存用户信息到redis//7.1 随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);//7.2 将user对象转为HashMap存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().ignoreNullValue().setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));//7.3 储存String userKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(userKey, userMap);//7.4 设置token有效期stringRedisTemplate.expire(userKey, LOGIN_USER_TTL, TimeUnit.MINUTES);//8.返回tokenreturn Result.ok(token);
}private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
save(user);
return user;
}
登录拦截器的优化

我们当前的拦截器存在一个问题,就是拦截了需要登录的请求时,为了避免永久数据对redis的压力,我们在执行登陆后,一般会给token设置有效期,意思是长时间不点击访问页面,那么token就会过期,不再允许访问。但是现在只有一个拦截器,拦截的是需要登录的路径,而且只有在用户登录时会更新token有效期,这样就会导致即使用户在不停操作,但是不需要登录操作的部分功能也不会更新token有效期,同时需要登陆操作的部分也不会更新token有效期。

怎么解决呢?

我们可以定义两个拦截器,一个用来拦截所有路径,但是不做“拦截”处理,主要负责获取token、查询Redis用户、保存到ThreadLocal、更新token有效期、放行。另外一个拦截需要登陆的路径,查询ThreadLocal用户,不存在就拦截,存在则放行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93130.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理的范式转变:从Seq2Seq模型到Transformer架构

Seq2Seq 定义 Seq2Seq是一个Encoder-Decoder结构的网络&#xff0c;它的输入是一个序列&#xff0c;输出也是一个序列&#xff0c; Encoder使用循环神经网络(RNN,GRU&#xff0c;LSTM等)&#xff0c;将一个可变长度的信号序列(输入句子)变为固定维度的向量编码表达&#xff0c;…

【博客系统测试报告】---接口自动化测试

目录 1、需求分析 2、挑选接口 3、设计博客系统的测试用例 4、设计自动化测试框架 test_add.py: test_detail.py: test_getAuthorInfo.py: test_getUserInfo: test_list.py: test_login.py: logger_util.py: request_util.py: yaml_util.py: 1、需求分析 根据业务…

Mysql数据库迁移到GaussDB注意事项

mysql数据库迁移高斯数据库 建议开启高斯数据库M模式&#xff0c;mysql兼容模式&#xff0c;可以直接使用mysql的建表语句&#xff0c;自增主键可以使用AUTO_INCREMENT&#xff0c;如果不开启M模式&#xff0c;只能使用高斯数据库的序列添加自增主键1&#xff1a;如果使用数据库…

苹果正计划大举进军人工智能硬件领域

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Serverless 架构核心解析与应用实践

Serverless 的核心定义与优势‌‌核心定义Serverless&#xff08;无服务器架构&#xff09;是一种云计算模型&#xff0c;开发者无需关注底层服务器管理&#xff0c;由云服务商自动分配资源、弹性扩缩容&#xff0c;并按实际使用量计费‌。其核心特点包括&#xff1a;‌按需计算…

Redis持久化机制详解:RDB与AOF的全面对比与实践指南

目录 一、RDB持久化机制 1.1 RDB概述 1.2 RDB触发机制 1) 手动执行save命令 2) 手动执行bgsave命令 3) Redis正常关闭时 4) 自动触发条件满足时 1.3 RDB详细配置 1.4 RDB实现原理 1.5 RDB的优缺点分析 二、AOF持久化机制 2.1 AOF概述 2.2 AOF工作流程 2.3 AOF同步…

介绍一下jQuery的AJAX异步请求

目录 一、核心方法&#xff1a;$.ajax() 二、简化方法&#xff08;常用场景&#xff09; 1. $.get()&#xff1a;快速发送 GET 请求&#xff08;获取数据&#xff09; 2. $.post()&#xff1a;快速发送 POST 请求&#xff08;提交数据&#xff09; 3. $.getJSON()&#xf…

Win10系统Ruby+Devkit3.4.5-1安装

Win10系统RubyDevkit3.4.5-1安装安装步骤软件工具安装Ruby安装gem mysql2处理libmysql.dll验证mysql2安装步骤 软件工具 mysql-connector-c-6.1.11-winx64.zip rubyinstaller-devkit-3.4.5-1-x64.exe 安装Ruby 执行rubyinstaller-devkit-3.4.5-1-x64.exe&#xff0c;期间可…

社交工程:洞穿人心防线的无形之矛

在网络安全领域&#xff0c;一道无形的裂痕正在迅速蔓延。它不是复杂的零日漏洞&#xff0c;也不是精妙的恶意代码&#xff0c;而是利用人性弱点进行攻击的古老技艺——社交工程。当全球网络安全支出突破千亿美元大关&#xff0c;防火墙筑得越来越高&#xff0c;加密算法越来越…

Go 并发控制利器 ants 使用文档

https://github.com/panjf2000/ants1.1 什么是 ants ants 是一个高性能的 Go 语言 goroutine 池&#xff0c;它能复用已完成任务的 goroutine&#xff0c;避免频繁创建和销毁 goroutine&#xff0c;节省 CPU 与内存开销&#xff0c;并且能限制并发数量防止资源被耗尽。 1.2 安装…

Day57--图论--53. 寻宝(卡码网)

Day57–图论–53. 寻宝&#xff08;卡码网&#xff09; 今天学习&#xff1a;最小生成树。有两种算法&#xff08;Prim和Kruskal&#xff09;和一道例题。 prim 算法是维护节点的集合&#xff0c;而 Kruskal 是维护边的集合。 最小生成树&#xff1a;所有节点的最小连通子图&am…

解决海洋探测数据同步网络问题的新思路——基于智能组网技术的探索

随着海洋探测技术的不断发展&#xff0c;数据同步网络的稳定性和低延迟需求变得愈发重要。海洋探测数据来自多个分布式采集点&#xff0c;这些点需要高效的组网方式来实现实时数据传输。然而&#xff0c;由于海洋环境的特殊性&#xff08;如复杂的网络拓扑、高湿度和极端温度&a…

设计模式笔记_行为型_责任链模式

1. 责任链模式介绍责任链模式&#xff08;Chain of Responsibility&#xff09;是一种行为设计模式&#xff0c;它允许将多个处理器&#xff08;处理对象&#xff09;连接成一条链&#xff0c;并沿着这条链传递请求&#xff0c;直到有一个处理器处理它为止。职责链模式的主要目…

pygame的帧处理中,涉及键盘的有`pg.event.get()`与`pg.key.get_pressed()` ,二者有什么区别与联系?

一、pg.event.get() 返回的是一组事件 pg.event.get() 返回的是一组事件&#xff08;一个包含多个事件对象的列表&#xff09;。这是因为在游戏的“一帧”时间内&#xff08;通常1/60秒左右&#xff09;&#xff0c;用户可能会触发多个事件&#xff08;比如同时按下多个键、快速…

TF - IDF算法面试与工作常见问题全解析

在自然语言处理领域&#xff0c;TF - IDF算法是一个基础且重要的概念。无论是在求职面试还是在实际工作中&#xff0c;都经常会遇到与TF - IDF相关的问题。以下是一些常见的问题及其详细解答&#xff1a; 一、基本概念类问题 1. 什么是TF - IDF算法&#xff1f; TF - IDF&#…

Transformer网络结构解析

博主会经常分享自己在人工智能阶段的学习笔记&#xff0c;欢迎大家访问我滴个人博客&#xff01;&#xff08;都不白来&#xff01;&#xff09; 小牛壮士 - 个人博客https://kukudelin.top/ 前言 Transformer 广泛应用于自然语言处理&#xff08;如机器翻译、文本生成&…

gateway进行接口日志打印

打印需求&#xff1a;对所有的接口打印&#xff1a;请求方式&#xff0c;请求路径&#xff0c;请求参数&#xff0c;用户id&#xff0c;访问IP&#xff0c;访问时间对增删改操作的接口打印&#xff1a;接口响应打印方案&#xff1a;给GET设置一个白名单&#xff08;因为get请求…

MATLAB实现图像增强(直方图均衡化)

直方图均衡化是一种常用的图像增强技术&#xff0c;它通过重新分布图像的像素强度值来增强图像的对比度。以下是MATLAB中实现直方图均衡化的详细方法。%% 直方图均衡变换 clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;%% …

java15学习笔记-密封类

360:Sealed Classes (Preview) 封闭类&#xff08;预览&#xff09; 总结 使用密封类和接口增强Java编程语言。密封类和接口限制了哪些其他类或接口可以扩展或实现它们。这是JDK 15中的预览语言功能。 目标 允许类或接口的作者控制负责实现它的代码。 提供一种比访问…

西门子PLC通过稳联技术EtherCAT转Profinet网关连接baumuller伺服器的配置案例

西门子PLC用稳联技术的EtherCAT转Profinet网关&#xff0c;连上baumuller伺服器的配置例子本案例实现西门子S71200 PLC通过EtherCAT转Profinet网关对baumuller&#xff08;Baumller&#xff09;伺服器的实时控制&#xff0c;适用于高精度运动控制场景&#xff08;如精密机床、自…