【 Redis | 實戰篇 短信登錄 】

前言:

主要完成了基于Session實現登錄,解決集群的Session共享問題,從而實現了基于Redis來實現共享Session登錄

1.基于Session實現登錄

1.1.發送短信驗證碼

步驟:

前端提交手機號

==》校驗手機號

==》不符合返回錯誤信息,符合生成驗證碼

==》保存驗證碼到Session(應該保存手機號與驗證碼)

==》發送驗證碼

==》返回數據

?

?解釋:我們這個是以手機號為唯一標識,前端提交用戶輸入的手機號,后端校驗手機號的格式,符合就生成一個隨機6位的數字驗證碼,并保存到Session中為了后續登錄與注冊時校驗用戶驗證碼是否填寫正確)(當然這里應該保存手機號與驗證碼,不然會有一個錯誤)

1.2.短信驗證碼實現登錄與注冊

步驟:

前端提交手機號和驗證碼

==》校驗手機號和驗證碼

==》不通過返回錯誤信息,通過根據手機號查詢用戶信息

==》判斷用戶是否存在

==》不存在,創建新用戶,并且保存到數據庫中

==》最終存在與不存在都將保存用戶到Session中(方便后續校驗登錄狀態)

==》結束

?

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//獲取緩存數據Object catchPhone = session.getAttribute("phone");Object catchCode = session.getAttribute("code");//獲取登錄數據String code = loginForm.getCode();String phone = loginForm.getPhone();//校驗if(!code.equals(catchCode) || !phone.equals(catchPhone)){return Result.fail("手機號或驗證碼錯誤");}
//        if (RegexUtils.isPhoneInvalid(phone)) {
//            return Result.fail("手機號格式錯誤");
//        }
//        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
//        if(!code.equals(cacheCode)){
//            return Result.fail("驗證碼錯誤");
//        }User user = query().eq("phone", phone).one();//判斷用戶是否存在if(user == null){//不存在User userNew = new User();userNew.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));userNew.setPhone(phone);user = userNew;//保存數據庫save(user);}//添加到SessionUserDTO userDTO = new UserDTO();BeanUtil.copyProperties(user,userDTO);
//        String token = UUID.randomUUID().toString(true);
//        String tokenKey = LOGIN_USER_KEY + token;
//        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
//                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
//        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);session.setAttribute("user",userDTO);
//       stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);return Result.ok();}

解釋: 前端提交用戶輸入的手機號與驗證碼,后端從Session中取出存入的手機號與驗證碼與用戶提交的相對比,相同那么我們就通過手機號來從數據庫中查詢用戶信息,信息為空,那么我們需要注冊一個用戶并將其存入數據庫中,最終存在與不存在的(我們自己注冊了一個用戶)都會有一個用戶信息,將用戶信息存入Session中(這里還有個細節)(方便后續校驗登錄狀態,判斷用戶是否登錄)

?注意:

老師有一個錯誤,在發送短信驗證碼的功能實現時,老師只保存了驗證碼到Session中,那么等到校驗驗證碼來實現登錄與注冊時,如果我將手機號修改了會怎么樣,只要我手機號符合格式一樣可以登錄與注冊,所以我們需要保持前后手機號的一致性,那么存入Session的數據應該是驗證碼與手機號,然后登錄與注冊時同時校驗手機號與驗證碼是否一致

1.3.校驗登陸狀態

步驟:

前端請求攜帶Cokie

==》后端攔截器從Session中獲取用戶信息

==》判斷用戶是否存在

==》不存在不放行,存在放行

?

public class RefreshTokenInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//獲取請求頭tokenString token = request.getHeader("authorization");//判斷是否為空if (StrUtil.isBlank(token)) {return true;}
//        HttpSession session = request.getSession();//設置keyString key = LOGIN_USER_KEY + token;//獲取Redis中的數據Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);//判斷是否為空if(userMap.isEmpty()){return true;}//獲取userUserDTO user = BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);//存入線程中UserHolder.saveUser( user);stringRedisTemplate.expire(key,LOGIN_USER_TTL, TimeUnit.MINUTES);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

?解釋:因為每次我們都需要寫一堆邏輯來判斷用戶是否存在,那么我們可以使用AOP思想,在具體一點就是使用攔截器,但是后續服務器是不是需要用戶信息,還是從Session中取出用戶信息嗎?不,頻繁的訪問Session會增加其訪問開銷并且造成線程不安全,所以我們依舊采用將用戶信息存入TreadLocal中(線程安全)

