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()方法即可
准备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 %}
<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>
<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">
<form method="post" class="register_form" v-cloak @submit="on_submit">
{% csrf_token %}
<ul>
<li>
<label>用户名:label>
<input type="text" name="username" v-model="username" @blur="check_username" id="user_name">
<span class="error_tip" v-show="error_name">[[ error_name_message ]]span>
li>
<li>
<label>密码:label>
<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>
<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>
<input type="text" name="mobile" v-model="mobile" @blur="check_mobile" id="phone">
<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">
<input type="checkbox" name="allow" v-model="allow" @change="check_allow" id="allow">
<label>同意”LG商城用户使用协议“label>
<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 Reservedp>
<p>电话:010-****888 京ICP备*******8号p>
div>
div>
<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>
<script src="{% static 'js/register.js' %}">script>
body>
html>
容联云通讯网址: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)
单例类是指实例化类的时候,为了防止被占用多个内存空间,采用单例类实例化的时候就只会创建一个内存空间,防止资源浪费。
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)
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[\w-]+)/' , views.ImageCodeView.as_view()), # 图形验证码子路由
re_path(r'sms_codes/(?P1[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定义状态码