订单交易平台五:短信登录验证界面(从0到1的过程)

1. 短信验证界面(版本一)

1.1 视图函数accont.py中

#  先定义form类
class SmsLoginForm(forms.Form):
    role = forms.ChoiceField(
        required=True,
        label='角色',
        choices=(('2', '客户'), ('1', '管理员')),
        widget=forms.Select(attrs={"class": "form-control"})
    )
    # <input type = "text" class ="form-control" placeholder="用户名" name="username" >
    mobile = forms.CharField(
        required=True,
        label='手机号',
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "手机号"})
    )
    code = forms.CharField(
        required=True,
        label='短信验证码',
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "短信验证码"})
    )

# 再定义视图函数

def sms_login(request):
    if request.method == 'GET':
        form = SmsLoginForm()
        return render(request, 'sms_login.html', {'form': form})

1.2 前端界面 sms_login.html中

  • 这里不能直接使用for循环,因为这里有一个发送短信验证码按钮
  • 所以我们要进行判断,如果forms组件中有短信验证码则,自己加入下面代码,如果没有则正常循环遍历
    <div class="row">
        <div class="col-xs-7">
            {{ field }}
            <span class="error-message">{{ field.errors.0 }}</span>
        </div>
        <div class="col-xs-5">
            <input id="sendBtn" type="button" value="点击获取验证码" class="btn btn-default"/>
        </div>
    </div>
    
  • 正常代码演示
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <style>
        .box {
            width: 450px;
            border: 1px solid #f0f0f0;
            margin-left: auto;
            margin-right: auto;
            margin-top: 100px;

            padding-left: 40px;
            padding-right: 40px;
            padding-bottom: 30px;

            box-shadow: 5px 10px 10px rgb(0 0 0 / 5%);
        }

        .error-message {
            color: red;
            position: absolute;
        }
    </style>
</head>
<body>
<div class="box">
    <form method="post" id="smsForm">
        <h2 style="text-align: center;">短信登录</h2>
        {% csrf_token %}

        {% for field in form %}
            {% if field.name == 'code' %}
                <div class="form-group" style="position: relative;margin-bottom: 25px">
                    <label>{{ field.label }}</label>
                    <div class="row">
                        <div class="col-xs-7">
                            {{ field }}
                            <span class="error-message">{{ field.errors.0 }}</span>
                        </div>
                        <div class="col-xs-5">
                            <input id="sendBtn" type="button" value="点击获取验证码" class="btn btn-default"/>
                        </div>
                    </div>

                </div>
            {% else %}
                <div class="form-group" style="position: relative;margin-bottom: 25px">
                    <label>{{ field.label }}</label>
                    {{ field }}
                    <span class="error-message">{{ field.errors.0 }}</span>
                </div>
            {% endif %}
        {% endfor %}

        <button type="button" class="btn btn-primary" id="loginBtn">登 录</button>
        <a href="{% url 'login' %}" style="float: right;">用户名登录</a>
    </form>
</div>
</body>
</html>

1.3 js发送短信60s倒计时

  • 先导入jquery文件
  • 在需要倒计时的标签按钮上绑定id
<script>
    $(function () {
        // 当页面框架加载完成之后,自动执行里面的代码。
        bindSendSmsEvent();
    })

    /**
     * 发送短信按钮倒计时效果
     */
    function sendSmsRemind() {
        var $smsBtn = $("#sendBtn");
        // 2.1 禁用
        $smsBtn.prop("disabled", true);

        // 2.2 改内容
        var time = 60;
        var remind = setInterval(function () {
            $smsBtn.val(time + "秒重新发送");
            time = time - 1;
            if (time < 1) {
                clearInterval(remind);

                $smsBtn.val("点击获取验证码");
                $smsBtn.prop("disabled", false);
            }
        }, 1000);
    }

</script>

2. 解决ajax发送POST请求,出现csrf_token问题的解决(ajax中get请求没有问题,正常发送就可以)

2.1 原始版本

img

2.2 最终版本,以后使用版本

  • 首先在js文件中导入csrf文件
  • 在前端界面引入此文件
    img
    内容如下:
    # 第一步
    <script src="{% static 'js/csrf.js' %}"></script>
    
    
    # 第二步,在csrf文件中
    /**
     * 根据cookie的name获取对应的值
     * @param name
     * @returns {null}
     */
    function getCookie(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            const cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                const cookie = cookies[i].trim();
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    
    function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type)) {
                xhr.setRequestHeader("X-CSRFTOKEN", getCookie('csrftoken'));
            }
        }
    })
    

