短信登陆
# 前端-发送短信
- 短信登陆的页面展示, 跟用户名登陆一样的, Get请求, 运行form组件自动生成标签 + 携带数据.. 代码略.
代码中, 用户名登陆 -- LoginForm、短信登陆 -- SmsLoginForm.- 在显示的页面里, 找到发送短信的按钮, 为其绑定一个事件.使用js代码完成两件事:
1> 获取手机号, 使用ajax向后台发送请求
2> 发送短信成功后, 按钮的 倒计时
# 代码实现
sms_login.html
{{ csrf_token }}
<input type="button" value="发送短信" class="btn btn-default" style="float: right" id="sendBtn">
<script src="{% static "js/jquery-3.6.4.min.js" %}"></script>
<script src="{% static "js/csrf.js" %}"></script>
<script>
$(function () {
// jq提供的功能,当页面框架夹在完成后(不会等所有的静态资源加载好),自动执行里面的代码!
bindSendSmsEvent();
})
function bindSendSmsEvent() {
let $smsBtn = $("#sendBtn"); // $smsBtn就是一个变量
$smsBtn.click(function () {
// 1.获取手机号,向后台发送请求
let mobileData = $("#id_mobile").val();
$.ajax({
url: "{% url 'sms_send' %}",
type: "POST",
dataType: "JSON",
data: {
mobile: mobileData,
},
success: function (res) {
if (res.status) {
// 2.发送短信成功后,短信按钮的倒计时
sendSmsRemind($smsBtn)
} else {
// 在页面对应位置显示错误信息.
}
}
})
})
}
function sendSmsRemind($smsBtn) {
// 2.1 点击后,按钮变成禁用状态
$smsBtn.prop("disabled", true);
// 2.2 改变按钮的内容,开始倒计时
let time = 60;
let remind = setInterval(function () {
$smsBtn.val(time + "s后重新发送!");
time -= 1;
// 2.3 倒计时结束,恢复按钮的内容,解除禁用状态
if (time < 0) {
clearInterval(remind);
$smsBtn.val("发送短信");
$smsBtn.prop("disabled", false);
}
}, 1000);
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
csrf.js
// 根据cookie的name获取对应的值,Django官方文档提供的代码!
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'));
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 注意事项!
1.短信登陆页面得有"不管在页面哪,但得有",{{ csrf_token }},不然不会在cookie中生成csrftoken!
csrfmiddlewaretoken从两个地方任一取: 表单中 or ajax的请求头X-CSRFTOKEN中!!
然后会将csrfmiddlewaretoken和csrftoken都进行清洗,比较得到的csrf_secret的值,若相等,403验证通过.
2.在ajax中发送的请求头,假如是如下的数据:
headers: {
'X-CSRFTOKEN': getCookie('csrftoken'),
'xxx-123': 9999,
},
在视图函数中,需要添加"HTTP_"的前缀来获取自定义的请求头! 并且在前端的 - 会在后端转化成 _
request.META.get("HTTP_X_CSRFTOKEN")
request.META.get("HTTP_XXX_123")
3.发送ajax请求 且 需要添加名为X-CSRFTOKEN的请求头时, 直接引入自己编写的csrf.js文件即可!! 用于解决csrf校验的问题.
除GET|HEAD|OPTIONS|TRACE之外的其它请求都需要添加名为X-CSRFTOKEN的请求头!!
4.若是用表单发送请求,只能发送 GET和POST请求!!像什么OPTIONS、TRACE之类的请求是发送不了的!
<form method="get"></form>
<form method="post">{{ csrf_token }}</form>
5.Django的Form组件默认在渲染标签的时候,id为"id_字段名"、name为"字段名" ★★★★★★★★★★★★★★★★★★★★★
6.ajax请求里,写的url的值,eg -- url:"{% url 'sms_send' %}"
若该ajax请求在单独一个js文件里,正确的url是反向解析不到的!!!
7.本质上,网络传输,Ajax拿到的返回值是字符串类型,若设置了dataType:"JSON",会将返回的值转换成一个对象,就可以使用`.`语法啦!
★ 花了一点时间,弄清楚了 request.META 和request.headers的区别..
他们都是字典,前者包含了请求中所有请求头的信息;后者包含了以HTTP_ 为前缀的请求头的信息 + 'Content-Length' + 'Content-Type'.. (在视图函数中打印,request.META和request.headers的值可以验证)
对于自定义的请求头,做了个实验,使用postman发送get请求,在请求中加入请求头 xxx-123 = waowao ;
那么在视图中 通过request.META.get("HTTP_XXX_123") 和 request.headers.get("xxx_123") 都能取到.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 后端-发送短信
step1: 前端将数据返回后端,后端接收数据进行校验,将校验结果用json格式返回给前端,前端在页面上展示相应的错误信息
(此处只使用了form组件校验功能!!)
step2: 生成验证码, 调用腾讯云的发生短信的sdk.. (需要进行一定调试.)
step3: 将手机号和验证码保存到redis中,并设置超时时间, 以便于下次校验!
大多数网站, 点击发送验证码后, 只会校验手机号格式是否正确, 正确则会发送;
用户拿到验证码登陆, 若该手机号存在且验证码正确登陆成功; 若手机号不存在,但验证码正确, 会自动注册用户, 登陆成功!!
但我们的订单系统的业务逻辑不同, 只有管理员可以注册客户..
所以在发送验证码时, 就得验证用户是否存在, 不存在则不会发送短信验证码!!
Q: 前端输入手机号的时候就对手机号进行了正则校验,那么在后端就无需对手机号进行校验了吗?
A: 在前端校验了手机号,校验不通过就不能向后端发送请求,减轻了后端的压力!
但后端的接口不仅可通过前端访问,还可以通过类似于POSTMAN这些软件模拟浏览器进行访问(绕过了前端).
So,前后端都需要对手机号进行校验!!
1
2
3
4
2
3
4
前端ajax关键代码
function bindSendSmsEvent() {
let $smsBtn = $("#sendBtn"); // $smsBtn就是一个变量
$smsBtn.click(function () {
// 注意:发送ajax之前,先清除所有的错误
$(".error-msg").empty();
// 1.获取手机号,向后台发送请求
let mobileData = $("#id_mobile").val();
let roleData = $("#id_role").val();
$.ajax({
url: "{% url 'sms_send' %}",
type: "POST",
data: {
mobile: mobileData,
role: roleData,
},
dataType: "JSON",
success: function (res) {
if (res.status) {
// 2.发送短信成功后,短信按钮的倒计时
sendSmsRemind($smsBtn)
} else {
// 在页面上展示错误
// {"status": false, "detail": {"mobile": ["手机格式错误"]},"role":["角色选择有误."]}
$.each(res.detail, function (k, v) {
$("#id_" + k).next().text(v[0])
})
}
}
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
后端对应接口关键代码
注意两点: 1> 统一规范了返回数据的格式; 2> 用Form组件对数据进行了校验.
# -- 统一规范了返回数据的格式
class BaseResponse:
def __init__(self, status=False, detail=None, data=None):
self.status = status
self.detail = detail
self.data = data
@property
def dict(self):
return self.__dict__
class MobileForm(forms.Form):
role = forms.ChoiceField(
label="角色",
choices=(("1", "管理员"), ("2", "客户"),),
)
mobile = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误')],
)
def sms_send(request):
"""发送短信"""
# --1.校验手机号的格式
res = BaseResponse()
form = MobileForm(data=request.POST)
if not form.is_valid():
# return JsonResponse(
# {'status': False, 'detail': form.errors},
# json_dumps_params={"ensure_ascii": False}
# )
res.detail = form.errors
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
# --2.生成验证码 + 发送短信
mobile = form.cleaned_data["mobile"]
sms_code = str(random.randint(1000, 9999))
is_success = send_tencent_sms(mobile, sms_code, '5') # -- 调用腾讯云发生短信的sdk!
if not is_success:
# return JsonResponse(
# {'status': False, 'detail': {"mobile": ["发送失败,请稍后再试!"]}},
# json_dumps_params={"ensure_ascii": False}
# )
res.detail = {"mobile": ["发送失败,请稍后再试!"]}
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
# --3.将手机号和验证码保存到redis中 --> 设置超时时间 (以便于下次校验!)
# PS:更合理的做法是,将该步骤套到一个函数里,try..except..返回true/false 避免redis连接失败
conn = get_redis_connection("default")
conn.set(mobile, sms_code, ex=60) # ex=60表明超时时间为60秒
# return JsonResponse({'status': True})
res.status = True
return JsonResponse(res.dict)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 后端-登陆
Q: 思考一个问题!此处的登陆,用form表单提交数据,还是用ajax提交呢?
A: 首先明确一点,这两种提交方式没有好坏之分.只有是否适用于当前的业务场景!
分析下,若使用表单提交,因为验证码输入错误登陆失败了,页面会刷新,发送验证码的按钮的读秒效果就会刷新.
★ Ps:不用担心短时间内同一号码的重复发送,腾讯云发送短信,可以限制同一个手机号在多少秒之内发送一次,即频率的限制!!
So,此处的登陆,使用ajax提交数据!!
1
2
3
4
5
2
3
4
5
咔咔咔的一堆写, 没做任何优化!
前端ajax关键代码
$(function () {
// jq提供的功能,当页面框架夹在完成后(不会等所有的静态资源加载好),自动执行里面的代码!
bindSendSmsEvent(); // 发送短信验证码
bindLoginEvent(); // 短信登陆
})
function bindLoginEvent() {
$("#loginBtn").click(function () {
$(".error-msg").empty();
$.ajax({
url: "{% url 'sms_login' %}",
type: "POST",
// 有一个好处,不用加请求头.
data: $("#sms_form").serialize(),
dataType: "JSON",
success: function (res) {
if (res.status) {
location.href = res.data // 跳转到指定的地址
} else {
$.each(res.detail, function (k, v) {
$("#id_" + k).next().text(v[0])
})
}
}
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
web/views/account.py
"""
账户相关的,用户名密码登陆、短信登陆、注销
"""
import random
from django import forms
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django_redis import get_redis_connection
from django.core.validators import RegexValidator
from utils.encrypt import md5
from utils.response import BaseResponse
from utils.tencent import send_tencent_sms
from web import models
class LoginForm(forms.Form):
role = forms.ChoiceField(
label="角色",
choices=(("1", "管理员"), ("2", "客户"),),
widget=forms.Select(attrs={"class": "form-control"})
)
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}),
error_messages={"required": "用户名不能为空!"},
)
password = forms.CharField(
label="密码",
min_length=6,
# validators=[RegexValidator(r'^[0-9]+$', '请输入数字')],
widget=forms.PasswordInput(
attrs={"class": "form-control", "placeholder": "请输入密码", },
render_value=True
),
error_messages={"min_length": "密码至少是6位!", "required": "密码不能为空!"},
)
def clean_username(self):
user = self.cleaned_data['username']
if len(user) > 10:
from django.core.exceptions import ValidationError
raise ValidationError("用户名不能超过10位!")
return user
def clean_password(self):
password = self.cleaned_data['password']
if len(password) > 10:
from django.core.exceptions import ValidationError
raise ValidationError("密码不能超过10位!")
return md5(password)
def login(request):
"""用户名登陆"""
if request.method == "GET":
form = LoginForm()
return render(request, "login.html", {"form": form})
form = LoginForm(data=request.POST)
if not form.is_valid():
return render(request, "login.html", {"form": form})
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password")
role = form.cleaned_data.get("role")
mapping = {"1": "ADMIN", "2": "CUSTOMER"}
if role not in mapping:
return render(request, "login.html", {"error": "选择的角色不存在!", 'form': form})
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, username=username, password=password).first()
else:
user_obj = models.Customer.objects.filter(active=1, username=username, password=password).first()
if not user_obj:
# form.add_error("password", "用户名或密码错误")
return render(request, "login.html", {"info": "用户名或密码错误!", 'form': form})
request.session['user_info'] = {'role': mapping[role], 'name': user_obj.username, 'id': user_obj.pk}
return redirect(settings.LOGIN_HOME)
class MobileForm(forms.Form):
role = forms.ChoiceField(
label="角色",
choices=(("1", "管理员"), ("2", "客户"),),
)
mobile = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^1[3758]\d{9}$', '手机格式错误')],
)
def sms_send(request):
"""发送短信"""
res = BaseResponse()
# --1.校验数据的合法性: 手机号的格式 + 角色
form = MobileForm(data=request.POST)
if not form.is_valid():
# return JsonResponse(
# {'status': False, 'detail': form.errors},
# json_dumps_params={"ensure_ascii": False}
# )
res.detail = form.errors
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
# 1.5 看手机号是否存在,不存在,就不允许发
mobile = form.cleaned_data["mobile"]
role = form.cleaned_data["role"]
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, mobile=mobile).exists()
else:
user_obj = models.Customer.objects.filter(active=1, mobile=mobile).exists()
if not user_obj:
res.detail = {"mobile": ["手机号不存在!"]}
return JsonResponse(res.dict)
# --2.生成验证码 + 发送短信
sms_code = str(random.randint(1000, 9999))
is_success = send_tencent_sms(mobile, sms_code, '5')
if not is_success:
# return JsonResponse(
# {'status': False, 'detail': {"mobile": ["发送失败,请稍后再试!"]}},
# json_dumps_params={"ensure_ascii": False}
# )
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) # ex=60表明超时时间为60秒
# return JsonResponse({'status': True})
res.status = True
return JsonResponse(res.dict)
class SmsLoginForm(forms.Form):
role = forms.ChoiceField(
label="角色",
choices=(("1", "管理员"), ("2", "客户"),),
widget=forms.Select(attrs={"class": "form-control"})
)
mobile = forms.CharField(
label="手机号",
validators=[RegexValidator(r'^1[3758]\d{9}$', '手机格式错误')],
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入手机号"}),
error_messages={"required": "手机号不能为空!"},
)
code = forms.CharField(
label="短信验证码",
validators=[
RegexValidator(r'^[0-9]{4}$', '验证码格式错误'),
],
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入验证码"}),
error_messages={"required": "验证码不能为空!"},
)
def sms_login(request):
"""短信验证码登陆"""
if request.method == "GET":
form = SmsLoginForm()
return render(request, "sms_login.html", {"form": form})
res = BaseResponse()
# --1.校验表单数据
form = SmsLoginForm(request.POST)
if not form.is_valid():
res.detail = form.errors
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
# --2.短信验证码
# 将用户输入的验证码和在radis中缓存的验证码进行对比!
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: # redis中可能没有手机号对应的code: 没发送短信验证 or 超时
res.detail = {"code": ["短信验证码未发送 or 失效!"]}
return JsonResponse(res.dict)
# 注意: cache_code若有,其类型为 byte类型!!
if code != cache_code.decode("utf-8"):
res.detail = {"code": ["短信验证码错误!"]}
return JsonResponse(res.dict)
# --3.登陆成功 + 注册:
# 一般来说, 1> 未注册,自动注册; 2> 已注册,直接登陆
# 而我们的业务场景是, 检测手机号是否存在, 不存在直接登陆失败! 因为只有管理员才能注册用户.
# PS: 验证手机号是否存在,还可以往上提,提到拿redis之前..
mapping = {"1": "ADMIN", "2": "CUSTOMER"}
if role not in mapping:
res.detail = {"role": ["所选角色不存在!"]}
return JsonResponse(res.dict)
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, mobile=mobile).first()
else:
user_obj = models.Customer.objects.filter(active=1, mobile=mobile).first()
if not user_obj:
res.detail = {"mobile": ["手机号不存在!"]}
return JsonResponse(res.dict)
request.session['user_info'] = {'role': mapping[role], 'name': user_obj.username, 'id': user_obj.pk}
res.status = True
res.data = settings.LOGIN_HOME
return JsonResponse(res.dict)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# 优化
1.将form表单的验证单独提取到一个文件里
# 用户名登陆
2.若表单中有10个字段,要通过这10个字段在数据库中查询,查询条件一个字段一个字段的写吗? No!!
# user_obj = models.Administrator.objects.filter(active=1, username=username, password=password).first()
user_obj = models.Administrator.objects.filter(active=1).filter(**data_dict).first()
Ps:注意,此处的优化得保证表单里字段的name跟数据库里表的字段名一样!!当然,我们通常也会写出一样的.
3.这段代码压根不用写,因为form组件里,choices=(("1", "管理员"), ("2", "客户"),)保证了,只能传1和2
"""
if role not in mapping:
return render(request, "login.html", {"error": "选择的角色不存在!", 'form': form})
"""
4.`if role == 1:`这样写不够直观,通常会使用枚举! `if role == Role.ADMIN:`
class Role:
ADMIN = "1"
CUSTOMER = "2"
"""理一理
>> http://127.0.0.1:8000/login/ GET请求
响应头里 - Set-Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA;
>> http://127.0.0.1:8000/login/ 再次GET请求
请求头里 - Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA;
响应头里 - Set-Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA;
>> http://127.0.0.1:8000/login/ POST请求
请求头里 - Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA
请求体信息
csrfmiddlewaretoken: OZDYj7vq1kLNQlI3oxSHHoDedlrn9XagY6ofP19iT9vEr9B9Mbo9IzoeXgDLqcR9
role: 1
username: dc
password: admin123!
响应头里 - Set-Cookie: sid=mce1w4fh90j6cdh6q7bmx4pribivzypf;
>> http://127.0.0.1:8000/login/ 再次GET请求
请求头里 - Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA;
sid=mce1w4fh90j6cdh6q7bmx4pribivzypf
响应头里 - Set-Cookie: csrftoken=millVKNohTVHxhKcCmQ8DheBt3B6IvbHwp6CrErg9IFy85Di00mAEsZBdYNuZKSA;
Set-Cookie: sid=mce1w4fh90j6cdh6q7bmx4pribivzypf;
So,浏览器每次访问页面都会携带cookie信息!
GET请求访问表单页面成功,浏览器会设置csrftoken的cookie信息;
POST请求访问表单页面登陆成功,浏览器会保留登陆凭证sid信息. (默认是sessionid,我在session配置里改了下那个k值)
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 发送短信
先捋一捋原先发送短信的逻辑. 任意一步出错,都不会往下执行.会返回报错.
step1: 校验数据的合法性:手机号的格式+角色
step2: 根据角色和手机号在数据库中查找手机号是否存在
step3: 生成验证码 + 调用接口发送短信
step4: 将手机号和验证码保存到redis中,并设置超时时间
最后,短信发送成功!!
5.优化的地方在于,可以将第2步、第3步、第4步,都放到form组件的钩子函数中!!
首先明确一点,字段校验的顺序是根据在form组件里自定义的字段先后顺序来决定的.MobileForm里先后写了两个字段role、mobile
`即role校验通过,在mobile的钩子里是能去取到role的!!role的格式校验不通过,mobile中是取不到的!`
然后,我们来分析,按照原先发送短信的逻辑,若角色role不存在是进行不到第二步的.
存在,则在钩子中执行第2步.. 至于第3步、第4步写不写在钩子中,看个人习惯.
(这样的优化的好处在于,逻辑更清晰,有错,就在mobile这个字段下面就展示啦! 而不在clean函数里写,是因为还得判断字段存不存在等)
def clean_mobile(self):
role = self.cleaned_data.get('role')
mobile = self.cleaned_data['mobile']
"""
mobile = form.cleaned_data["mobile"]
role = form.cleaned_data["role"]
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, mobile=mobile).exists()
else:
user_obj = models.Customer.objects.filter(active=1, mobile=mobile).exists()
if not user_obj:
res.detail = {"mobile": ["手机号不存在!"]}
return JsonResponse(res.dict)
"""
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("手机号不存在-钩子")
"""
sms_code = str(random.randint(1000, 9999))
is_success = send_tencent_sms(mobile, sms_code, '5')
if not is_success:
res.detail = {"mobile": ["发送失败,请稍后再试!"]}
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
"""
sms_code = str(random.randint(1000, 9999))
is_success = send_tencent_sms(mobile, sms_code, '5')
if not is_success:
raise ValidationError("短信发送失败-钩子")
"""
# --将手机号和验证码保存到redis中,并设置超时时间 (以便于下次校验!)
conn = get_redis_connection("default")
conn.set(mobile, sms_code, ex=60) # ex=60表明超时时间为60秒
"""
conn = get_redis_connection("default")
conn.set(mobile, sms_code, ex=60)
return mobile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 短信登陆
同理,先捋一捋原先短信登陆的逻辑. 任意一步出错,都不会往下执行.会返回报错.
step1: 校验role、mobile、code三个字段的格式
step2: 校验手机号是否存在
step3: 校验用户输入的验证码与redis缓存中的验证码是否一致
step4: 用户信息写入session+进入项目后台
6.优化的地方在于,可以将第3步放到code的钩子函数中!!
★ 其实还可以将第2步放到mobile的钩子函数中,但不建议,因为第4步要用到检验手机号是否存在时查询数据库得到的对象!!
在短信登陆那,会依次执行 role、mobile、code字段的校验; 然后验证手机号是否存在.. 接着比对验证码是否正确, 最后存入session..
根据武sir的讲解,是可以将验证码的比对放到clean_code钩子中,验证手机号是否存在放到clean_mobile中
但存在一个问题, 最后存入session的数据会用到 `验证手机号是否存在` 时查询数据库时的 user_obj对象..
于是乎, 最后武sir没有将验证手机号是否存在放到clean_mobile中..
我想了想, 如何在视图函数中拿到user_obj?!
在clean_mobile的钩子函数中 self.user_obj = user_obj, 然后在视图函数中通过user_obj = form.user_obj拿到!!
内心OS:
可以实现, 但总觉得有点变扭, 感觉self 也就是form组件的实例化对象 是不是有个类似于上下文的东西 来承载.
(就像是drf里的request有context) 而不是直接这样简单粗暴. 查了下 没找到.
def clean_mobile(self):
role = self.cleaned_data.get("role")
mobile = self.cleaned_data["mobile"]
if not role:
return mobile
"""
mobile = form.cleaned_data["mobile"]
role = form.cleaned_data["role"]
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, mobile=mobile).first()
else:
user_obj = models.Customer.objects.filter(active=1, mobile=mobile).first()
if not user_obj:
res.detail = {"mobile": ["手机号不存在!"]}
return JsonResponse(res.dict)
"""
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, mobile=mobile).first()
else:
user_obj = models.Customer.objects.filter(active=1, mobile=mobile).first()
if not user_obj:
raise ValidationError("手机号不存在-钩子")
self.user_obj = user_obj
return mobile
def clean_code(self):
# 万一mobile字段校验没通过,也是会执行clean_code方法的,但取不到mobile
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: # redis中可能没有手机号对应的code: 没发送短信验证 or 超时
res.detail = {"code": ["短信验证码未发送 or 失效!"]}
return JsonResponse(res.dict)
# 注意: cache_code若有,其类型为 byte类型!!
if code != cache_code.decode("utf-8"):
res.detail = {"code": ["短信验证码错误!"]}
return JsonResponse(res.dict)
"""
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70