登陸註冊後臺

後臺

後臺路由:user/urls.py

from django.urls import path
from . import views
urlpatterns = [
    path('mobile/', views.MobileAPIView.as_view()),
    path('sms/', views.SMSAPIView.as_view()),
    path('register/', views.RegisterCreateAPIView.as_view()),
    path('login/', views.LoginAPIView.as_view()),
    path('login/mobile/', views.LoginMobileAPIView.as_view()),
]

常量配置:settings/const.py | settings/dev.py

# settings/const.py

# 短信過時時間(單位:s)
SMS_EXP = 300000
# 短信緩存key
SMS_CACHE_KEY = 'sms_%(mobile)s'
# 輪播圖推薦量
BANNER_COUNT = 3


# settings/dev.py
REST_FRAMEWORK = {
    # 異常配置
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
    # 頻率限制配置
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
        'sms': '1/m',
    },
}

序列化組件:user/serializers.py

from rest_framework import serializers
from . import models
import re
from django.core.cache import cache
from settings.const import SMS_CACHE_KEY
class RegisterModelSerializer(serializers.ModelSerializer):
    # 自定義反序列化字段的規則必須在字段聲明時規定
    code = serializers.CharField(write_only=True, min_length=4, max_length=4)
    class Meta:
        model = models.User
        fields = ('mobile', 'password', 'code', 'username', 'email')
        extra_kwargs = {
            'password': {
                'min_length': 6,
                'max_length': 18,
                'write_only': True
            },
            'username': {
                'read_only': True
            },
            'email': {
                'read_only': True
            }
        }

    def validate_mobile(self, value):
        if not re.match(r'^1[3-9]\d{9}$', value):
            raise serializers.ValidationError('手機號有誤')
        return value

    def validate(self, attrs):
        mobile = attrs.get('mobile')
        code = attrs.pop('code')  # code不入庫
        old_code = cache.get(SMS_CACHE_KEY % {'mobile': mobile})
        if not old_code:
            raise serializers.ValidationError({'code': '驗證碼已失效'})
        if code != old_code:
            raise serializers.ValidationError({'code': '驗證碼錯誤'})
        # 驗證碼一旦驗證成功,就失效(一次性)
        # cache.set(SMS_CACHE_KEY % {'mobile': mobile}, '0000', 1)
        return attrs

    # create方法重寫:經過手機號註冊的用戶,用戶名默認就是手機號
    def create(self, validated_data):
        mobile = validated_data.get('mobile')
        username = mobile
        password = validated_data.get('password')
        return models.User.objects.create_user(username=username, mobile=mobile, password=password)



from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
class LoginModelSerializer(serializers.ModelSerializer):
    usr = serializers.CharField(write_only=True)
    pwd = serializers.CharField(write_only=True)
    class Meta:
        model = models.User
        fields = ['usr', 'pwd', 'username', 'mobile', 'email']
        extra_kwargs = {
            'username': {
                'read_only': True
            },
            'mobile': {
                'read_only': True
            },
            'email': {
                'read_only': True
            },
        }

    def validate(self, attrs):
        usr = attrs.get('usr')
        pwd = attrs.get('pwd')

        # 多方式登陸:各分支處理獲得該方式下對應的用戶
        if re.match(r'.+@.+', usr):
            user_query = models.User.objects.filter(email=usr)
        elif re.match(r'1[3-9][0-9]{9}', usr):
            user_query = models.User.objects.filter(mobile=usr)
        else:
            user_query = models.User.objects.filter(username=usr)
        user_obj = user_query.first()

        # 簽發:獲得登陸用戶,簽發token並存儲在實例化對象中
        if user_obj and user_obj.check_password(pwd):
            # 簽發token,將token存放到 實例化類對象的token 名字中
            payload = jwt_payload_handler(user_obj)
            token = jwt_encode_handler(payload)
            # 將當前用戶與簽發的token都保存在序列化對象中
            self.user = user_obj
            self.token = token
            return attrs

        raise serializers.ValidationError({'data': '數據有誤'})

頻率組件:user/thorttles.py

