前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python项目49-用户验证短信接口(可劲撸)

Python项目49-用户验证短信接口(可劲撸)

作者头像
DriverZeng
发布2022-09-26 13:54:31
1.7K0
发布2022-09-26 13:54:31
举报
文章被收录于专栏:Linux云计算及前后端开发

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


短信接口分析


短信发送验证码步骤

代码语言:javascript
复制
    # 1.拿到前台的手机号
    # 2.调用tzsms生成手机验证码
    # 3.调用txsms发送手机验证码
    # 4.失败反馈信息给前台
    # 5.成功服务器缓存手机验证码 - redis
    # 6.反馈成功信息给前台

使用Redis缓存验证码


配置Django使用redis

安装

代码语言:javascript
复制
(luffy) bash-3.2$ pip install django-redis

settings/dev.py

代码语言:javascript
复制
##  配置Django缓存 - 采用redis
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://10.0.0.51:6379/10",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
        }
    }
}

测试

scripts/test_dg_redis.py

代码语言:javascript
复制
from  django.core.cache import cache
import os, django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffyapi.settings.dev")
django.setup()

from user.models import User

user = User.objects.first()

from rest_framework.serializers import ModelSerializer
class UserModelSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ('username','password')

user_data = UserModelSerializer(user).data

print(user_data)

# 原生redis,django不支持直接存储

# import redis
#
# r = redis.Redis(host='10.0.0.51')
# r.set(user.username,user_data)

cache.set(user.username,user_data,60)
print(cache.get(user.username))

手机号验证接口


创建路由

apps/user/urls.py

代码语言:javascript
复制
from django.urls import path,re_path
from . import views
urlpatterns = [
    path('mobile/',views.MobileAPIView.as_view())
]

视图层

代码语言:javascript
复制
from rest_framework.views import APIView
from .models import User
from utils.repsonse import APIResponse
import re


# 注册逻辑:1.校验手机号是否存在 2.发送验证码 3.完成注册
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')

        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已注册')
        except:
            return APIResponse(0, '未注册')

短信接口


路由

apps/user/urls.py

代码语言:javascript
复制
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('mobile/', views.MobileAPIView.as_view()),
    path('sms/', views.SMSAPIView.as_view()),
]

视图层

apps/user/views.py

代码语言:javascript
复制
from rest_framework.views import APIView
from .models import User
from utils.repsonse import APIResponse
import re
from libs import txsms
from django.core.cache import cache


# 注册逻辑:1.校验手机号是否存在 2.发送验证码 3.完成注册
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')

        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已注册')
        except:
            return APIResponse(0, '未注册')


#  发送验证码接口
# 1.拿到前台的手机号
# 2.调用tzsms生成手机验证码
# 3.调用txsms发送手机验证码
# 4.失败反馈信息给前台
# 5.成功服务器缓存手机验证码 - redis
# 6.反馈成功信息给前台

class SMSAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # 1.拿到前台的手机号
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')

        # 2.调用tzsms生成手机验证码
        code = txsms.get_code()
        # 3.调用txsms发送手机验证码
        result = txsms.send_sms(mobile, code, 5)
        # 4.失败反馈信息给前台
        if not result:
            return APIResponse(1, '短信发送失败')
        # 5.成功服务器缓存手机验证码 - redis
        cache.set('sms_%s' % mobile, code, 5 * 60)
        # 6.反馈成功信息给前台
        return APIResponse(0, '短信发送成功')

前端注册页

Register.vue

代码语言:javascript
复制
<template>
    <div class="box">
        <img src="@/assets/img/Loginbg.jpg" alt="">
        <div class="register">
            <div class="register_box">
                <div class="register-title">注册路飞学城</div>
                <div class="inp">
                    <input v-model="mobile" @blur="checkMobile" type="text" placeholder="手机号码" class="user">
                    <input v-model="password" type="password" placeholder="用户密码" class="user">
                    <div class="sms">
                        <input v-model="sms" type="text" placeholder="输入验证码" class="user">
                        <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span>
                    </div>
                    <div id="geetest"></div>
                    <button class="register_btn" @click="registerMobile">注册</button>
                    <p class="go_login">已有账号
                        <router-link to="/login">直接登录</router-link>
                    </p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Register',
        data() {
            return {
                sms: "",
                mobile: "",
                password: "",
                is_send: false,  // 是否在60s内发送了短信
                sms_interval_tips: "获取验证码",
            }
        },
        created() {
        },
        methods: {
            checkMobile() {
                // 手机框必须填内容
                if (this.mobile.length < 1) {
                    return false;
                }
                // 手机号码格式是否正确
                // js正则语法糖 /正则语法/
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 验证手机号码是否已经注册了
                // this.$axios.get(this.$settings.base_url+"/user/mobile/?mobile="+this.mobile+"/");
                this.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    let data = response.data;
                    // window.console.log(data);
                    if (data.status !== 0) {
                        this.$message({
                            message: "对不起!手机号码已经被注册!",
                            type: 'warning'
                        });
                        return false;
                    } else {
                        this.$message({
                            message: "期待您加入我们!"
                        });
                    }
                }).catch(error => {
                    let data = error.response.data;
                    this.$message({
                        message: data.message
                    })
                })

            },
            send_sms() {
                // 发送短信
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 判断是否在60s内发送过短信
                if (this.is_send) {
                    this.$message({
                        message: "对不起,不能频繁发送短信验证!"
                    });
                    return false;
                }

                // 请求发送短信
                this.$axios({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    this.$message({
                        message: response.data.msg,
                    });

                }).catch(error => {
                    this.$message({
                        message: error.response.data.result,
                    })
                });

                // 修改短信的发送状态
                this.is_send = true;
                // 设置间隔时间60s
                let sms_interval_time = 60;
                // 设置短信发送间隔倒计时,.60s后把is_send改成false
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval_tips = "获取验证码";
                        this.is_send = false; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval_tips = `${sms_interval_time}秒后再次获取`;
                    }
                }, 1000);

            },
            registerMobile() {
                // 注册信息提交
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });

                    return false;
                }

                if (this.sms.length < 1) {
                    this.$message({
                        message: "短信验证码不能为空!"
                    });

                    return false;
                }

                if (this.password.length < 6 || this.password.length > 16) {
                    this.$message({
                        message: "对不起,密码长度必须在6-16个字符之间!"
                    });

                    return false;
                }

                this.$axios({
                    url: this.$settings.Host + 'user/register/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        password: this.password,
                        sms: this.sms
                    }
                }).then(response => {
                    let _this = this;

                    let status = response.data.status;
                    let msg = response.data.msg;
                    _this.$message({
                        message: msg,
                        onClose() {
                            if (status === 0) {
                                // 保存登录状态
                                sessionStorage.user_name = response.data.user.username;
                                // sessionStorage.user_mobile = response.data.user.mobile;
                                // 跳转到用户中心
                                // _this.$router.push('/user');
                            }
                        }
                    });



                }).catch(error => {
                    this.$message({
                        message: error.response.data.result
                    });
                })

            }
        },

    };
</script>

