美文网首页
用户登录、登出、注册功能的实现

用户登录、登出、注册功能的实现

作者: 丨山有乔松 | 来源:发表于2019-05-21 19:28 被阅读0次

    ## 用户注册功能

    [TOC]

    ### 一、设计接口思路

    - 分析业务逻辑,明确在这个业务中需要涉及到几个相关子业务,将每个子业务当做一个接口来设计

    - 分析接口的功能任务,明确接口的访问方式与返回数据:

      - 接口的请求方式,如GET 、POST 、PUT等

      - 接口的URL路径定义

      - 需要前端传递的数据及数据格式(如路径参数、查询字符串、请求体表单、JSON等)

      - 返回给前端的数据及数据格式

    ### 二、功能分析

    - 用户名判断是否存在

    - 手机号判断是否存在

    - 图片验证码

    - 短信验证码

    - 注册保存用户数据

    图片验证码、短信验证码考虑到后续可能会在其他业务中也会用到,因此将验证码功能独立出来,**创建一个新应用verifications,在此应用中实现图片验证码、短信验证码**

    ### 三、图片验证码接口代码实现

    #### 1.分析

    **请求方法**:**GET**

    **url定义**:`/image_codes/<uuid:image_code_id>/`

    **请求参数**:url路径参数

    | 参数          | 类型       | 前端是否必须传 | 描述           |

    | ------------- | ---------- | -------------- | -------------- |

    | image_code_id | uuid字符串 | 是             | 图片验证码编号 |

    uuid:Universally unique identifier(eg. 123e4567-e89b-12d3-a456-426655440000)

    #### 2.后端代码实现

    a.将生成图像验证码的模块文件夹(百度云盘有提供captcha文件夹)复制粘贴到项目根目录utils文件夹下

    b.由于验证(图片验证、短信验证)功能,以后有可能在其他应用或项目中重用,所以单独创建一个应用来实现,所有验证相关的业务逻辑接口。在apps目录中创建一个verifications应用,并在settings.py文件中的INSTALLED_APPS列表中指定。

    ```python

    # 在verifications/views.py文件中添加如下代码:

    import logging

    from django.shortcuts import render

    from django.views import View

    from django_redis import get_redis_connection

    from django.http import HttpResponse

    from utils.captcha.captcha import captcha

    # 安装图片验证码所需要的 Pillow 模块

    # pip install Pillow

    from . import constants

    from users.models import Users

    # 导入日志器

    logger = logging.getLogger('django')

    class ImageCode(View):

        """

        define image verification view

        # /image_codes/<uuid:image_code_id>/

        """

        def get(self, request, image_code_id):

            text, image = captcha.generate_captcha()

            # 确保settings.py文件中有配置redis CACHE

            # Redis原生指令参考 http://redisdoc.com/index.html

            # Redis python客户端 方法参考 http://redis-py.readthedocs.io/en/latest/#indices-and-tables

            con_redis = get_redis_connection(alias='verify_codes')

            img_key = "img_{}".format(image_code_id).encode('utf-8')

            # 将图片验证码的key和验证码文本保存到redis中,并设置过期时间

            con_redis.setex(img_key, constants.IMAGE_CODE_REDIS_EXPIRES, text)

            logger.info("Image code: {}".format(text))

            return HttpResponse(content=image, content_type="images/jpg")

    ```

    c.为了保存应用中用到的常量信息,需要在verifications应用下创建一个constants.py文件

    ```python

    # 在verifications/constants.py文件中加入如下代码:

    # 图片验证码redis有效期,单位秒

    IMAGE_CODE_REDIS_EXPIRES = 5 * 60

    ```

    d.本项目需要将图形验证码、短信验证码以及用户的会话信息保存到redis服务器中,所以需要在settings.py文件中指定如下配置信息:

    ```python

    # settings.py文件中加入如下内容:

    CACHES = {

        "default": {

            "BACKEND": "django_redis.cache.RedisCache",  # 指定redis缓存后端

            "LOCATION": "redis://127.0.0.1:6379/0",

            "OPTIONS": {

                "CLIENT_CLASS": "django_redis.client.DefaultClient",

                # "PASSWORD": "mysecret"

            }

        },

        # 同样可以指定多个redis

        "session": {

            "BACKEND": "django_redis.cache.RedisCache",

            "LOCATION": "redis://127.0.0.1:6379/1",

            "OPTIONS": {

                "CLIENT_CLASS": "django_redis.client.DefaultClient",

            }

        },

        "verify_codes": {

            "BACKEND": "django_redis.cache.RedisCache",

            "LOCATION": "redis://127.0.0.1:6379/2",

            "OPTIONS": {

                "CLIENT_CLASS": "django_redis.client.DefaultClient",

            }

        },

        "sms_codes": {

            "BACKEND": "django_redis.cache.RedisCache",

            "LOCATION": "redis://127.0.0.1:6379/3",

            "OPTIONS": {

                "CLIENT_CLASS": "django_redis.client.DefaultClient",

            }

        },

    }

    # 将用户的session保存到redis中

    SESSION_ENGINE = "django.contrib.sessions.backends.cache"

    # 指定缓存redis的别名

    SESSION_CACHE_ALIAS = "session"

    ```

    e.在verifications应用下创建一个urls.py文件并添加如下内容:

    ```python

    # verifications应用下创建一个urls.py

    from django.urls import path, re_path

    from . import views

    app_name = "verifications"

    urlpatterns = [

        # re_path(r'^image_codes/(?P<image_code_id>[\w-]+)/$', view=views.ImageCodeView.as_view(), name="image_code"),

        # image_code_id为uuid格式

        path('image_codes/<uuid:image_code_id>/', views.ImageCode.as_view(), name='image_code'),

    ]

    ```

    #### 3.前端代码实现

    html代码:

    ```jinja2

    {# 继承base基类模版 #}

    {% extends 'base/base.html' %}

    {% block link %}

    {#  <link rel="stylesheet" href="../../static/css/users/auth.css">#}

      <link rel="stylesheet" href="{% static 'css/users/auth.css' %}">

    {% endblock %}

    {% block title %}

    注册

    {% endblock %}

    <!-- main-contain start  -->

    {% 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">立即登录 &gt;</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="" 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-contain  end -->

    {% block hot_recommend %}

    {% endblock %}

    {% block script %}

    {#  <script src="/static/js/users/auth.js"></script>#}

      <script src="{% static 'js/users/auth.js' %}"></script>

    {% endblock %}

    ```

    在js文件夹下创建一个users文件夹用户存放用户模块相关的js文件,在users文件下创建auth.js文件。

    ```javascript

    $(function () {

      let $img = $(".form-item .captcha-graph-img img");  // 获取图像标签

      let sImageCodeId = "";  // 定义图像验证码ID值

      generateImageCode();  // 生成图像验证码图片

      $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片

      // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性

      function generateImageCode() {

        // 1、生成一个图片验证码随机编号

        sImageCodeId = generateUUID();

        // 2、拼接请求url /image_codes/<uuid:image_code_id>/

        let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";

        // 3、修改验证码图片src地址

        $img.attr('src', imageCodeUrl)

      }

      // 生成图片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;

      }

    });

    ```

    ### 四、判断用户名是否注册功能实现

    #### 1.分析

    **请求方法**:**GET**

    **url定义**:`/usernames/(?P<username>\w{5,20})/`

    **请求参数**:url路径参数

    | 参数     | 类型   | 前端是否必须传 | 描述             |

    | -------- | ------ | -------------- | ---------------- |

    | username | 字符串 | 是             | 用户输入的用户名 |

    #### 2.后端代码实现

    ```python

    from utils.json_fun import to_json_data

    from django.views import View

    class CheckUsernameView(View):

        """

        Check whether the user exists 

        GET usernames/(?P<username>\w{5,20})/

        """

        def get(self, request, username):

            # count = 1 if User.objects.get(username=username) else 0

            data = {

                'username': username,

                'count': Users.objects.filter(username=username).count()

            }

            return to_json_data(data=data)

    ```

    在项目根目录中的utils目录下创建json_fun.py文件,用于处理json格式转化功能。

    ```python

    from django.http import JsonResponse

    from .res_code import Code

    def to_json_data(errno=Code.OK, errmsg='', data=None, kwargs=None):

        json_dict = {'errno': errno, 'errmsg': errmsg, 'data': data}

        if kwargs and isinstance(kwargs, dict) and kwargs.keys():

            json_dict.update(kwargs)

        return JsonResponse(json_dict)

    ```

    在项目根目录中的utils目录下创建res_code.py文件,用于把后端执行的情况返回给前端。

    ```python

    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             : "未知错误",

    }

    ```

    ```python

    # url 定义

    from django.urls import path, re_path

    from . import views

    app_name = "verifications"

    urlpatterns = [

        # image_code_id为uuid格式

        path('image_codes/<uuid:image_code_id>/', views.ImageCode.as_view(), name='image_code'),

        re_path('usernames/(?P<username>\w{5,20})/', views.CheckUsernameView.as_view(), name='check_username'),

    ]

    ```

    #### 3.前端代码实现

    ```javascript

    $(function () {

      let $username = $('#user_name');

      let $img = $(".form-item .captcha-graph-img img");

      let sImageCodeId = "";

      // 1、图像验证码逻辑

      generateImageCode();  // 生成图像验证码图片

      $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片

      // 2、用户名验证逻辑

      $username.blur(function () {

        fn_check_usrname();

      });

      // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性

      function generateImageCode() {

        // 1、生成一个图片验证码随机编号

        sImageCodeId = generateUUID();

        // 2、拼接请求url /image_codes/<uuid:image_code_id>/

        let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";

        // 3、修改验证码图片src地址

        $img.attr('src', imageCodeUrl)

      }

      // 生成图片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 fn_check_usrname() {

        let sUsername = $username.val();  // 获取用户名字符串

        if (sUsername === "") {

          message.showError('用户名不能为空!');

          return

        }

        if (!(/^\w{5,20}$/).test(sUsername)) {

          message.showError('请输入5-20个字符的用户名');

          return

        }

        // 发送ajax请求,去后端查询用户名是否存在

        $.ajax({

          url: '/usernames/' + sUsername + '/',

          type: 'GET',

          dataType: 'json',

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.username + '已注册,请重新输入!')

            } else {

              message.showInfo(res.data.username + '能正常使用!')

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

          });

      }

    });

    ```

    ### 五、判断手机号是否注册功能实现

    #### 1.分析

    **请求方法**:**GET**

    **url定义**:`/mobiles/(?P<mobile>1[3-9]\d{9})/`

    **请求参数**:url路径参数

    | 参数   | 类型   | 前端是否必须传 | 描述             |

    | ------ | ------ | -------------- | ---------------- |

    | mobile | 字符串 | 是             | 用户输入的手机号 |

    #### 2.后端代码实现

    ```python

    # 在verifications目录下的views.py文件中定义如下类视图:

    class CheckMobileView(View):

        """

        Check whether the mobile exists

        GET mobiles/(?P<mobile>1[3-9]\d{9})/

        """

        def get(self, request, mobile):

            data = {

                'mobile': mobile,

                'count': Users.objects.filter(mobile=mobile).count()

            }

            return to_json_data(data=data)

    ```

    ```python

    # 在verifications目录下的urls.py文件中定义如下路由:

    from django.urls import path, re_path

    from . import views

    app_name = "verifications"

    urlpatterns = [

        re_path('mobiles/(?P<mobile>1[3-9]\d{9})/', views.CheckMobileView.as_view(), name='check_mobiles'),

    ]

    ```

    #### 3.前端代码实现

    ```python

    $(function () {

      let $username = $('#user_name');  // 选择id为user_name的网页元素,需要定义一个id为user_name

      let $img = $(".form-item .captcha-graph-img img");  // 获取图像标签

      let sImageCodeId = "";  // 定义图像验证码ID值

      let $mobile = $('#mobile');  // 选择id为mobile的网页元素,需要定义一个id为mobile

      // 1、图像验证码逻辑

      generateImageCode();  // 生成图像验证码图片

      $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片

      // 判断用户是否注册

      // 2、用户名验证逻辑

      $username.blur(function () {

        fn_check_usrname();

      });

      // 3、手机号验证逻辑

      // 判断用户手机号是否注册

      $mobile.blur(function () {

        fn_check_mobile();

      });

      // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性

      function generateImageCode() {

        // 1、生成一个图片验证码随机编号

        sImageCodeId = generateUUID();

        // 2、拼接请求url /image_codes/<uuid:image_code_id>/

        let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";

        // 3、修改验证码图片src地址

        $img.attr('src', imageCodeUrl)

      }

      // 生成图片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 fn_check_usrname() {

        let sUsername = $username.val();  // 获取用户名字符串

        if (sUsername === "") {

          message.showError('用户名不能为空!');

          return

        }

        // test()方法 判断字符串中是否匹配到正则表达式内容,返回的是boolean值 ( true / false )

        if (!(/^\w{5,20}$/).test(sUsername)) {

          message.showError('请输入5-20个字符的用户名');

          return

        }

        // 发送ajax请求,去后端查询用户名是否存在

        $.ajax({

          url: '/usernames/' + sUsername + '/',

          type: 'GET',

          dataType: 'json',

          // data:{'code':300268}

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.username + '已注册,请重新输入!')

            } else {

              message.showInfo(res.data.username + '能正常使用!')

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

          });

      }

      function fn_check_mobile() {

        let sMobile = $mobile.val();  // 获取用户输入的手机号码字符串

        let SreturnValue = "";

        if (sMobile === "") {

          message.showError('手机号不能为空!');

          return

        }

        if (!(/^1[345789]\d{9}$/).test(sMobile)) {

          message.showError('手机号码格式不正确,请重新输入!');

          return

        }

        $.ajax({

          url: '/mobiles/' + sMobile + '/',

          type: 'GET',

          dataType: 'json',

          async: false    // 把async关掉

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.mobile + '已注册,请重新输入!')

              SreturnValue = ""

            } else {

              SreturnValue = "success"

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

            SreturnValue = ""

          });

        return SreturnValue

      }

    });

    ```

    ### 六、发送手机短信验证码功能实现

    #### 1.分析

    业务处理流程:

    - 判断图片验证码是否正确

    - 判断是否在60s内有发送记录

    - 生成短信验证码

    - 保存短信验证码与发送记录

    - 发送短信

    **请求方法**:**POST**

    **url定义**:`/sms_codes/`

    **请求参数**:url路径参数

    | 参数          | 类型   | 前端是否必须传 | 描述                     |

    | ------------- | ------ | -------------- | ------------------------ |

    | mobile        | 字符串 | 是             | 用户输入的手机号         |

    | image_code_id | UUID   | 是             | js生成的图片uuid号       |

    | text          | 字符串 | 是             | 用户输入的图片验证码文本 |

    注:由于是post请求,在向后端发起请求时,需要附带csrf token

    #### 2.后端代码实现

    ```python

    # 在verifications目录下的views.py文件中定义如下类视图:

    import logging

    import json

    import random

    import string

    from django.views import View

    from django_redis import get_redis_connection

    from . import constants

    from utils.json_fun import to_json_data

    from utils.res_code import Code, error_map

    from users.models import Users

    from . import forms

    from utils.yuntongxun.sms import CCP

    # 导入日志器

    logger = logging.getLogger('django')

    class SmsCodesView(View):

        """

        send mobile sms code

        POST /sms_codes/

        """

        def post(self, request):

            # 1、

            json_data = request.body

            if not json_data:

                return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])

            # 将json转化为dict

            dict_data = json.loads(json_data.decode('utf8'))

            # 2、

            form = forms.CheckImgCodeForm(data=dict_data)

            if form.is_valid():

                # 获取手机号

                mobile = form.cleaned_data.get('mobile')

                # 3、

                # 创建短信验证码内容

                sms_num = ''.join([random.choice(string.digits) for _ in range(constants.SMS_CODE_NUMS)])

                # 将短信验证码保存到数据库

                # 确保settings.py文件中有配置redis CACHE

                # Redis原生指令参考 http://redisdoc.com/index.html

                # Redis python客户端 方法参考 http://redis-py.readthedocs.io/en/latest/#indices-and-tables

                # 4、

                redis_conn = get_redis_connection(alias='verify_codes')

                pl = redis_conn.pipeline()

                # 创建一个在60s以内是否有发送短信记录的标记

                sms_flag_fmt = "sms_flag_{}".format(mobile)

                # 创建保存短信验证码的标记key

                sms_text_fmt = "sms_{}".format(mobile)

                # 此处设置为True会出现bug

                try:

                    pl.setex(sms_flag_fmt.encode('utf8'), constants.SEND_SMS_CODE_INTERVAL, 1)

                    pl.setex(sms_text_fmt.encode('utf8'), constants.SMS_CODE_REDIS_EXPIRES, sms_num)

                    # 让管道通知redis执行命令

                    pl.execute()

                except Exception as e:

                    logger.debug("redis 执行出现异常:{}".format(e))

                    return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])

                logger.info("Sms code: {}".format(sms_num))

                # 发送短语验证码

                try:

                    result = CCP().send_template_sms(mobile,

                                                     [sms_num, constants.SMS_CODE_YUNTX_EXPIRES],

                                                     constants.SMS_CODE_TEMP_ID)

                except Exception as e:

                    logger.error("发送验证码短信[异常][ mobile: %s, message: %s ]" % (mobile, e))

                    return to_json_data(errno=Code.SMSERROR, errmsg=error_map[Code.SMSERROR])

                else:

                    if result == 0:

                        logger.info("发送验证码短信[正常][ mobile: %s sms_code: %s]" % (mobile, sms_num))

                        return to_json_data(errno=Code.OK, errmsg="短信验证码发送成功")

                    else:

                        logger.warning("发送验证码短信[失败][ mobile: %s ]" % mobile)

                        return to_json_data(errno=Code.SMSFAIL, errmsg=error_map[Code.SMSFAIL])

            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 to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)

    ```

    ```python

    # 在verifications目录下的forms.py文件中定义如下form表单:

    #!/usr/bin/env python3

    # -*- coding: utf-8 -*-

    """

    -------------------------------------------------

      @Time : 2018/12/3 4:18 PM 

      @Auth : Youkou 

      @Site : www.youkou.site

      @File : forms.py

      @IDE  : PyCharm

      @Edit : 2018/12/3

    -------------------------------------------------

    """

    from django import forms

    from django.core.validators import RegexValidator

    from django_redis import get_redis_connection

    from users.models import Users

    # 创建手机号的正则校验器

    mobile_validator = RegexValidator(r"^1[3-9]\d{9}$", "手机号码格式不正确")

    class CheckImgCodeForm(forms.Form):

        """

        check image code

        """

        mobile = forms.CharField(max_length=11, min_length=11, validators=[mobile_validator, ],

                                 error_messages={"min_length": "手机号长度有误", "max_length": "手机号长度有误",

                                                 "required": "手机号不能为空"})

        image_code_id = forms.UUIDField(error_messages={"required": "图片UUID不能为空"})

        text = forms.CharField(max_length=4, min_length=4,

                               error_messages={"min_length": "图片验证码长度有误", "max_length": "图片验证码长度有误",

                                               "required": "图片验证码不能为空"})

        # Cleaning and validating fields that depend on each other

        def clean(self):

            cleaned_data = super().clean()

            # 1、

            image_uuid = cleaned_data.get("image_code_id")

            image_text = cleaned_data.get("text")

            mobile_num = cleaned_data.get("mobile")

            # 2、

            if Users.objects.filter(mobile=mobile_num).count():

                raise forms.ValidationError("手机号已注册,请重新输入")

            # 确保settings.py文件中有配置redis CACHE

            # Redis原生指令参考 http://redisdoc.com/index.html

            # Redis python客户端 方法参考 http://redis-py.readthedocs.io/en/latest/#indices-and-tables

            # 2、

            con_redis = get_redis_connection(alias='verify_codes')

            # 创建保存到redis中图片验证码的key

            img_key = "img_{}".format(image_uuid).encode('utf-8')

            # 取出图片验证码

            real_image_code_origin = con_redis.get(img_key)

            real_image_code = real_image_code_origin.decode('utf-8') if real_image_code_origin else None

            con_redis.delete(img_key)

            # 验证手机号

            if (not real_image_code) or (image_text != real_image_code):

                raise forms.ValidationError("图片验证失败")

            # 检查是否在60s内有发送记录

            sms_flag_fmt = "sms_flag_{}".format(mobile_num).encode('utf-8')

            sms_flag = con_redis.get(sms_flag_fmt)

            if sms_flag:

                raise forms.ValidationError("获取手机短信验证码过于频繁")

    ```

    ```python

    # 在verifications目录下的constants.py文件中定义如下常数:

    # 图片验证码redis有效期,单位秒

    IMAGE_CODE_REDIS_EXPIRES = 5 * 60

    # 短信验证码有效期,单位秒

    SMS_CODE_REDIS_EXPIRES = 5 * 60

    # 云通讯短信验证码过期时间(发送短信是显示为5分钟)

    SMS_CODE_YUNTX_EXPIRES = 5

    # 发送间隔

    SEND_SMS_CODE_INTERVAL = 60

    # 短信发送模板

    SMS_CODE_TEMP_ID = 1

    # 短信验证码位数

    SMS_CODE_NUMS = 6

    ```

    #### 3. 短信验证码平台-云通讯

    a.**本项目中使用的短信发送模块为**[云通讯](<http://www.yuntongxun.com/>)平台:

    - 用户发送短信(或语音)验证码的第三方平台

    - [参考文档地址](<http://www.yuntongxun.com/doc/ready/demo/1_4_1_2.html>)

    b.**注册登录**

    - 免费注册登录

    - 赠送8元,用于测试

    - 身份认证之后,才能正常使用

    c.**获取开发者相关参数**

    ![yuntongxun_1](../images/yuntongxun_1.jpg)

    ```python

    _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'

    ```

    ![modify_parm](../images/modify_parms.jpg)

    d.**设置测试账号**

    ![edit_test_mobile](../images/yuntongxun_2.jpg)

    #### 4.前端代码实现

    ```javascript

    $(function () {

      let $username = $('#user_name');  // 选择id为user_name的网页元素,需要定义一个id为user_name

      let $img = $(".form-item .captcha-graph-img img");  // 获取图像标签

      let sImageCodeId = "";  // 定义图像验证码ID值

      let $mobile = $('#mobile');  // 选择id为mobile的网页元素,需要定义一个id为mobile

      let $smsCodeBtn = $('.form-item .sms-captcha');  // 获取短信验证码按钮元素,需要定义一个id为input_smscode

      let $imgCodeText = $('#input_captcha');  // 获取用户输入的图片验证码元素,需要定义一个id为input_captcha

      // 1、图像验证码逻辑

      generateImageCode();  // 生成图像验证码图片

      $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片

      // 2、判断用户名是否注册

      $username.blur(function () {

        fn_check_usrname();

      });

      // 3、判断用户手机号是否注册

      $mobile.blur(function () {

        fn_check_mobile();

      });

      // 4、发送短信逻辑

      $smsCodeBtn.click(function () {

        // 判断手机号是否输入

        if (fn_check_mobile() !== "success") {

          return

        }

        // 判断用户是否输入图片验证码

        let text = $imgCodeText.val();  // 获取用户输入的图片验证码文本

        if (!text) {

            message.showError('请填写验证码!');

            return

        }

        // 判断是否生成的UUID

        if (!sImageCodeId) {

          message.showError('图片UUID为空');

          return

        }

        // 正常获取参数

        let SdataParams = {

          "mobile": $mobile.val(),   // 获取用户输入的手机号

          "text": text,  // 获取用户输入的图片验证码文本

          "image_code_id": sImageCodeId  // 获取图片UUID

        };

        // for test

        // let SdataParams = {

        //   "mobile": "1886608",   // 获取用户输入的手机号

        //   "text": "ha3d",  // 获取用户输入的图片验证码文本

        //   "image_code_id": "680a5a66-d9e5-4c3c-b8ea"  // 获取图片UUID

        // };

        // 向后端发送请求

        $.ajax({

          // 请求地址

          url: "/sms_codes/",

          // 请求方式

          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("获取验证码");

                } else {

                  num -= 1;

                  // 展示倒计时信息

                  $smsCodeBtn.html(num + "秒");

                }

              }, 1000);

            } else {

              message.showError(res.errmsg);

            }

          })

          .fail(function(){

            message.showError('服务器超时,请重试!');

          });

      });

      // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性

      function generateImageCode() {

        // 1、生成一个图片验证码随机编号

        sImageCodeId = generateUUID();

        // 2、拼接请求url /image_codes/<uuid:image_code_id>/

        let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";

        // 3、修改验证码图片src地址

        $img.attr('src', imageCodeUrl)

      }

      // 生成图片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 fn_check_usrname() {

        let sUsername = $username.val();  // 获取用户名字符串

        let sReturnValue = "";

        if (sUsername === "") {

          message.showError('用户名不能为空!');

          return

        }

        // test()方法 判断字符串中是否匹配到正则表达式内容,返回的是boolean值 ( true / false )

        if (!(/^\w{5,20}$/).test(sUsername)) {

          message.showError('请输入5-20个字符的用户名');

          return

        }

        // 发送ajax请求,去后端查询用户名是否存在

        $.ajax({

          url: '/usernames/' + sUsername + '/',

          type: 'GET',

          dataType: 'json',

          async: false

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.username + '已注册,请重新输入!');

              sReturnValue = ""

            } else {

              message.showInfo(res.data.username + '能正常使用!');

              sReturnValue = ""

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

            sReturnValue = ""

          });

          return sReturnValue

      }

      function fn_check_mobile() {

        let sMobile = $mobile.val();  // 获取用户输入的手机号码字符串

        let sReturnValue = "";

        if (sMobile === "") {

          message.showError('手机号不能为空!');

          return

        }

        if (!(/^1[345789]\d{9}$/).test(sMobile)) {

          message.showError('手机号码格式不正确,请重新输入!');

          return

        }

        $.ajax({

          url: '/mobiles/' + sMobile + '/',

          type: 'GET',

          dataType: 'json',

          async: false

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.mobile + '已注册,请重新输入!')

              sReturnValue = ""

            } else {

              SreturnValue = "success"

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

            sReturnValue = ""

          });

        return sReturnValue

      }

      // get cookie using jQuery

      function getCookie(name) {

        let cookieValue = null;

        if (document.cookie && document.cookie !== '') {

          let cookies = document.cookie.split(';');

          for (let i = 0; i < cookies.length; i++) {

            let cookie = jQuery.trim(cookies[i]);

            // 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));

      }

      // Setting the token on the AJAX request

      $.ajaxSetup({

        beforeSend: function (xhr, settings) {

          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {

            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));

          }

        }

      });

    });

    ```

    ### 七、用户注册功能实现

    #### 1.分析

    业务处理流程:

    - 判断用户名是否为空,是否已注册

    - 判断手机号是否为空,是否已注册

    - 判断密码是否为空,格式是否正确

    - 判断确认密码与密码是否相同

    - 判断短信验证码是否为空,是否格式正确,是否与真实的短信验证码相同

    **请求方法**:**POST**

    **url定义**:`/users/register/`

    **请求参数**:url路径参数

    | 参数            | 类型   | 前端是否必须传 | 描述                 |

    | --------------- | ------ | -------------- | -------------------- |

    | username        | 字符串 | 是             | 用户输入的用户名     |

    | password        | 字符串 | 是             | 用户输入的密码       |

    | password_repeat | 字符串 | 是             | 用户输入的重复密码   |

    | mobile          | 字符串 | 是             | 用户输入的手机号     |

    | sms_code        | 字符串 | 是             | 用户输入的短信验证码 |

    注:由于是post请求,在向后端发起请求时,需要附带csrf token

    #### 2.后端代码实现

    ```python

    # 在users目录下的views.py文件中定义如下类视图:

    import json

    from django.shortcuts import render

    from django.views import View

    from .forms import RegisterForm

    from .models import Users

    from utils.json_fun import to_json_data

    from utils.res_code import Code, error_map

    class RegisterView(View):

        """

        """

        def get(self, request):

            """

            """

            return render(request, 'users/register.html')

        def post(self, request):

            """

            """

            # 1、

            json_data = request.body

            if not json_data:

                return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])

            # 将json转化为dict

            dict_data = json.loads(json_data.decode('utf8'))

            form = RegisterForm(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)

                login(request, user)

                return to_json_data(errmsg="恭喜您,注册成功!")

            else:

                # 定义一个错误信息列表

                err_msg_list = []

                for item in form.errors.get_json_data().values():

                    err_msg_list.append(item[0].get('message'))

                err_msg_str = '/'.join(err_msg_list)

                return to_json_data(errno=Code.PARAMERR, errmsg=err_msg_str)

    ```

    ```python

    # 在users目录下的forms.py文件中定义如下form表单:

    #!/usr/bin/env python3

    # -*- coding: utf-8 -*-

    """

    -------------------------------------------------

      @Time : 2018/12/8 2:22 PM 

      @Auth : Youkou 

      @Site : www.youkou.site

      @File : forms.py

      @IDE  : PyCharm

      @Edit : 2018/12/8

    -------------------------------------------------

    """

    import re

    from django import forms

    from django_redis import get_redis_connection

    from verifications.constants import SMS_CODE_NUMS

    from .models import Users

    class RegisterForm(forms.Form):

        """

        """

        username = forms.CharField(label='用户名', max_length=20, min_length=5,

                                   error_messages={"min_length": "用户名长度要大于5", "max_length": "用户名长度要小于20",

                                                   "required": "用户名不能为空"}

                                   )

        password = forms.CharField(label='密码', max_length=20, min_length=6,

                                   error_messages={"min_length": "密码长度要大于6", "max_length": "密码长度要小于20",

                                                   "required": "密码不能为空"}

                                   )

        password_repeat = forms.CharField(label='确认密码', max_length=20, min_length=6,

                                          error_messages={"min_length": "密码长度要大于6", "max_length": "密码长度要小于20",

                                                          "required": "密码不能为空"}

                                          )

        mobile = forms.CharField(label='手机号', max_length=11, min_length=11,

                                 error_messages={"min_length": "手机号长度有误", "max_length": "手机号长度有误",

                                                 "required": "手机号不能为空"})

        sms_code = forms.CharField(label='短信验证码', max_length=SMS_CODE_NUMS, min_length=SMS_CODE_NUMS,

                                   error_messages={"min_length": "短信验证码长度有误", "max_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).exists():

                raise forms.ValidationError("手机号已注册,请重新输入!")

            return tel

        def clean(self):

            """

            """

            #

            cleaned_data = super().clean()

            passwd = cleaned_data.get('password')

            passwd_repeat = cleaned_data.get('password_repeat')

            if passwd != passwd_repeat:

                #

                raise forms.ValidationError("两次密码不一致")

            #

            tel = cleaned_data.get('mobile')

            sms_text = cleaned_data.get('sms_code')

            # 建立redis连接

            redis_conn = get_redis_connection(alias='verify_codes')

            #

            sms_fmt = "sms_{}".format(tel).encode('utf-8')

            #

            real_sms = redis_conn.get(sms_fmt)

            #

            if (not real_sms) or (sms_text != real_sms.decode('utf-8')):

                raise forms.ValidationError("短信验证码错误")

    ```

    ```python

    # 在users目录下的urls.py文件中添如下路由:

    #!/usr/bin/env python3

    # -*- coding: utf-8 -*-

    """

    -------------------------------------------------

      @Time : 2018/11/29 9:58 AM 

      @Auth : Youkou 

      @Site : www.youkou.site

      @File : urls.py

      @IDE  : PyCharm

      @Edit : 2018/11/29

    -------------------------------------------------

    """

    from django.urls import path

    from . import views

    app_name = 'users'

    urlpatterns = [

        # path('register/', views.register, name='register'),

        path('register/', views.RegisterView.as_view(), name='register'),

    ]

    ```

    #### 3.前端代码实现

    ```javascript

    $(function () {

      let $username = $('#user_name');  // 选择id为user_name的网页元素,需要定义一个id为user_name

      let $img = $(".form-item .captcha-graph-img img");  // 获取图像标签

      let sImageCodeId = "";  // 定义图像验证码ID值

      let $mobile = $('#mobile');  // 选择id为mobile的网页元素,需要定义一个id为mobile

      let $smsCodeBtn = $('.form-item .sms-captcha');  // 获取短信验证码按钮元素,需要定义一个id为input_smscode

      let $imgCodeText = $('#input_captcha');  // 获取用户输入的图片验证码元素,需要定义一个id为input_captcha

      let $register = $('.form-contain');  // 获取注册表单元素

      // 1、图片验证码逻辑

      // 通过uuid生成验证码编号

      // 拼接验证码地址

      // 设置验证码图片标签的src

      generateImageCode();  // 生成图像验证码图片

      $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片

      // 2、用户名验证逻辑

      $username.blur(function () {

        fn_check_usrname();

      });

      // 3、手机号验证逻辑

      // 判断用户手机号是否注册

      $mobile.blur(function () {

        fn_check_mobile();

      });

      // 4、发送短信验证码逻辑

      $smsCodeBtn.click(function () {

        // 判断手机号是否输入

        if (fn_check_mobile() !== "success") {

          return

        }

        // 判断用户是否输入图片验证码

        let text = $imgCodeText.val();  // 获取用户输入的图片验证码文本

        if (!text) {

            message.showError('请填写验证码!');

            return

        }

        if (!sImageCodeId) {

          message.showError('图片UUID为空');

          return

        }

        // 正常

        let SdataParams = {

          "mobile": $mobile.val(),   // 获取用户输入的手机号

          "text": text,   // 获取用户输入的图片验证码文本

          "image_code_id": sImageCodeId  // 获取图片UUID

        };

        // for test

        // let SdataParams = {

        //   "mobile": "1806508",   // 获取用户输入的手机号

        //   "text": "ha3d",  // 获取用户输入的图片验证码文本

        //   "image_code_id": "680a5a66-d9e5-4c3c-b8ea"  // 获取图片UUID

        // };

        // 向后端发送请求

        $.ajax({

          // 请求地址

          url: "/sms_codes/",

          // 请求方式

          type: "POST",

          // 向后端发送csrf token

          // headers: {

          //           // 根据后端开启的CSRFProtect保护,cookie字段名固定为X-CSRFToken

          //           "X-CSRFToken": getCookie("csrf_token")

          // },

          // data: JSON.stringify(SdataParams),

          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("获取验证码");

                } else {

                  num -= 1;

                  // 展示倒计时信息

                  $smsCodeBtn.html(num + "秒");

                }

              }, 1000);

            } else {

              message.showError(res.errmsg);

            }

          })

          .fail(function(){

            message.showError('服务器超时,请重试!');

          });

      });

      // 5、注册逻辑

      $register.submit(function (e) {

        // 阻止默认提交操作

        e.preventDefault();

        // 获取用户输入的内容

        let sUsername = $username.val();  // 获取用户输入的用户名字符串

        let sPassword = $("input[name=password]").val();

        let sPasswordRepeat = $("input[name=password_repeat]").val();

        let sMobile = $mobile.val();  // 获取用户输入的手机号码字符串

        let sSmsCode = $("input[name=sms_captcha]").val();

        // 判断用户名是否已注册

        if (fn_check_usrname() !== "success") {

          return

        }

        // 判断手机号是否为空,是否已注册

        if (fn_check_mobile() !== "success") {

          return

        }

        // 判断用户输入的密码是否为空

        if ((!sPassword) || (!sPasswordRepeat)) {

          message.showError('密码或确认密码不能为空');

          return

        }

        // 判断用户输入的密码和确认密码长度是否为6-20位

        if ((sPassword.length < 6 || sPassword.length > 20) ||

          (sPasswordRepeat.length < 6 || sPasswordRepeat.length > 20)) {

          message.showError('密码和确认密码的长度需在6~20位以内');

          return

        }

        // 判断用户输入的密码和确认密码是否一致

        if (sPassword !== sPasswordRepeat) {

          message.showError('密码和确认密码不一致');

          return

        }

        // 判断用户输入的短信验证码是否为6位数字

        if (!(/^\d{6}$/).test(sSmsCode)) {

          message.showError('短信验证码格式不正确,必须为6位数字!');

          return

        }

        // 发起注册请求

        // 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('服务器超时,请重试!');

          });

      });

      // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性

      function generateImageCode() {

        // 1、生成一个图片验证码随机编号

        sImageCodeId = generateUUID();

        // 2、拼接请求url /image_codes/<uuid:image_code_id>/

        let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";

        // 3、修改验证码图片src地址

        $img.attr('src', imageCodeUrl)

      }

      // 生成图片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 fn_check_usrname() {

        let sUsername = $username.val();  // 获取用户名字符串

        let sReturnValue = "";

        if (sUsername === "") {

          message.showError('用户名不能为空!');

          return

        }

        if (!(/^\w{5,20}$/).test(sUsername)) {

          message.showError('请输入5-20个字符的用户名');

          return

        }

        // 发送ajax请求,去后端查询用户名是否存在

        $.ajax({

          url: '/usernames/' + sUsername + '/',

          type: 'GET',

          dataType: 'json',

          async: false

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.username + '已注册,请重新输入!')

              sReturnValue = ""

            } else {

              message.showInfo(res.data.username + '能正常使用!')

              sReturnValue = "success"

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

            sReturnValue = ""

          });

        return sReturnValue

      }

      // 判断手机号是否注册

      function fn_check_mobile() {

        let sMobile = $mobile.val();  // 获取用户输入的手机号码字符串

        let sReturnValue = "";

        if (sMobile === "") {

          message.showError('手机号不能为空!');

          return

        }

        if (!(/^1[345789]\d{9}$/).test(sMobile)) {

          message.showError('手机号码格式不正确,请重新输入!');

          return

        }

        $.ajax({

          url: '/mobiles/' + sMobile + '/',

          type: 'GET',

          dataType: 'json',

          async: false

        })

          .done(function (res) {

            if (res.data.count !== 0) {

              message.showError(res.data.mobile + '已注册,请重新输入!')

              sReturnValue = ""

            } else {

              message.showSuccess(res.data.mobile + '能正常使用!');

              sReturnValue = "success"

            }

          })

          .fail(function () {

            message.showError('服务器超时,请重试!');

            sReturnValue = ""

          });

        return sReturnValue

      }

      // get cookie using jQuery

      function getCookie(name) {

        let cookieValue = null;

        if (document.cookie && document.cookie !== '') {

          let cookies = document.cookie.split(';');

          for (let i = 0; i < cookies.length; i++) {

            let cookie = jQuery.trim(cookies[i]);

            // 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));

      }

      // Setting the token on the AJAX request

      $.ajaxSetup({

        beforeSend: function (xhr, settings) {

          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {

            xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));

          }

        }

      });

    });

    ```

    相关文章

      网友评论

          本文标题:用户登录、登出、注册功能的实现

          本文链接:https://www.haomeiwen.com/subject/eksakqtx.html