最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Redis實現(xiàn)短信驗證碼登錄功能

 更新時間:2025年01月22日 10:31:35   作者:mikey棒棒棒  
對于我們用戶來講,我們在登陸一個APP的時候,有很多種登陸方式,比如"微信掃碼"、"手機號登陸"、"支付寶掃碼"、"賬號密碼登錄",現(xiàn)在大多都會要求微信掃碼登錄或者是手機號驗證碼登錄,所以本文給大家介紹了基于Redis實現(xiàn)短信驗證碼登錄功能,需要的朋友可以參考下

1 基于Session實現(xiàn)短信驗證碼登錄

    /**
     * 發(fā)送驗證碼
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判斷手機號是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手機號格式不正確");
        }
        // 2、手機號合法,生成驗證碼,并保存到Session中
        String code = RandomUtil.randomNumbers(6);
        session.setAttribute(SystemConstants.VERIFY_CODE, code);
        // 3、發(fā)送驗證碼
        log.info("驗證碼:{}", code);
        return Result.ok();
    }
 
    /**
     * 用戶登錄
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判斷手機號是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手機號格式不正確");
        }
        // 2、判斷驗證碼是否正確
        String sessionCode = (String) session.getAttribute(LOGIN_CODE);
        if (code == null || !code.equals(sessionCode)) {
            return Result.fail("驗證碼不正確");
        }
        // 3、判斷手機號是否是已存在的用戶
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPassword, phone));
        if (Objects.isNull(user)) {
            // 用戶不存在,需要注冊
            user = createUserWithPhone(phone);
        }
        // 4、保存用戶信息到Session中,便于后面邏輯的判斷(比如登錄判斷、隨時取用戶信息,減少對數(shù)據(jù)庫的查詢)
        session.setAttribute(LOGIN_USER, user);
        return Result.ok();
    }
 
    /**
     * 根據(jù)手機號創(chuàng)建用戶
     */
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

2 配置登錄攔截器

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 前置攔截器,用于判斷用戶是否登錄
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 1、判斷用戶是否存在
        User user = (User) session.getAttribute(LOGIN_USER);
        if (Objects.isNull(user)){
            // 用戶不存在,直接攔截
            response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
            return false;
        }
        // 2、用戶存在,則將用戶信息保存到ThreadLocal中,方便后續(xù)邏輯處理
        // 比如:方便獲取和使用用戶信息,session獲取用戶信息是具有侵入性的
        ThreadLocalUtls.saveUser(user);
 
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

3 配置完攔截器還需將自定義攔截器添加到SpringMVC的攔截器列表中 才能生效

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加登錄攔截器
        registry.addInterceptor(new LoginInterceptor())
                // 設(shè)置放行請求
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}
 

4 Session集群共享問題

(1)什么是Session集群共享問題?

在分布式集群環(huán)境中,會話(Session)共享是一個常見的挑戰(zhàn)。默認(rèn)情況下,Web 應(yīng)用程序的會話是保存在單個服務(wù)器上的,當(dāng)請求不經(jīng)過該服務(wù)器時,會話信息無法被訪問。

(2) Session集群共享問題造成哪些問題

 服務(wù)器之間無法實現(xiàn)會話狀態(tài)的共享。比如:在當(dāng)前這個服務(wù)器上用戶已經(jīng)完成了登錄,Session中存儲了用戶的信息,能夠判斷用戶已登錄,但是在另一個服務(wù)器的Session中沒有用戶信息,無法調(diào)用顯示沒有登錄的服務(wù)器上的服務(wù)

 (3)如何解決Session集群共享問題?

方案一:Session拷貝(不推薦)

Tomcat提供了Session拷貝功能,通過配置Tomcat可以實現(xiàn)Session的拷貝,但是這會增加服務(wù)器的額外內(nèi)存開銷,同時會帶來數(shù)據(jù)一致性問題

方案二:Redis緩存(推薦)

Redis緩存具有Session存儲一樣的特點,基于內(nèi)存、存儲結(jié)構(gòu)可以是key-value結(jié)構(gòu)、數(shù)據(jù)共享

(4)Redis緩存相較于傳統(tǒng)Session存儲的優(yōu)點

