使用第三方平台发送短信验证码
云通讯
七、发送短信功能
1.分析
请求方法**:POST
url定义:/sms_code/
请求参数:url路径参数
参数 | 类型 | 是否必传 | 描述 |
---|---|---|---|
mobile | 字符串 | 是 | 用户输入 |
image___code__-id | uuid | 是 | js生成的图片uuid号 |
text | 字符串 | 是 | 用户输入的图片验证码文本 |
2.后端代码实现
分析
1,获取参数
2,验证参数
3,发送短信
4,保存短信验证码
5,返回给前端
send mobile sms code
POST /sms_code/
- 检查图片验证码是否正确
- 检查是否在60s内有发送记录
- 生成短信验证码
- 保存短信验证码与发送记录
- 发送短信
将云通讯接口放到utils文件夹下:
把这些换成自己的云通讯账号:
测试的时候要添加测试号码,只有添加的号码才能发送短信
测试是否能发送短信:
在sms.py文件下修改发送手机号为自己的测试手机号
运行sms.py文件
1、后台视图 views.py
class SMSCodeView(View):
"""短信验证码"""
def post(self, request):
"""
:param reqeust: 请求对象
:param mobile: 手机号
:return: JSON
"""
# 接收参数
json_str = request.body
dict_data = json.loads(json_str)
image_code_client = dict_data.get('text')
uuid = dict_data.get('image_code_id')
mobile = dict_data.get('mobile')
# 校验参数
if not all([image_code_client, uuid,mobile]):
return res_json(errno=Code.PARAMERR,errmsg='参数错误')
# 创建连接到redis的对象
redis_conn = get_redis_connection('verify_codes')
# 提取数据库的图形验证码
image_code_server = redis_conn.get('img_%s' % uuid)
if image_code_server is None:
# 图形验证码过期或者不存在
return res_json(errno=Code.PARAMERR,errmsg='参数错误')
# 删除图形验证码,避免恶意测试图形验证码
try:
redis_conn.delete('img_%s' % uuid)
except Exception as e:
logger.error(e)
# 对比图形验证码
image_code_server = image_code_server.decode() # bytes转字符串
if image_code_client.lower() != image_code_server.lower(): # 转小写后比较
return res_json(errno=Code.PARAMERR, errmsg= '输入图形验证码有误')
# 生成短信验证码:生成6位数验证码
sms_code = '%06d' % random.randint(0, 999999)
logger.info(sms_code)
# 限定频繁发送验证码
send_flag = redis_conn.get('send_flag_%s' % mobile)
if send_flag:
return res_json(errno=Code.DATAEXIST, errmsg='发送短信过于频繁')
redis_conn.setex('sms_%s' % mobile, 300, sms_code)
# 重新写入send_flag
redis_conn.setex('send_flag_%s' % mobile, 500, 1)
# 执行请求
# 发送短信
logger.info('短信验证码: {}'.format(sms_code))
logging.info('发送短信正常[mobile:%s sms_num:%s]' % (mobile,sms_code) )
# 发送短信验证码
# ccp = CCP()
# ccp.send_template_sms(mobile, [sms_code, 5], 1)
# 响应结果
return res_json(errmsg='短信验证码发送成功')
2、url路径 urls.py
from django.urls import path,re_path
from . import views
app_name = 'verifications'
urlpatterns = [
path('image_code/<uuid:image_code>/',views.ImageView.as_view(),name='image_code'),
re_path('username/(?P<username>\w{5,20})/',views.UsernameView.as_view(),name='username'),
re_path('mobiles/(?P<mobile>1[3-9]\d{9})/', views.MobileView.as_view(), name='mobile'),
path('sms_code/', views.SMSCodeView.as_view(), name='sms_code'),
]
3、前端代码 js
$(function () {
let $username = $('#user_name'); //获取用户名
let $img = $(".form-item .captcha-graph-img img"); // 获取图像
let sImageCodeId = ""; //uuid
let $mobile = $('#mobile'); // 选择id为mobile的网页元素,需要定义一个id为mobile手机号
// 校验功能
// 定义一些状态变量
let isUsernameReady = false,
isPasswordReady = false,
isMobileReady = false;
send_flag = true; // 短信标记
// let $imgCodeText = $('#input_captcha');
// console.log(img);
genreate();
$img.click(genreate);
function genreate() {
sImageCodeId = generateUUID();
let imageCodeUrl = '/image_code/' + sImageCodeId + '/';
$img.attr('src', imageCodeUrl)
}
// 生成图片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;
}
// 2、用户名验证逻辑
// blur,触发失去焦点事件
$username.blur(function () {
fn_check_username();
});
// 判断用户名是否已经注册
function fn_check_username() {
// 校验用户名
isUsernameReady = false;
let sUsername = $username.val(); // 获取用户名字符串
if (sUsername === "") {
message.showError('用户名不能为空!');
return
}
if (!(/^\w{5,20}$/).test(sUsername)) {
message.showError('请输入5-20个字符的用户名');
return
}
// 发送ajax请求,去后端查询用户名是否存在
$.ajax({
url: '/username/' + sUsername + '/',
type: 'GET',
dataType: 'json',
})
.done(function (res) {
if (res.data.count === 1) {
message.showError(res.data.username + '已注册,请重新输入!');
} else {
message.showSuccess(res.data.username + '能正常使用!');
isUsernameReady = true
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
//3、手机号验证
// 手机号校验,光标离开手机号输入框就校验
$mobile.blur(fn_check_mobile);
// 判断手机号是否注册
function fn_check_mobile() {
//检验手机号
isMobileReady = false;
let sMobile = $mobile.val(); // 获取用户输入的手机号码字符串
if (sMobile === "") {
message.showError('手机号不能为空!');
return
}
if (!(/^1[345789]\d{9}$/).test(sMobile)) {
message.showError('手机号码格式不正确,请重新输入!');
return
}
$.ajax({
url: '/mobiles/' + sMobile + '/',
type: 'GET',
dataType: 'json',
async: false
})
.done(function (res) {
if (res.data.count === 1) {
message.showError(res.data.mobile + '已注册,请重新输入!');
} else {
message.showSuccess(res.data.mobile + '能正常使用!');
isMobileReady = true;
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
// 4、发送短信验证码逻辑
let $smsCodeBtn = $('.form-item .sms-captcha'); // 获取短信验证码按钮元素,需要定义一个id为input_smscode
let $imgCodeText = $('#input_captcha'); // 获取用户输入的图片验证码元素,需要定义一个id为input_captcha
$smsCodeBtn.click(function () {
// 判断手机号是否输入
if(send_flag){
send_flag = false;
// 判断手机号码是否准备好
if(!isMobileReady){
fn_check_mobile();
return
}
// 13535353535
// 判断用户是否输入图片验证码
let text = $imgCodeText.val(); // 获取用户输入的图片验证码文本
if (!text) {
message.showError('请填写验证码!');
return
}
if (!sImageCodeId) {
message.showError('图片UUID为空');
return
}
// 正常
let SdataParams = {
"mobile": $mobile.val(), // 获取用户输入的手机号
"text": text, // 获取用户输入的图片验证码文本
"image_code_id": sImageCodeId // 获取图片UUID
};
// 向后端发送请求
$.ajax({
// 请求地址
url: "/sms_code/",
// 请求方式
type: "POST",
// 向后端发送csrf token
data: JSON.stringify(SdataParams),
// data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
.done(function (res) {
console.log(res);
if (res.errno === "0") {
// 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
message.showSuccess('短信验证码发送成功');
let num = 60;
// 设置一个计时器
let t = setInterval(function () {
if (num === 1) {
// 如果计时器到最后, 清除计时器对象
clearInterval(t);
// 将点击获取验证码的按钮展示的文本恢复成原始文本
$smsCodeBtn.html("获取验证码");
send_flag = true;
} else {
num -= 1;
// 展示倒计时信息
$smsCodeBtn.html(num + "秒");
}
}, 1000);
}
else {
message.showError(res.errmsg);
send_flag = true;
}
})
.fail(function(){
message.showError('服务器超时,请重试!');
});
}
});
});
关于csrf_token的问题:
我前面是把csrf_token的中间件给注释了
我们可以看到在中间件中有这样一个函数
class CsrfViewMiddleware(MiddlewareMixin):
def _get_token(self, request):
if settings.CSRF_USE_SESSIONS:
try:
return request.session.get(CSRF_SESSION_KEY)
except AttributeError:
raise ImproperlyConfigured(
'CSRF_USE_SESSIONS is enabled, but request.session is not '
'set. SessionMiddleware must appear before CsrfViewMiddleware '
'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '')
)
else:
try:
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
except KeyError:
return None
csrf_token = _sanitize_token(cookie_token)
if csrf_token != cookie_token:
# Cookie token needed to be replaced;
# the cookie needs to be reset.
request.csrf_cookie_needs_reset = True
return csrf_token
#在views.py之前进行
def process_request(self, request):
csrf_token = self._get_token(request)
if csrf_token is not None:
# Use same token next time.
request.META['CSRF_COOKIE'] = csrf_token
我们看到在process_request中会拿到get_token得到前端传来的cookie中token的值,与post请求csrf_token进行比较,因此post等修改操作的请求我们必须前端传来token,不然就会报错,生成token的方式有很多 比如配合form的{%csrf_token%} 也可以设置全局的token值
这里使用中间件生成token值,作用与所有视图和模板
生成token的方式很多,本项目使用中间件生成token:
在utils文件下创建一个middlewares.py文件
from django.middleware.csrf import get_token
from django.utils.deprecation import MiddlewareMixin
class Middleware(MiddlewareMixin):
def process_request(self,request):
get_token(request)
别忘了要到设置文件中添加中间件
js:
还要修改ajax:(部分)请求头中加入
$.ajax({
// 请求地址
url: "/sms_code/",
// 请求方式
type: "POST",
// 向后端发送csrf token
headers: {
// 根据后端开启的CSRFProtect保护,cookie字段名固定为X-CSRFToken
"X-CSRFToken": getCookie("csrftoken")
},
data: JSON.stringify(SdataParams),
// data: JSON.stringify(SdataParams),
// 请求内容的数据类型(前端发给后端的格式)
contentType: "application/json; charset=utf-8",
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
})
#得到cookie的值
// get cookie using jQuery
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
let cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let 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;
}
可以看到为每个页面都设置了cookie值
总结:
1、使用第三方发送短信
2、图形验证码的校验、redis手机验证码保存300秒,在60秒内不能重复发送短信,60秒后发送就会覆盖原来的键
3、csrf_token的问题