当前位置: 首页 > news >正文

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 中,在调用此方法效率就提升了。

缓存一般分为:

  1. 查询 User 的时候,尝试从缓存中取值,如果取到直接使用,如果取不到,则进行初始化缓存数据,相当于重构 findUserId 方法
  2. 改变用户数据(头像、密码等):更新缓存数据或者直接删除缓存(一般使用删除,下次请求访问用户重新查询)
  • 从缓存中取值为 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 存储验证码&#xff1a;验证码需要频繁的访问与刷新&#xff0c;对性能要求较高&#xff1b;验证码不需要永…...

Nacos-NacosRule 负载均衡—设置集群使本地服务优先访问

userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 NacosRule 权重计算方法 目录 一、介绍 二、示例&#xff08;案例截图&#xff09; 三、总结 一、介绍 NacosRule是AlibabaNacos自己实现的一个负载均衡策略&…...

软件设计师——信息安全(二)

&#x1f4d1;前言 本文主要是【信息安全】——软件设计师——信息安全的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…...

Unity中实现ShaderToy卡通火(原理实现篇)

文章目录 前言一、我们在片元着色器中&#xff0c;实现卡通火的大体框架1、使用 noise 和 _CUTOFF 判断作为显示火焰的区域2、_CUTOFF &#xff1a; 用于裁剪噪波范围的三角形3、noise getNoise(uv, t); : 噪波函数 二、顺着大体框架依次解析具体实现的功能1、 uv.x * 4.0; : …...

引迈信息-JNPF平台怎么样?值得入手吗?

目录 1.前言 2.引迈低代码怎么样&#xff1f; 3.平台亮点展示 4.引迈产品特点 5.引迈产品技术栈&#xff1a; 1.前言 低代码是近几年比较火的一种应用程序快速开发方式&#xff0c;它能帮助用户在开发软件的过程中大幅减少手工编码量&#xff0c;并通过可视化组件加速应用…...

大数据云计算——使用Prometheus-Operator进行K8s集群监控

大数据云计算——使用Prometheus-Operator进行K8s集群监控 一、 背景 在非operator配置的普罗中我们监控k8s集群都是通过配置configmap进行服务发现和指标拉取。切换到prometheus-operator难免会有些使用问题。不少用户已经习惯底层配置自动发现的方式。当过渡到servicemonit…...

[蓝桥杯刷题]合并区间、最长不连续子序列、最长不重复数组长度

前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录: 成功的关键在于对目标的持久追求。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法 数据库 文章目录 前言合并区间问题&#x1f4d5;现实应用大致思路代码实现代码讲解 最长不连续子序列&a…...

Hazel引擎学习(十二)

我自己维护引擎的github地址在这里&#xff0c;里面加了不少注释&#xff0c;有需要的可以看看 参考视频链接在这里 这是这个系列的最后一篇文章&#xff0c;Cherno也基本停止了Games Engine视频的更新&#xff0c;感觉也差不多了&#xff0c;后续可以基于此项目开发自己想要…...

中文字符串逆序输出

今天碰到这个题&#xff0c;让我逆序输出中文字符串&#xff0c;可给我烦死了&#xff0c;之前没有遇到过&#xff0c;也是查了资料才知道&#xff0c;让我太汗颜了。 英文字符串逆序输出很容易&#xff0c;开辟一块空间用来存放逆序后的字符串&#xff0c;从后往前遍历原字符串…...

MySQL BinLog 数据还原恢复

博文目录 文章目录 查看状态查看 binlog 开关及存储路径查看 binlog 配置 如 存储格式 binlog_format查看当前还存在的日志查看当前正在使用的日志 切换日志确定日志确定日志文件日志格式改写日志简要说明确定日志位置以事件为单位查看日志分析日志 还原数据 查看状态 查看 b…...

理想汽车校招内推--大量hc等你来

投递链接: https://li.jobs.feishu.cn/s/i8BLJE1j 欢迎大家投递...

RabbitMQ死信队列详解

什么是死信队列 由于特定的**原因导致 Queue 中的某些消息无法被消费&#xff0c;**这类消费异常的数据将会保存在死信队列中防止消息丢失&#xff0c;例如用户在商城下单成功并点击支付后&#xff0c;在指定时间未支付时的订单自动失效死信队列只不过是绑定在死信交换机上的队…...

计算机网络:物理层(编码与调制)

今天又学会了一个知识&#xff0c;加油&#xff01; 目录 一、基带信号与宽带信号 1、基带信号 2、宽带信号 3、选择 4、关系 二、数字数据编码为数字信号 1、非归零编码【NRZ】 2、曼彻斯特编码 3、差分曼彻斯特编码 4、归零编码【RZ】 5、反向不归零编码【NRZI】 …...

嵌入式开发板qt gdb调试

1&#xff09; 启动 gdbserver ssh 或者 telnet 登陆扬创平板 192.168.0.253&#xff0c; 进入命令行执行如下&#xff1a; chmod 777 /home/HelloWorld &#xff08;2&#xff09; 打 开 QTcreator->Debug->StartDebugging->Attach to Running Debug Server 进行…...

基于python实现原神那维莱特开转脚本

相信不少原友都抽取了枫丹大C那维莱特&#xff0c;其强力的输出让不少玩家爱不释手。由于其转的越快&#xff0c;越不容易丢伤害的特点&#xff0c;很多原友在开转时容易汗流浃背&#xff0c;所以特意用python写了一个自动转圈脚本&#xff0c;当按住鼠标侧键时&#xff0c;即可…...