<style scoped>
    .box {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }

    .box img {
        width: 100%;
        min-height: 100%;
    }

    .box .register {
        position: absolute;
        width: 500px;
        height: 400px;
        left: 0;
        margin: auto;
        right: 0;
        bottom: 0;
        top: -238px;
    }

    .register .register-title {
        width: 100%;
        font-size: 24px;
        text-align: center;
        padding-top: 30px;
        padding-bottom: 30px;
        color: #4a4a4a;
        letter-spacing: .39px;
    }

    .register-title img {
        width: 190px;
        height: auto;
    }

    .register-title p {
        font-size: 18px;
        color: #fff;
        letter-spacing: .29px;
        padding-top: 10px;
        padding-bottom: 50px;
    }

    .register_box {
        width: 400px;
        height: auto;
        background: #fff;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
        border-radius: 4px;
        margin: 0 auto;
        padding-bottom: 40px;
    }

    .register_box .title {
        font-size: 20px;
        color: #9b9b9b;
        letter-spacing: .32px;
        border-bottom: 1px solid #e6e6e6;
        display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
    }

    .register_box .title span:nth-of-type(1) {
        color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
    }

    .inp {
        width: 350px;
        margin: 0 auto;
    }

    .inp input {
        outline: 0;
        width: 100%;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp input.user {
        margin-bottom: 16px;
    }

    .inp .rember {
        display: flex;
        justify-content: space-between;
        align-items: center;
        position: relative;
        margin-top: 10px;
    }

    .inp .rember p:first-of-type {
        font-size: 12px;
        color: #4a4a4a;
        letter-spacing: .19px;
        margin-left: 22px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        /*position: relative;*/
    }

    .inp .rember p:nth-of-type(2) {
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
        cursor: pointer;
    }

    .inp .rember input {
        outline: 0;
        width: 30px;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp .rember p span {
        display: inline-block;
        font-size: 12px;
        width: 100px;
        /*position: absolute;*/
        /*left: 20px;*/

    }

    #geetest {
        margin-top: 20px;
    }

    .register_btn {
        width: 100%;
        height: 45px;
        background: #84cc39;
        border-radius: 5px;
        font-size: 16px;
        color: #fff;
        letter-spacing: .26px;
        margin-top: 30px;
    }

    .inp .go_login {
        text-align: center;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .26px;
        padding-top: 20px;
    }

    .inp .go_login a {
        color: #84cc39;
        cursor: pointer;
    }

    .sms {
        position: relative;
    }

    .sms .sms_btn {
        position: absolute;
        top: -12px;
        right: 0;
        bottom: 0;
        margin: auto;
        width: 130px;
        text-align: center;
        height: 24px;
        color: #ff7000;
        cursor: pointer;
        border-left: 1px solid #999;
    }
</style>

短信过期时间


设置常量

在settings目录下创建一个const.py文件

settings/const.py

代码语言:javascript
复制
# 短信过期时间(单位:s)
SMS_EXP = 300

视图

代码语言:javascript
复制
from rest_framework.views import APIView
from .models import User
from utils.repsonse import APIResponse
import re
from libs import txsms
from django.core.cache import cache
from settings.const import SMS_EXP


# 注册逻辑:1.校验手机号是否存在 2.发送验证码 3.完成注册
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')

        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已注册')
        except:
            return APIResponse(0, '未注册')


#  发送验证码接口
# 1.拿到前台的手机号
# 2.调用tzsms生成手机验证码
# 3.调用txsms发送手机验证码
# 4.失败反馈信息给前台
# 5.成功服务器缓存手机验证码 - redis
# 6.反馈成功信息给前台

class SMSAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # 1.拿到前台的手机号
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')

        # 2.调用txsms生成手机验证码
        code = txsms.get_code()
        # 3.调用txsms发送手机验证码
        result = txsms.send_sms(mobile, code, SMS_EXP // 60)
        print(result)
        # 4.失败反馈信息给前台
        if not result:
            return APIResponse(1, '短信发送失败')
        # 5.成功服务器缓存手机验证码 - redis
        cache.set('sms_%s' % mobile, code, SMS_EXP)
        # 6.反馈成功信息给前台
        return APIResponse(0, '短信发送成功')

注册后台接口


路由

代码语言:javascript
复制
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('mobile/', views.MobileAPIView.as_view()),
    path('sms/', views.SMSAPIView.as_view()),
    path('register/', views.RegisterCreateAPIView.as_view()),
]

视图

代码语言:javascript
复制
from rest_framework.generics import CreateAPIView
from . import serializers
class RegisterCreateAPIView(CreateAPIView):
    # queryset = User.objects.filter(is_active=True)
    serializer_class = serializers.RegisterModelSerializer

    # 自定义响应结果
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # 校验失败就主动抛异常 => 自定义异常结果,配置异常模块
        user_obj = serializer.save()  # 要自定义入库逻辑,重写create方法
        headers = self.get_success_headers(serializer.data)
        # 响应结果需要格式化,使用序列化类要提供序列化与反序列化两套规则
        return APIResponse(0, 'ok',
            results=serializers.RegisterModelSerializer(user_obj).data,
            http_status=201,
            headers=headers
            )

序列化

user/serializers.py

代码语言:javascript
复制
from rest_framework import serializers
from . import models
import re
from django.core.cache import cache
from settings.const import SMS_CACHE_KEY
class RegisterModelSerializer(serializers.ModelSerializer):
    # 自定义反序列化字段的规则必须在字段声明时规定
    code = serializers.CharField(write_only=True, min_length=4, max_length=4)
    class Meta:
        model = models.User
        fields = ('mobile', 'password', 'code', 'username', 'email')
        extra_kwargs = {
            'password': {
                'min_length': 6,
                'max_length': 18,
                'write_only': True
            },
            'username': {
                'read_only': True
            },
            'email': {
                'read_only': True
            }
        }

    def validate_mobile(self, value):
        if not re.match(r'^1[3-9]\d{9}$', value):
            raise serializers.ValidationError('手机号有误')
        return value

    def validate(self, attrs):
        mobile = attrs.get('mobile')
        code = attrs.pop('code')  # code不入库
        old_code = cache.get(SMS_CACHE_KEY % {'mobile': mobile})
        if not old_code:
            raise serializers.ValidationError({'code': '验证码已失效'})
        if code != old_code:
            raise serializers.ValidationError({'code': '验证码错误'})
        # 验证码一旦验证成功,就失效(一次性)
        # cache.set(SMS_CACHE_KEY % {'mobile': mobile}, '0000', 1)
        return attrs

    # create方法重写:通过手机号注册的用户,用户名默认就是手机号
    def create(self, validated_data):
        mobile = validated_data.get('mobile')
        username = mobile
        password = validated_data.get('password')
        return models.User.objects.create_user(username=username, mobile=mobile, password=password)

自定义exception

settings/dev.py

代码语言:javascript
复制
# drf配置
from rest_framework import settings
REST_FRAMEWORK = {
    # 异常配置
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
    # 频率限制配置
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
        'sms': '1/m',
    },
}

utils/exception.py

代码语言:javascript
复制
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
from utils.logging import logger
from utils.response import APIResponse
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response is None:
        logger.error('%s - %s - %s' % (context['view'], context['request'].method, exc))
        return APIResponse(3, '异常',
            results={'detail': '服务器错误'},
           http_status=status.HTTP_500_INTERNAL_SERVER_ERROR,
           exception=True
        )
    return APIResponse(3, '异常', results=response.data, http_status=status.HTTP_401_UNAUTHORIZED)

注册前台逻辑


注册页面

Register.vue

