12.使用 Redis 优化登陆模块
目录
1. 使用 Redis 优化登陆模块
1.1 使用 Redis 存储验证码
1.2 使用 Redis 存储登录凭证
1.3 使用 Redis 缓存用户信息
1. 使用 Redis 优化登陆模块
- 使用 Redis 存储验证码:验证码需要频繁的访问与刷新,对性能要求较高;验证码不需要永久保存,通常在很短的时间后就会失效;分布式部署时,存在 Session 共享的问题
- 使用 Redis 存储登陆凭证:处理每次请求时,都要查询用户的登陆凭证,访问的频率非常高
- 使用 Redis 缓存用户信息:处理每次请求时,都要根据拼争查询用户信息,访问的频率非常高
1.1 使用 Redis 存储验证码
在 RedisKeyUtil 类中添加:
- 定义验证码的前缀
- 添加登录验证码方法(验证码和用户是相关的,不同的用户验证码不同):给用户登录页面发送凭证(随机生成的字符串),存入 Cookie 中,以字符串临时标识用户,传入字符串(用户临时的凭证),返回 前缀 + 分隔符 + 凭证
private static final String PREFIX_KAPTCHA = "kaptcha";// 登录验证码public static String getKaptchaKey(String owner) {return PREFIX_KAPTCHA + SPLIT + owner;}
验证码在登陆功能中使用(修改 LoginController 类中的生成验证码的方法):
- 重构获取验证码方法:之前是把验证码存入 Session 中,现在使用 Redis
- 将验证码存入 Redis 中:首先构造 key,而 key 需要验证码的归属,这个凭证需要发送给客户端,客户端需要 cookie 保存,创建 Cookie、设置 Cookie 生成时间、有效路径,最后发送给客户端
- 拼接 key,将验证码存入 Redis 中,注入 RedisTemplate
//验证码在登陆功能中使用,重构获取验证码方法:之前是把验证码存入 Session 中,现在使用 Redis//生成验证码的方法@RequestMapping(path = "/kaptcha", method = RequestMethod.GET)public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {// 生成验证码String text = kaptchaProducer.createText();BufferedImage image = kaptchaProducer.createImage(text);// 将验证码存入session//session.setAttribute("kaptcha", text);//将验证码存入 Redis 中:首先构造 key,而 key 需要验证码的归属// 这个凭证需要发送给客户端,客户端需要 cookie 保存,创建 Cookie、设置 Cookie 生成时间、有效路径,最后发送给客户端String kaptchaOwner = CommunityUtil.generateUUID();Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);cookie.setMaxAge(60);cookie.setPath(contextPath);response.addCookie(cookie);//拼接 key, 将验证码存入Redis,注入 RedisTemplateString redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);// 将图片输出给浏览器response.setContentType("image/png");try {OutputStream os = response.getOutputStream();ImageIO.write(image, "png", os);} catch (IOException e) {logger.error("响应验证码失败:" + e.getMessage());}}
首次访问登陆页面,当 getKaptcha 方法被调用,生成验证码存入 Redis 中,在对登陆具体验证的时候使用,因此还需要处理登陆的方法(logic 方法)
- 之前是从 Session 中获取验证码,现在需要从 Redis 中获取(需要 key,而 key 需要验证码的归属,从 Cookie 中获取),因此登陆方法还需要添加注解@CookieValue,从 Cookie 中取值
- 判断 key 是否存在:如果存在,构造 key,然后从 Redis 中获取这个值
//首次访问登陆页面,当getKaptcha方法被调用,生成验证码存入Redis中,在对登陆具体验证的时候使用,因此还需要处理登陆的方法(logic 方法)@RequestMapping(path = "/login", method = RequestMethod.POST)//表单中传入 用户、密码、验证码、记住我(boolean 类型)、Model(返回数据)、HttpSession(页面传入验证码和之前的验证码进行对比)// 、 HttpServletResponse (登录成功,要把 ticket 发送给客户端使用 cookie 保存,创建 cookie 使用 HttpServletResponse 对象)//之前是从 Session 中获取验证码,现在需要从 Redis 中获取(需要 key,而 key 需要验证码的归属,从 Cookie 中获取)// 因此登陆方法还需要添加注解@CookieValue,从 Cookie 中取值public String login(String username, String password, String code, boolean remember, Model model, /*HttpSession session, */ HttpServletResponse response, @CookieValue("kaptchaOwner") String kaptchaOwner) {//检查验证码//String kaptcha = (String) session.getAttribute("kaptcha");String kaptcha = null;//判断 key 是否存在:如果存在,构造 key,然后从 Redis 中获取这个值if (StringUtils.isNotBlank(kaptchaOwner)) {String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);kaptcha = (String) redisTemplate.opsForValue().get(redisKey);}if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {model.addAttribute("codeMsg", "验证码不正确");return "/site/login";}//检查账号、密码:判断账号密码是否正确:没有勾选记住我,存入库中的时间比较短;勾选记住我,存入库中的时间比较长// 定义两个常量放入 CommunityConstant 接口中:如果勾选记住我,使用记住状态时间;如果不勾选,则使用默认的int expiredSeconds = remember ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;Map<String, Object> map = userService.login(username, password, expiredSeconds);//成功:取出 ticket 发送 cookie 给客户端,重定向首页if (map.containsKey("ticket")) {Cookie cookie = new Cookie("ticket", map.get("ticket").toString());//map 中拿到的是对象需要转化为字符串cookie.setPath(contextPath);//有效路径:整个项目但是不要写死,写参数即可cookie.setMaxAge(expiredSeconds);//设置 cookie 有效时间response.addCookie(cookie);//把 cookie 发送到页面return "redirect:/index";} else { //如果登录失败,返回登陆页面//把错误的消息返回给页面model.addAttribute("usernameMsg", map.get("usernameMsg"));model.addAttribute("passwordMsg", map.get("passwordMsg"));return "/site/login";}}
1.2 使用 Redis 存储登录凭证
在 RedisKeyUtil 类中添加:
- 定义登录凭证的前缀
- 添加登录的凭证方法:获得登录凭证的详细数据,传入登录成功的凭证(ticket),返回 前缀 + 分隔符 + 凭证
//定义登录凭证的前缀private static final String PREFIX_TICKET = "ticket";// 添加登录的凭证方法:获得登录凭证的详细数据,传入登录成功的凭证(ticket),返回 前缀 + 分隔符 + 凭证public static String getTicketKey(String ticket) {return PREFIX_TICKET + SPLIT + ticket;}
接下来使用 Redis 存储凭证代替之前的 LoginTicketMapper 类,在此类中添加注解 @Deprecated,表示不推荐使用;重构使用到 Bean 的地方:在
UserService 中使用到(在登录成功以后生成凭证并且保存、退出删除凭证、查询凭证),修改 UserService 中登录功能模块
- 在登录凭证时保存凭证到 Redis 中,拼接 key,注入 RedisTemplate
- 退出的时候,将状态改为1:将 key 传入 Redis 中,返回为一个对象,将状态改为1,再把 key 传回去
- 查询凭证的时候:需要在 Redis 中查找,首先拼接 key,直接取
@Service
public class UserService implements CommunityConstant {@Autowiredprivate RedisTemplate redisTemplate;/*** 实现登录功能*///实现登录功能:成功、失败、不存在等等情况,返回数据的情况很多,可以使用 map 封装多种返回结果//登录需要传入用户、密码、凭证有限时间public Map<String, Object> login(String username, String password, int expiredSeconds) {Map<String, Object> map = new HashMap<>();// 空值处理if (StringUtils.isBlank(username)) {map.put("usernameMsg", "账号不能为空!");return map;}if (StringUtils.isBlank(password)) {map.put("passwordMsg", "密码不能为空!");return map;}// 验证账号User user = userMapper.selectByName(username);if (user == null) {map.put("usernameMsg", "该账号不存在!");return map;}// 验证状态if (user.getStatus() == 0) {map.put("usernameMsg", "该账号未激活!");return map;}// 验证密码password = CommunityUtil.md5(password + user.getSalt());//加密后的密码if (!user.getPassword().equals(password)) {map.put("passwordMsg", "密码不正确!");return map;}// 生成登录凭证LoginTicket loginTicket = new LoginTicket();//创建实体往库里存loginTicket.setUserId(user.getId());loginTicket.setTicket(CommunityUtil.generateUUID());//生成不重复随机字符串loginTicket.setStatus(0);loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));//loginTicketMapper.insertLoginTicket(loginTicket);String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());redisTemplate.opsForValue().set(redisKey, loginTicket);map.put("ticket", loginTicket.getTicket());return map;}//退出业务方法//退出的时候把凭证传入,根据凭证找到用户进行退出,最后改变凭证状态public void logout(String ticket) {//loginTicketMapper.updateStatus(ticket, 1);//退出的时候,将状态改为1:将 key 传入 Redis 中,返回为一个对象,将状态改为1,再把 key 传回去String redisKey = RedisKeyUtil.getTicketKey(ticket);LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);loginTicket.setStatus(1);redisTemplate.opsForValue().set(redisKey, loginTicket);}//添加 查询凭证代码public LoginTicket findLoginTicket(String ticket) {//return loginTicketMapper.selectByTicket(ticket);String redisKey = RedisKeyUtil.getTicketKey(ticket);return (LoginTicket) redisTemplate.opsForValue().get(redisKey);}//更新修改头像路径public int updateHeader(int userId, String headerUrl) {return userMapper.updateHeader(userId, headerUrl);}//修改密码public int updatePassword(int userId,String password){return userMapper.updatePassword(userId,password);}//通过用户名查询用户得到 idpublic User findUserByName(String username) {return userMapper.selectByName(username);}
}
1.3 使用 Redis 缓存用户信息
在 RedisKeyUtil 类中添加:
- 定义用户凭证的前缀
- 添加用户的凭证方法:传入用户 id,返回 前缀 + 分隔符 + 凭证
private static final String PREFIX_USER = "user";// 用户public static String getUserKey(int userId) {return PREFIX_USER + SPLIT + userId;}
缓存数据主要是重构 UserService 中 findUserId 方法:每次请求获取凭证,根据凭证查询用户就需要调用 findUserId 方法,把每个 User 缓存到 Redis 中,在调用此方法效率就提升了。
缓存一般分为:
- 查询 User 的时候,尝试从缓存中取值,如果取到直接使用,如果取不到,则进行初始化缓存数据,相当于重构 findUserId 方法
- 改变用户数据(头像、密码等):更新缓存数据或者直接删除缓存(一般使用删除,下次请求访问用户重新查询)
- 从缓存中取值为 User:传入 UserId,拼接 key;尝试用 Redis 中取值
- 取不到,则进行初始化缓存数据:数据来源于 MySQL,在 MySQL 查询数据,拼接 key,往 Redis 中存储据
- 数据变更时清除缓存数据:拼接 key,删除缓存
- 在 findUserId 方法中调用:首先在缓存中取值,如果为空初始化缓存,最后返回 User
- 在修改 User 的地方进行缓存清除
- 在 activation 方法中修改状态为 1,然后清除缓存
- 在 修改头像路径 updateHeader 方法中:首先更新头像,再去清理缓存
相关文章:
12.使用 Redis 优化登陆模块
目录 1. 使用 Redis 优化登陆模块 1.1 使用 Redis 存储验证码 1.2 使用 Redis 存储登录凭证 1.3 使用 Redis 缓存用户信息 1. 使用 Redis 优化登陆模块 使用 Redis 存储验证码:验证码需要频繁的访问与刷新,对性能要求较高;验证码不需要永…...
Nacos-NacosRule 负载均衡—设置集群使本地服务优先访问
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 NacosRule 权重计算方法 目录 一、介绍 二、示例(案例截图) 三、总结 一、介绍 NacosRule是AlibabaNacos自己实现的一个负载均衡策略&…...
软件设计师——信息安全(二)
📑前言 本文主要是【信息安全】——软件设计师——信息安全的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是听风与他🥇 ☁️博客首页:CSDN主页听风与他 🌄…...
Unity中实现ShaderToy卡通火(原理实现篇)
文章目录 前言一、我们在片元着色器中,实现卡通火的大体框架1、使用 noise 和 _CUTOFF 判断作为显示火焰的区域2、_CUTOFF : 用于裁剪噪波范围的三角形3、noise getNoise(uv, t); : 噪波函数 二、顺着大体框架依次解析具体实现的功能1、 uv.x * 4.0; : …...
引迈信息-JNPF平台怎么样?值得入手吗?
目录 1.前言 2.引迈低代码怎么样? 3.平台亮点展示 4.引迈产品特点 5.引迈产品技术栈: 1.前言 低代码是近几年比较火的一种应用程序快速开发方式,它能帮助用户在开发软件的过程中大幅减少手工编码量,并通过可视化组件加速应用…...
大数据云计算——使用Prometheus-Operator进行K8s集群监控
大数据云计算——使用Prometheus-Operator进行K8s集群监控 一、 背景 在非operator配置的普罗中我们监控k8s集群都是通过配置configmap进行服务发现和指标拉取。切换到prometheus-operator难免会有些使用问题。不少用户已经习惯底层配置自动发现的方式。当过渡到servicemonit…...
[蓝桥杯刷题]合并区间、最长不连续子序列、最长不重复数组长度
前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录: 成功的关键在于对目标的持久追求。 ⭐个人主页:欧_aita ψ(._. )>⭐个人专栏: 数据结构与算法 数据库 文章目录 前言合并区间问题📕现实应用大致思路代码实现代码讲解 最长不连续子序列&a…...
Hazel引擎学习(十二)
我自己维护引擎的github地址在这里,里面加了不少注释,有需要的可以看看 参考视频链接在这里 这是这个系列的最后一篇文章,Cherno也基本停止了Games Engine视频的更新,感觉也差不多了,后续可以基于此项目开发自己想要…...
中文字符串逆序输出
今天碰到这个题,让我逆序输出中文字符串,可给我烦死了,之前没有遇到过,也是查了资料才知道,让我太汗颜了。 英文字符串逆序输出很容易,开辟一块空间用来存放逆序后的字符串,从后往前遍历原字符串…...
MySQL BinLog 数据还原恢复
博文目录 文章目录 查看状态查看 binlog 开关及存储路径查看 binlog 配置 如 存储格式 binlog_format查看当前还存在的日志查看当前正在使用的日志 切换日志确定日志确定日志文件日志格式改写日志简要说明确定日志位置以事件为单位查看日志分析日志 还原数据 查看状态 查看 b…...
理想汽车校招内推--大量hc等你来
投递链接: https://li.jobs.feishu.cn/s/i8BLJE1j 欢迎大家投递...
RabbitMQ死信队列详解
什么是死信队列 由于特定的**原因导致 Queue 中的某些消息无法被消费,**这类消费异常的数据将会保存在死信队列中防止消息丢失,例如用户在商城下单成功并点击支付后,在指定时间未支付时的订单自动失效死信队列只不过是绑定在死信交换机上的队…...
计算机网络:物理层(编码与调制)
今天又学会了一个知识,加油! 目录 一、基带信号与宽带信号 1、基带信号 2、宽带信号 3、选择 4、关系 二、数字数据编码为数字信号 1、非归零编码【NRZ】 2、曼彻斯特编码 3、差分曼彻斯特编码 4、归零编码【RZ】 5、反向不归零编码【NRZI】 …...
嵌入式开发板qt gdb调试
1) 启动 gdbserver ssh 或者 telnet 登陆扬创平板 192.168.0.253, 进入命令行执行如下: chmod 777 /home/HelloWorld (2) 打 开 QTcreator->Debug->StartDebugging->Attach to Running Debug Server 进行…...
基于python实现原神那维莱特开转脚本
相信不少原友都抽取了枫丹大C那维莱特,其强力的输出让不少玩家爱不释手。由于其转的越快,越不容易丢伤害的特点,很多原友在开转时容易汗流浃背,所以特意用python写了一个自动转圈脚本,当按住鼠标侧键时,即可…...
C# 实现Lru缓存
C# 实现Lru缓存 LRU 算法全称是最近最少使用算法(Least Recently Use),是一种简单的缓存策略。 通常用在对象池等需要频繁获取但是又需要释放不用的地方。 代码实现的基本原理就是使用链表,当某个元素被访问时(Get或…...
牛客网BC107矩阵转置
答案: #include <stdio.h> int main() {int n0, m0,i0,j0,a0,b0;int arr1[10][10]{0},arr2[10][10]{0}; //第一个数组用来储存原矩阵,第二个数组用来储存转置矩阵scanf("%d%d",&n,&m); if((n>1&&n<10)&&am…...
协作办公原来如此简单?详解 ONLYOFFICE 协作空间 2.0 更新
协作办公原来如此简单?详解 ONLYOFFICE 协作空间 2.0 更新 上周,ONLYOFFICE 的协作空间推出升级版 2.0 版本了: ONLYOFFICE 协作空间 2.0 现已发布:新增公共房间、插件、重新分配数据、RTL 界面等功能 ONLYOFFICE 协作空间是去…...
2023年国赛高教杯数学建模A题定日镜场的优化设计解题全过程文档及程序
2023年国赛高教杯数学建模 A题 定日镜场的优化设计 原题再现 构建以新能源为主体的新型电力系统,是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。 定日镜是塔式太阳能光热发电站(以下…...
c/c++ 结构体、联合体、枚举
结构体 结构体内存对齐规则: 1、结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处 2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数:编译器默认的一个对齐数与该成员变量大小的较小值。 vs 中…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
