點評項目(Redis中間件)第三部分短信登錄,查詢緩存

可以直接看后面Redis實現功能的部分

基于session實現短信登錄

發送短信驗證碼

前端請求樣式

業務層代碼

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@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();}
}
關于參數httpsession
  • 來源Spring框架(Servlet容器)自動注入

  • 機制:這是Spring MVC一個非常強大的特性,叫做參數解析(Handler Method Argument Resolution)。當你在一個控制器方法中聲明某個類型的參數時,Spring會查看它注冊的眾多?HandlerMethodArgumentResolver?組件,找到一個能處理該類型的解析器,然后自動為你創建或獲取該對象,并傳入方法。

    • 對于?HttpSession?類型,Spring使用的是?ServletWebRequest?等相關解析器。這個解析器會:

      1. 檢查當前的HTTP請求對象 (HttpServletRequest)。

      2. 調用?request.getSession()?方法來獲取當前請求關聯的Session。

        • getSession()?方法非常智能:如果Session已經存在,則返回它;如果不存在,則創建一個新的Session并返回

HttpSession?參數不是前端發送來的,而是Spring框架根據你的參數類型,自動從當前HTTP請求中獲取并“注入”到方法中的。它的作用是提供一種在服務器端存儲用戶相關數據并在多次請求間共享的機制。

登錄功能

前端請求樣式

業務層代碼

