0033 腾讯云短信发送

核能气质少年 提交于 2020-03-03 15:29:20

1 获取短信发送参数

1.1 登录腾讯云,获取腾讯云短信开发者ID(AppID)和开发者密码(AppKey)

1.2 设置并注册短信模板,获取模板号

1.3 在GeneralTools目录下创建一个Constants.py文件,用于保存运行常量。内容如下:

"""
    腾讯云短信相关常量设置
"""
# 云短信应用 SDK AppID
SMS_SDK_APP_ID = '14044453'

# 云短信应用 SDK AppKey
SMS_APP_KEY = 'e8dfd927c527513f6c6787289b402d'

# 注册短信模板ID
SMS_REGISTER_TEMPLATE_ID = 391568

# 短信签名,签名参数使用的是`签名内容`,而不是`签名ID`。
SMS_SIGN = '仝恒智能科技温馨提示'

# 验证access_token有效时间: s
VERIFY_ACCESS_TOKEN_EXPIRES = 300

# 短信验证码的有效期,单位秒
SMS_CODE_REDIS_EXPIRES = 300

# 短信验证码发送间隔,单位秒
SEND_SMS_CODE_INTERVAL = 60

2 获取短信接口

  获取短信接口的基本思路是:前端先发一个手机号到后端,后端根据手机号生成用户身份令牌access_token,并返回前端。

  有以下几个问题要处理:

2.1 需前端需要一个临时字段,用于前端提交手机号。

  创建临时字段,可以建一个序列化器,但这个字段只有一个手机号验证,直接用正则表达式验证就可以了,而且和数据库也没有任何关联,所以,可以直接继承GeneralTools的CustomSchema类,直接在视图中实现。

schema = CustomSchema(
    manual_fields={
        'get': [
            Field(name="mobile", required=True, location="query", schema=String(description='手机号')),
        ],
    }
)

2.2 手机号验证

  手机号验证的正则表达式调用GeneralTools下的Vierifications.py文件中的mobileVerify方法,返回True表示手机号正确,否则表示手机号格式错误。

2.3 生成身份令牌

  这一步最关键的是生成身份令牌。身份令牌的作用是,代表用户身份,先生成用户身份令牌,用户再携带这个身份令牌来申请发送短信。这样处理的原因控制前端重复提交的问题。

  用JWT生成临时身份令牌。导入函数TimedJSONWebSignatureSerializer,进行序列生成,该函数有三个参数:

  secret_key:密钥,一般采用settings.py中的SECRET_KEY

  salt:盐值,也是常量,可以设置在常量文件GeneralTools/Constents.py中设置为常量SALT。

  expires_in:超时的时间间隔,以秒为单位,在GeneralTools/Constents.py文件中设置为常量VERIFY_ACCESS_TOKEN_EXPIRES。

from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer


tjwserializer = TJWSSerializer(
    settings.SECRET_KEY, 
    Constents.SALT,
    Constents.VERIFY_ACCESS_TOKEN_EXPIRES
)

  TimedJSONWebSignatureSerializer返回一个序列生成器,然后用序列生成器的dumps方法对手机号进行编码,从而生成了acess_token身份令牌。

2.4 完成编码

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from TongHeng2 import settings
from GeneralTools.CustomSchema import CustomSchema
from GeneralTools import Constents
from coreapi import Field
from coreschema import String
from itsdangerous import TimedJSONWebSignatureSerializer as TJWSSerializer
from GeneralTools.Verifications import mobileVerify


class GetSMSToken(APIView):
    """
    用户注册第一步:获取短信接口access token
    """

    schema = CustomSchema(
        manual_fields={
            'get': [
                Field(name="mobile", required=True, location="query", schema=String(description='手机号')),
            ],
        }
    )

    @classmethod
    def get(cls, request):
        """
        【功能描述】根据手机号生成验证码身份令牌</br>
        【返回参数】</br>
                mobile:手机号</br>
                access_token:短信身份令牌</br>
        """

        mobile = request.query_params.get('mobile')
        if not mobile:
            return Response(data={'message': '缺少mobile参数'}, status=status.HTTP_400_BAD_REQUEST)

        # 验证手机号码格式
        if not mobileVerify(mobile):
            return Response(data={'message': '手机号格式错误'}, status=status.HTTP_400_BAD_REQUEST)
        # 创建itsdangerous模型的转换工具
        tjwserializer = TJWSSerializer(
            secret_key=settings.SECRET_KEY,
            salt=Constents.SALT,
            expires_in=Constents.VERIFY_ACCESS_TOKEN_EXPIRES
        )
        access_token = tjwserializer.dumps({'mobile': mobile})  # bytes
        access_token = access_token.decode()  # str

        data = {
            'mobile': mobile,
            'access_token': access_token
        }
        return Response(data=data, status=status.HTTP_200_OK)

3 发送验证码

  发送验证码分为三步,第一步是确定这个验证码该不该发,第二步是如果该发,就执行发送,第三步是发送后,把发送信息返回到前端。

  为此,把第二步独立出来写个函数。而第一步和第三步写成一个视图。

