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