luffy-5 登陆注册5个接口

登陆注册设计

# 登录注册版本,设计
	-用户表,没有其他表
    -写哪些接口?
    	-1 用户名密码登录---》多方式登录(可以使用  用户名+密码,手机号+密码,邮箱+密码)
        -2 校验手机号是否存在的接口
        -3 手机号+验证码登录  ---》后期改成如果没注册,直接注册并登录
        -4 发送验证码接口  ---》使用第三方发送短信
        -5 注册接口(手机号,验证码,密码)
        
        
   -目前可以先写出来
		-多方式登录
    	-校验手机号是否存在
        
  -如果发送短信---》第三方平台---》阿里大于短信,腾讯云短信,容联云通信----》花钱买短信条数---》调用它的接口---》给固定的人发送短信

多方式登录接口

# 前端传入的数据---》post请求
	{username:lqz,password:123}
    {username:18953675221,password:123}
    {username:33@qq.com,password:123}
    
# 咱们现在把逻辑写在序列化类中
	-写在全局钩子中

序列化类

from .models import User
from rest_framework import serializers
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.settings import api_settings
from rest_framework.exceptions import APIException

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class MulLoginSerializer(serializers.ModelSerializer):
    username = serializers.CharField()  # 必须重写该字段,否则会做唯一性校验,如果数据库存在,就抛异常了,没法往下走了

    class Meta:
        model = User
        fields = ['username', 'password']  # 字段自己的规则没有校验通过,从表模型映射过来的username是unique的

    def _check_user(self, attrs):  # 公司里约定俗称,隐藏属性 __,这个方法只想在内部使用,万一外部真要用,也可以用
        try:
            username = attrs.get('username')
            password = attrs.get('password')
            if re.match(r'^1[3-9][0-9]{9}$', username):  # 手机号登录
                user = User.objects.get(mobile=username)
            elif re.match(r'^.+@.+$', username):  # 邮箱登录
                user = User.objects.get(email=username)
            else:
                user = User.objects.get(username=username)
            if user.check_password(password):
                return user
            else:
                # 使用ValidationError抛异常,一定要写成字典形式,或者直接抛APIException
                # raise ValidationError({'detail':'用户名或密码错误'})
                raise APIException()
        except APIException:
            # raise ValidationError({'detail':'用户名或密码错误'})
            raise APIException(detail='密码错误')
        except Exception:
            raise APIException(detail='用户不存在')

    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token

    def validate(self, attrs):  # 在全局钩子中,校验用户是否登录成功,如果登录成功直接签发token
        # 1 多方式得到user---》用户名,手机号,邮箱
        user = self._check_user(attrs)  # 如果登录失败,就抛异常了
        # 2 user签发token---》签发token
        token = self._get_token(user)
        # 3 把token放到 self 对象中得context属性中
        self.context['token'] = token
        host = self.context.get('request').META['HTTP_HOST']
        self.context['icon'] = 'http://%s/media/%s' % (host, str(user.icon))

        return attrs

视图类

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

    @action(methods=['POST'], detail=False)
    def mul_login(self, request):
        ser = self.get_serializer(data=request.data)
        # 这一句,会校验字段自己的规则,还会校验局部钩子,还会校验全局钩子
        ser.is_valid(raise_exception=True)  # 这样写,我们已经处理了全局异常,如果校验不通过,会抛异常
        # 生成token---->从序列化类中取出来
        token = ser.context.get('token')
        return APIResponse(token=token)

路由

from rest_framework.routers import SimpleRouter


from .views import MobileView,LoginView

# 向这个地址发送get请求,# 127.0.0.1:8000/api/v1/user/    mobile  ---get  就能校验手机号是否存在
router = SimpleRouter()

router.register('mobile', MobileView, 'mobile')
router.register('login', LoginView, 'login')

urlpatterns = [
]
urlpatterns += router.urls

手机号是否存在接口

urls.py

#总路由
path('api/v1/user/', include('user.urls')),



# user 的app下的urls.py
from .views import MobileView
router = SimpleRouter()
router.register('mobile', MobileView, 'mobile')
urlpatterns = [
]
urlpatterns += router.urls

视图类

class MobileView(ViewSet):
    def list(self, request):
        try:
            mobile = request.query_params.get('mobile')
            User.objects.get(mobile=mobile)  # 有且只有一条才正常
            return APIResponse()
        except:
            raise APIException(detail='手机号不存在')
            # return APIResponse(code=101, msg='手机号不存在')

