注册功能是每一个项目锁必须的模块,在此项目中,重点是后端的服务器之间的交互,前端的代码并非重点,所以,有些js代码是硬编码,没有做优化,敬请注意。下面是对注册功能的一些分析与代码,分析过程的书写并非非常的规范,只可做为逻辑来参考。如果需要技术规范文档格式,请自行百度。
一、用户注册功能
数据库创建一条数据
django操作数据库模型

1、用户模型设计(用户表的设计)
1.1、用户表字段分析
用户名(账号)
密码
手机号
邮箱
邮箱状态(邮箱是否有效)
1.2、用户模型设计
自定义User模型
from django.db import models
from django.contrib.auth.models import AbstractUser,UserManager as _UserManager
class UserManager(_UserManager):
"""
自定义管理器,用来修改使用createsuper命令创建用户时候,必须要提供Email行为
"""
def create_superuser(self, username, password, email=None, **extra_fields):
super().create_superuser(username=username,password=password,email=email)
class User(AbstractUser):
"""
注册模型,添加mobile,email_active
"""
mobile = models.CharField('手机号',max_length=11,unique=True,help_text='手机号',error_messages={'unique':'此手机号已经存在'})
email_active = models.BooleanField('邮箱状态',default=False)
class Meta:
db_table = 'tb_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
REQUIRED_FILEDS = ['email']
objects = UserManager()
二、功能需求分析
1、接口设计思路
分析业务逻辑,明确在这个业务上涉及几个相关业务,将每一个子业务当作一个接口来设计
-
分析接口功能,明确接口的访问方式与数据返回
接口请求方式GET,POST,PUT,DELETE
接口的url定义
接口的参数(路径参数,查询参数,表单,json)
返回数据以及数据格式
2、注册功能分析
1、流程图分析
2、功能接口分析
发送一次请求,就作为一个接口
注册页面
图形验证码
用户名校验是否注册
手机号码校验是否注册
短信验证码
注册保存用户信息
三、各个接口分析
1、注册页面
1、接口设计
1.1、接口说明
| 条目 | 说明 |
|---|---|
| 请求方法 | GET |
| Url定义 | /user/register/ |
| 参数格式 | 无参数 |
1.2、返回数据
返回html页面
2、后端代码
1.1、视图
class RegisterView(View):
"""
register page
url:/user/register/
"""
def get(self,request):
return render(request, 'user/register.html')
1.2、url
path('register/',views.RegisterView.as_view(),name='register'),
2、图形验证码
1、接口设计
使用第三方库说明:captcha库是python非常有效的图片验证库
1.1、接口说明
| 条目 | 说明 |
|---|---|
| 请求方式 | GET |
| Url定义 | /image_code/ |
| 参数格式 | 查询参数 |
1.2、参数说明
| 参数名 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| rand | 字符 | 否 | 随机浮点字符 |
1.3、返回数据
图形验证码
2、后端代码
1.1、视图
import logging
from django.shortcuts import render
from django.http import HttpResponse
from utils.captcha.captcha import captcha
from . import constants
logger = logging.getLogger('django')
def image_code_view(request):
"""
生成验证码
url:/image_code/
:param request:
:return:
"""
text,image = captcha.generate_captcha()
request.session['image_code'] = text
request.session.set_expiry(constants.IMAGE_CODE_EXPIRES)
logger.info('Image_code:{}'.format(text))
return HttpResponse(content=image,content_type='image/jpg')
1.2、url
path('image_code/',views.image_code_view,name='image_code'),
1.3、js
$('.captcha-graph-img img').click(function () {
$(this).attr('src','/image_code/?rand='+Math.random())
});
3、用户名校验是否注册
1、接口设计
使用message.js文件,用来弹出提示信息,增加用户体验度。
1.1、接口说明
| 条目 | 说明 |
|---|---|
| 请求方式 | GET |
| Url定义 | /username/(/?P<username>\w{5,20}/)/ |
| 参数格式 | url路径参数 |
1.2、参数说明
| 参数名 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| username | 字符 | 是 | 随机浮点字符 |
1.3、返回数据
{
'errno':'0',
'errmsg':'OK',
'data':{
'username':username, #查询用户名
'count':1 #查询用户数量
}
}
2、后端代码
1.1、视图
from django.http import HttpResponse,JsonResponse
def check_username_view(request,username):
"""
用户验证信息
url:/username/(?P<username>\w{5,20})/
:param request:
:param username:
:return:
"""
{
'errno':'0',
'errmsg':'OK',
'data':{
'username':username, #查询用户名
'count':1 #查询用户数量
}
}
return JsonResponse(data)
1.2、url
from django.urls import path,re_path
from . import views
app_name = 'verifcation'
urlpatterns = [
re_path('username/(?P<username>\w{5,20})/',views.check_username_view,name='check_username'),
]
1.3、js
let $username = $('#username');
$username.blur(fnCheckUsername);
function fnCheckUsername() {
isUsernameReady = false;
let sUsername = $username.val();
if (sUsername === ""){
message.showError("用户名不能为空!");
return
}
if (!(/^\w{5,20}$/).test(sUsername)){
message.showError("请使用5-20个字符作为用户名");
return
}
//发送ajax
$
.ajax({
url:'/username/'+ sUsername + '/',
type:'GET',
dataType:'json',
})
.done((res)=>{
if (res.data.count !== 0){
message.showError(res.data.username + '用户名已注册,请重新输入!');
}else{
message.showInfo(res.data.username +'用户名正常,可以使用!');
isUsernameReady = true
}
})
.fail(()=>{
message.showError('服务超时,请重新输入!');
})
}
4、手机号码校验是否注册
1、接口设计
使用message.js文件,用来弹出提示信息,增加用户体验度。
1.1、接口说明
| 条目 | 说明 |
|---|---|
| 请求方式 | GET |
| Url定义 | /mobile/(?P<mobile>1[3-9]\d{9})/ |
| 参数格式 | url路径参数 |
1.2、参数说明
| 参数名 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| mobile | 字符 | 是 | 输入手机号 |
1.3、返回数据
{
'errno':'0',
'errmsg':'OK',
'data':{
'mobile':mobile, #查询手机号码
'count':1 #查询手机号码数量
}
}
2、后端代码
1.1、视图
from django.http import HttpResponse,JsonResponse
def check_mobile_view(request,mobile):
"""
手机号码验证信息
url:/mobile/(?P<mobile>1[3-9]\d{9})/
:param request:
:param mobile:
:return:
"""
data = {
'errno':'0',
'errmsg':'OK',
'data':{
'mobile':mobile, #查询用户名
'count':1 #查询用户数量
}
}
return JsonResponse(data)
1.2、url
from django.urls import path,re_path
from . import views
app_name = 'verifcation'
urlpatterns = [
re_path('mobile/(?P<mobile>1[3-9]\d{9})/',views.check_mobile_view,name='check_mobile'),
]
1.3、js
let $mobile = $('#mobile');
$mobile.blur(fnCheckMobile);
function fnCheckMobile() {
isMobileReady = false;
let sMobile = $mobile.val();
if (sMobile === ""){
message.showError("手机号不能为空!");
return
}
if (!(/^1[3-9]\d{9}$/).test(sMobile)){
message.showError("请使用正确手机号码");
return
}
//发送ajax
$
.ajax({
url:'/mobile/'+ sMobile + '/',
type:'GET',
dataType:'json',
})
.done((res)=>{
if (res.data.count !== 0){
message.showError(res.data.mobile + '手机号已注册,请重新输入!');
}else{
message.showInfo(res.data.mobile + '手机号正常,可以使用!');
isMobileReady = true
}
})
.fail(()=>{
message.showError('服务超时,请重新输入!');
})
}
5、 获取短信验证码
1、业务流程分析:
1.1、校验手机号码:长度,格式...
1.2、校验图形验证码:
1.3、校验是否在60秒内有发送记录
1.4、发送短信验证码
1.5、保存短信验证码,(保存在哪里?)
1.6、保存发送记录
2、接口说明
| 条目 | 说明 |
|---|---|
| 请求方式 | POST |
| Url定义 | /sms_code/ |
| 参数格式 | from表单 |
1.3、参数说明
| 参数名 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| mobile | 字符串 | 是 | 输入手机号 |
| captcha | 字符串 | 是 | 用户输入的图形验证码 |
1.4、返回数据
返回数据
{
'errno':'0',
'errmsg':'发送短信验证码成功',
}
2、后端代码
1.1、视图
import logging
import random
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from django.views import View
from django_redis import get_redis_connection
from utils.captcha.captcha import captcha
from utils.json_res import json_response
from utils.res_code import Code,error_map
from user.models import User
from . import constants
from .forms import CheckImageForm
logger = logging.getLogger('django')
class SmscodeView(View):
"""
发送短信验证码验证视图
url:/sms_code/
"""
def post(self,request):
"""
1.1、校验手机号码:长度,格式...
1.2、校验图形验证码:
1.3、校验是否在60秒内有发送记录
校验部分放在form表单中进行
1.4、发送短信验证码
1.5、保存短信验证码,(保存在哪里?)
1.6、保存发送记录
"""
#request需要传参数到form表单中
form = CheckImageForm(request.POST,request=request)
if form.is_valid():
#获取手机号码
mobile = form.cleaned_data.get('mobile')
#生成短信验证码
sms_code = ('').join([random.choice('1234567890') for _ in range(constants.SMS_CODE_LENGTH)])
logger.info('发送短信验证码:[正常][mobile:{},sms_code:{}]'.format(mobile,sms_code))
# 发送短信验证码
# 保存发送记录
#创建短信验证码发送记录的key
sms_flag_key = 'sms_flag_{}'.format(mobile)
#创建短信验证码内容的key
sms_text_key = 'sms_key_{}'.format(mobile)
radis_conn = get_redis_connection(alias='verify_code')
pl = radis_conn.pipeline()
try:
pl.setex(sms_flag_key,constants.SMS_CODE_INTERVAL,1)
pl.setex(sms_text_key,constants.SMS_CODE_EXPIRES*60,sms_code)
#让管道通知radis执行命令
pl.execute()
return json_response(errmsg='短信验证码发送成功!')
except Exception as e:
logger.error('radis执行异常:{}'.format(e))
return json_response(errno=Code.UNKOWNERR,errmsg=error_map[Code.UNKOWNERR])
else:
#将表单的保存信息进行拼接e
err_msg_list = []
for item in form.errors.values():
err_msg_list.append(item[0])
err_msg_str = '/'.join(err_msg_list)
return json_response(errno=Code.PARAMERR,errmsg=err_msg_str)
1.2、form表单
from django import forms
from django.core.validators import RegexValidator
from django_redis import get_redis_connection
from user.models import User
#创建手机号码正则校验器
mobile_validator = RegexValidator(r'^1[3-9]\d{9}/pre>,'手机号码格式不正确')
class CheckImageForm(forms.Form):
"""
校验图形验证码
1.1、校验手机号码:长度,格式...
1.2、校验图形验证码:
1.3、校验是否在60秒内有发送记录
"""
# 1、校验手机号码
# 获取手机号码
# mobile = request.POST.get('mobile')
# 校验手机号长度
# 校验手机号格式
# 校验手机号是否被注册
# 2、校验图形验证码
# 以上校验过程可以放在form表单中进行
#接受视图穿过来的参数,并重写父级函数,否则会报错
def __init__(self,*args,**kwargs):
#request参数是通过不定长来传参数的,所以就其在不定长中剔除出来并赋值给form表单类本身
self.request = kwargs.pop('request')
super().__init__(*args,**kwargs)
mobile = forms.CharField(max_length=11,min_length=11,validators=[mobile_validator],error_messages={
'max_length':'手机号长度有误!',
'min_length':'手机号长度有误!',
'required':'手机号码不能为空!',
})
captcha = forms.CharField(max_length=4,min_length=4,error_messages={
'max_length': '图形验证码长度有误!',
'min_length': '图形验证码长度有误!',
'required': '图形验证码不能为空!',
})
def clean(self):
clean_data = super().clean()
mobile = clean_data.get('mobile')
captcha = clean_data.get('captcha')
#如果前面的的校验有问题,就没必要往下进行了
if mobile and captcha:
#校验图形验证码
#获取session保存的验证码,和用户输入的验证码比对
image_code = self.request.session.get('image_code')
if not image_code:
raise forms.ValidationError('图形验证码失效')
if image_code.upper() !=captcha.upper():
raise forms.ValidationError('图形验证码验证失败!')
#验证是否60秒内发过短信
#保存在了radis内
radis_conn = get_redis_connection(alias='verify_code')
if radis_conn.get('sms_flag_{}'.format(mobile)):
raise forms.ValidationError('获取验证码过于频繁')
#校验手机号是否注册
if User.objects.filter(mobile=mobile):
raise forms.ValidationError('手机号码已经注册')
return clean_data
1.3 、 js
let $smsButton = $('.sms-captcha');
$smsButton.click(()=>{
//拿到数据,图形验证码
let sCaptcha = $('#captcha_graph').val();
if(sCaptcha === ""){
message.showError('请输入图形验证码')
return
}
//判断手机号是否准备好了
if(!isMobileReady){
fnCheckMobile();
return
}
$
.ajax({
url:"/sms_code/",
type:"POST",
data:{
mobile:$mobile.val(),
captcha:sCaptcha,
},
dataType:"json"
})
.done((res)=>{
if(res.errno !== '0'){
message.showError(res.errmsg);
}else{
message.showSuccess(res.errmsg);
}
})
.fail(()=>{
message.showError("服务超时,请重试");
})
});
1.4、url
path('sms_code/',views.SmscodeView.as_view(),name='sms_code')
6、注册保存用户信息
1、业务流程分析:
-
对参数进行校验
校验用户名
校验密码
校验手机号
校验短信验证码
新建数据库记录
1.1、接口设计及说明
| 条目 | 说明 |
|---|---|
| 请求方式 | POST |
| Url定义 | /user/register/ |
| 参数格式 | from表单 |
注意:post请求,一定要带csrf_token
1.2、参数说明
| 参数名 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| username | 字符串 | 是 | 用户输入的用户名 |
| password | 字符串 | 是 | 用户输入的密码 |
| password_repeat | 字符串 | 是 | 用户输入的重复密码 |
| mobile | 字符串 | 是 | 输入手机号 |
| captcha | 字符串 | 是 | 用户输入的图形验证码 |
1.3、返回数据
{
'errno':'0',
'errmsg':'恭喜,注册成功',
}
2、后端代码
1.1、视图
from django.shortcuts import render
from django.views import View
from .forms import RegisterForm
from utils.res_code import json_response,Code,error_map
from user.models import User
def login(request):
return render(request, 'user/login.html')
class RegisterView(View):
"""
注册视图
"""
def post(self,request):
form = RegisterForm(request.POST)
if form.is_valid():
# 创建数据
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
mobile = form.cleaned_data.get('mobile')
User.objects.create_user(username=username,password=password,mobile=mobile)
return json_response(errmsg='恭喜您,注册成功!')
else:
# 将表单报错信息进行拼接
err_msg_list = []
for item in form.errors.values():
err_msg_list.append(item[0])
err_msg_str = '/'.join(err_msg_list)
return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)
1.2、form表单
from django import forms
from django_redis import get_redis_connection
from verifcation import constants
from verifcation.forms import mobile_validator
from .models import User
class RegisterForm(forms.Form):
"""
用户注册校验表单
"""
# 校验用户名
# 校验密码
# 校验手机号
# 校验短信验证码
username = forms.CharField(label='用户名',max_length=20,min_length=5,error_messages={
'max_length':'用户名长度小于20!',
'min_length':'用户名长度大于5!',
'required':'用户名不能为空!',
})
password = forms.CharField(label='密码', max_length=20, min_length=6, error_messages={
'max_length': '密码长度小于20!',
'min_length': '密码长度大于6!',
'required': '密码不能为空!',
})
password_repeat = forms.CharField(label='重复密码', max_length=20, min_length=6, error_messages={
'max_length': '密码长度小于20!',
'min_length': '密码长度大于6!',
'required': '密码不能为空!',
})
mobile = forms.CharField(label='手机号',max_length=11, min_length=11,validators=[mobile_validator], error_messages={
'max_length': '手机号长度有误!',
'min_length': '手机号长度有误!',
'required': '手机号不能为空!',
})
sms_code = forms.CharField(label='短信验证码', max_length=constants.SMS_CODE_LENGTH, min_length=constants.SMS_CODE_LENGTH,
error_messages={
'max_length': '短信验证码长度有误!',
'min_length': '短信验证码长度有误!',
'required': '短信验证码不能为空!',
})
def clean_username(self):
"""
校验用户名
:return:
"""
username = self.cleaned_data.get('username')
if User.objects.filter(username=username).exists():
raise forms.ValidationError('用户名已存在,请重新输入!')
return username
def clean_mobile(self):
"""
校验手机号码
:return:
"""
mobile = self.cleaned_data.get('mobile')
if User.objects.filter(mobile=mobile).exists():
raise forms.ValidationError('手机号码已注册!')
return mobile
def clean(self):
"""
密码联合校验
:return:
"""
clean_data = super().clean()
password = clean_data.get('password')
password_repeat = clean_data.get('password_repeat')
if password != password_repeat:
raise forms.ValidationError('两次密码不一致!')
#短息验证码校验
sms_code = clean_data.get('sms_code')
mobile = clean_data.get('mobile')
redis_conn = get_redis_connection(alias='verify_code')
real_code = redis_conn.get('sms_text_{}'.format(mobile))
if (not real_code) or (real_code.decode('utf-8') != sms_code):
raise forms.ValidationError('短息验证码错误!')
1.3 、js
//6、注册
let $subimBtn = $('.register-btn');
$subimBtn.click((e)=>{
//阻止默认提交
e.preventDefault();
//校验用户名
if(!isUsernameReady){
fnCheckUsername();
return
}
//检验密码
if(!isPasswordReady){
fnCheckPassword();
return
}
//检验手机号码
if(!isMobileReady){
fnCheckMobile();
return
}
//检验短息验证码
let sSMsCode = $('#sms_code_captcha').val();
if(sSMsCode === ''){
message.showError('短息验证码不能为空');
return
}
if(!(/^\d{4}$/).test(sSMsCode)){
message.showError('短息验证码长度错误,必须位4位')
}
// 发送ajax
$
.ajax({
url:'/user/register/',
type:'POST',
data:{
username:$username.val(),
password:$('#password').val(),
password_repeat:$passwordRepeat.val(),
mobile:$mobile.val(),
sms_code:sSMsCode,
},
dataType:'json'
})
.done((res)=>{
if(res.errno === '0'){
//注册成功
message.showError(res.errmsg);
//1秒后跳转到登录界面
setTimeout(()=>{
window.location.href='/user/login/'
},1000);
}else{
//注册失败
message.showError(res.errmsg)
}
})
.fail(()=>{
message.showError('服务超时,请重试!')
})
})
1.4、url
path('register/',views.RegisterView.as_view(),name='register'),
7、json数据响应结构设计
1、结构设计
为了达到多人协同开发,特别是前后端交互,后端返回数据结构要一致。
{"errno": "0", "errmsg": "OK", "data": {...}}
| 字段 | 类型 | 说明 |
|---|---|---|
| errno | 字符串 | 错误编码 |
| errmsg | 字符串 | 错误信息 |
| data | 返回数据 |
在项目根目录中utils文件夹下创建res_code.py文件,用于定义错误编码,代码如下:
class Code:
OK = "0"
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: "未知错误",
}
2、快捷设计
为了方便定义一个快捷方法,在utils目录下创建json_res.py文件代码如下:
from django.http import JsonResponse
from .res_code import Code
def json_response(errno=Code.OK, errmsg='', data=None, kwargs=None):
json_dict = {
'errno': errno,
'errmsg': errmsg,
'data': data
}
if kwargs and isinstance(kwargs, dict):
json_dict.update(kwargs)
return JsonResponse(json_dict)