django项目--注册功能

注册功能是每一个项目锁必须的模块,在此项目中,重点是后端的服务器之间的交互,前端的代码并非重点,所以,有些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. 校验用户名

    2. 校验密码

    3. 校验手机号

    4. 校验短信验证码

  • 新建数据库记录

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 13,017评论 6 13
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,818评论 1 32
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 4,440评论 0 5
  • 增广贤文分享: “晴天不肯去,直待雨淋头。” 意思就是说:天气晴朗的时候不肯提前出门去办事,等到事情紧急了,就算大...
    刘子曰_b08e阅读 737评论 0 3
  • 山村腊月的早晨,奇冷无比。 太阳懒懒的不肯起床,大白狗也懒懒的蜷缩在大门旁的角落里做梦…… 小女孩和弟弟刚把表面已...
    wdj_b0c0阅读 205评论 0 0

友情链接更多精彩内容