Django集成阿里云短信发送短信验证码

功能演示

核心

前端

  • 用Ajax发送验证码
  • 输入完验证码后用django的form表单验证验证码是否正确

    后端

  • 发送短信验证码
  • 验证码校验

    集成Redis

  • 使用Redis代替session缓存, 存储数据!
  • Redis集成到Django中!

步骤一

创建项目,项目名称sendsms

  • python3.6
  • Django 2.1.7

新建app 命名为 smsapp

smsapp.models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.db import models

# Create your models here.
class UserInfo(models.Model):
name = models.CharField(verbose_name='姓名',max_length=128)
telephone = models.CharField(verbose_name='电话',max_length=128)
desc = models.CharField(verbose_name='备注', max_length=128)

class Meta:
verbose_name = '信息'
verbose_name_plural = verbose_name

def __str__(self):
return self.name

注册在setting文件中,执行迁移命令,在smsapp中admin.py中注册模型。

步骤二

创建template模板, 在templates文件夹中创建index.html用作我们本项目的表单文件,在index.html中创建表单

1
2
3
4
5
6
7
8
9
10
11
<form action="" method="post">
{% csrf_token %}
<input type="text" id="name" class="phone" name="name" placeholder="姓名"> <br>
<input type="text" id="desc" class="phone" name="desc" placeholder="备注"><br>
<input type="tel" id="j_phone" class="phone" name="telephone" placeholder="手机号">
<div id="j_getVerifyCode" class="getverify-code-btn">获取手机验证码</div>
<br>
<input type="text" name="sms_captcha" id="code" class="phone" placeholder="验证码"><br>
<button type="button" id="abort_btn">中断计时</button>
<button type="submit">提交</button>
</form>

css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
body,div{
padding: 0;
margin: 0;
}
.wraper{
padding: 100px;
}
.phone{
width: 220px;
height: 40px;
box-sizing: border-box;
outline: none;
padding: 0 10px;
}
.getverify-code-btn{
display: inline-block;
width: 140px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: blue;
border: 1px solid #ccc;
box-sizing: border-box;
vertical-align: middle;
cursor: pointer;
color: #fff;
}
.getverify-code-btn.unabled{
background-color: lightblue;
color: #eee;
cursor: default;
}

js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script type="text/javascript" src="http://www.jq22.com/demo/jquery-yzsj201701191018/TimerButton.js"></script>
<script src="{% static 'AJAX.js' %}"></script>
<script type="text/javascript">
$(function (){
/*TimerButton是一个对象,该对象中有两个方法,一个是SecondCountDown,该方法的作用是精确倒计时。普通的使用setInterval倒计时会存在一定的偏差,特别是当我们切换窗口时,而SecondCountDown解决了这个误差问题(具体用法见timedown.html)。
另一个方法是verify,该方法的作用是实现按钮倒计时的功能,有了这个按钮倒计时就可以实现获取验证码倒计时的功能*/
console.log(timerButton);

var btn = $("#j_getVerifyCode");
timerButton.verify("#j_getVerifyCode", {
time: 60,//倒计时时间
event: "click",//事件触发方式
//执行条件,可以是function也可以是Boolean值,如果是函数则需返回true才会执行
condition: function () {
var phoneReg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/,
flag = phoneReg.test($("#j_phone").val());
if (!flag) {
alert("电话号码填写不正确!");
return false;
} else {
telephone = $('#j_phone').val();
xfzajax.get({
"url": "/sms_captcha",
'data': {
'telephone': telephone
},
'success': function (result) {
console.log(result);
if (result['code'] == 200) {
alert('验证码发送成功')
}

},
'fail': function (error) {
console.log(error)
}
})
}
return true;
},
unableClass: "unabled",//按钮不能使用时的class
runningText: " s后重新获取",//计时正在进行中时按钮显示的文字
timeUpText: "重新获取",//时间到了时按钮显示的文字
progress: function (time) {//计时正在进行中时的回调
btn.html(time + " s后重新获取");
/*console.log(this);//这里的this指向按钮
console.log(this.timedown);//这个timedown就是计时器*/
},
timeUp: function (time) {//计时结束时执行的回调
btn.html("重新获取");
/*console.log("时间到了!");
console.log(this);//这里的this指向按钮
*/
},
abort: function () {//中断计时
btn.html("重新获取");
/*console.log("我被中断了!");
console.log(this);//这里的this指向按钮
*/
},
eventFn: function () {//事件执行后的回调
/* console.log(this);
console.log("执行了");
console.log(this);//这里的this指向按钮

*/
}
});
//中断计时
$("#abort_btn").on("click", function (){
document.getElementById("j_getVerifyCode").timedown.abort();
});
});
</script>