代码语言:javascript
复制
<template>
    <div class="box">
        <img src="@/assets/img/Loginbg.jpg" alt="">
        <div class="register">
            <div class="register_box">
                <div class="register-title">注册路飞学城</div>
                <div class="inp">
                    <input v-model="mobile" @blur="checkMobile" type="text" placeholder="手机号码" class="user">
                    <input v-model="password" type="password" placeholder="用户密码" class="user">
                    <div class="sms">
                        <input v-model="sms" type="text" placeholder="输入验证码" class="user">
                        <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span>
                    </div>
                    <div id="geetest"></div>
                    <button class="register_btn" @click="registerMobile">注册</button>
                    <p class="go_login">已有账号
                        <router-link to="/login">直接登录</router-link>
                    </p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Register',
        data() {
            return {
                sms: "",
                mobile: "",
                password: "",
                is_send: false,  // 是否在60s内发送了短信
                sms_interval_tips: "获取验证码",
            }
        },
        created() {
        },
        methods: {
            checkMobile() {
                // 手机框必须填内容
                if (this.mobile.length < 1) {
                    return false;
                }
                // 手机号码格式是否正确
                // js正则语法糖 /正则语法/
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 验证手机号码是否已经注册了
                // this.$axios.get(this.$settings.base_url+"/user/mobile/?mobile="+this.mobile+"/");
                this.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    let data = response.data;
                    // window.console.log(data);
                    if (data.status !== 0) {
                        this.$message({
                            message: "对不起!手机号码已经被注册!",
                            type: 'warning'
                        });
                        return false;
                    } else {
                        this.$message({
                            message: "期待您加入我们!"
                        });
                    }
                }).catch(error => {
                    let data = error.response.data;
                    this.$message({
                        message: data.message
                    })
                })

            },
            send_sms() {
                // 发送短信
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 判断是否在60s内发送过短信
                if (this.is_send) {
                    this.$message({
                        message: "对不起,不能频繁发送短信验证!"
                    });
                    return false;
                }

                // 请求发送短信
                this.$axios({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    this.$message({
                        message: response.data.msg,
                    });

                }).catch(error => {
                    this.$message({
                        message: error.response.data.result,
                    })
                });

                // 修改短信的发送状态
                this.is_send = true;
                // 设置间隔时间60s
                let sms_interval_time = 60;
                // 设置短信发送间隔倒计时,.60s后把is_send改成false
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval_tips = "获取验证码";
                        this.is_send = false; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval_tips = `${sms_interval_time}秒后再次获取`;
                    }
                }, 1000);

            },
            registerMobile() {
                // 注册信息提交
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }

                if (this.sms.length < 1) {
                    this.$message({
                        message: "短信验证码不能为空!"
                    });
                    return false;
                }

                if (this.password.length < 6 || this.password.length > 18) {
                    this.$message({
                        message: "对不起,密码长度必须在6-16个字符之间!"
                    });
                    return false;
                }

                this.$axios({
                    url: this.$settings.base_url + '/user/register/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        password: this.password,
                        code: this.sms
                    }
                }).then(response => {
                    window.console.log(response);
                    // let _this = this;
                    let status = response.data.status;
                    this.$message({
                        message: '注册成功',
                        duration: 1500,
                        onClose: () => {
                            if (status === 0) {
                                // 跳转到主页
                                this.$router.push('/')
                            }
                        }
                    });
                }).catch(error => {
                    window.console.log(error)
                    // this.$message({
                    //     message: error.response.data.result
                    // });
                })

            }
        },

    };
</script>

