用户模型使用django自带的auth中的AbstractUser来进行改写,
具体实现过程:在 users app 里面的 models.py 里面进行用户模型的修改
users/models.py
from django.db import models
from django.contrib.auth.models import UserManager as _UserManager
class UserManager(_UserManager):
"""
重写创建超级用户时需要输入email字段
"""
def create_superuser(self, username, password, email=None, **extra_fields):
return super().create_superuser(username=username, password=password, email=email, **extra_fields)
users/models.py
from django.contrib.auth.models import AbstractUser
class Users(AbstractUser):
"""
重写users模型
"""
objects = UserManager()
mobile = models.CharField(
max_length=11,
help_text="手机号",
verbose_name="手机号",
error_messages={
"unique": "手机号已被注册"
})
email_active = models.BooleanField(default=False, help_text="邮箱状态")
class Meta:
db_table = "tb_users" # 数据库名
verbose_name = "用户" # 指明在admin站点中的名字
verbose_name_plural = verbose_name # 显示复数名称
def __str__(self):
return self.username
自定义的Users模型需要进行配置后才能使用。
settings.py
# 配置自定义的用户模型
AUTH_USER_MODEL = "users.Users"
在用户注册时很多字段验证在后续的其他项目时还能使用,为了复用这里重新新建一个app专门用来做用户注册的用户名、手机号、图形验证码、短信验证码的验证。
创建1个新的app verifications,拉到apps里面去。
项目下面的 urls.py
from django.urls import path, include
urlpatterns = [
path('', include("verifications.urls")),
]
应用 verifications/urls.py配置
from django.urls import re_path
from verifications import views
app_name = "verifications"
urlpatterns = [
re_path("image_code//" , views.ImageCodes.as_view(), name="image_code"),
]
注册功能的js代码不单独放,都统一放在register.js里面
users/register.js
$(function () {
let $img = $('.form-item .captcha-graph-img img'); //获取图片信息
let sImageCodeId=''; //定义一个空字符串用来放uuid字符串
generateImageCode();
// 当点击验证码时刷新验证码
$img.click(generateImageCode);
// 生成图片UUID验证码
function generateUUID() {
let d = new Date().getTime();
if (window.performance && typeof window.performance.now === "function") {
d += performance.now(); //use high-precision timer if available
}
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c==='x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}
//生成验证码图片
function generateImageCode() {
sImageCodeId = generateUUID(); //这里不能有let这相当于对前面的sImageCodeId进行修改
// console.log("第一个uuid:"+sImageCodeId);
let imageCodeUrl = '/image_code/'+sImageCodeId+'/'; //拼接图片验证码请求路径
$img.attr('src',imageCodeUrl); //给图片添加src链接 路径
}
});
图形验证码需要使用图形验证码插件,网上有很多现成的代码,这里可以在gitee.com获取captcha图形验证码插件。
在前面settings.py配置redis数据库时只设置了一个为default默认0数据库,将图形验证码和短信验证码的放在为verify_code的1数据库
settings.py中添加验证码存放的数据库
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/0',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient'
}
},
# 新添加存放短信验证码和图形验证码
'verify_code': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient'
}
},
}
verifications/views.py
import logging
from django.views import View
from django.http import HttpResponse
from django_redis import get_redis_connection
from verifications import contains
from utils.captcha.captcha import captcha
logger = logging.getLogger("django")
class ImageCodes(View):
"""
create image_code view
"""
def get(self, request, image_code_id): # 从前端获取到传过来的参数uuid
verify_text, verify_image = captcha.generate_captcha() # 生成验证码文本和图片
# 链接redis步骤 将生成的验证码文本和图片保存到redis中
# 1.链接redis区
redis_obj = get_redis_connection(alias="verify_code")
# 2. 构造键值对
verify_key = "image_{}".format(image_code_id)
# 3. 保存数据到redis并设置有效期
redis_obj.setex(verify_key, contains.IMAGE_CODE_EXPIRE_TIME, verify_text)
logger.info('verify_text:{}'.format(verify_text)) # 后台显示验证码信息
return HttpResponse(content=verify_image, content_type='image/jpg') # 把验证码图片返回到前端
verification/urls.py
from django.urls import re_path
from verifications import views
app_name = "verifications"
urlpatterns = [
re_path("username/(?P\w{6,18})/" , views.CheckUsernameView.as_view(), name="username"),
]
users/register.js
$(function () {
let $username = $('#user_name'); //获取用户名标签
//鼠标移出用户名输入框时执行判断
$username.blur(function () {
fn_check_username();
});
// 用户名是否存在
function fn_check_username() {
let sUsername = $username.val(); //获取表单数据
let sReturnValue = '';
// 前端校验
if (sUsername === ""){
// alert("用户名不能为空") ;
// alert('用户名能为空!'); //这里需要改进
return
}else if(!(/\w{6,18}/).test(sUsername)){
// alert('请输入6~18位字符的用户名!'); //这里需要改进
return
}
//ajax请求判断数据库是否存在 后端校验
$.ajax({
url:'/username/' + sUsername +'/',
type:'GET',
dataType:'json',
async: false, // 关闭异步
success:function (arg) {
if(arg.data.count !== 0){ // 不等于0表示已经注册了
// alert("用户名已注册,请重新输入")
sReturnValue = "";
}else{
sReturnValue="success";
alert("用户名可以正常使用")
}
},
error:function(){
alert("用户名ajax,服务器超时,请重新输入");
sReturnValue = "";
}
});
return sReturnValue //返回出该信息
}
});
verifications/views.py
from django.views import View
from django.http import JsonResponse
from users import models
class CheckUsernameView(View):
"""
create username verify view
# 1. 创建一个类
request: GET
params: username
"""
# 2. 创建个get方法来处理逻辑
def get(self, request, username):
# 3. 从数据库中查看是否存在该用户
data = {
"username": username,
"count": models.Users.objects.filter(username=username).count() # 获取数据库中有几条这个信息:无则0
}
# 4. 返回到前端
return JsonResponse(data=data)
verification/urls.py
from django.urls import re_path
from verifications import views
app_name = "verifications"
urlpatterns = [
re_path("mobile/(?P1[3-9]\d{11})/" , views.CheckMobileView.as_view(), name="mobile"),
]
users/register.js
$(function () {
let $mobile = $("#mobile"); //获取手机号
//鼠标移出手机号输入框时执行判断
$mobile.blur(function() {
fn_check_mobile();
});
// 手机号是否存在
function fn_check_mobile(){
let sMobile = $mobile.val();
let sReturnMobileValue = "";
// 前端校验
if (sMobile === ""){
// alert("手机号输入为空");
alert("手机号为空");
}else if(!(/^1[3-9]\d{9}/).test(sMobile)){
alert("请输入正确的手机号格式") ;
}
//ajax请求判断数据库是否存在 后端校验
$.ajax({
url:"/mobile/"+sMobile+'/',
type:"GET",
dataType: "json",
async:false, // 关闭异步
success:function (arg) {
if(arg.data.count !== 0){
alert(sMobile+"手机号已被注册,请重新输入");
sReturnMobileValue = "";
}else{
alert(sMobile+"手机号能正常使用");
sReturnMobileValue = "success";
}
},
error:function () {
alert("手机号ajax,服务器超时,请重新输入");
sReturnMobileValue = "";
}
});
return sReturnMobileValue //返回出该信息
}
});
verifications/views.py
from django.views import View
from django.http import HttpResponse, JsonResponse
from users import models
class CheckMobileView(View):
"""
create mobile verify view
# 1.创建一个类
request: GET
params: mobile
"""
# 2. 创建个get方法来处理逻辑
def get(self, request, mobile):
# 3. 从数据库中查看是否存在该用户
data = {
"mobile": mobile,
"count": models.Users.objects.filter(mobile=mobile).count()
}
# 5. 返回到前端
return JsonResponse(data=data)
verification/urls.py
from django.urls import re_path
from verifications import views
app_name = "verifications"
urlpatterns = [
re_path("sms_code/", views.SmsCodeView.as_view(), name="sms_code"),
]
users/register.js
$(function () {
let sImageCodeId=''; //定义一个空字符串用来放uuid字符串
let $mobile = $("#mobile"); //获取手机号
let $smsCodeBtn = $(".form-item .sms-captcha"); //发送短信验证按钮
let $smsCodeText = $(".form-item .form-captcha"); //图形验证码输入框文本
//短信验证
$smsCodeBtn.click(function(){
// 判定手机号是否有输入
if ( fn_check_mobile() !== "success"){
alert("手机号为空");
return
}
// 判断用户是否输入图形验证码
let text = $smsCodeText.val();
if (!text){
alert("请输入图形验证码");
return
}
// 判断UUID
if(!sImageCodeId){
console.log("第2个"+sImageCodeId);
alert("图形UUID不对");
return
}
let dataParams={
'mobile':$mobile.val(),
'image_text':text,
"image_code_id":sImageCodeId,
};
$.ajax({
url:"/sms_code/",
type:"POST",
data:JSON.stringify(dataParams),
success:function(arg){
console.log(arg);
if(arg.errno==="200"){ // errno: "200", errmsg: "短信发送正常", data: null
alert("短信验证码发送成功");
//倒计时功能
let num = 60;
// 设置计时器
let t = setInterval(function () {
if (num===1){
clearInterval(t);
$smsCodeBtn.html("重新发送验证码")
}else{
num -= 1;
//展示倒计时信息
$smsCodeBtn.html(num+"秒");
}
},1000)
}else{
alert(arg.errmsg);
}
},
error:function(){
alert("短信验证ajax,服务器超时,请重试")
}
})
});
});
在写代码时需要很多错误代码和错误代码的意思,这里单独建一个文件用来放这个错误代码,同时后续在写代码时需要很多时候传递给前端一个json格式的文件,包含错误代码,这样前端才能做好判断。
在utlis下面创建一个res_code文件夹,创建一个 res_code.py文件用来放错误代码, json_function.py用来放置转化json格式的插件:
utils/res_code/res_code.py
class Code:
OK = "200"
DBERR = "4001"
NODATA = "4002"
DATAEXIST = "4003"
DATAERR = "4004"
METHERR = "4005"
SMSERROR = "4006"
SMSFAIL = "4007"
SESSIONERR = "4101"
LOGINERR = "4102"
PARAMERR = "4103"
USERERR = "4104"
ROLEERR = "4105"
PWDERR = "4106"
SERVERERR = "4500"
UNKOWNERR = "4501"
error_map = {
Code.OK: "成功",
Code.DBERR: "数据库查询错误",
Code.NODATA: "无数据",
Code.DATAEXIST: "数据已存在",
Code.DATAERR: "数据错误",
Code.METHERR: "方法错误",
Code.SMSERROR: "发送短信验证码异常",
Code.SMSFAIL: "发送短信验证码失败",
Code.SESSIONERR: "用户未登录",
Code.LOGINERR: "用户登录失败",
Code.PARAMERR: "参数错误",
Code.USERERR: "用户不存在或未激活",
Code.ROLEERR: "用户身份错误",
Code.PWDERR: "密码错误",
Code.SERVERERR: "内部错误",
Code.UNKOWNERR: "未知错误",
}
utils/res_code/json_funciton.py
from django.http import JsonResponse
from utils.res_code.res_code import Code
# 处理json格式转化,并加入异常码和异常信息
def to_json_data(errno=Code.OK, errmsg='', data=None, **kwargs):
"""
返回给前端 json数据以及错误信息
:param errno: 错误代码
:param errmsg: 错误信息
:param data: 参数
:param kwargs: 不定长数据
:return:
"""
json_dict = {'errno': errno, 'errmsg': errmsg, 'data': data}
if kwargs:
json_dict.update(kwargs)
return JsonResponse(json_dict)
verifications/contains.py
# -*- coding: utf-8 -*-
"""
@Time : 2020/2/26 21:29
@Author : 半纸梁
@File : contains.py
"""
IMAGE_CODE_EXPIRE_TIME = 5*60 # 图形验证码有效时间
SMS_CODE_EXPIRE_TIME = 5*60 # 短信验证码有效时间
SMS_REPEAT_EXPIRE_TIME = 60 # 可重复发送短信的时间
SMS_TEMPLATE = 1 # 短信发送模板
SMS_CCP_EXPIRE_TIME = 5 # 云通讯短信有效时间 以分钟计算的
verifications/views.py
import logging
import json
import random
from django.views import View
from django_redis import get_redis_connection
from verifications import contains
from verifications.forms import SmsCodeForm
from utils.yuntongxun.sms import CCP
from utils.res_code.json_function import to_json_data
from utils.res_code.res_code import Code, error_map
logger = logging.getLogger("django")
class SmsCodeView(View):
"""
# 1. 创建一个SmsCodeView类
param: mobile、image_text、image_code_id
"""
# 2. 创建一个post方法用来处理逻辑
def post(self, request):
# 3. 获取前端传来的数据
json_data = request.body
# 4. 将数据转化为字典
dict_data = json.loads(json_data)
# 5. 将数据传递给SmsCodeForm表单进行校验
form = SmsCodeForm(data=dict_data)
# 6. 校验成功处理方式
if form.is_valid():
# 7. 获取校验后的数据
mobile = form.cleaned_data.get("mobile")
# 8. 生成短信验证码
sms_text = "%06d" % random.randint(0, 999999)
# 9. 将短信验证码和和过期时间保存到redis中
redis_obj = get_redis_connection("verify_code")
sms_text_key = "sms_code_{}".format(mobile).encode("utf8")
sms_repeat_key = "sms_sixty_{}".format(mobile).encode("utf8")
redis_obj.setex(sms_text_key, contains.SMS_CODE_EXPIRE_TIME, sms_text) # key, expire_time, value
redis_obj.setex(sms_repeat_key, contains.SMS_CODE_EXPIRE_TIME, contains.SMS_REPEAT_EXPIRE_TIME)
# logging.info("发送短信正常[mobile:%s sms_num:%s]" % (mobile, sms_num)) # 调试代码时候用,在控制台显示
# 9. 使用用通讯插件发送短信
try:
result = CCP().send_Template_sms(mobile, [sms_text, contains.SMS_CCP_EXPIRE_TIME], contains.SMS_TEMPLATE)
except Exception as e:
logger.error("短信发送异常[mobile:{},error:{}]".format(mobile, e))
return to_json_data(errno=Code.SMSERROR, errmsg=error_map[Code.SMSERROR]) # 短信发送异常
else:
if result == 0: # 发送成功
logger.info("短信发送成功[mobile:{},sms_code:{}]".format(mobile, sms_text))
return to_json_data(errmsg="短信发送正常")
else: # 发送失败
logger.warning("短信发送失败[mobile:{}]".format(mobile))
return to_json_data(errno=Code.SMSFAIL, errmsg=error_map[Code.SMSFAIL])
# 校验未通过
else:
err_msg_list = []
for item in form.errors.values():
err_msg_list.append(item[0])
err_info = '/'.join(err_msg_list)
return to_json_data(errno=Code.PARAMERR, errmsg=err_info)
verifications/forms.py
# -*- coding: utf-8 -*-
"""
@Time : 2020/2/27 9:49
@Author : 半纸梁
@File : forms.py
"""
from django import forms
from django_redis import get_redis_connection
from django.core.validators import RegexValidator
mobile_reg = RegexValidator(r"^1[3-9]\d{9}","手机号格式不正确")
class SmsCodeForm(forms.Form):
"""
check sms_code field
字段一定要和js中传递过来的保持一样
"""
mobile = forms.CharField(
max_length=11,
min_length=11,
validators=[mobile_reg],
error_messages={
"max_length": "手机号格式不正确",
"min_length": "手机号格式不正确",
"required": "手机号不能为空"
}
)
image_code_id = forms.UUIDField(error_messages={"required": "UUID不能为空"}) #
image_text = forms.CharField(
max_length=4,
min_length=4,
error_messages={
"max_length": "图形验证码格式不正确",
"min_length": "图像验证码格式不正确",
"required": "图形验证码不能为空"
}
)
def clean(self):
# 1. 获取清洗后的数据
cleaned_data = super().clean()
mobile = cleaned_data.get("mobile")
image_code_uuid = cleaned_data.get("image_code_id")
image_text = cleaned_data.get("image_text")
# 2. 建立redis链接
try:
redis_obj = get_redis_connection("verify_code")
except Exception as e:
raise forms.ValidationError("redis数据库连接错误")
# 3. 构建image_code查询的键
image_code_key = "image_{}".format(image_code_uuid).encode("utf8")
# 4. 获取数据库中的image_code的值
redis_byte_image_text = redis_obj.get(image_code_key)
# 5. 删除redis中该uuid的键
redis_obj.delete(image_code_key) #
# 6. 将拿到的值化为utf8字符:redis中拿到的值都是二进制
redis_image_text = redis_byte_image_text.decode("utf8") if redis_byte_image_text else None
# 7. 判断现在的image_text和redis_image_text是否一致
if image_text.upper() != redis_image_text:
raise forms.ValidationError("图形验证码校验失败")
# 8. 判断60秒内是否有重复发送短信
redis_sms_repeat_key = "mobile_{}".format(mobile).encode("utf8") # 构建短信60秒内发送查询的键
if redis_obj.get(redis_sms_repeat_key):
raise forms.ValidationError("短信发送太频繁,请稍后再试")
用户注册功能点击注册放在users app里面进行完成。