本篇以注册或者登陆的单次单人发送场景为例,使用Python进行短信接口的调试,从而实现DRF应用的用户注册功能,至于群发或者语音等短信的场景,合此类似,本篇不做完整介绍。
一、申请腾讯云签名并创建模板
首先在腾讯云服务中找到短信服务,点击开通,填写基本信息,最后生成如下:
其中SDK AppID和App key是在https请求发送是的必要参数。
然后需要创建一个签名,签名相当于是短信服务的标题,提交后大概审核1-2个小时就可以了。我这里申请的是小程序的服务,所以只需要提供小程序的后台截图就可以了。
要注意的是签名的内容必须和小程序或者公众号的名称一样,不然会审核不通过。
创建成功以后就会显示已通过,并且创建签名的ID
同理再创建短信正文的模板:
我们可以看到在正文的内容当中,我设置了两个变量{1}和{2},所以在接下来的传参过程中,仅正文内容的传参就需要两个,另外这里要注意正文模板的ID号,待会在发送请求的过程中也是需要的。
二、创建并封装Python短信请求文件tengxun.py
首先我们还是在我们的项目中创建一个tengxun.py文件用于封装短信发送的代码。
以我的项目为例,我使用的DRF,将这个文件放在了app目录下的utils文件夹中。
为了方便进行请求,我们使用的是腾讯短信的SDK,对于Python而言,可以直接使用pip安装:
pip install qcloudsms_py
如下图所示安装成功:
对于单条短信的发送有其特定的发送参数,至于其他类型的发送,大家可以参考腾讯官方的SDK文档,托管到了Github上,所以我直接给出Github的地址,下发都是有说明的:
如下创建tengxun.py文件:
#!/usr/bin/env python
# encoding: utf-8
'''
@author: ZhonghangAlex
@contact: [email protected]
@software: pycharm
@file: tengxun.py
@time: 2018/11/28 12:43
@desc:
'''
from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
class TengXun(object):
def __init__(self, appkey):
self.appkey = appkey
def send_sms(self, code, mobile):
# 短信应用SDK AppID
appid = 1400xx3806 # SDK AppID是1400开头
# 短信应用SDK AppKey
appkey = self.appkey
# 需要发送短信的手机号码
phone_numbers = "{mobile}".format(mobile=mobile)
# 短信模板ID,需要在短信应用中申请
template_id = 2393xx # NOTE: 这里的模板ID`7839`只是一个示例,真实的模板ID需要在短信控制台中申请
# 签名
sms_sign = "Enet车联" # NOTE: 这里的签名"腾讯云"只是一个示例,真实的签名需要在短信控制台中申请,另外签名参数使用的是`签名内容`,而不是`签名ID`
ssender = SmsSingleSender(appid, appkey)
params = ["{code}".format(code=code),"3"] # 当模板没有参数时,`params = []`
try:
result = ssender.send_with_param(86, phone_numbers, template_id, params, sign=sms_sign, extend="", ext="") # 签名参数未提供或者为空时,会使用默认签名发送短信
except HTTPError as e:
print(e)
except Exception as e:
print(e)
print(result)
return result
# 脚本自测
if __name__ == "__main__":
teng_xun = TengXun("a067bbcc3xx1c4b924xxc04bef7dc926")
teng_xun.send_sms("2018", "15927093114")
上面的很多参数,我都使用了xx做了替换,大家在写的过程中填写自己的相关参数就好。
可以看到在代码中我们封装了一个Tengxun类用于接收参数以及进行短信的发送。
代码下方的三行是脚本自测,先实例化,传入appkey,2018是要生产的验证码,15927093114是要发送的手机号。
这个时候直接运行这个文件就可以完成对短信的发送。
可以看到返回的参数:
说明发送成功,此时手机上也接收到了短信:
如果你仅仅是想使用一个脚本进行短信发送那么就完成了,接下来我会结合DRF的相关操作,实现一个完整的用户注册功能。
三、DRF实现用户注册
为了代码的标准化,我们将一些全局的参数放在settings中:
# 手机号码正则表达式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
# 腾讯短信设置
APIKEY = "a067bbcxx1f1c4b924xxc04bef7dc926"
由于注册功能面向的是用户,所以我们就在user这个app中进行实现:
首先我们要创建短信验证的数据库表,在models.py中编辑如下:
from datetime import datetime
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class VerifyCode(models.Model):
"""
短信验证
"""
code = models.CharField(max_length=10, verbose_name="验证码")
mobile = models.CharField(max_length=11, verbose_name="电话")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "短信验证码"
verbose_name_plural = verbose_name
def __str__(self):
return self.code
接着在serserializer.py中设置序列化格式,并进行检验:
#!/usr/bin/env python
# encoding: utf-8
'''
@author: ZhonghangAlex
@contact: [email protected]
@software: pycharm
@file: serializer.py
@time: 2018/11/28 13:15
@desc:
'''
import re
from rest_framework import serializers
from django.contrib.auth import get_user_model
from datetime import datetime
from datetime import timedelta
from .models import VerifyCode
from VueShop.settings import REGEX_MOBILE
User = get_user_model()
class SmsSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11)
def validate_mobile(self, mobile):
"""
验证手机号码
:param data:
:return:
"""
# 手机是否注册
if User.objects.filter(mobile=mobile).count() != 0:
raise serializers.ValidationError("用户已经存在")
# 验证手机号码是否合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError("手机号码非法")
# 验证发送频率
one_min_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
if VerifyCode.objects.filter(add_time__gt=one_min_ago, mobile=mobile).count():
raise serializers.ValidationError("距离上一次发送未超过60s")
return mobile
接下来就要构建views.py的代码,我们在Tengxun.py文件中封装类TengXun这个类,所以我们可以在view中进行调用,其中用于单条短信发送的class如下(采用CBV方式):
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from random import choice
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from VueShop.settings import APIKEY
from .serializer import SmsSerializer
from utils.tengxun import TengXun
from .models import VerifyCode
class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
"""
发送短信验证码
"""
serializer_class = SmsSerializer
def generate_code(self):
"""
生成四位数字的验证码
:return:
"""
seeds = "1234567890"
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return "".join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
mobile = serializer.validated_data["mobile"]
teng_xun = TengXun(APIKEY)
code = self.generate_code()
sms_status = teng_xun.send_sms(code=code, mobile=mobile)
if sms_status["result"] != 0:
return Response({
"mobile":sms_status["errmsg"]
}, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, mobile=mobile)
code_record.save()
return Response({
"mobile":mobile
}, status=status.HTTP_201_CREATED)
然后在url配置中加入code配置:
# 配置code的url
router.register(r'code', SmsCodeViewset, base_name="code")
就完成了DRF应用对短信接口的调用,生成了如下的API:
最后要进行的就是注册表单的验证
在serializer.py中添加类
class UserRegSerializer(serializers.ModelSerializer):
code = serializers.CharField(required=True, write_only=True, allow_blank=False, max_length=4, min_length=4,label="验证码",
error_messages={
"blank":"请输入验证码",
"required":"请输入验证码",
"max_length":"验证码格式错误",
"min_length":"验证码格式错误"
}, help_text="验证码")
username = serializers.CharField(required=True, allow_blank=False, label="用户名", validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
password = serializers.CharField(
write_only=True,
style={'input_type':'password'},
label="密码"
)
# 二次加密密码,存储数据库时生成不能反解的密码
def create(self, validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
def validate_code(self, code):
# 为什么不使用get方法
# 因为使用get方法需要做异常的捕获
# try:
# verify_records = VerifyCode.objects.get(mobile=self.initial_data["userame"], code=code)
# except VerifyCode.DoesNotExist as e:
# return e
# except VerifyCode.MultipleObjectsReturned as e:
# return e
verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
if verify_records:
last_records = verify_records[0]
three_min_ago = datetime.now() - timedelta(hours=0, minutes=3, seconds=0)
if three_min_ago > last_records.add_time:
raise serializers.ValidationError("验证码过期")
if last_records.code != code:
raise serializers.ValidationError("验证码错误")
else:
raise serializers.ValidationError("验证码错误")
def validate(self, attrs):
attrs["mobile"] = attrs["username"]
del attrs["code"]
return attrs
class Meta:
model = User
fields = ("username", "code", "mobile", "password")
在View中添加视图类,并且通过重写create和perform_create实现token定制化,实现注册后自动登录:
class UserViewSet(CreateModelMixin, viewsets.GenericViewSet):
"""
用户
"""
serializer_class = UserRegSerializer
queryset = User.objects.all()
# 重新定义create函数 实现注册后自动登录 及定制化Token
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
re_dict = serializer.data
payload = jwt_payload_handler(user)
re_dict["token"] = jwt_encode_handler(payload)
re_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
return serializer.save()
同样在urls.py中进行路由的配置:
# 配置user的url
router.register(r'user', UserViewSet, base_name="users")
这样以来就完整地实现了通过短信注册的逻辑。