<style scoped>
    .box {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }

    .box img {
        width: 100%;
        min-height: 100%;
    }

    .box .register {
        position: absolute;
        width: 500px;
        height: 400px;
        left: 0;
        margin: auto;
        right: 0;
        bottom: 0;
        top: -238px;
    }

    .register .register-title {
        width: 100%;
        font-size: 24px;
        text-align: center;
        padding-top: 30px;
        padding-bottom: 30px;
        color: #4a4a4a;
        letter-spacing: .39px;
    }

    .register-title img {
        width: 190px;
        height: auto;
    }

    .register-title p {
        font-size: 18px;
        color: #fff;
        letter-spacing: .29px;
        padding-top: 10px;
        padding-bottom: 50px;
    }

    .register_box {
        width: 400px;
        height: auto;
        background: #fff;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
        border-radius: 4px;
        margin: 0 auto;
        padding-bottom: 40px;
    }

    .register_box .title {
        font-size: 20px;
        color: #9b9b9b;
        letter-spacing: .32px;
        border-bottom: 1px solid #e6e6e6;
        display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
    }

    .register_box .title span:nth-of-type(1) {
        color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
    }

    .inp {
        width: 350px;
        margin: 0 auto;
    }

    .inp input {
        outline: 0;
        width: 100%;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp input.user {
        margin-bottom: 16px;
    }

    .inp .rember {
        display: flex;
        justify-content: space-between;
        align-items: center;
        position: relative;
        margin-top: 10px;
    }

    .inp .rember p:first-of-type {
        font-size: 12px;
        color: #4a4a4a;
        letter-spacing: .19px;
        margin-left: 22px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        /*position: relative;*/
    }

    .inp .rember p:nth-of-type(2) {
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
        cursor: pointer;
    }

    .inp .rember input {
        outline: 0;
        width: 30px;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp .rember p span {
        display: inline-block;
        font-size: 12px;
        width: 100px;
        /*position: absolute;*/
        /*left: 20px;*/

    }

    #geetest {
        margin-top: 20px;
    }

    .register_btn {
        width: 100%;
        height: 45px;
        background: #84cc39;
        border-radius: 5px;
        font-size: 16px;
        color: #fff;
        letter-spacing: .26px;
        margin-top: 30px;
    }

    .inp .go_login {
        text-align: center;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .26px;
        padding-top: 20px;
    }

    .inp .go_login a {
        color: #84cc39;
        cursor: pointer;
    }

    .sms {
        position: relative;
    }

    .sms .sms_btn {
        position: absolute;
        top: -12px;
        right: 0;
        bottom: 0;
        margin: auto;
        width: 130px;
        text-align: center;
        height: 24px;
        color: #ff7000;
        cursor: pointer;
        border-left: 1px solid #999;
    }
</style>

多方式登录


安装jwt

代码语言:javascript
复制
(luffy) bash-3.2$  pip install djangorestframework-jwt

序列化类

代码语言:javascript
复制
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
class LoginModelSerializer(serializers.ModelSerializer):
    usr = serializers.CharField(write_only=True)
    pwd = serializers.CharField(write_only=True)
    class Meta:
        model = models.User
        fields = ['usr', 'pwd', 'username', 'mobile', 'email']
        extra_kwargs = {
            'username': {
                'read_only': True
            },
            'mobile': {
                'read_only': True
            },
            'email': {
                'read_only': True
            },
        }

    def validate(self, attrs):
        usr = attrs.get('usr')
        pwd = attrs.get('pwd')

        # 多方式登录:各分支处理得到该方式下对应的用户
        if re.match(r'.+@.+', usr):
            user_query = models.User.objects.filter(email=usr)
        elif re.match(r'1[3-9][0-9]{9}', usr):
            user_query = models.User.objects.filter(mobile=usr)
        else:
            user_query = models.User.objects.filter(username=usr)
        user_obj = user_query.first()

        # 签发:得到登录用户,签发token并存储在实例化对象中
        if user_obj and user_obj.check_password(pwd):
            # 签发token,将token存放到 实例化类对象的token 名字中
            payload = jwt_payload_handler(user_obj)
            token = jwt_encode_handler(payload)
            # 将当前用户与签发的token都保存在序列化对象中
            self.user = user_obj
            self.token = token
            return attrs

        raise serializers.ValidationError({'data': '数据有误'})

视图

代码语言:javascript
复制
from rest_framework.views import APIView
from .models import User
from utils.repsonse import APIResponse
import re
# 注册逻辑:1.校验手机号是否存在 2.发送验证码 3.完成注册
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')
        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已注册')
        except:
            return APIResponse(0, '未注册')


# 发送验证码接口分析
from libs import txsms
from django.core.cache import cache
from settings.const import SMS_EXP, SMS_CACHE_KEY
# from .thorttles import SMSRateThrottle
class SMSAPIView(APIView):
    # 频率限制
    # throttle_classes = [SMSRateThrottle]
    def post(self, request, *args, **kwargs):
        # 1)拿到前台的手机号
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(2, '数据有误')
        # 2)调用txsms生成手机验证码
        code = txsms.get_code()
        # 3)调用txsms发送手机验证码
        result = txsms.send_sms(mobile, code, SMS_EXP // 60)
        # 4)失败反馈信息给前台
        if not result:
            return APIResponse(1, '短信发送失败')
        # 5)成功服务器缓存手机验证码 - 用缓存存储(方便管理) - redis
        cache.set(SMS_CACHE_KEY % {'mobile': mobile}, code, SMS_EXP)
        # 6)反馈成功信息给前台
        return APIResponse(0, '短信发送成功')


from rest_framework.generics import CreateAPIView
from . import serializers
class RegisterCreateAPIView(CreateAPIView):
    # queryset = User.objects.filter(is_active=True)
    serializer_class = serializers.RegisterModelSerializer

    # 自定义响应结果
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # 校验失败就主动抛异常 => 自定义异常结果,配置异常模块
        user_obj = serializer.save()  # 要自定义入库逻辑,重写create方法
        headers = self.get_success_headers(serializer.data)
        # 响应结果需要格式化,使用序列化类要提供序列化与反序列化两套规则
        return APIResponse(0, 'ok',
            results=serializers.RegisterModelSerializer(user_obj).data,
            http_status=201,
            headers=headers
            )

# 多方式登录
class LoginAPIView(APIView):
    # 1) 禁用认证与权限组件
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        # 2) 拿到前台登录信息,交给序列化类,规则:账号用usr传,密码用pwd传
        user_ser = serializers.LoginModelSerializer(data=request.data)
        # 3) 序列化类校验得到登录用户与token存放在序列化对象中
        user_ser.is_valid(raise_exception=True)
        # 4) 取出登录用户与token返回给前台
        return APIResponse(token=user_ser.token, results=serializers.LoginModelSerializer(user_ser.user).data)

路由

代码语言:javascript
复制
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('mobile/', views.MobileAPIView.as_view()),
    path('sms/', views.SMSAPIView.as_view()),
    path('register/', views.RegisterCreateAPIView.as_view()),
    path('login/', views.LoginAPIView.as_view()),
]

前台登录页

代码语言:javascript
复制
<template>
    <div class="login box">
        <img src="@/assets/img/Loginbg.jpg" alt="">
        <div class="login">
            <div class="login-title">
                <img src="@/assets/img/Logotitle.png" alt="">
                <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
            </div>
            <div class="login_box">
                <div class="title">
                    <span :class="{active: a0}" @click="changeLogin(0)">密码登录</span>
                    <span :class="{active: a1}" @click="changeLogin(1)">短信登录</span>
                </div>
                <div class="inp" v-if="login_type===0">
                    <input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user">
                    <input v-model="password" type="password" name="" class="pwd" placeholder="密码">
                    <div id="geetest1"></div>
                    <div class="rember">
                        <p>
                            <input id="checkbox" type="checkbox" class="no" v-model="remember"/>
                            <span>记住密码</span>
                        </p>
                        <p>忘记密码</p>
                    </div>
                    <button class="login_btn" @click="loginAction">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
                <div class="inp" v-show="login_type===1">
                    <input v-model="mobile" type="text" placeholder="手机号码" class="user">
                    <div class="sms">
                        <input v-model="sms" type="text" placeholder="输入验证码" class="user">
                        <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span>
                    </div>
                    <button class="login_btn" @click="loginMobile">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Login',
        data() {
            return {
                a0: 1,
                a1: 0,
                login_type: 0,
                username: "",
                password: "",
                remember: false,
                mobile: "",
                sms: "",
                is_send: false,  // 是否在60s内发送了短信
                sms_interval_tips: "获取验证码",
            }
        },
        methods: {
            changeLogin(i) {
                this.login_type = i;
                if (i) {
                    this.a0 = 0;
                    this.a1 = 1;
                } else {
                    this.a0 = 1;
                    this.a1 = 0;
                }
            },

            loginAction() {
                if (!this.username || !this.password) {
                    return
                }
                this.$axios({
                    url: this.$settings.base_url + '/user/login/',
                    method: 'post',
                    data: {
                        'usr': this.username,
                        'pwd': this.password
                    }
                }).then((response) => {
                    // 判断用户是否要记住密码
                    // window.console.log(">>>>", response.data);
                    if (this.remember) {  // 记住密码
                        sessionStorage.clear();
                        localStorage.token = response.data.token;
                        localStorage.user_name = response.data.results.username;
                        localStorage.user_mobile = response.data.results.mobile;

                    } else { /// 没记住密码
                        localStorage.clear();
                        sessionStorage.token = response.data.token;
                        sessionStorage.user_name = response.data.results.username;
                        sessionStorage.user_mobile = response.data.results.mobile;
                    }

                    // 页面跳转
                    this.$alert("欢迎回来!", "登录成功!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            // 跳转页面
                            // this.$router.go(-1); // 返回上一页
                            // 进行制定的网站内部地址跳转
                            // this.$router.push("站内地址");
                            this.$router.push("/");  // 前往主页
                        }
                    })
                }).catch(() => {
                    this.$alert("检查账号密码!", "登录失败!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            this.username = '';
                            this.password = '';
                        }
                    });
                })
            },

            send_sms() {
                // 发送短信
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 判断是否在60s内发送过短信
                if (this.is_send) {
                    this.$message({
                        message: "对不起,不能频繁发送短信验证!"
                    });
                    return false;
                }

                // 请求发送短信
                this.$axios({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    this.$message({
                        message: response.data.msg,
                    });

                }).catch(error => {
                    this.$message({
                        message: error.response.data.result,
                    })
                });

                // 修改短信的发送状态
                this.is_send = true;
                // 设置间隔时间60s
                let sms_interval_time = 60;
                // 设置短信发送间隔倒计时,.60s后把is_send改成false
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval_tips = "获取验证码";
                        this.is_send = false; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval_tips = `${sms_interval_time}秒后再次获取`;
                    }
                }, 1000);

            },

            loginMobile() {
                // 注册信息提交
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                if (this.sms.length < 1) {
                    this.$message({
                        message: "短信验证码不能为空!"
                    });
                    return false;
                }

                this.$axios({
                    url: this.$settings.base_url + '/user/login/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms
                    }
                }).then(response => {
                    let _this = this;
                    let status = response.data.status;
                    let msg = response.data.msg;
                    _this.$message({
                        message: msg,
                        duration: 1500,
                        onClose() {
                            if (status === 0) {
                                // 保存登录状态
                                sessionStorage.token = response.data.token;
                                sessionStorage.user_name = response.data.results.username;
                                sessionStorage.user_mobile = response.data.results.mobile;
                                // 跳转到主页
                                _this.$router.push('/');
                            } else {
                                // 清空数据库
                                sessionStorage.clear();
                                // 清空输入框
                                _this.mobile = '';
                                _this.sms = '';
                            }
                        }
                    });
                }).catch(error => {
                    this.$message({
                        message: error.response.data.result
                    });
                })

            },
        },

    };
</script>

