Luffy--------------(四)

一.頻率限制短信接口html

settings/dev.py:前端

# drf配置
REST_FRAMEWORK = {
    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
        'sms': '1/min'
    },
}

apps/user/throttles.py:vue

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
        }

user/views.py:ios

from libs.txm import get_code, send_sms
from django.core.cache import cache
from .throttles import SMSRateThrottle
from . import models, serializers
from settings.const import SMS_CODE_EXC

class SMSAPIView(APIView):
    throttle_classes = [SMSRateThrottle]
    def post(self, request, *args, **kwargs):
        # 拿到前端的手機號
        mobile = request.data.get('mobile')
        # 完成手機號的校驗(是否合法)
        if not mobile or not re.match(r'^1[3-9][0-9]{9}$', mobile):
            return APIResponse(1, '手機號有誤')
        # 產生驗證碼
        code = get_code()
        # 通知第三方發送短信
        result = send_sms(mobile, code, SMS_CODE_EXC // 60)

        # 失敗:響應前臺發送失敗
        if not result:
            return APIResponse(1, '短信發送失敗')

        # 成功:緩存驗證碼(校驗備用),響應前臺發送成功
        cache.set('%s_code' % mobile, code, SMS_CODE_EXC)

        # print(cache.get('%s_code' % mobile))
        return APIResponse(0, '短信發送成功')

 二.手機號驗證接口redis

前臺手機號校驗register.vue:vue-router

 methods: {
            checkMobile() {
                if (this.mobile.length < 1) {
                    return false;
                }

                // 手機號碼格式是否正確
                if (!/^1[3-9]\d{9}$/.test(this.mobile)) {
                    this.$message({
                        message: "對不起!手機號碼格式有誤!"
                    });
                    return false;
                }

                // 驗證手機號碼是否已經註冊了
                this.$axios({
                    url: this.$settings.base_url + '/user/mobile/',
                    method: 'get',
                    params: {
                        mobile: this.mobile
                    }
                }).then(response => {
                    let data = response.data;
                    // window.console.log(data);
                    if (data.status != 0) {
                        this.$message({
                            message: "對不起!手機號碼已經被註冊!",
                            duration: 1000
                        });
                        return false;
                    } else {
                        this.$message({
                            message: "期待您加入咱們!",
                            duration: 1000,
                            // onClose: () => {
                            //     console.log('信息框關閉了')
                            // }
                        });
                    }
                }).catch(error => {
                    let data = error.response.data;
                    this.$message({
                        message: data.message
                    })
                })

            },
            ...

後臺手機號校驗:數據庫

          user/urlsdjango

from django.urls import path

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

        user/views.pyaxios

class MobileAPIView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取手機號
        mobile = request.query_params.get('mobile')
        if not mobile:
            return APIResponse(1, '手機號必須提供')
        # 校驗手機號是否合法
        if not re.match(r'^1[3-9][0-9]{9}$', mobile):
            return APIResponse(1, '手機號有誤')
        # 校驗手機號是否存在
        try:
            models.User.objects.get(mobile=mobile)
            return APIResponse(1, '手機號已註冊')
        except:
            return APIResponse(0, '手機號未註冊')

三.註冊接口實現(用戶名是手機號)windows

    settings/const.py:

# 驗證碼過時時間(s),替換過時時間
SMS_CODE_EXC = 300000

    前臺發送短信修訂register.vue:

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,
                    });
                    // 修改短信的發送狀態
                    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);

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

            },

   後臺註冊接口:

      user/ url.py

from django.urls import path

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

     views.py

class RegisterAPIView(APIView):
    def post(self, request, *args, **kwargs):
        # 將請求數據 手機密碼驗證碼 => 帳號手機密碼驗證碼
        request_data = request.data
        request_data['username'] = request_data.get('mobile')
        # 校驗
        user_ser = serializers.UserModelSerializer(data=request_data)
        if user_ser.is_valid():
            user_ser.save()
            return APIResponse(0, '註冊成功', results=user_ser.data)
        else:
            return APIResponse(1, '註冊失敗', results=user_ser.errors)

      序列化類user/serializers.py

