随笔 - 77  文章 - 0  评论 - 0  阅读 - 21363 

在注册功能中集成短信验证码功能

接下来,我们把注册过程中一些注册信息(例如:短信验证码)和session缓存到redis数据库中。

安装django-redis。

pip install django-redis

在settings/dev.py配置中添加一下代码:

# 设置redis缓存
CACHES = {
   # 默认缓存
   "default": {
       "BACKEND": "django_redis.cache.RedisCache",
       # 项目上线时,需要调整这里的路径
       "LOCATION": "redis://127.0.0.1:6379/0",

       "OPTIONS": {
           "CLIENT_CLASS": "django_redis.client.DefaultClient",
      }
  },
   # 提供给xadmin或者admin的session存储
   "session": {
       "BACKEND": "django_redis.cache.RedisCache",
       "LOCATION": "redis://127.0.0.1:6379/1",
       "OPTIONS": {
           "CLIENT_CLASS": "django_redis.client.DefaultClient",
      }
  },
   # 提供存储短信验证码
   "sms_code":{
       "BACKEND": "django_redis.cache.RedisCache",
       "LOCATION": "redis://127.0.0.1:6379/2",
       "OPTIONS": {
           "CLIENT_CLASS": "django_redis.client.DefaultClient",
      }
  }
}

# 设置xadmin用户登录时,登录信息session保存到redis
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"

关于django-redis 的使用,说明文档可见http://django-redis-chs.readthedocs.io/zh_CN/latest/

django-redis提供了get_redis_connection的方法,通过调用get_redis_connection方法传递redis的配置名称可获取到redis的连接对象,通过redis连接对象可以执行redis命令

https://redis-py.readthedocs.io/en/latest/

使用范例:

from django_redis import get_redis_connection
// 链接redis数据库
redis_conn = get_redis_connection("default")

 

 

使用云通讯发送短信

在登录后的平台上面获取一下信息:

ACCOUNT SID:8aaf0708697b6beb01699f4442911776
AUTH TOKEN : b4dea244f43a4e0f90e557f0a99c70fa
AppID(默认):8aaf0708697b6beb01699f4442e3177c
Rest URL(生产): app.cloopen.com:8883         [项目上线时使用真实短信发送服务器]
Rest URL(开发): sandboxapp.cloopen.com:8883 [项目开发时使用沙箱短信发送服务器]

找到sdkdemo进行下载

 

在开发过程中,为了节约发送短信的成本,可以把自己的或者同事的手机加入到测试号码中.

 

后端生成短信验证码

