VerifyCoder.class

/**
 * 用于简化与验证码相关的操作,如发送验证码、校验验证码
 *
 * @author NightDW 2022/3/2 16:05
 */
@SuppressWarnings("All")
public abstract class AbstractVerifyCoder {

    /** 默认的key的前缀,以及默认的项目名称 */
    private static final String VERIFY_CODE_KEY_PREFIX = "VERIFY_CODER:VerifyCode";
    private static final String FREQUENT_CHECK_ONLY_CHECK_MODE_KEY_PREFIX = "VERIFY_CODER:FrequentCheck_OnlyCheckMode";
    private static final String FREQUENT_CHECK_DELETE_IF_CORRECT_KEY_PREFIX = "VERIFY_CODER:FrequentCheck_DeleteIfCorrectMode";
    private static final String DEFAULT_PROJECT_ID = "verify-code-project";


    // =================================================================================================================


    /** 新增一个键值对(一般是在NoSql上),并设置其过期时间 */
    protected abstract void set(String key, String value, int expireSeconds);

    /** 获取key的剩余过期时间,返回null则说明key不存在 */
    protected abstract Long getRemainExpire(String key);

    /** 获取key对应的value */
    protected abstract String get(String key);

    /** 删除key */
    protected abstract void delete(String key);

    /** 发送验证码(比如通过短信方式等),返回值代表是否发送成功 */
    protected abstract boolean doSendVerifyCode(String receiver, String codeType, String verifyCode);


    // =================================================================================================================


    /** 获取项目的id;项目id将作为key的一部分;建议重写该方法,避免多个项目的验证码功能相互干扰 */
    protected String getProjectId() { return DEFAULT_PROJECT_ID; }

    /** 验证码的过期时间 */
    protected int getVerifyCodeExpireSeconds() { return 15 * 60; }

    /** 发送验证码后,经过多久才可以再次发送;该值不能大于验证码的过期时间 */
    protected int getResendGapSeconds() { return 60; }

    /** 校验验证码失败后,经过多久才可以再次校验 */
    protected int getRecheckGapSeconds() { return 3; }

    /** 生成验证码 */
    protected String generateVerifyCode() { return String.valueOf((int)(Math.random() * Math.pow(10, 6))); }

    /** 获取key的前缀 */
    protected String getVerifyCodeKeyPrefix() { return VERIFY_CODE_KEY_PREFIX; }
    protected String getFrequentCheckOnlyCheckModeKeyPrefix() { return FREQUENT_CHECK_ONLY_CHECK_MODE_KEY_PREFIX; }
    protected String getFrequentCheckDeleteIfCorrectKeyPrefix() { return FREQUENT_CHECK_DELETE_IF_CORRECT_KEY_PREFIX; }


    // =================================================================================================================


    /** 生成验证码,并向接收者发送验证码消息 */
    public final void sendVerifyCode(String codeReceiver, String codeType) {
        String verifyCodeKey = getRedisKey(getVerifyCodeKeyPrefix(), getProjectId(), codeType, codeReceiver);
        int initExpire = getVerifyCodeExpireSeconds();
        int resendGap = getResendGapSeconds();

        Long remainExpire = getRemainExpire(verifyCodeKey);
        if (remainExpire != null && (initExpire - remainExpire < resendGap)) {
            throw new RuntimeException(resendGap + "秒内只能发送一次验证码!");
        }

        String verifyCode = generateVerifyCode();
        if (doSendVerifyCode(codeReceiver, codeType, verifyCode)){
            set(verifyCodeKey, verifyCode, initExpire);
        } else {
            throw new RuntimeException("验证码发送失败!");
        }
    }

    public final boolean checkVerifyCode(String codeReceiver, String codeType, String code, boolean deleteIfCorrect) {
        if (isBlank(code)) {
            return false;
        }

        String verifyCodeKey = getRedisKey(getVerifyCodeKeyPrefix(), getProjectId(), codeType, codeReceiver);
        String trueCode = get(verifyCodeKey);
        if (isBlank(trueCode)) {
            throw new RuntimeException("验证码过期,或者未发送验证码!");
        }

        String frequentCheckKey = getRedisKey(deleteIfCorrect ? getFrequentCheckDeleteIfCorrectKeyPrefix() : getFrequentCheckOnlyCheckModeKeyPrefix(), getProjectId(), codeType, codeReceiver);
        int recheckGap = getRecheckGapSeconds();
        if (!isBlank(get(frequentCheckKey))) {
            throw new RuntimeException("校验操作频繁,请在" + recheckGap + "秒后重试!");
        }

        if (!code.equals(trueCode)) {
            set(frequentCheckKey, "校验cd重置中", recheckGap);
            return false;
        }

        if (deleteIfCorrect) {
            delete(verifyCodeKey);
        }
        return true;
    }


    // =================================================================================================================


    private static String getRedisKey(String prefix, String projectId, String codeType, String codeReceiver) {
        return prefix + ":" + projectId + ":" + codeType + ":" + codeReceiver;
    }

    private static boolean isBlank(final CharSequence cs) {
        if (cs == null) return true;

        final int strLen = cs.length();
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * 使用Redis作为NoSql的VerifyCoder
 *
 * @author NightDW 2022/3/3 16:06
 */
public abstract class RedisVerifyCoder extends AbstractVerifyCoder {

    protected abstract RedisTemplate<String, String> getRedisTemplate();

    @Override
    protected final void set(String key, String value, int expireSeconds) {
        getRedisTemplate().opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
    }

    @Override
    protected final Long getRemainExpire(String key) {
        return getRedisTemplate().getExpire(key);
    }

    @Override
    protected final String get(String key) {
        return getRedisTemplate().opsForValue().get(key);
    }

    @Override
    protected final void delete(String key) {
        getRedisTemplate().delete(key);
    }
}
全部评论

相关推荐

03-31 14:46
已编辑
门头沟学院 Web前端
励志成为双港第一ja...:这其实很正常,离的太远了,他认为你不会来,就为了混个面试,而且成本很高,实习生都优先选本地高校。吃了地域的亏,所有很多时候地域可能比院校层次更重要。
点赞 评论 收藏
分享
03-12 15:35
嘉应学院 Python
快说谢谢牛牛精灵:说不定就是下一个寒武纪!
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务