天天看點

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定義狀态碼