3. 校验手机号格式 + 发送验证码 + 存入redis + 取出所存的数据 =====》 进行登录

3.1 ajax先获取前端界面输入的数据,并且再发送给后端进行校验

function bindSendSmsEvent() {
        // 按钮绑定点击事件
        $("#sendBtn").click(function () {
            // 1.获取手机号, 向后台发送请求【先不写】

            // 清楚所有的错误
            $(".error-message").empty();

            $.ajax({
                url: "{% url 'sms_send' %}",
                type: "POST",
                data: {
                    mobile: $("#id_mobile").val(),
                    role: $("#id_role").val(),
                },
                {# 这里写个JSON表示将后端JsonResponse发送过来的字符串数据,变成字典类型的数据 #}
                dataType: "JSON",
                success: function (res) {
                    console.log(res);
                    {# 如果status=True则表示发送成功,没有错误信息;False发送失败,有错误信息 #}
                    if (res.status) {
                        // 2.动态效果
                        sendSmsRemind();
                    } else {
                        // {"status": false, "detail": {"mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        // {"status": false, "detail": {"id_mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        $.each(res.detail, function (k, v) {
                            $("#id_" + k).next().text(v[0]);
                        })
                    }
                }
            })

        });
    }

3.2 接受ajax发送过来的数据,并进行校验,再发送短信、生成验证码、存入redis

class MobileForm(forms.Form):
    mobile = forms.CharField(
        label='手机号',
        required=True,
        validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ]
    )

def sms_send(request):
    """ 验证短信 """
    # 1.校验手机号的格式
    request.POST.get('mobile')
    form = MobileForm(data=request.POST)
    if not form.is_valid():
        return JsonResponse({'status': False, "detail": form.errors}, json_dumps_params={"ensure_ascii": False})

    # 2.发送短信 + 生成验证码
    mobile = request.cleaned_data['mobile']
    sms_code = str(random.randint(1000, 9999))
    is_success = tencent.send_sms(mobile, sms_code)
    if not is_success:
        raise ValidationError("短信发送失败-钩子")

    # 3.将手机号和验证码保存(以便于下次校验) redis -> 超时时间
    conn = get_redis_connection("default")
    conn.set(mobile, sms_code, ex=60)

    return JsonResponse({'status':True})

3.2.1 进行改进

img

# 如果觉得return JsonResponse({'status': False, "detail": form.errors}, json_dumps_params={"ensure_ascii": False})比较复杂


class MobileForm(forms.Form):
    mobile = forms.CharField(
        label='手机号',
        required=True,
        validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ]
    )


class Response(object):
    def __init__(self):
        self.status = True
        self.detail = None
        self.data = [11, 22, 33]

    @property
    def dict(self):
        return self.__dict__


def sms_send(request):
    """ 验证短信 """
    res = Response()
    # 1.校验手机号的格式
    request.POST.get('mobile')
    form = MobileForm(data=request.POST)
    if not form.is_valid():
        res.detail = form.errors
        # return JsonResponse({'status': False, "detail": form.errors}, json_dumps_params={"ensure_ascii": False})
        return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})

    # 2.发送短信 + 生成验证码
    mobile = request.cleaned_data['mobile']
    sms_code = str(random.randint(1000, 9999))
    is_success = tencent.send_sms(mobile, sms_code)
    if not is_success:
        res.detail = {'mobile':['发送失败,请稍等重试']}
        return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})

    # 3.将手机号和验证码保存(以便于下次校验) redis -> 超时时间
    conn = get_redis_connection("default")
    conn.set(mobile, sms_code, ex=60)
    res.status = True

    return JsonResponse(res.dict)

3.2.2 再scripts文件中写入发送短信代码,并且再终端安装第三方库

img

from tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, models


