黑馬點評【基于redis實現共享session登錄】

目錄

一、基于Session實現登錄流程

1.發送驗證碼:

2.短信驗證碼登錄、注冊:

3.校驗登錄狀態:

4.session共享問題

4.1為什么會出現 Session 集群共享問題?

4.2常見解決方案

1. 基于 Cookie 的 Session(客戶端存儲)

2. Session 復制(服務器間同步)

3. 集中式 Session 存儲(推薦方案)

二、基于redis實現共享session登錄

1.選擇合適的存儲結構

2.修改發送短信的邏輯

3.解決狀態登錄刷新問題


一、基于Session實現登錄流程

1.發送驗證碼:

用戶在提交手機號后,會校驗手機號是否合法,如果不合法,則要求用戶重新輸入手機號

如果手機號合法,后臺此時生成對應的驗證碼,同時將驗證碼進行保存,然后再通過短信的方式將驗證碼發送給用戶

    @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();}

2.短信驗證碼登錄、注冊:

用戶將驗證碼和手機號進行輸入,后臺從session中拿到當前驗證碼,然后和用戶輸入的驗證碼進行校驗,如果不一致,則無法通過校驗,如果一致,則后臺根據手機號查詢用戶,如果用戶不存在,則為用戶創建賬號信息,保存到數據庫,無論是否存在,都會將用戶信息保存到session中,方便后續獲得當前登錄信息

    @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗手機號String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.校驗驗證碼Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode == null || !cacheCode.toString().equals(code)){//3.不一致,報錯return Result.fail("驗證碼錯誤");}//一致,根據手機號查詢用戶User user = query().eq("phone", phone).one();//5.判斷用戶是否存在if(user == null){//不存在,則創建user =  createUserWithPhone(phone);}//7.保存用戶信息到session中session.setAttribute("user",user);return Result.ok();}

3.校驗登錄狀態:

用戶在請求時候,會從cookie中攜帶者JsessionId到后臺,后臺通過JsessionId從session中拿到用戶信息,如果沒有session信息,則進行攔截,如果有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((User)user);//6.放行return true;}
}

讓攔截器生效

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登錄攔截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);// token刷新的攔截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

4.session共享問題

在分布式系統或集群環境中,Session 共享問題是指多個服務器節點之間如何共享用戶會話(Session)數據,以確保用戶在不同節點間切換時會話不丟失、狀態一致。

4.1為什么會出現 Session 集群共享問題?

  1. 無狀態協議特性
    HTTP 是無狀態協議,服務器通過 Session 機制維持用戶狀態(如登錄信息、購物車數據等)。在單體應用中,Session 通常存儲在服務器內存中。
    但在集群 / 分布式架構中,用戶請求可能被負載均衡器分配到不同節點,若節點間無法共享 Session,會導致:

    • 用戶重復登錄
    • 會話數據丟失(如購物車內容丟失)
    • 業務邏輯異常(如分布式事務狀態不一致)
  2. 集群節點的獨立性
    每個節點的內存獨立,無法直接訪問其他節點的 Session 數據。

4.2常見解決方案

1. 基于 Cookie 的 Session(客戶端存儲)
  • 原理:將 Session 數據序列化后存儲在客戶端 Cookie 中,每次請求攜帶 Cookie,服務器從 Cookie 中讀取 Session。
  • 優點
    • 無需服務器間共享數據,完全分布式
    • 減輕服務器存儲壓力
  • 缺點
    • 安全性差(敏感數據需加密,否則易被篡改)
    • Cookie 大小受限(通常不超過 4KB)
    • 不適用于大量 Session 數據

適用場景:輕量級應用、非敏感數據場景。

2. Session 復制(服務器間同步)
  • 原理:多個節點之間通過網絡實時復制 Session 數據,確保每個節點擁有全量 Session 副本。
  • 優點
    • 透明性高,應用無需修改代碼
    • 適用于中小型集群
  • 缺點
    • 性能開銷大(Session 變更時需全量同步,節點越多延遲越高)
    • 內存占用高(每個節點存儲所有 Session)
    • 擴展性差,不適合大規模集群

適用場景:節點較少(≤5 個)、Session 更新不頻繁的場景。

3. 集中式 Session 存儲(推薦方案)

將 Session 數據存儲在獨立的第三方存儲系統中(如 Redis、Memcached、數據庫),所有節點通過訪問統一存儲獲取 Session。

  • 優點
    • 高性能(內存存儲,Redis 支持集群和持久化)
    • 可擴展性強(支持動態擴縮容)
    • 數據安全(可配置密碼、加密傳輸)
  • 缺點
    • 需要額外維護緩存集群
    • 存在緩存穿透、緩存雪崩風險(需通過布隆過濾器、限流等機制防范)

