注册的逻辑
- 注册需要的参数用户名,密码等,主要是图片验证码等输入
- 输入图片验证码之后,点击获得验证码,这时候要验证图片验证码的正确性
- 图片验证码正确才会发生短信,用户收到短信之后,输入后,点击注册就可以把数据写入数据库。
图片验证码的作用是防止短信发送的浪费,但是背后真的那么简单吗?想多了,下面讲一下细节。这是代码===>
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("用户名已注册,请重新输入!")
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):
"""
"""
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_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("手机号已经注册,请重新输入!")
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)
if (not real_image_code) or image_text.upper() != real_image_code:
raise forms.ValidationError("图片验证失败")
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/(?<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/(?P<mobile>1[3-9]\d{9})/
"""
def get(self, request, mobile):
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秒有记录
-生成短信验证码
-发送短信
"""
def post(self, request):
json_data = request.body
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)
if form.is_valid():
mobile = form.cleaned_data.get('mobile')
sms_num = "%06d" % random.randint(0, 999999)
con_redis = get_redis_connection(alias='verify_codes')
sms_flag_fmt = "sms_flag_{}".format(mobile).encode('utf8')
sms_text_fmt = "sms_{}".format(mobile).encode('utf8')
pl = con_redis.pipeline()
try:
pl.setex(sms_flag_fmt, constants.SEND_SMS_CODE_INTERVAL, 1)
pl.setex(sms_text_fmt, constants.IMAGE_CODE_REDIS_EXPIRES, sms_num)
pl.execute()
except Exception as e:
logger.debug('redis 执行异常{}'.format(e))
return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])
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)
用户收到短信验证码后填入就大概注册完成了。
挖坑
pipeline是啥?
https://blog.csdn.net/weixin_43673156/article/details/123981998
celery是什么?
celery异步任务
sms_tasks.send_sms_code.delay(mobile, sms_num, expires, constants.SMS_CODE_TEMP_ID)
celery队列
Celery 是一个 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery.
使用场景:
1.你想对100台机器执行一条批量命令,可能会花很长时间 ,但你不想让你的程序等着结果返回,而是给你返回 一个任务ID,你过一段时间只需要拿着这个任务id就可以拿到任务执行结果, 在任务执行ing进行时,你可以继续做其它的事情。
2.你想做一个定时任务,比如每天检测一下你们所有客户的资料,如果发现今天 是客户的生日,就给他发个短信祝福
Celery原理:
Celery 在执行任务时需要通过一个消息中间件来接收和发送任务消息,以及存储任务结果, 一般使用rabbitMQ or Redis 或者是数据库来存放消息的中间结果
Celery优点:
简单:一旦熟悉了celery的工作流程后,配置和使用还是比较简单的 高可用:当任务执行失败或执行过程中发生连接中断,celery 会自动尝试重新执行任务 快速:一个单进程的celery每分钟可处理上百万个任务 灵活: 几乎celery的各个组件都可以被扩展及自定制
|