def send_sms(mobile, sms_code):
    mobile = "+86{}".format(mobile)
    try:
        cred = credential.Credential("AKIDa0B7nhOq3zf5G8l9TMzNVO0MRHrAE3Yn", "4rPincBUYMuCEzUjsdIiuqWv3vYu0qPh")
        client = sms_client.SmsClient(cred, "ap-guangzhou")

        req = models.SendSmsRequest()

        req.SmsSdkAppId = "1400455481"
        req.SignName = "Python之路"
        req.TemplateId = "548762"
        req.TemplateParamSet = [sms_code, ]
        req.PhoneNumberSet = [mobile, ]
        resp = client.SendSms(req)
        print(resp.SendStatusSet)
        data_object = resp.SendStatusSet[0]
        # print(data_dict,type(data_dict))
        from tencentcloud.sms.v20210111.models import SendStatus
        print(data_object.Code)
        if data_object.Code == "Ok":
            return True
    except Exception as e:
        print(e)

3.2.3 最终登录校验

  • 在account.py文件中
class SmsLoginForm(forms.Form):
    role = forms.ChoiceField(
        required=True,
        label='角色',
        choices=(('2', '客户'), ('1', '管理员')),
        widget=forms.Select(attrs={"class": "form-control"})
    )
    # <input type = "text" class ="form-control" placeholder="用户名" name="username" >
    mobile = forms.CharField(
        required=True,
        label='手机号',
        validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ],
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "手机号"})
    )
    code = forms.CharField(
        required=True,
        label='短信验证码',
        validators=[RegexValidator(r'^[0-9]\d{4}$', '验证码格式错误'), ],
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "短信验证码"})
    )

def sms_login(request):
    if request.method == 'GET':
        form = SmsLoginForm()
        return render(request, 'sms_login.html', {'form': form})

    res = BaseResponse()
    # print(request.POST)
    # < QueryDict:'role': ['2'], 'mobile': ['123465798'], 'code': ['zt7758521']} >
    # 这里需要校验这三个字段 role,mobile,code(code这个字段需要在redis里面进行校验)

    # 1.手机号进行校验’
    form = SmsLoginForm(data=request.POST)
    if not form.is_valid():
        res.detail = form.errors
        # return JsonResponse({'status': False, "detail": form.errors}, json_dumps_params={"ensure_ascii": False})
        return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
    # 2.code进行校验 + redis取
    mobile = form.cleaned_data['mobile']
    code = form.cleaned_data['code']
    role = form.cleaned_data['role']

    conn = get_redis_connection('default')
    cache_code = conn.get(mobile)

    if not cache_code:
        res.detail = {"code": ["短信验证码失效或未发送", ]}
        return JsonResponse(res.dict)
    # 如果存在则需要比较这两个,code是字符串,cache_code是二进制数字
    if code != cache_code:
        res.detail = {"code": ["短信验证码错误", ]}
        return JsonResponse(res.dict)

    # 3.登录成功 + 注册
    #       未注册,自动注册
    #       已注册,直接登录
    if role == "1":
        user_object = models.Administrator.objects.filter(active=1,mobile=mobile).first()
    else:
        user_object = models.Customer.objects.filter(active=1,mobile=mobile).first()

    if not user_object:
        res.detail = {"mobile": ["手机号不存在", ]}
        return JsonResponse(res.dict)

    # 校验成功
        # 2.2 校验成功,用户信息写入session+进入项目后台
    mapping = {"1": "ADMIN", "2": "CUSTOMER"}
    request.session['user_info'] = {'role': mapping[role], 'name': user_object.username, 'id': user_object.id}
    res.status = True
    res.data = '/home/'
    return JsonResponse(res.dict)
  • 前端界面中
# 登录按钮进行数据校验
function bindLoginEvent() {
        $("#loginBtn").click(function () {
            // 清楚所有的错误
            $(".error-message").empty();

            $.ajax({
                url: "{% url 'sms_login' %}",
                type: "POST",
                data: $("#smsForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    console.log(res);
                    if (res.status) {
                        // res.data = "/level/list/
                        location.href = res.data;
                    } else {
                        $.each(res.detail, function (k, v) {
                            $("#id_" + k).next().text(v[0]);
                        })
                    }
                }
            })
        });
    }
  # 发送按钮进行数据校验
    function bindSendSmsEvent() {
        // 按钮绑定点击事件
        $("#sendBtn").click(function () {
            // 1.获取手机号, 向后台发送请求【先不写】

            // 清楚所有的错误
            $(".error-message").empty();

            $.ajax({
                url: "{% url 'sms_send' %}",
                type: "POST",
                data: {
                    mobile: $("#id_mobile").val(),
                    role: $("#id_role").val(),
                },
                {# 这里写个JSON表示将后端JsonResponse发送过来的字符串数据,变成字典类型的数据 #}
                dataType: "JSON",
                success: function (res) {
                    console.log(res);
                    {# 如果status=True则表示发送成功,没有错误信息;False发送失败,有错误信息 #}
                    if (res.status) {
                        // 2.动态效果
                        sendSmsRemind();
                    } else {
                        // {"status": false, "detail": {"mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        // {"status": false, "detail": {"id_mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        $.each(res.detail, function (k, v) {
                            $("#id_" + k).next().text(v[0]);
                        })
                    }
                }
            })

        });
    }
  • 自己统一的校验规则类