<style scoped>
    .box {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }

    .box img {
        width: 100%;
        min-height: 100%;
    }

    .box .login {
        position: absolute;
        width: 500px;
        height: 400px;
        left: 0;
        margin: auto;
        right: 0;
        bottom: 0;
        top: -338px;
    }

    .login .login-title {
        width: 100%;
        text-align: center;
        padding-top: 20px;
    }

    .login-title img {
        width: 190px;
        height: auto;
    }

    .login-title p {
        font-family: PingFangSC-Regular;
        font-size: 18px;
        color: #fff;
        letter-spacing: .29px;
        padding-top: 10px;
        padding-bottom: 50px;
    }

    .login_box {
        width: 400px;
        height: auto;
        background: #fff;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
        border-radius: 4px;
        margin: 0 auto;
        padding-bottom: 40px;
    }

    .login_box .title {
        font-size: 20px;
        color: #9b9b9b;
        letter-spacing: .32px;
        border-bottom: 1px solid #e6e6e6;
        display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
    }

    .login_box .title span.active {
        color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
    }

    .inp {
        width: 350px;
        margin: 0 auto;
    }

    .inp input {
        outline: 0;
        width: 100%;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp input.user {
        margin-bottom: 16px;
    }

    .inp .rember {
        display: flex;
        justify-content: space-between;
        align-items: center;
        position: relative;
        margin-top: 10px;
    }

    .inp .rember p:first-of-type {
        font-size: 12px;
        color: #4a4a4a;
        letter-spacing: .19px;
        margin-left: 22px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        /*position: relative;*/
    }

    .inp .rember p:nth-of-type(2) {
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
        cursor: pointer;
    }

    .inp .rember input {
        outline: 0;
        width: 30px;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp .rember p span {
        display: inline-block;
        font-size: 12px;
        width: 100px;
        /*position: absolute;*/
        /*left: 20px;*/

    }

    #geetest {
        margin-top: 20px;
    }

    .login_btn {
        width: 100%;
        height: 45px;
        background: #84cc39;
        border-radius: 5px;
        font-size: 16px;
        color: #fff;
        letter-spacing: .26px;
        margin-top: 30px;
    }

    .inp .go_login {
        text-align: center;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .26px;
        padding-top: 20px;
    }

    .inp .go_login a {
        color: #84cc39;
        cursor: pointer;
    }

    #get_code {
        border: 0;
        width: 120px;
        height: 30px;
        background-color: antiquewhite;
        outline: none;
    }

    #get_code:active {
        color: white;
    }
    #checkbox {
        width: 20px;
        height: 20px;
    }
    .sms {
        position: relative;
    }

    .sms .sms_btn {
        position: absolute;
        top: -12px;
        right: 0;
        bottom: 0;
        margin: auto;
        width: 130px;
        text-align: center;
        height: 24px;
        color: #ff7000;
        cursor: pointer;
        border-left: 1px solid #999;
    }
</style>

前台登录注销


登录注销

components/Header.vue

代码语言:javascript
复制
<template>
    <div class="header-box">
        <div class="header">
            <div class="content">
                <div class="logo full-left">
                    <router-link to="/"><img @click="jump('/')" src="@/assets/img/logo.svg" alt=""></router-link>
                </div>
                <ul class="nav full-left">
                    <li><span @click="jump('/course')" :class="this_nav=='/course'?'this':''">免费课</span></li>
                    <li><span @click="jump('/light-course')" :class="this_nav=='/light-course'?'this':''">轻课</span></li>
                    <li><span>学位课</span></li>
                    <li><span>题库</span></li>
                    <li><span>老男孩教育</span></li>
                </ul>
                <div class="login-bar full-right">
                    <div class="shop-cart full-left">
                        <img src="@/assets/img/cart.svg" alt="">
                        <span><router-link to="/cart">购物车</router-link></span>
                    </div>
                    <div class="login-box full-left">
                        <span v-if="!token">
                            <router-link to="/login">登录</router-link>
                            &nbsp;|&nbsp;
                            <router-link to="/register">注册</router-link>
                        </span>
                        <span v-else>
                            <router-link to="/user">{{ username }}</router-link>
                            &nbsp;|&nbsp;
                            <i @click="logoutAction">注销</i>
                        </span>

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

<script>
    export default {
        name: "Header",
        data() {
            return {
                this_nav: "",
                token: '',
            }
        },
        created() {
            this.this_nav = localStorage.this_nav;
            this.token = sessionStorage.token || localStorage.token || '';
            this.username = sessionStorage.user_name || localStorage.user_name || '';
        },
        methods: {
            jump(location) {
                localStorage.this_nav = location;
                // vue-router除了提供router-link标签跳转页面以外,还提供了js跳转的方式
                this.$router.push(location);
            },
            logoutAction() {
                //  清除登录信息
                sessionStorage.clear();
                localStorage.clear();
                this.token = '';
                this.username = '';
            }
        }
    }
</script>

<style scoped>
    .header-box {
        height: 80px;
    }

    .header {
        width: 100%;
        height: 80px;
        box-shadow: 0 0.5px 0.5px 0 #c9c9c9;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        margin: auto;
        z-index: 99;
        background: #fff;
    }

    .header .content {
        max-width: 1200px;
        width: 100%;
        margin: 0 auto;
    }

    .header .content .logo {
        height: 80px;
        line-height: 80px;
        margin-right: 50px;
        cursor: pointer;
    }

    .header .content .logo img {
        vertical-align: middle;
    }

    .header .nav li {
        float: left;
        height: 80px;
        line-height: 80px;
        margin-right: 30px;
        font-size: 16px;
        color: #4a4a4a;
        cursor: pointer;
    }

    .header .nav li span {
        padding-bottom: 16px;
        padding-left: 5px;
        padding-right: 5px;
    }

    .header .nav li span a {
        display: inline-block;
    }

    .header .nav li .this {
        color: #4a4a4a;
        border-bottom: 4px solid #ffc210;
    }

    .header .nav li:hover span {
        color: #000;
    }

    .header .login-bar {
        height: 80px;
    }

    .header .login-bar .shop-cart {
        margin-right: 20px;
        border-radius: 17px;
        background: #f7f7f7;
        cursor: pointer;
        font-size: 14px;
        height: 28px;
        width: 88px;
        margin-top: 30px;
        line-height: 32px;
        text-align: center;
    }

    .header .login-bar .shop-cart:hover {
        background: #f0f0f0;
    }

    .header .login-bar .shop-cart img {
        width: 15px;
        margin-right: 4px;
        margin-left: 6px;
    }

    .header .login-bar .shop-cart span {
        margin-right: 6px;
    }

    .header .login-bar .login-box {
        margin-top: 33px;
    }

    .header .login-bar .login-box span {
        color: #4a4a4a;
        cursor: pointer;
    }

    .header .login-bar .login-box span:hover {
        color: #000000;
    }

    .full-left {
        float: left !important;
    }

    .full-right {
        float: right !important;
    }

    .el-carousel__arrow {
        width: 120px;
        height: 120px;
    }

    .el-checkbox__input.is-checked .el-checkbox__inner,
    .el-checkbox__input.is-indeterminate .el-checkbox__inner {
        background: #ffc210;
        border-color: #ffc210;
        border: none;
    }

    .el-checkbox__inner:hover {
        border-color: #9b9b9b;
    }

    .el-checkbox__inner {
        width: 16px;
        height: 16px;
        border: 1px solid #9b9b9b;
        border-radius: 0;
    }

    .el-checkbox__inner::after {
        height: 9px;
        width: 5px;
    }

    a {
        color: #333;
    }
</style>

