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
![](https://img2022.cnblogs.com/blog/2479385/202202/2479385-20220228165846109-481825180.png)
二、短信接口频率限制
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
![](https://img2022.cnblogs.com/blog/2479385/202202/2479385-20220228165910326-699520940.png)
四、根据验证码登录接口简化代码
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
![](https://img2022.cnblogs.com/blog/2479385/202202/2479385-20220228165920956-1346221621.png)
五、短信注册接口
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/
![](https://img2022.cnblogs.com/blog/2479385/202202/2479385-20220228165941903-665770050.png)
六、根据序列化类封装
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