Django 使用短信验证码登录

实现页面效果

在这里插入图片描述

实现思路

当输入手机号时需要检测该手机号是否注册过,然后点击登录时一起将手机号和验证吗提交给后端,再次校验手机号和验证码。
这里使用form的方式将字段渲染再前端,在form中校验字段。

画页面

首先将字段传入到模板中:

views.py:

def login_sms(request):
    if request.method == 'GET':
        form = LoginSmsForm(request.GET)
        return render(request, 'user/login_sms.html', dict(form=form))

login.html:

<div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <div class="account">
                    <h2 class="text-center">短信登录</h2>
                    <form id="form_data">
                        {% csrf_token %}
                        {% for field in form %}
                            {% if field.label == '验证码' %}
                                <div class="form-group">
                                    <label for="{
     
     { field.id_for_label }}">{
   
   { field.label }}</label>
                                    <div class="row">
                                        <div class="col-xs-6">
                                            {
   
   { field }}
                                            <span class="error_msg"></span>
                                        </div>
                                        <div class="col-xs-6">
                                            <button id="get_code" type="button" class="btn btn-default">点击获取验证码</button>
                                        </div>
                                    </div>
                                </div>
                            {% else %}
                                <div class="form-group">
                                    <label for="{
     
     { field.id_for_label }}">{
   
   { field.label }}</label>
                                    {
   
   { field }}
                                    <span class="error_msg"></span>
                                </div>
                            {% endif %}
                        {% endfor %}
                    <div>
                         <button type="button" id="login_sms" class="btn btn-primary">登 录</button>
                        <div class="pull-right">
                            <a href="{% url 'login' %}">用户登录>>></a>
                        </div>
                    </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

点击获取验证码,发送ajax请求:

// 绑定点击获取验证码的事件
    function bindGetCodeEvent(){
        $('#get_code').click(function () {
            var phoneEle = $('#id_phone');
            var phone_numbe = phoneEle.val().trim();
            var phone_reg = /^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
            if (phone_numbe && phone_reg.test(phone_numbe)){
                phoneEle.next().text('');
                // 校验通过之后 发送 ajax 请求
                $.ajax({
                    url:'{% url "sms_code" %}',
                    type:'get',
                    // tpl:短信模板
                    data:{phone:phone_numbe, tpl:"login"},
                    success:function (res){
                        if (res.status){
                            //开启倒计时效果
                            SmsTimer();
                        }else{
                            phoneEle.next().text(res.error_msg.phone);
                        }
                    }
                })
            }else {
                phoneEle.next().text('手机号格式不对!');
                return false
            }
    })
    }

倒计时功能:

// 倒计时效果函数
    function SmsTimer() {
        // 将标签设置为不可点击的
        // jquery 的变量一般以 $ 开头
        var $btnEle = $('#get_code');
        $btnEle.prop('disable',true);
        // 设置定时器读秒效果和标签文本内容的修改
        var timer = 60;
        var t = setInterval(
            function () {
                $btnEle.text(`${timer}秒后重新发送`);
                timer--;
                // 如果小于0就将定时器清除并且将$btnEle设置为可操作
                if (timer <= 0){
                    clearInterval(t);
                    $btnEle.text('点击获取验证码');
                    $btnEle.prop('disable',false);
                }
            },
            1000 // 1秒中执行一次这个函数
        )
    }

发送登录的数据:

// 发送登录数据
    function sendLoginData() {
        // 获取用户输入的所有数据
        // 将数据发送到后端
        // 根据响应结果进行一些页面效果处理

        // 绑定点击事件
        $('#login_sms').click(function () {
            var data = $('#form_data').serialize(); //拿到form表单中的所有数据
            $.ajax({
                url: '{% url 'login_sms' %}',
                type: 'post',
                data: data,
                success:function (res) {
                    if (res.status){
                        location.href = res.path;
                    }else{
                        $.each(res.error_msg,function (k,v){
                            $('#id_' + k).next().text(v)
                        })
                    }
                }
            })
        })
    }

后端校验数据