修改登录页面

Login.vue

代码语言:javascript
复制
<template>
    <div class="login box">
        <img src="@/assets/img/Loginbg.jpg" alt="">
        <div class="login">
            <div class="login-title">
                <img src="@/assets/img/Logotitle.png" alt="">
                <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
            </div>
            <div class="login_box">
                <div class="title">
                    <span :class="{active: a0}" @click="changeLogin(0)">密码登录</span>
                    <span :class="{active: a1}" @click="changeLogin(1)">短信登录</span>
                </div>
                <div class="inp" v-if="login_type===0">
                    <input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user">
                    <input v-model="password" type="password" name="" class="pwd" placeholder="密码">
                    <div id="geetest1"></div>
                    <div class="rember">
                        <p>
                            <input id="checkbox" type="checkbox" class="no" v-model="remember"/>
                            <span>记住密码</span>
                        </p>
                        <p>忘记密码</p>
                    </div>
                    <button class="login_btn" @click="loginAction">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
                <div class="inp" v-show="login_type===1">
                    <input v-model="mobile" type="text" placeholder="手机号码" class="user">
                    <div class="sms">
                        <input v-model="sms" type="text" placeholder="输入验证码" class="user">
                        <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span>
                    </div>
                    <button class="login_btn" @click="loginMobile">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Login',
        data() {
            return {
                a0: 1,
                a1: 0,
                login_type: 0,
                username: "",
                password: "",
                remember: false,
                mobile: "",
                sms: "",
                is_send: false,  // 是否在60s内发送了短信
                sms_interval_tips: "获取验证码",
            }
        },
        methods: {
            changeLogin(i) {
                this.login_type = i;
                if (i) {
                    this.a0 = 0;
                    this.a1 = 1;
                } else {
                    this.a0 = 1;
                    this.a1 = 0;
                }
            },

            loginAction() {
                if (!this.username || !this.password) {
                    return
                }
                this.$axios({
                    url: this.$settings.base_url + '/user/login/',
                    method: 'post',
                    data: {
                        'usr': this.username,
                        'pwd': this.password
                    }
                }).then((response) => {
                    // 判断用户是否要记住密码
                    // window.console.log(">>>>", response.data);
                    if (this.remember) {  // 记住密码
                        sessionStorage.clear();
                        localStorage.token = response.data.token;
                        localStorage.user_name = response.data.results.username;
                        localStorage.user_mobile = response.data.results.mobile;

                    } else { /// 没记住密码
                        localStorage.clear();
                        sessionStorage.token = response.data.token;
                        sessionStorage.user_name = response.data.results.username;
                        sessionStorage.user_mobile = response.data.results.mobile;
                    }

                    // 页面跳转
                    this.$alert("欢迎回来!", "登录成功!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            // 跳转页面
                            // this.$router.go(-1); // 返回上一页
                            // 进行制定的网站内部地址跳转
                            // this.$router.push("站内地址");
                            this.$router.push("/");  // 前往主页
                        }
                    })
                }).catch(() => {
                    this.$alert("检查账号密码!", "登录失败!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            this.username = '';
                            this.password = '';
                        }
                    });
                })
            },

            send_sms() {
                // 发送短信
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 判断是否在60s内发送过短信
                if (this.is_send) {
                    this.$message({
                        message: "对不起,不能频繁发送短信验证!"
                    });
                    return false;
                }

                // 请求发送短信
                this.$axios({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    this.$message({
                        message: response.data.msg,
                    });

                }).catch(error => {
                    this.$message({
                        message: error.response.data.result,
                    })
                });

                // 修改短信的发送状态
                this.is_send = true;
                // 设置间隔时间60s
                let sms_interval_time = 60;
                // 设置短信发送间隔倒计时,.60s后把is_send改成false
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval_tips = "获取验证码";
                        this.is_send = false; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval_tips = `${sms_interval_time}秒后再次获取`;
                    }
                }, 1000);

            },

            loginMobile() {
                // 注册信息提交
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                if (this.sms.length < 1) {
                    this.$message({
                        message: "短信验证码不能为空!"
                    });
                    return false;
                }

                this.$axios({
                    url: this.$settings.base_url + '/user/login/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms
                    }
                }).then(response => {
                    let _this = this;
                    let status = response.data.status;
                    let msg = response.data.msg;
                    _this.$message({
                        message: msg,
                        duration: 1500,
                        onClose() {
                            if (status === 0) {
                                // 保存登录状态
                                sessionStorage.token = response.data.token;
                                sessionStorage.user_name = response.data.results.username;
                                sessionStorage.user_mobile = response.data.results.mobile;
                                // 跳转到主页
                                _this.$router.push('/');
                            } else {
                                // 清空数据库
                                sessionStorage.clear();
                                // 清空输入框
                                _this.mobile = '';
                                _this.sms = '';
                            }
                        }
                    });
                }).catch(error => {
                    this.$message({
                        message: error.response.data.result
                    });
                })

            },
        },

    };
</script>

<style scoped>
    .box {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }

    .box img {
        width: 100%;
        min-height: 100%;
    }

    .box .login {
        position: absolute;
        width: 500px;
        height: 400px;
        left: 0;
        margin: auto;
        right: 0;
        bottom: 0;
        top: -338px;
    }

    .login .login-title {
        width: 100%;
        text-align: center;
        padding-top: 20px;
    }

    .login-title img {
        width: 190px;
        height: auto;
    }

    .login-title p {
        font-family: PingFangSC-Regular;
        font-size: 18px;
        color: #fff;
        letter-spacing: .29px;
        padding-top: 10px;
        padding-bottom: 50px;
    }

    .login_box {
        width: 400px;
        height: auto;
        background: #fff;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
        border-radius: 4px;
        margin: 0 auto;
        padding-bottom: 40px;
    }

    .login_box .title {
        font-size: 20px;
        color: #9b9b9b;
        letter-spacing: .32px;
        border-bottom: 1px solid #e6e6e6;
        display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
    }

    .login_box .title span.active {
        color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
    }

    .inp {
        width: 350px;
        margin: 0 auto;
    }

    .inp input {
        outline: 0;
        width: 100%;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp input.user {
        margin-bottom: 16px;
    }

    .inp .rember {
        display: flex;
        justify-content: space-between;
        align-items: center;
        position: relative;
        margin-top: 10px;
    }

    .inp .rember p:first-of-type {
        font-size: 12px;
        color: #4a4a4a;
        letter-spacing: .19px;
        margin-left: 22px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        /*position: relative;*/
    }

    .inp .rember p:nth-of-type(2) {
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
        cursor: pointer;
    }

    .inp .rember input {
        outline: 0;
        width: 30px;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp .rember p span {
        display: inline-block;
        font-size: 12px;
        width: 100px;
        /*position: absolute;*/
        /*left: 20px;*/

    }

    #geetest {
        margin-top: 20px;
    }

    .login_btn {
        width: 100%;
        height: 45px;
        background: #84cc39;
        border-radius: 5px;
        font-size: 16px;
        color: #fff;
        letter-spacing: .26px;
        margin-top: 30px;
    }

    .inp .go_login {
        text-align: center;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .26px;
        padding-top: 20px;
    }

    .inp .go_login a {
        color: #84cc39;
        cursor: pointer;
    }

    #get_code {
        border: 0;
        width: 120px;
        height: 30px;
        background-color: antiquewhite;
        outline: none;
    }

    #get_code:active {
        color: white;
    }
    #checkbox {
        width: 20px;
        height: 20px;
    }
    .sms {
        position: relative;
    }

    .sms .sms_btn {
        position: absolute;
        top: -12px;
        right: 0;
        bottom: 0;
        margin: auto;
        width: 130px;
        text-align: center;
        height: 24px;
        color: #ff7000;
        cursor: pointer;
        border-left: 1px solid #999;
    }
