上图是注册的页面,用户名、手机号、图形验证码、手机短信验证码、密码都是需要验证的接口,有的需要分别验证。关于前端的验证,我在这里就不具体说明了。
1.前端找到用户输入框标签,获取框中值,通过Ajax传来 2.后端路由匹配获取传来的值,利用user模型过滤方法查看用户名是否已被注册过 3.以json的格式返货给前端,前端通过Ajax方法接收。
from django.views import View
from django.http import JsonResponse
class Check_username(View):
"""
"""
def get(self,request,username):
data = {
'count': models.Users.objects.filter(username=username).count(),
'username':username
}
return JsonResponse(data=data)
// 发送ajax请求,去后端查询用户名是否存在
$.ajax({
type: 'GET',
url: '/check_username/' + username + '/',
dataType: 'json',
async: false
})
.done(function (res) {
if (res.data.count !== 0) {
message.showError(res.data.username + '已被注册,请重新输入!');
returnvalue = ''
}
else {
message.showSuccess(res.data.username + '能正常使用');
returnvalue = 'success'
}
})
.fail(function () {
message.showError('服务器请求超时,请重试!');
returnvalue = ''
});
urlpatterns = [
re_path('check_username/(?P\w{5,20})/',views.Check_username.as_view(),name='check_username'),
]
与验证用户名类似,通过re_path获取得到手机号,并对其检查是否已被注册过。
class Check_mobile(View):
"""
"""
def get(self,request,mobile):
data = {
'count': models.Users.objects.filter(mobile=mobile).count(),
'mobile':mobile
}
return JsonResponse(data=data)
$.ajax({
type: 'GET',
url: '/check_mobile/' + mobile + '/',
dataType: 'json',
async: false
})
.done(function (res) {
if (res.data.count !== 0) {
message.showError(res.data.mobile + '已被注册,请重新输入!');
returnvalue = ''
}
else {
message.showSuccess(res.data.mobile + '能正常使用');
returnvalue = 'success'
}
})
.fail(function () {
message.showError('服务器请求超时,请重试!');
returnvalue = ''
});
urlpatterns = [
re_path('check_mobile/(?P1[3-9]\d{9})/',views.Check_mobile.as_view(),name='check_mobile'),
]
踩坑记录: url路由re匹配手机号,后面加上边界限定符$,就匹配不到,原因是手机号后面还跟了一个/。
1.前端传来手机号、用户名、uuid、图像验证码,通过body获取 2.后端form和视图函数一起检验手机号存在及格式、用户名存在及格式、uuid存在及格式以及对应数据库是否存在、图像验证码存在及是否与数据库中的匹配 3.检验成功后,记录发送的key、发送文本一起存入redis,再记录发送的标志为60秒 4.对于一切的错误,都主动抛出异常并且logger记录
from django_redis import get_redis_connection
from django import forms
from django.core.validators import RegexValidator
from apps.users.models import Users
mobile_validator = RegexValidator(r"^1[3-9]\d{9}$",'手机号格式不正确')
class Send_message_form(forms.Form):
mobile = forms.CharField(max_length=11,min_length=11,required=True,validators=[mobile_validator,],error_messages={
'required':'手机号不能为空','max_length':'手机号长度有误','min_length':'手机号长度有误'
})
image_uuid = forms.UUIDField(required=True,error_messages={
'required':'图片uuid不能为空'
})
image_text = forms.CharField(required=True,max_length=4,min_length=4,error_messages={
'required': '图片验证码不能为空', 'max_length': '图片验证码长度有误', 'min_length': '图片验证码长度有误'
})
def clean(self):
#取数据
cleaned_data = super().clean()
mobile = cleaned_data.get('mobile')
image_text = cleaned_data.get('image_text')
image_uuid = cleaned_data.get('image_uuid')
#校验手机号
if Users.objects.filter(mobile=mobile).count():
raise forms.ValidationError('该手机号已被注册')
#取出图片文本
con_reids = get_redis_connection(alias='verify_codes')
img_key = 'imagecode_{}'.format(image_uuid).encode('utf-8')
real_image_text_origin = con_reids.get(img_key)
real_image_text = real_image_text_origin.decode('utf-8') if real_image_text_origin else None
con_reids.delete(img_key)
#校验图片验证码
if (not real_image_text) or (image_text != real_image_text):
raise forms.ValidationError('图片验证失败')
#验证短信发送标志是否还在
sms_flag_key = 'sms_flag_{}'.format(mobile).encode('utf-8')
sms_flag = con_reids.get(sms_flag_key)
if sms_flag:
raise forms.ValidationError("短信发送太频繁")
class Check_send_message(View):
"""
"""
def post(self,request):
data_json = request.body
if not data_json:
return to_json_data(errno= Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
data_dict = json.loads(data_json.decode('utf-8'))
form = Send_message_form(data = data_dict)
if form.is_valid():
mobile = form.cleaned_data.get('mobile')
sms_text = ''.join(random.sample(string.digits,constants.MESSAGE_NUM))
con_reids = get_redis_connection(alias='verify_codes')
#创建一个短信发送标志,60秒有效期
p1 = con_reids.pipeline()
sms_flag_key = 'sms_flag_{}'.format(mobile).encode('utf-8')
sms_text_key = 'sms_text_{}'.format(mobile).encode('utf-8')
try:
p1.setex(sms_flag_key,constants.SMS_FLAG,1)
p1.setex(sms_text_key,constants.SMS_CODE_REDIS_EXPIRES,sms_text)
p1.execute()
except Exception as e:
logger.debug("redis存放短信验证码失败:{}".format(e))
return to_json_data(errno=Code.UNKOWNERR,errmsg=error_map[Code.UNKOWNERR])
logger.info('短信验证码为:{}'.format(sms_text))
#发送短信验证码
try:
ccp = CCP()
result = ccp.send_template_sms(mobile, [sms_text, constants.SMS_CODE_REDIS_EXPIRES], constants.SMS_CODE_TEMP_ID)
except Exception as e:
logger.error("短信验证码发送异常,mobile:{}-error:{}".format(mobile,e))
return to_json_data(errno=Code.SMSERROR,errmsg=error_map[Code.SMSFAIL])
if result != 0:
logger.warning("发送短信验证码失败,mobile:{}".format(mobile))
return to_json_data(errno=Code.SMSFAIL,errmsg=error_map(Code.SMSFAIL))
else:
logger.info("{}手机号发送验证码成功".format(mobile))
return to_json_data(errno=Code.OK,errmsg="手机验证码发送成功")
else:
error_list = []
for item in form.errors.get_json_data().values():
# error_list = error_list.append(item[0].get('message'))
error_list.append(item[0].get('message'))
err_msg_str = '/'.join(error_list) # 拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
urlpattern = [
path('send_message/',views.Check_send_message.as_view(),name = 'send_message'),
]
$.ajax({
// 请求地址
url: "/send_message/",
// 请求方式
type: "POST",
data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
async: false // 关掉异步功能
})
.done(function (res) {
if (res.errno === "0") {
// 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
message.showSuccess('短信验证码发送成功');
let num = 60;
// 设置一个计时器
let t = setInterval(function () {
if (num === 1) {
// 如果计时器到最后, 清除计时器对象
clearInterval(t);
// 将点击获取验证码的按钮展示的文本恢复成原始文本
$smsCodeBtn.html("获取验证码");
// // 将点击按钮的onclick事件函数恢复回去
// $(".get_code").attr("onclick", "sendSMSCode();");
} else {
num -= 1;
// 展示倒计时信息
$smsCodeBtn.html(num + "秒");
}
}, 1000);
} else {
message.showError(res.errmsg);
}
})
.fail(function(res){
message.showError(res.errmsg);
});
前后端传来传去的是json格式,故要用json模块相互对dict、json转换
前后端传的的数据也是byte形式,对于传来的要主动解码
redis的数据存放都是byte格式,故要记得编码,解码
最后的注册接口,我们除了图片验证码不需要验证,其他都要重复验证一遍。
1、接收前端传来的参数,用户名、电话、密码、确认密码、短信验证码 2、form表单的构造,并实现相应的验证逻辑 3、验证成功后,将用户名、密码、电话保存在数据库中,用到用户模型的creat_user方法 4、注册成功后,自动跳转到已登陆状态
class User_form(forms.Form):
username = forms.CharField(max_length=20,min_length=5,required=True,error_messages={
'max_length':'用户名长度不能低于5位','min_length':'用户名长度不能大于20位','required':'用户名不能为空'
})
password = forms.CharField(max_length=20,min_length=6,required=True,error_messages={
'max_length':'密码长度不能低于6位','min_length':'密码名长度不能大于20位','required':'密码不能为空'
})
password_repeat = forms.CharField(max_length=20,min_length=6,required=True,error_messages={
'max_length':'密码长度不能低于6位','min_length':'密码名长度不能大于20位','required':'密码不能为空'
})
mobile = forms.CharField(max_length=11,min_length=11,required=True,error_messages={
'max_length':'手机号长度不符合','min_length':'手机号长度不符合','required':'手机号不能为空'
})
sms_code = forms.CharField(max_length=6,min_length=6,required=True,error_messages={
'max_length': '验证码长度不符合', 'min_length': '验证码长度不符合', 'required': '验证码不能为空'
})
def clean_mobile(self):
tel = self.cleaned_data.get('mobile')
if not re.match(r'^1[3-9]\d{9}$',tel):
raise forms.ValidationError('手机号格式不正确')
if Users.objects.filter(mobile=tel).count():
raise forms.ValidationError('该手机号已被注册')
return tel
def clean(self):
clean_data = super().clean()
passwd = clean_data.get('password')
passwd_re = clean_data.get('password_repeat')
tel = clean_data.get('mobile')
sms = clean_data.get('sms_code')
#验证密码
if passwd != passwd_re:
raise forms.ValidationError('两次密码不一致')
#验证手机验证码
con_redis = get_redis_connection(alias='verify_codes')
sms_key = 'sms_text_{}'.format(tel).encode('utf-8')
sms_text = con_redis.get(sms_key)
if not sms_text or sms_text.decode('utf-8') != sms:
raise forms.ValidationError('短信验证码错误')
class Register(View):
"""
"""
def get(self,request):
return render(request, 'users/register.html')
def post(self,request):
json_data = request.body
if not json_data:
return to_json_data(errno= Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf-8'))
form = User_form(data=dict_data)
if form.is_valid():
#将用户名、密码保存到数据库中
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
user = Users.objects.create_user(username=username,password=password,mobile=mobile)
user.save()
login(request, user)
return to_json_data(errmsg="恭喜您,注册成功!")
else:
error_list = []
for item in form.errors.get_json_data().values():
# error_list = error_list.append(item[0].get('message'))
error_list.append(item[0].get('message'))
err_msg_str = '/'.join(error_list) # 拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)
urlpatterns = [
path('register/',views.Register.as_view(),name='register'),
]
// 1、创建请求参数
let SdataParams = {
"username": sUsername,
"password": sPassword,
"password_repeat": sPasswordRepeat,
"mobile": sMobile,
"sms_code": sSmsCode
};
// 2、创建ajax请求
$.ajax({
// 请求地址
url: "/users/register/", // url尾部需要添加/
// 请求方式
type: "POST",
data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
if (res.errno === "0") {
// 注册成功
message.showSuccess('恭喜你,注册成功!');
setTimeout(function () {
// 注册成功之后重定向到主页
window.location.href = document.referrer;
}, 1000)
} else {
// 注册失败,打印错误信息
message.showError(res.errmsg);
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
踩坑记录: 1、reids所有的有效期的数字单位是秒 2、保存在redis的key,value都是编码后的,所有取出来要解码,取值要对key编码