3.1 发送验证码

  发送验证码有几个步骤:

  生成验证码:调用randint随机数生成器,生成6位验证码

  把生成的验证码存储到redis数据库中。

  获取短信云平台接口

  发送短信。

from GeneralTools.Redis import get_redis_connection
import random
from GeneralTools import Constents
from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
import logging

logger = logging.getLogger('tongheng2')


def send_sms_code(mobile, param):
    """
    发送短信验证码
    :return: 错误码,0表示成功
    """

    sms_code = '%06d' % random.randint(0, 999999)
    # 缓存验证码到Redis
    conn = get_redis_connection("sms_codes")
    # 使用Redis的pipeline管道一次执行多个命令
    pl = conn.pipeline()
    pl.setex("sms_%s" % mobile, Constents.SMS_CODE_REDIS_EXPIRES, sms_code)
    pl.setex("send_flag_%s" % mobile, Constents.SEND_SMS_CODE_INTERVAL, 1)
    # 让管道执行命令
    pl.execute()

    sender = SmsSingleSender(Constents.SMS_SDK_APP_ID, Constents.SMS_APP_KEY)
    params = [param, sms_code, '5', mobile[7:]]
    # 【仝恒智能科技温馨提示】您正在手机注册,验证码为:1314,请于5分钟内填写。尾号(6960)验证

    try:
        data = sender.send_with_param(
            nationcode=86,  # 国别
            phone_number=mobile, # 手机号
            template_id=Constents.SMS_REGISTER_TEMPLATE_ID, # 短信模板ID
            params=params, # 发送参数
            sign=Constents.SMS_SIGN, # 短信签名
            extend="",
            ext=""
        )
        logger.error("sms result:%s" % data)
    except HTTPError as e:
        logger.error(e)
        return 500
    except Exception as e:
        logger.error(e)
        return 500

    # {'result': 0, 'errmsg': 'OK', 'ext': '', 'sid': '2106:369348897515820777343026696', 'fee': 1}
    # result 是 number 错误码,0表示成功(计费依据),非0表示失败,更多详情请参见 错误码
    # errmsg 是 string 错误消息,result 非0时的具体错误信息
    # ext 否 string 用户的 session 内容,腾讯 server 回包中会原样返回
    # fee 否 number 短信计费的条数,计费规则请参考 国内短信内容长度计算规则 或 国际/港澳台短信内容长度计算规则
    # sid 否 string 本次发送标识 ID,标识一次短信下发记录
    return data['result']

3.2 发送验证码视图

  该视图需要以下几个功能:

  提供前端输入验证码的临时字段,类似生成令牌,直接在视图中生成临时字段。

  获取并解析前端提交的access_token,得到mobile

  从redis数据库中读取验证码,如果存在,则表示发送太频繁,如果不存在,则继续执行

  调用发送验证码函数,发送验证码

  把验证码发送信息返回前端。

from itsdangerous import BadData


class SendSMSCode(APIView):
    schema = CustomSchema(
        manual_fields={
            'get': [
                Field(name="access_token", required=True, location="query", schema=String(description='access token')),
            ],
        }
    )

    @classmethod
    def get(cls, request):
        """
        用户注册第二步:发送短信验证码
        GET /OrgsAndUsers/user/register/sms/?access_token=***
        """

        access_token = request.query_params.get('access_token')
        if not access_token:
            return Response(data={'message': '缺少access_token参数'}, status=status.HTTP_400_BAD_REQUEST)

        # 校验access_token
        tjwserializer = TJWSSerializer(
            secret_key=settings.SECRET_KEY,
            salt=Constents.SALT,
            expires_in=Constents.VERIFY_ACCESS_TOKEN_EXPIRES
        )
        try:
            data = tjwserializer.loads(access_token)
            mobile = data['mobile']

            # 60秒之内只允许发送一次验证码
            # send_flag_<mobile> = 1,由Redis维护60秒有效期
            conn = get_redis_connection("sms_codes")
            send_flag = conn.get("send_flag_%s" % mobile)
            if send_flag:
                return Response(data={'message': '发送短信次数过于频繁'}, status=status.HTTP_400_BAD_REQUEST)

            # 发送短信验证码
            param = '手机注册'
            code = send_sms_code(mobile, param)
            if code == 0:
                data = {'message': 'ok'}
            else:
                return Response(data={'message': '发送短信验失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        except BadData as e:
            logger.error(e)
            return Response(data={'message': '无效access_token参数'}, status=status.HTTP_400_BAD_REQUEST)

        return Response(data=data, status=status.HTTP_200_OK) 

4 配置Organizations分路由,同时在工程总路由中增加此分路由。

from django.urls import path
from .views.SendSMS import GetSMSToken,SendSMSCode
urlpatterns = [
    path('GetSMSToken/', GetSMSToken.as_view()),
    path('SendSMSCode/', SendSMSCode.as_view()),
]

5 运行工程,进入接口文档,先获取token,再发送验证码。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!