基于redis实现的数字验证码生成核验

# @Time     :2022/3/13 0013 14:07
# @Author   :lionlone
# @FIle:    :vcs.py
# @Software :PyCharm
# @Describe :基于redis的验证码生成、校验系统, 可对验证码生成和校验做次数限制, 用于手机验证码、邮箱验证码的实现
import hashlib
import random
import string

from redis import Redis


class Vcs:

    def __init__(self,
                 conn: Redis,
                 busName: str,
                 codeLen: int = 6,
                 exp: int = 60,
                 gen_day_rate: int = 5,
                 maxAttempt: int = 3
                 ):
        """
        :param conn: redis的连接
        :param busName: 业务名称,用于区分不同业务的验证码,如邮箱验证、手机号验证、微信公众号自动回复等,或者是注册验证码、修改信息验证码
        :param codeLen: 自动生成的验证码长度
        :param exp: 验证码的有效期
        :param gen_day_rate: 单个key生成验证码次数的日限额
        :param maxAttempt: 每次发送的验证码,可以尝试的最大次数
        """
        self._conn = conn
        self._codeLen = codeLen
        self._exp = exp
        self._gen_day_rate = gen_day_rate
        self._maxAttempt = maxAttempt
        self._templates = {
            'val': 'TOOLS:VCS:VAL:' + busName + ':{key}',  # 记录验证码的值
            'at': 'TOOLS:VCS:AT:' + busName + ':{key}',  # 记录验证码的尝试的次数
            'gt': 'TOOLS:VCS:GT:' + busName + ':{key}',  # 每个key生成验证码的日频率
        }

    def setting(self):
        pass

    def _gen_code_core(self, key):
        code = ''.join(random.sample('1234567890', self._codeLen))
        nonce = ''.join(random.sample(string.ascii_letters, 10))
        requestId = hashlib.md5((key+nonce).encode()).hexdigest()
        return code, requestId

    def gen_code(self, key: str):
        k_val = self._templates['val'].format(key=key)
        k_at = self._templates['at'].format(key=key)
        k_gt = self._templates['gt'].format(key=key)
        # 限制日频率
        gen_times = self._conn.get(k_gt)
        if gen_times is None:  # 若不存在日限额记录,则为新key
            self._conn.setex(k_gt, 86400, 1)
            code, requestId = self._gen_code_core(key)
            # 写入redis
            self._conn.setex(k_val, self._exp, requestId+code)
            self._conn.setex(k_at, self._exp, 0)
        elif self._conn.exists(k_val):  # 对于已存在的key,优先判断其是否处于上一个验证码生存周期内
            raise GenFastLiveError(self._conn.ttl(k_val))
        elif int(gen_times) < self._gen_day_rate:
            self._conn.incr(k_gt)
            # 生成验证码及requestId
            code, requestId = self._gen_code_core(key)
            # 写入redis
            self._conn.setex(k_val, self._exp, requestId+code)
        else:
            raise GenFastDayError
        return code, requestId

    def verify_code(self, key, requestId, code):
        k_val = self._templates['val'].format(key=key)
        k_at = self._templates['at'].format(key=key)
        data = self._conn.get(k_val)
        if data is None:  # 验证码已过期或不存在
            raise ExpiredNoExistError
        if not self._conn.exists(k_at):
            raise UnExceptedError("attempt_times should not be None")
        attempt_times = int(self._conn.get(k_at))
        if attempt_times >= self._maxAttempt:
            raise MaxAttemptError
        elif data != requestId + code:
            self._conn.incr(k_at)
            raise WrongCodeError(self._maxAttempt - attempt_times - 1)
        else:
            rdc.delete(k_val, k_at)


class GenFastLiveError(Exception):
    leave = 0

    def __init__(self, leave: int):
        self.leave = leave

    def __str__(self):
        return "leave out %d sec" % self.leave


class GenFastDayError(Exception):

    def __str__(self):
        return "too many times to generate code today"


class ExpiredNoExistError(Exception):

    def __str__(self):
        return "verification code might be expired or no exist"


class WrongCodeError(Exception):
    leave = 0

    def __str__(self):
        return "verification code is wrong, you can try %d times" % self.leave

    def __init__(self, leave: int):
        self.leave = leave


class MaxAttemptError(Exception):

    def __str__(self):
        return 'your has no chance to attempt'


class UnExceptedError(Exception):
    pass


if __name__ == '__main__':
    from _config import rdc
    te_vcs = Vcs(conn=rdc, busName="test", gen_day_rate=3, exp=60)
    tk = ''.join(random.sample('0123456789', 10))
    code, rqid = te_vcs.gen_code(tk)
    print(code)
    while True:
        try:
            te_vcs.verify_code(tk, rqid, input('>>'))
            print('验证成功')
            break
        except WrongCodeError as e:
            print(e)
        except ExpiredNoExistError as e:
            print(e)
        except MaxAttemptError as e:
            print(e)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值