class BaseResponse(object):
    def __init__(self):
        self.status = False
        self.detail = None
        self.data = None

    @property
    def dict(self):
        return self.__dict__

总结:

  1. 先对发送短信进行代码编写,再写出他的校验规则(要保证mobile正确)
  2. form检验规则写完之后,再写如何发送验证码,并且生成验证码,然后进行保存,存入到redis缓存中(发送验证码只需要电话号码格式正确、有效就可以)
  3. 最后再写登录按钮验证,登录按钮要检验三个数据(role、mobile、code)这三个都正确才能登录

4. 短信登录界面最终版代码(优化之后)

  • 对导包进行优化,依次顺序是:python内置三方包、第三方模块包、自己写的包(尽量由短到长)
  • 将form和view拆开放,在app下创建一个Forms文件,将自己定义的form组件放进来
  • 将密码md5处理,放到form组件中使用

4.1 前端界面

4.1.1 前端文件(sms_send.html)

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap.css' %}">
{#    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>#}
    <style>
        .box {
            width: 450px;
            border: 1px solid #f0f0f0;
            margin-left: auto;
            margin-right: auto;
            margin-top: 100px;

            padding-left: 40px;
            padding-right: 40px;
            padding-bottom: 30px;

            box-shadow: 5px 10px 10px rgb(0 0 0 / 5%);
        }

        .error-message {
            color: red;
            position: absolute;
        }
    </style>
</head>
<body>
<div class="box">
    <form method="post" id="smsForm">
        <h2 style="text-align: center;">短信登录</h2>
        {% csrf_token %}

        {% for field in form %}
            {% if field.name == 'code' %}
                <div class="form-group" style="position: relative;margin-bottom: 25px">
                    <label>{{ field.label }}</label>
                    <div class="row">
                        <div class="col-xs-7">
                            {{ field }}
                            <span class="error-message">{{ field.errors.0 }}</span>
                        </div>
                        <div class="col-xs-5">
                            <input id="sendBtn" type="button" value="点击获取验证码" class="btn btn-default"/>
                        </div>
                    </div>

                </div>
            {% else %}
                <div class="form-group" style="position: relative;margin-bottom: 25px">
                    <label>{{ field.label }}</label>
                    {{ field }}
                    <span class="error-message">{{ field.errors.0 }}</span>
                </div>
            {% endif %}
        {% endfor %}

        <button type="button" class="btn btn-primary" id="loginBtn">登 录</button>
        <a href="{% url 'login' %}" style="float: right;">用户名登录</a>
    </form>
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'js/csrf.js' %}"></script>
<script>
    $(function () {
        // 当页面框架加载完成之后,自动执行里面的代码。
        bindSendSmsEvent();

        bindLoginEvent();
    })

    function bindLoginEvent() {
        $("#loginBtn").click(function () {
            // 清楚所有的错误
            $(".error-message").empty();

            $.ajax({
                url: "{% url 'sms_login' %}",
                type: "POST",
                data: $("#smsForm").serialize(),
                dataType: "JSON",
                success: function (res) {
                    console.log(res);
                    if (res.status) {
                        // res.data = "/level/list/
                        location.href = res.data;
                    } else {
                        $.each(res.detail, function (k, v) {
                            $("#id_" + k).next().text(v[0]);
                        })
                    }
                }
            })
        });
    }

    function bindSendSmsEvent() {
        // 按钮绑定点击事件
        $("#sendBtn").click(function () {
            // 1.获取手机号, 向后台发送请求【先不写】

            // 清楚所有的错误
            $(".error-message").empty();

            $.ajax({
                url: "{% url 'sms_send' %}",
                type: "POST",
                data: {
                    mobile: $("#id_mobile").val(),
                    role: $("#id_role").val(),
                },
                {# 这里写个JSON表示将后端JsonResponse发送过来的字符串数据,变成字典类型的数据 #}
                dataType: "JSON",
                success: function (res) {
                    console.log(res);
                    {# 如果status=True则表示发送成功,没有错误信息;False发送失败,有错误信息 #}
                    if (res.status) {
                        // 2.动态效果
                        sendSmsRemind();
                    } else {
                        // {"status": false, "detail": {"mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        // {"status": false, "detail": {"id_mobile": ["手机格式错误"],"role":["xxxxx",]}}
                        $.each(res.detail, function (k, v) {
                            $("#id_" + k).next().text(v[0]);
                        })
                    }
                }
            })

        });
    }

    /**
     * 发送短信按钮倒计时效果
     */
    function sendSmsRemind() {
        var $smsBtn = $("#sendBtn");
        // 2.1 禁用
        $smsBtn.prop("disabled", true);

        // 2.2 改内容
        var time = 60;
        var remind = setInterval(function () {
            $smsBtn.val(time + "秒重新发送");
            time = time - 1;
            if (time < 1) {
                clearInterval(remind);

                $smsBtn.val("点击获取验证码");
                $smsBtn.prop("disabled", false);
            }
        }, 1000);
    }


</script>
</body>
</html>

4.1.2 ajax使用到POST请求需要的csrf_token配置

/**
 * 根据cookie的name获取对应的值
 * @param name
 * @returns {null}
 */
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type)) {
            xhr.setRequestHeader("X-CSRFTOKEN", getCookie('csrftoken'));
        }
    }
})

