Django项目: 3.用户注册功能
本章内容的补充知识点
导入库的良好顺序:
1.系统库 2.django库 3.自己定义的库(第三方库)
redis缓存数据库的数据调用速度快,但是不利于长时间保存。
mysql用于长时间存储,但是调用比较慢。
session会话存储的内容(以字典的方式存放)放在redis缓存里面,要设置过期时间
一、用户模型设计
1. 用户表字段分析
-
用户名
-
密码
-
手机号
-
2.用户模型设计
django的强大之处在于开发效率高,内置了权限模块之类的很多常用功能。在开始一个新的django项目时,如果权限模块中的User模型不满足项目要求,我们需要扩展或者自定义User模型。
扩展User模型有两种方法
-
如果你不需要改变数据库存储内容,只是改变行为,那么可以建立有一个基于User模型的代理模型。
-
如果想存储与User模型关联的信息,可以使用OneToOneField到包含其他信息字段的模型。这种one-to-one模型经常被称作Profile模型,因为它可能存储站点用户的非身份验证的相关信息。例如:
from django.contrib.auth.models import User class Employee(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) department = models.CharField(max_length=100)
自定义User模型:
如果不想使用django内置的权限系统,当然你需要自定义用户模型,这种情况不讨论。当然也不建议这么做,django内置权限系统有大的自定义功能扩展,而不是重复造轮子。
开启一个新项目,官方强烈推荐用户自定义用户模型,即是默认的用户模型目前已经足够,但是未来可能会要扩展。
from django.contrib.auth.models import AbstractUser class User(AbstractUser): pass
一旦已经创立数据库表之后再去修改AUTH_USER_MODEL
,会困难很多,因为它会影响外键和多对多关系。这个改动并不能自动完成,需要手动修复(巨坑)。
根据上面的分析我们的用户模型代码如下:
from django.db import models from django.contrib.auth.models import AbstractUser, UserManager as _UserManager class UserManager(_UserManager): """ 自定义 user manager 修改在使用`python manage.py createsuperuser`命令时 可以提供email """ def create_superuser(self, username, password, email=None, **extra_fields): return super().create_superuser(username=username, password=password, email=email, **extra_fields) class User(AbstractUser): """ add mobile, email_active fields to Django user model. """ 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 = '用户' # 在admin站点中显示名称 verbose_name_plural = verbose_name # 显示复数 def __str__(self): return self.username # A list of the field names that will be prompted for # when create a user via createsuperuser management command. REQUIRED_FIELDS = ['mobile'] # specify manager objects = UserManager()
在settings.py文件中添加如下配置:
# 自定义用户模型 AUTH_USER_MODEL = 'user.User'
然后运行命令进行数据库迁移:
# 1. 相当于 在该app下建立 migrations目录,并记录下你所有的关于modes.py的改动,比如0001_initial.py, 但是这个改动还没有作用到数据库文件你可以手动打开这个文件,看看里面是什么 python manage.py makemigrations # 2. 将该改动作用到数据库文件,比如产生table之类 python manage.py migrate
再创建一个管理用户
(tzproject) ~/code/tztz$ python manage.py createsuperuser 用户名: admin 手机号: 158xxxxxxxx Password: Password (again): 密码长度太短。密码必须包含至少 8 个字符。 这个密码太常见了。 Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
1.设计接口思路
-
分析业务逻辑,明确在这个业务中需要涉及到几个相关子业务,将每个子业务党组欧一个接口来设计
-
分析接口的功能任务,明确接口的访问方式与返回数据:
-
接口的请求方式,如GET,POST,PUT等
-
接口的URL路径定义
-
需要接受的参数及参数格式(如路径参数,查询字符串,请求表单,JSON等)
-
返回的数据及数据格式
-
2.注册功能分析
-
流程图
-
功能
根据流程图总结注册业务包含如下功能
-
注册页面
-
图片验证码
-
用户名检测是否注册
-
-
短信验证码
-
注册保存用户数据
因为图片验证码,短信验证码考虑到后续可能会在其他业务中用到,因此将验证码功能独立出来,创建一个新应用verification。
1.接口设计
接口说明:
|
说明 | |
|
GET | |
url定义 | /image_code/ | |
参数格式 | 查询参数 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 | |
rand | 字符串 | 否 |
|
返回结果:验证码图片
2.后端代码
-
将验证码生成模块复制到根目录utils文件夹下
-
创建新的app verification专门用来处理验证
cd ~/code/tztz/apps/ python ../manage.py startapp verification
别忘了在settings文件中注册app
-
constants.py(用于设置常量的文件) 和 verification/views.py代码如下:
# constants.py 文件 # 保存设置常量,单位秒 IMAGE_CODE_EXPIRES = 300
# views.py 文件 import logging from django.http import HttpResponse from utils.captcha.captcha import captcha from . import constants # 先要在本app中创建constants.py文件 # 日志器 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 # 将验证码存入session中 request.session.set_expiry(constants.IMAGE_CODE_EXPIRES) logger.info('Image code:{}'.format(text)) return HttpResponse(content=image, content_type='image/jpg')
-
verification/urls.py代码如下:
from django.urls import path from . import views # url的命名空间 app_name = 'verification' urlpatterns = [ path('image_code/', views.image_code_view, name='image_code'), ]
- 根urls.py代码如下:
from django.urls import path, include urlpatterns = [ path('', include('news.urls')), path('', include('verification.urls')), ]
验证码的走向图:
四、注册页面
1.接口设计
-
接口说明:
类目
说明 GET url定义 /user/register/ 参数格式 - 返回结果: 注册页面
-
user/views.py
from django.shortcuts import render from django.views import View class RegisterView(View): def get(self, request): return render(request, 'user/register.html')
- user/urls.py
from django.urls import path, include from . import views app_name = 'user' urlpatterns = [ path('register/', views.RegisterView.as_view(), name='register') ]
- 根urls.py
from django.urls import path, include urlpatterns = [ path('', include('news.urls')), path('user/', include('user.urls')) ]
3.前端页面代码
-
{% extends 'base/base.html' %} {% load static %} {% block title %}注册{% endblock title %} {% block link %} <link rel="stylesheet" href="{% static 'css/user/auth.css' %}"> {% endblock link %} {% block main_start %} <main id="container"> <div class="register-contain"> <div class="top-contain"> <h4 class="please-register">请注册</h4> <a href="javascript:void(0);" class="login">立即登录 ></a> </div> <form action="" method="post" class="form-contain"> <div class="form-item"> <input type="text" placeholder="请输入用户名" name="username" class="form-control" autocomplete="off"> </div> <div class="form-item"> <input type="password" placeholder="请输入密码" name="password" class="form-control"> </div> <div class="form-item"> <input type="password" placeholder="请输入确认密码" name="password_repeat" class="form-control"> </div> <div class="form-item"> <input type="tel" placeholder="请输入手机号" name="telephone" class="form-control" autocomplete="off" autofocus> </div> <div class="form-item"> <input type="text" placeholder="请输入图形验证码" name="captcha_graph" class="form-captcha"> <a href="javascript:void(0);" class="captcha-graph-img"> <img src="{% url 'verification:image_code' %}" alt="验证码" title="点击刷新"> </a> </div> <div class="form-item"> <input type="text" placeholder="请输入短信验证码" name="sms_captcha" class="form-captcha" autocomplete="off"> <a href="javascript:void(0);" class="sms-captcha" title="发送验证码">获取短信验证码</a> </div> <div class="form-item"> <input type="submit" value="立即注册" class="register-btn"> </div> </form> </div> </main> {% endblock main_start %} {% block script %} <script src="{% static 'js/user/auth.js' %}"></script> {% endblock script %}
-
js代码
点击验证码图片刷新的js代码如下:
$(function () { let $img = $('.form-contain .form-item .captcha-graph-img img'); // 1.点击刷新图像验证码 $img.click(function () { $img.attr('src', '/image_code/?rand=' + Math.random()) }); });
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: "未知错误", }
为了方便定义一个快捷方法,在utils目录下创建json_res.py文件(也可以直接在res_code文件中直接加上)代码如下:
from django.http import JsonResponse # 在res_code文件中时不用加下面这个导入 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)
六、判断用户是否注册功能实现
1.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /username/(?P<username>\w{5,20})/ |
参数格式 | url路径参数 |
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
username | 字符串 | 是 | 输入的用户名 |
返回结果:
{ "errno": "0", "errmsg": "OK", # 错误信息 "data": { "username": "username", # 查询的用户名 "count": 1 # 用户名查询的数量 } }
2.后端代码
-
创建新的app
cd ~/code/tztz/apps/ python ../manage.py startapp verification
-
from user.models import User from utils.json_res import json_response def check_username_view(request, username): """ 校验用户名 url: /username/(?p<username>\w{5,20})/ """ data = { 'username': username, 'count': User.objects.filter(username=username).count() } return json_response(data=data)
- verification/urls.py代码
from django.urls import path, re_path from . import views # url的命名空间 app_name = 'verification' urlpatterns = [ path('image_code/', views.image_code_view, name='image_code'), re_path('username/(?P<username>\w{5,20})/', views.check_username_view, name='check_username'), ]
user/register.html代码如下:
{% extends 'base/base.html' %} {% load static %} {% block title %}注册{% endblock title %} {% block link %} <link rel="stylesheet" href="{% static 'css/user/auth.css' %}"> {% endblock link %} {% block main_start %} <main id="container"> <div class="register-contain"> <div class="top-contain"> <h4 class="please-register">请注册</h4> <a href="javascript:void(0);" class="login">立即登录 ></a> </div> <form action="" method="post" class="form-contain"> <div class="form-item"> <input type="text" placeholder="请输入用户名" id="username" name="username" class="form-control" > </div> <div class="form-item"> <input type="password" placeholder="请输入密码" name="password" class="form-control"> </div> <div class="form-item"> <input type="password" placeholder="请输入确认密码" name="password_repeat" class="form-control"> </div> <div class="form-item"> <input type="tel" placeholder="请输入手机号" name="telephone" class="form-control" autocomplete="off"> </div> <div class="form-item"> <input type="text" placeholder="请输入图形验证码" name="captcha_graph" class="form-captcha"> <a href="javascript:void(0);" class="captcha-graph-img"> <img src="{% url 'verification:image_code' %}" alt="验证码" title="点击刷新"> </a> </div> <div class="form-item"> <input type="text" placeholder="请输入短信验证码" name="sms_captcha" class="form-captcha" autocomplete="off"> <a href="javascript:void(0);" class="sms-captcha" title="发送验证码">获取短信验证码</a> </div> <div class="form-item"> <input type="submit" value="立即注册" class="register-btn"> </div> </form> </div> </main> {% endblock main_start %} {% block script %} <script src="{% static 'js/user/auth.js' %}"></script> {% endblock script %}
4.前端js代码
user/auth.js代码:
$(function () { // 定义状态变量 let isUsernameReady = false, isPasswordReady = false, isMobileReady = false, isSmsCodeReady = false; // 1.点击刷新图像验证码 let $img = $('.form-contain .form-item .captcha-graph-img img'); $img.click(function () { $img.attr('src', '/image_code/?rand=' + Math.random()) }); // 2.鼠标离开用户名输入框校验用户名 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({ url: '/username/' + sUsername + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.username + '已经注册,请重新输入!') }else { message.showInfo(data.data.username + '可以正常使用!') isUsernameReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 3.检测密码是否一致 let $passwordRepeat = $('input[name="password_repeat"]'); $passwordRepeat.blur(fnCheckPassword); function fnCheckPassword () { isPasswordReady = false; let password = $('input[name="password"]').val(); let passwordRepeat = $passwordRepeat.val(); if (password === '' || passwordRepeat === ''){ message.showError('密码不能为空'); return } if (password !== passwordRepeat){ message.showError('两次密码输入不一致'); return } if (password === passwordRepeat){ isPasswordReady = true } }
七、判断手机号码是否注册功能
接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /mobile/(?P<mobile>\1[3-9]\d{9})/ |
参数格式 | url路径参数 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
moblie | 字符串 | 是 | 输入的手机号码 |
返回结果:
{ "errno": "0", "errmsg": "OK", "data": { "mobile": "13xxxxxxxxx", # 查询的手机号 "count": 1 # 手机号查询的数量 } }
-
verification/views.py代码
# ···· def check_mobile_view(request, mobile): """ 校验手机号是否存在 url:/moblie/(?P<moblie>1[3-9]\d{9})/ :param request: :param username: :return: """ data = { 'mobile': mobile, 'count': User.objects.filter(mobile=mobile).count() } return json_response(data=data)
- verification/urls.py
from django.urls import path, re_path from . import views # url的命名空间 app_name = 'verification' urlpatterns = [ path('image_code/', views.image_code_view, name='image_code'), re_path('username/(?P<username>\w{5,20})/', views.check_username_view, name='check_username'), re_path('mobile/(?P<mobile>1[3-9]\d{9})/', views.check_mobile_view, name='check_mobile'), ]
- 前端js代码:
$(function () { // 定义状态变量 let isUsernameReady = false, isPasswordReady = false, isMobileReady = false, isSmsCodeReady = false; // 1.点击刷新图像验证码 let $img = $('.form-contain .form-item .captcha-graph-img img'); $img.click(function () { $img.attr('src', '/image_code/?rand=' + Math.random()) }); // 2.鼠标离开用户名输入框校验用户名 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({ url: '/username/' + sUsername + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.username + '已经注册,请重新输入!') }else { message.showInfo(data.data.username + '可以正常使用!') isUsernameReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 3.检测密码是否一致 let $passwordRepeat = $('input[name="password_repeat"]'); $passwordRepeat.blur(fnCheckPassword); function fnCheckPassword () { isPasswordReady = false; let password = $('input[name="password"]').val(); let passwordRepeat = $passwordRepeat.val(); if (password === '' || passwordRepeat === ''){ message.showError('密码不能为空'); return } if (password !== passwordRepeat){ message.showError('两次密码输入不一致'); return } if (password === passwordRepeat){ isPasswordReady = true } } // 4.检查手机号码是否可用 let $mobile = $('input[name="mobile"]'); $mobile.blur(fnCheckMobile); function fnCheckMobile () { isMobileReady = true; let sMobile = $mobile.val(); if(sMobile === ''){ message.showError('手机号码不能为空'); return } if(!(/^1[3-9]\d{9}$/).test(sMobile)){ message.showError('手机号码格式不正确'); return } $.ajax({ url: '/mobile/' + sMobile + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.mobile + '已经注册,请重新输入!') }else { message.showInfo(data.data.mobile + '可以正常使用!'); isMobileReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } });
1.业务流程分析
-
校验手机号码
-
检查图片验证码是否正确
-
检查是否在60s内发送记录
-
生成短信验证码
-
发送短信
-
保存短信验证码与发送记录
2.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /sms_code/ |
参数格式 | 表单 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
moblie | 字符串 | 是 | 用户输入的手机号码 |
captcha | 字符串 | 是 | 用户输入的验证码文本 |
返回结果:
{ "errno": "0", "errmsg": "发送短信验证码成功!", }
3.后端代码
-
verification/views.py代码如下:
import logging import random from django.http import HttpResponse from django.views import View from django_redis import get_redis_connection from user.models import User from utils.json_res import json_response from utils.res_code import Code, error_map from utils.captcha.captcha import captcha from utils.yuntongxun.sms import CCP from . import constants from .forms import CheckImagForm # 日志器 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 # 将验证码存入session中 request.session.set_expiry(constants.IMAGE_CODE_EXPIRES) logger.info('Image code:{}'.format(text)) return HttpResponse(content=image, content_type='image/jpg') def check_username_view(request, username): """ 校验用户名是否存在 url:/username/(?P<username>\w{5,20})/ :param request: :param username: :return: """ data = { 'username': username, 'count': User.objects.filter(username=username).count() } return json_response(data=data) def check_mobile_view(request, mobile): """ 校验手机号是否存在 url:/moblie/(?P<moblie>1[3-9]\d{9})/ :param request: :param username: :return: """ data = { 'mobile': mobile, 'count': User.objects.filter(mobile=mobile).count() } return json_response(data=data) class SmsCodeView(View): """ 发送短信验证码 POST /sms_codes/ """ def post(self, request): # 1.校验参数 form = CheckImagForm(request.POST, request=request) if form.is_valid(): # 2.获取手机 mobile = form.cleaned_data.get('mobile') # 3.生成手机验证码 sms_code = ''.join([random.choice('0123456789') for _ in range(constants.SMS_CODE_LENGTH)]) # 4.发送手机验证码 ccp = CCP() try: res = ccp.send_template_sms(mobile, [sms_code, constants.SMS_CODE_EXPIRES], "1") if res == 0: logger.info('发送短信验证码[正常][mobile: %s sms_code: %s]' % (mobile, sms_code)) else: logger.error('发送短信验证码[失败][moblie: %s sms_code: %s]' % (mobile, sms_code)) return json_response(errno=Code.SMSFAIL, errmsg=error_map[Code.SMSFAIL]) except Exception as e: logger.error('发送短信验证码[异常][mobile: %s message: %s]' % (mobile, e)) return json_response(errno=Code.SMSERROR, errmsg=error_map[Code.SMSERROR]) # 5.保存到redis数据库 # 创建短信验证码发送记录 sms_flag_key = 'sms_flag_{}'.format(mobile) # 创建短信验证码内容记录 sms_text_key = 'sms_text_{}'.format(mobile) redis_conn = get_redis_connection(alias='verify_code') pl = redis_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) # 让管道通知redis执行命令 pl.execute() return json_response(errmsg="短信验证码发送成功!") except Exception as e: logger.error('redis 执行异常:{}'.format(e)) return json_response(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR]) else: # 将表单的报错信息进行拼接 err_msg_list = [] for item in form.errors.get_json_data().values(): err_msg_list.append(item[0].get('message')) # print(item[0].get('message')) # for test err_msg_str = '/'.join(err_msg_list) # 拼接错误信息为一个字符串 return json_response(errno=Code.PARAMERR, errmsg=err_msg_str)
- verification/forms.py文件代码如下:
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}$', '手机号码格式不正确') class CheckImagForm(forms.Form): """ check image code """ def __init__(self, *args, **kwargs): 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') # 1.校验图片验证码 image_code = self.request.session.get('image_code') if (not image_code) or (image_code.upper() != captcha.upper()): raise forms.ValidationError('图片验证码校验失败!') # 2.校验是否在60秒内已发送过短信 redis_conn = get_redis_connection(alias='verify_code') if redis_conn.get('sms_flag_{}'.format(mobile)): raise forms.ValidationError('获取短信验证码过于频繁') # 3.校验手机号码是否已注册 if User.objects.filter(mobile=mobile).count(): raise forms.ValidationError('手机号已注册,请重新输入')
- verification/constants.py代码如下:
# 图片验证码过期时间 单位秒 IMAGE_CODE_EXPIRES = 300 # 短信验证码长度 SMS_CODE_LENGTH = 4 # 短信验证码发送间隔 秒 SMS_CODE_INTERVAL = 60 # 短信验证码过期时间 分 SMS_CODE_EXPIRES = 5 # 短信发送模板 SMS_CODE_TEMP_ID = 1
4.短信验证码平台-云通讯
本项目中使用的短信验证码平台为云通讯平台,文档参考地址
主要是因为可以免费测试,注册后赠送8元用于测试。
开发参数:
_accountSid = '开发者主账号中的ACCOUNT SID' # 说明:主账号Token,登陆云通讯网站后,可在控制台-应用中看到开发者主账号AUTH TOKEN _accountToken = '开发者主账号中的AUTH TOKEN' # 请使用管理控制台首页的APPID或自己创建应用的APPID _appId = '开发者主账号中的AppID(默认)' # 说明:请求地址,生产环境配置成app.cloopen.com _serverIP = 'sandboxapp.cloopen.com' # 说明:请求端口 ,生产环境为8883 _serverPort = "8883" # 说明:REST API版本号保持不变 _softVersion = '2013-12-26'
设置测试手机号码
5.前端js代码
user/auth.js代码:
$(function () { // 定义状态变量 let isUsernameReady = false, isPasswordReady = false, isMobileReady = false, isSmsCodeReady = false; // 1.点击刷新图像验证码 let $img = $('.form-contain .form-item .captcha-graph-img img'); $img.click(function () { $img.attr('src', '/image_code/?rand=' + Math.random()) }); // 2.鼠标离开用户名输入框校验用户名 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({ url: '/username/' + sUsername + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.username + '已经注册,请重新输入!') }else { message.showInfo(data.data.username + '可以正常使用!') isUsernameReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 3.检测密码是否一致 let $passwordRepeat = $('input[name="password_repeat"]'); $passwordRepeat.blur(fnCheckPassword); function fnCheckPassword () { isPasswordReady = false; let password = $('input[name="password"]').val(); let passwordRepeat = $passwordRepeat.val(); if (password === '' || passwordRepeat === ''){ message.showError('密码不能为空'); return } if (password !== passwordRepeat){ message.showError('两次密码输入不一致'); return } if (password === passwordRepeat){ isPasswordReady = true } } // 4.检查手机号码是否可用 let $mobile = $('input[name="mobile"]'); $mobile.blur(fnCheckMobile); function fnCheckMobile () { isMobileReady = true; let sMobile = $mobile.val(); if(sMobile === ''){ message.showError('手机号码不能为空'); return } if(!(/^1[3-9]\d{9}$/).test(sMobile)){ message.showError('手机号码格式不正确'); return } $.ajax({ url: '/mobile/' + sMobile + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.mobile + '已经注册,请重新输入!') }else { message.showInfo(data.data.mobile + '可以正常使用!'); isMobileReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 5.发送手机验证码 let $smsButton = $('.sms-captcha'); $smsButton.click(function () { let sCaptcha = $('input[name="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', success: function (data) { if(data.errno !== '0'){ message.showError(data.errmsg) }else { message.showSuccess(data.errmsg); let num = 60; //设置计时器 let t = setInterval(function () { if(num===1){ clearInterval(t) } }) } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); }); });
因为用到了post方法,django默认带有csrf防护,所以在base/common.js中添加如下代码:
$(()=>{ let $navLi = $('#header .nav .menu li'); $navLi.click(function(){ $(this).addClass('active').siblings('li').removeClass('active') }); function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } } }); });
1.业务流程分析
-
判断用户名是否为空,是否已注册
-
判断密码是否为空,格式是否正确
-
判断两次密码是否一致
-
判断手机号码是否为空,格式是否正确
-
判断短信验证码是否为空,格式是否正确,是否与真实短信验证码相同
2.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /user/register/ |
参数格式 | 表单 |
注意:post请求,前端请求要带上csrf token
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
username | 字符串 | 是 | 用户输入的用户名 |
password | 字符串 | 是 | 用户输入的密码 |
password_repeat | 字符串 | 是 | 用户输入的重复密码 |
mobile | 字符串 | 是 | 用户输入的手机号码 |
sms_code | 字符串 | 是 | 用户输入的短信验证码 |
返回结果:
{ "errno": "0", "errmsg": "恭喜您,注册成功!", }
-
user/views.py代码:
from django.shortcuts import render from django.views import View from .forms import RegisterForm from .models import User from utils.json_res import json_response from utils.res_code import Code, error_map class RegisterView(View): def get(self, request): return render(request, 'user/register.html') 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)
-
user/forms.py代码:
import re from django import forms from django_redis import get_redis_connection from .models import User from verification.constants import SMS_CODE_LENGTH class RegisterForm(forms.Form): username = forms.CharField(label='用户名', max_length=20, min_length=5, error_messages={ 'max_length': '用户名长度要小于20', 'min_length': '用户名长度要大于4', 'required': '用户名不能为空' }) password = forms.CharField(label='密码', max_length=20, min_length=6, error_messages={ 'max_length': '密码长度要小于20', 'min_length': '密码长度要大于5', 'required': '用户名不能为空' }) password_repeat = forms.CharField(label='确认密码', max_length=20, min_length=6, error_messages={ 'max_length': '密码长度要小于20', 'min_length': '密码长度要大于5', 'required': '用户名不能为空' }) mobile = forms.CharField(label='手机号码', max_length=11, min_length=11, error_messages={ 'max_length': '手机号码长度有误', 'min_length': '手机号码长度有误', 'required': '手机号码不能为空' }) sms_code = forms.CharField(label='短信验证码', max_length=SMS_CODE_LENGTH, min_length=SMS_CODE_LENGTH, error_messages={ 'max_length': '短信验证码长度有误', 'min_length': '短信验证码长度有误长度有误', 'required': '短信验证码不能为空' }) ### clean_username这种是单独校验,写多少就校验多少,上面的错了,下面的同方照样执行 ### clean一般用于多个字段联合校验,但是只要上面错了下面就不会校验了 def clean_username(self): """ 校验用户名 :return: """ username = self.cleaned_data.get('username') if User.objects.filter(username=username).exists(): return forms.ValidationError('用户名已存在!') return username def clean_mobile(self): """ 校验手机号 :return: """ mobile = self.cleaned_data.get('mobile') if not re.match(r'^1[3-9]\d{9}$', mobile): raise forms.ValidationError('手机号码格式不正确') 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') moblie = clean_data.get('mobile') redis_conn = get_redis_connection(alias='verify_code') real_code = redis_conn.get('sms_text_{}'.format(moblie)) if (not real_code) or (real_code.decode('utf-8') != sms_code): raise forms.ValidationError('短信验证码错误!')
clean_username和clean的差别用法:
$(function () { // 定义状态变量 let isUsernameReady = false, isPasswordReady = false, isMobileReady = false; // 1.点击刷新图像验证码 let $img = $('.form-contain .form-item .captcha-graph-img img'); $img.click(function () { $img.attr('src', '/image_code/?rand=' + Math.random()) }); // 2.鼠标离开用户名输入框校验用户名 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({ url: '/username/' + sUsername + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.username + '已经注册,请重新输入!') }else { message.showInfo(data.data.username + '可以正常使用!') isUsernameReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 3.检测密码是否一致 let $passwordRepeat = $('input[name="password_repeat"]'); $passwordRepeat.blur(fnCheckPassword); function fnCheckPassword () { isPasswordReady = false; let password = $('input[name="password"]').val(); let passwordRepeat = $passwordRepeat.val(); if (password === '' || passwordRepeat === ''){ message.showError('密码不能为空'); return } if (password !== passwordRepeat){ message.showError('两次密码输入不一致'); return } if (password === passwordRepeat){ isPasswordReady = true } } // 4.检查手机号码是否可用 let $mobile = $('input[name="mobile"]'); $mobile.blur(fnCheckMobile); function fnCheckMobile () { isMobileReady = true; let sMobile = $mobile.val(); if(sMobile === ''){ message.showError('手机号码不能为空'); return } if(!(/^1[3-9]\d{9}$/).test(sMobile)){ message.showError('手机号码格式不正确'); return } $.ajax({ url: '/mobile/' + sMobile + '/', type: 'GET', dataType: 'json', success: function (data) { if(data.data.count !== 0){ message.showError(data.data.mobile + '已经注册,请重新输入!') }else { message.showInfo(data.data.mobile + '可以正常使用!'); isMobileReady = true } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); } // 5.发送手机验证码 let $smsButton = $('.sms-captcha'); $smsButton.click(function () { let sCaptcha = $('input[name="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', success: function (data) { if(data.errno !== '0'){ message.showError(data.errmsg) }else { message.showSuccess(data.errmsg); let num = 60; //设置计时器 let t = setInterval(function () { if(num===1){ clearInterval(t) } }) } }, error: function (xhr, msg) { message.showError('服务器超时,请重试!') } }); }); // 6.注册 let $submitBtn = $('.register-btn'); $submitBtn.click(function (e) { //阻止默认提交 e.preventDefault(); // 1.检查用户名 if(!isUsernameReady){ fnCheckUsername(); return } // 2.检查密码 if(!isPasswordReady){ fnCheckPassword(); return } // 3.检查电话号码 if(!isMobileReady){ fnCheckMobile(); return } // 4.检查短信验证码 let sSmsCode = $('input[name="sms_captcha"]').val(); if(sSmsCode === ''){ message.showError('短信验证码不能为空!'); return } if(!(/^\d{4}$/).test(sSmsCode)){ message.showError('短信验证码长度不正确,必须是4位数字!'); return } $.ajax({ url: '/user/register/', type: 'POST', data:{ username: $username.val(), password: $('input[name="password"]').val(), password_repeat: $passwordRepeat.val(), mobile: $mobile.val(), sms_code: sSmsCode }, dataType: 'json', success: function (res) { if(res.errno === '0'){ message.showSuccess('恭喜您,注册成功!'); setTimeout(function () { //注册成功后重定向到登录页面 window.location.href = '/user/login/' }, 3000) }else{ //注册失败 message.showError(res.errmsg) } }, error: function () { message.showError('服务器超时,请重试!') } }) }); });