from rest_framework import serializers
from . import models
from django.core.cache import cache
import re
class UserModelSerializer(serializers.ModelSerializer):
    code = serializers.CharField(write_only=True, min_length=4, max_length=4)
    class Meta:
        model = models.User
        fields = ('username', 'password', 'mobile', 'code')
        extra_kwargs = {
            'password': {
                'write_only': True
            }
        }

    def validate_mobile(self, value):
        if not re.match(r'^1[3-9][0-9]{9}$', value):
            raise serializers.ValidationError('手機號有誤')
        return value

    def validate(self, attrs):
        code = attrs.pop('code')
        mobile = attrs.get('mobile')
        old_code = cache.get('%s_code' % mobile)
        if code != old_code:
            raise serializers.ValidationError({'驗證碼': '驗證碼有誤'})
        return attrs

    def create(self, validated_data):
        return models.User.objects.create_user(**validated_data)
#重寫create方法,密碼爲密文

注.手機號註冊不能相同,models.py中將mobile字段加一個unique=true

註冊前臺register.vue:

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.base_url + '/user/register/',
                    method: 'post',
                    data: {
                        mobile: this.mobile,
                        password: this.password,
                        code: this.sms
                    }
                }).then(response => {
                    let status = response.data.status;
                    let msg = response.data.msg;
                    if (status == 0) {
                        this.$message({
                            message: msg,
                            duration: 1000,
                            onClose: () => {
                                this.$router.push('/login')
                            }
                        });
                    } else {
                        // 實際根據錯誤信息,提示錯誤的具體輸入框
                        this.mobile = '';
                        this.password = '';
                        this.sms = '';
                        this.$message({
                            message: msg,
                            duration: 1000,
                        });
                    }
                }).catch(error => {
                    this.$message({
                        message: error.response.data.result
                    });
                })

            }
        },

四.登陸接口的實現

兩種登陸路由:

from django.urls import path

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

user/views

首先安裝jwt:  pip install djangorestframework-jwt

from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler
class LoginAPIView(APIView):
    authentication_classes = ()
    permission_classes = ()
    def post(self, request, *args, **kwargs):
        # username可能攜帶的不止是用戶名,可能仍是用戶的其它惟一標識 手機號 郵箱
        username = request.data.get('username')
        password = request.data.get('password')

        if not (username and password):
            return APIResponse(1, '帳號密碼必須')

        # 若是username匹配上手機號正則 => 多是手機登陸
        if re.match(r'^1[3-9][0-9]{9}$', username):
            try:
                # 手動經過 user 簽發 jwt-token
                user = models.User.objects.get(mobile=username)
            except:
                return APIResponse(1, '該手機未註冊')

        # 郵箱登陸 等

        # 帳號登陸
        else:
            try:
                # 手動經過 user 簽發 jwt-token
                user = models.User.objects.get(username=username)
            except:
                return APIResponse(1, '該帳號未註冊')

        # 得到用戶後,校驗密碼並簽發token
        if not user.check_password(password):
            return APIResponse(1, '密碼錯誤')
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return APIResponse(0, 'ok', results={
            'username': user.username,
            'mobile': user.mobile,
            'token': token
        })

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 and code):
            return APIResponse(1, '手機號與驗證碼必須')
        # 驗證碼獲取並校驗
        old_code = cache.get('%s_code' % mobile)
        # 驗證碼實際開發只能夠使用一次
        # cache.set('%s_code' % mobile, '0000', 1)
        if code != old_code:
            return APIResponse(1, '驗證碼錯誤')
        # 經過手機號獲取用戶
        try:
            user = models.User.objects.get(mobile=mobile)
        except:
            return APIResponse(1, '該帳號未註冊')

        # 簽發token
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)
        return APIResponse(0, 'ok', results={
            'username': user.username,
            'mobile': user.mobile,
            'token': token
        })

登陸前臺login.vue:

