登录注册相关功能(前后端不分离)

发布时间 2023-07-07 14:21:01作者: 等日落

1.登录相关

要现实的功能:

  • 用户名与密码登录
  • 手机号和验证码登录

登录成功,将用户登录信息保存到session中,跳转home页面
登录失败,在页面提示报错信息


1.1 用户名与密码登录

效果图展示:

image

处理逻辑步骤:

  • 页面展示(前端构建出登录的页面)(借用forms组件和不借助forms组件构建)
  • 提交数据(采用post请求,把用户数据发给服务端)
  • 去查询数据库之前,先用forms组件校验数据格式
    • 校验成功:执行下一步
    • 校验失败:在页面返回错误信息
  • 根据数据库去校验登录信息
    • 校验成功:将登录信息保存到session中,跳转页面
    • 校验失败:页面展示错误信息

1.页面展示

构建逻辑:

用户访问某给url---》跳转到登陆页面(自己根据css和bootstrap构建好看的页面)---》登陆页面除了要有常规登录输入信息框之外,还要有一个链接,点击可以跳转到手机号验证码登录---》页面还要有一个下拉框,选择是客户登录还是管理员登录(因为我们客户和管理员是分表的)

会遇见的问题:

输入完登录信息,点击提交到django会报错,因为django内部有csrftoken,需要在form表单内添加一个csrf_token
不用forms组件构建的页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <style>
        body{
            background-color: #f0ffff;
        }
        .box {
            width: 20%;
            border: 1px solid #ccc;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            margin: 10% auto;
            padding: 15px;
        }

        .box a {
            text-decoration: none;
            float: right;
            padding-top: 8px;
            padding-right: 5px;
        }
    </style>
</head>
<body>
<div class="box">
    <h3 style="text-align: center">登录</h3>
    <form action="" method="post">
        {% csrf_token %}
        <div class="form-group">
            <label for="name">用户名:</label>
            <input type="text" id='name' placeholder="用户名" class="form-control" name="username">
        </div>
        <div class="form-group">
            <label for="pwd">密码:</label>
            <input type="password" id="pwd" placeholder="密码" class="form-control" name="password">
        </div>
        <div class="form-group">
            <label for="role">角色:</label>
            <select class="form-control" id="role">
                <option value="customer">客户</option>
                <option value="admin">管理员</option>
            </select>
        </div>
        <input type="submit" value="登录" class="btn btn-primary">
        <span>{{ error }}</span>
        <a href="{% url 'sms_login' %}">短信登录</a>
    </form>
</div>
</body>
</html>

上述,不用forms组件构建的页面有一大缺点,就是我们没有对用户传入的数据进行校验,而直接去查询数据库有没有这个用户了,
通常情况下,我们需要先对用户传的数据现校验一下,满足格式在去查数据库,因此就需要用到forms组件了(当然也可以自己在视图函数里写校验规则,但是它麻烦),django提供了一个forms组件可以很好的帮我们完成这个问题!

利用forms组件进行优化
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
    <style>
        body {
            background-color: #f0ffff;
        }

        .box {
            width: 20%;
            border: 1px solid #ccc;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            margin: 10% auto;
            padding: 15px;
        }

        .box a {
            text-decoration: none;
            float: right;
            padding-top: 8px;
            padding-right: 5px;
        }
        .sub{
            margin-top: 20px;
        }

    </style>
</head>
<body>
<div class="box">
    <h3 style="text-align: center">登录</h3>
    <form action="" method="post" novalidate id="myform">
        {% csrf_token %}
        {% for form in form_obj %}
            <div class="form-group">
                <label for="{{ form.id_for_label }}">{{ form.label }}:</label>
                {{ form }}
                <span style="color: red;float:right" id="error-msg">{{ form.errors.0 }}</span>
            </div>
        {% endfor %}

        <div class="sub">
            <input type="submit" value="登录" class="btn btn-primary">
            <span style="color: red" id="error">{{ error }}</span>
            <a href="{% url 'login_sms' %}">短信登录</a>
        </div>
    </form>
</div>
</body>
<script>
    $(function () {
        // 点击哪个input框就消除下面的错误信息
        $('#myform').on('click', 'input', function () {
            $(this).next().text('')
            $('#error').text('')
        })
    })
</script>
</html>

用到的forms组件
class LoginPwdForm(forms.Form):
    role = forms.ChoiceField(
        choices=((1, "客户"), (2, "管理员")),
        label="角色",
        initial=1,
        widget=forms.Select(attrs={'class': 'form-control'})
    )

    username = forms.CharField(max_length=32, label='用户名', required=True,
                               widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '用户名'}),
                               error_messages={
                                   'max_length': '用户名最大32位',
                                   'required': '用户名不能为空'
                               })

    password = forms.CharField(max_length=16, label='密码', required=True,
                               widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '密码'}),
                               error_messages={
                                   'max_length': '密码最大16位',
                                   'required': '密码不能为空'
                               })

    def clean_password(self):
        # 对于校验通过的密码进行md5加密之后在返回
        return md5(self.cleaned_data['password'])