整体数据(手机号、验证码)提交时的数据校验:

class LoginSmsForm(BootStrapForm, forms.Form):
    phone = forms.CharField(label='手机号', validators=[mobile_validate, ])
    sms_code = forms.CharField(label='验证码')

    def clean_phone(self):
        '''手机号唯一性校验'''
        phone = self.cleaned_data.get('phone')
        phone_require = models.UserInfo.objects.filter(phone=phone)
        if not phone_require.exists():
            raise ValidationError('该手机号还没注册,请先注册!!')

        return phone

    def clean_sms_code(self):
        '''验证码校验
        * 取出 sms_code、phone
        * 获取连接,从 redis 中取出 验证码
        * 如果不同则抛出错误

        注意点:一般局部钩子只获取当前局部变量,无法获取其他变量,如在phone中不能获取email的对象
        解决方式:
            phone 必须在 sms_code 校验的前面,顺序就是fields中指定的。
        '''
        sms_code = self.cleaned_data.get('sms_code')
        phone = self.cleaned_data.get('phone')
        conn = get_redis_connection('sms_code')
        code = conn.get(phone)
        if code is not None:
            if code.decode('utf-8') != sms_code.strip():
                raise ValidationError('验证码错误!!!')
        else:
            raise ValidationError('验证码无效或已经超时!!')

        return code

发送验证码时的数据校验:

class SendSmsForm(forms.Form):
    phone = forms.CharField(label='手机号', validators=[mobile_validate, ])

    # 重写 init 方法接受额外参数(request)
    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def clean_phone(self):
        '''手机号和短信模板校验'''
        tpl = self.request.GET.get('tpl')
        try:
            sms_template_id = settings.SMS_TEMPLATE_ID[tpl]
        except:
            raise ValidationError('短信模板错误!!')

        phone = self.cleaned_data.get('phone')
        unique_phone = models.UserInfo.objects.filter(phone=phone)

        # 获取短信验证码时需要区分一下是注册还是登录
        if tpl == settings.SMS_TEMPLATE_ID.get('register'):
            if unique_phone.exists():
                raise ValidationError('该手机号已经注册过了!')
        elif tpl == settings.SMS_TEMPLATE_ID.get('login'):
            if not unique_phone.exists():
                raise ValidationError('该手机号还未注册过!')
        elif tpl == settings.SMS_TEMPLATE_ID.get('repassword'):
            # TODO 逻辑待定
            pass
        else:
            raise ValidationError(f'短信模板:{tpl}不存在!')

        # 发送并校验验证码
        # 校验该手机号是不是已经发送过短信了,是不是在有效期内
        conn = get_redis_connection('sms_code')
        if conn.get(phone) is not None:
            raise ValidationError('已经发送过短信!!')

        # 生成验证码
        sms_code = '%06d' % random.randint(1, 999999)
        # 将验证码保存到 redis 中
        conn.set(phone, sms_code, ex=settings.SMS_CODE_EXPIRE)
        # 发送短信
        obj = MySmsSender()
        obj.send(phone, sms_template_id, sms_code)

        return phone

完整view试图函数:
登录:

def login_sms(request):
    if request.method == 'GET':
        form = LoginSmsForm(request.GET)
        return render(request, 'user/login_sms.html', dict(form=form))
    else:
        form = LoginSmsForm(data=request.POST)
        if form.is_valid():
            # TODO 将登录成功之后的数据保存到session中,方便后续使用
            return JsonResponse(dict(status=True, path=reverse('index')))
        else:
            return JsonResponse(dict(status=False, error_msg=form.errors))

发送验证码:

def sms_code(request):
    '''获取手机号,校验手机号,发送短信验证码
    * 格式校验
    * 是否注册过
    '''
    form = SendSmsForm(request, data=request.GET)
    if form.is_valid():
        return JsonResponse({
    
    'status': True})
    else:
        return JsonResponse({
    
    'status': False, 'error_msg': form.errors})

猜你喜欢

转载自blog.csdn.net/qq_39253370/article/details/114271668