注册的逻辑
图片验证码的作用是防止短信发送的浪费,但是背后真的那么简单吗?想多了,下面讲一下细节。这是代码===>
class RegisterForm(forms.Form):
username = forms.CharField(label='用户名', max_length=20, min_length=5,
error_messages={"min_length": "用户名长度要大于5",
"max_length": "用户名长度要小于20",
"required": "用户名不能为空"}
)
password = forms.CharField(label='密码', max_length=20, min_length=6,
error_messages={"min_length": "密码长度要大于6",
"max_length": "密码长度要小于20",
"required": "密码不能为空"}
)
password_repeat = forms.CharField(label='确认密码', max_length=20, min_length=6,
error_messages={"min_length": "密码长度要大于6",
"max_length": "密码长度要小于20",
"required": "密码不能为空"}
)
mobile = forms.CharField(label='手机号', max_length=11, min_length=11,
error_messages={"min_length": "手机号长度有误",
"max_length": "手机号长度有误",
"required": "手机号不能为空"})
sms_code = forms.CharField(label='短信验证码', max_length=6, min_length=6,
error_messages={"min_length": "短信验证码长度有误",
"max_length": "短信验证码长度有误",
"required": "短信验证码不能为空"})
# 上面是简单清洗,下面是再次清洗
def clean_username(self):
"""
check mobile
:return:
"""
uname = self.cleaned_data.get('username') # 拿到数据
if Users.objects.filter(username=uname).exists(): # 数据库里面判断是不是有
raise forms.ValidationError("用户名已注册,请重新输入!") # exists 有就返回false没有就返回Ture
return uname
def clean_mobile(self):
tel = self.cleaned_data.get('mobile') # 获取手机号
if not re.match(r"^1[3-9]\d{9}$", tel):
raise forms.ValidationError("手机号码格式不正确")
if Users.objects.filter(mobile=tel).exists():
raise forms.ValidationError("手机号已注册,请重新输入!")
return tel
def clean(self):
"""
"""
# 继承父类的clean方法, 获取提交的表单数据
cleaned_data = super().clean()
passwd = cleaned_data.get('password')
passwd_repeat = cleaned_data.get('password_repeat')
# 密码判断
if passwd != passwd_repeat:
raise forms.ValidationError("两次密码不一致")
# 拿到手机号
tel = cleaned_data.get('mobile')
# 拿到短信验证码
sms_text = cleaned_data.get('sms_code') # 字典类型通过键取值
# 建立redis连接
redis_conn = get_redis_connection(alias='verify_codes')
# 创建用户输入的手机号标志
sms_fmt = "sms_{}".format(tel).encode('utf8')
# 从数据库里面拿出手机号,名字是一样的
real_sms = redis_conn.get(sms_fmt) # 那边的数据也是这样写入数据库的,不是只写了手机号进数据库。
# 判断是否一致
if (not real_sms) or (sms_text != real_sms.decode('utf8')):
raise forms.ValidationError("短信验证码错误")
首先图片验证码已开始加载的时候放在哪里?当用户点入注册页面,会自动生成图片验证码,并且保存在redis中,为了保证图片key和其他用户的不一致,前端会传一个参数uuid,这样redis里面就保存了一个这样的键指对了。
img_aksjdknwas(uuid): 9821(图片验证码)
class ImageCode(View):
def get(self, request, image_code_id):
text, image = captcha.generate_captcha()
conn_redis = get_redis_connection('verify_codes')
img_key = "img_{}".format(image_code_id).encode('utf8')
conn_redis.setex(img_key, constants.IMAGE_CODE_REDIS_EXPIRES, text)
logger.info("Image code:{}".format(text))
return HttpResponse(content=image, content_type='image/jpg')
那什么时候拿出来呢?当然是点击注册的时候要验证,就要拿出来。
form = forms.CheckImgCodeForm(data=dict_data)
下面是验证图片验证码的正确性,要去redis里面查出来,同时生成一个手机号60s内不能再次发生的标志保存进redis数据库。
class CheckImgCodeForm(forms.Form):
"""
check image code
"""
mobile = forms.CharField(max_length=11, min_length=11, validators=[mobile_validator, ],
error_messages={"min_length": "手机号不能为空",
"max_length": "手机号不能为空",
"required": "手机号不能为空",
})
image_code_id = forms.UUIDField(error_messages={"required": "图片UUID不能为空"})
text = forms.CharField(max_length=4, min_length=4,
error_messages={"min_length": "图片验证码长度有误",
"max_length": "图片验证码长度有误",
"required": "图片验证码不能为空",
})
def clean(self):
cleaned_data = super().clean()
image_text = cleaned_data.get('text')
mobile_num = cleaned_data.get('mobile')
image_uuid = cleaned_data.get('image_code_id')
if Users.objects.filter(mobile=mobile_num).count():
raise forms.ValidationError("手机号已经注册,请重新输入!")
# 确保settings.py文件中有配置redis CACHE
# Redis原生指令参考 http://redisdoc.com/index.html
# Redis python客户端 方法参考 http://redis-py.readthedocs.io/en/latest/#indices-and-tables
# 1.获取图片验证码
try:
con_redis = get_redis_connection(alias='verify_codes')
except Exception as e:
raise forms.ValidationError("未知错误")
img_key = "img_{}".format(image_uuid).encode('utf8')
real_image_code_origin = con_redis.get(img_key)
real_image_code = real_image_code_origin.decode('utf-8') if real_image_code_origin else None
con_redis.delete(img_key)
# 2. 验证手机号
if (not real_image_code) or image_text.upper() != real_image_code:
raise forms.ValidationError("图片验证失败")
# 3.检查是否在60s内有发送记录
sms_flag_fmt = "sms_flag_{}".format(mobile_num).encode('utf8')
sms_flag = con_redis.get(sms_flag_fmt)
if sms_flag:
raise forms.ValidationError("获取手机短信验证码过于频繁")
class CheckUsernameView(View):
"""
GET username/(?\w{5,20})/
"""
def get(self, request, username):
count = Users.objects.filter(username=username).count()
data = {
'username': username,
'count': count,
}
return to_json_data(data=data)
class CheckMobileView(View):
"""
GET mobiles/(?P1[3-9]\d{9})/
"""
def get(self, request, mobile):
# count = User.objects.filter(mobile=mobile).count()
data = {
'mobile': mobile,
'count': Users.objects.filter(mobile=mobile).count()
}
return to_json_data(data=data)
上面的代码是检查数据库中是否有一样的手机号和用户名。
下面可以发送短信验证码了吧,烦死了!!!
生产短信验证码,保存在redis数据库里,这里保存的话用到了redis的pipeline技术和redis进行交互。然后通知平台进行发送(这里用了celery技术)。
class SmsCodesView(View):
"""
1,获取参数
2,验证参数
3,发送信息
4,保存短信验证码
5,返回给前端
POST /sms_codes/
-检查图片验证码是否正确
-检查是否60秒有记录
-生成短信验证码
-发送短信
"""
# 1. 获取参数,
def post(self, request): # 表单
json_data = request.body # body里面包含的是什么,body就是前台给到后台的数据,ajax发过来的数据
if not json_data:
return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR]) # 报错罢了
dict_data = json.loads(json_data.decode('utf8'))
form = forms.CheckImgCodeForm(data=dict_data)
# 2. 校验参数
if form.is_valid():
# 获取手机号
mobile = form.cleaned_data.get('mobile')
# 创建短信验证码内容
sms_num = "%06d" % random.randint(0, 999999)
# 1.将短信验证码保存到数据库
# 确保settings.py文件中有配置redis CACHE
# Redis原生指令参考 http://redisdoc.com/index.html
# Redis python客户端 方法参考 http://redis-py.readthedocs.io/en/latest/#indices-and-tables
con_redis = get_redis_connection(alias='verify_codes')
# 创建一个在60秒内是否发送记录的标记
sms_flag_fmt = "sms_flag_{}".format(mobile).encode('utf8')
# 创建保存短信验证码的标记key
sms_text_fmt = "sms_{}".format(mobile).encode('utf8')
# 节省通讯次数
pl = con_redis.pipeline() # redis管道技术
try:
pl.setex(sms_flag_fmt, constants.SEND_SMS_CODE_INTERVAL, 1) # 60秒的标志,1号模板
pl.setex(sms_text_fmt, constants.IMAGE_CODE_REDIS_EXPIRES, sms_num) # 把短信验证码保存进去了
# 通知redis执行命令
pl.execute()
except Exception as e:
logger.debug('redis 执行异常{}'.format(e))
return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])
# 2.发送短信 通知平台
logger.info('SMS code:{}'.format(sms_num))
expires = constants.SMS_CODE_REDIS_EXPIRES
sms_tasks.send_sms_code.delay(mobile, sms_num, expires, constants.SMS_CODE_TEMP_ID)
return to_json_data(errno=Code.OK, errmsg="短信验证码发送成功")
else:
# 定义错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list)
return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
用户收到短信验证码后填入就大概注册完成了。
https://blog.csdn.net/weixin_43673156/article/details/123981998
celery异步任务
sms_tasks.send_sms_code.delay(mobile, sms_num, expires, constants.SMS_CODE_TEMP_ID)
Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery.
1.你想对100台机器执行一条批量命令,可能会花很长时间 ,但你不想让你的程序等着结果返回,而是给你返回 一个任务ID,你过一段时间只需要拿着这个任务id就可以拿到任务执行结果, 在任务执行ing进行时,你可以继续做其它的事情。
2.你想做一个定时任务,比如每天检测一下你们所有客户的资料,如果发现今天 是客户的生日,就给他发个短信祝福
Celery 在执行任务时需要通过一个消息中间件来接收和发送任务消息,以及存储任务结果, 一般使用rabbitMQ or Redis 或者是数据库来存放消息的中间结果
简单:一旦熟悉了celery的工作流程后,配置和使用还是比较简单的
高可用:当任务执行失败或执行过程中发生连接中断,celery 会自动尝试重新执行任务
快速:一个单进程的celery每分钟可处理上百万个任务
灵活: 几乎celery的各个组件都可以被扩展及自定制