13 后端短信接口

13 后端短信接口

一、短信接口

1、user/views.py

from rest_framework.viewsets import ViewSet
from luffyapi.utils.response import APIResponse
from rest_framework.decorators import action


# 发送短信验证码接口
class SendSmSView(ViewSet):

    @action(methods=['GET'], detail=False)
    def send_sms(self, request, *args, **kwargs):
        '''
        发送短信验证码
        '''
        from libs.tx_sms import get_code, send_message_v2, send_sms_v3
        from django.core.cache import cache
        from django.conf import settings
        phone = request.query_params.get('phone')
        sms_code = get_code()
        # 保存验证码到内存中,后期放到缓存中
        cache.set(settings.SMS_CODE_CACHE % phone, sms_code)
        result = send_sms_v3(phone, sms_code)
        print(sms_code)
        if result:
            return APIResponse(code=1, msg='验证码发送成功')
        else:
            return APIResponse(code=0, msg='验证码发送失败')

2、user/urls

from django.urls import path
from rest_framework.routers import SimpleRouter
from . import views
router = SimpleRouter()
router.register('', views.LoginViewSet, 'login')
router.register('', views.SendSmSView, 'send')

urlpatterns = [

]
urlpatterns += router.urls

3、settings/const.py

# 用户缓存验证码的key值
SMS_CODE_CACHE = 'sms_code_mobile_%s'  # 要想使用这个文件必须在dev中导入才能使用

5、settings/dev.py

# 导入自己的配置文件
from settings.const import *

6、接口访问

http://127.0.0.1:8000/user/send_sms/?phone=15112345678

二、短信接口频率限制

1、user/throttings.py

from rest_framework.throttling import SimpleRateThrottle


class SMSThrotting(SimpleRateThrottle):
    scope = 'sms'

    def get_cache_key(self, request, view):
        telephone = request.query_params.get('telephone')
        return self.cache_format % {'scope': self.scope, 'ident': telephone}

2、dev.py

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        # 设置一分钟访问3次
        'sms': '1/m',  # key:sms 对应频率类的scope属性, value: 1/m 一分钟访问1次
    },
}

3、user/views.py

from .throtting import SMSThrotting


class SendSmSView(ViewSet):
    throttle_classes = [SMSThrotting, ]  # 配置局部频率

    @action(methods=['GET'], detail=False)
    def send_sms(self, request, *args, **kwargs):
        '''
        发送短信验证码
        '''
        import re
        from libs.tx_sms import get_code, send_message_v2, send_sms_v3
        from django.core.cache import cache
        from django.conf import settings
        phone = request.query_params.get('phone')
        sms_code = get_code()
        # 保存验证码
        cache.set(settings.SMS_CODE_CACHE % phone, sms_code)
        result = send_sms_v3(phone, sms_code)
        print(sms_code)
        if result:
            return APIResponse(code=1, msg='验证码发送成功')
        else:
            return APIResponse(code=0, msg='验证码发送失败')

三、验证码登录接口

1、user/serializer.py

class CodeUserSerializer(serializers.ModelSerializer):
    code = serializers.CharField()

    class Meta:
        model = models.User
        fields = ['telephone', 'code']

    def validate(self, attrs):
        user = self._get_user(attrs)
        # 用户存在,签发token
        token = self._get_token(user)
        self.context['token'] = token
        self.context['username'] = user.username
        return attrs

    def _get_user(self, attrs):
        import re
        from django.core.cache import cache
        from django.conf import settings
        telephone = attrs.get('telephone')
        code = attrs.get('code')

        # 取出原来的code
        cache_code = cache.get(settings.SMS_CODE_CACHE % telephone)
        if code == cache_code:
            # 验证码通过
            if re.match('^1[3-9][0-9]{9}$', telephone):
                user = models.User.objects.filter(telephone=telephone).first()
                if user:
                    return user
                else:
                    raise ValueError('用户不存在')
            else:
                raise ValueError('手机号不合法')
        else:
            raise ValueError('验证码错误')

    def _get_token(self, user):
        from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
        payload = jwt_payload_handler(user)  # 通过user对象获得payload
        token = jwt_encode_handler(payload)  # 通过payload对象获得token
        return token

2、user/views.py

from django.shortcuts import render

from rest_framework.viewsets import ViewSet
from . import serializers, models
from luffyapi.utils.response import APIResponse
from rest_framework.decorators import action


class LoginViewSet(ViewSet):
    # 验证码登录接口
    @action(methods=['POST'], detail=False)
    def code_login(self, request, *args, **kwargs):
        ser = serializers.CodeUserSerializer(data=request.data)
        if ser.is_valid():
            token = ser.context['token']
            username = ser.context['username']
            return APIResponse(token=token, username=username)

3、接口访问

http://127.0.0.1:8000/user/code_login/?telephone=15124948390&code=5768

四、根据验证码登录接口简化代码

1、user/serializer.py

