实现页面效果
实现思路
当输入手机号时需要检测该手机号是否注册过,然后点击登录时一起将手机号和验证吗提交给后端,再次校验手机号和验证码。
这里使用form的方式将字段渲染再前端,在form中校验字段。
画页面
首先将字段传入到模板中:
views.py:
def login_sms(request):
if request.method == 'GET':
form = LoginSmsForm(request.GET)
return render(request, 'user/login_sms.html', dict(form=form))
login.html:
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="account">
<h2 class="text-center">短信登录</h2>
<form id="form_data">
{% csrf_token %}
{% for field in form %}
{% if field.label == '验证码' %}
<div class="form-group">
<label for="{
{ field.id_for_label }}">{
{ field.label }}</label>
<div class="row">
<div class="col-xs-6">
{
{ field }}
<span class="error_msg"></span>
</div>
<div class="col-xs-6">
<button id="get_code" type="button" class="btn btn-default">点击获取验证码</button>
</div>
</div>
</div>
{% else %}
<div class="form-group">
<label for="{
{ field.id_for_label }}">{
{ field.label }}</label>
{
{ field }}
<span class="error_msg"></span>
</div>
{% endif %}
{% endfor %}
<div>
<button type="button" id="login_sms" class="btn btn-primary">登 录</button>
<div class="pull-right">
<a href="{% url 'login' %}">用户登录>>></a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
点击获取验证码,发送ajax请求:
// 绑定点击获取验证码的事件
function bindGetCodeEvent(){
$('#get_code').click(function () {
var phoneEle = $('#id_phone');
var phone_numbe = phoneEle.val().trim();
var phone_reg = /^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
if (phone_numbe && phone_reg.test(phone_numbe)){
phoneEle.next().text('');
// 校验通过之后 发送 ajax 请求
$.ajax({
url:'{% url "sms_code" %}',
type:'get',
// tpl:短信模板
data:{phone:phone_numbe, tpl:"login"},
success:function (res){
if (res.status){
//开启倒计时效果
SmsTimer();
}else{
phoneEle.next().text(res.error_msg.phone);
}
}
})
}else {
phoneEle.next().text('手机号格式不对!');
return false
}
})
}
倒计时功能:
// 倒计时效果函数
function SmsTimer() {
// 将标签设置为不可点击的
// jquery 的变量一般以 $ 开头
var $btnEle = $('#get_code');
$btnEle.prop('disable',true);
// 设置定时器读秒效果和标签文本内容的修改
var timer = 60;
var t = setInterval(
function () {
$btnEle.text(`${timer}秒后重新发送`);
timer--;
// 如果小于0就将定时器清除并且将$btnEle设置为可操作
if (timer <= 0){
clearInterval(t);
$btnEle.text('点击获取验证码');
$btnEle.prop('disable',false);
}
},
1000 // 1秒中执行一次这个函数
)
}
发送登录的数据:
// 发送登录数据
function sendLoginData() {
// 获取用户输入的所有数据
// 将数据发送到后端
// 根据响应结果进行一些页面效果处理
// 绑定点击事件
$('#login_sms').click(function () {
var data = $('#form_data').serialize(); //拿到form表单中的所有数据
$.ajax({
url: '{% url 'login_sms' %}',
type: 'post',
data: data,
success:function (res) {
if (res.status){
location.href = res.path;
}else{
$.each(res.error_msg,function (k,v){
$('#id_' + k).next().text(v)
})
}
}
})
})
}
后端校验数据
整体数据(手机号、验证码)提交时的数据校验:
class LoginSmsForm(BootStrapForm, forms.Form):
phone = forms.CharField(label='手机号', validators=[mobile_validate, ])
sms_code = forms.CharField(label='验证码')
def clean_phone(self):
'''手机号唯一性校验'''
phone = self.cleaned_data.get('phone')
phone_require = models.UserInfo.objects.filter(phone=phone)
if not phone_require.exists():
raise ValidationError('该手机号还没注册,请先注册!!')
return phone
def clean_sms_code(self):
'''验证码校验
* 取出 sms_code、phone
* 获取连接,从 redis 中取出 验证码
* 如果不同则抛出错误
注意点:一般局部钩子只获取当前局部变量,无法获取其他变量,如在phone中不能获取email的对象
解决方式:
phone 必须在 sms_code 校验的前面,顺序就是fields中指定的。
'''
sms_code = self.cleaned_data.get('sms_code')
phone = self.cleaned_data.get('phone')
conn = get_redis_connection('sms_code')
code = conn.get(phone)
if code is not None:
if code.decode('utf-8') != sms_code.strip():
raise ValidationError('验证码错误!!!')
else:
raise ValidationError('验证码无效或已经超时!!')
return code
发送验证码时的数据校验:
class SendSmsForm(forms.Form):
phone = forms.CharField(label='手机号', validators=[mobile_validate, ])
# 重写 init 方法接受额外参数(request)
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
def clean_phone(self):
'''手机号和短信模板校验'''
tpl = self.request.GET.get('tpl')
try:
sms_template_id = settings.SMS_TEMPLATE_ID[tpl]
except:
raise ValidationError('短信模板错误!!')
phone = self.cleaned_data.get('phone')
unique_phone = models.UserInfo.objects.filter(phone=phone)
# 获取短信验证码时需要区分一下是注册还是登录
if tpl == settings.SMS_TEMPLATE_ID.get('register'):
if unique_phone.exists():
raise ValidationError('该手机号已经注册过了!')
elif tpl == settings.SMS_TEMPLATE_ID.get('login'):
if not unique_phone.exists():
raise ValidationError('该手机号还未注册过!')
elif tpl == settings.SMS_TEMPLATE_ID.get('repassword'):
# TODO 逻辑待定
pass
else:
raise ValidationError(f'短信模板:{tpl}不存在!')
# 发送并校验验证码
# 校验该手机号是不是已经发送过短信了,是不是在有效期内
conn = get_redis_connection('sms_code')
if conn.get(phone) is not None:
raise ValidationError('已经发送过短信!!')
# 生成验证码
sms_code = '%06d' % random.randint(1, 999999)
# 将验证码保存到 redis 中
conn.set(phone, sms_code, ex=settings.SMS_CODE_EXPIRE)
# 发送短信
obj = MySmsSender()
obj.send(phone, sms_template_id, sms_code)
return phone
完整view试图函数:
登录:
def login_sms(request):
if request.method == 'GET':
form = LoginSmsForm(request.GET)
return render(request, 'user/login_sms.html', dict(form=form))
else:
form = LoginSmsForm(data=request.POST)
if form.is_valid():
# TODO 将登录成功之后的数据保存到session中,方便后续使用
return JsonResponse(dict(status=True, path=reverse('index')))
else:
return JsonResponse(dict(status=False, error_msg=form.errors))
发送验证码:
def sms_code(request):
'''获取手机号,校验手机号,发送短信验证码
* 格式校验
* 是否注册过
'''
form = SendSmsForm(request, data=request.GET)
if form.is_valid():
return JsonResponse({
'status': True})
else:
return JsonResponse({
'status': False, 'error_msg': form.errors})