注意:使用攔截器時,前端需要我們返回一些基礎數據給它渲染用戶信息,而我們之前存入Session的數據是整個用戶的數據(包含密碼,手機號),這些信息不適合暴露出去,所以對應之前存入Session時的數據需要做一些修改,只需要存一些基礎用戶信息即可(且存入Session的信息過多也是不好的)

因此我們需要隱藏用戶的敏感信息(存入一些需展示的信息即可)

2.集群的Session共享問題

問題介紹:Session的數據一般存儲到服務端或Redis中(手動存),而客戶端只保存了一個SessionID(通過Cokie傳遞)(而且每個客戶端的SessionID不同),那么當需要訪問多個服務端時,Session數據并不共享,就會出現問題

解決:

方案一:服務器之間進行Session的拷貝(內存浪費,有延遲)

方案二:使用Redis存(Redis是存入內存的,訪問速度快,多個服務器可以同時訪問不會造成內存浪費)

3.基于Redis實現共享Session登錄

?

  @Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校驗手機號if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手機號格式錯誤");}//2.生成驗證碼String code = RandomUtil.randomNumbers(6);//3.保存驗證碼stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
//        session.setAttribute("code",code);
//        session.setAttribute("phone",phone);//4.返回驗證碼log.debug("當前驗證碼:{}",code);//5.返回return Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {
//        //獲取緩存數據
//        Object catchPhone = session.getAttribute("phone");
//        Object catchCode = session.getAttribute("code");//獲取登錄數據String code = loginForm.getCode();String phone = loginForm.getPhone();//校驗
//        if(!code.equals(catchCode) || !phone.equals(catchPhone)){
//            return Result.fail("手機號或驗證碼錯誤");
//        }if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手機號格式錯誤");}String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);if(!code.equals(cacheCode)){return Result.fail("驗證碼錯誤");}User user = query().eq("phone", phone).one();//判斷用戶是否存在if(user == null){//不存在User userNew = new User();userNew.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));userNew.setPhone(phone);user = userNew;//保存數據庫save(user);}//添加到SessionUserDTO userDTO = new UserDTO();BeanUtil.copyProperties(user,userDTO);String token = UUID.randomUUID().toString(true);String tokenKey = LOGIN_USER_KEY + token;Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//        session.setAttribute("user",userDTO);stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);return Result.ok(token);}
public class RefreshTokenInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//獲取請求頭tokenString token = request.getHeader("authorization");//判斷是否為空if (StrUtil.isBlank(token)) {return true;}
//        HttpSession session = request.getSession();//設置keyString key = LOGIN_USER_KEY + token;//獲取Redis中的數據Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);//判斷是否為空if(userMap.isEmpty()){return true;}//獲取userUserDTO user = BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);//存入線程中UserHolder.saveUser( user);stringRedisTemplate.expire(key,LOGIN_USER_TTL, TimeUnit.MINUTES);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 {UserDTO user = UserHolder.getUser();if(user == null){response.setStatus(401);return false;}return true;}}

解釋:其實最終我們只需要修改存入Session的部分,改為存入Redis即可

注意:

1.由于先前Session是用戶訪問一次(就是進行一次操作)就會更新登錄憑證的過期時間(防止登錄失效),那么我們也需要實現該功能,一般想到是在攔截器中放行之前更新時間,但是由于之前實現的攔截器有特定的攔截路徑,那么沒有被攔截的路徑我們也需要進行更新,所以我們可以在加一個攔截器專門來更新時間(第一個攔截器更新時間,并進行存用戶,第二個攔截器進行判斷用戶是否存在(它有特定的攔截路徑),不存在不放行)

2.由于使用的是StringRedisTemplate,它要求key與value都必須為String類型,所以我們需要將數據轉換成String類型再存入Redis中

3.注意攔截器的順序,一般先添加的攔截器先執行(你也可以設置優先級order

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate 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);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0).addPathPatterns("/**");}
}

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

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

相關文章

藍橋杯14屆國賽 合并數列

