Redis实战—黑马点评(一) 登录篇
Redis实战 — 黑马点评(一) 登录篇
来自黑马的redis课程的笔记
【黑马程序员Redis入门到实战教程,深度透析redis底层原理+redis分布式锁+企业解决方案+黑马点评实战项目】
目录
- Redis实战 — 黑马点评(一) 登录篇
- 1. 项目介绍
- 2. 短信登录
- 2.1 流程:
- 2.2 一些小的收获
- 2.3 系统安全(重点)
- 2.4 实现流程和关键代码
- 2.4.1 发送验证码
- 2.4.2 短信验证码登录/注册
- 2.4.3 校验登录状态
1. 项目介绍


2. 短信登录
2.1 流程:

2.2 一些小的收获
-
使用hutool中的
RandomUtil.randomNumbers(6)和RandomUtil.randomString(10)随机生成验证码和随机字符串(用于默认用户名) -
代码中不要出现“魔法值”,要统一定义常量
-
mp中
lambdaQuery的使用(相信之后不用再new QueryWrapper了)User user = lambdaQuery().eq(User::getPhone, phone).one(); -
使用
UUID.randomUUID().toString(true)来生成不带‘-’的uuid -
// 使用hutool工具中的对象转map,并自定义操作。 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().ignoreNullValue().setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())); -
封装好的常用的正则表达式和正则工具类
public abstract class RegexPatterns {/*** 手机号正则* 使用过程中发现有些手机号不支持如191这些新手机号,自行修改即可,例如要支持191,只需将9[89]改为9[189]* 括号里面的就是第2,3位手机号*/public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";/*** 邮箱正则*/public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";/*** 密码正则。4~32位的字母、数字、下划线*/public static final String PASSWORD_REGEX = "^\\w{4,32}$";/*** 验证码正则, 6位数字或字母*/public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";}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);} }
2.3 系统安全(重点)
用户登录,管理的三种常见方案:
-
传统的cookie + session
流程:
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
优点:
- 数据存放在服务端,客户端只拿到令牌,服务端对用户状态掌控力强。
缺点:
- 浏览器支持cookie,移动端不行。
- session是存放在服务端程序内存中的,分布式架构下session共享是个问题,不易水平扩展。
-
JWT
流程:
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,颁发一个jwt字符串,该字符串中可以包含一些不敏感的用户信息
3、客户端存储jwt。
4、用户随后的每一次请求,都会带上jwt,服务端验证jwt判断用户是否合法。
优点:
- 去中心化,便于分布式系统使用。
- 基本信息可以直接放在token中。 username,nickname,role等。
缺点:
- 一旦颁发jwt,该jwt在有效期内都有效,服务端不能主动让其失效。
-
token + redis
该方案就是cookie + session的一个升级版,其思想是一样的。
也就是让redis代替session,让token代替sessionid。
流程:
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在redis里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 token。
4、用户随后的每一次请求,都会带上该token。
5、服务器收到 token,在redis中找到前期保存的数据,由此得知用户的身份。
优点:
- 数据存放在服务端,客户端只拿到令牌,服务端对用户状态掌控力强。
- 用redis代替session后,不再担心分布式架构session共享的问题,性能好。
缺点:
- 依赖redis,redis操作增加系统复杂度。
redis课程当然采用的是第三种方案。
jwt的致命缺点就是无法主动踢出用户,个人感觉用户管理最佳方案就是token + redis的方式,遇到瓶颈只需要横向扩展redis即可。
2.4 实现流程和关键代码
2.4.1 发送验证码
要考虑的问题:
-
如何生成验证码?
-
验证码有效期如何实现?
-
如何将手机号和验证码绑定?
-
如何发送短信?
解决:
- 使用
RandomUtil.randomNumbers(6);生成随机六位验证码。 - 使用redis的特性,设置该数据的ttl。
- 将手机号作为key,验证码作为value。
- 购买短信服务。
@Autowired
StringRedisTemplate stringRedisTemplate;@Override
public Result sendCode(String phone, HttpSession session) {// 1. 校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2. 若不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3. 符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4. 保存验证码和手机号到redis, 设置过期时间, 此处LOGIN_CODE_TTL为避免产生魔法值而设置的常量 2LstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5. 发送验证码(此处没钱购买服务,一般可以去各大云购买短信服务调用相应的api)log.debug("发送验证码成功,验证码:{}", code);return Result.ok();
}
2.4.2 短信验证码登录/注册
跟着流程走即可,前端需要保存此处返回的token:
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1. 校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2. 若不符合,返回错误信息return Result.fail("手机号格式错误!");}// 2. 校验验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (StrUtil.isBlank(code) || !code.equals(cacheCode)) {// 3. 不一致 报错return Result.fail("验证码错误");}// 4. 一致 根据手机号查询用户User user = lambdaQuery().eq(User::getPhone, phone).one();// 5. 判断用户是否存在if (user == null) {// 6. 不存在 创建用户user = createUserWithPhone(phone);}// 7. 存在 保存到session/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((name, value) -> value.toString()));// 7.3 存储stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, userMap);stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8. 返回tokenreturn Result.ok(token);
}// 创建用户
private User createUserWithPhone(String phone) {// 1. 创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2. 保存用户save(user);return user;
}
2.4.3 校验登录状态
主要是使用拦截器实现,知识点:
-
拦截无权限请求
-
token自动续签
-
使用ThreadLocal
思路:
一个拦截器用于token续签,一个拦截器用于权限验证。

