luffy值短信登录接口、注册接口

复习

# 1 pycharm 操作git
	# fetch和pull的区别 :fetch+merge=pull
# 2 给开源项目贡献代码
	# fork代码---》clone下来改就行了---》提交pr---》同意--》贡献者
    
# 3 登录注册 前端页面
# 4 5个接口
	-多方式登录 #手机号/邮箱/用户名
    -手机号是否存在 #验证手机号是否在数据库存在 
    -发送短信  #点发送短信
    -短信登录 #短信登录
    -短信注册 #短信注册
    
# 5 手机号是否存在
	try:
        pass
    except:
        raise 
    return Response()

# 6 多方式登录
	-前端数据:{username:用户名/手机号/邮箱,password:密码}
     -序列化类中---》视图类中调用 ser.is_valid--->执行字段自己的规则,局部钩子,全局钩子---》登录(通过正则匹配),查出用户,校验密码是否正确--->通过用户签发token---》context---》头像处理(imageField)

今日内容

1 腾讯云发送短信二次封装

# 腾讯云短信---》https://console.cloud.tencent.com/smsv2/csms-template
# 步骤:
	-创建签名   
    -创建模板
    -发送短信  # 用代码写个方法发送短信  调用他的接口 他给某个手机发短信
    
# API和SDK
	-api接口:http接口 
        其实就是别人已经写好的可以实现特定功能的函数,而你只需要根据他提供好的接口,也就是调用他的方法,传入他规定的参数,然后这个函数就会帮你实现这些功能。
    -sdk:不同语言对api的封装,更方便的使用提供的服务  #软件开发工具包 底层也是封装了api
    
    
    
# 腾讯云短信的sdk
	-老版本:2.0 :只针对于发短信(qcloudsms_py):https://cloud.tencent.com/document/product/382/11672
    -新版本:3.0:不仅仅针对于发短信(tencentcloud-sdk-python):https://cloud.tencent.com/document/product/382/43196
        
# 注意:python3.8以上,2.0用起来会有问题, 所以直接用3.0
# 原因用了内置的json模块
# 如何解决


# json:3.6以前是不能序列化bytes格式的

libs/sms/settings.py

APP_ID = ''
APP_KEY = ''
TEMPLATE_ID = ''
SMS_SIGN = ''

SECRET_ID = ''
SECRET_KEY = ''

libs/sms/init.py


from .v2 import send_sms as send_sms_v2
from .v3 import send_sms as send_sms_v3
from .code import gen_code


libs/sms/code.py

import random


# 生成4,或5位的验证码
def gen_code(length=4):
    code_str = ''
    for i in range(length):
        code_str += str(random.randrange(0, 9))  # python 是动态强类型,所以需要转换

    return code_str

libs/sms/v2.py

from qcloudsms_py import SmsSingleSender
from qcloudsms_py.httpclient import HTTPError

from . import settings
from utils.logging import logger


def send_sms(phone, code, exp=2):
    ssender = SmsSingleSender(settings.APP_ID, settings.APP_KEY)
    params = [code, exp]
    try:
        result = ssender.send_with_param(86, phone, settings.TEMPLATE_ID, params, sign=settings.SMS_SIGN, extend="",
                                         ext="")
        if result['result'] == 0:
            return True
        else:
            logger.error('发送短信失败,失败原因为:%s' % result['errmsg'])
            return False
    except Exception as e:
        logger.error('发送短信失败,失败原因为:%s' % str(e))
        return False

libs/sms/v3.py

from . import settings