class MobileSerializer(serializers.ModelSerializer):
    code = serializers.CharField(max_length=5, min_length=4)  # 因为code在表里没有,所以要重写字段
    mobile = serializers.CharField()

    class Meta:
        model = User
        fields = ['mobile', 'code']

    def validate(self, attrs):
        # 取出手机号对应的token
        mobile = attrs.get('mobile')

        # 1.校验code是否正确
        self._check_code(attrs, mobile)
        # 2.根据手机号查到用户
        user = self._get_user_by_mobile(attrs, mobile)
        # 3.签发token
        token = self._get_token(user)
        # 4.把token放入当前对象给视图类用
        self.context['token'] = token
        self.context['username'] = user.username
        # 这个地址是服务端地址,服务端地址从request对象中可以取出request.META['HTTP_HOST']
        request = self.context.get('request')
        self.context['icon'] = 'http://%s/media/'%request.META['HTTP_HOST']+str(user.icon)
        return attrs

    def _check_code(self, attrs, mobile):
        code = attrs.get('code')
        # 获取存入内存中的验证码
        old_code = cache.get(settings.SMS_CODE_CACHE % mobile)
        # 存入内存的验证码是否与发来的验证码一样
        if not old_code == code:
            raise ValidationError('验证码错误')

    def _get_user_by_mobile(self, attrs, mobile):
        # 根据手机号查询用户
        user = User.objects.filter(telephone=mobile).first()
        if user:
            return user
        else:
            raise ValidationError('用户不存在')

    def _get_token(self, user):
        # 根据user获取payload
        payload = jwt_payload_handler(user)
        # 根据payload得到token
        token = jwt_encode_handler(payload)
        return token

2、user/views.py

from rest_framework.viewsets import ViewSet, GenericViewSet
from luffy.utils.response import APIResponse
from .serializers import LoginSerializer, MobileSerializer
from .models import User
from rest_framework.decorators import action
from rest_framework.exceptions import APIException


class LoginView(GenericViewSet):
    queryset = User.objects.all()
    serializer_class = LoginSerializer

    def get_serializer_class(self):
        # self 是试图类的对象---》里面有个request
        '''
        通过重写get_serializer_class方法,来达到简化代码的目的
        '''
        print(self.action)
        if self.action == 'mul_login':  # 可以根据路径判断使用哪个序列化类
            return self.serializer_class
        else:
            return MobileSerializer

    # 多方式登录接口
    @action(methods=['POST'], detail=False)
    def mul_login(self, request, *args, **kwargs):
        return self._login(request)

    # 验证码登录接口
    @action(methods=['POST'], detail=False)
    def mobile_login(self, request):
        return self._login(request)

    def _login(self, request):
        try:
            # 校验规则和签发token都写到序列化类中
            ser = self.get_serializer(data=request.data, context={'request': request})
            # context是视图类和序列化类中的桥梁,可以通过context互相传值,因为序列化类用到request,所以传值
            ser.is_valid(raise_exception=True)
            token = ser.context.get('token')
            username = ser.context.get('username')
            icon = ser.context.get('icon')
        except Exception as e:
            raise APIException(str(e))
        return APIResponse(token=token, username=username, icon=icon)

3、接口访问

http://127.0.0.1:8000/user/mobile_login/?telephone=15124948390&code=5768

五、短信注册接口

1、user/urls.py

from django.urls import path
from . import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('register', views.RegisterView, 'register')

urlpatterns = [

]

urlpatterns += router.urls

2、user/serializer.py

class RegisterSerializer(serializers.ModelSerializer):

    # 因为code不是User表的字段---》这里会有错,需要加上write_only
    code = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['telephone', 'code', 'password', 'username']
        extra_kwargs = {
            'username': {'read_only': True},
            'password': {'write_only': True}
        }

    def validate(self, attrs):
        # 1 校验手机号是否合法(写上没问题,但是没必要)
        # 2 验证code
        self._check_code(attrs)
        # 3 创建出username,剔除code,因为code不是表的字段
        # attrs:{mobile:1111,password:2222,username:1111,code:222}
        attrs['username'] = attrs.get('telephone')
        attrs.pop('code')  # 因为表字段没有code,先删除code
        return attrs

    def _check_code(self, attrs):
        code = attrs.get('code')
        # 取出该手机号对应的code
        mobile = attrs.get('telephone')
        old_code = cache.get(settings.SMS_CODE_CACHE % mobile)
        # cache.set(settings.SMS_CODE_CACHE % mobile,'')
        # 如果是调试模式,有个万能验证码
        if not old_code == code:
            raise ValidationError('验证码错误')

    def create(self, validated_data):
        # 密码是密文,传进来的是明文User.objects.create()
        user = User.objects.create_user(**validated_data)  # create_user创建后密码是密文
        return user

3、user/view.py