適用場景:大型分布式系統、高并發場景。

這里我們使用redis

二、基于redis實現共享session登錄

1.選擇合適的存儲結構

首先我們要思考一下利用redis來存儲數據,那么到底使用哪種結構呢?由于存入的數據比較簡單,我們可以考慮使用String,或者是使用哈希,如上圖,如果使用String,注意他的value,用多占用一點空間,如果使用哈希,則他的value中只會存儲他數據本身,如果不是特別在意內存,其實使用String就可以啦。

String 結構(以 JSON 字符串存用戶信息)

優點

  • 存儲直觀易懂:直接將整個用戶對象序列化為 JSON 字符串存到單個 key - value 中,業務側讀取時反序列化就能拿到完整用戶信息,開發調試時查看數據很方便,比如快速通過?get heima:user:1?命令看到?{name:"Jack", age:21}?全貌 。
  • 整體讀寫高效:存和取都是針對整個用戶對象操作,Redis 處理單 key 的讀寫命令(如?SET/GET?)本身就很高效,對于頻繁需要完整用戶信息的場景,一次操作就能滿足需求 。

缺點

  • 局部更新成本高:若只想修改用戶的?age?字段,得先?GET?整個 JSON 字符串,反序列化成對象改字段,再序列化成 JSON 存回 Redis,多了序列化 / 反序列化以及全量寫的開銷,操作不靈活 。
  • 內存占用相對高:每個用戶對象要存完整的 JSON 字符串,包含字段名、分隔符等冗余信息,大量用戶數據存儲時,內存浪費會更明顯 。

Hash 結構(字段獨立存儲)

優點

  • 操作靈活性強:支持對單個字段 CRUD,比如只想改?age,直接執行?HSET heima:user:1 age 22?即可,無需處理整個對象的序列化 / 反序列化,更新局部字段效率高 。
  • 內存更節省:只存字段名和對應值,沒有額外的 JSON 格式冗余(如分隔符、字段名重復存儲),相同用戶數據量下,Hash 結構內存占用通常更少,適合大規模用戶數據存儲

缺點

  • 數據查看稍復雜:想看完整用戶信息,得用?HGETALL?命令獲取所有字段再拼接,不像 String 結構?GET?命令直接拿到直觀的 JSON 結果,對調試不太友好 。
  • 批量操作有局限:若業務經常需要一次性獲取 / 設置整個用戶對象的多個字段,Hash 得多次調用?HSET/HGET?,相比 String 結構一次?SET/GET?,在這類場景下效率可能稍低 。

2.修改發送短信的邏輯

現在驗證碼要存入redis,用于之后的登錄注冊

發送驗證碼:

