Redis進行驗證碼登錄的項目實踐
1.基于Session進行對驗證碼等信息進行存儲
我們?nèi)绻獙崿F(xiàn)一個驗證碼的收發(fā)校驗登錄的模塊,可以基于Session來進行對信息的存儲,然后通過對這些信息的校驗來完成對驗證碼的校驗
首先要了解發(fā)送驗證碼的邏輯
注意:以下內(nèi)容所有帶Utill的類都是hutool包下的類,在使用時導(dǎo)入依賴即可使用
1.1發(fā)送驗證碼
流程如下:

在controller中定義方法,傳參為前端的手機號和session,其中session是儲存信息的容器,可以實現(xiàn)存儲用戶,手機號等信息
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
//發(fā)送短信驗證碼并保存驗證碼
return userService.sendCode(phone,session);
}在service里定義方法后在實現(xiàn)類中實現(xiàn)
根據(jù)流程來分步實現(xiàn),這里發(fā)送驗證碼的用到了Hutool包里的生成隨機數(shù)來生成驗證碼。
苒后利用一個Utils包里的lambda表達式來判斷手機號格式
/**
* 是否是無效手機格式
* @param phone 要校驗的手機號
* @return true:符合,false:不符合
*/
public static boolean isPhoneInvalid(String phone){
return mismatch(phone, RegexPatterns.PHONE_REGEX);
}
/** /**
* 手機號正則
*/
public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";然后發(fā)送驗證碼可以利用阿里云來對手機號進行發(fā)送,但是我們主要講邏輯,就不實現(xiàn)這個功能了,直接用日志將驗證碼打印過來
/**
* 發(fā)送短信驗證碼
* @return
*/
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校驗手機號
if(RegexUtils.isPhoneInvalid(phone)){
//不符合
return Result.fail("手機號格式錯誤");
}
//符合
//2.生成驗證碼
String code = RandomUtil.randomNumbers(6);
//3.保存驗證碼到seesion
session.setAttribute("code",code);
//4.發(fā)送驗證碼
log.debug("發(fā)送成功,驗證碼為:{}", code);
return Result.ok();
}苒后利用session來儲存驗證碼
1.2校驗驗證碼并登錄
流程如下:

