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

1、图形验证码后端逻辑

准备captcha扩展包

captcha扩展包用于后端生成图形验证码,captcha扩展包可以从网上百度找到相关代码和文件,fonts是支持的字体文件,包含有actionj.ttf、Arial.ttf、Georgia.ttf。
在这里插入图片描述
生成验证码文件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库
在这里插入图片描述

准备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数据库中调出数据,查看是否一致。
在这里插入图片描述

图形验证码前端逻辑

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不具备发送短信的功能,所以我们借助第三方的容联云通讯短信平台来帮助我们发送短信验证码。

容联云通讯短信平台介绍

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

在这里插入图片描述
容联云通讯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
在这里插入图片描述

封装发送短信单例类

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

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

项目创建短信验证码的文件目录如下
在这里插入图片描述

实例如下:发送短信验证码文件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、短信验证码后端逻辑

短信验证码接口设计

在这里插入图片描述

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

在这里插入图片描述

响应结果:JSON

在这里插入图片描述

短信验证码接口定义

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定义状态码


### 使用 Django 实现短信验证码后端逻辑 #### 创建视图函数处理验证码请求 为了实现短信验证码的功能,在 `views.py` 中定义两个主要的视图函数:一个是用来生成并发送验证码;另一个是用来验证用户输入的验证码。 ```python from django.http import JsonResponse import random import redis from django.conf import settings def generate_code(): """生成六位随机数作为验证码""" return str(random.randint(100000, 999999)) def send_sms(request): phone_number = request.POST.get(&#39;phone&#39;) if not phone_number: return JsonResponse({&#39;status&#39;: &#39;error&#39;, &#39;message&#39;: &#39;缺少手机号&#39;}, status=400) code = generate_code() # 这里应该调用实际的SMS服务提供商API来发送短信 # 此处仅模拟发送过程 r = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0) r.set(phone_number, code, ex=300) # 设置过期时间为5分钟 return JsonResponse({ &#39;status&#39;: &#39;success&#39;, &#39;message&#39;: f&#39;SMS sent to {phone_number}&#39; }) def verify_code(request): received_phone = request.POST.get(&#39;phone&#39;) received_code = request.POST.get(&#39;code&#39;) if not all([received_phone, received_code]): return JsonResponse({&#39;status&#39;: &#39;error&#39;, &#39;message&#39;: &#39;参数不全&#39;}, status=400) r = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0) stored_code = r.get(received_phone).decode() if r.exists(received_phone) else None if stored_code and stored_code == received_code: return JsonResponse({&#39;status&#39;: &#39;success&#39;}) else: return JsonResponse({&#39;status&#39;: &#39;error&#39;, &#39;message&#39;: &#39;验证码错误或已失效&#39;}, status=400) ``` 上述代码展示了如何通过 POST 请求接收电话号码,生成验证码并通过 Redis 存储它[^1]。当接收到用户的验证码提交时,则会再次访问 Redis 来比较存储的值和用户提供的值是否匹配。 #### URL 路由配置 为了让客户端能够正确地向服务器发起请求,还需要设置相应的路由规则: ```python urlpatterns = [ ... path(&#39;api/send-sms/&#39;, views.send_sms), path(&#39;api/verify-code/&#39;, views.verify_code), ... ] ``` 此部分确保了 `/send-sms/` 和 `/verify-code/` 接口可以被外部应用所调用[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值