<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: "獲取驗證碼",
            }
        },
        created() {
            // 攔截已登陸狀況
            let token = this.$cookies.get('token');
            if (token) {
                this.$message({
                    message: '無需重複登陸',
                    duration: 1000,
                    onClose: () => {
                        this.$router.go(-1)
                    }
                })
            }
        },
        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: {
                        'username': this.username,
                        'password': this.password
                    }
                }).then((response) => {
                    // 判斷用戶是否要記住密碼
                    if (this.remember) {  // 記住密碼,永存
                        sessionStorage.clear();
                       localStorage.token = response.data.results.token;
                        localStorage.username = response.data.results.username;
                        localStorage.mobile = response.data.results.mobile;
                    } else { /// 沒記住密碼,暫存
                        localStorage.clear();
                        sessionStorage.token = response.data.results.token;
                        sessionStorage.username = response.data.results.username;
                        sessionStorage.mobile = response.data.results.mobile;
                    }

                    if (response.data.status == 0) {
                        let token = response.data.results.token;
                        this.$cookies.set('token', token, 24 * 60 * 60);
                        let username = response.data.results.username;
                        this.$cookies.set('username', username, 24 * 60 * 60);

                        this.$alert("歡迎回來!", "登陸成功!", {
                            confirmButtonText: '肯定',
                            callback: () => {
                                // 登陸成功去主頁
                                this.$router.push("/");
                            }
                        })
                    } else {
                        this.username = '';
                        this.password = '';
                    }
                }).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 => {
                    let msg = response.data.result;
                    this.$message({
                        message: msg,
                    });
                    if (msg === '短信發送失敗') return;

                    // 修改短信的發送狀態
                    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);

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

            },

            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;
                    if (status == 0) {
                        // cookie存儲token,保存登陸狀態
 let token = response.data.results.token;
                        _this.$cookies.set('token', token, 24 * 60 * 60);
                        let username = response.data.results.username;
                        _this.$cookies.set('username', username, 24 * 60 * 60);

                        _this.$message({
                            message: '登陸成功',
                            duration: 1000,
                            onClose() {
                                // 保存登陸狀態
                                sessionStorage.token = token;
                                sessionStorage.username = response.data.results.username;
                                sessionStorage.mobile = response.data.results.mobile;
                                // 跳轉到主頁
                                _this.$router.push('/');
                            }
                        });
                    }
                    else {
                        _this.mobile = '';
                        _this.sms = '';
                        _this.$message({
                            message: msg,
                        });
                    }

                }).catch(error => {
                    this.mobile = '';
                    this.sms = '';
                    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>

五.前臺註銷

    後臺token過時時間配置dev.py:

# jwt配置
import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

  cookie在登陸成功後已保存

   header.vue:

<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">
                        <div v-if="!is_login">
                            <router-link to="/login">登陸</router-link>
                            &nbsp;|&nbsp;
                            <router-link to="/register">註冊</router-link>
                        </div>
                        <div v-else>
                            <router-link to="/user">{{ username }}</router-link>
                            &nbsp;|&nbsp;
                            <span @click="logoutAction">註銷</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Header",
        data() {
            return {
                this_nav: "",
                is_login: false,
                username: '',
            }
        },
        created() {
            this.this_nav = localStorage.this_nav;
            this.is_login = this.$cookies.get('token');
            this.username = this.$cookies.get('username');
        },
        methods: {
            jump(location) {
                localStorage.this_nav = location;
                // vue-router除了提供router-link標籤跳轉頁面之外,還提供了 js跳轉的方式
                this.$router.push(location);
            },
            logoutAction() {
                this.is_login = false;
                this.username = '';
                this.$cookies.remove('token');
                this.$cookies.remove('username');
            }
        }
    }
</script>

 六.接口緩存

home/views.py

from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from . import models, serializers

"""
class BannerListAPIView(ListAPIView):
    queryset = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
    serializer_class = serializers.BannerModelSerializer
"""
# 接口緩存
from django.core.cache import cache
class BannerListAPIView(ListAPIView):
    def get(self, request, *args, **kwargs):
        banner_list_data = cache.get('api_banner_list_data')
        if not banner_list_data:
            print('查詢了數據庫')
            banner_query = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
            banner_list_data = serializers.BannerModelSerializer(banner_query, many=True).data
            # 創建接口緩存
            cache.set('api_banner_list_data', banner_list_data)
        return Response(banner_list_data)

不走ListAPIView的Response,返回給前臺的數據不帶前綴,因此banner.vue需加前綴才能訪問:

<template>
    <div class="banner">
        <el-carousel height="520px" :interval="3000" arrow="always">
            <!--<el-carousel-item>-->
                <!--<img src="@/assets/img/banner1.png" alt="">-->
            <!--</el-carousel-item>-->
            <!--<el-carousel-item>-->
                <!--<img src="@/assets/img/banner2.png" alt="">-->
            <!--</el-carousel-item>-->
            <!--<el-carousel-item>-->
                <!--<img src="@/assets/img/banner3.png" alt="">-->
            <!--</el-carousel-item>-->
            <el-carousel-item v-for="banner in banner_list" :key="banner.image">
                <!--<router-link :to="banner.link">-->
                    <!--<img :src="banner.image" alt="">-->
                <!--</router-link>-->
                <a :href="banner.link">
                    <img :src="$settings.base_url + banner.image" alt="">
                </a>
            </el-carousel-item>
        </el-carousel>
    </div>
</template>

訪問量過多,緩存也沒法減緩數據庫的訪問壓力,須要藉助celery

七. Celery

官方

Celery 官網:http://www.celeryproject.org/

Celery 官方文檔英文版:http://docs.celeryproject.org/en/latest/index.html

Celery 官方文檔中文版:http://docs.jinkan.org/docs/celery/

 

Celery架構

Celery的架構由三部分組成,消息中間件(message broker)、任務執行單元(worker)和 任務執行結果存儲(task result store)組成。

消息中間件

Celery自己不提供消息服務,可是能夠方便的和第三方提供的消息中間件集成。包括,RabbitMQ, Redis等等

任務執行單元

Worker是Celery提供的任務執行的單元,worker併發的運行在分佈式的系統節點中。

任務結果存儲

Task result store用來存儲Worker執行的任務的結果,Celery支持以不一樣方式存儲任務的結果,包括AMQP, redis等

 

使用場景

異步任務:將耗時操做任務提交給Celery去異步執行,好比發送短信/郵件、消息推送、音視頻處理等等

定時任務:定時執行某件事情,好比天天數據統計

 

Celery的安裝配置

pip install celery

消息中間件:RabbitMQ/Redis

app=Celery('任務名', broker='xxx', backend='xxx')

 

Celery執行異步任務

包架構封裝

project
   ├── celery_task # celery包
   │   ├── __init__.py # 包文件
   │   ├── celery.py   # celery鏈接和配置相關文件,且名字必須叫celery.py
   │   └── tasks.py    # 全部任務函數
   ├── add_task.py # 添加任務
   └── get_result.py   # 獲取結果

 安裝:  pip install celery

celery.py:

# 1)建立app + 任務

# 2)啓動celery(app)服務:
# 非windows
# 命令:celery worker -A celery_task -l info
# windows:
# pip3 install eventlet
# celery worker -A celery_task -l info -P eventlet

# 3)添加任務:手動添加,要自定義添加任務的腳本,右鍵執行腳本

# 4)獲取結果:手動獲取,要自定義獲取任務的腳本,右鍵執行腳本

代碼:

from celery import Celery

"""
broker=存儲tasks的倉庫 
backend=存儲results的倉庫 
include=[任務文件們]
"""
broker = 'redis://127.0.0.1:6379/11'
backend = 'redis://127.0.0.1:6379/12'
include = ['celery_task_1.task1', 'celery_task_1.task2']
app = Celery(broker=broker, backend=backend, include=include)

# 啓動celery服務的命令
# 前提:必定要進入celery_task所屬的文件夾(上一層)
# celery worker -A celery_task_1 -l info -P eventlet

 

基本使用(可寫多個task文件,也可把多個函數寫在一個task.py文件中):

tasks.py

from .celery import app
import time, random
# 任務就是一個個的功能函數,功能函數的返回值就是任務的執行結果
@app.task
def add(n, m):
    res = n + m
    # 模擬不固定耗時操做
    time.sleep(random.randint(1, 5))
    print(res)
    return res

add_task.py:

# 手動添加任務的文件,該文件不是必須的(能夠自動添加任務)
# 該文件也不屬於 celery task 包的一部分,就是用來實現手動添加任務的

 # 將add添加到broker倉庫,celery會去異步執行

from celery_task import tasks

# 添加當即執行任務
t1 = tasks.add.delay(10, 20)
t2 = tasks.low.delay(100, 50)
print(t1.id)


# 添加延遲任務
from datetime import datetime, timedelta
def eta_second(second):
    ctime = datetime.now()
    utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
    time_delay = timedelta(seconds=second)
    return utc_ctime + time_delay

tasks.low.apply_async(args=(200, 50), eta=eta_second(10))

get_result.py:

from celery_task_1.celery import app

from celery.result import AsyncResult

id = '4e249f2d-559a-4a3e-8b43-d498b3d6355e'
if __name__ == '__main__':
    async = AsyncResult(id=id, app=app)
    if async.successful():
        result = async.get()
        print(result)
    elif async.failed():
        print('任務失敗')
    elif async.status == 'PENDING':
        print('任務等待中被執行')
    elif async.status == 'RETRY':
        print('任務異常後正在重試')
    elif async.status == 'STARTED':
        print('任務已經開始被執行')

自動添加(沒有add_task.py文件):

celery.py

from celery import Celery

broker = 'redis://127.0.0.1:6379/11'
backend = 'redis://127.0.0.1:6379/12'
include = ['celery_task.tasks',]
app = Celery(broker=broker, backend=backend, include=include)

# 啓動celery服務的命令:
# 前提:必定要進入celery_task所屬的文件夾
# celery worker -A celery_task -l info -P eventlet


# 自動添加任務
# 時區
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False

# 任務的定時配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
    'low-task': {
        'task': 'celery_task.tasks.low',
        'schedule': timedelta(seconds=3),
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每週一早八點
        'args': (300, 150),
    },
    'my-add-task': {
        'task': 'celery_task.tasks.add',
        'schedule': timedelta(seconds=6),
        'args': (300, 150),
    }
}