校驗驗證碼邏輯較為復(fù)雜,首先要定義方法
其中l(wèi)oginFormDTO里傳入的就是前端的參數(shù),如手機號,驗證碼等信息
/**
* 登錄功能
* @param loginForm 登錄參數(shù),包含手機號、驗證碼;或者手機號、密碼
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
//實現(xiàn)登錄功能
return userService.login(loginForm,session);
}然后在實現(xiàn)類里實現(xiàn)
其中我們先將手機號從loginForm里取出來再進行判斷
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校驗手機號
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//不一致
return Result.fail("手機號格式錯誤");
}
//一致
//2.校驗驗證碼
//先在session中取出驗證碼
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();//前端提交的code
//不一致
if(cacheCode==null || !cacheCode.toString().equals(code)){
return Result.fail("驗證碼已過期或錯誤");
}
//重新提交驗證碼
//一致
//3.根據(jù)手機號查詢用戶
User user = query().eq("phone",phone).one();
//用戶不存在
if(user==null){
//創(chuàng)建新用戶并保存到數(shù)據(jù)庫
user = creatUserWithPhone(phone);
//保存用戶到session 在最后一步完成
}
//用戶存在
//保存用戶到session
//將user轉(zhuǎn)為dto儲存
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}我們將驗證碼從session里取出來和前端得到的驗證碼進行對比:cacheCode對比code
再后利用mybatisplus進行查詢用戶存入到user中
然后判斷用戶是否存在,如果不存在,就證明是新用戶
新用戶創(chuàng)建我們利用一個方法creatUserWithPhone來根據(jù)手機號來進行創(chuàng)建
private User creatUserWithPhone(String phone) {
//1.創(chuàng)建用戶
//由于新用戶大部分創(chuàng)建時都有默認參數(shù),因此我們只需要將手機號和昵稱傳入即可,昵稱隨機
//邏輯和大部分創(chuàng)建新用戶的軟件邏輯一樣
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//2.保存到數(shù)據(jù)庫
save(user);
return null;
}創(chuàng)建以后用mp存入到數(shù)據(jù)庫中
如果存在就將其轉(zhuǎn)為dto存入到session中以便后續(xù)使用
1.3攔截校驗
實現(xiàn)了基本邏輯業(yè)務(wù)后,就可以實現(xiàn)進行攔截校驗了
先寫一個攔截器,基本業(yè)務(wù)是:在每次請求處理之前獲取session里的用戶,如果用戶不存在,也就是在之前的登錄中沒有登錄,即沒有存入用戶信息,就進行攔截,返回401
然后在請求處理之后移除用戶信息,節(jié)約內(nèi)存開銷
//校驗登錄狀態(tài)
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//首先由于請求里面帶有cookie,我們的request參數(shù)例就有session,我們只需要在里面取出即可
//1.獲取session
HttpSession session = request.getSession();
//2.獲取session中的用戶
Object user = session.getAttribute("user");
//3.判斷用戶是否存在
if(user== null){
//不存在,攔截,返回false即可攔截,返回401狀態(tài)碼
response.setStatus(401);
return false;
}
//存在,將用戶保存到ThreadLocal中
UserHolder.saveUser((UserDTO) user);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用戶
UserHolder.removeUser();
}
}然后在配置類里注冊這個攔截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
//配置攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
//排除一些路徑,不需要攔截的地方
//如一些頁面可以不需要登錄校驗
"/user/code",
"/user/login",
"/blog/hot",//查看熱點,也與登錄無關(guān),可以放行
"/shop/**",
"/shop-type/**",
"/upload/**",
"/voucher/**"
);
}2.用Redis實現(xiàn)對驗證碼信息校驗
我們前面使用Httpsession來進行儲存用戶信息和驗證碼,但是使用session也有很多問題
主要是session的共享問題:

如果我們使用了tomcat集群來部署項目,多臺Tomcat并不共享session存儲空間,當請求切換到不同的tomcat服務(wù)時導(dǎo)致數(shù)據(jù)丟失的問題;
而session的替代方案應(yīng)該滿足:
1.數(shù)據(jù)共享
2.內(nèi)存存儲
3.key,value架構(gòu)
首先保存的基本思想是用手機號作為key來存儲,因為要保證每個用戶的key都不同
我們一般用String結(jié)構(gòu),以Json字符串來保存
或者使用hash結(jié)構(gòu)來進行保存,利用hash結(jié)構(gòu)將對象中的每個字段獨立儲存,可以針對單個字段進行CRUD,并且內(nèi)存占用更少
我們以hash結(jié)構(gòu)為例
我們只需要在原來的儲存session步驟上替換為存儲到Redis里即可
邏輯如下:
注意:
但是在校驗驗證碼登錄注冊時,我們儲存用戶信息的key可以使用隨機生成的token,值為json對象,為了防止泄漏風險,然后判斷用戶是否存在時如果存在,就報錯用戶到Redis里,然后返回token給前端,不存在也在創(chuàng)建新用戶后返回token給前端
而且session仍然有用,就是在request請求頭中的token里的session就存儲的有用戶信息,作為校驗登錄狀態(tài)時判斷用戶是否存在的依據(jù),就是在登錄或者注冊后得到的token獲取用戶信息
2.1發(fā)送驗證碼時存儲到Redis邏輯
我們之前已經(jīng)實現(xiàn)了發(fā)送驗證碼的功能,我們只需要修改存儲驗證碼到session這個功能即可
在pom文檔里導(dǎo)入Redis客戶端依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>首先導(dǎo)入RedisTemplete實現(xiàn)API,這里用@Resource和@Autowired進行注入都可以
一個是針對類名裝配,一個針對類型裝配
@Resource
private StringRedisTemplate stringRedisTemplate;
然后修改業(yè)務(wù)邏輯
//3.保存驗證碼到seesion
// session.setAttribute("code",code);
//在key里加上一個前綴來和其他業(yè)務(wù)里的phone來區(qū)分
//然后設(shè)置一個有效期
stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);2.2校驗驗證碼實現(xiàn)登錄注冊邏輯

首先就是修改從session中獲取驗證碼進行校驗的邏輯
//從Redis獲取驗證碼并校驗
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
String code = loginForm.getCode();//前端提交的code然后就是修改保存用戶信息到redis中這些邏輯
我們把用戶信息設(shè)置為哈希類型,把號碼設(shè)置為string類型,因為哈希類型可以保證用戶信息等隱私
//保存用戶到Redis中
//3.1生成token作為登錄令牌
String token = UUID.randomUUID().toString(true);
//3.2將USer對象轉(zhuǎn)為HashMap存儲
UserDTO userDTO= BeanUtil.copyProperties(user,UserDTO.class);
Map<String,Object> userMap= BeanUtil.beanToMap(userDTO);
//3.3存儲,利用putALL,key可以是多個值,即hashMap,然后設(shè)置有效期
String tokenKey= LOGIN_CODE_KEY+token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//設(shè)置有效期
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//但是還有一個問題,就是有效期的邏輯是,只要是在有效期內(nèi)無論進行訪問,只要有效期到了就失效,不會重置有效期
//因此我們要進行修改邏輯,修改為,只要三十分鐘內(nèi)訪問過了就重置有效期,又可以過三十分鐘過期
//我們就可以利用攔截器來實現(xiàn),只要訪問了請求,即觸發(fā)了攔截器,就實現(xiàn)更新有效期的操作
//8.返回token給前端
return Result.ok();然后修改校驗登錄狀態(tài)時的從redis獲取用戶邏輯

如圖,然后在每次校驗時也要刷新token有效期
//1.獲取請求頭中的token,根據(jù)前端返回的名字來獲取
String token= request.getHeader("authorization");
if(StrUtil.isBlank(token)){
response.setStatus(401);
return false;
}
//2.基于token獲取redis中的用戶,利用entries來獲取所有key里的值組成hashMap
Map<Object,Object> userMap= stringRedisTemplate.opsForHash().entries(RedisConstants.LOCK_SHOP_KEY+token);
//3.判斷用戶是否存在
if(userMap.isEmpty()){
//不存在,攔截,返回false即可攔截,返回401狀態(tài)碼
response.setStatus(401);
return false;
}
//將查詢到的hash轉(zhuǎn)為userDto對象
UserDTO userDTO= BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);
//存在,將用戶保存到ThreadLocal中
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(RedisConstants.LOCK_SHOP_KEY+token,30, TimeUnit.MINUTES);
//放行
return true;到此這篇關(guān)于Redis進行驗證碼登錄的項目實踐的文章就介紹到這了,更多相關(guān)Redis 驗證碼登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis動態(tài)熱點數(shù)據(jù)緩存策略設(shè)計
本文主要介紹了Redis動態(tài)熱點數(shù)據(jù)緩存策略設(shè)計,包括熱點數(shù)據(jù)識別、動態(tài)緩存、多級緩存、預(yù)加載機制、更新策略以及監(jiān)控告警等,具有一定的參考價值,感興趣的可以了解一下2025-01-01
Spring?Boot?緩存?與?Redis問題小結(jié)
本文總結(jié)SpringBoot緩存方案,對比Redis與EnCache優(yōu)劣,指出Redis支持集群但效率較低,EnCache速度快但不支持分布式,SpringBoot默認使用ConcurrentHashMapManager,本文給大家介紹Spring?Boot緩存與Redis問題小結(jié),感興趣的朋友一起看看吧2025-07-07
Redis過期Key刪除策略和內(nèi)存淘汰策略的實現(xiàn)
當內(nèi)存使用達到上限,就無法存儲更多數(shù)據(jù)了,為了解決這個問題,Redis內(nèi)部會有兩套內(nèi)存回收的策略,過期Key刪除策略和內(nèi)存淘汰策略,本文就來詳細的介紹一下這兩種方法,感興趣的可以了解一下2024-02-02
Redis的五種基本類型和業(yè)務(wù)場景和使用方式
Redis是一種高性能的鍵值存儲數(shù)據(jù)庫,支持多種數(shù)據(jù)結(jié)構(gòu)如字符串、列表、集合、哈希表和有序集合等,它提供豐富的API和持久化功能,適用于緩存、消息隊列、排行榜等多種場景,Redis能夠?qū)崿F(xiàn)高速讀寫操作,尤其適合需要快速響應(yīng)的應(yīng)用2024-10-10
kubernetes環(huán)境部署單節(jié)點redis數(shù)據(jù)庫的方法
這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點redis數(shù)據(jù)庫的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
redis持久化AOF和RDB的區(qū)別及解決各個場景問題示例
這篇文章主要為大家介紹了redis持久化AOF和RDB的區(qū)別及解決各個場景問題示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08