访问路径

http://127.0.0.1:8000/api/v1/user/mobile/?mobile=189536745211

腾讯短信功能二次封装

# 腾讯云官方
	-短信申请:申请签名,申请模板
    -使用官方提供的sdk或者api发送短信
    -如果没有sdk就是用api调用
    -现在有sdk,优先使用sdk,简单---》不同语言封装的包,直接调用包的方法就可以完成相关操作
    - 2.0版本的sdk,3.0版本的sdk
    
    -安装3.0的sdk:pip install tencentcloud-sdk-python   # 腾讯云所有的功能都集成到这个包中了
    -安装2.0的sdk:pip install qcloudsms_py              # 腾讯云短信功能在这个包中

封装v2版本

init.py

from .sms import get_code,send_sms_v2

settings.py

# 短信应用 SDK AppID
APPID =   # SDK AppID 以1400开头
# 短信应用 SDK AppKey
APPKEY = ""
# 需要发送短信的手机号码

# 短信模板ID,需要在短信控制台中申请
TEMPLATE_ID = 1470213  # NOTE: 这里的模板 ID`7839` 只是示例,真实的模板 ID 需要在短信控制台中申请
# 签名
SMS_SIGN = "咋啦叭呼"  # NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台中申请

sms.py

# 生成随机n位验证码的函数
import random

from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
from . import settings  # 使用相对导入


def get_code(n=4):
    code = ''
    for i in range(n):
        code += str(random.randint(0, 9))
    return code


# 发送短信的函数
def send_sms(phone, code):
    phone_numbers = [phone, ]
    ssender = SmsSingleSender(settings.APPID, settings.APPKEY)
    params = [code, '1']  # 当模板没有参数时,`params = []`
    try:
        result = ssender.send_with_param(86, phone_numbers[0],
                                         settings.TEMPLATE_ID, params, sign=settings.SMS_SIGN, extend="", ext="")
    except Exception as e:
        return False
    return True


if __name__ == '__main__':
    print(get_code())

封装v3版本

init.py

from .sms import get_code,send_sms_v2

settings.py

SECRET_ID='AKIDZL2jO2WtBOWaXPE5qV9iKtPvRrCQZiiY'
SECRET_KEY='kKl5FF6oNvLZaR5WklZAZllY9XkneIl2'


APPID = '1400705789'  # SDK AppID 以1400开头
TEMPLATE_ID = '1470213'
# 签名
SMS_SIGN = "咋啦叭呼"


### v3版本的APPID  TEMPLATE_ID  都必须使用str类型,数字类型报错

sms.py

# 生成随机n位验证码的函数
import random

from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
from . import settings  # 使用相对导入
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
# 导入对应产品模块的client models。
from tencentcloud.sms.v20210111 import sms_client, models
# 导入可选配置类
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from . import settings


def get_code(n=4):
    code = ''
    for i in range(n):
        code += str(random.randint(0, 9))
    return code


# 发送短信的函数
def send_sms(phone, code):
    try:
        cred = credential.Credential(settings.SECRET_ID, settings.SECRET_KEY)
        httpProfile = HttpProfile()
        httpProfile.reqMethod = "POST"
        httpProfile.reqTimeout = 30
        httpProfile.endpoint = "sms.tencentcloudapi.com"
        clientProfile = ClientProfile()
        clientProfile.signMethod = "TC3-HMAC-SHA256"
        clientProfile.language = "en-US"
        clientProfile.httpProfile = httpProfile
        client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile)
        req = models.SendSmsRequest()
        req.SmsSdkAppId = settings.APPID
        req.SignName = settings.SMS_SIGN
        req.TemplateId = settings.TEMPLATE_ID
        req.TemplateParamSet = [code, '5']
        req.PhoneNumberSet = ["+86" + phone, ]
        req.SessionContext = ""
        req.ExtendCode = ""
        req.SenderId = ""
        resp = client.SendSms(req)
        # print(resp.to_json_string(indent=2))
        return True
    except TencentCloudSDKException as err:
        # print(err)
        return False

短信验证码接口

# get
# 前端传入手机号,调用发送短信函数,完成发送短信

路由

