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

Redis進(jìn)行驗(yàn)證碼登錄的項(xiàng)目實(shí)踐

 更新時(shí)間:2025年09月07日 14:18:52   作者:雞蛋灌Bean  
本文主要介紹了Redis進(jìn)行驗(yàn)證碼登錄的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

1.基于Session進(jìn)行對(duì)驗(yàn)證碼等信息進(jìn)行存儲(chǔ)

我們?nèi)绻獙?shí)現(xiàn)一個(gè)驗(yàn)證碼的收發(fā)校驗(yàn)登錄的模塊,可以基于Session來(lái)進(jìn)行對(duì)信息的存儲(chǔ),然后通過(guò)對(duì)這些信息的校驗(yàn)來(lái)完成對(duì)驗(yàn)證碼的校驗(yàn)

首先要了解發(fā)送驗(yàn)證碼的邏輯

注意:以下內(nèi)容所有帶Utill的類都是hutool包下的類,在使用時(shí)導(dǎo)入依賴即可使用

1.1發(fā)送驗(yàn)證碼

流程如下:

在controller中定義方法,傳參為前端的手機(jī)號(hào)和session,其中session是儲(chǔ)存信息的容器,可以實(shí)現(xiàn)存儲(chǔ)用戶,手機(jī)號(hào)等信息

    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        //發(fā)送短信驗(yàn)證碼并保存驗(yàn)證碼
        return userService.sendCode(phone,session);
    }

在service里定義方法后在實(shí)現(xiàn)類中實(shí)現(xiàn)

根據(jù)流程來(lái)分步實(shí)現(xiàn),這里發(fā)送驗(yàn)證碼的用到了Hutool包里的生成隨機(jī)數(shù)來(lái)生成驗(yàn)證碼。

苒后利用一個(gè)Utils包里的lambda表達(dá)式來(lái)判斷手機(jī)號(hào)格式

 /**
     * 是否是無(wú)效手機(jī)格式
     * @param phone 要校驗(yàn)的手機(jī)號(hào)
     * @return true:符合,false:不符合
     */
    public static boolean isPhoneInvalid(String phone){
        return mismatch(phone, RegexPatterns.PHONE_REGEX);
    }
    /**
 /**
     * 手機(jī)號(hào)正則
     */
    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ā)送驗(yàn)證碼可以利用阿里云來(lái)對(duì)手機(jī)號(hào)進(jìn)行發(fā)送,但是我們主要講邏輯,就不實(shí)現(xiàn)這個(gè)功能了,直接用日志將驗(yàn)證碼打印過(guò)來(lái)

 /**
     * 發(fā)送短信驗(yàn)證碼
     * @return
     */
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校驗(yàn)手機(jī)號(hào)

        if(RegexUtils.isPhoneInvalid(phone)){
            //不符合
            return Result.fail("手機(jī)號(hào)格式錯(cuò)誤");
        }
        //符合
        //2.生成驗(yàn)證碼
        String code = RandomUtil.randomNumbers(6);
        //3.保存驗(yàn)證碼到seesion
        session.setAttribute("code",code);
        //4.發(fā)送驗(yàn)證碼
        log.debug("發(fā)送成功,驗(yàn)證碼為:{}", code);


        return Result.ok();
    }

苒后利用session來(lái)儲(chǔ)存驗(yàn)證碼

1.2校驗(yàn)驗(yàn)證碼并登錄

流程如下:

 校驗(yàn)驗(yàn)證碼邏輯較為復(fù)雜,首先要定義方法