from renranapi.libs.yuntongxun.sms import CCP
import random
from django_redis import get_redis_connection
from renranapi.settings import constants
class SMSCodeAPIView(APIView):
   """
  短信验证码
  """
   def get(self, request, mobile):
       """
      短信验证码
      """
       # 生成短信验证码
       sms_code = "%06d" % random.randint(0, 999999)

       # 保存短信验证码与发送记录
       redis_conn = get_redis_connection('sms_code')
       # 使用redis提供的管道操作可以一次性执行多条redis命令
       pl = redis_conn.pipeline()
       pl.multi()
       pl.setex("sms_%s" % mobile, constants.SMS_CODE_EXPIRE, sms_code)   # 设置短信有效期为300s
       pl.setex("sms_time_%s" % mobile, constants.SMS_CODE_INTERVAL, "_")    # 设置发送短信间隔为60s
       pl.execute()

       # 发送短信验证码
       ccp = CCP()
       ccp.send_template_sms(mobile, [sms_code, ], constants.SMS_CODE_EXPIRE//60, settings.SMS.SMS_TEMPLATE_ID)

       return Response({"message": "OK"}, status.HTTP_200_OK)

settings/constants.py代码:

# 短信有效使用时间
SMS_CODE_EXPIRE = 300
# 短信发送冷却时间
SMS_CODE_INTERVAL = 60

 

客户端发送注册信息和发送短信

<template>
<div class="sign">
   <div class="logo"><a href="/"><img src="/static/image/nav-logo.png" alt="Logo"></a></div>
   <div class="main">
     <h4 class="title">
       <div class="normal-title">
         <router-link to="/user/login">登录</router-link>
         <b</b>
         <router-link id="js-sign-up-btn" class="active" to="/user/register">注册</router-link>
       </div>
     </h4>

     <div class="js-sign-up-container">
       <form class="new_user" id="new_user" action="" accept-charset="UTF-8" method="post">
         <div class="input-prepend restyle">
             <input placeholder="你的昵称" type="text" value="" v-model="nickname" id="user_nickname">
           <i class="iconfont ic-user"></i>
         </div>
           <div class="input-prepend restyle no-radius js-normal">
               <input placeholder="手机号" type="tel" v-model="mobile" id="user_mobile_number">
             <i class="iconfont ic-phonenumber"></i>
           </div>
         <div class="input-prepend restyle no-radius security-up-code js-security-number" v-if="is_show_sms_code">
             <input type="text" v-model="sms_code" id="sms_code" placeholder="手机验证码">
           <i class="iconfont ic-verify"></i>
           <a tabindex="-1" class="btn-up-resend js-send-code-button"  href="javascript:void(0);" id="send_code" @click.prevent="send_sms">{{sms_code_text}}</a>
         </div>
         <input type="hidden" name="security_number" id="security_number">
         <div class="input-prepend">
           <input placeholder="设置密码" type="password" v-model="password" id="user_password">
           <i class="iconfont ic-password"></i>
         </div>
         <input type="submit" name="commit" value="注册" class="sign-up-button" id="sign_up_btn" @click.prevent="registerHandler">
         <p class="sign-up-msg">点击 “注册” 即表示您同意并愿意遵守荏苒<br> <a target="_blank" href="">用户协议</a> 和 <a target="_blank" href="">隐私政策</a> 。</p>
       </form>
       <!-- 更多注册方式 -->
       <div class="more-sign">
         <h6>社交帐号直接注册</h6>
           <ul>
           <li><a id="weixin" class="weixin" target="_blank" href=""><i class="iconfont ic-wechat"></i></a></li>
           <li><a id="qq" class="qq" target="_blank" href=""><i class="iconfont ic-qq_connect"></i></a></li>
         </ul>

       </div>
     </div>

   </div>
 </div>
</template>

<script>
   export default {
       name: "Register",
       data(){
         return {
           nickname:"",
           mobile:"",
           sms_code:"",
           password:"",
           sms_code_text:"发送验证码",
           is_show_sms_code:false,
        }
      },
       watch: {
         mobile() {
           if (/^1[3-9]\d{9}$/.test(this.mobile)) {
             this.is_show_sms_code = true;
          } else {
             this.is_show_sms_code = false;
          }
        }
      },
       methods:{
         registerHandler(){
           // todo 数据验证

           // 注册处理
           this.$axios.post(`${this.$settings.Host}/users/`,{
               mobile: this.mobile,
               nickname: this.nickname,
               password: this.password,
               sms_code: this.sms_code,
          }).then(response=> {
             sessionStorage.user_token = response.data.token;
             sessionStorage.user_id = response.data.id;
             sessionStorage.user_name = response.data.username;
             sessionStorage.user_avatar = response.data.avatar;
             sessionStorage.user_nickname = response.data.nickname;

             this.$confirm('注册成功, 欢迎加入荏苒!', '提示', {
               confirmButtonText: '跳转到首页',
               cancelButtonText: '跳转上一页',
               type: 'success'
            }).then(() => {
               this.$router.push("/");
            }).catch(() => {
               this.$router.go(-1);
            });

          }).catch(error=>{
               this.$message.error("用户注册失败!");
          })
        },
         send_sms(){
               if( !/1[1-9]{2}\d{8}/.test(this.mobile) ){
                 return false;
              }

               // 如果 sms_code_text 不是文本,而是数字,则表示当前手机号码还在60秒的发送短信间隔内
               if(this.sms_code_text != "发送验证码"){
                 return false;
              }

               // 发送短信
               let _this = this;
               this.$axios.get(`${_this.$settings.Host}/users/sms/${_this.mobile}/`).then(response=>{
                 // 显示发送短信以后的文本倒计时
                 let time = 60;
                 let timer = setInterval(()=>{
                   --time;
                   if(time <=1){
                     // 如果倒计时为0,则关闭当前定时器
                     _this.sms_code_text = "发送验证码";
                     clearInterval(timer);
                     time = 60;
                  }else{
                       _this.sms_code_text = time+"秒";
                  }
                },1000);

              }).catch(error=>{
                 console.log(error);
              })
          }
      }
  }
</script>

 

服务端完成手机短信的校验和冷却的判断

视图代码,views.py,代码:

# Create your views here.
from rest_framework.views import APIView
from django.conf import settings
import json
from urllib.parse import urlencode
from urllib.request import urlopen
from rest_framework.response import Response
from rest_framework import status

class CaptchaAPIView(APIView):
   def get(self,request):
       """验证码的验证结果校验"""
       AppSecretKey = settings.TENCENT_CAPTCHA["App_Secret_Key"]
       appid = settings.TENCENT_CAPTCHA["APPID"]
       Ticket = request.query_params.get("ticket")
       Randstr = request.query_params.get("randstr")
       UserIP = request._request.META.get("REMOTE_ADDR")
       print("用户ID地址:%s" % UserIP)
       params = {
           "aid": appid,
           "AppSecretKey": AppSecretKey,
           "Ticket": Ticket,
           "Randstr": Randstr,
           "UserIP": UserIP
      }
       params = urlencode(params)

       f = urlopen("%s?%s" % (settings.TENCENT_CAPTCHA["GATEWAY"], params))
       content = f.read()
       res = json.loads(content)
       print(res)
       if res:
           error_code = res["response"]
           if error_code == "1":
               return Response("验证通过!")
           else:
               return Response("验证失败!%s" % res["err_msg"], status=status.HTTP_400_BAD_REQUEST)
       else:
           return Response("验证失败!", status=status.HTTP_400_BAD_REQUEST)

from rest_framework.generics import CreateAPIView
from .models import User
from .serializers import UserModelSerializer
class UserCreateAPIView(CreateAPIView):
   queryset = User.objects.all()
   serializer_class = UserModelSerializer

from renranapi.libs.yuntongxun.sms import CCP
import random
from django_redis import get_redis_connection
from renranapi.settings import constants
class SMSCodeAPIView(APIView):
   """
  短信验证码
  """
   def get(self, request, mobile):
       """
      短信验证码
      """
       redis_conn = get_redis_connection('sms_code')

       # 手机号是否处于发送短信的冷却时间内
       interval = redis_conn.get("sms_time_%s" % mobile)
       if interval is not None:
           return Response("不能频繁发送短信!")

       # 生成短信验证码
       sms_code = "%06d" % random.randint(0, 999999)

       # 保存短信验证码与发送记录
       # 使用redis提供的管道操作可以一次性执行多条redis命令
       pl = redis_conn.pipeline()
       pl.multi()
       pl.setex("sms_%s" % mobile, constants.SMS_CODE_EXPIRE, sms_code)      # 设置短信有效期
       pl.setex("sms_time_%s" % mobile, constants.SMS_CODE_INTERVAL, "_")    # 设置发送短信间隔为60s
       pl.execute()

       # 发送短信验证码
       ccp = CCP()
       ccp.send_template_sms(mobile, [sms_code, constants.SMS_CODE_EXPIRE//60], settings.SMS.get("_templateID"))

       return Response({"message": "OK"}, status.HTTP_200_OK)

序列化器:

from rest_framework import serializers
from .models import User
import re
from django_redis import get_redis_connection
class UserModelSerializer(serializers.ModelSerializer):
   """用户信息序列化器"""
   sms_code = serializers.CharField(required=True, write_only=True, max_length=5, help_text="短信验证码")
   token = serializers.CharField(read_only=True, help_text="jwt登录认证")
   class Meta:
       model = User
       fields = ["id","username","mobile","password","nickname","sms_code","token"]
       extra_kwargs = {
           "id":{"read_only":True, },
           "username":{"read_only":True, },
           "mobile":{"required":True, "write_only":True, },
           "password":{"required":True, "write_only":True, "max_length": 16, "min_length": 6},
           "nickname":{"required":True }
      }

   def validate(self, attrs):
       # 1. 验证手机号码是否格式正确
       mobile = attrs.get("mobile")
       if not re.match("^1[3-9]\d{9}$", mobile):
           raise serializers.ValidationError("手机号码格式错误!")

       # 2. 验证手机号是否注册了
       try:
           User.objects.get(mobile=mobile)
           raise serializers.ValidationError("手机号码被占用!")
       except User.DoesNotExist:
           pass

       # 3. 昵称是否被注册了
       nickname = attrs.get("nickname")
       try:
           User.objects.get(nickname=nickname)
           raise serializers.ValidationError("用户昵称被占用!")
       except User.DoesNotExist:
           pass

       # todo 4. 验证手机短信是否正确
       redis_conn = get_redis_connection("sms_code")
       redis_sms_code = redis_conn.get("sms_%s" % mobile).decode()
       client_sms_code = attrs.get("sms_code")
       if redis_sms_code != client_sms_code:
           raise serializers.ValidationError("短信验证码填写有误!")

       return attrs

   def create(self, validated_data):
       """保存用户注册信息"""
       mobile = validated_data.get("mobile")
       nickname = validated_data.get("nickname")
       password = validated_data.get("password")
       try:
           user = User.objects.create_user(
               username=mobile,
               mobile=mobile,
               nickname=nickname,
               password=password
          )

           # 清除redis中的短信记录
           redis_conn = get_redis_connection("sms_code")
           redis_conn.delete("sms_%s" % mobile)
           redis_conn.delete("sms_time_%s" % mobile)

       except:
           raise serializers.ValidationError("用户信息注册失败!")

       # 返回jwt登录token
       from rest_framework_jwt.settings import api_settings

       jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
       jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

       payload = jwt_payload_handler(user)
       user.token = jwt_encode_handler(payload)

       return user

 

posted on   rider_yang  阅读(140)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示