4.1.3 项目所需要的前端文件目录

4.2 后端代码

4.2.1 account.py文件(后端的主逻辑代码)

# -*- coding utf-8 -*-
import random

from django.http import JsonResponse
from django.shortcuts import render, redirect, HttpResponse
from django_redis import get_redis_connection

from web import models
from utils import tencent
from utils.reponse import BaseResponse
from web.forms.account import LoginForm, SmsLoginForm, MobileForm
from django.conf import settings


"""
@version : python 3.10
@author : T-mars
@file : xxx.py
@time : 2023-10-28 下午13:20
"""


class Role:
    ADMIN = "1",
    CUSTOMER = "2"


# return render(request, 'login.html')
def login(request):
    if request.method == "GET":
        form = LoginForm()
        return render(request, "login.html", {"form": form})

    # 1.接收并获取数据(数据格式或是否为空验证 - Form组件 & ModelForm组件)
    form = LoginForm(data=request.POST)
    if not form.is_valid():
        return render(request, "login.html", {"form": form})

    # 2.去数据库校验  1管理员  2客户
    data_dict = form.cleaned_data
    role = data_dict.pop('role')
    if role == Role.ADMIN:
        user_object = models.Administrator.objects.filter(active=1).filter(**data_dict).first()
    else:
        user_object = models.Customer.objects.filter(active=1).filter(**data_dict).first()

    # 2.1 校验失败
    if not user_object:
        form.add_error("username", "用户名或密码错误")
        return render(request, "login.html", {'form': form})

    # 2.2 校验成功,用户信息写入session+进入项目后台
    mapping = {"1": "ADMIN", "2": "CUSTOMER"}
    request.session['user_info'] = {'role': mapping[role], 'name': user_object.username, 'id': user_object.id}

    return redirect('/home/')


def sms_login(request):
    if request.method == "GET":
        form = SmsLoginForm()
        return render(request, "sms_login.html", {'form': form})

    res = BaseResponse()
    # 1.手机格式校验
    form = SmsLoginForm(data=request.POST)
    if not form.is_valid():
        res.detail = form.errors
        return JsonResponse(res.dict)

    role = form.cleaned_data['role']
    mobile = form.cleaned_data['mobile']
    # 3.登录成功 + 注册  (监测手机号是否存在)
    #     - 未注册,自动注册
    #     - 已注册,直接登录
    if role == "1":
        user_object = models.Administrator.objects.filter(active=1, mobile=mobile).first()
    else:
        user_object = models.Customer.objects.filter(active=1, mobile=mobile).first()

    if not user_object:
        res.detail = {"mobile": ["手机号不存在"]}
        return JsonResponse(res.dict)

    # 2.2 校验成功,用户信息写入session+进入项目后台
    mapping = {"1": "ADMIN", "2": "CUSTOMER"}
    request.session['user_info'] = {'role': mapping[role], 'name': user_object.username, 'id': user_object.id}
    res.status = True
    res.data = settings.LOGIN_HOME
    return JsonResponse(res.dict)