@Overridepublic Result sendCode(String phone) {// 1 校驗手機號if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手機號格式不合法");}// 2 符合,生成驗證碼,不符合返回String code = RandomUtil.randomNumbers(6);// 3 保存驗證碼到sessionstringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone,code,RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);// 4 發送驗證碼log.info("發送驗證碼成功:{}",code);return Result.ok();}

登錄注冊:

@Overridepublic Result login(LoginFormDTO loginForm) {String phone = loginForm.getPhone();// 1 校驗手機號if(RegexUtils.isPhoneInvalid(loginForm.getPhone())){return Result.fail("手機號格式不合法");}// 2 校驗驗證碼String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if(cacheCode == null || !cacheCode.equals(code)){return Result.fail("驗證碼錯誤");}// 3 一致,根據用戶手機號查詢用戶User user = query().eq("phone", phone).one();if(user == null){user = createUserwithPhone(phone);}UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);Map<String, Object> userDtomap = BeanUtil.beanToMap(userDTO,new HashMap<>() ,CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));// 4 生成tokenString token = UUID.randomUUID().toString();stringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token,userDtomap);stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);return Result.ok(token);}

當注冊完成后,用戶去登錄會去校驗用戶提交的手機號和驗證碼,是否一致,如果一致,則根據手機號查詢用戶信息,不存在則新建,最后將用戶數據保存到redis,并且生成token作為redis的key,當我們校驗用戶是否登錄時,會去攜帶著token進行訪問,從redis中取出token對應的value,判斷是否存在這個數據,如果沒有則攔截,如果存在則將其保存到threadLocal中,并且放行。

3.解決狀態登錄刷新問題

在這個方案中,他確實可以使用對應路徑的攔截,同時刷新登錄token令牌的存活時間,但是現在這個攔截器他只是攔截需要被攔截的路徑,假設當前用戶訪問了一些不需要攔截的路徑,那么這個攔截器就不會生效,所以此時令牌刷新的動作實際上就不會執行,所以這個方案他是存在問題的

優化方案

既然之前的攔截器無法對不需要攔截的路徑生效,那么我們可以添加一個攔截器,在第一個攔截器中攔截所有的路徑,把第二個攔截器做的事情放入到第一個攔截器中,同時刷新令牌,因為第一個攔截器有了threadLocal的數據,所以此時第二個攔截器只需要判斷攔截器中的user對象是否存在即可,完成整體刷新功能。

RefreshTokenInterceptor:

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.基于TOKEN獲取redis中的用戶String key  = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判斷用戶是否存在if (userMap.isEmpty()) {return true;}// 5.將查詢到的hash數據轉為UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用戶信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用戶UserHolder.removeUser();}
}

LoginInterceptor:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判斷是否需要攔截(ThreadLocal中是否有用戶)if (UserHolder.getUser() == null) {// 沒有,需要攔截,設置狀態碼response.setStatus(401);// 攔截return false;}// 有用戶,則放行return true;}
}

看到這如果有用的話記得點贊關注哦,后續會更新更多內容的!!

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

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

相關文章

Python讀取阿里法拍網的html+解決登錄cookie

效果圖 import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from lxml import etreedef get_taobao_auct…

【win | docker開啟遠程配置】使用 SSH 隧道訪問 Docker的前操作

在主機A pycharm如何連接遠程主機B win docker? 需要win docker配置什么&#xff1f; 快捷配置-主機B win OpenSSH SSH Server https://blog.csdn.net/z164470/article/details/121683333 winR,打開命令行&#xff0c;輸入net start sshd,啟動SSH。 或者右擊我的電腦&#…

Cursor生成Java的架構設計圖

文章目錄 整體說明一、背景二、前置條件三、生成 Promt四、結果查看五、結果編輯 摘要&#xff1a; Cursor生成Java的架構設計圖 關鍵詞&#xff1a; Cursor、人工智能 、開發工具、Java 架構設計圖 整體說明 Cursor 作為現在非常好用的開發工具&#xff0c;非常的火爆&#…

1Panel運行的.net程序無法讀取系統字體(因為使用了docker)

問題來源 我之前都是使用的寶塔面板&#xff0c;之前我也部署過我的程序&#xff0c;就沒有什么問題&#xff0c;但是上次我部署我的程序的時候&#xff0c;就提示無法找到字體Arial。 我的程序中使用該字體生成驗證碼。 我多次安裝了微軟的字體包&#xff0c;但是依舊沒有效…

面試總結。

一、回流&#xff08;重排&#xff09;與重繪&#xff08;Repaint&#xff09; 優化回答&#xff1a; 概念區分&#xff1a; 回流&#xff08;Reflow/Relayout&#xff09;&#xff1a;當元素的幾何屬性&#xff08;如寬高、位置、隱藏 / 顯示&#xff09;發生改變時&#xff…

TensorFlow深度學習實戰(20)——自組織映射詳解

TensorFlow深度學習實戰&#xff08;20&#xff09;——自組織映射詳解 0. 前言1. 自組織映射原理2. 自組織映射的優缺點3. 使用自組織映射實現顏色映射小結系列鏈接 0. 前言 自組織映射 (Self-Organizing Map, SOM) 是一種無監督學習算法&#xff0c;主要用于高維數據的降維、…

Go內存泄漏排查與修復最佳實踐

一、引言 即使Go語言擁有強大的垃圾回收機制&#xff0c;內存泄漏仍然是我們在生產環境中經常面臨的挑戰。與傳統印象不同&#xff0c;垃圾回收并不是萬能的"記憶清道夫"&#xff0c;它只能處理那些不再被引用的內存&#xff0c;而無法識別那些仍被引用但實際上不再…

LeetCode刷題 -- 542. 01矩陣 基于 DFS 更新優化的多源最短路徑實現

LeetCode刷題 – 542. 01矩陣 基于 DFS 更新優化的多源最短路徑實現 題目描述簡述 給定一個 m x n 的二進制矩陣 mat&#xff0c;其中&#xff1a; 每個元素為 0 或 1返回一個同樣大小的矩陣 ans&#xff0c;其中 ans[i][j] 表示 mat[i][j] 到最近 0 的最短曼哈頓距離 算法思…

MySQL用戶遠程訪問權限設置

mysql相關指令 一. MySQL給用戶添加遠程訪問權限1. 創建或者修改用戶權限方法一&#xff1a;創建用戶并授予遠程訪問權限方法二&#xff1a;修改現有用戶的訪問限制方法三&#xff1a;授予特定數據庫的特定權限 2. 修改 MySQL 配置文件3. 安全最佳實踐4. 測試遠程連接5. 撤銷權…

如何使用 BPF 分析 Linux 內存泄漏,Linux 性能調優之 BPF 分析內核態、用戶態內存泄漏

寫在前面 博文內容為 通過 BCC 工具集 memleak 進行內存泄漏分析的簡單認知包括 memleak 腳本簡單認知,內核態(內核模塊)、用戶態(Java,Python,C)內存跟蹤泄漏分析 Demo理解不足小伙伴幫忙指正 ??,生活加油知其不可奈何而安之若命,德之至也。----《莊子內篇人間世》 …

谷歌Sign Gemma: AI手語翻譯,溝通從此無界!

嘿&#xff0c;朋友們&#xff01;想象一下&#xff0c;語言不再是交流的障礙&#xff0c;每個人都能順暢表達與理解。這聽起來是不是很酷&#xff1f;谷歌最新發布的Sign Gemma AI模型&#xff0c;正朝著這個激動人心的未來邁出了一大步&#xff01;它就像一位隨身的、不知疲倦…

全生命周期的智慧城市管理

前言 全生命周期的智慧城市管理。未來&#xff0c;城市將在 實現從基礎設施建設、日常運營到數據管理的 全生命周期統籌。這將避免過去智慧城市建設 中出現的“碎片化”問題&#xff0c;實現資源的高效配 置和項目的協調發展。城市管理者將運用先進 的信息技術&#xff0c;如物…

最新Spring Security實戰教程(十七)企業級安全方案設計 - 多因素認證(MFA)實現

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有堅忍不拔之志 &#x1f390; 個人CSND主頁——Micro麥可樂的博客 &#x1f425;《Docker實操教程》專欄以最新的Centos版本為基礎進行Docker實操教程&#xff0c;入門到實戰 &#x1f33a;《RabbitMQ》…

logstash拉取redisStream的流數據,并存儲ES

先說結論&#xff0c; window驗證logstash截至2025-06-06 是沒有原生支持的。 為啥考慮用redisStream呢&#xff1f;因為不想引入三方的kafka等組件&#xff0c; 讓服務部署輕量化&#xff0c; 所以使用現有的redis來實現&#xff0c; 為啥不用list呢&#xff1f; 已經用strea…

IEC 61347-1:2015 燈控制裝置安全通用要求詳解

IEC 61347-1:2015 燈控制裝置安全通用要求詳解 IEC 61347-1:2015《燈控制裝置 第1部分&#xff1a;一般要求和安全要求》是國際電工委員會&#xff08;IEC&#xff09;制定的關于燈控制裝置安全性能的核心基礎標準。它為各類用于啟動和穩定工作電流的燈控制裝置&#xff08;如…

26、跳表

在C標準庫中&#xff0c;std::map 和 std::set 是使用紅黑樹作為底層數據結構的容器。 紅黑樹是一種自平衡二叉搜索樹&#xff0c;能夠保證插入、刪除和查找操作的時間復雜度為O(log n)。 以下是一些使用紅黑樹的C標準庫容器&#xff1a; std::map&#xff1a;一種關聯容器&a…

LabVIEW音頻測試分析

LabVIEW通過讀取指定WAV 文件&#xff0c;實現對音頻信號的播放、多維度測量分析功能&#xff0c;為音頻設備研發、聲學研究及質量檢測提供專業工具支持。 主要功能 文件讀取與播放&#xff1a;支持持續讀取示例數據文件夾內的 WAV 文件&#xff0c;可實時播放音頻以監聽被測信…

JUC并發編程(二)Monitor/自旋/輕量級/鎖膨脹/wait/notify/鎖消除

目錄 一 基礎 1 概念 2 賣票問題 3 轉賬問題 二 鎖機制與優化策略 0 Monitor 1 輕量級鎖 2 鎖膨脹 3 自旋 4 偏向鎖 5 鎖消除 6 wait /notify 7 sleep與wait的對比 8 join原理 一 基礎 1 概念 臨界區 一段代碼塊內如果存在對共享資源的多線程讀寫操作&#xf…

Doris 與 Elasticsearch:誰更適合你的數據分析需求?

一、Doris 和 Elasticsearch 的基本概念 &#xff08;一&#xff09;Doris 是什么&#xff1f; Doris 是一個用于數據分析的分布式 MPP&#xff08;大規模并行處理&#xff09;數據庫。它主要用于存儲和分析大量的結構化數據&#xff08;比如表格數據&#xff09;&#xff0c…

使用Virtual Serial Port Driver+com2tcp(tcp2com)進行兩臺電腦的串口通訊

使用Virtual Serial Port Drivercom2tcp或tcp2com進行兩臺電腦的串口通訊 問題說明解決方案方案三具體操作流程網上教程軟件安裝拓撲圖準備工作com2tcp和tcp2com操作使用串口助手進行驗證 方案三存在的問題數據錯誤通訊延時 問題說明 最近想進行串口通訊的一個測試&#xff0c…