def send_sms(phone, code, exp='2'):
    # -*- coding: utf-8 -*-
    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
    try:
        cred = credential.Credential(settings.SECRET_ID, settings.SECRET_KEY)

        httpProfile = HttpProfile()

        httpProfile.reqMethod = "POST"  # post请求(默认为post请求)
        httpProfile.reqTimeout = 30  # 请求超时时间,单位为秒(默认60秒)
        httpProfile.endpoint = "sms.tencentcloudapi.com"  # 指定接入地域域名(默认就近接入)

        # 非必要步骤:
        # 实例化一个客户端配置对象,可以指定超时时间等配置
        clientProfile = ClientProfile()
        clientProfile.signMethod = "TC3-HMAC-SHA256"  # 指定签名算法
        clientProfile.language = "en-US"
        clientProfile.httpProfile = httpProfile

        # 实例化要请求产品(以sms为例)的client对象
        # 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8
        client = sms_client.SmsClient(cred, "ap-guangzhou", clientProfile)

        # 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
        # 你可以直接查询SDK源码确定SendSmsRequest有哪些属性可以设置
        # 属性可能是基本类型,也可能引用了另一个数据结构
        # 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明
        req = models.SendSmsRequest()

        # 基本类型的设置:
        # SDK采用的是指针风格指定参数,即使对于基本类型你也需要用指针来对参数赋值。
        # SDK提供对基本类型的指针引用封装函数
        # 帮助链接:
        # 短信控制台: https://console.cloud.tencent.com/smsv2
        # sms helper: https://cloud.tencent.com/document/product/382/3773

        # 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666
        req.SmsSdkAppId = settings.APP_ID
        # 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看
        req.SignName = settings.SMS_SIGN
        # 短信码号扩展号: 默认未开通,如需开通请联系 [sms helper]
        req.ExtendCode = ""
        # 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回
        req.SessionContext = "xxx"
        # 国际/港澳台短信 senderid: 国内短信填空,默认未开通,如需开通请联系 [sms helper]
        req.SenderId = ""
        # 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
        # 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
        req.PhoneNumberSet = ["+86" + phone, ]
        # 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看
        req.TemplateId = settings.TEMPLATE_ID
        # 模板参数: 若无模板参数,则设置为空
        req.TemplateParamSet = [code, exp]

        # 通过client对象调用DescribeInstances方法发起请求。注意请求方法名与请求对象是对应的。
        # 返回的resp是一个DescribeInstancesResponse类的实例,与请求对象对应。
        resp = client.SendSms(req)

        # 输出json格式的字符串回包
        import json
        res_dict = json.loads(resp.to_json_string(indent=2))
        print(res_dict)
        if res_dict['SendStatusSet'][0]['Code'] == 'Ok':
            return True
        else:
            return False

    except TencentCloudSDKException as err:
        print(err)
        return False

2 发送短信(验证码)接口

class UserView(ViewSet):
    @action(methods=['GET'], detail=False)
    def send_sms(self, request):
        from libs.sms import gen_code, send_sms_v3
        # 从请求url获取phone
        phone = request.query_params.get('phone')
        #调用gen_code获得随机字符串
        sms_code = gen_code()
        #验证码不只是需要发送还需要保存起来 放到缓存,默认放到内存中
        #设置缓存
        cache.set(settings.SMS_CODE_CACHE%phone,sms_code)
        # 调用方法获得发送短信
        res = send_sms_v3(phone, sms_code)
        print(sms_code)
        if res:
            return APIResponse()
        else:
            return APIResponse(code=101, msg='发送失败,请稍后再试')

3 短信登录接口

# 前端传入(post):{mobile:1892322345,code:1234}
界面如下

3.1 serializer.py

class MobileSerializer(serializers.ModelSerializer):
    # code字段不是表字段,需要重写 他是验证码字段
    code = serializers.CharField(max_length=5, min_length=4)
    mobile= serializers.CharField()
    class Meta:
        model = User
        # 往里走--->反序列化校验:username和password---》只是校验长短
        # 往外走--->序列化---》username,icon
        fields = ['mobile', 'code']
        # 不需要序列化 所以不用这个
        # extra_kwargs = {
        #     'password': {'write_only': True},
        #     'icon': {'read_only': True}
        # }

    def validate(self, attrs):
        # 1.检验code是否正确
        self._check_code(attrs)
        # 2.根据手机号查用户
        user=self._get_user_by_mobile(attrs)
        # 3.签发token
        token=_get_token(user)
        # 4.把token 放入到当前对象中给view用
        self.context['token'] = token
        self.context['username'] = user.username
        # self.context['icon']=user.icon # icon的有点问题
        # 这个地址是服务端地址,服务端地址从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
	#检查code验证码是否正确
    def _check_code(self, attrs):
        # 发送验证码接口还要存一份验证码 和注册的手机号 方便后期验证
        code = attrs.get('code')
        # 取出该手机号对应的code
        mobile = attrs.get('mobile')
        old_code = cache.get(settings.SMS_CODE_CACHE % mobile)

        #比较code
        if not old_code==code:
            raise ValidationError('验证码错误')
	#判断用户是否存在
    def _get_user_by_mobile(self,attrs):
        mobile=attrs.get('mobile')
        user=User.objects.filter(mobile=mobile).first()
        if user:
            return user
        else:
            raise ValidationError('用户不存在')
            