from rest_framework.throttling import SimpleRateThrottle

class SMSRateThrottle(SimpleRateThrottle):
    scope = 'sms'
    def get_cache_key(self, request, view):
        mobile = request.data.get('mobile') or request.query_params.get('mobile')
        if not mobile:
            return None
        return self.cache_format % {'scope': self.scope, 'ident': mobile}

異常模塊:utils/exception.py

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
from utils.logging import logger
from utils.response import APIResponse
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response is None:
        logger.error('%s - %s - %s' % (context['view'], context['request'].method, exc))
        return APIResponse(3, '異常',
            results={'detail': '服務器錯誤'},
           http_status=status.HTTP_500_INTERNAL_SERVER_ERROR,
           exception=True
        )
    return APIResponse(3, '異常', results=response.data, http_status=status.HTTP_401_UNAUTHORIZED)

視圖模塊:user/views.py

from rest_framework.views import APIView
from .models import User
from utils.response import APIResponse
import re
# 註冊邏輯:1.校驗手機號是否存在 2.發送驗證碼 3.完成註冊
# 校驗手機號
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '數據有誤')
        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已註冊')
        except:
            return APIResponse(0, '未註冊')


# 發送驗證碼
from libs import txsms
from django.core.cache import cache
from settings.const import SMS_EXP, SMS_CACHE_KEY
from .thorttles import SMSRateThrottle
class SMSAPIView(APIView):
    # 頻率限制
    throttle_classes = [SMSRateThrottle]
    def post(self, request, *args, **kwargs):
        # 1)拿到前臺的手機號
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(2, '數據有誤')
        # 2)調用txsms生成手機驗證碼
        code = txsms.get_code()
        # 3)調用txsms發送手機驗證碼
        result = txsms.send_sms(mobile, code, SMS_EXP // 60)
        # 4)失敗反饋信息給前臺
        if not result:
            return APIResponse(1, '短信發送失敗')
        # 5)成功服務器緩存手機驗證碼 - 用緩存存儲(方便管理) - redis
        cache.set(SMS_CACHE_KEY % {'mobile': mobile}, code, SMS_EXP)
        # 6)反饋成功信息給前臺
        return APIResponse(0, '短信發送成功')

# 註冊
from rest_framework.generics import CreateAPIView
from . import serializers
class RegisterCreateAPIView(CreateAPIView):
    # queryset = User.objects.filter(is_active=True)
    serializer_class = serializers.RegisterModelSerializer

    # 自定義響應結果
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # 校驗失敗就主動拋異常 => 自定義異常結果,配置異常模塊
        user_obj = serializer.save()  # 要自定義入庫邏輯,重寫create方法
        headers = self.get_success_headers(serializer.data)
        # 響應結果須要格式化,使用序列化類要提供序列化與反序列化兩套規則
        return APIResponse(0, 'ok',
            results=serializers.RegisterModelSerializer(user_obj).data,
            http_status=201,
            headers=headers
            )


# 多方式登陸
class LoginAPIView(APIView):
    # 1) 禁用認證與權限組件
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        # 2) 拿到前臺登陸信息,交給序列化類,規則:帳號用usr傳,密碼用pwd傳
        user_ser = serializers.LoginModelSerializer(data=request.data)
        # 3) 序列化類校驗獲得登陸用戶與token存放在序列化對象中
        user_ser.is_valid(raise_exception=True)
        # 4) 取出登陸用戶與token返回給前臺
        return APIResponse(token=user_ser.token, results=serializers.LoginModelSerializer(user_ser.user).data)


# 手機驗證碼登陸
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
class LoginMobileAPIView(APIView):
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        code = request.data.get('code')
        if not mobile or not code:
            return APIResponse(1, '數據有誤')
        old_code = cache.get(SMS_CACHE_KEY % {'mobile': mobile})
        if code != old_code:
            return APIResponse(1, '驗證碼錯誤')
        try:
            user = User.objects.get(mobile=mobile)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, results=serializers.LoginModelSerializer(user).data)
        except:
            return APIResponse(1, '用戶不存在')
相關文章
相關標籤/搜索