</style>

短信登录


后台路由

代码语言:javascript
复制
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('mobile/', views.MobileAPIView.as_view()),
    path('sms/', views.SMSAPIView.as_view()),
    path('register/', views.RegisterCreateAPIView.as_view()),
    path('login/', views.LoginAPIView.as_view()),
    path('login/mobile/', views.LoginMobileAPIView.as_view()),
]

后台视图

代码语言:javascript
复制
# 手机验证码登录
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
class LoginMobileAPIView(APIView):
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        code = request.data.get('code')
        if not mobile or not code:
            return APIResponse(1, '数据有误')
        old_code = cache.get(SMS_CACHE_KEY % {'mobile': mobile})
        if code != old_code:
            return APIResponse(1, '验证码错误')
        try:
            user = User.objects.get(mobile=mobile)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, results=serializers.LoginModelSerializer(user).data)
        except:
            return APIResponse(1, '用户不存在')

前台登录页

代码语言:javascript
复制
<template>
    <div class="login box">
        <img src="@/assets/img/Loginbg.jpg" alt="">
        <div class="login">
            <div class="login-title">
                <img src="@/assets/img/Logotitle.png" alt="">
                <p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
            </div>
            <div class="login_box">
                <div class="title">
                    <span :class="{active: a0}" @click="changeLogin(0)">密码登录</span>
                    <span :class="{active: a1}" @click="changeLogin(1)">短信登录</span>
                </div>
                <div class="inp" v-if="login_type===0">
                    <input v-model="username" type="text" placeholder="用户名 / 手机号码" class="user">
                    <input v-model="password" type="password" name="" class="pwd" placeholder="密码">
                    <div id="geetest1"></div>
                    <div class="rember">
                        <p>
                            <input id="checkbox" type="checkbox" class="no" v-model="remember"/>
                            <span>记住密码</span>
                        </p>
                        <p>忘记密码</p>
                    </div>
                    <button class="login_btn" @click="loginAction">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
                <div class="inp" v-show="login_type===1">
                    <input v-model="mobile" type="text" placeholder="手机号码" class="user">
                    <div class="sms">
                        <input v-model="sms" type="text" placeholder="输入验证码" class="user">
                        <span class="sms_btn" @click="send_sms">{{sms_interval_tips}}</span>
                    </div>
                    <button class="login_btn" @click="loginMobile">登录</button>
                    <p class="go_login">没有账号 <router-link to="/register">立即注册</router-link></p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Login',
        data() {
            return {
                a0: 1,
                a1: 0,
                login_type: 0,
                username: "",
                password: "",
                remember: false,
                mobile: "",
                sms: "",
                is_send: false,  // 是否在60s内发送了短信
                sms_interval_tips: "获取验证码",
            }
        },
        methods: {
            changeLogin(i) {
                this.login_type = i;
                if (i) {
                    this.a0 = 0;
                    this.a1 = 1;
                } else {
                    this.a0 = 1;
                    this.a1 = 0;
                }
            },

            loginAction() {
                if (!this.username || !this.password) {
                    return
                }
                this.$axios({
                    url: this.$settings.base_url + '/user/login/',
                    method: 'post',
                    data: {
                        'usr': this.username,
                        'pwd': this.password
                    }
                }).then((response) => {
                    // 判断用户是否要记住密码
                    // window.console.log(">>>>", response.data);
                    if (this.remember) {  // 记住密码
                        sessionStorage.clear();
                        localStorage.token = response.data.token;
                        localStorage.user_name = response.data.results.username;
                        localStorage.user_mobile = response.data.results.mobile;

                    } else { /// 没记住密码
                        localStorage.clear();
                        sessionStorage.token = response.data.token;
                        sessionStorage.user_name = response.data.results.username;
                        sessionStorage.user_mobile = response.data.results.mobile;
                    }

                    // 页面跳转
                    this.$alert("欢迎回来!", "登录成功!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            // 跳转页面
                            // this.$router.go(-1); // 返回上一页
                            // 进行制定的网站内部地址跳转
                            // this.$router.push("站内地址");
                            this.$router.push("/");  // 前往主页
                        }
                    })
                }).catch(() => {
                    this.$alert("检查账号密码!", "登录失败!", {
                        confirmButtonText: '确定',
                        callback: () => {
                            this.username = '';
                            this.password = '';
                        }
                    });
                })
            },

            send_sms() {
                // 发送短信
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                // 判断是否在60s内发送过短信
                if (this.is_send) {
                    this.$message({
                        message: "对不起,不能频繁发送短信验证!"
                    });
                    return false;
                }

                // 请求发送短信
                this.$axios({
                    url: this.$settings.base_url + '/user/sms/',
                    method: 'post',
                    data: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    this.$message({
                        message: response.data.msg,
                    });

                }).catch(error => {
                    this.$message({
                        message: error.response.data.result,
                    })
                });

                // 修改短信的发送状态
                this.is_send = true;
                // 设置间隔时间60s
                let sms_interval_time = 60;
                // 设置短信发送间隔倒计时,.60s后把is_send改成false
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval_tips = "获取验证码";
                        this.is_send = false; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval_tips = `${sms_interval_time}秒后再次获取`;
                    }
                }, 1000);

            },

            loginMobile() {
                // 注册信息提交
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "对不起!手机号码格式有误!"
                    });
                    return false;
                }
                if (this.sms.length < 1) {
                    this.$message({
                        message: "短信验证码不能为空!"
                    });
                    return false;
                }

                this.$axios({
                    url: this.$settings.base_url + '/user/login/mobile/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        code: this.sms
                    }
                }).then(response => {
                    let _this = this;
                    let status = response.data.status;
                    let msg = response.data.msg;
                    _this.$message({
                        message: msg,
                        duration: 1500,
                        onClose() {
                            if (status === 0) {
                                // 保存登录状态
                                sessionStorage.token = response.data.token;
                                sessionStorage.user_name = response.data.results.username;
                                sessionStorage.user_mobile = response.data.results.mobile;
                                // 跳转到主页
                                _this.$router.push('/');
                            } else {
                                // 清空数据库
                                sessionStorage.clear();
                                // 清空输入框
                                _this.mobile = '';
                                _this.sms = '';
                            }
                        }
                    });
                }).catch(error => {
                    this.$message({
                        message: error.response.data.result
                    });
                })

            },
        },

    };
</script>

<style scoped>
    .box {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }

    .box img {
        width: 100%;
        min-height: 100%;
    }

    .box .login {
        position: absolute;
        width: 500px;
        height: 400px;
        left: 0;
        margin: auto;
        right: 0;
        bottom: 0;
        top: -338px;
    }

    .login .login-title {
        width: 100%;
        text-align: center;
        padding-top: 20px;
    }

    .login-title img {
        width: 190px;
        height: auto;
    }

    .login-title p {
        font-family: PingFangSC-Regular;
        font-size: 18px;
        color: #fff;
        letter-spacing: .29px;
        padding-top: 10px;
        padding-bottom: 50px;
    }

    .login_box {
        width: 400px;
        height: auto;
        background: #fff;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .5);
        border-radius: 4px;
        margin: 0 auto;
        padding-bottom: 40px;
    }

    .login_box .title {
        font-size: 20px;
        color: #9b9b9b;
        letter-spacing: .32px;
        border-bottom: 1px solid #e6e6e6;
        display: flex;
        justify-content: space-around;
        padding: 50px 60px 0 60px;
        margin-bottom: 20px;
        cursor: pointer;
    }

    .login_box .title span.active {
        color: #4a4a4a;
        border-bottom: 2px solid #84cc39;
    }

    .inp {
        width: 350px;
        margin: 0 auto;
    }

    .inp input {
        outline: 0;
        width: 100%;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp input.user {
        margin-bottom: 16px;
    }

    .inp .rember {
        display: flex;
        justify-content: space-between;
        align-items: center;
        position: relative;
        margin-top: 10px;
    }

    .inp .rember p:first-of-type {
        font-size: 12px;
        color: #4a4a4a;
        letter-spacing: .19px;
        margin-left: 22px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        /*position: relative;*/
    }

    .inp .rember p:nth-of-type(2) {
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
        cursor: pointer;
    }

    .inp .rember input {
        outline: 0;
        width: 30px;
        height: 45px;
        border-radius: 4px;
        border: 1px solid #d9d9d9;
        text-indent: 20px;
        font-size: 14px;
        background: #fff !important;
    }

    .inp .rember p span {
        display: inline-block;
        font-size: 12px;
        width: 100px;
        /*position: absolute;*/
        /*left: 20px;*/

    }

    #geetest {
        margin-top: 20px;
    }

    .login_btn {
        width: 100%;
        height: 45px;
        background: #84cc39;
        border-radius: 5px;
        font-size: 16px;
        color: #fff;
        letter-spacing: .26px;
        margin-top: 30px;
    }

    .inp .go_login {
        text-align: center;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .26px;
        padding-top: 20px;
    }

    .inp .go_login a {
        color: #84cc39;
        cursor: pointer;
    }

    #get_code {
        border: 0;
        width: 120px;
        height: 30px;
        background-color: antiquewhite;
        outline: none;
    }

    #get_code:active {
        color: white;
    }
    #checkbox {
        width: 20px;
        height: 20px;
    }
    .sms {
        position: relative;
    }

    .sms .sms_btn {
        position: absolute;
        top: -12px;
        right: 0;
        bottom: 0;
        margin: auto;
        width: 130px;
        text-align: center;
        height: 24px;
        color: #ff7000;
        cursor: pointer;
        border-left: 1px solid #999;
    }
</style>

短信接口频率限制


重写频率限制

user/thorttles.py

代码语言:javascript
复制
from rest_framework.throttling import SimpleRateThrottle

class SMSRateThrottle(SimpleRateThrottle):
    scope = 'sms'
    def get_cache_key(self, request, view):
        mobile = request.data.get('mobile') or request.query_params.get('mobile')
        if not mobile:
            return None
        return self.cache_format % {'scope': self.scope, 'ident': mobile}

视图层

代码语言:javascript
复制
from rest_framework.views import APIView
from .models import User
from utils.repsonse import APIResponse
import re
# 注册逻辑:1.校验手机号是否存在 2.发送验证码 3.完成注册
class MobileAPIView(APIView):
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(1, '数据有误')
        try:
            User.objects.get(mobile=mobile)
            return APIResponse(2, '已注册')
        except:
            return APIResponse(0, '未注册')


# 发送验证码接口分析
from libs import txsms
from django.core.cache import cache
from settings.const import SMS_EXP, SMS_CACHE_KEY
from .thorttles import SMSRateThrottle
class SMSAPIView(APIView):
    # 频率限制
    throttle_classes = [SMSRateThrottle]
    def post(self, request, *args, **kwargs):
        # 1)拿到前台的手机号
        mobile = request.data.get('mobile')
        if not mobile or not re.match(r'^1[3-9]\d{9}$', mobile):
            return APIResponse(2, '数据有误')
        # 2)调用txsms生成手机验证码
        code = txsms.get_code()
        # 3)调用txsms发送手机验证码
        result = txsms.send_sms(mobile, code, SMS_EXP // 60)
        # 4)失败反馈信息给前台
        if not result:
            return APIResponse(1, '短信发送失败')
        # 5)成功服务器缓存手机验证码 - 用缓存存储(方便管理) - redis
        cache.set(SMS_CACHE_KEY % {'mobile': mobile}, code, SMS_EXP)
        # 6)反馈成功信息给前台
        return APIResponse(0, '短信发送成功')


from rest_framework.generics import CreateAPIView
from . import serializers
class RegisterCreateAPIView(CreateAPIView):
    # queryset = User.objects.filter(is_active=True)
    serializer_class = serializers.RegisterModelSerializer

    # 自定义响应结果
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # 校验失败就主动抛异常 => 自定义异常结果,配置异常模块
        user_obj = serializer.save()  # 要自定义入库逻辑,重写create方法
        headers = self.get_success_headers(serializer.data)
        # 响应结果需要格式化,使用序列化类要提供序列化与反序列化两套规则
        return APIResponse(0, 'ok',
            results=serializers.RegisterModelSerializer(user_obj).data,
            http_status=201,
            headers=headers
            )

# 多方式登录
class LoginAPIView(APIView):
    # 1) 禁用认证与权限组件
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        # 2) 拿到前台登录信息,交给序列化类,规则:账号用usr传,密码用pwd传
        user_ser = serializers.LoginModelSerializer(data=request.data)
        # 3) 序列化类校验得到登录用户与token存放在序列化对象中
        user_ser.is_valid(raise_exception=True)
        # 4) 取出登录用户与token返回给前台
        return APIResponse(token=user_ser.token, results=serializers.LoginModelSerializer(user_ser.user).data)


# 手机验证码登录
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
class LoginMobileAPIView(APIView):
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        mobile = request.data.get('mobile')
        code = request.data.get('code')
        if not mobile or not code:
            return APIResponse(1, '数据有误')
        old_code = cache.get(SMS_CACHE_KEY % {'mobile': mobile})
        if code != old_code:
            return APIResponse(1, '验证码错误')
        try:
            user = User.objects.get(mobile=mobile)
            payload = jwt_payload_handler(user)
            token = jwt_encode_handler(payload)
            return APIResponse(token=token, results=serializers.LoginModelSerializer(user).data)
        except:
            return APIResponse(1, '用户不存在')

django配置

settings/dev.py

代码语言:javascript
复制
# drf配置
from rest_framework import settings
REST_FRAMEWORK = {
    # 异常配置
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
    # 频率限制配置
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
        'sms': '1/m',
    },
}

接口缓存

接口每次访问都会去查数据库 ,这样数据库的压力会很大,所以我们要对接口进行缓存。


home视图

代码语言:javascript
复制
from rest_framework.generics import ListAPIView
from utils.repsonse import APIResponse
from . import models, serializers
from django.core.cache import cache
from rest_framework.response import Response


class BannerListAPIView(ListAPIView):
    queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('-orders')
    serializer_class = serializers.BannerModelSerializer

    # 找缓存,缓存有走缓存,缓存没有走数据库

    def list(self, request, *args, **kwargs):
        banner_data = cache.get('banner_list')
        if not banner_data:
            print('走数据库')
            response = super().list(request, *args, **kwargs)
            banner_data = response.data
            # 建立缓存
            cache.set('banner_list', banner_data)
        return Response(banner_data)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 短信接口分析
  • 使用Redis缓存验证码
  • 手机号验证接口
  • 短信接口
  • 短信过期时间
  • 注册后台接口
  • 注册前台逻辑
  • 多方式登录
  • 前台登录注销
  • 短信登录
  • 短信接口频率限制
  • 接口缓存
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档