1 高性能和可伸縮性:Redis 是一個內(nèi)存數(shù)據(jù)庫,具有快速的讀寫能力。相比于傳統(tǒng)的 Session 存儲方式,將會話數(shù)據(jù)存儲在 Redis 中可以大大提高讀寫速度和處理能力。此外,Redis 還支持集群和分片技術(shù),可以實現(xiàn)水平擴展,處理大規(guī)模的并發(fā)請求。

2 可靠性和持久性:Redis 提供了持久化機制,可以將內(nèi)存中的數(shù)據(jù)定期或異步地寫入磁盤,以保證數(shù)據(jù)的持久性。這樣即使發(fā)生服務(wù)器崩潰或重啟,會話數(shù)據(jù)也可以被恢復(fù)。

3 豐富的數(shù)據(jù)結(jié)構(gòu):Redis 不僅僅是一個鍵值存儲數(shù)據(jù)庫,它還支持多種數(shù)據(jù)結(jié)構(gòu),如字符串、列表、哈希、集合和有序集合等。這些數(shù)據(jù)結(jié)構(gòu)的靈活性使得可以更方便地存儲和操作復(fù)雜的會話數(shù)據(jù)。

4 分布式緩存功能:Redis 作為一個高效的緩存解決方案,可以用于緩存會話數(shù)據(jù),減輕后端服務(wù)器的負載。與傳統(tǒng)的 Session 存儲方式相比,使用 Redis 緩存會話數(shù)據(jù)可以大幅提高系統(tǒng)的性能和可擴展性。

5 可用性和可部署性:Redis 是一個強大而成熟的開源工具,有豐富的社區(qū)支持和活躍的開發(fā)者社區(qū)。它可以輕松地與各種編程語言和框架集成,并且可以在多個操作系統(tǒng)上運行。

PS:但是Redis費錢,而且增加了系統(tǒng)的復(fù)雜度

5 基于Redis實現(xiàn)短信驗證碼登錄

6 Hash 結(jié)構(gòu)與 String 結(jié)構(gòu)類型的比較

  • String 數(shù)據(jù)結(jié)構(gòu)是以 JSON 字符串的形式保存,更加直觀,操作也更加簡單,但是 JSON 結(jié)構(gòu)會有很多非必須的內(nèi)存開銷,比如雙引號、大括號,內(nèi)存占用比 Hash 更高
  • Hash 數(shù)據(jù)結(jié)構(gòu)是以 Hash 表的形式保存,可以對單個字段進行CRUD,更加靈活

7 Redis替代Session需要考慮的問題

(1)選擇合適的數(shù)據(jù)結(jié)構(gòu),了解 Hash 比 String 的區(qū)別

(2)選擇合適的key,為key設(shè)置一個業(yè)務(wù)前綴,方便區(qū)分和分組,為key拼接一個UUID,避免key沖突防止數(shù)據(jù)覆蓋

(3)選擇合適的存儲粒度,對于驗證碼這類數(shù)據(jù),一般設(shè)置TTL為3min即可,防止大量緩存數(shù)據(jù)的堆積,而對于用戶信息這類數(shù)據(jù)可以稍微設(shè)置長一點,比如30min,防止頻繁對Redis進行IO操作

8 基于redis短信驗證登錄

    /**
     * 發(fā)送驗證碼
     *
     * @param phone
     * @param session
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1、判斷手機號是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手機號格式不正確");
        }
        // 2、手機號合法,生成驗證碼,并保存到Redis中
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code,
                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 3、發(fā)送驗證碼
        log.info("驗證碼:{}", code);
        return Result.ok();
    }
 
    /**
     * 用戶登錄
     *
     * @param loginForm
     * @param session
     * @return
     */
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        String code = loginForm.getCode();
        // 1、判斷手機號是否合法
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手機號格式不正確");
        }
        // 2、判斷驗證碼是否正確
        String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        if (code == null || !code.equals(redisCode)) {
            return Result.fail("驗證碼不正確");
        }
        // 3、判斷手機號是否是已存在的用戶
        User user = this.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if (Objects.isNull(user)) {
            // 用戶不存在,需要注冊
            user = createUserWithPhone(phone);
        }
        // 4、保存用戶信息到Redis中,便于后面邏輯的判斷(比如登錄判斷、隨時取用戶信息,減少對數(shù)據(jù)庫的查詢)
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 將對象中字段全部轉(zhuǎn)成string類型,StringRedisTemplate只能存字符串類型的數(shù)據(jù)
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).
                        setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String token = UUID.randomUUID().toString(true);
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
 
        return Result.ok(token);
    }
 
    /**
     * 根據(jù)手機號創(chuàng)建用戶并保存
     *
     * @param phone
     * @return
     */
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        this.save(user);
        return user;
    }