#由于token获取经常用到拿出来写一个方法需要就调用一下减少了代码冗余
def _get_token(user):
    # 根据user获取payload
    payload = jwt_payload_handler(user)
    # 根据payload得到token
    token = jwt_encode_handler(payload);
    return token

3.2 views.py

class LoginView(GenericViewSet):
    queryset = User.objects.all()
    serializer_class = LoginSerializer
	
    #重写get_serializer_class方法 get serializer从这里取值
    def get_serializer_class(self):
        print(self.action)
        if self.action == 'mul_login':
            return self.LoginSerializer
        else:
            return MobileSerializer

    @action(methods=['POST'], detail=False, )  # user/login/mobile_login
    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})
            # 方式一
            # ser=MobileSerializer(data=request.data, context={'request': request})
            # 方式一 重写get_serializer

            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.3 cache的使用

from django.core.cache import cache

cache.set(key,value) #存
cache.get(key) #取

4 短信注册接口

# 前端传入的 {mobile:1234,code:1234,password:1234}  手机号 验证码 密码(这个有的网站没有)
# 自动给他设置一个密码 或者没有密码可以登陆之后修改密码 
# 新增--》User表中插入数据,username唯一,干脆手机号就是用户名
界面如下

4.1 serializer.py

class RegisterSerializer(serializers.ModelSerializer,Code_Token):
    # code 字段不存在 需要重写 不然
    code = serializers.CharField(write_only=True)


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

    def validate(self, attrs):
        # 1.校验手机号是否合法 没有必要去校验 因为如果手机号不合法 他在缓存取出code一对比就失败了code就已经不合法了,写上也没问题
        # 2.验证code
        self._check_code(attrs)
        # 3.创建出username 不然怎么存数据库
        # attrs{'mobile':xxx,'password':123,'username':xxx,'code':1234}
        attrs['username'] = attrs.get('mobile')

        # 4.踢出code 因为code不是表字段
        attrs.pop('code')


        return attrs





    # 因为在父类里 save方法触发 如果是新增触发create 修改触发update 所以自己需要重写create
    # validated_data 校验成功的数据
    def create(self, validated_data):
        # 重写目的是因为密码是秘文 传过来的是明文 需要使用create_user
        user = User.objects.create_user(**validated_data)
        print(user)
        return user

4.2 views.py

# 短信注册接口 继承GenericViewSet为了自动生成路由 CreateModelMixin为了create方法 写入数据库
class RegisterView(GenericViewSet,CreateModelMixin):
    # 数据库
    queryset = User.objects.all()
    # 序列化类
    serializer_class = RegisterSerializer


    # 重写create

    def create(self, request, *args, **kwargs):
        # 父类返回的是return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
        # 父类create里 self.perform_create(serializer) 会调用save方法
        # save方法 会触发  新增触发create   修改触发update方法  所以序列化类重写create
        res = super().create(request, *args, **kwargs)
        print(res)
        # data=父类的serializer.data
        return APIResponse(msg='注册成功')
        # data是一个字典 有mobile password等

5 登录注册前端完成(账号密码登录和短信验证码方式登录)

前端存的三个位置
1 cookie中  
2 localStorage  3 sessionStorage

cookie 可以设置过期时间,
	需要借助于第三方模块,vue-cookies
	-cnpm install vue-cookies
    -main.js中 设置
    	import cookies from 'vue-cookies'
		Vue.prototype.$cookies = cookies;
        
localStorage:永久存储
sessionStorage:关闭浏览器就失效 清空

5.1 Login.vue

<template>
    <div class="login">
        <div class="box">
            <i class="el-icon-close" @click="close_login"></i>
            <div class="content">
                <div class="nav">
                    <span :class="{active: login_method === 'is_pwd'}"
                          @click="change_login_method('is_pwd')">密码登录</span>
                    <span :class="{active: login_method === 'is_sms'}"
                          @click="change_login_method('is_sms')">短信登录</span>
                </div>
                <el-form v-if="login_method === 'is_pwd'">
                    <el-input
                            placeholder="用户名/手机号/邮箱"
                            prefix-icon="el-icon-user"
                            v-model="username"
                            clearable>
                    </el-input>
                    <el-input
                            placeholder="密码"
                            prefix-icon="el-icon-key"
                            v-model="password"
                            clearable
                            show-password>
                    </el-input>
                    <el-button type="primary" @click="mul_login">登录</el-button>
                </el-form>
                <el-form v-if="login_method === 'is_sms'">
                    <el-input
                            placeholder="手机号"
                            prefix-icon="el-icon-phone-outline"
                            v-model="mobile"
                            clearable
                            @blur="check_mobile">
                    </el-input>
                    <el-input
                            placeholder="验证码"
                            prefix-icon="el-icon-chat-line-round"
                            v-model="sms"
                            clearable>
                        <template slot="append">
                            <span class="sms" @click="send_sms">{{ sms_interval }}</span>
                        </template>
                    </el-input>
                    <el-button type="primary" @click="sms_login">登录</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_register">立即注册</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Login",
        data() {
            return {
                username: '',
                password: '',
                mobile: '',
                sms: '',
                login_method: 'is_pwd',
                sms_interval: '获取验证码',
                is_send: false,
            }
        },
        methods: {
            close_login() {
                this.$emit('close')
            },
            go_register() {
                this.$emit('go')
            },
            change_login_method(method) {
                this.login_method = method;
            },
            check_mobile() {
                if (!this.mobile) return;
                if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
                    this.$message({
                        message: '手机号有误',
                        type: 'warning',
                        duration: 1000,
                        onClose: () => {
                            this.mobile = '';
                        }
                    });
                    return false;
                }
                this.is_send = true;
            },
            send_sms() {

                if (!this.is_send) return;
                this.is_send = false;
                let sms_interval_time = 60;
                this.sms_interval = "发送中...";
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval = "获取验证码";
                        this.is_send = true; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval = `${sms_interval_time}秒后再发`;
                    }
                }, 1000);
                this.$axios.get(this.$settings.base_url + 'user/userinfo/send_sms/?phone=' + this.mobile).then(res => {
                    if (res.data.code == 100) {
                        this.$message({
                            message: '发送成功',
                            type: 'success'
                        });

                    } else {
                        this.$message({
                            message: '发送失败,请稍后再试',
                            type: 'error'
                        });
                    }
                })

            },
            mul_login() {
                if (this.username && this.password) {
                    // 发送axios请求
                    this.$axios.post(this.$settings.base_url + 'user/login/mul_login/',
                        {'username': this.username, 'password': this.password}).then(res => {
                        console.log(res.data)
                        if (res.data.code == 100) {
                            // token,username,存起来(存到哪?)
                            // 1 cookie中  2 localStorage  3 sessionStorage
                            // localStorage.setItem('name', 'lqz')
                            // sessionStorage.setItem('name', 'pyy')
                            this.$cookies.set('token', res.data.token, '7d')
                            this.$cookies.set('username', res.data.username, '7d')
                            // 关闭模态框
                            this.$emit("close")
                        } else {
                            this.$message({
                                message: '用户名或密码错误',
                                type: 'error'
                            });
                            this.username = ''
                        }
                    })

                } else {
                    this.$message({
                        message: '用户名或密码不能为空',
                        type: 'warning'
                    });
                }
            },
            sms_login() {
                if (this.mobile && this.sms) {
                    // 发送axios请求
                    this.$axios.post(this.$settings.base_url + 'user/login/mobile_login/',
                        {'mobile': this.mobile, 'code': this.sms}).then(res => {
                        console.log(res.data)
                        if (res.data.code == 100) {
                            this.$cookies.set('token', res.data.token, '7d')
                            this.$cookies.set('username', res.data.username, '7d')
                            // 关闭模态框
                            this.$emit("close")
                        } else {
                            this.$message({
                                message: '用户名或密码错误',
                                type: 'error'
                            });
                            this.username = ''
                        }
                    })

                } else {
                    this.$message({
                        message: '手机号或验证码不能为空',
                        type: 'warning'
                    });
                }


            }
        }
    }
</script>

<style scoped>
    .login {
        width: 100vw;
        height: 100vh;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 10;
        background-color: rgba(0, 0, 0, 0.5);
    }

    .box {
        width: 400px;
        height: 420px;
        background-color: white;
        border-radius: 10px;
        position: relative;
        top: calc(50vh - 210px);
        left: calc(50vw - 200px);
    }

    .el-icon-close {
        position: absolute;
        font-weight: bold;
        font-size: 20px;
        top: 10px;
        right: 10px;
        cursor: pointer;
    }

    .el-icon-close:hover {
        color: darkred;
    }

    .content {
        position: absolute;
        top: 40px;
        width: 280px;
        left: 60px;
    }

    .nav {
        font-size: 20px;
        height: 38px;
        border-bottom: 2px solid darkgrey;
    }

    .nav > span {
        margin: 0 20px 0 35px;
        color: darkgrey;
        user-select: none;
        cursor: pointer;
        padding-bottom: 10px;
        border-bottom: 2px solid darkgrey;
    }

    .nav > span.active {
        color: black;
        border-bottom: 3px solid black;
        padding-bottom: 9px;
    }

    .el-input, .el-button {
        margin-top: 40px;
    }

    .el-button {
        width: 100%;
        font-size: 18px;
    }

    .foot > span {
        float: right;
        margin-top: 20px;
        color: orange;
        cursor: pointer;
    }

    .sms {
        color: orange;
        cursor: pointer;
        display: inline-block;
        width: 70px;
        text-align: center;
        user-select: none;
    }
</style>

5.2 Header.vu

<template>
    <div class="header">
        <div class="slogan">
            <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p>
        </div>
        <div class="nav">
            <ul class="left-part">
                <li class="logo">
                    <router-link to="/">
                        <img src="../assets/img/head-logo.svg" alt="">
                    </router-link>
                </li>
                <li class="ele">
                    <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span>
                </li>
                <li class="ele">
                    <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span>
                </li>
                <li class="ele">
                    <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span>
                </li>
            </ul>

            <div class="right-part">
                <div v-if="username">
                    <span>{{username}}</span>
                    <span class="line">|</span>
                    <span @click="logout">退出</span>
                </div>
                <div v-else>
                    <span @click="put_login">登录</span>
                    <span class="line">|</span>
                    <span @click="put_register">注册</span>

                    <Login v-if="is_login" @close="close_login" @go="put_register"/>
                    <Register v-if="is_register" @close="close_register" @go="put_login"/>
                </div>

            </div>
        </div>
    </div>
</template>

<script>
    import Login from "./Login";
    import Register from "./Register";

    export default {
        name: "Header",
        data() {
            return {
                url_path: sessionStorage.url_path || '/',
                is_login: false,
                is_register: false,
                username: this.$cookies.get('username')
            }
        },
        methods: {
            goPage(url_path) {
                // 已经是当前路由就没有必要重新跳转
                if (this.url_path !== url_path) {
                    this.$router.push(url_path);
                }
                sessionStorage.url_path = url_path;
            },
            put_login() {
                this.is_login = true;
                this.is_register = false;
            },
            put_register() {
                this.is_login = false;
                this.is_register = true;
            },
            close_login() {
                this.is_login = false;
                this.username = this.$cookies.get('username')
            },
            close_register() {
                this.is_register = false;
            },
            logout() {
                this.$cookies.set('token', '')
                this.$cookies.set('username', '')
                this.username = ''
            }
        },
        created() {
            sessionStorage.url_path = this.$route.path;
            this.url_path = this.$route.path;
        },
        components: {
            Login, Register
        }
    }
</script>

<style scoped>

    .header {
        background-color: white;
        box-shadow: 0 0 5px 0 #aaa;
    }

    .header:after {
        content: "";
        display: block;
        clear: both;
    }

    .slogan {
        background-color: #eee;
        height: 40px;
    }

    .slogan p {
        width: 1200px;
        margin: 0 auto;
        color: #aaa;
        font-size: 13px;
        line-height: 40px;
    }

    .nav {
        background-color: white;
        user-select: none;
        width: 1200px;
        margin: 0 auto;

    }

    .nav ul {
        padding: 15px 0;
        float: left;
    }

    .nav ul:after {
        clear: both;
        content: '';
        display: block;
    }

    .nav ul li {
        float: left;
    }

    .logo {
        margin-right: 20px;
    }

    .ele {
        margin: 0 20px;
    }

    .ele span {
        display: block;
        font: 15px/36px '微软雅黑';
        border-bottom: 2px solid transparent;
        cursor: pointer;
    }

    .ele span:hover {
        border-bottom-color: orange;
    }

    .ele span.active {
        color: orange;
        border-bottom-color: orange;
    }

    .right-part {
        float: right;
    }

    .right-part .line {
        margin: 0 10px;
    }

    .right-part span {
        line-height: 68px;
        cursor: pointer;
    }

</style>
posted @ 2022-02-26 13:49  迪迦张  阅读(215)  评论(0编辑  收藏  举报