def sms_send(request):
    """ 发送短信  """
    res = BaseResponse()

    # 校验数据合法性:手机号的格式 + 角色
    form = MobileForm(data=request.POST)
    if not form.is_valid():
        res.detail = form.errors
        return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
    res.status = True
    return JsonResponse(res.dict)

4.2.2 使用到的form组件代码

# -*- coding utf-8 -*-
import random

from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.http import JsonResponse
from django.shortcuts import render, redirect, HttpResponse
from django_redis import get_redis_connection

from web import models
from utils.encrypt import md5
from utils import tencent
from utils.reponse import BaseResponse
from django import forms


class LoginForm(forms.Form):
    role = forms.ChoiceField(
        required=True,
        label='角色',
        choices=(('2', '客户'), ('1', '管理员')),
        widget=forms.Select(attrs={"class": "form-control"})
    )
    # <input type = "text" class ="form-control" placeholder="用户名" name="username" >
    username = forms.CharField(
        required=True,
        label='用户名',
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "用户名"})
    )
    password = forms.CharField(
        required=True,
        label='密码',
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "密码"}),
        # render_value=True
    )

    def clean_username(self):
        user = self.cleaned_data['username']
        # 校验规则
        # 校验失败
        if len(user) < 3:
            from django.core.exceptions import ValidationError
            raise ValidationError("用户名格式错误")
        return user

    def clean_password(self):
        return md5(self.cleaned_data['password'])

    def clean(self):
        # 对所有值进行校验,无论前面的字段校验成功与否
        user = self.cleaned_data.get('username')
        pwd = self.cleaned_data.get('password')
        if user and pwd:
            pass
        from django.core.exceptions import ValidationError
        # 1.不返回值,默认 self.cleaned_data
        # 2.返回值,self.cleaned_data=返回的值
        # 3.报错,ValidationError ->  self.add_error(None, e)
        # print(self.cleaned_data)
        # raise ValidationError("整xxxxx体错误")

    def _post_clean(self):
        pass


class SmsLoginForm(forms.Form):
    role = forms.ChoiceField(
        label="角色",
        required=True,
        choices=(("2", "客户"), ("1", "管理员")),
        widget=forms.Select(attrs={"class": "form-control"})
    )

    mobile = forms.CharField(
        label="手机号",
        validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ],
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "手机号"})
    )

    code = forms.CharField(
        label="短信验证码",
        validators=[RegexValidator(r'^[0-9]{4}$', '验证码格式错误'), ],
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "短信验证码"})
    )


    def clean_code(self):
        mobile = self.cleaned_data.get('mobile')
        code = self.cleaned_data['code']
        if not mobile:
            return code

        conn = get_redis_connection("default")
        cache_code = conn.get(mobile)
        if not cache_code:
            raise ValidationError("短信验证码未发送或失效")

        if code != cache_code.decode('utf-8'):
            raise ValidationError("短信验证码未发送或失效")

        return code


class MobileForm(forms.Form):
    role = forms.ChoiceField(
        label="角色",
        required=True,
        choices=(("2", "客户"), ("1", "管理员")),
        widget=forms.Select(attrs={"class": "form-control"})
    )

    mobile = forms.CharField(
        label="手机号",
        required=True,
        validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ]
    )

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

        if role == "1":
            exists = models.Administrator.objects.filter(active=1, mobile=mobile).exists()
        else:
            exists = models.Customer.objects.filter(active=1, mobile=mobile).exists()
        if not exists:
            raise ValidationError("手机号不存在-钩子")

        # 2.发送短信 + 生成验证码
        sms_code = str(random.randint(1000, 9999))
        is_success = tencent.send_sms(mobile, sms_code)
        if not is_success:
            raise ValidationError("短信发送失败-钩子")

        # 3.将手机号和验证码保存(以便于下次校验) redis -> 超时时间
        conn = get_redis_connection("default")
        conn.set(mobile, sms_code, ex=60)

        return mobile

4.2.3 使用的两个离线脚本,导入客户和管理员数据

# 管理员
# -*- coding utf-8 -*-