C# 实现Lru缓存

C# 实现Lru缓存 LRU 算法全称是最近最少使用算法&#xff08;Least Recently Use&#xff09;&#xff0c;是一种简单的缓存策略。 通常用在对象池等需要频繁获取但是又需要释放不用的地方。 代码实现的基本原理就是使用链表&#xff0c;当某个元素被访问时&#xff08;Get或…...

牛客网BC107矩阵转置

答案&#xff1a; #include <stdio.h> int main() {int n0, m0,i0,j0,a0,b0;int arr1[10][10]{0},arr2[10][10]{0}; //第一个数组用来储存原矩阵&#xff0c;第二个数组用来储存转置矩阵scanf("%d%d",&n,&m); if((n>1&&n<10)&&am…...

协作办公原来如此简单?详解 ONLYOFFICE 协作空间 2.0 更新

协作办公原来如此简单&#xff1f;详解 ONLYOFFICE 协作空间 2.0 更新 上周&#xff0c;ONLYOFFICE 的协作空间推出升级版 2.0 版本了&#xff1a; ONLYOFFICE 协作空间 2.0 现已发布&#xff1a;新增公共房间、插件、重新分配数据、RTL 界面等功能 ONLYOFFICE 协作空间是去…...

2023年国赛高教杯数学建模A题定日镜场的优化设计解题全过程文档及程序

2023年国赛高教杯数学建模 A题 定日镜场的优化设计 原题再现 构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。   定日镜是塔式太阳能光热发电站&#xff08;以下…...

c/c++ 结构体、联合体、枚举

结构体 结构体内存对齐规则&#xff1a; 1、结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处 2、其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处。 对齐数&#xff1a;编译器默认的一个对齐数与该成员变量大小的较小值。 vs 中…...

stl模板库成员函数重载类型混肴编译不通过解决方法

stl模板库成员函数重载类型混肴编译不通过解决方法 这种方式编译不通过IsArithmetic和HasMemberList编译器存在混肴 template <typename T, typename Enable std::enable_if<IsArithmetic<T>::value>::type >static void DumpWrapper(T* filed, std::strin…...

MySQL——表的约束

目录 一.表的约束 二.空属性 ​编辑三.默认值 四.列描述 五.主键 1.主键 2.符合主键 六.自增长 七.唯一键 八.外键 一.表的约束 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&…...

cordic 算法学习记录

参考&#xff1a;b站教学视频FPGA&#xff1a;Cordic算法介绍与实现_哔哩哔哩_bilibili FPGA硬件实现加减法、移位等操作比较简单&#xff0c;但是实现乘除以及函数计算复杂度高且占用资源多&#xff0c;常见的计算三角函数/平方根的求解方式有①查找表&#xff1a;先把函数对应…...

【STM32】电机驱动

一、电机分类 二、直流电机的分类 1.有刷电机 2.无刷电机 3.直流减速电机 三、H桥电路 正向旋转 驱动Q1和Q4 反向旋转 驱动Q2和Q3 四、MC3386电机驱动芯片 1.基本原理图 1&#xff09;前进/后退&#xff1a;IN1和IN2的电平顺序决定电机的正反转 2&#xff09;调节速度&#…...

csp 如此编码 C语言(回归唠嗑版)

熟悉的开篇废话&#xff0c;最近其实在研究那个web开发这一块&#xff0c;导致csp联系就减少了&#xff0c;好久没更csp的帖子了&#xff0c;尽管明天就要考了&#xff0c;但是嘞&#xff0c;能看一道是一道呗对吧。 等过段时间我把web开发这一块整明白了就发帖子&#xff0c;…...

或许是全网最全的延迟队列

什么是延迟队列 作用&#xff1a;用来存储延迟消息延迟消息&#xff1a;生产者发送一个消息给mq&#xff0c;然后mq会经过一段时间&#xff08;延迟时间&#xff09;&#xff0c;然后在把这个消息发送给消费者 应用场景 预定会议后&#xff0c;需要在预定的时间点前十分钟通…...

C语言结构体小项目之通讯录代码实现+代码分析

一、思路 1.文件 这里由于通讯录实现代码较长&#xff0c;因此分三个文件进行&#xff0c;contact.c用于实现通讯录主体代码&#xff0c;声明各项头文件用contact.h实现&#xff0c;测试用test.c 二.功能 增加联系人删除联系人修改联系人查找指定联系人排序显示通讯录的信息…...

tp5 rewrite nginx重写

tp框架,默认的访问路径是 www.xxxx.com/index.php/admin/shop/index格式的&#xff0c;为了方便和更规范&#xff0c;也看起来有逼格一些&#xff0c;需要将index.php去掉 无index.php就会报404 我这里是宝塔 #地址重写if (!-e $request_filename) {rewrite ^(.*)$ /index.…...

.NET 反射优化的经验分享

比如针对 GetCustomAttributes 通过反射获取属性的优化,以下例子 // dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0public class Tests{public object[] GetCustomAttributes() => typeof(C).GetCustomAttributes(typeof(MyAttribute…...

使用opencv的Sobel算子实现图像边缘检测

1 边缘检测介绍 图像边缘检测技术是图像处理和计算机视觉等领域最基本的问题&#xff0c;也是经典的技术难题之一。如何快速、精确地提取图像边缘信息&#xff0c;一直是国内外的研究热点&#xff0c;同时边缘的检测也是图像处理中的一个难题。早期的经典算法包括边缘算子方法…...