token续签:
小tips:由于注册拦截器需要手动new,所以我们不使用自动装配的方式来注入StringRedisTemplate,而是使用构造方法在注册拦截器的时候注入。
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. 获取redis中的用户String key = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3. 判断用户是否存在if (userMap.isEmpty()) {return true;}
// UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);UserDTO user = BeanUtil.toBean(userMap, UserDTO.class);// 5. 存在 保存用户信息到ThreadLocalUserHolder.saveUser(user);// 6. 刷新token有效期stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);// 7. 放行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 {if (UserHolder.getUser() == null) {response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);return false;}return true;}
}
拦截器注册:
小tips:
-
在这里自动装配StringRedisTemplate,通过构造函数注入RefreshTokenInterceptor,使代码更优雅。
-
不是所有请求都需要权限验证,所以根据需求设置白名单。
-
使用order来设置拦截器的优先级,order越小,优先级越高,order相同,优先级按注册顺序降低。
@Configuration
public class MvcConfig implements WebMvcConfigurer {@AutowiredStringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {List<String> excludePath = new ArrayList<>();excludePath.add("/user/code");excludePath.add("/user/login");excludePath.add("/blog/hot");excludePath.add("/shop/**");excludePath.add("/shop-type/**");excludePath.add("/upload/**");excludePath.add("/voucher/**");registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(excludePath).order(1);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}
相关文章:
Redis实战—黑马点评(一) 登录篇
Redis实战 — 黑马点评(一) 登录篇 来自黑马的redis课程的笔记 【黑马程序员Redis入门到实战教程,深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】 目录Redis实战 — 黑马点评(一) 登录篇1. 项目…...
建造者模式-搭建Qt窗口案例
文章目录logging日志输出子线程设计模式可视化插件类界面设计呼吸灯实现综合案例实现本综合案例,应用到如下的知识点。logging日志输出 自定义日志记录器,实现将日志输出到指定的控件中。 # 自定义日志记录器类子线程 threading实现子线程及Qt中的子线…...
*from . import _imaging as core : ImportError: DLL load failed: 找不到指定的模块
错误提示如上。为了解决这个问题,首先参考了解决 from . import _imag…模块。. 首先尝试了彻底卸载pillow:conda uninstall pillow ; pip uninstall pillow 然后重装 pip install pillow,发现问题仍然没有解决。 并且尝试了windo…...
关于尚硅谷Hadoop-报错解决方案日志
以后都会将学习Hadoop中遇到的问题写到这里,供自己参考,能帮到大家更好SecondaryNameNode未启动解决办法:可能是端口被占用(我没遇到)hadoop104未在/etc/hosts配置映射路径我在hadoop104的/etc/hosts 添加了所有hadoop…...
前端高频面试题-HTML和CSS篇(二)
💻 前端高频面试题-HTML和CSS篇(二) 🏠专栏:前端面试题 👀个人主页:繁星学编程🍁 🧑个人简介:一个不断提高自我的平凡人🚀 🔊分享方向…...
神经网络损失函数分布可视化神器
论文名称和地址:Visualizing the Loss Landscape of Neural Netshttps://arxiv.org/pdf/1712.09913.pdf1.1 背景和动机作者主要想研究几个问题:为什么我们能够最小化高度非凸神经损失函数?为什么得到的最小值这个结果具有泛化性?不…...
ansible的部署与命令模块
目录 一、ansible的概述 1、ansible简介 2、ansible特点 3、官方网站 4、ansible的模块组成 5、ansible的工作机制 二、ansible部署 1、ansible的安装 三、ansible的命令行模块 1、command模块 2、shell模块 3、cron模块 4、user模块 5、group模块 6、copy模块 7…...
开发人员与测试人员关系的理解
在软件开发中都会有开发人员(以下简称开发)和测试人员(以下简称测试),在一些小型公司可能并没有测试,仅仅是开发兼任测试。在这里我仅针对于有专业的测试和专业的开发的项目。 每个公司应该都有考核机制&am…...
直面原理:5 张图彻底了解 Android TextToSpeech 机制
ChatGPT 如此火爆,但它的强悍在于 NLU(自然语言理解)、DM(对话管理)和 NLG (自然语言生成)这三块,而 Recognition 识别和 TTS 播报这两块是缺失的。假使你的 App 接入了 ChatGPT&…...
Ruby Socket 编程
Ruby提供了两个级别访问网络的服务,在底层你可以访问操作系统,它可以让你实现客户端和服务器为面向连接和无连接协议的基本套接字支持。 Ruby 统一支持应用程序的网络协议,如FTP、HTTP等。 不管是高层的还是底层的。ruby提供了一些基本类&a…...
Vue3+ElementPlus+koa2实现本地图片的上传
一、示例图二、实现过程利用Koa2书写提交图片的后台接口这个模块是我写的项目中的其中一个板块——上传图片,这个项目的后台接口主要是是使用了后端的Koa2框架,前端小伙伴想要试着自己书写一些增删改查的接口可以从这个入手,Koa2用来了解后端…...
常见漏洞之 Fastjson
数据来源 01 Fastjson相关介绍 》Fastjson概述 》Fastjson历史漏洞 02 Fastson的识别与漏洞发现 》Fastjson寻找 》Fastjson漏洞发现(利用 dnslog) 03 修复建议 建议1:使用fastjson1.2.83版本; Github地址:https:…...
绕过Nginx Host限制
目录绕过Nginx Host限制SNI第三种方法:总结绕过Nginx Host限制 SNI SNI(Server Name Indication)是 TLS 的扩展,这允许在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。 作用:用来解决一个服务器拥有…...
Visual Studio 2022 常用快捷键,记录一下别忘记~
Visual Studio 2022 常用快捷键,记录一下别忘记~ CtrlEC 注释代码 CtrlEU 取消注释代码 CtrlED 格式化全部代码 CtrlShiftA 新建类 CtrlRG 删除无效Using CtrlH 批量替换 CtrlG 跳转到指定行 CtrlEE 在交互窗口中运行选中代码(很实用) AltEnter 快速引用 …...
软件测试回顾---重点知识
软件测试重点知识回顾 8.1.1软件测试的目的是 尽可能的发现程序中的错误并不是发现所有的错误并不是证明程序是错误的也不是为了调试程序8.1.2白盒测试根据什么设置测试用例?黑盒测试根据什么设置测试用例? 白盒测试根据内部逻辑来设计的黑盒测试根据的是…...
2D图像处理:2D Shape_Base_Matching_缩放_旋转_ICP_显示ROI
文章目录 调试结果参考调试说明问题0:并行运行问题问题1:模板+Mask大小问题问题2:组合缩放和旋转问题3:可以直接将计算边缘的代码删除问题4:如何在原始图像上显示匹配到的ROI问题5:计算的原始旋转角度不需要判断,直接可以在ICP中使用问题6:绘制坐标轴问题7:绘制ROI调试…...
HTTP、HTTPS
目录 1.HTTP 1.1.概述 1.2.报文结构 1.2.1.请求报文 1.2.2.响应报文 1.3.方法 2.HTTPS 1.HTTP 1.1.概述 HTTP,超文本传输协议,WEB体系选用了该协议作为应用层协议。 1.2.报文结构 1.2.1.请求报文 HTTP的请求报文(request࿰…...
计算机网络之http03:HTTPS RSA握手解析
不同的秘钥交换算法,握手过程可能略有差别 上文对HTTPS四次握手的学习 SSL/TLS Secure Sockets Layer/Transport Layer Security 协议握手过程 四次通信:请求服务端公钥 2次 秘钥协商 2次 (1)ClientHello请求 客户端向服务端发送client…...
一款针对EF Core轻量级分表分库、读写分离的开源项目
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 在项目开发中,如果数据量比较大,比如日志记录,我们往往会采用分表分库的方案;为了提升性能,把数据库查询与更新操作分开,这时候就要采用读写…...
Linux环境变量讲解
目录 环境变量 alias命令 type命令 变量分类 Linux最主要的全局环境变量 环境变量 变量是计算机系统用于保存可变数值的数据类型 在Linux中,一般变量都是大写,命令是小写 在Linux中,变量直接使用,不需要定义(更快…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...
LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
🧠 LangChain 中 TextSplitter 的使用详解:从基础到进阶(附代码) 一、前言 在处理大规模文本数据时,特别是在构建知识库或进行大模型训练与推理时,文本切分(Text Splitting) 是一个…...
Docker、Wsl 打包迁移环境
电脑需要开启wsl2 可以使用wsl -v 查看当前的版本 wsl -v WSL 版本: 2.2.4.0 内核版本: 5.15.153.1-2 WSLg 版本: 1.0.61 MSRDC 版本: 1.2.5326 Direct3D 版本: 1.611.1-81528511 DXCore 版本: 10.0.2609…...