# 127.0.0.1:8000/api/v1/user/sms/send_sms
router.register('sms', SMSView, 'sms')

视图类

# from libs import tencent_sms_v2 as tencent_sms
from libs import tencent_sms_v3 as tencent_sms
import re
class SMSView(ViewSet):
    @action(methods=['GET'], detail=False)
    def send_sms(self, request):
        mobile = request.query_params.get('mobile')
        if mobile and re.match(r'^1[3-9][0-9]{9}$', mobile):
            code = tencent_sms.get_code()
            res = tencent_sms.send_sms(mobile, code)
            if res:
                return APIResponse()
            else:
                raise APIException(detail='发送短信失败')
        else:
            raise APIException(detail='手机号有误')

短信登录接口

# 前端传入的格式---{mobile:12334455,code:8888}

路由

router.register('sms', SMSView, 'sms')

视图类

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

    def common_login(self, request):
        ser = self.get_serializer(data=request.data, context={'request': request})
        ser.is_valid(raise_exception=True)
        token = ser.context.get('token')
        icon = ser.context.get('icon')
        return APIResponse(token=token, icon=icon)
    # 重写这个方法get_serializer_class,返回什么序列化类,当前用的序列化类就是哪个
    def get_serializer_class(self):
        # 方式一 :通过请求路径来判断,可以
        # if 'mul_login' in self.request.path:
        #     return self.serializer_class
        # else:
        #     return 序列化类

        # 方式二:通过action判断
        if self.action =='sms_login':
            return SMSLoginSerializer
        else:
            return self.serializer_class

    @action(methods=['POST'], detail=False)
    def mul_login(self, request):
        return self.common_login(request)

    @action(methods=['POST'], detail=False)
    def sms_login(self, request):
        return self.common_login(request)

序列化类

class SMSLoginSerializer(serializers.ModelSerializer):
    code = serializers.CharField()  # code不是User表的字段,所以要重写code

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

    def _check_user(self, attrs):
        mobile = attrs.get('mobile')
        code = attrs.get('code')
        # 校验code对不对?从缓存中取出来
        old_code = cache.get('sms_cache_%s' % mobile)
        # 取出来,立马失效
        cache.set('sms_cache_%s' % mobile,'')
        if old_code == code:  # 万能验证码
            user = User.objects.filter(mobile=mobile).first()
            if user:
                return user
            else:
                raise APIException(detail='用户不存在')
        else:
            raise APIException(detail='验证码错误')

    def _get_token(self, user):
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return token

    def validate(self, attrs):  # 在全局钩子中,校验用户是否登录成功,如果登录成功直接签发token
        # 1 手机号得到user
        user = self._check_user(attrs)
        # 2 user签发token---》签发token
        token = self._get_token(user)
        # 3 把token放到 self 对象中得context属性中
        self.context['token'] = token
        host = self.context.get('request').META['HTTP_HOST']
        self.context['icon'] = 'http://%s/media/%s' % (host, str(user.icon))

        return attrs

短信注册接口

# 前端传入数据----》{mobile:1983334,password:123,code:8888}

视图类

class UserView(GenericViewSet, CreateModelMixin):
    serializer_class = UserSerializer
    queryset = User.objects.all()

    def create(self, request, *args, **kwargs):
        super().create(request, *args, **kwargs)
        return APIResponse(msg='注册成功')

序列化类

class UserSerializer(serializers.ModelSerializer):
    code = serializers.CharField(write_only=True)  # code不是User表的字段,所以要重写code

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

    def validate(self, attrs):
        # 1 校验code是否正确
        mobile = attrs.get('mobile')
        code = attrs.get('code')
        old_code = cache.get('sms_cache_%s' % mobile)
        if not code == old_code:
            raise APIException(detail='验证码错误')

        # 2 手机号是否被注册过
        if User.objects.filter(mobile=mobile).first():
            raise APIException(detail='该手机号已经被注册')

        # 3 入库前准备---》code字段从attrs中剔除,username必填,手机号就是用户名
        attrs.pop('code')
        attrs['username'] = mobile

        return attrs

    def create(self, validated_data):
        # 因为新增用户,是用create_user新增的,不是使用create新增的
        user = User.objects.create_user(**validated_data)
        return user

路由

router.register('register', UserView, 'register')
posted @ 2022-07-12 22:47  Rain_Kz  阅读(23)  评论(0编辑  收藏  举报