在static文件夹中创建Ajax.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}

var xfzajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post': function (args) {
args['method'] = 'post';
this._ajaxSetup();
this.ajax(args);
},
'ajax': function (args) {
var success = args['success'];
args['success'] = function (result) {
if(result['code'] === 200){
if(success){
success(result);
}
}else{
var messageObject = result['message'];
if(typeof messageObject == 'string' || messageObject.constructor == String){
window.messageBox.showError(messageObject);
}else{
// {"password":['密码最大长度不能超过20为!','xxx'],"telephone":['xx','x']}
for(var key in messageObject){
var messages = messageObject[key];
var message = messages[0];
window.messageBox.showError(message);
}
}
if(success){
success(result);
}
}
};
args['fail'] = function (error) {
console.log(error);
window.messageBox.showError('服务器内部错误!');
};
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
}
};

引入到index.html

步骤三

在阿里云申请短信服务
申请链接:https://help.aliyun.com/product/44282.html?spm=5176.12212976.0.0.6b821cbeOWmU6B

申请完成后 下载python版本的SDK
下载完成后解压的项目根目录,虚拟环境进入SDK目录执行python setup.py install命令安装
在根目录下新建包为utils,在utils包中再次新建包为aliyunsdk 将下载的sdk文件夹下的aliyunsdkdysmsapi文件拷贝到阿里云aliyunsdk文件夹中,在
aliyunsdk文件夹中新建aliyunsms.py文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# -*- coding: utf-8 -*-
import sys
from aliyunsdkdysmsapi.request.v20170525 import SendSmsRequest
from aliyunsdkdysmsapi.request.v20170525 import QuerySendDetailsRequest
from aliyunsdkcore.client import AcsClient
import uuid
from aliyunsdkcore.profile import region_provider
from aliyunsdkcore.http import method_type as MT
from aliyunsdkcore.http import format_type as FT

import json
"""
短信业务调用接口示例,版本号:v20170525

Created on 2017-06-12

"""

ACCESS_KEY_ID = "填你自己的"
ACCESS_KEY_SECRET = "填你自己的"



# 注意:不要更改
REGION = "cn-hangzhou"
PRODUCT_NAME = "Dysmsapi"
DOMAIN = "dysmsapi.aliyuncs.com"

acs_client = AcsClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET, REGION)
region_provider.add_endpoint(PRODUCT_NAME, REGION, DOMAIN)


def send_sms(phone_numbers, code):
business_id = uuid.uuid1()
sign_name='填你自己的' # 签名名称
template_code='填你自己的' # 模板id
template_param = json.dumps({"code":code})
smsRequest = SendSmsRequest.SendSmsRequest()
# 申请的短信模板编码,必填
smsRequest.set_TemplateCode(template_code)

# 短信模板变量参数
if template_param is not None:
smsRequest.set_TemplateParam(template_param)

# 设置业务请求流水号,必填。
smsRequest.set_OutId(business_id)

# 短信签名
smsRequest.set_SignName(sign_name)

# 数据提交方式
# smsRequest.set_method(MT.POST)

# 数据提交格式
# smsRequest.set_accept_format(FT.JSON)

