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 方法中:首先更新頭像,再去清理緩存

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/215015.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/215015.shtml
英文地址,請注明出處:http://en.pswp.cn/news/215015.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【計算機網絡】序列化,反序列化和初識協議

目錄 ?編輯 一、概念 二、 序列化過程&#xff1a; 選擇序列化格式&#xff1a; 實現序列化代碼&#xff1a; JSON示例&#xff1a; Protocol Buffers示例&#xff1a; JSON編碼示例&#xff1a; 傳輸或存儲&#xff1a; 三、反序列化過程&#xff1a; 下面是反序列…

web前端之中文輸入法導致的高頻事件、addEventListener、compositionstart、compositionend

MENU 代碼compositionendcompositionstartaddEventListener 代碼 html <input type"text" />JavaScript var inp document.querySelector(input); let isComposing false;function search() {if (isComposing) return false;console.log(搜索: , inp.valu…

(企業 / 公司項目) 企業項目如何使用jwt?

按照企業的項目然后寫的小demo&#xff0c; 自己搞一個登錄接口然后調用jwtUtil工具類 后端實現 創建一個通用模塊common來實現jwt生成token 登錄注冊的基本實現邏輯思路 面試| ProcessOn免費在線作圖,在線流程圖,在線思維導圖 注釋挺詳細的jwtUtil工具類&#xff0c; 封裝的…

WPF仿網易云搭建筆記(5):信息流控制之IOC容器

文章目錄 專欄和Gitee倉庫前言IOC容器Prism IOC使用聲明兩個測試的服務類MainWindow IOC 注入[單例]MainWindow里面獲取UserController無法使用官方解決方案 使用自定義IOC容器&#xff0c;完美解決既然Prism不好用&#xff0c;直接上微軟的IOC解決方案App.xaml.csViewModel里面…

網絡測試工具:tcping-測試端口連接

網絡測試工具&#xff1a;tcping-測試端口連接 平常使用的ping&#xff0c;是通過icmp協議去測試網絡連通性的&#xff0c;tcping是通過tcp三次握手測試端口的連通性。總的來說&#xff0c;ping測試的是L3的連通性&#xff0c;tcping測試的是L4的連通性。 tcping工具下載 htt…

10.RIP路由信息協議

10.RIP 網段經常產生變化的話&#xff0c;建議使用動態路由協議&#xff0c;當網段發生變化的時候會自動通告給其他路由器 它不看鏈路的帶寬&#xff0c;只看鏈路中的跳數&#xff0c;只要是跳數多的&#xff0c;不管帶寬有多大&#xff0c;它就認為是不好的 RIP跳數有限 …

java中LinkedList和List繼承有什么區別?

在Java中&#xff0c;LinkedList 和 List 是兩個不同的概念。List 是一個接口&#xff0c;而 LinkedList 是實現了 List 接口的一個具體類。 List 接口&#xff1a; List 是Java集合框架中的一個接口&#xff0c;它表示有序的集合&#xff0c;允許重復元素。List 接口繼承自 C…

TYPE-C接口設備實現DRP+OTG功能芯片

隨著USB-C接口的普及&#xff0c;歐盟的法律法規強制越來越多的設備開始采用這種接口。由于 USB-C接口的高效性和便攜性&#xff0c;使各種設備之間的連接和數據傳輸變得非常方便快捷&#xff0c;它們不僅提供了強大的功能&#xff0c;還為我們的日常生活和工作帶來了極大的便利…

青少年CTF-Misc(持續更新中)

FLAG&#xff1a;當覺得自己很菜的時候&#xff0c;就靜下心來學習 專研方向:Web安全&#xff0c;CTF 每日emo&#xff1a;聽一千遍反方向的鐘&#xff0c;我們能回到過去嗎&#xff1f; 1.StegoTXT&#xff1a; 解壓縮文件。發現字母中存在覆蓋。使用0寬隱寫在線解密得到flag…

YOLOv8改進 | 2023主干篇 | EfficientViT替換Backbone(高效的視覺變換網絡)

一、本文介紹 本文給大家帶來的改進機制是EfficientViT&#xff08;高效的視覺變換網絡&#xff09;&#xff0c;EfficientViT的核心是一種輕量級的多尺度線性注意力模塊&#xff0c;能夠在只使用硬件高效操作的情況下實現全局感受野和多尺度學習。本文帶來是2023年的最新版本…

?sqlite3 --- SQLite 數據庫 DB-API 2.0 接口模塊?

源代碼&#xff1a; Lib/sqlite3/ SQLite 是一個C語言庫&#xff0c;它可以提供一種輕量級的基于磁盤的數據庫&#xff0c;這種數據庫不需要獨立的服務器進程&#xff0c;也允許需要使用一種非標準的 SQL 查詢語言來訪問它。一些應用程序可以使用 SQLite 作為內部數據存儲。可…

量子算力引領未來!玻色量子出席第二屆CCF量子計算大會

?8月19日至20日&#xff0c;中國計算機學會&#xff08;CCF&#xff09;主辦的第二屆CCF量子計算大會暨中國量子計算峰會&#xff08;CQCC 2023&#xff09;在中國合肥成功舉辦。本屆大會以“量超融合&#xff0c;大國算力”為主題&#xff0c;設有量子計算軟件、硬件、應用生…

【C++類模板對象做函數參數】

類模板實例化出的對象&#xff0c;向函數傳參的方式 一共有三種傳入方式&#xff1a; 指定傳入的類型 — 直接顯示對象的數據類型參數模板化 — 將對象中的參數變為模板進行傳遞整個類模板化 — 將這個對象類型 模板化進行傳遞 總結&#xff1a;比較廣泛使用的是第一種&…

計算機網絡(三)

&#xff08;十一&#xff09;路由算法 A、路由算法分類 動態路由和靜態路由 靜態路由&#xff1a;人工配制&#xff0c;路由信息更新慢&#xff0c;優先級高。這種在實際網絡中要投入成本大&#xff0c;準確但是可行性弱。 動態路由&#xff1a;路由更新快&#xff0c;自動…

12/11

完善對話框&#xff0c;點擊登錄對話框&#xff0c;如果賬號和密碼匹配&#xff0c;則彈出信息對話框&#xff0c;給出提示”登錄成功“&#xff0c;提供一個Ok按鈕&#xff0c;用戶點擊Ok后&#xff0c;關閉登錄界面&#xff0c;跳轉到其他界面 如果賬號和密碼不匹配&#xf…

css的Grid布局

1.簡單布局 .grid { display: grid; grid-template-columns: 1fr 2fr 1fr; 布局樣式 column-gap: 24px; 列間距 row-gap: 24px; 行間距 } 2.排列布局 center垂直方向居中對其 end靠下對齊 3.水平方向對齊 center居中 end靠右對齊 space-between兩段對齊 4.對…

【SpringBoot】Spring Boot 單體應用升級 Spring Cloud 微服務

Spring Cloud 是在 Spring Boot 之上構建的一套微服務生態體系&#xff0c;包括服務發現、配置中心、限流降級、分布式事務、異步消息等&#xff0c;因此通過增加依賴、注解等簡單的四步即可完成 Spring Boot 應用到 Spring Cloud 升級。 Spring Boot 應用升級為 Spring Cloud…

插入排序和希爾排序

目錄 前言 一.插入排序 1.思想 2.實現 3.特點 二,希爾排序 1.思想 2,實現 3.特點 前言 排序算法是計算機科學中的基礎工具之一&#xff0c;對于數據處理和算法設計有著深遠的影響。了解不同排序算法的特性和適用場景&#xff0c;能夠幫助程序員在特定情況下選擇最合適的…

如何使用玻璃材質制作3D鉆石模型

在線工具推薦&#xff1a; 3D數字孿生場景編輯器 - GLTF/GLB材質紋理編輯器 - 3D模型在線轉換 - Three.js AI自動紋理開發包 - YOLO 虛幻合成數據生成器 - 三維模型預覽圖生成器 - 3D模型語義搜索引擎 當談到游戲角色的3D模型風格時&#xff0c;有幾種不同的風格&#xf…

Spark與PySpark(1.概述、框架、模塊)

目錄 1.Spark 概念 2. Hadoop和Spark的對比 3. Spark特點 3.1 運行速度快 3.2 簡單易用 3.3 通用性強 3.4 可以允許運行在很多地方 4. Spark框架模塊 4.1 Spark Core 4.2 SparkSQL 4.3 SparkStreaming 4.4 MLlib 4.5 GraphX 5. Spark的運行模式 5.1 本地模式(單機) Local運行模…