知识点补充:

  1. forms组件中Form的使用
  2. js如何动态清除form表单中input框下的错误信息
  3. django中使用form表单或者ajax提交数据时如何验证csrftoken

2.提交数据

构建逻辑:

form表单点击登录,将用户输入内容提交给指定url,在后端可以根据request.POST.get获取到用户输入信息

3.校验登录数据

构建逻辑:

在视图函数里面去根据传来的登录信息,查找数据库有没有---》没有,在给登录页面返回错误信息,并在页面展示---》有,则跳转到其他页面显示登录成功并写入session

注意点:
1.我们密码在数据库里一般是加密形式,不是明文形式。因此,我们需要对用户登录输入的密码先加密(md5加密和加盐,一般可以在forms组件里就进行加密即可,不需要在视图函数里进行),再去查找数据库有没有
2.session借助cookie发送给浏览器,我们在django服务端存一份,session可以存放在文件、数据库、缓存中,我们这边先采用存在缓存里
不用forms组件后端校验逻辑
from django.shortcuts import render,redirect
from web import models
from utils.encrypt import md5

def pwd_login(request):

    if request.method == 'GET':
         return render(request,'pwd_login.html')

    # 当时post请求时,获取用户提交数据,查找数据库校验
    username = request.POST.get('username')
    password = request.POST.get('password')

    # 因为数据库里的密码是md5加密加盐的,我们这里也需要将其加密加盐一下在查找
    password = md5(password)

    # 查找数据库有没有这个用户,注意要添加active查找活跃用户,而不是被删除的用户
    use_obj = models.Administrator.objects.filter(active='1',username=username,password=password)

    # 查不到用户返回错误信息
    if not use_obj:
        return render(request,'pwd_login.html',{'error':'用户名或密码错误'})

    return redirect('/home/')
用forms组件后端校验逻辑
# 密码登录视图函数
def login_pwd(request):

    if request.method == 'GET':
        form_obj = myforms.LoginPwdForm()
        return render(request, 'login_pwd.html', locals())

    if request.method == 'POST':

        form_obj = myforms.LoginPwdForm(request.POST)

        role_mapping = {
            '1': 'CUSTOMER',
            '2': 'ADMIN'
        }

        if not form_obj.is_valid():
            return render(request, 'login_pwd.html', locals())

        data_dict = form_obj.cleaned_data
        role = data_dict.pop('role')
        if role_mapping[role] == 'ADMIN':
            # 查找数据库有没有这个用户,注意要添加active查找活跃用户,而不是被删除的用户
            use_obj = models.Administrator.objects.filter(active=1).filter(**data_dict).first()
        else:
            use_obj = models.Customer.objects.filter(active=1).filter(**data_dict).first()

        # 查不到用户返回错误信息
        if not use_obj:
            error = '用户名或密码错误'
            return render(request, 'login_pwd.html', locals())

        # 用户名和密码正确,保存到session然后跳转
        request.session['user_info'] = {'role': role_mapping[role], 'username': use_obj.username, 'id': use_obj.id}

        return redirect(settings.LOGIN_HOME)