# 短信发送的号码列表,必填。
smsRequest.set_PhoneNumbers(phone_numbers)

# 调用短信发送接口,返回json
smsResponse = acs_client.do_action_with_exception(smsRequest)

# TODO 业务处理

return smsResponse


# if __name__ == '__main__':
# __business_id = uuid.uuid1()
# # print(__business_id)
# params = {
# 'code':1234
# }
# # params = u'{"name":"wqb","code":"12345678","address":"bz","phone":"13000000000"}'
# print(send_sms(__business_id, "手机号", "签名名称", "模板id", json.dumps(params)))

步骤四

安装Redis

1
pip install django-redis

在setting.py中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# redis配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
# "PASSWORD": "密码",
}
}
}
REDIS_TIMEOUT=7*24*60*60
CUBES_REDIS_TIMEOUT=60*60
NEVER_REDIS_TIMEOUT=365*24*60*60

创建验证表单form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from django import forms
from django.core import validators
from .models import UserInfo
from django.core.cache import cache

class BaseForm(forms.Form):
def get_errors(self):
errors = self.errors.get_json_data()
new_errors = {}
for key, message_dicts in errors.items():
messages = []
for message_dict in message_dicts:
message = message_dict['message']
messages.append(message)

new_errors[key] = messages
return new_errors


class InfoForm(BaseForm):
name = forms.CharField(max_length=4, error_messages={'invalid':'请输入4个字符以内的姓名', 'required':'用户名不能为空'})
telephone = forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}', message='手机号码格式不对',)], error_messages={'required':'手机号码不能为空'})
sms_captcha = forms.CharField(min_length=4, max_length=4)
desc = forms.CharField(max_length=128)

def clean(self):
cleaned_data = super(InfoForm, self).clean()

telephone = cleaned_data.get('telephone')
sms_captcha = cleaned_data.get('sms_captcha')
cached_sms_captcha = cache.get(telephone)

if cached_sms_captcha != sms_captcha:
raise forms.ValidationError('短信验证码错误!')

exists = UserInfo.objects.filter(telephone=telephone).exists()
if exists:
forms.ValidationError('该手机号码已经被注册!')

return cleaned_data

# def clean_telephone(self):
# telephone = self.cleaned_data.get('telephone')
# exists = UserInfo.objects.filter(telephone=telephone).exists()
# if exists:
# raise forms.ValidationError(message='手机号码已存在')
#
# return telephone

在views.py中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from django.shortcuts import render, HttpResponse
from .forms import InfoForm
from .models import UserInfo
from django.views import View
from utils.aliyunsdk import aliyunsms
from utils.captcha import captcha
from utils import restful
from django.core.cache import cache
# Create your views here.

# 获取验证码
def sms_captcha(request):
# /sms_captcha/?telephone=xxx
telephone = request.GET.get('telephone')
code = captcha.get_code(4, False)
cache.set(telephone, code)
result = aliyunsms.send_sms(telephone, code)
print(result)
print('发送的验证码:',code)
print('判断缓存中是否有:', cache.has_key(telephone))
print('获取Redis验证码:', cache.get(telephone))

return restful.ok()





class IndexView(View):
def get(self, request):
return render(request,'index.html')

def post(self, request):
form = InfoForm(request.POST)
if form.is_valid():
name = form.cleaned_data.get('name')
desc = form.cleaned_data.get('desc')
telephone = form.cleaned_data.get('telephone')

UserInfo.objects.create(name=name, telephone=telephone, desc=desc)


else:
return restful.params_error(message=form.get_errors())


return render(request, 'index.html', {'form':form})

url.py

1
2
3
4
5
6
7
8
9
10
from django.contrib import admin
from django.urls import path
from smsapp import views
from smsapp.views import IndexView

urlpatterns = [
path('admin/', admin.site.urls),
path('sms_captcha', views.sms_captcha),
path('', IndexView.as_view())
]

运行项目,127.0.0.1:8000

-------------本文结束感谢您的阅读-------------