天天看点

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

1、图形验证码后端逻辑

准备captcha扩展包

captcha扩展包用于后端生成图形验证码,captcha扩展包可以从网上百度找到相关代码和文件,fonts是支持的字体文件,包含有actionj.ttf、Arial.ttf、Georgia.ttf。

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

生成验证码文件:

apps/verifications/libs/captcha/captcha.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# refer to `https://bitbucket.org/akorn/wheezy.captcha`

"""
生成验证码文件:apps/verifications/libs/captcha/captcha.py

需要安装 pillow 库:pip install pillow
"""
import random
import string
import os.path
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def pascal_row(self, n):
        """ Returns n-th row of Pascal's triangle
        """
        result = [1]
        x, numerator = 1, n
        for denominator in range(1, n // 2 + 1):
            x *= numerator
            x /= denominator
            result.append(x)
            numerator -= 1
        if n & 1 == 0:
            result.extend(reversed(result[:-1]))
        else:
            result.extend(reversed(result))
        return result

    def make_bezier(self, n):
        """ Bezier curves:
            http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
        """
        try:
            return self.beziers[n]
        except KeyError:
            combinations = self.pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha(object):
    def __init__(self):
        self._bezier = Bezier()
        self._dir = os.path.dirname(__file__)
        # self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')

    @staticmethod
    def instance():
        if not hasattr(Captcha, "_instance"):
            Captcha._instance = Captcha()
        return Captcha._instance

    def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
        # self.image = Image.new('RGB', (width, height), (255, 255, 255))
        self._text = text if text else random.sample(string.ascii_uppercase + string.ascii_uppercase + '3456789', 4)
        self.fonts = fonts if fonts else \
            [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
        self.width = width
        self.height = height
        self._color = color if color else self.random_color(0, 200, random.randint(220, 255))

    @staticmethod
    def random_color(start, end, opacity=None):
        red = random.randint(start, end)
        green = random.randint(start, end)
        blue = random.randint(start, end)
        if opacity is None:
            return red, green, blue
        return red, green, blue, opacity

    # draw image

    def background(self, image):
        Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
        return image

    @staticmethod
    def smooth(image):
        return image.filter(ImageFilter.SMOOTH)

    def curve(self, image, width=4, number=6, color=None):
        dx, height = image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = self._bezier.make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(image).line(points, fill=color if color else self._color, width=width)
        return image

    def noise(self, image, number=50, level=2, color=None):
        width, height = image.size
        dx = width / 10
        width -= dx
        dy = height / 10
        height -= dy
        draw = Draw(image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
        return image

    def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(image)
        char_images = []
        for c in self._text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            image.paste(char_image,
                        (offset, int((height - c_height) / 2)),
                        mask)
            offset += int(c_width * squeeze_factor)
        return image

    # draw text
    @staticmethod
    def warp(image, dx_factor=0.27, dy_factor=0.21):
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        image2 = Image.new('RGB',
                           (width + abs(x1) + abs(x2),
                            height + abs(y1) + abs(y2)))
        image2.paste(image, (abs(x1), abs(y1)))
        width2, height2 = image2.size
        return image2.transform(
            (width, height), Image.QUAD,
            (x1, y1,
             -x1, height2 - y2,
             width2 + x2, height2 + y2,
             width2 - x2, -y1))

    @staticmethod
    def offset(image, dx_factor=0.1, dy_factor=0.2):
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        image2 = Image.new('RGB', (width + dx, height + dy))
        image2.paste(image, (dx, dy))
        return image2

    @staticmethod
    def rotate(image, angle=25):
        return image.rotate(
            random.uniform(-angle, angle), Image.BILINEAR, expand=1)

    def captcha(self, path=None, fmt='JPEG'):
        """Create a captcha.

        Args:
            path: save path, default None.
            fmt: image format, PNG / JPEG.
        Returns:
            A tuple, (text, StringIO.value).
            For example:
                ('JGW9', '\x89PNG\r\n\x1a\n\x00\x00\x00\r...')

        """
        image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
        image = self.background(image)
        image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
        image = self.curve(image)
        image = self.noise(image)
        image = self.smooth(image)
        text = "".join(self._text)
        out = BytesIO()
        image.save(out, format=fmt)
        return text, out.getvalue()

    def generate_captcha(self):
        self.initialize()
        return self.captcha("")


captcha = Captcha.instance()


if __name__ == '__main__':
    print(captcha.generate_captcha())         # 输出的内容是(‘验证码’,验证码背景图片二进制文件)
    # 得到验证码就只需要调用captcha.generate_captcha()方法即可

           

安装pillow库:

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

准备Redis数据库

准备Redis的2号库存储验证码数据

开发项目配置文件:

dev.py

# Redis数据库配置
CACHES = {
    "default": {        # 默认
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",                 # 最后一个0是第0个数据库,redis共有16个数据库  0-15
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "session": {        # session       session可以放在redis中,例如验证码,图形验证等等
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",                  # session保存在第1个数据库中
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "verify_code": {    # 验证码
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },

}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"          # engine保存在cache数据库中
SESSION_CACHE_ALIAS = "session"
           

图形验证码后端逻辑实现

apps/verifications/views.py

验证码视图文件

"""
apps/verifications/views.py   验证码视图文件
"""
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
from .libs.captcha.captcha import captcha      # 调用实例化对象captcha
from django_redis import get_redis_connection   # 连接redis


class ImageCodeView(View):
    """图像验证码"""
    
    def get(self, request, uuid):
        """
        :param uuid:用于标识该验证码属于哪个用户,用户注册时期没有id标识,用uuid不会产生重复的id
        :return:
        """
        #  生成验证码,从生成验证码文件中调用实例化对象的方法
        text, image = captcha.generate_captcha()
        
        # 先连接redis,再保存到redis
        redis_conn = get_redis_connection('verify_code')  # verify_code是dev.py配置文件中的redis配置参数
        # redis_conn.set('img_%s' % uuid, text)   # set(key,value), 这种保存redis数据,无法设置保存时效
        redis_conn.setex('img_%s' % uuid, 300, text)   # set(key,expries,value),expries是时效,以秒s为单位
        
        # 响应图形验证码
        # return HttpResponse('image/jpg')
        return HttpResponse(image, content_type='image/jpg')

           

因为获取图形验证码是

get

请求,直接访问它的路由进行测试,

uuid

可以从网上复制一个,然后在

Redis

数据库中调出数据,查看是否一致。

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

图形验证码前端逻辑

Vue实现图形验证码展示

验证注册界面的逻辑文件:

static/js/register.js

// 实例化Vue的对象   static/js/register.js文件,验证注册界面的逻辑
let vm = new Vue({
    el:"#app",
    // 修改Vue读取变量的语法 {{}}  [[]],Vue中可能不识别Django的{{}}语法形式
    delimiters: ['[[', ']]'],

    data: {
        // v-model绑定名称
        username:"",    //绑定前端界面的username标签
        password:"",
        password2:"",
        mobile:"",
        allow:"",
        image_code_url:"",     // 验证码绑定的路由
        uuid:"",               // 采用common.js文件方法生成的uuid
        image_code:"",         // 检测验证码的格式

        // v-show绑定的名称,默认false不显示
        error_name: false,
        error_password: false,
        error_password2: false,
        error_mobile: false,
        error_allow: false,
        error_image_code: false,

        // 绑定的错误信息
        error_name_message:"",
        error_mobile_message:"",
        error_image_code_message:"",     // 验证码错误信息
    },

    // Vue的生命周期,页面加载完成后被调用该方法,验证码生成
        mounted(){
            this.generate_image_code();
        },

    methods: {
        // 验证码点击更换方法,且需要在页面加载完成时候,该方法已经执行
        generate_image_code(){
            // 生成uuid,调用common.js方法
            this.uuid = generateUUID()
            // 验证码路由拼接
            this.image_code_url = "/image_codes/"+ this.uuid + "/"
        },

        // 定义方法  定义标签失去焦点的方法
        // check_username:function () {
        // }

        // @blur="check_username"方法
        check_username(){
            // 正则表达式  5-20位字符数字组成
            let re = /^[a-zA-Z0-9_-]{5,20}$/;
            if(re.test(this.username)){
                // 匹配成功   错误信息不展示
                this.error_name = false
            }else{
                this.error_name = true  // v-show为true,显示信息
                this.error_name_message = "请输入5-20个字符的用户"
            }

            // 使用Vue的ajax进行表单验证
            if(this.error_name == false){        // 用户名正确情况
                // http://127.0.0.1:8000/users/usernames/用户名/count      路由格式
                let url = '/users/usernames/'+ this.username + '/count'    // url拼接

                // Vue发送ajax请求
                axios.get(url, {
                    responseType:'json'
                })
                // 请求成功
                .then(response=> {                // .then(function(response))
                    // 从apps/users/views.py文件返回的JsonResponse({"code": 0, "errmsg": "OK", "count": count})
                    if(response.data.count == 1 ){  // 用户名已经存在   count数据就是在views.py文件中传出的
                        this.error_name_message = '用户名已经存在'
                        this.error_name = true
                    }else{
                        this.error_name = false      //可以继续注册其他的字段
                    }
                })
                // 请求失败
                .catch(error =>{
                    console.log(error.response)         // 前端界面打印error.response
                })
            }
        },

        // @blur="check_password"
        check_password(){
            let re = /^[a-zA-Z0-9]{8,20}$/;
            if(re.test(this.password)){
                this.error_password = false
            }else{
                this.error_password = true
            }
        },

        //  @blur="check_password2"
        check_password2(){
            // 保持一致就行
            if(this.password2 != this.password){
                this.error_password2 = true
            }else{
                this.error_password2 = false
            }
        },

        // @blur="check_mobile"
        check_mobile(){
            let re = /^1[3456789]\d{9}$/;
            if(re.test(this.mobile)){
                this.error_mobile = false
            }else{
                this.error_mobile = true
                this.error_mobile_message = "请输入正确格式手机号!"
            }
        },

        // @change="check_allow"
        check_allow(){                        // checkbox的选中状态
            if(!this.allow){                  // allow是个空的bool类型值
                this.error_allow = true
            }else{
                this.error_allow = false
            }
        },

        // on_submit 表单提交
        on_submit(){
            // 如果表单验证中有true就说明有错误信息,不能提交
            if(this.error_name == true || this.error_password == true || this.error_password2 ==true ||
            this.error_mobile == true || this.error_allow == true){
                // 禁止表单提交
                window.event.returnValue=false
            }
        },

        // 检测验证码格式方法
        check_image_code(){
            if(this.image_code.length != 4){
                this.error_image_code_message = '图形验证码长度不正确'
                this.error_image_code = true      // 前端界面v-show=true,展示错误信息
            }else{
                this.error_image_code = false     // 不显示信息
            }
        },

    },
});
           

生成uuid的文件:

static/js/common.js

文件

// 生成uuid的文件:static/js/common.js文件

// 获取cookie
function getCookie(name) {
    let r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

// 提取地址栏中的查询字符串
function get_query_string(name) {
    let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
    let r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return decodeURI(r[2]);
    }
    return null;
}

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

templates/register.html

前端注册界面

{#  templates/register.html  前端注册界面  #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-注册</title>
	<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
</head>
<body>
<!--绑定vue的app-->
<div id="app">
	<div class="register_con">
		<div class="l_con fl">
			<a href="index.html" class="reg_logo"><img src="{% static 'images/1.png' %}"></a>
			<div class="reg_slogan">商品美 · 种类多 · 欢迎光临</div>
			<div class="reg_banner"></div>
		</div>
		<div class="r_con fr">
			<div class="reg_title clearfix">
				<h1>用户注册</h1>
				<a href="login.html">登录</a>
			</div>
			<div class="reg_form clearfix">
<!--             v-cloak:解决Vue界面加载延迟的bug   @submit="on_submit"提交注册信息前的验证   -->
				<form method="post" class="register_form" v-cloak @submit="on_submit">
                    {% csrf_token %}
					<ul>
						<li>
							<label>用户名:</label>
<!--                        绑定Vue的名称v-model      @blur="check_username"是方法:当标签失去焦点,即鼠标光标消失 -->
							<input type="text" name="username" v-model="username" @blur="check_username" id="user_name">
<!--						当输入信息不合法,使用v-show标签是个bool类型的值,通过ajax传递信息到前端界面	 v-show为False时候,元素信息会被隐藏-->
                            <span class="error_tip" v-show="error_name">[[ error_name_message ]]</span>
						</li>					
						<li>
							<label>密码:</label>
<!--                              绑定Vue的名称v-model           -->
							<input type="password" name="password" v-model="password" @blur="check_password" id="pwd">
							<span class="error_tip" v-show="error_password">请输入8-20位的密码</span>
						</li>
						<li>
							<label>确认密码:</label>
<!--                          绑定Vue的名称v-model   -->
							<input type="password" name="password2" v-model="password2" @blur="check_password2" id="cpwd">
							<span class="error_tip" v-show="error_password2">两次输入的密码不一致</span>
						</li>
						<li>
							<label>手机号:</label>
                            <!--                          绑定Vue的名称v-model   -->
							<input type="text" name="mobile" v-model="mobile" @blur="check_mobile" id="phone">
<!--                        [[ error_mobile_message ]]是为了解决Vue识别不了{{ error_mobile_message }}语法的问题    -->
							<span class="error_tip" v-show="error_mobile">[[ error_mobile_message ]]</span>
						</li>
						<li>
							<label>图形验证码:</label>
{#                             v-model="image_code" @blur="check_image_code"绑定验证码的格式   #}
							<input type="text" name="image_code" id="pic_code" class="msg_input" v-model="image_code" @blur="check_image_code" >
{#                           图形验证码路径进行绑定 :src="image_code_url"  点击变换@click="generate_image_code"   #}
							<img :src="image_code_url" @click="generate_image_code" alt="图形验证码" class="pic_code">
{#							 错误信息显示error_image_code_message     绑定js文件中的错误信息名称v-show="error_image_code" #}
                            <span class="error_tip" v-show="error_image_code">[[ error_image_code_message ]]</span>
						</li>
						<li>
							<label>短信验证码:</label>
							<input type="text" name="sms_code" id="msg_code" class="msg_input">
							<a href="javascript:;" class="get_msg_code">获取短信验证码</a>
							<span class="error_tip">请填写短信验证码</span>
						</li>
						<li class="agreement">
<!--                        type="checkbox" 需要使用@change方法来判定焦点的信息      绑定Vue的名称v-model            -->
							<input type="checkbox" name="allow" v-model="allow" @change="check_allow"  id="allow">
							<label>同意”LG商城用户使用协议“</label>
						<!--	当输入信息不合法,使用v-show标签是个bool类型的值,通过ajax传递信息到前端界面	 v-show为False时候,元素信息会被隐藏-->
							<span class="error_tip" v-show="error_allow">请勾选用户协议</span>

{# 这部分传注册错误信息到前端界面是用的Django自带的,前端验证form表单信息我们采用的是  @submit="on_submit" 中的ajax进行验证,这是两种方式,选择一种就可以 #}
                        {#  将apps/users/views.py文件中的注册错误信息context进行循环   #}
                            <span class="error_tip">
                                {% if form_errors %}
                                    {% for key,error in form_errors.items %}
                                            {{ error }}
                                    {% endfor %}
                                {% endif %}
                            {# 保存用户注册数据失败的信息apps/users/views.py中传递的register_error_message             #}
                                {% if register_error_message %}
                                    {{ register_error_message }}
                                {% endif %} 
                            </span>

						</li>
						<li class="reg_sub">
							<input type="submit" value="注 册">
						</li>
					</ul>				
				</form>
			</div>
		</div>
	</div>
	<div class="footer no-mp">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
</div>
    <!--  引入Vue前端框架和ajax用于发送验证信息到前端界面  -->
    <script src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script src="{% static 'js/axios-0.18.0.min.js' %}"></script>

    {#   生成uuid的文件 需要先加载common.js文件,再加载注册界面js #}
    <script src="{% static 'js/common.js' %}"></script>
    <!--  引入注册界面的js文件   -->
    <script src="{% static 'js/register.js' %}"></script>

</body>
</html>
           

2、短信验证码

短信验证码逻辑分析

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

总结

  • 保存短信验证码是为注册做准备的。
  • 为了避免用户使用图形验证码恶意测试,后端提取了图形验证码后,立即删除图形验证码。
  • Django不具备发送短信的功能,所以我们借助第三方的容联云通讯短信平台来帮助我们发送短信验证码。

容联云通讯短信平台介绍

容联云通讯网址:https://www.yuntongxun.com/

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

容联云通讯Python SDK

https://doc.yuntongxun.com/p/5f029ae7a80948a1006e776e

from ronglian_sms_sdk import SmsSDK
 
accId = '容联云通讯分配的主账号ID'
accToken = '容联云通讯分配的主账号TOKEN'
appId = '容联云通讯分配的应用ID'
 
def send_message():
    sdk = SmsSDK(accId, accToken, appId)
    tid = '容联云通讯创建的模板ID'
    mobile = '手机号1,手机号2'
    datas = ('变量1', '变量2')
    resp = sdk.sendMessage(tid, mobile, datas)
    print(resp)
           

要先安装

ronglian_sms_sdk

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

封装发送短信单例类

单例类是指实例化类的时候,为了防止被占用多个内存空间,采用单例类实例化的时候就只会创建一个内存空间,防止资源浪费。

class CCP(object):
    """发送短信的单例类"""

    def __new__(cls, *args, **kwargs):
        # 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
        if not hasattr(cls, "_instance"):
            cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
            cls._instance.rest = SmsSDK(accId, accToken, appId)
        return cls._instance
           

项目创建短信验证码的文件目录如下:

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

实例如下:发送短信验证码文件:

apps/verifications/libs/ronglianyun/ccp_sms.py

# -*- encoding: utf-8 -*-
"""
@File    : ccp_sms.py
@Time    : 2020/8/2 17:23
@Author  : chen


发送短信验证码文件:apps/verifications/libs/ronglianyun/ccp_sms.py
"""
from ronglian_sms_sdk import SmsSDK
import json

accId = '8a216da873a33a500173a407c9bf010c'        # 容联云通讯分配的主账号ID
accToken = '3e0d6a4bbe884ad9889d8d9a16c3747a'     # 容联云通讯分配的主账号TOKEN
appId = '8a216da873a33a500173a407cab00113'        # 容联云的APP ID


# 单例类的实例化能够节省内存空间,无论实例化多少次,内存空间只有一个
class CCP(object):
    # __new__方法是__init__方法之上被调用的,__init__方法会在实例化时候调用,__new__方法是生成类的方法
    def __new__(cls, *args, **kwargs):                              # 这里的cls代表CCP这个类
        if not hasattr(cls, '_instance'):                           # 当不具有_instance这个属性的时候
            cls._instance = super().__new__(cls, *args, **kwargs)
            # print(type(cls._instance))                            # cls._instance相当于CCP这个类
            cls._instance.sdk = SmsSDK(accId, accToken, appId)      # 给CCP这个类添加sdk这个属性,相当于实例化了SmsSDK这个类
        return cls._instance

    # 发送短信验证码
    def send_message(self, mobile, datas, tid):
        resp = self._instance.sdk.sendMessage(tid, mobile, datas)
        print(type(resp))                        # str  需要转换成字典
        result = json.loads(resp)                # 转换数据类型

        # sdk = SmsSDK(accId, accToken, appId)
        # tid = '容联云通讯创建的模板ID'
        # mobile = '15210438734'
        # datas = ('变量1', '变量2')
        # resp = sdk.sendMessage(tid, mobile, datas)
        # print(resp)
        
        if result['statusCode'] == '000000':      # 当传输状态码为000000时候,代表发送信息成功
            return 0
        else:
            return 1


# if __name__ == '__main__':
#     a = CCP()
#     res = a.send_message('15210438734', ('123456', 5), 1)
#     print(res)
           

3、短信验证码后端逻辑

短信验证码接口设计

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

请求参数:路径参数和查询字符串

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

响应结果:JSON

Django项目实战——3—(图形验证码后端逻辑、短信验证码、短信验证码后端逻辑)1、图形验证码后端逻辑2、短信验证码3、短信验证码后端逻辑

短信验证码接口定义

apps/verifications/urls.py

验证码路由文件

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/7/29 19:14
@Author  : chen


apps/verifications/urls.py   验证码路由文件
"""
from django.urls import path, include, re_path
from . import views

urlpatterns = [
    re_path('image_codes/(?P<uuid>[\w-]+)/', views.ImageCodeView.as_view()),      # 图形验证码子路由
    re_path(r'sms_codes/(?P<mobile>1[3-9]\d{9})/', views.SMSCodeView.as_view()),      # 短信验证码子路由

]
           

定义软编码文件:

apps/verifications/constants.py

# -*- encoding: utf-8 -*-
"""
@File    : constants.py
@Time    : 2020/8/4 16:44
@Author  : chen

定义软编码文件:apps/verifications/constants.py
"""
# 图形验证码有效期   单位:s秒
IMAGE_CODE_REDIS_EXPIRES = 300

# 短信验证码有效期   单位:s秒
SMS_CODE_REDIS_EXPIRES = 300

# 短信模板
SEND_SMS_TEMPLATE_ID = 1

           

utils/response_code.py

定义各种状态码文件

# coding:utf-8
"""
utils/response_code.py   定义各种状态码文件
"""
class RETCODE:
    OK = "0"
    IMAGECODEERR = "4001"
    THROTTLINGERR = "4002"
    NECESSARYPARAMERR = "4003"
    USERERR = "4004"
    PWDERR = "4005"
    CPWDERR = "4006"
    MOBILEERR = "4007"
    SMSCODERR = "4008"
    ALLOWERR = "4009"
    SESSIONERR = "4101"
    DBERR = "5000"
    EMAILERR = "5001"
    TELERR = "5002"
    NODATAERR = "5003"
    NEWPWDERR = "5004"
    OPENIDERR = "5005"
    PARAMERR = "5006"
    STOCKERR = "5007"


err_msg = {
    RETCODE.OK: "成功",
    RETCODE.IMAGECODEERR: "图形验证码错误",
    RETCODE.THROTTLINGERR: "访问过于频繁",
    RETCODE.NECESSARYPARAMERR: "缺少必传参数",
    RETCODE.USERERR: "用户名错误",
    RETCODE.PWDERR: "密码错误",
    RETCODE.CPWDERR: "密码不一致",
    RETCODE.MOBILEERR: "手机号错误",
    RETCODE.SMSCODERR: "短信验证码有误",
    RETCODE.ALLOWERR: "未勾选协议",
    RETCODE.SESSIONERR: "用户未登录",
    RETCODE.DBERR: "数据错误",
    RETCODE.EMAILERR: "邮箱错误",
    RETCODE.TELERR: "固定电话错误",
    RETCODE.NODATAERR: "无数据",
    RETCODE.NEWPWDERR: "新密码数据",
    RETCODE.OPENIDERR: "无效的openid",
    RETCODE.PARAMERR: "参数错误",
    RETCODE.STOCKERR: "库存不足",
}

           

apps/verifications/views.py

验证码视图文件

"""
apps/verifications/views.py   验证码视图文件
"""
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from .libs.captcha.captcha import captcha      # 调用实例化对象captcha
from django_redis import get_redis_connection   # 连接redis
import random
from verifications.libs.ronglianyun.ccp_sms import CCP    # 导入发送短信验证码类
from verifications import constants                       # 导入定义软编码文件
from utils.response_code import RETCODE                   # 导入定义状态码文件


# 图形验证码
class ImageCodeView(View):
    """图像验证码"""
    
    def get(self, request, uuid):
        """
        :param uuid:用于标识该验证码属于哪个用户,用户注册时期没有id标识,用uuid不会产生重复的id
        :return:
        """
        #  生成验证码,从生成验证码文件中调用实例化对象的方法
        text, image = captcha.generate_captcha()
        
        # 先连接redis,再保存到redis
        redis_conn = get_redis_connection('verify_code')  # verify_code是dev.py配置文件中的redis配置参数
        # redis_conn.set('img_%s' % uuid, text)   # set(key,value), 这种保存redis数据,无法设置保存时效
        redis_conn.setex('img_%s' % uuid, constants.IMAGE_CODE_REDIS_EXPIRES, text)   # set(key,expries,value),expries是时效,以秒s为单位
        
        # 响应图形验证码
        # return HttpResponse('image/jpg')
        return HttpResponse(image, content_type='image/jpg')


# 手机验证码
class SMSCodeView(View):
    """短信验证码"""
    def get(self, request, mobile):
        """
        :param mobile: 手机号
        :return:   json数据类型
        """
        # 接收参数
        image_code_client = request.GET.get('image_code')        # image_code_client是字符串数据类型
        uuid = request.GET.get('uuid')
        
        # 校验参数   image_code_client, uuid必须都存在
        if not all([image_code_client, uuid]):
            return HttpResponseForbidden('缺少必传参数')
         
        # 创建连接到redis的对象
        # 提取图形验证码
        redis_conn = get_redis_connection('verify_code')
        image_code_server = redis_conn.get('img_%s' % uuid)      # 此时的image_code_server是字节数据类型
        # print(type(image_code_server))
        # print(type(image_code_client))
        
        # 删除图形验证码,避免恶意测试图形验证码
        redis_conn.delete('img_%s' % uuid)
        
        # 对比图形验证码  .lower()都转成小写
        if image_code_client.lower() != image_code_server.decode().lower():      # decode()方法将字节数据转换成str
            return JsonResponse({"code": RETCODE.IMAGECODEERR, 'errmsg': "图形验证码输入有误!"})   # RETCODE.IMAGECODEERR定义的状态码
        
        # 随机生成短信验证码:生成6位数验证码
        sms_code = "%06d" % random.randint(0, 999999)  # 06d代表前几位可以用0补充
        print(sms_code)
        # 生成验证码的另一种写法
        # for i in range(6):
        #     sms_code += str(random.randint(0, 9))
        
        # 保存短信验证码
        redis_conn.setex('sms_%s' % mobile, constants.SMS_CODE_REDIS_EXPIRES, sms_code)   # 保存300s
        # 发送短信验证码send_message(mobile, datas, tid)中tid是模板,默认为1,datas是数据和保存时效;constants.SMS_CODE_REDIS_EXPIRES//60 双//为了得到整数
        CCP().send_message(mobile, (sms_code, constants.SMS_CODE_REDIS_EXPIRES//60), 1)
        # 响应结果
        return JsonResponse({"code": RETCODE.OK, 'errmsg': "发送短信验证码成功"})   # RETCODE.OK定义状态码