黑馬給的代碼里面,工具類的相關注解有一處錯誤

 /*** 是否是無效手機格式* @param phone 要校驗的手機號* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}

返回ture是無效驗證碼

之所以一直使用反向驗證是因為正向驗證會使得代碼有很多if的嵌套

業務層代碼

@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗手機號String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.從redis獲取驗證碼并校驗String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,報錯return Result.fail("驗證碼錯誤");}// 4.一致,根據手機號查詢用戶 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判斷用戶是否存在if (user == null) {// 6.不存在,創建新用戶并保存user = createUserWithPhone(phone);}// 7.保存用戶信息到 redis中// 7.1.隨機生成token,作為登錄令牌String token = UUID.randomUUID().toString(true);// 7.2.將User對象轉為HashMap存儲UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3.存儲String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.設置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);}

如果保存可能會導致一些泄露問題,而且我們也不需要那么多信息,只需要唯一標識,所以我們把user對象轉換成userdto對象,轉換代碼上面有,還是使用的BeanUtil工具類來實現。

驗證攔截功能

這個功能放到攔截器里面,方便所有的可控制器都可以實現用戶校驗。然后將攔截到的用戶信息傳遞到控制器里面去。不過這里有線程安全問題,所以我們需要使用threadlocal保證安全。

線程安全問題

簡單來說,線程安全的根源在于:多個線程同時讀寫共享的可變數據(狀態)時,如果沒有正確的同步,就會導致不確定的結果。

特征線程安全線程不安全
數據來源局部變量、方法參數、不可變對象共享的成員變量、非線程安全容器
操作類型單純的讀操作、無狀態的計算復合操作(如?i++)、"檢查-然后-執行"
代碼示例return a + b;?String s = "hello";count++;?if (map.contains(key)) { map.put(key, value); }
核心原因沒有共享?或?共享不可變共享且可變,且未正確同步
變量類型存儲位置共享范圍線程安全問題來源
實例變量
(非?static)
堆內存 (Heap)
(每個對象內)
同一個對象實例被多個線程引用高發區。因為容易被忽略,人們常常以為“非static就是安全的”,卻忘了單個實例被多線程共享這一常見場景。
類變量
(static)
方法區 (Method Area)整個JVM內的所有線程和所有實例顯而易見。因為大家都知道
攔截器代碼(實現Redis最終版)

在工具類里面寫一個攔截器,需要實現一個接口,以及三個接口方法

  1. preHandle:在Controller方法執行之前被調用。通常用于身份驗證、日志記錄、權限檢查等。如果返回?false,則中斷請求流程。

  2. postHandle:在Controller方法執行之后,但視圖渲染之前被調用。通常用于向模型添加公共數據、記錄執行時間等。

  3. afterCompletion:在整個請求完成之后(即視圖渲染完畢)被調用。通常用于資源清理、記錄最終日志等。

package com.hmdp.utils;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;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;}
}
配置攔截器

需要一個mvc config類(實現Redis最終版)

package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@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);}
}

里面有些控制器不需要被攔截所以我們需要排除在外。

共享問題

替代辦法

Redis

使用Redis實現session登錄

獲取并保存驗證碼

public Result sendCode(String phone, HttpSession session) {// 1.校驗手機號if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.符合,生成驗證碼String code = RandomUtil.randomNumbers(6);// 4.保存驗證碼到 sessionstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.發送驗證碼log.debug("發送短信驗證碼成功,驗證碼:{}", code);// 返回okreturn Result.ok();}

驗證碼發送的功能很復雜,這里直接用日志代替。

TimeUnit.MINUTES?- 時間單位

短信登錄以及注冊功能

登錄之后用戶的信息還要存儲到redis里面,key就用一個隨機的token,存儲對象轉換成哈希,這個token會返回給前端。

public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校驗手機號String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回錯誤信息return Result.fail("手機號格式錯誤!");}// 3.從redis獲取驗證碼并校驗String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 不一致,報錯return Result.fail("驗證碼錯誤");}// 4.一致,根據手機號查詢用戶 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 5.判斷用戶是否存在if (user == null) {// 6.不存在,創建新用戶并保存user = createUserWithPhone(phone);}// 7.保存用戶信息到 redis中// 7.1.隨機生成token,作為登錄令牌String token = UUID.randomUUID().toString(true);// 7.2.將User對象轉為HashMap存儲UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3.存儲String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.設置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);}
  • 先檢查?cacheCode?是否為?null(防止空指針異常)

  • 如果是?null,直接進入if塊,不會執行后面的equals

  • 如果不是?null,再比較內容是否相等

token來源

UUID是一個全球唯一的標識符,就像每個人的身份證號一樣,幾乎不可能重復。

  1. 唯一性?- 幾乎不可能重復

  2. 安全性?- 難以猜測,適合做token

  3. 分布式?- 不同服務器生成也不會沖突

  4. 無需協調?- 不需要中央機構分配

下一步就是redis的語法應用了,將信息存儲到redis里面。

// 7.1.隨機生成token,作為登錄令牌String token = UUID.randomUUID().toString(true);// 7.2.將User對象轉為HashMap存儲UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3.存儲String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.設置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);

這里面用beantil工具把bean轉換成了一個map對象,然后使用

設置token有效期,

String tokenKey = LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.設置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

另外只要用戶在不斷訪問我們就要不斷的更新redis的時間限制,在攔截器里面添加更新邏輯。

null?-?不存在

  • 對象根本不存在

  • 沒有分配內存空間

  • 相當于"無中生有"

isEmpty()?-?存在但為空

  • 對象存在,但內容為空

  • 已經分配了內存空間

  • 相當于"空盒子"

刷新token時間的攔截器

因為有一些功能是不需要校驗的,所以為了這些功能也有刷新token的功能所以我們重新創建一個新的攔截器。

package com.hmdp.utils;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;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();}
}

攔截器的代碼我們在前面就是放的最終版的,兩個攔截器需要設置先后順序,我們使用order設置大小,越小的越先執行。

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

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

相關文章

線性方程求解器的矩陣分裂

大概思路是對的&#xff0c;但是查老師可能會出現幻覺&#xff0c;小心食用 &#x1f603;這段代碼是在初始化迭代法求解器&#xff0c;構建迭代矩陣和分裂矩陣。以下是詳細解釋&#xff1a; if init_from_func or init_from_input:# 1. 存儲剛度矩陣self.stiff_p stiff_p# 2.…

【Beetle RP2350】雷達模塊 CEM5861G-M11 開發使用指南

一、硬件介紹 1、產品特點 Beetle RP2350【RP2350A_QFN60】是一款基于RP2350微控制器的高性能迷你開發板&#xff0c;雙核雙架構設計&#xff08;支持 Arm Cortex-M33或Hazard3 RISC-V內核&#xff09;為開發者提供靈活的性能配置。 雙核雙架構&#xff0c;性能自由切換 采…

高通Android 13 開機黑屏問題深度剖析與解決方案

1. 問題概述 在 Android 13 系統定制化開發過程中&#xff0c;開機流程的調試與優化頗具挑戰性。一個典型問題是&#xff1a;當開機動畫播放完畢后&#xff0c;設備會先出現數秒黑屏&#xff0c;然后才進入鎖屏界面。本文基于開機日志分析&#xff0c;結合實際項目經驗&#x…

騰訊推出AI CLI工具CodeBuddy,國內首家同時支持插件、IDE和CLI三種形態的AI編程工具廠商

2025年9月9日&#xff0c;騰訊正式推出自研AI CLI工具CodeBuddy code&#xff0c;成為國內首家同時支持插件、IDE和CLI三種形態的AI編程工具廠商。這一創新不僅填補了國內市場在全形態AI編程工具領域的空白&#xff0c;更以編碼時間縮短40%、AI生成代碼占比超50%的硬核數據&…

零基礎學習QT的第二天-組件基礎知識

組件聲明以及設置屬性 所有的組件的基類為&#xff1a;QtObject&#xff0c;在c中名稱為&#xff1a;QObject。 在qml和c名稱有所區別&#xff0c;例如在Qml中QtObject&#xff0c;在C會省略一個t(QObject) 聲明組件的方式&#xff1a; 組件名 {屬性名:值}在實際應用中&#xf…

像素圖生成小程序開發全解析:從圖片上傳到Excel圖紙

像素圖生成小程序開發全解析&#xff1a;從圖片上傳到Excel圖紙 前言 在數字化創作和工藝設計領域&#xff0c;像素圖生成工具具有廣泛的應用價值&#xff0c;無論是十字繡設計、LED燈陣布置還是復古游戲美術創作。本文將詳細解析一個功能完整的像素圖生成小程序的開發過程&…

mac-intel操作系統go-stock項目(股票分析工具)安裝與配置指南

1. 項目基礎介紹 go-stock 是一個基于Wails和NaiveUI開發的AI賦能股票分析工具。旨在為用戶提供自選股行情獲取、成本盈虧展示、漲跌報警推送等功能。它支持A股、港股、美股等市場&#xff0c;能夠進行市場整體或個股的情緒分析、K線技術指標分析等功能。所有數據均保存在本地…

spring-單例bean是線程安全的嗎

其中可修改的成員變量有線程不安全問題&#xff0c;不可修改的無狀態的 userService是沒有線程安全問題的 spring框架中有一個 Scope注解&#xff0c;默認的值就是singleton&#xff0c;單例的。 不是線程安全的&#xff0c;一般來說&#xff0c;我們在bean中注入的對象都是無狀…

CM1033系列 3串鋰電池保護IC - 高精度±25mV 內置延時 多型號可選(含鐵鋰)

1. 核心亮點 高精度多重保護&#xff1a;專為3串電池組設計&#xff0c;提供過充、過放、三級過流&#xff08;含短路&#xff09;、充電過流及斷線檢測等全方位保護&#xff0c;電壓檢測精度高達25mV。超低功耗&#xff1a;工作電流典型值僅7μA&#xff0c;休眠電流低至4μA&…

【第23話:定位建圖】SLAM后端優化方法詳解

SLAM 后端優化方法詳解 SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;后端優化是SLAM系統中的關鍵環節&#xff0c;負責對前端輸出的傳感器數據進行全局一致性優化&#xff0c;消除累積誤差。后端通常基于圖優化框架&#xff08;如g2o、GTSAM&#xff09…

MongoDB 備份與恢復終極指南:mongodump 和 mongorestore 深度實戰

MongoDB 備份與恢復終極指南&#xff1a;mongodump 和 mongorestore 深度實戰引言&#xff1a;數據守護者的使命第一部分&#xff1a;基礎概念與核心原理1.1 邏輯備份 vs. 物理備份&#xff1a;根本性的區別1.2 核心工具介紹第二部分&#xff1a;mongodump 備份實戰詳解2.1 基礎…

鴻蒙的“分布式架構”理念:未來操作系統的關鍵突破

一、引言&#xff1a;為什么需要分布式架構&#xff1f; 隨著移動互聯網的發展&#xff0c;智能設備不斷普及。用戶身邊可能同時擁有 手機、平板、PC、電視、手表、耳機、智能音箱、車機 等多種終端設備。 但現實中&#xff0c;我們常遇到以下問題&#xff1a; 不同設備系統割…

MySQL 事務管理與鎖機制:解決并發場景下的數據一致性問題

前言在電商下單、金融轉賬、庫存扣減等并發業務場景中&#xff0c;若不控制數據操作的原子性與隔離性&#xff0c;極易出現 “超賣”“重復扣款”“臟讀數據” 等問題。MySQL 的事務管理與鎖機制是解決這些問題的核心技術&#xff0c;也是后端開發者必須掌握的生產環境能力。本…

MySQL集群高可用架構

一、MySQL高可用之組復制&#xff08;MGR&#xff09;1.1 組復制核心特性與優勢MySQL Group Replication&#xff08;MGR&#xff09;是基于分布式一致性協議&#xff08;Paxos&#xff09;實現的高可用集群方案&#xff0c;核心特性包括&#xff1a;自動故障檢測與恢復&#x…

判別模型 VS 生成模型

1. 判別模型&#xff08;Discriminative Models&#xff09;判別模型直接學習輸入特征&#xff08;X&#xff09;與輸出標簽&#xff08;Y&#xff09;之間的映射關系&#xff0c;即直接對條件概率P(Y|X)進行建模。判別模型關注于如何區分不同類別的數據。特點&#xff1a;直接…

代碼隨想錄算法訓練營第三十一天 | 合并區間、單調遞增的數字

合并區間&#xff1a; 這里還是先對左區間進行排序&#xff0c;判斷重疊區間&#xff0c;首先判斷是否存在元素&#xff0c;存在那么就將元素的第一個放到結果中&#xff0c;那么判斷重疊就是當前元素的左區間和結果集里的最后元素的右區間進行判斷&#xff0c;如果重疊&#x…

EXCEL VBA 清空Excel工作表(Sheet)的方法

1. 刪除所有內容&#xff0c;但保留格式和對象 這種方法只會清除單元格的內容&#xff0c;不會影響格式和嵌入的圖表或對象。 Sub ClearSheetContents()Worksheets("Sheet1").Cells.ClearContents End Sub2. 刪除所有內容和格式&#xff0c;但保留對象 這種方法會刪除…

智能客戶服務支持智能體

超越傳統客服機器人。智能體可以深度查詢知識庫、調用訂單系統API、甚至根據客戶情緒靈活處理退貨、退款、升級投訴等復雜流程。 案例&#xff1a; 客戶說&#xff1a;“我上周買的鞋子尺碼不對&#xff0c;想換貨但是找不到訂單頁面了。” 智能體行動&#xff1a; ① 通過用戶…

【MySQL|第四篇】DQL語句(二)——數據查詢語言

4、排序分頁&#xff1a;&#xff08;1&#xff09;排序&#xff1a;查詢數據的時候進行排序&#xff0c;就是根據某個字段的值&#xff0c;按照升序或者降序的情況將記錄顯示出來語法&#xff1a; select col_name,... from tb_name order by col_name [asc|desc]注意事項&…

百度文心X1.1發布!實測深度思考能力!

文章目錄背景模型實測效果事實性指令跟隨智能體模型技術解讀基準測試文心飛槳攜手共進總結背景 9月9日&#xff0c;WAVE SUMMIT深度學習開發者大會上&#xff0c;百度首席技術官、深度學習技術及應用國家工程研究中心主任王海峰正式發布了文心大模型X1.1深度思考模型&#xff…