其中l(wèi)oginFormDTO里傳入的就是前端的參數(shù),如手機(jī)號(hào),驗(yàn)證碼等信息

    /**
     * 登錄功能
     * @param loginForm 登錄參數(shù),包含手機(jī)號(hào)、驗(yàn)證碼;或者手機(jī)號(hào)、密碼
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        //實(shí)現(xiàn)登錄功能
        return userService.login(loginForm,session);
    }

然后在實(shí)現(xiàn)類里實(shí)現(xiàn)

其中我們先將手機(jī)號(hào)從loginForm里取出來(lái)再進(jìn)行判斷

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校驗(yàn)手機(jī)號(hào)
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            //不一致
            return Result.fail("手機(jī)號(hào)格式錯(cuò)誤");
        }
        //一致
        //2.校驗(yàn)驗(yàn)證碼
        //先在session中取出驗(yàn)證碼
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();//前端提交的code
        //不一致
        if(cacheCode==null || !cacheCode.toString().equals(code)){
            return Result.fail("驗(yàn)證碼已過(guò)期或錯(cuò)誤");
        }
            //重新提交驗(yàn)證碼
        //一致
        //3.根據(jù)手機(jī)號(hào)查詢用戶
        User user = query().eq("phone",phone).one();
        //用戶不存在
        if(user==null){
            //創(chuàng)建新用戶并保存到數(shù)據(jù)庫(kù)
            user =  creatUserWithPhone(phone);
            //保存用戶到session 在最后一步完成
        }

        //用戶存在
        //保存用戶到session
        //將user轉(zhuǎn)為dto儲(chǔ)存
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

        return Result.ok();
    }

我們將驗(yàn)證碼從session里取出來(lái)和前端得到的驗(yàn)證碼進(jìn)行對(duì)比:cacheCode對(duì)比code

再后利用mybatisplus進(jìn)行查詢用戶存入到user中

然后判斷用戶是否存在,如果不存在,就證明是新用戶

新用戶創(chuàng)建我們利用一個(gè)方法creatUserWithPhone來(lái)根據(jù)手機(jī)號(hào)來(lái)進(jìn)行創(chuàng)建

private User creatUserWithPhone(String phone) {

        //1.創(chuàng)建用戶
        //由于新用戶大部分創(chuàng)建時(shí)都有默認(rèn)參數(shù),因此我們只需要將手機(jī)號(hào)和昵稱傳入即可,昵稱隨機(jī)
        //邏輯和大部分創(chuàng)建新用戶的軟件邏輯一樣
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));

        //2.保存到數(shù)據(jù)庫(kù)
        save(user);
        return null;
    }

 創(chuàng)建以后用mp存入到數(shù)據(jù)庫(kù)中

如果存在就將其轉(zhuǎn)為dto存入到session中以便后續(xù)使用

1.3攔截校驗(yàn)

實(shí)現(xiàn)了基本邏輯業(yè)務(wù)后,就可以實(shí)現(xiàn)進(jìn)行攔截校驗(yàn)了

先寫一個(gè)攔截器,基本業(yè)務(wù)是:在每次請(qǐng)求處理之前獲取session里的用戶,如果用戶不存在,也就是在之前的登錄中沒(méi)有登錄,即沒(méi)有存入用戶信息,就進(jìn)行攔截,返回401

然后在請(qǐng)求處理之后移除用戶信息,節(jié)約內(nèi)存開(kāi)銷

//校驗(yàn)登錄狀態(tài)
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //首先由于請(qǐng)求里面帶有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();
    }
}

然后在配置類里注冊(cè)這個(gè)攔截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    //配置攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(new LoginInterceptor())
               .excludePathPatterns(
                //排除一些路徑,不需要攔截的地方
                       //如一些頁(yè)面可以不需要登錄校驗(yàn)
                       "/user/code",
                       "/user/login",
                       "/blog/hot",//查看熱點(diǎn),也與登錄無(wú)關(guān),可以放行
                       "/shop/**",
                       "/shop-type/**",
                       "/upload/**",
                       "/voucher/**"

               );
    }

2.用Redis實(shí)現(xiàn)對(duì)驗(yàn)證碼信息校驗(yàn)

我們前面使用Httpsession來(lái)進(jìn)行儲(chǔ)存用戶信息和驗(yàn)證碼,但是使用session也有很多問(wèn)題

主要是session的共享問(wèn)題:

如果我們使用了tomcat集群來(lái)部署項(xiàng)目,多臺(tái)Tomcat并不共享session存儲(chǔ)空間,當(dāng)請(qǐng)求切換到不同的tomcat服務(wù)時(shí)導(dǎo)致數(shù)據(jù)丟失的問(wèn)題;

而session的替代方案應(yīng)該滿足:

1.數(shù)據(jù)共享

2.內(nèi)存存儲(chǔ)

3.key,value架構(gòu)

首先保存的基本思想是用手機(jī)號(hào)作為key來(lái)存儲(chǔ),因?yàn)橐WC每個(gè)用戶的key都不同

我們一般用String結(jié)構(gòu),以Json字符串來(lái)保存

或者使用hash結(jié)構(gòu)來(lái)進(jìn)行保存,利用hash結(jié)構(gòu)將對(duì)象中的每個(gè)字段獨(dú)立儲(chǔ)存,可以針對(duì)單個(gè)字段進(jìn)行CRUD,并且內(nèi)存占用更少

我們以hash結(jié)構(gòu)為例
我們只需要在原來(lái)的儲(chǔ)存session步驟上替換為存儲(chǔ)到Redis里即可

邏輯如下:

注意:

但是在校驗(yàn)驗(yàn)證碼登錄注冊(cè)時(shí),我們儲(chǔ)存用戶信息的key可以使用隨機(jī)生成的token,值為json對(duì)象,為了防止泄漏風(fēng)險(xiǎn),然后判斷用戶是否存在時(shí)如果存在,就報(bào)錯(cuò)用戶到Redis里,然后返回token給前端,不存在也在創(chuàng)建新用戶后返回token給前端

而且session仍然有用,就是在request請(qǐng)求頭中的token里的session就存儲(chǔ)的有用戶信息,作為校驗(yàn)登錄狀態(tài)時(shí)判斷用戶是否存在的依據(jù),就是在登錄或者注冊(cè)后得到的token獲取用戶信息

2.1發(fā)送驗(yàn)證碼時(shí)存儲(chǔ)到Redis邏輯

我們之前已經(jīng)實(shí)現(xiàn)了發(fā)送驗(yàn)證碼的功能,我們只需要修改存儲(chǔ)驗(yàn)證碼到session這個(gè)功能即可

在pom文檔里導(dǎo)入Redis客戶端依賴:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

首先導(dǎo)入RedisTemplete實(shí)現(xiàn)API,這里用@Resource和@Autowired進(jìn)行注入都可以

一個(gè)是針對(duì)類名裝配,一個(gè)針對(duì)類型裝配

    @Resource
    private StringRedisTemplate stringRedisTemplate;

然后修改業(yè)務(wù)邏輯

        //3.保存驗(yàn)證碼到seesion
//        session.setAttribute("code",code);
        //在key里加上一個(gè)前綴來(lái)和其他業(yè)務(wù)里的phone來(lái)區(qū)分
        //然后設(shè)置一個(gè)有效期
        stringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);

2.2校驗(yàn)驗(yàn)證碼實(shí)現(xiàn)登錄注冊(cè)邏輯

首先就是修改從session中獲取驗(yàn)證碼進(jìn)行校驗(yàn)的邏輯

 //從Redis獲取驗(yàn)證碼并校驗(yàn)
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
        String code = loginForm.getCode();//前端提交的code

然后就是修改保存用戶信息到redis中這些邏輯

我們把用戶信息設(shè)置為哈希類型,把號(hào)碼設(shè)置為string類型,因?yàn)楣n愋涂梢员WC用戶信息等隱私

        //保存用戶到Redis中

        //3.1生成token作為登錄令牌
        String token = UUID.randomUUID().toString(true);
        //3.2將USer對(duì)象轉(zhuǎn)為HashMap存儲(chǔ)
        UserDTO userDTO= BeanUtil.copyProperties(user,UserDTO.class);
        Map<String,Object> userMap= BeanUtil.beanToMap(userDTO);
        //3.3存儲(chǔ),利用putALL,key可以是多個(gè)值,即hashMap,然后設(shè)置有效期
        String tokenKey= LOGIN_CODE_KEY+token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        //設(shè)置有效期
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //但是還有一個(gè)問(wèn)題,就是有效期的邏輯是,只要是在有效期內(nèi)無(wú)論進(jìn)行訪問(wèn),只要有效期到了就失效,不會(huì)重置有效期
        //因此我們要進(jìn)行修改邏輯,修改為,只要三十分鐘內(nèi)訪問(wèn)過(guò)了就重置有效期,又可以過(guò)三十分鐘過(guò)期

        //我們就可以利用攔截器來(lái)實(shí)現(xiàn),只要訪問(wèn)了請(qǐng)求,即觸發(fā)了攔截器,就實(shí)現(xiàn)更新有效期的操作
        //8.返回token給前端
        return Result.ok();

然后修改校驗(yàn)登錄狀態(tài)時(shí)的從redis獲取用戶邏輯

如圖,然后在每次校驗(yàn)時(shí)也要刷新token有效期

//1.獲取請(qǐng)求頭中的token,根據(jù)前端返回的名字來(lái)獲取
        String token= request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            response.setStatus(401);
            return false;
        }


        //2.基于token獲取redis中的用戶,利用entries來(lái)獲取所有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對(duì)象
       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進(jìn)行驗(yàn)證碼登錄的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Redis 驗(yàn)證碼登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫)

    高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫)

    本文主要介紹了高并發(fā)下Redis如何保持?jǐn)?shù)據(jù)一致性(避免讀后寫),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 如何解決Redis緩存穿透(緩存空對(duì)象、布隆過(guò)濾器)

    如何解決Redis緩存穿透(緩存空對(duì)象、布隆過(guò)濾器)

    緩存穿透是一個(gè)常見(jiàn)的問(wèn)題,它發(fā)生當(dāng)請(qǐng)求的數(shù)據(jù)既不在緩存中也不在數(shù)據(jù)庫(kù)中,文章通過(guò)一個(gè)查詢商品店鋪的案例,展示了如何結(jié)合這兩種方法來(lái)避免緩存穿透,首先利用布隆過(guò)濾器過(guò)濾掉不存在的id,對(duì)于誤判的情況,則采用緩存空對(duì)象的策略進(jìn)行補(bǔ)救
    2024-11-11
  • redis8.0新特性之t-digest計(jì)算數(shù)據(jù)百分位的使用

    redis8.0新特性之t-digest計(jì)算數(shù)據(jù)百分位的使用

    t-digest是一種概率型數(shù)據(jù)結(jié)構(gòu),用于使用緊湊的sketch估計(jì)數(shù)據(jù)流或大型數(shù)據(jù)集的百分位數(shù),下面就來(lái)介紹了t-digest計(jì)算數(shù)據(jù)百分位的使用,感興趣的可以了解一下
    2025-08-08
  • redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解

    redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解

    在使用redis時(shí),需要保持redis和數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性,最流行的解決方案之一就是延時(shí)雙刪策略,今天我們就來(lái)詳細(xì)刨析一下,需要的朋友可以參考下
    2023-09-09
  • Redis模擬延時(shí)隊(duì)列實(shí)現(xiàn)日程提醒的方法

    Redis模擬延時(shí)隊(duì)列實(shí)現(xiàn)日程提醒的方法

    文章介紹了如何使用Redis實(shí)現(xiàn)一個(gè)簡(jiǎn)單的延時(shí)任務(wù)隊(duì)列,通過(guò)Redis的有序集合特性來(lái)存儲(chǔ)和管理延時(shí)任務(wù),通過(guò)定期檢查集合中小于等于當(dāng)前時(shí)間的任務(wù)并執(zhí)行,可以實(shí)現(xiàn)延時(shí)任務(wù)的管理,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • Redis哨兵模式的實(shí)現(xiàn)

    Redis哨兵模式的實(shí)現(xiàn)

    Redis的哨兵模式是一種用于自動(dòng)監(jiān)控Redis實(shí)例狀態(tài)并在主服務(wù)器出現(xiàn)故障時(shí)自動(dòng)切換到從服務(wù)器的機(jī)制,本文主要介紹了Redis哨兵模式的實(shí)現(xiàn),感興趣的可以了解一下
    2024-02-02
  • window環(huán)境redis通過(guò)AOF恢復(fù)數(shù)據(jù)的方法

    window環(huán)境redis通過(guò)AOF恢復(fù)數(shù)據(jù)的方法

    這篇文章主要介紹了window環(huán)境redis通過(guò)AOF恢復(fù)數(shù)據(jù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • 基于Redis實(shí)現(xiàn)登錄功能思路詳解(手機(jī)號(hào)+驗(yàn)證碼)

    基于Redis實(shí)現(xiàn)登錄功能思路詳解(手機(jī)號(hào)+驗(yàn)證碼)

    本文介紹了使用手機(jī)號(hào)和驗(yàn)證碼登錄的方式,驗(yàn)證碼通過(guò)控制臺(tái)輸出,重點(diǎn)解釋了UserServiceImpl實(shí)現(xiàn)類的結(jié)構(gòu),包括sendCode、login和createUserWithPhone方法,還介紹了攔截器框架,包括preHandle和攔截器鏈的配置,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • 使用Redis實(shí)現(xiàn)記錄訪問(wèn)次數(shù)的三種方案

    使用Redis實(shí)現(xiàn)記錄訪問(wèn)次數(shù)的三種方案

    這篇文章主要介紹了使用Redis實(shí)現(xiàn)記錄訪問(wèn)次數(shù)的三種方案,文中通過(guò)代碼示例和圖文講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-09-09
  • redis如何查看鎖是否存在

    redis如何查看鎖是否存在

    文章介紹了兩種方法來(lái)檢查Redis鎖的狀態(tài):使用GET命令查看鎖的值和使用EXISTS命令檢查鎖的存在性,這兩種方法都是通過(guò)連接到Redis服務(wù)器并執(zhí)行相應(yīng)的命令來(lái)實(shí)現(xiàn)的,GET命令用于獲取指定鍵的值,而EXISTS命令用于檢查指定鍵是否存在
    2025-01-01

最新評(píng)論

阜宁县| 沙洋县| 沁阳市| 祁东县| 阿瓦提县| 讷河市| 浦县| 嘉义市| 霍州市| 福建省| 吉安市| 河南省| 望城县| 沁阳市| 天峻县| 札达县| 平凉市| 海南省| 许昌县| 明溪县| 深泽县| 黎城县| 沽源县| 和政县| 汕尾市| 宜黄县| 酉阳| 红河县| 砀山县| 全州县| 黔西| 普兰店市| 化德县| 南漳县| 新闻| 泸定县| 高淳县| 康定县| 斗六市| 淮南市| 保靖县|