問題描述 小明發現有很多方案可以把一個很大的正整數拆成若干正整數的和。他采取了其中兩種方案&#xff0c;分別將他們列為兩個數組 {a1,a2,...,an} 和 {b1,b2,...,bm}。兩個數組的和相同。 定義一次合并操作可以將某數組內相鄰的兩個數合并為一個新數&#xff0c;新數的值是…

Doris和Clickhouse對比

目錄 一、Doris和Clickhouse對比1. 底層架構**DorisClickHouse** 2. 運行原理DorisClickHouse 3. 使用場景DorisClickHouse 4. 優缺點對比總結 二、MPP架構和Shared-Nothing 架構對比1. 什么是 MPP 架構&#xff1f;定義特點典型代表 2. 什么是 Shared-Nothing 架構&#xff1f…

niushop單商戶V5多門店版V5.5.0全插件+商品稱重、商家手機端+搭建環境教程

一.系統介紹 【全開源】niushop單商戶V5多門店版V5.5.0版本&#xff0c;我看很多人都想要 商品稱重、商家手機端等插件這套是全插件版本&#xff0c;整合起來本博主也花了不少啦~ Niushop系統是應用thinkphp6開發的完善的電商系統&#xff0c;擁有完善的商品機制&#xff0c;…

內存、磁盤、CPU區別,Hadoop/Spark與哪個聯系密切

1. 內存、磁盤、CPU的區別和作用 1.1 內存&#xff08;Memory&#xff09; 作用&#xff1a; 內存是計算機的短期存儲器&#xff0c;用于存儲正在運行的程序和數據。它的訪問速度非常快&#xff0c;比磁盤快幾個數量級。在分布式計算中&#xff0c;內存用于緩存中間結果、存儲…

Jenkins linux安裝

jenkins啟動 service jenkins start 重啟 service jenkins restart 停止 service jenkins stop jenkins安裝 命令切換到自己的下載目錄 直接用命令下載 wget http://pkg.jenkins-ci.org/redhat-stable/jenkins-2.190.3-1.1.noarch.rpm 下載直接安裝 rpm -ivh jenkins-2.190.3-…

RabbitMQ ②-工作模式

RabbitMQ 工作模式 官方提供了七種工作模式 Simple&#xff08;簡單模式&#xff09; P&#xff1a;生產者&#xff0c;發布消息到隊列C&#xff1a;消費者&#xff0c;從隊列中獲取消息并消費Queue&#xff1a;消息隊列&#xff0c;存儲消息。 一個生產者&#xff0c;一個…

(2)python開發經驗

文章目錄 1 pyside6加載ui文件2 使用pyinstaller打包 更多精彩內容&#x1f449;內容導航 &#x1f448;&#x1f449;Qt開發 &#x1f448;&#x1f449;python開發 &#x1f448; 1 pyside6加載ui文件 方法1&#xff1a; 直接加載ui文件 from PySide6.QtWidgets import QAp…

【C++】互斥鎖(Mutex)

在C中&#xff0c;互斥鎖&#xff08;Mutex&#xff09;是用于線程同步的重要工具&#xff0c;用于保護共享資源&#xff0c;防止多線程同時訪問導致的數據競爭&#xff08;Data Race&#xff09;問題。 以下是C中互斥鎖的核心用法和示例&#xff1a; 一、基本互斥鎖 std::mut…

Jsoup與HtmlUnit:兩大Java爬蟲工具對比解析

Jsoup&#xff1a;HTML解析利器 定位&#xff1a;專注HTML解析的輕量級庫&#xff08;也就是快&#xff0c;但動態頁面無法抓取&#xff09; 核心能力&#xff1a; DOM樹解析與CSS選擇器查詢 HTML凈化與格式化 支持元素遍歷與屬性提取 應用場景&#xff1a;靜態頁面數據抽…

小白成長之路-vim編輯

文章目錄 Vim一、命令模式二、插入模式3.a:進入插入模式&#xff0c;在當前光標的后一個字符插入![在這里插入圖片描述](https://i-blog.csdnimg.cn/direct/fd293c3832ed49e2974abfbb63eeb5bb.png)4.o: 在當前光標的下一行插入5.i:在當前光標所在字符插入&#xff0c;返回命令模…

[redis進階六]詳解redis作為緩存分布式鎖