知识点补充:

  1. django离线脚本的使用(就是需要借助django的功能,然后写一些脚本
  2. md5加密加盐

1.2 手机验证码登录

效果图:

image

处理逻辑步骤:

  • 页面展示(前端根据forms组件构建出登录的页面)
  • 获取验证码(采用ajax请求提交,把用户角色和手机号发给服务端校验)
    • 根据角色,去对应数据库查询该手机号格式是否正确,是否注册,未注册先提示注册再登录
    • 已注册,并且格式正确,再生成验证码,调用第三方接口发送验证码
    • 验证码发送成功,则修改前端页面获取验证码为倒计时,并且把验证码保存到redis中
    • 验证码发送失败,展示错误信息
  • 点击登录按钮,提交验证码、手机号和角色提交数据到服务端(ajax提交)
    • 校验成功:将登录信息保存到session中,跳转页面
    • 校验失败:页面展示错误信息

1.页面展示

forms组件构建的验证码登录页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
    <script src="{% static 'js/csrf.js' %}"></script>
    <style>
        body {
            background-color: #f0ffff;
        }

        .box {
            width: 20%;
            border: 1px solid #ccc;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            margin: 10% auto;
            padding: 15px;
        }

        .box a {
            text-decoration: none;
            float: right;
            padding-top: 8px;
            padding-right: 5px;
        }

        .col-md-4 {
            margin-left: -15px;
            margin-right: -15px;
        }

        .sub {
            margin-top: 20px;
        }


    </style>
</head>
<body>
<div class="box">
    <h3 style="text-align: center">登录</h3>
    <form action="" method="post" id="smsForm">
{#        {% csrf_token %}#}
        {% for form in form_obj %}
            {% if form.name == 'code' %}
                <div class="form-group">
                    <label for="{{ form.id_for_label }}">{{ form.label }}:</label>
                    <div class="row">
                        <div class="col-md-8">{{ form }}
                        </div>
                        <div class="col-md-4">
                            <input type="button" value="获取验证码" class="form-control sms-btn">
                        </div>
                    </div>
                </div>
            {% else %}
                <div class="form-group">
                    <label for="{{ form.id_for_label }}">{{ form.label }}:</label>
                    {{ form }}
                    <span style="color: red;float: right" class="error-message">{{ form.errors.0 }}</span>
                </div>
            {% endif %}
        {% endfor %}

        <div class="sub">
            <button class="btn btn-primary login-btn" type="button">登录</button>
            <span style="color: red" class="error-message" id="code-error">{{ form.errors.0 }}</span>
            <a href="{% url 'login_pwd' %}">密码登录</a>
        </div>
    </form>
</div>
</body>
</html>

2.点击获取验证码

前端js代码
    $(function () {
        // 页面加载完毕执行下述函数

        // 1.点击获取验证码事件
        bingSendSmsEvent();

        // 2.点击登录按钮,提交验证码登录信息
        bingSubmitSmsEvent()
    })

    var $btn = $('.sms-btn');

    function bingSendSmsEvent() {

        $btn.click(function () {

            // 向后端提交手机号,获取验证码
            $.ajax({
                type: 'POST',
                url: '{% url "tencent" %}',
                data: {
                    'mobile': $('#id_mobile').val(),
                    'role': $('#id_role').val(),
                },
                dataType: 'JSON',
                success: function (res) {
                    // 如果调用腾讯接口发送短信成功,则修改页面获取验证码为倒计时
                    if (res.status) {
                        changeSendBtn()
                    } else {
                        $.each(res.detail, function (k, v) {
                            $('#id_' + k).next().text(v[0])
                        })
                    }
                }
            })
        })
    }

    // 点击哪个input框就消除下面的错误信息
    $('#smsForm').on('click', 'input', function () {
        // 清楚所有的错误
        $(".error-message").empty();
    })

    // 修改获取验证码按钮
    function changeSendBtn() {
        var $time = 60;
        $btn.prop('disabled', true)
        var ID = setInterval(function () {
            $btn.val($time + ' 秒重试')
            $time--
            if ($time < 1) {
                clearInterval(ID);
                $btn.val('获取验证码');
                $btn.prop('disabled', false);
            }
        }, 1000)
    }
后端视图代码
def tencent_sms(request):
    res = myresponse.BasicResponse()

    # 校验手机号和角色
    form_obj = myforms.MobileForm(request.POST)

    # 校验手机号、角色、动态生成一个验证码
    # 调用腾讯发短信接口:传入手机号和验证码
    # 上述这些都在forms组件里做了
    if not form_obj.is_valid():
        res.detail = form_obj.errors
        return JsonResponse(res.dict, json_dumps_params={'ensure_ascii': False})

    # 校验通过并发送验证码成功之后
    res.status = True
    return JsonResponse(res.dict, json_dumps_params={'ensure_ascii': False})
forms组件校验代码
class MobileForm(forms.Form):
    role = forms.ChoiceField(
        choices=((1, "客户"), (2, "管理员")),
        label="角色",
        initial=1,
        widget=forms.widgets.Select(attrs={'class': 'form-control'})
    )

    mobile = forms.CharField(max_length=11, label='手机号', required=True,
                             widget=forms.widgets.TextInput(attrs={'class': 'form-control', 'placeholder': '手机号'}),
                             validators=[
                                 RegexValidator(r'^1[358]\d{9}$', '手机号格式不正确')
                             ],
                             error_messages={
                                 'max_length': '手机号最大11位',
                                 'required': '手机号不能为空'
                             })

    def clean_mobile(self):
        role = self.cleaned_data.get('role')
        mobile = self.cleaned_data.get('mobile')
        if not role:
            return mobile

        if role == '1':
            is_exist = models.Customer.objects.filter(active=1,mobile=mobile).exists()
        else:
            is_exist =  models.Administrator.objects.filter(active=1,mobile=mobile).exists()

        if not is_exist:
            raise ValidationError('该手机号还未注册,请先注册')

        # 手机号存在,调用腾讯接口发送短信
        # 先生成验证码
        code = str(random.randint(1000, 9999))
        print(code)
        # 调用接口发短信
        # is_success = tencent.send_sms(mobile, code)
        is_success = True
        if not is_success:
            raise ValidationError("短信发送失败,请重试")

        # 短信发送成功,将信息保存到redis中
        conn = get_redis_connection('default')
        conn.set(mobile,code,ex=60)
        return mobile

补充知识点: