0108 luffy登录注册接口

昨日回顾

1、git的协同操作
	1)拿公钥换源远程仓库源链接 - 成为项目开发者
	2)根据源链接克隆远程仓库 - git clone 源地址
	3)参与项目的团队开发,永远要遵循 先pull后push,在pull之前要将所有修改的代码提交到本地版本库

2、冲突解决
	1)当不同开发者协同开发,导致远程仓库与本地仓库的版本不一致,在pull远程仓库到本地仓库时,会出现版本冲突
	2)两个仓库不同版本中,出现了相同文件的修改情况,会出现文件的冲突
	3)同一文件的修改,代码有重叠,一定会产生代码冲突,打开冲突的文件,文件中会表示冲突的开始与结束,分割线上下分别是冲突的代码
	>>>>>>>>>>header
	===========
	<<<<<<<<<<1321adsa21
	4)冲突的解决没有固定的结果,但是要将冲突的标识删除,根据代码实际情况,线下沟通,整合代码即可
	
3、登录业务
	1)多方式登录
	2)短信验证码
		腾讯短信服务 - 创建短信服务应用(appid、appkey),申请签名与模板
			-- 安装对应sdk
			-- 通过短信服务应用得到短信发送者sender
			-- 结合签名与模板、手机、验证码、其他所需参数,发送验证码
	3)手机验证码登录
	
	
4、注册业务
	1)手机注册验证
	2)短信验证码
	3)手机验证码密码注册

luffy后台

验证手机号是否已注册

// url.py

urlpatterns = [
    path('mobile/',views.MobileCheckAPIView.as_view()),
]
    
----------------------------------------------------------

// views.py

# 手机验证码是否已注册
class MobileCheckAPIView(APIView):
    def get(self,request,*args,**kwargs):
        # 从拼接参数中获取手机号
        mobile = request.query_paramas.get('mobile')
        # 对获取的数据进行校验
        if not mobile:
            return APIResponse(1,'mobile必须提供',http_status=400)
        # 对手机号格式进行校验
        if not re.match(r'1[3-9][0-9]{9}$',mobile):
            return APIResponse(1,msg='mobile格式有误',http_status=400)
        try:
            # 只要数据库中有,就代表已注册
            models.User.objects.get(mobile=mobile)
            return APIResponse(2,msg='手机号已注册')
        except:
            return APIResponse(0,msg='手机号未注册')
        
---------------------------------------------------------

使用手机号与验证码注册

'''urls.py'''

    # 手机号验证码方式的注册接口
  path('register/mobile/',views.RegisterMobileAPIView.as_view()),
-------------------------------------------------------

'''views.py'''

# 手机号与验证码注册
class RegisterMobileAPIView(APIView):
    def post(self,request):
        # 将前端传的数据进行反序列化处理(保存数据库)
        serializer = serializers.RegisterMobileserializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 校验通过保存获取obj对象
        user_obj = serializer.save
        # 将user对象序列化输出得到data,当做返回数据
        return APIResponse(results=serializers.RegisterMobileserializer(user_obj).data)
    
----------------------------------------------------------

'''serializers.py'''

# 手机号验证码注册的序列化与反序列化
class RegisterMobileserializer(serializers.ModelSerializer):
    # 反序列化的字段code(保存数据库)
    code = serializers.CharField(write_only=True,min_length=6,max_length=6)
    class Meta:
        model = models.User
        fields = ('username', 'mobile','password','code')
        # username,mobile序列化与反序列化,password不进行序列化给前端
        extra_kwargs = {
            'password': {
                'write_only': True
            },
            'username': {
                'read_only': True
            }
        }

    # 每一个反序列化字段都可以配置一个局部钩子
    #     注册提交后对手机号进行校验,对验证码格式进行校验,校验验证码一致
    # 手机号校验钩子
    def validate_mobile(self,value):
        if not re.match(r'^1[3-9][0-9]{9}$', value.username):  # 电话
            raise serializers.ValidationError('手机号格式不正确')
        return value

    # 验证码格式内容有误就不需要进行 取服务器存储的验证码(IO操作) 进行校验
    def validate_code(self, value):
        try:
            # 验证码格式数字
            int(value)
            return value
        except:
            raise serializers.ValidationError('验证码格式有误')


    # 全局校验

    def validate(self, attrs):
        mobile = attrs.get('mobile')
        code = attrs.pop('code')
        # 封装的获取云短信的函数方法
        old_code = cache.get(settings.SMS_CACHE_FORMAT % mobile)
        if code != old_code:
            raise serializers.ValidationError({'code': '验证码有误'})
        # 创建用户需要一些额外的信息,比如username
        attrs['username'] = mobile
        return attrs

    #create方法是否需要重写: 默认入库的密码是明文(重写)
    def create(self, validated_data):
        # auth组件的create_user方法进行密码密文创建
        return models.User.objects.create_user(**validated_data)
    

luffy前台

避免页面横向产生滚动条

会随着屏幕的缩放而缩放'overflow: hidden;'

/*避免横向产生滚动条*/
body {
    overflow: hidden;
}

img

header.vue

<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('/course')" :class="{active: url_path === '/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="!token">
                    <span @click="pull_login">登录</span>
                    <span class="line">|</span>
                    <span @click="pull_register">注册</span>
                </div>
                <div v-else>
                    <span>{{ username }}</span>
                    <span class="line">|</span>
                    <span @click="logout">注销</span>
                </div>
            </div>

        </div>


        <Login v-if="is_login" @close="close_login" @go="pull_register" @success="login_success"/>
        <Register v-if="is_register" @close="close_register" @go="pull_login" @success="register_success"/>

    </div>

</template>

<script>
    import Login from './Login'
    import Register from './Register'

    export default {
        name: "Header",
        components: {
            Login,
            Register,
        },
        data() {
            return {
                url_path: sessionStorage.url_path || '/',
                is_login: false,
                is_register: false,
                token: $cookies.get('token') || '',
                username: $cookies.get('username') || '',
            }
        },
        methods: {
            goPage(url_path) {
                // 已经是当前路由就没有必要重新跳转
                if (this.url_path !== url_path) {
                    this.$router.push(url_path);
                }
                sessionStorage.url_path = url_path;
            },
            // 显示登录模态框
            pull_login() {
                this.is_login = true;
                this.close_register();
            },
            close_login() {
                this.is_login = false;
            },
            pull_register() {
                this.is_register = true;
                this.close_login();
            },
            close_register() {
                this.is_register = false;
            },
            login_success() {
                this.close_login();
                this.token = this.$cookies.get('token') || '';
                this.username = this.$cookies.get('username') || '';
            },
            register_success() {
                this.pull_login();
            },
            logout() {
                this.$cookies.remove('token');
                this.$cookies.remove('username');
                this.token = '';
                this.username = '';
            }
        },
        created() {
            sessionStorage.url_path = this.$route.path;
            this.url_path = this.$route.path;
            // 也可以在data中只做空声明,钩子中初始化
            // this.token = this.$cookies.get('token') || '';
            // this.username = this.$cookies.get('username') || '';
        }
    }
</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 {
         1200px;
        margin: 0 auto;
        color: #aaa;
        font-size: 13px;
        line-height: 40px;
    }

    .nav {
        background-color: white;
        user-select: none;
         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;
    }

    .search {
        float: right;
        position: relative;
        margin-top: 22px;
    }

    .search input, .search button {
        border: none;
        outline: none;
        background-color: white;
    }

    .search input {
        border-bottom: 1px solid black;
    }

    .search input:focus {
        border-bottom-color: orange;
    }

    .search input:focus + button {
        color: orange;
    }

    .search .tips {
        position: absolute;
        bottom: 3px;
        left: 0;
    }

    .search .tips span {
        border-radius: 11px;
        background-color: #eee;
        line-height: 22px;
        display: inline-block;
        padding: 0 3px;
        margin-right: 3px;
        cursor: pointer;
        color: #666;
        font-size: 14px;
    }
</style>

登录模态框

img

img

img

img

img

img

img

img

img

img

<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="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="login_mobile">登录</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.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'get',
                    params: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 0) {
                        this.$message({
                            message: response.data.msg,
                            type: 'warning',
                            duration: 1000,
                        })
                    } else {
                        // 注册过的手机才允许发送验证码
                        this.is_send = true;
                    }
                }).catch(error => {
                    this.$message({
                        message: error.response.data.msg,
                        type: 'error'
                    })
                });
            },
            // 发送验证码
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                this.sms_interval = "发送中...";

                // 倒计时
                let sms_interval_time = 60;
                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({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 0) {
                        // 成功
                        this.$message({
                            message: '验证码发送成功',
                            type: 'success',
                        })
                    } else {
                        // 失败
                        this.$message({
                            message: '验证码发送失败',
                            type: 'error',
                        })
                    }
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '获取验证码异常',
                        type: 'error',
                    })
                });
            },
            // 验证码登录
            login_mobile() {
                if (!this.mobile || !this.sms) return false;

                this.$axios({
                    url: this.$settings.base_url + '/user/login/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms,
                    }
                }).then(response => {
                    // 要将响应的用户信息和token存储到cookies中
                    this.$cookies.set('token', response.data.results.token, '1d');
                    this.$cookies.set('username', response.data.results.username, '1d');

                    // 弹出框提示后,关闭登录界面
                    this.$message({
                        message: '登录成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '登录失败',
                        type: 'error',
                        duration: 1500,
                        onClose: () => {
                            this.mobile = '';
                            this.sms = '';
                        }
                    })
                });
            },
            // 密码登录
            login() {
                if (!this.username || !this.password) return false;
                this.$axios({
                    url: this.$settings.base_url + '/user/login/',
                    method: 'post',
                    data: {
                        username: this.username,
                        password: this.password,
                    }
                }).then(response => {
                    // 要将响应的用户信息和token存储到cookies中
                    this.$cookies.set('token', response.data.results.token, '1d');
                    this.$cookies.set('username', response.data.results.username, '1d');

                    // 弹出框提示后,关闭登录界面
                    this.$message({
                        message: '登录成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '登录失败',
                        type: 'error',
                        duration: 1500,
                        onClose: () => {
                            this.username = '';
                            this.password = '';
                        }
                    })
                });
            },
        }
    }
</script>

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

    .box {
         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;
         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 {
         100%;
        font-size: 18px;
    }

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

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

注册模态框

img

img

img

img

<template>
    <div class="register">
        <div class="box">
            <i class="el-icon-close" @click="close_register"></i>
            <div class="content">
                <div class="nav">
                    <span class="active">新用户注册</span>
                </div>
                <el-form>
                    <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-key"
                            v-model="password"
                            clearable
                            show-password>
                    </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="register">注册</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_login">立即登录</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Register",
        data() {
            return {
                mobile: '',
                password: '',
                sms: '',
                sms_interval: '获取验证码',
                is_send: false,
            }
        },
        methods: {
            close_register() {
                this.$emit('close', false)
            },
            go_login() {
                this.$emit('go')
            },
            // 校验手机对应用户是否注册
            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.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'get',
                    params: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 2) {
                        this.$message({
                            message: response.data.msg,
                            type: 'warning',
                            duration: 1000,
                        })
                    } else {
                        // 未注册过的手机才允许发送验证码
                        this.is_send = true;
                    }
                }).catch(error => {
                    this.$message({
                        message: error.response.data.msg,
                        type: 'error'
                    })
                });
            },
            // 发送验证码
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                this.sms_interval = "发送中...";

                // 倒计时
                let sms_interval_time = 60;
                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({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 0) {
                        // 成功
                        this.$message({
                            message: '验证码发送成功',
                            type: 'success',
                        })
                    } else {
                        // 失败
                        this.$message({
                            message: '验证码发送失败',
                            type: 'error',
                        })
                    }
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '获取验证码异常',
                        type: 'error',
                    })
                });
            },
            // 注册
            register() {
                if (!this.mobile || !this.password || !this.sms) return false;

                this.$axios({
                    url: this.$settings.base_url + '/user/register/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms,
                        password: this.password,
                    }
                }).then(response => {
                    // 弹出框提示后,关闭注册界面,前台登录页面
                    this.$message({
                        message: '注册成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '注册失败',
                        type: 'error',
                        duration: 1500,
                        onClose: () => {
                            this.mobile = '';
                            this.password = '';
                            this.sms = '';
                        }
                    })
                });
            }
        }
    }
</script>

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

    .box {
         400px;
        height: 480px;
        background-color: white;
        border-radius: 10px;
        position: relative;
        top: calc(50vh - 240px);
        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;
         280px;
        left: 60px;
    }

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

    .nav > span {
        margin-left: 90px;
        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 {
         100%;
        font-size: 18px;
    }

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

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

用户模块三大认证处理

img

# dev.py中设置三大认证

# drf框架的配置
REST_FRAMEWORK = {
    # 异常模块
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
    # 三大认证模块
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ],
    # 拥有具体权限限制的视图类局部配置权限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    # 拥有具体频率限制的视图类局部配置频率
    'DEFAULT_THROTTLE_CLASSES': [],
    # 频率限制scope的规则
    'DEFAULT_THROTTLE_RATES': {
        'sms': '1/min'
    },
}

---------------------------------------------------------

# 新建重写的throttles文件

from rest_framework.throttling import SimpleRateThrottle

class SMSRateThrottle(SimpleRateThrottle):
    scope = 'sms'
    def get_cache_key(self, request, view):
        mobile = request.query_params.get('mobile') or request.data.get('mobile')
        if not mobile:
            return None  # 没有提供手机号不进行限制
        return self.cache_format % {
            'scope': self.scope,
            'ident': mobile
        }

# 对views中的发送验证码接口进行频率限制

前后台交互

img

img

img

img

img

img

登录

前台登录手机号校验

<script>
    ......
// 校验手机对应用户是否存在
            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.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'get',
                    params: {
                        mobile: this.mobile
                    }
                    // 后台返回数据
                }).then(response => {
                    if (response.data.status === 0) {
                        // 弹出框,当后台校验的是0,时
                        this.$message({
                            message: response.data.msg,
                            type: 'warning',
                            duration: 1000,  // 弹出框停留时间
                        })
                    } else {
                        // 注册过的手机才允许发送验证码
                        this.is_send = true;
                    }
                    // 前台校验码发送失败
                }).catch(error => {
                    this.$message({
                        message: error.response.data.msg,
                        type: 'error'
                    })
                });
            },
</script>

前台获取验证码

<script>            
            // 发送验证码
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                this.sms_interval = "发送中...";

                // 倒计时
                let sms_interval_time = 60;
                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({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                    // 获取返回值
                }).then(response => {
                    if (response.data.status === 0) {
                        // 成功
                        this.$message({
                            message: '验证码发送成功',
                            type: 'success',
                        })
                    } else {
                        // 失败
                        this.$message({
                            message: '验证码发送失败',
                            type: 'error',
                        })
                    }
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '获取验证码异常',
                        type: 'error',
                    })
                });
            },

</script>

前台短信验证码登录

cookies的使用

在组件逻辑中使用

this.$cookies.set(key, value, exp)  // exp: '1s' | '1h' | '1d' 默认过期时间1天
	
this.$cookies.get(key)

this.$cookies.remove(key)

后台设置token的jwt设置

# dev.py 

# drf-jwt配置
# 设置token的过期时间
import datetime

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_ALLOW_REFRESH': False,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

前台代码

header中设置组件头,根据token显示登录注册或者用户名注销头部(v-if)

<script>
    // 验证码登录
            login_mobile() {
                // 判断手机号与验证码是否为空
                if (!this.mobile || !this.sms) return false;
                // 后台数据
                this.$axios({
                    url: this.$settings.base_url + '/user/login/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms,
                    }
                }).then(response => {
                    // 要将响应的用户信息和token存储到cookies中
                    this.$cookies.set('token', response.data.results.token, '1d');
                    this.$cookies.set('username', response.data.results.username, '1d');

                    // 弹出框提示后,关闭登录界面
                    this.$message({
                        message: '登录成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            // 向header组件发送事件,登录成功跳转刷新
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '登录失败',
                        type: 'error',
                        duration: 1500,
                        // 错误,将输入框置为空
                        onClose: () => {
                            this.mobile = '';
                            this.sms = '';
                        }
                    })
                });
            },
</script>

前台登录注销

前台的逻辑(删除前台cookies中的token与username即可)

前台密码登录

<script>

            // 密码登录
            login() {
                if (!this.username || !this.password) return false;
                this.$axios({
                    url: this.$settings.base_url + '/user/login/',
                    method: 'post',
                    data: {
                        username: this.username,
                        password: this.password,
                    }
                }).then(response => {
                    // 要将响应的用户信息和token存储到cookies中
                    this.$cookies.set('token', response.data.results.token, '1d');
                    this.$cookies.set('username', response.data.results.username, '1d');

                    // 弹出框提示后,关闭登录界面
                    this.$message({
                        message: '登录成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '登录失败',
                        type: 'error',
                        duration: 1500,
                        onClose: () => {
                            this.username = '';
                            this.password = '';
                        }
                    })
</script>

注册

<template>
    <div class="register">
        <div class="box">
            <i class="el-icon-close" @click="close_register"></i>
            <div class="content">
                <div class="nav">
                    <span class="active">新用户注册</span>
                </div>
                <el-form>
                    <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-key"
                            v-model="password"
                            clearable
                            show-password>
                    </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="register">注册</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_login">立即登录</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Register",
        data() {
            return {
                mobile: '',
                password: '',
                sms: '',
                sms_interval: '获取验证码',
                is_send: false,
            }
        },
        methods: {
            close_register() {
                this.$emit('close', false)
            },
            go_login() {
                this.$emit('go')
            },
            // 校验手机对应用户是否注册
            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.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'get',
                    params: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 2) {
                        this.$message({
                            message: response.data.msg,
                            type: 'warning',
                            duration: 1000,
                        })
                    } else {
                        // 未注册过的手机才允许发送验证码
                        this.is_send = true;
                    }
                }).catch(error => {
                    this.$message({
                        message: error.response.data.msg,
                        type: 'error'
                    })
                });
            },
            // 发送验证码
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                this.sms_interval = "发送中...";

                // 倒计时
                let sms_interval_time = 60;
                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({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    if (response.data.status === 0) {
                        // 成功
                        this.$message({
                            message: '验证码发送成功',
                            type: 'success',
                        })
                    } else {
                        // 失败
                        this.$message({
                            message: '验证码发送失败',
                            type: 'error',
                        })
                    }
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '获取验证码异常',
                        type: 'error',
                    })
                });
            },
            // 注册
            register() {
                if (!this.mobile || !this.password || !this.sms) return false;

                this.$axios({
                    url: this.$settings.base_url + '/user/register/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms,
                        password: this.password,
                    }
                }).then(response => {
                    // 弹出框提示后,关闭注册界面,前台登录页面
                    this.$message({
                        message: '注册成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                            this.$emit('success')
                        }
                    });
                }).catch(() => {
                    // 异常
                    this.$message({
                        message: '注册失败',
                        type: 'error',
                        duration: 1500,
                        onClose: () => {
                            this.mobile = '';
                            this.password = '';
                            this.sms = '';
                        }
                    })
                });
            }
        }
    }
</script>

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

    .box {
         400px;
        height: 480px;
        background-color: white;
        border-radius: 10px;
        position: relative;
        top: calc(50vh - 240px);
        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;
         280px;
        left: 60px;
    }

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

    .nav > span {
        margin-left: 90px;
        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 {
         100%;
        font-size: 18px;
    }

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

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

验证码的一次性处理

后台序列化中全局钩子,校验严验证码
# 验证码校验通过,验证码失效(验证码一次性使用)
cache.set(settings.SMS_CACHE_FORMAT % mobile, None, 0)
原文地址:https://www.cnblogs.com/fwzzz/p/12184500.html