目錄 一 什么是緩存 緩存總結板書: 二 使?Redis作為緩存 三 緩存的更新策略 1) 定期?成 2) 實時?成 四 面試重點:緩存預熱,緩存穿透,緩存雪崩 和緩存擊穿 1)緩存預熱 2)緩存穿透 3)緩存雪崩 4)緩存擊穿 五 分布式鎖 板書: 1)什么是分布式鎖 2)分布式鎖的基…

【MySQL】數據表插入數據

個人主頁&#xff1a;Guiat 歸屬專欄&#xff1a;MySQL 文章目錄 1. 插入數據概述1.1 插入數據的重要性1.2 插入數據的基本原則 2. 基本插入語句2.1 INSERT INTO語法2.2 插入多行數據2.3 不指定列名的插入2.4 插入NULL和默認值 3. 高級插入技術3.1 使用子查詢插入數據3.2 IGNOR…

軟考-軟件設計師中級備考 14、刷題 算法

一、考點歸納 1&#xff09;排序 2、查找 3、復雜度 4、經典問題 0 - 1 背包動態規劃0 - 1 背包問題具有最優子結構性質和重疊子問題性質。通過動態規劃可以利用一個二維數組來記錄子問題的解&#xff0c;避免重復計算&#xff0c;從而高效地求解出背包能裝下的最大價值。分…

【阿里云】阿里云 Ubuntu 服務器無法更新 systemd(Operation not permitted)的解決方法

零、前言 目前正在使用的Ubuntu服務器中&#xff0c;僅阿里云&#xff08;不止一臺&#xff09;出現了這個問題&#xff0c;因此我判定是阿里云服務器獨有的問題。如果你的服務器提供商不是阿里云&#xff0c;那么這篇文章可能對你沒有幫助。 如果已經因為升級錯誤導致依賴沖突…

css 點擊后改變樣式

背景&#xff1a; 期望實現效果&#xff1a;鼠標點擊之后&#xff0c;保持選中樣式。 實現思路&#xff1a;在css樣式中&#xff0c;:active 是一種偽類&#xff0c;用于表示用戶當前正在與被選定的元素進行交互。當用戶點擊或按住鼠標時&#xff0c;元素將被激活&#xff0c;此…

采用AI神經網絡降噪算法的語言降噪消回音處理芯片NR2049-P

隨著AI時代來臨.通話設備的環境噪音抑制也進入AI降噪算法時代. AI神經網絡降噪技術是一款革命性的語音處理技術&#xff0c;他突破了傳統單麥克風和雙麥克風降噪的局限性,利用采集的各種日常環境中的噪音樣本進行訓練學習.讓降噪算法具有自適應噪聲抑制功能&#xff0c;可以根…

不用聯網不用編程,PLC通過智能網關快速實現HTTP協議JSON格式與MES等系統平臺雙向數據通訊

智能網關IGT-DSER集成了多種PLC的原廠協議&#xff0c;方便實現各種PLC、智能儀表通過HTTP協議與MES等各種系統平臺通訊對接。PLC內不用編寫程序&#xff0c;設備不用停機&#xff0c;通過網關的參數配置軟件(下載地址)配置JSON文件的字段與PLC寄存器地址等參數即可。 …

如何將兩臺虛擬機進行搭橋

將兩臺虛擬機實現網絡互通&#xff08;“搭橋”&#xff09;需配置虛擬網絡&#xff0c;以下是基于 VMware Workstation 和 VirtualBox 的詳細操作指南&#xff08;以 Windows 系統為例&#xff0c;Linux 原理類似&#xff09;&#xff1a; 一、VMware Workstation 配置&#x…

Xianyu AutoAgent,AI閑魚客服機器人

Xianyu AutoAgent是一款專為閑魚平臺開發的智能客服機器人系統&#xff0c;旨在提供全天候的自動化服務。它具備多專家協同決策、智能議價和上下文感知對話等功能&#xff0c;能夠管理輕量級的對話記憶&#xff0c;利用完整的對話歷史為用戶提供更自然的交流體驗。 Xianyu Aut…

鍵盤輸出希臘字符方法

在不同操作系統中&#xff0c;輸出希臘字母的方法有所不同。以下是針對 Windows 和 macOS 系統的詳細方法&#xff0c;以及一些通用技巧&#xff1a; 1.Windows 系統 1.1 使用字符映射表 字符映射表是一個內置工具&#xff0c;可以方便地找到并插入希臘字母。 ? 步驟&#xf…