class RegisterView(GenericViewSet, CreateModelMixin):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer

    def create(self, request, *args, **kwargs):
        # 调用的是父类的create方法,
        res = super().create(request, *args, **kwargs)  # 正常操作是下面的,但是步骤太多,直接调用父类的
        # ser=self.get_serializer(data=request.data)
        # ser.is_valid(raise_exception=True)
        # ser.save() # 如果是新增,会触发序列化类的create,如果是修改,会触发序列化类的update
        return APIResponse(msg='注册成功', user=res.data)  # {code:100,msg:注册成功,data:{mobile:122}}

4、访问接口

http://127.0.0.1:8000/user/register/

六、根据序列化类封装

1、serializer_base.py

from rest_framework import serializers
from user.models import User
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
import re
from rest_framework.exceptions import ValidationError
from django.core.cache import cache
from django.conf import settings


class BaseSerializer(serializers.ModelSerializer):

    def _get_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        if re.match(r'^1[3-9][0-9]{9}$', username):
            user = User.objects.filter(telephone=username).first()
        elif re.match(r'^.+@.+$', username):
            user = User.objects.filter(email=username).first()
        else:
            user = User.objects.filter(username=username).first()

        if user and user.check_password(password):  # 使用check_password检验加密秘密
            return user
        else:
            raise ValidationError('用户名或密码错误')

    def _get_token(self, user):
        payload = jwt_payload_handler(user)  # 通过user对象获得payload
        token = jwt_encode_handler(payload)  # 通过payload对象获得token
        return token

    def _token_or_user(self, user, token):
        # 3.把token放入当前对象给视图类用
        self.context['token'] = token
        self.context['username'] = user.username
        # 这个地址是服务端地址,服务端地址从request对象中可以取出request.META['HTTP_HOST']
        request = self.context.get('request')
        self.context['icon'] = 'http://%s/media/' % request.META['HTTP_HOST'] + str(user.icon)
        return True


    def _check_code(self, attrs, mobile):
        code = attrs.get('code')
        # 获取存入内存中的验证码
        old_code = cache.get(settings.SMS_CODE_CACHE % mobile)
        # 存入内存的验证码是否与发来的验证码一样
        if not old_code == code:
            raise ValidationError('验证码错误')

    def _get_user_by_mobile(self, mobile):
        # 根据手机号查询用户
        user = User.objects.filter(telephone=mobile).first()
        if user:
            return user
        else:
            raise ValidationError('用户不存在')

    def _get_code(self, attrs):
        code = attrs.get('code')
        # 取出该手机号对应的code
        mobile = attrs.get('telephone')
        old_code = cache.get(settings.SMS_CODE_CACHE % mobile)
        # cache.set(settings.SMS_CODE_CACHE % mobile,'')
        # 如果是调试模式,有个万能验证码
        if not old_code == code:
            raise ValidationError('验证码错误')

2、user/serializer.py

from rest_framework import serializers
from .models import User
from utils.serializer_base import BaseSerializer


class LoginSerializer(BaseSerializer):
    # 因为username自己会有唯一键,所以要重写username字段
    username = serializers.CharField()

    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'icon']
        extra_kwargs = {
            'id': {'read_only': True},
            'username': {'read_only': True},
            'password': {'write_only': True},
            'icon': {'read_only': True}
        }

    def validate(self, attrs):
        # 1.获取登录用户和密码
        user = self._get_user(attrs)
        # 2.签发token
        token = self._get_token(user)
        # 3.把token放入当前对象给视图类用
        self._token_or_user(user, token)
        return attrs


class MobileSerializer(BaseSerializer):
    code = serializers.CharField(max_length=5, min_length=4)  # 因为code在表里没有,所以要重写字段
    mobile = serializers.CharField()

    class Meta:
        model = User
        fields = ['mobile', 'code']

    def validate(self, attrs):
        # 取出手机号对应的token
        mobile = attrs.get('mobile')

        # 2.根据手机号查到用户
        user = self._get_user_by_mobile(mobile)
        # 1.校验code是否正确
        self._check_code(attrs, mobile)
        # 3.签发token
        token = self._get_token(user)
        # 4.把token放入当前对象给视图类用
        self._token_or_user(user, token)
        return attrs


class RegisterSerializer(BaseSerializer):
    # 因为code不是User表的字段---》这里会有错,需要加上write_only
    code = serializers.CharField(write_only=True)

    class Meta:
        model = User
        fields = ['telephone', 'code', 'password', 'username']
        extra_kwargs = {
            'username': {'read_only': True},
            'password': {'write_only': True}
        }

    def validate(self, attrs):
        # 1 校验手机号是否合法(写上没问题,但是没必要)
        # 2 验证code
        self._get_code(attrs)
        # 3 创建出username,剔除code,因为code不是表的字段
        # attrs:{mobile:1111,password:2222,username:1111,code:222}
        attrs['username'] = attrs.get('telephone')
        attrs.pop('code')  # 因为表字段没有code,先删除code
        return attrs

    def create(self, validated_data):
        # 密码是密文,传进来的是明文User.objects.create()
        user = User.objects.create_user(**validated_data)  # create_user创建后密码是密文
        return user
posted @ 2022-02-28 17:00  迷恋~以成伤  阅读(162)  评论(0编辑  收藏  举报