后台
后台路由: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, '用户不存在')