"""
@version : python 3.10
@author : T-mars
@file : xxx.py
@time : 2023-10-28 下午13:20
"""
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_crm.settings')
django.setup()  # 伪造让django启动

from web import models
from utils.encrypt import md5

models.Administrator.objects.create(username='root', password=md5("root"), mobile="1888888889")
models.Administrator.objects.create(username='admin', password=md5("admin"), mobile="1888888888")



# 客户
# -*- coding utf-8 -*-

"""
@version : python 3.10
@author : T-mars
@file : xxx.py
@time : 2023-10-28 下午13:20
"""
import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopping_crm.settings')
django.setup()  # 伪造让django启动

from web import models
from utils.encrypt import md5

# 创建级别
# models.Level.objects.create(title="VIP", percent=90)

models.Customer.objects.create(
    username='ztcy',
    password=md5("123456"),
    mobile='188888888',
    level_id=1,
    creator_id=1
)

4.2.4 写的密码加密加盐功能

# -*- coding utf-8 -*-

"""
@version : python 3.10
@author : T-mars
@file : xxx.py
@time : 2023-10-28 下午13:20
"""
import hashlib
from django.conf import settings


def md5(data_string):
    obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
    obj.update(data_string.encode('utf-8'))
    return obj.hexdigest()

4.2.5 自己写的项目校验统一规则,防止多人开发出现不同的规则

class BaseResponse(object):
    def __init__(self):
        self.status = False
        self.detail = None
        self.data = None

    @property
    def dict(self):
        return self.__dict__

4.2.6 发送短信功能

from tencentcloud.common import credential
from tencentcloud.sms.v20210111 import sms_client, models


def send_sms(mobile, sms_code):
    mobile = "+86{}".format(mobile)
    try:
        cred = credential.Credential("AKIDa0B7nhOq3zf5G8l9TMzNVO0MRHrAE3Yn", "4rPincBUYMuCEzUjsdIiuqWv3vYu0qPh")
        client = sms_client.SmsClient(cred, "ap-guangzhou")

        req = models.SendSmsRequest()

        req.SmsSdkAppId = "1400455481"
        req.SignName = "Python之路"
        req.TemplateId = "548762"
        req.TemplateParamSet = [sms_code, ]
        req.PhoneNumberSet = [mobile, ]
        resp = client.SendSms(req)
        print(resp.SendStatusSet)
        data_object = resp.SendStatusSet[0]
        # print(data_dict,type(data_dict))
        from tencentcloud.sms.v20210111.models import SendStatus
        print(data_object.Code)
        if data_object.Code == "Ok":
            return True
    except Exception as e:
        print(e)

4.2.7 settings文件配置

Django settings for shopping_crm project.

Generated by 'django-admin startproject' using Django 3.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-dfcdcdh+ytfrz8#$mc)695aud2p5=o&@1ck5$m-#rndv-85d-0'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    # 'django.contrib.admin',
    # 'django.contrib.auth',
    # 'django.contrib.contenttypes',
    # 'django.contrib.sessions',
    # 'django.contrib.messages',
    'django.contrib.staticfiles',
    'web.apps.WebConfig',
]

MIDDLEWARE = [
    # 'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
    # 'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'shopping_crm.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                # 'django.contrib.auth.context_processors.auth',
                # 'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'shopping_crm.wsgi.application'

# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'day06',
        'USER': 'root',
        'PASSWORD': 'zt7758521',
        'HOST': '127.0.0.1',
        'PORT': 3306,
    }
}

# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en-hans'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

MENU = {
    "ADMIN": [],
    "CUSTOMER": [],
}
PERMISSION = {
    "ADMIN": {},
    "CUSTOMER": {},
}

# cache缓存
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100},
            "PASSWORD": "",
        }
    }
}

# session配置
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
# 设置session保存时间
PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 3

SESSION_COOKIE_NAME = "sid"  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/"  # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False  # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600  # Session的cookie失效日期(2周)

SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = True  # 是否每次请求都保存Session,默认修改之后才保存

try:
    from .local_settings import *
except Exception:
    pass

# =====================自定义配置=============================
LOGIN_HOME = "/home/"
NB_SESSION_KEY = "user_info"
NB_LOGIN_URL = "/login/"
NB_WHITE_URL = ['/login/', '/sms/login/', '/sms/send/']


posted @ 2024-10-08 17:16  tmars  阅读(4)  评论(0编辑  收藏  举报