# 啓動 添加任務 服務的命令,也要進入celery_task所屬文件夾
# celery beat -A celery_task -l info

tasks.py:

from .celery import app
@app.task
def add(n, m):
    res = n + m
    print(res)
    return res

@app.task
def low(n, m):
    res = n - m
    print(res)
    return res

get_result.py同上

 

celery異步處理django任務(把celery_task放在大luffy下):

celery.py

# 啓動django依賴

# 將celery服務框架放在項目根目錄下
# import sys
# sys.path.append(r'C:\Users\oldboy\Desktop\luffy\luffyapi')

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


from celery import Celery

broker = 'redis://127.0.0.1:6379/11'
backend = 'redis://127.0.0.1:6379/12'
include = ['celery_task.tasks',]
app = Celery(broker=broker, backend=backend, include=include)

# 啓動celery服務的命令:
# 前提:必定要進入celery_task所屬的文件夾
# celery worker -A celery_task -l info -P eventlet


# 自動添加任務
# 時區
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False

# 任務的定時配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
    'django-task': {
        'task': 'celery_task.tasks.django_task',
        'schedule': timedelta(seconds=10),
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每週一早八點
        'args': (),
    },
}

# 啓動 添加任務 服務的命令
# celery beat -A celery_task -l info

tasks.py:

from .celery import app

from django.core.cache import cache
from apps.home import models, serializers
@app.task
def django_task():
    banner_query = models.Banner.objects.filter(is_show=True, is_delete=False).order_by('-orders')
    banner_list_data = serializers.BannerModelSerializer(banner_query, many=True).data
    # 創建接口緩存
    cache.set('api_banner_list_data', banner_list_data)
    return '輪播圖緩存更新完畢'
相關文章
相關標籤/搜索