9 配置登錄攔截器

單獨配置一個攔截器用戶刷新Redis中的token:在基于Session實現(xiàn)短信驗證碼登錄時,我們只配置了一個攔截器,這里需要另外再配置一個攔截器專門用于刷新存入Redis中的 token,因為我們現(xiàn)在改用Redis了,為了防止用戶在操作網(wǎng)站時突然由于Redis中的 token 過期,導(dǎo)致直接退出網(wǎng)站,嚴(yán)重影響用戶體驗。那為什么不把刷新的操作放到一個攔截器中呢,因為之前的那個攔截器只是用來攔截一些需要進行登錄校驗的請求,對于哪些不需要登錄校驗的請求是不會走攔截器的,刷新操作顯然是要針對所有請求比較合理,所以單獨創(chuàng)建一個攔截器攔截一切請求,刷新Redis中的Key

 登錄攔截器:

public class LoginInterceptor implements HandlerInterceptor {
 
    /**
     * 前置攔截器,用于判斷用戶是否登錄
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判斷當(dāng)前用戶是否已登錄
        if (ThreadLocalUtls.getUser() == null){
            // 當(dāng)前用戶未登錄,直接攔截
            response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
            return false;
        }
        // 用戶存在,直接放行
        return true;
    }
}

刷新token的攔截器

public class RefreshTokenInterceptor implements HandlerInterceptor {
 
    // new出來的對象是無法直接注入IOC容器的(LoginInterceptor是直接new出來的)
    // 所以這里需要再配置類中注入,然后通過構(gòu)造器傳入到當(dāng)前類中
    private StringRedisTemplate stringRedisTemplate;
 
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、獲取token,并判斷token是否存在
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)){
            // token不存在,說明當(dāng)前用戶未登錄,不需要刷新直接放行
            return true;
        }
        // 2、判斷用戶是否存在
        String tokenKey = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        if (userMap.isEmpty()){
            // 用戶不存在,說明當(dāng)前用戶未登錄,不需要刷新直接放行
            return true;
        }
        // 3、用戶存在,則將用戶信息保存到ThreadLocal中,方便后續(xù)邏輯處理,比如:方便獲取和使用用戶信息,Redis獲取用戶信息是具有侵入性的
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        ThreadLocalUtls.saveUser(BeanUtil.copyProperties(userMap, UserDTO.class));
        // 4、刷新token有效期
        stringRedisTemplate.expire(token, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return true;
    }
}

將自定義的攔截器添加到SpringMVC的攔截器表中,使其生效:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
 
    // new出來的對象是無法直接注入IOC容器的(LoginInterceptor是直接new出來的)
    // 所以這里需要再配置類中注入,然后通過構(gòu)造器傳入到當(dāng)前類中
    @Resource
    private StringRedisTemplate stringRedisTemplate;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加登錄攔截器
        registry.addInterceptor(new LoginInterceptor())
                // 設(shè)置放行請求
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                ).order(1); // 優(yōu)先級默認(rèn)都是0,值越大優(yōu)先級越低
        // 添加刷新token的攔截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}
  • RefreshTokenInterceptor 先執(zhí)行,主要用來檢查 token 的有效性并刷新 token 的有效期,同時將用戶信息存入 ThreadLocal。
  • LoginInterceptor 后執(zhí)行,驗證 ThreadLocal 中是否有用戶信息,以確認(rèn)用戶是否登錄。

以上就是基于Redis實現(xiàn)短信驗證碼登錄功能的詳細內(nèi)容,更多關(guān)于Redis短信驗證碼登錄的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • redis實現(xiàn)分布式延時隊列的示例代碼

    redis實現(xiàn)分布式延時隊列的示例代碼

    延時隊列是一種特殊的消息隊列,它允許將消息在一定的延遲時間后再進行消費,延時隊列的實現(xiàn)方式可以有多種,本文主要來介紹一種redis實現(xiàn)的分布式延時隊列,希望對大家有所幫助
    2023-10-10
  • Redis實現(xiàn)信息已讀未讀狀態(tài)提示

    Redis實現(xiàn)信息已讀未讀狀態(tài)提示

    這篇文章主要介紹了Redis實現(xiàn)信息已讀未讀狀態(tài)提示的相關(guān)資料,需要的朋友可以參考下
    2016-04-04
  • 詳解如何在Windows上配置和使用Redis持久化功能

    詳解如何在Windows上配置和使用Redis持久化功能

    Redis 是一個強大的內(nèi)存數(shù)據(jù)庫,常用于緩存和實時數(shù)據(jù)處理,然而,由于其內(nèi)存特性,一旦服務(wù)器重啟或故障,存儲在 Redis 中的數(shù)據(jù)可能會丟失,為了確保數(shù)據(jù)的安全性和持久性,Redis 提供了多種持久化機制,本文將詳細介紹如何在 Windows 上配置和使用 Redis 的持久化功能
    2024-08-08
  • Redis與自定義注解實現(xiàn)重復(fù)方式

    Redis與自定義注解實現(xiàn)重復(fù)方式

    文章介紹了如何使用Redis和自定義注解`SubmitLock`來防止重復(fù)提交,首先,創(chuàng)建了一個注解`SubmitLock`,然后在需要防止重復(fù)提交的方法上使用該注解,最后,通過一個controller測試驗證了該功能的有效性
    2025-12-12
  • Redisson分布式鎖之加解鎖詳解

    Redisson分布式鎖之加解鎖詳解

    這篇文章主要為大家介紹了Redisson分布式鎖加解鎖的詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • 在Redis中存儲和操作JSON數(shù)據(jù)的方法詳解

    在Redis中存儲和操作JSON數(shù)據(jù)的方法詳解

    RedisJSON 是 Redis 的一個模塊,為 Redis 提供了 JSON 數(shù)據(jù)類型的支持,允許直接存儲、更新和查詢 JSON 文檔,本文給大家詳細介紹了RedisJSON 安裝部署及使用示例,需要的朋友可以參考下
    2025-07-07
  • Redis消息隊列實現(xiàn)異步秒殺功能

    Redis消息隊列實現(xiàn)異步秒殺功能

    在高并發(fā)場景下,為了提高秒殺業(yè)務(wù)的性能,可將部分工作交給 Redis 處理,并通過異步方式執(zhí)行,Redis 提供了多種數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)消息隊列,總結(jié)三種,本文詳細介紹Redis消息隊列實現(xiàn)異步秒殺功能,感興趣的朋友一起看看吧
    2025-04-04
  • SpringSession通過Redis統(tǒng)計在線用戶數(shù)量的實現(xiàn)代碼

    SpringSession通過Redis統(tǒng)計在線用戶數(shù)量的實現(xiàn)代碼

    這篇文章主要介紹了SpringSession通過Redis統(tǒng)計在線用戶數(shù)量,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Redis 如何批量設(shè)置過期時間(PIPLINE的使用)

    Redis 如何批量設(shè)置過期時間(PIPLINE的使用)

    有時候我們并不希望redis的key一直存在。例如緩存,驗證碼等數(shù)據(jù),我們希望它們能在一定時間內(nèi)自動的被銷毀。本文就詳細的介紹一下Redis 如何批量設(shè)置過期時間,感興趣的可以了解一下
    2021-11-11
  • Redis高效安全的遍歷所有key的幾種方法

    Redis高效安全的遍歷所有key的幾種方法

    在 Redis 中,遍歷所有的 key 是一個相對昂貴的操作,特別是當(dāng)你有大量的數(shù)據(jù)時,直接使用 KEYS 命令會導(dǎo)致阻塞,這對于大規(guī)模的 Redis 實例來說是非常不安全的,所以本文給大家介紹了幾種Redis高效安全的遍歷所有key的方法,需要的朋友可以參考下
    2025-08-08

最新評論

昌图县| 长阳| 肃北| 历史| 北辰区| 盐城市| 巴林右旗| 乌拉特中旗| 闽侯县| 阿克苏市| 富锦市| 苗栗县| 宿州市| 景洪市| 永平县| 寻乌县| 平顶山市| 运城市| 右玉县| 扬中市| 沅江市| 中宁县| 双辽市| 宝鸡市| 安西县| 犍为县| 资兴市| 同德县| 文昌市| 桐庐县| 云林县| 新野县| 阿城市| 盐池县| 铜鼓县| 沁水县| 荣昌县| 绥滨县| 和硕县| 海淀区| 唐山市|