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中
{% 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 原始版本
2.2 最终版本,以后使用版本
- 首先在js文件中导入csrf文件
- 在前端界面引入此文件
内容如下:# 第一步
<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 进行改进
# 如果觉得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文件中写入发送短信代码,并且再终端安装第三方库
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 最终登录校验
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__
总结:
- 先对发送短信进行代码编写,再写出他的校验规则(要保证mobile正确)
- form检验规则写完之后,再写如何发送验证码,并且生成验证码,然后进行保存,存入到redis缓存中(发送验证码只需要电话号码格式正确、有效就可以)
- 最后再写登录按钮验证,登录按钮要检验三个数据(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)
# -*- 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/']