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

2019-05-21  本文已影响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'));

      }

    }

  });

});

```

上一篇下一篇

猜你喜欢

热点阅读