Redis解決“重試次數”場景的實現思路


很多地方都要用到重試次數限制,不然就會被暴力破解。比如登錄密碼。

下面不是完整代碼,只是偽代碼,提供一個思路。

第一種(先聲明,這樣寫有個bug)

import java.text.MessageFormat;

public class Demo {

    /**
     * 限制次數
     */
    private static final Integer MAX_TIMES = 5;
    /**
     * 鎖定時間(也是key的失效時間)
     */
    private static final Integer LIMIT_TIME = 3;
    /**
     * key
     */
    private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s";

    public void deal(String phone, String password){
        // 用戶id
        Long userId = 6815356L;
        // 組裝key
        String key = MessageFormat.format(LIMIT_TIMES_KEY, userId);
        // 先獲取key對應的value
        String s = redisService.get(key);
        int currentTimes = s != null ? Integer.parseInt(s) : 0;
        // 如果當前次數為[LIMIT_TIMES]次或以上,則拋異常
        if(currentTimes >= MAX_TIMES){
            throw new RuntimeException("請在"+ LIMIT_TIME +"分鍾后繼續嘗試");
        }

        // TODO 做其它邏輯,比如登錄操作
        Integer code = userService.login(phone, password);

        // 比如密碼不正確的狀態碼是10086
        if(code == 10086){
            // 失敗次數+1
            int thisTimes = currentTimes + 1;
            String value = String.valueOf(thisTimes);
            
            // 如果小於最大限制
            if(thisTimes < MAX_TIMES){
                redisService.set(key, value);
                throw new RuntimeException("原密碼錯誤,還可以重試"+ (MAX_TIMES - thisTimes) +"次");
            }else{
                redisService.setex(key, LIMIT_TIME*60, value);
                throw new RuntimeException("原密碼錯誤,請在"+ LIMIT_TIME +"分鍾后繼續嘗試");
            }
        }

        // 登陸成功,清理redis
        redisService.del(key);
    }
}

 

以上代碼思路:

以上代碼有什么問題呢:當失敗次數小於最大限制,那里直接set了一個值,沒有設置失效時間。如果用戶失敗了一次就不再嘗試了,那么我們設置的key就會永遠存在;同時用戶在n年后再去登陸,他擁有的重試次數是凌駕於n年前的重試次數之上的,也就是說我今年浪費了1次重試次數,還剩下4次,我明年再重試,我能夠重試的次數就不是5而是4了,因為我的重試次數記錄一直存在。

 

import java.text.MessageFormat;

public class Demo {

    /**
     * 限制次數
     */
    private static final Integer MAX_TIMES = 5;
    /**
     * 鎖定時間(也是key的失效時間)
     */
    private static final Integer LIMIT_TIME = 3;
    /**
     * key
     */
    private static final String LIMIT_TIMES_KEY = "LimitTimesKey:%s";

    public void deal(String phone, String password){
        // 用戶id
        Long userId = 6815356L;
        // 組裝key
        String key = MessageFormat.format(LIMIT_TIMES_KEY, userId);
        // 先獲取key對應的value
        String s = redisService.get(key);
        int currentTimes = s != null ? Integer.parseInt(s) : 0;
        // 如果當前次數為[LIMIT_TIMES]次或以上,則拋異常
        if(currentTimes >= MAX_TIMES){
            throw new RuntimeException("請在"+ LIMIT_TIME +"分鍾后繼續嘗試");
        }

        // TODO 做其它邏輯,比如登錄操作
        Integer code = userService.login(phone, password);

        // 比如密碼不正確的狀態碼是10086
        if(code == 10086){
            // 失敗次數+1
            int thisTimes = currentTimes + 1;
            String value = String.valueOf(thisTimes);
            // 設置值,重點是失效時間
            redisService.setex(key, LIMIT_TIME*60, value);
            // 如果小於最大限制
            if(thisTimes < MAX_TIMES){
                throw new RuntimeException("原密碼錯誤,還可以重試"+ (MAX_TIMES - thisTimes) +"次");
            }else{
                throw new RuntimeException("原密碼錯誤,請在"+ LIMIT_TIME +"分鍾后繼續嘗試");
            }
        }

        // 登陸成功,清理redis
        redisService.del(key);
    }
}

  

改進之后的思路如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM