面试-Django实现注册短信验证码发送

注册的逻辑

  1. 注册需要的参数用户名,密码等,主要是图片验证码等输入
  2. 输入图片验证码之后,点击获得验证码,这时候要验证图片验证码的正确性
  3. 图片验证码正确才会发生短信,用户收到短信之后,输入后,点击注册就可以把数据写入数据库。

图片验证码的作用是防止短信发送的浪费,但是背后真的那么简单吗?想多了,下面讲一下细节。这是代码===>

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/(?<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):
        # 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)

用户收到短信验证码后填入就大概注册完成了。

挖坑

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的各个组件都可以被扩展及自定制
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值