(生鲜项目)17. drf实现发送短信验证码功能
第一步; 前期分析
由于之前在设计model的时候, 我们已经决定对code进行数据库保存, 所以这里我们应该使用 mixins.CreateModelMixin
同时, 用户发过来的手机号码, 我们在后端应该执行自己的验证, 包括
- 手机号码是否被注册
- 手机号码是否合法 [在settings.py中加上 REGEX_MOBILE="^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"]
- 两次验证码的间隔是否大于60秒
基于以上考虑, 我们需要创建一个 serializers
在写serializers逻辑的时候, 要注意这里我们不能像之前写goods那样, 把serializers和users.models.VerifyCode绑定起来, 因为VerifyCode的code是必填字段, 但是用户获取验证码只需要手机号码即可
如果要单独使用serializers并对其中的某个字段进行验证, 需要重写validate_字段名()函数
验证码code应该在发送成功之后再保存, 以防止用户猜到了code, 而登录成功
第二步; 写代码
首先写user.serializers.py
import re from datetime import datetime, timedelta from rest_framework import serializers from django.contrib.auth import get_user_model from MxShop.settings import REGEX_MOBILE from .models import VerifyCode User = get_user_model() # 可以获取数据库的userprofile表 # 验证手机号码 class SmsSerializer(serializers.Serializer): mobile = serializers.CharField(max_length=11) # 单独对mobile字段进行验证 def validate_mobile(self, mobile): # 手机号是否注册 if User.objects.filter(mobile=mobile).count(): raise serializers.ValidationError("用户已经存在") # 手机号码是否合法 if not re.match(REGEX_MOBILE, mobile): raise serializers.ValidationError("手机号码非法") # 验证发送频率 one_minute_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0) if VerifyCode.objects.filter(add_time__gt=one_minute_ago, mobile=mobile).count(): raise serializers.ValidationError("距离上一次发送未超过60s") # 验证通过 return mobile
然后写user.views.py
from django.shortcuts import render from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model from django.db.models import Q from rest_framework.mixins import CreateModelMixin from rest_framework import viewsets from rest_framework.response import Response from rest_framework import status from random import choice from .serializers import SmsSerializer from utils.yunpian import YunPian from MxShop.settings import APIKEY from .models import VerifyCode User = get_user_model()# 发送短信验证码 class SmsCodeViewset(CreateModelMixin,viewsets.GenericViewSet): serializer_class = SmsSerializer # 生成随机验证码的函数 def generate_code(self): seeds="1234567890" code_lst=[] for i in range(4): code_lst.append(choice(seeds)) return "".join(code_lst) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) # 如果serializer不通过, 那么后面就不会执行,而且drf会自动封装400的状态码 # 上面的mobile字段校验成功后,获取mobile, 并对其发送验证码 mobile=serializer.validated_data["mobile"] # validated_data就是serializer返回的数据, 是一个字典 yunpian = YunPian(APIKEY) code=self.generate_code() sms_status = yunpian.send_sms(code=code,mobile=mobile) # 如果发送失败,把错误信息填充给mobile,同时封装一个400的状态码 if sms_status["code"] != 0: return Response( {"mobile":sms_status["msg"]}, status=status.HTTP_400_BAD_REQUEST ) else: # 如果发送成功就先保存code到数据库, 然后返回手机号码,并封装201状态码 code_record=VerifyCode(code=code,mobile=mobile) code_record.save() return Response( {"mobile":mobile}, status=status.HTTP_201_CREATED )
然后是settings.py
# 手机号码正则表达式 REGEX_MOBILE="^1[358]\d{9}$|^147\d{8}$|^176\d{8}$" # 云片网设置 APIKEY = "cdb6f4b3860c552f790176a9e4f3fd85"
最后是urls.py, 注册一个验证码接口
# 发送验证码的接口 router.register(r'code', SmsCodeViewset, basename="code")
然后去浏览器测试
--- 君子处其实,不处其华;治其内,不治其外 张居正 ----