# @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)
基于redis实现的数字验证码生成核验
最新推荐文章于 2025-03-30 22:32:36 发布