路飛學城之 luffy (2 )

目錄

路飛學城之 luffy (2 )

1、各種配置

1.pip源

pip安裝源

介紹

"""
一、採用國內源,加速下載模塊的速度
二、經常使用pip源:
	-- 豆瓣:https://pypi.douban.com/simple
	-- 阿里:https://mirrors.aliyun.com/pypi/simple
三、加速安裝的命令:
	-- >: pip install -i https://pypi.douban.com/simple 模塊名
"""

永久配置安裝源

Windows
"""
一、文件管理器文件路徑地址欄敲:%APPDATA% 回車,快速進入 C:\Users\電腦用戶\AppData\Roaming 文件夾中
二、新建 pip 文件夾並在文件夾中新建 pip.ini 配置文件
三、新增 pip.ini 配置文件內容
"""
MacOS、Linux
"""
一、在用戶根目錄下 ~ 下建立 .pip 隱藏文件夾,若是已經有了能夠跳過
	-- mkdir ~/.pip
二、進入 .pip 隱藏文件夾並建立 pip.conf 配置文件
	-- cd ~/.pip && touch pip.conf
三、啓動 Finder(訪達) 按 cmd+shift+g 來的進入,輸入 ~/.pip 回車進入
四、新增 pip.conf 配置文件內容
"""
配置文件內容
"""
[global]
index-url = http://pypi.douban.com/simple
[install]
use-mirrors =true
mirrors =http://pypi.douban.com/simple/
trusted-host =pypi.douban.com
"""

2.虛擬環境的搭建

虛擬環境的搭建

優勢

一、使不一樣應用開發環境相互獨立
二、環境升級不影響其餘應用,也不會影響全局的python環境
三、防止出現包管理混亂及包版本衝突

windows

安裝
# 建議使用pip3安裝到python3環境下
pip3 install virtualenv
pip3 install virtualenvwrapper-win
配置虛擬環境管理器工做目錄
# 配置環境變量:
# 控制面板 => 系統和安全 => 系統 => 高級系統設置 => 環境變量 => 系統變量 => 點擊新建 => 填入變量名與值
變量名:WORKON_HOME  變量值:自定義存放虛擬環境的絕對路徑
eg: WORKON_HOME: D:\Virtualenvs

# 同步配置信息:
# 去向Python3的安裝目錄 => Scripts文件夾 => virtualenvwrapper.bat => 雙擊

MacOS、Linux

安裝
# 建議使用pip3安裝到python3環境下
pip3 install -i https://pypi.douban.com/simple virtualenv
pip3 install -i https://pypi.douban.com/simple virtualenvwrapper
工做文件
# 先找到virtualenvwrapper的工做文件 virtualenvwrapper.sh,該文件能夠刷新自定義配置,但須要找到它
# MacOS可能存在的位置 /Library/Frameworks/Python.framework/Versions/版本號文件夾/bin
# Linux可能所在的位置 /usr/local/bin  |  ~/.local/bin  |  /usr/bin
# 建議無論virtualenvwrapper.sh在哪一個目錄,保證在 /usr/local/bin 目錄下有一份
# 若是不在 /usr/local/bin 目錄,如在 ~/.local/bin 目錄,則複製一份到 /usr/local/bin 目錄
	-- sudo cp -rf ~/.local/bin/virtualenvwrapper.sh /usr/local/bin
配置
# 在 ~/.bash_profile 完成配置,virtualenvwrapper的默認默認存放虛擬環境路徑是 ~/.virtualenvs
# WORKON_HOME=自定義存放虛擬環境的絕對路徑,須要自定義就解注
VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
source /usr/local/bin/virtualenvwrapper.sh

# 在終端讓配置生效:
	-- source ~/.bash_profile

使用

# 在終端工做的命令

# 一、建立虛擬環境到配置的WORKON_HOME路徑下
# 選取默認Python環境建立虛擬環境:
	-- mkvirtualenv 虛擬環境名稱
# 基於某Python環境建立虛擬環境:
	-- mkvirtualenv -p python2.7 虛擬環境名稱
	-- mkvirtualenv -p python3.6 虛擬環境名稱

# 二、查看已有的虛擬環境
	-- workon

# 三、使用某個虛擬環境
	-- workon 虛擬環境名稱
	
# 四、進入|退出 該虛擬環境的Python環境
	-- python | exit()

# 五、爲虛擬環境安裝模塊
	-- pip或pip3 install 模塊名

# 六、退出當前虛擬環境
	-- deactivate

# 七、刪除虛擬環境(刪除當前虛擬環境要先退出)
	-- rmvirtualenv 虛擬環境名稱

pycharm使用

新建項目

添加環境

使用環境

3.luffy後臺

後臺:Django項目建立

環境

"""
爲luffy項目建立一個虛擬環境
>: mkvirtualenv luffy
"""

"""
按照基礎環境依賴
>: pip install django==2.0.7
>: pip install djangorestframework
>: pip install pymysql
"""

建立項目

"""
前提:在目標目錄新建luffy文件夾
>: cd 創建的luffy文件夾
>: django-admin startproject luffyapi

開發:用pycharm打開項目,並選擇提早備好的虛擬環境
"""

重構項目目錄

"""
├── luffyapi
	├── logs/				# 項目運行時/開發時日誌目錄 - 包
    ├── manage.py			# 腳本文件
    ├── luffyapi/      		# 項目主應用,開發時的代碼保存 - 包
     	├── apps/      		# 開發者的代碼保存目錄,以模塊[子應用]爲目錄保存 - 包
        ├── libs/      		# 第三方類庫的保存目錄[第三方組件、模塊] - 包
    	├── settings/  		# 配置目錄 - 包
			├── dev.py   	# 項目開發時的本地配置
			└── prod.py  	# 項目上線時的運行配置
		├── urls.py    		# 總路由
		└── utils/     		# 多個模塊[子應用]的公共函數類庫[本身開發的組件]
    └── scripts/       		# 保存項目運營時的腳本文件 - 文件夾
"""

配置開發環境

"""
1.修改 wsgi.py 與 manage.py 兩個文件:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev')

2.將settings.py刪除或更名,內容拷貝到settings/dev.py中

3.修改dev.py文件內容
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

4.修改啓動配置:見插圖

5.在任何一個__init__.py文件中測試默認配置文件是不是dev.py文件
from django.conf import settings
print(settings)
"""

配置日誌

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 實際開發建議使用WARNING
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日誌位置,日誌文件名,日誌保存目錄必須手動建立,注:這裏的文件路徑要注意BASE_DIR表明的是小luffyapi
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"),
            # 日誌文件的最大值,這裏咱們設置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日誌文件的數量,設置最大日誌數量爲10
            'backupCount': 10,
            # 日誌格式:詳細格式
            'formatter': 'verbose',
            # 文件內容編碼
            'encoding': 'utf-8'
        },
    },
    # 日誌對象
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True, # 是否讓日誌信息繼續冒泡給其餘的日誌處理系統
        },
    }
}

4.luffy後臺配置

環境變量

dev.py
# 環境變量操做:小luffyapiBASE_DIR與apps文件夾都要添加到環境變量
import sys
sys.path.insert(0, BASE_DIR)
APPS_DIR = os.path.join(BASE_DIR, 'apps')
sys.path.insert(1, APPS_DIR)
在寫項目直接導入utils文件夾也不''錯誤提示''

封裝logger

dev.py
# 真實項目上線後,日誌文件打印級別不能太低,由於一第二天志記錄就是一次文件io操做
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            # 實際開發建議使用WARNING
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 實際開發建議使用ERROR
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日誌位置,日誌文件名,日誌保存目錄必須手動建立,注:這裏的文件路徑要注意BASE_DIR表明的是小luffyapi
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"),
            # 日誌文件的最大值,這裏咱們設置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日誌文件的數量,設置最大日誌數量爲10
            'backupCount': 10,
            # 日誌格式:詳細格式
            'formatter': 'verbose',
            # 文件內容編碼
            'encoding': 'utf-8'
        },
    },
    # 日誌對象
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True, # 是否讓日誌信息繼續冒泡給其餘的日誌處理系統
        },
    }
}
utils/logging.py
import logging
logger = logging.getLogger('django')

封裝項目異常處理

utils/exception.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
from utils.logging import logger
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    # 異常模塊就是記錄項目的錯誤日誌
    logger.error('%s - %s - %s' % (context['view'], context['request'].method, exc))
    if response is None:
        return Response({
            'detail': '%s' % exc
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
    return response
settings.py
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',
}

二次封裝Response模塊

utils/response.py
from rest_framework.response import Response

class APIResponse(Response):
    def __init__(self, data_status=0, data_msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):
        data = {
            'status': data_status,
            'msg': data_msg,
        }
        if results is not None:
            data['results'] = results
        data.update(kwargs)

        super().__init__(data=data, status=http_status, headers=headers, exception=exception)

5.luffy數據庫

數據庫配置

建立數據庫

"""
1.管理員鏈接數據庫
>: mysql -uroot -proot

2.建立數據庫
>: create database luffy default charset=utf8;

3.查看用戶
>: select user,host,password from mysql.user;
"""

爲指定數據庫配置指定帳戶

"""
設置權限帳號密碼
# 受權帳號命令:grant 權限(create, update) on 庫.表 to '帳號'@'host' identified by '密碼'

1.配置任意ip均可以連入數據庫的帳戶
>: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?';

2.因爲數據庫版本的問題,可能本地還鏈接不上,就給本地用戶單獨配置
>: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?';

3.刷新一下權限
>: flush privileges;

只能操做luffy數據庫的帳戶
帳號:luffy
密碼:Luffy123?
"""

Django 2.x 一些版本pymysql兼容問題

Django不採用2.0.7版本極可能出現如下問題,須要修改源代碼

6.user模塊User表

user模塊User表

建立user模塊

前提:在 luffy 虛擬環境下

1.終端從項目根目錄進入apps目錄
>: cd luffyapi & cd apps

2.建立app
>: python ../../manage.py startapp user

建立User表對應的model:user/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
    mobile = models.CharField(max_length=11, unique=True)
    icon = models.ImageField(upload_to='icon', default='icon/default.png')

    class Meta:
        db_table = 'luffy_user'
        verbose_name = '用戶表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

註冊user模塊,配置User表:dev.py

INSTALLED_APPS = [
    # ...
    'user',
]

# 自定義User表
AUTH_USER_MODEL = 'user.User'

配置media

media配置:dev.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
media目錄配置
"""
├── luffyapi
    └──	luffyapi/
       	└──	media/  	
			└──	icon 
				└── default.png
"""
主路由:luffyapi/urls.py
from django.contrib import admin
from django.urls import path, re_path, include
from django.views.static import serve
from django.conf import settings
urlpatterns = [
    path('admin/', admin.site.urls),

    path('user/', include('user.urls')),

    re_path('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
子路由:user/urls.py
from django.urls import path, re_path
urlpatterns = [

]

7.luffy前臺

前臺

vue環境

1.傻瓜式安裝node: 
官網下載:https://nodejs.org/zh-cn/

2.安裝cnpm: 
>: npm install -g cnpm --registry=https://registry.npm.taobao.org

3.安裝vue最新腳手架: 
>: cnpm install -g @vue/cli

注:若是二、3步報錯,清除緩存後從新走二、3步
>: npm cache clean --force

建立項目

"""
前提:在目標目錄新建luffy文件夾
>: cd 創建的luffy文件夾
>: vue create luffycity
"""

重構項目目錄

"""
├── luffycity
	├── public/          			# 項目共有資源
		├── favicon.ico				# 站點圖標
		└── index.html				# 主頁
    ├── src/      					# 項目主應用,開發時的代碼保存
    	├── assets/      			# 前臺靜態資源總目錄
    		├── css/				# 自定義css樣式
    			└── global.css		# 自定義全局樣式
    		├── js/					# 自定義js樣式
				└── settings.js		# 自定義配置文件
			└── img/				# 前臺圖片資源
		├── components/    			# 小組件目錄
		├── views/  				# 頁面組件目錄
		├── App.vue	    			# 根路由
		├── main.js	    			# 入口腳本文件
		├── router    		
			└── index.js			# 路由腳本文件
		store	    		
			└── index.js			# 倉庫腳本文件
    ├── vue.config.js	    		# 項目配置文件
    └── *.*							# 其餘配置文件
"""

文件修訂:目錄中非配置文件的多餘文件能夠移除

App.vue
<template>
    <div id="app">
        <router-view/>
    </div>
</template>
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter);

const routes = [
    {
        path: '/',
        name: 'home',
        component: Home
    },
];

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
});

export default router
Home.vue
<template>
    <div class="home">
    </div>
</template>

<script>
    export default {
        name: 'home',
        components: {
        },
    }
</script>

全局配置:全局樣式、配置文件

global.css
/* 聲明全局樣式和項目的初始化樣式 */
body, h1, h2, h3, h4, p, table, tr, td, ul, li, a, form, input, select, option, textarea {
    margin: 0;
    padding: 0;
    font-size: 15px;
}

a {
    text-decoration: none;
    color: #333;
}

ul {
    list-style: none;
}

table {
    border-collapse: collapse; /* 合併邊框 */
}
settings.js
export default {
    base_url: 'http://127.0.0.1:8000'
}
main.js
// 配置全局樣式
import '@/assets/css/global.css'

// 配置全局自定義設置
import settings from '@/assets/js/settings'
Vue.prototype.$settings = settings;
// 在全部須要與後臺交互的組件中:this.$settings.base_url + '再拼接具體後臺路由'

8.luffy前臺配置

luffy前臺配置

axios先後臺交互

安裝:前端項目目錄下的終端
>: cnpm install axios
配置:main.js
import axios from 'axios'
Vue.prototype.$axios = axios;

cookies操做

安裝:前端項目目錄下的終端
>: cnpm install vue-cookies
配置:main.js
import cookies from 'vue-cookies'
Vue.prototype.$cookies = cookies;

element-ui頁面組件框架

安裝:前端項目目錄下的終端
>: cnpm install element-ui
配置:main.js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

bootstrap頁面組件框架

安裝:前端項目目錄下的終端
>: cnpm install jquery
>: cnpm install bootstrap@3
配置jquery:vue.config.js
const webpack = require("webpack");

module.exports = {
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery",
                "window.jQuery": "jquery",
                "window.$": "jquery",
                Popper: ["popper.js", "default"]
            })
        ]
    }
};
配置bootstrap:main.js
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'

9.luffy前臺主頁

前端主頁

圖片準備

將提供的資料中的圖片移植到項目的img文件夾下

頁頭組件:components/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">
                        <span>登陸</span>
                        &nbsp;|&nbsp;
                        <span>註冊</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Header",
        data() {
            return {
                this_nav: "",
            }
        },
        created() {
            this.this_nav = localStorage.this_nav;
        },
        methods: {
            jump(location) {
                localStorage.this_nav = location;
                // vue-router除了提供router-link標籤跳轉頁面之外,還提供了js跳轉的方式
                this.$router.push(location);
            }
        }
    }
</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;
    }
</style>

輪播圖組件:components/Banner.vue

<template>
    <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>
</template>
<script>
    export default {
        name: "Banner",
    }
</script>

<style scoped>
    .el-carousel__item h3 {
        color: #475669;
        font-size: 18px;
        opacity: 0.75;
        line-height: 300px;
        margin: 0;
    }

    .el-carousel__item:nth-child(2n) {
        background-color: #99a9bf;
    }

    .el-carousel__item:nth-child(2n+1) {
        background-color: #d3dce6;
    }
    .el-carousel__item img {
        text-align: center;
        height: 520px;
        margin: 0 auto;
        display: block;
    }
</style>

頁腳組件:components/Footer.vue

<template>
    <div class="footer">
        <ul>
            <li>關於咱們</li>
            <li>聯繫咱們</li>
            <li>商務合做</li>
            <li>幫助中心</li>
            <li>意見反饋</li>
            <li>新手指南</li>
        </ul>
        <p>Copyright © luffycity.com版權全部 | 京ICP備17072161號-1</p>
    </div>
</template>

<script>
    export default {
        name: "Footer"
    }
</script>

<style scoped>
    .footer {
        width: 100%;
        height: 128px;
        background: #25292e;
        color: #fff;
    }

    .footer ul {
        margin: 0 auto 16px;
        padding-top: 38px;
        width: 810px;
    }

    .footer ul li {
        float: left;
        width: 112px;
        margin: 0 10px;
        text-align: center;
        font-size: 14px;
    }

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

    .footer p {
        text-align: center;
        font-size: 12px;
    }
</style>

主頁組件:views/Home.vue

<template>
    <div class="home">
        <Header />
        <Banner />
        <Footer />
    </div>
</template>

<script>
    import Header from '@/components/Header'
    import Banner from '@/components/Banner'
    import Footer from '@/components/Footer'

    export default {
        name: 'home',
        components: {
            Header,
            Banner,
            Footer
        },
    }
</script>

10.後臺主頁模塊設計

home模塊

建立home模塊

前提:在 luffy 虛擬環境下

1.終端從項目根目錄進入apps目錄
>: cd luffyapi & cd apps

2.建立app
>: python ../../manage.py startapp home

路由分發

主路由:luffyapi/urls.py
from django.urls import path, re_path, include
urlpatterns = [
	# ...
    path('user/', include('home.urls')),
    # ...
]
子路由:home/urls.py
from django.urls import path, re_path
urlpatterns = [

]

Banner數據表model設計

utils/model.py
from django.db import models

class BaseModel(models.Model):
    orders = models.IntegerField(verbose_name='顯示順序')
    is_show = models.BooleanField(verbose_name="是否上架", default=False)
    is_delete = models.BooleanField(verbose_name="邏輯刪除", default=False)

    class Meta:
        abstract = True
home/models.py
from django.db import models
from utils.model import BaseModel

class Banner(BaseModel):
    image = models.ImageField(upload_to='banner', verbose_name='輪播圖', null=True, blank=True)
    name = models.CharField(max_length=150, verbose_name='輪播圖名稱')
    note = models.CharField(max_length=150, verbose_name='備註信息')
    link = models.CharField(max_length=150, verbose_name='輪播圖廣告地址')

    class Meta:
        db_table = 'luffy_banner'
        verbose_name = '輪播圖'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
數據遷移:在大luffyapi路徑下的終端
>: python manage.py makemigrations
>: python manage.py migrate

註冊home模塊:dev.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'home',
]

設計Banner數據接口

home/serializers.py
from rest_framework.serializers import ModelSerializer
from . import models
class BannerModelSerializer(ModelSerializer):
    class Meta:
        model = models.Banner
        fields = ('name', 'note', 'image', 'link')
home/views.py
from rest_framework.generics import ListAPIView
from utils.response import APIResponse
from . import models, serializers
class BannerListAPIView(ListAPIView):
    queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('-orders')
    serializer_class = serializers.BannerModelSerializer
home/urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
    path('banners/', views.BannerListAPIView.as_view())
]
接口
http://api.luffy.cn:8000/home/banner/

11.先後臺分離跨域交互

分離的先後臺交互

後臺處理跨域

安裝插件
>: pip install django-cors-headers

插件參考地址:https://github.com/ottoyiu/django-cors-headers/
項目配置:dev.py
# 註冊app
INSTALLED_APPS = [
	...
	'corsheaders'
]

# 添加中間件
MIDDLEWARE = [
	...
	'corsheaders.middleware.CorsMiddleware'
]

# 容許跨域源
CORS_ORIGIN_ALLOW_ALL = True

前臺請求Banner數據

修訂Banner.vue
<template>
    <el-carousel height="520px" :interval="3000" arrow="always">
        <!-- 渲染後臺數據 -->
        <el-carousel-item v-for="banner in banner_list" :key="banner.name">
            <a :href="banner.link">
                <img :src="banner.image" alt="" :title="banner.note">
            </a>
        </el-carousel-item>

    </el-carousel>
</template>
<script>
    export default {
        name: "Banner",
        data() {
            return {
                banner_list: []
            }
        },
        created() {
            // 請求後臺數據
            this.$axios({
                url: this.$settings.base_url + '/home/banners/',
                method: 'get',
            }).then(response => {
                // window.console.log(response.data);
                this.banner_list = response.data;
            }).catch(errors => {
                window.console.log(errors)
            })
        }
    }
</script>

<style scoped>
    .el-carousel__item h3 {
        color: #475669;
        font-size: 18px;
        opacity: 0.75;
        line-height: 300px;
        margin: 0;
    }

    .el-carousel__item:nth-child(2n) {
        background-color: #99a9bf;
    }

    .el-carousel__item:nth-child(2n+1) {
        background-color: #d3dce6;
    }
    .el-carousel__item img {
        text-align: center;
        height: 520px;
        margin: 0 auto;
        display: block;
    }
</style>

12.xadmin後臺管理

xadmin後臺管理

安裝:luffy虛擬環境下
# >: pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
註冊app:dev.py
INSTALLED_APPS = [
    # ...
    # xamin主體模塊
    'xadmin',
    # 渲染表格模塊
    'crispy_forms',
    # 爲模型經過版本控制,能夠回滾數據
    'reversion',
]
xadmin:須要本身的數據庫模型類,完成數據庫遷移
python manage.py makemigrations
python manage.py migrate
設置主路由替換掉admin:主urls.py
# xadmin的依賴
import xadmin
xadmin.autodiscover()
# xversion模塊自動註冊須要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()

urlpatterns = [
    # ...
    path(r'xadmin/', xadmin.site.urls),
]
建立超級用戶:大luffyapi路徑終端
# 在項目根目錄下的終端
python manage.py createsuperuser
# 帳號密碼設置:admin | Admin123
完成xadmin全局配置:新建home/adminx.py
# home/adminx.py
# xadmin全局配置
import xadmin
from xadmin import views

class GlobalSettings(object):
    """xadmin的全局配置"""
    site_title = "路飛學城"  # 設置站點標題
    site_footer = "路飛學城有限公司"  # 設置站點的頁腳
    menu_style = "accordion"  # 設置菜單摺疊

xadmin.site.register(views.CommAdminView, GlobalSettings)
在adminx.py中註冊model:home/adminx.px
from . import models
# 註冊
xadmin.site.register(models.Banner)
修改app:home的名字:xadmin頁面上的顯示效果
# home/__init__.py
default_app_config = "home.apps.HomeConfig"

# home/apps.py
from django.apps import AppConfig
class HomeConfig(AppConfig):
    name = 'home'
    verbose_name = '個人首頁'

筆記鞏固

知識點鞏固

"""
一、jwt認證:三段式的格式、每一段的內容、由後臺簽發到前臺存儲再到傳給後臺校驗的認證流水線
	頭.載荷.簽名:頭、載荷(base64)| 簽名(HS256)
	
				頭:{基礎信息:公司項目組等信息,加密方式}
				載荷:{核心信息:用戶信息,過時時間}
				簽名:{安全信息:頭+載荷+祕鑰的md5加密結果}
				
				服務器簽發(login) -> web傳給前端存儲 -> 請求須要登陸的結果再攜帶給後臺 -> 服務器校驗(認證組件) => 權限管理
				
				服務器壓力小,集羣部署更友善

二、drf-jwt插件:
	三個接口:簽發token、校驗token、刷新token
	自定義jwt插件的配置:在路由配置簽發token的視圖類接口、在全局或局部配置認證類
	
三、使用jwt插件完成多方式登陸
	視圖類:將請求數據交給序列化類完成校驗,而後返回用戶信息和token(從序列化對象中拿到)
	序列化類:自定義反序列化字段,全局鉤子校驗數據獲得user和token,並保存在序列化類對象中
		token能夠用jwt插件的rest_framework_jwt.serializers中
			jwt_payload_handler,jwt_encode_handler
		完成簽發

四、自定義頻率類完成視圖類的頻率限制
	1)定義類繼承SimpleRateThrottle,重寫get_cache_key方法,設置scope類屬性
	2)scope就是一個認證字符串,在配置文件中配置scope字符串對應的頻率設置
	3)get_cache_key的返回值是字符串,該字符串是緩存訪問次數的緩存key	
"""

"""
請求生命週期:as_view、dispatch
基礎模塊:請求、響應、解析、渲染、異常
核心模塊:序列化、視圖家族、三大認證、過濾
"""

"""
路飛項目

先後臺項目建立 => 項目目錄規範(項目管理)=> 先後臺跨域交互 => 
	主頁頁面邏輯接口與展現 => 課程頁 => 用戶的登陸註冊 => 課程購買(支付模塊) 
=> 項目上線(線上測試支付回調)

開發技術點:git、redis、celery、短信接口、支付寶支付、阿里雲服務器
"""

總結

"""
一、項目準備:pip換源、項目虛擬環境

二、後臺項目建立,項目目錄重構,項目配置:環境變量、日誌、異常、響應、數據庫、媒體文件、國際化、先後臺跨越、xadmin

三、前臺項目建立,項目目錄重構,項目配置:全局樣式與設置、axios、vue-cookies、element-ui、bs+jq
	
四、後臺建立用戶模塊user:自定義User表、配置User表、路由分發

五、前臺自定義主頁組件和頁頭、輪播圖小組件

六、核心:後臺重構、後臺配置、數據庫
"""

A做業(必作)

"""
一、整理今天所學知識點

二、創建先後臺luffy項目:luffyapi、luffycity,並按照課堂要求完成先後臺配置

三、打通先後臺跨越交互,按照課堂內容完成主頁設計
"""

B做業(選作)

"""
一、後臺設計輪播圖表,肯定都要哪些字段(核心:必定要先本身設計一下,而後明天對比一下)
二、完善主頁的前臺設計,在後臺設計一個Banner表,輪播圖數據由後臺提供給前臺
"""

2、git

版本控制器

"""
完成 協同開發 項目,幫助程序員整合代碼

軟件:SVN 、 GIT

git:集羣化、多分支
"""

git

簡介

"""
什麼是git:版本控制器 - 控制的對象是開發的項目代碼
代碼開發時間軸:需求1 > 版本庫1 > 需求2 > 版本庫2 > 版本庫1 > 版本庫2 
"""

git與svn比較

git的工做流程

git分支管理

git使用

安裝

# 1.下載對應版本:https://git-scm.com/download
# 2.安裝git:在選取安裝路徑的下一步選取 Use a TrueType font in all console windows 選項

基礎命令

將已有的文件夾 - 初始化爲git倉庫
"""
>: cd 目標文件夾內部
>: git init
"""
在指定目錄下 - 初始化git倉庫
"""
>: cd 目標目錄
>: git init 倉庫名
"""
在倉庫目錄終端下 - 設置全局用戶
"""
>: git config --global user.name '用戶名'
>: git config --global user.email '用戶郵箱'

注:在全局文件 C:\Users\用戶文件夾\.gitconfig新建用戶信息,在全部倉庫下均可以使用
"""
在倉庫目錄終端下 - 設置局部用戶
"""
>: git config user.name '用戶名'
	-- 用戶名
>: git config user.email '用戶郵箱'
	-- 用戶郵箱
	
注:在當前倉庫下的config新建用戶信息,只能在當前倉庫下使用
注:一個倉庫有局部用戶,優先使用局部用戶,沒有配置再找全局用戶
"""
查看倉庫狀態
"""
# 當倉庫中有文件增長、刪除、修改,均可以在倉庫狀態中查看
>: git status  
	-- 查看倉庫狀態
>: git status -s  
	-- 查看倉庫狀態的簡約顯示
"""
工做區操做
# 經過任何方式完成的文件刪與改
# 空文件夾不會被git記錄
撤銷工做區操做:改、刪
"""
>: git checkout .
	-- 撤銷全部暫存區的提交
>: git checkout 文件名
	-- 撤銷某一文件的暫存區提交
"""
工做區內容提交到暫存區
"""
>: git add .  
	-- 添加項目中全部文件
>: git add 文件名  
	-- 添加指定文件
"""
撤銷暫存區提交:add的逆運算
"""
>: git reset HEAD .
	-- 撤銷全部暫存區的提交
>: git reset 文件名
	-- 撤銷某一文件的暫存區提交
"""
提交暫存區內容到版本庫
# git commit -m "版本描述信息"
撤銷版本庫提交:commit的逆運算
"""
回滾暫存區已經提交到版本庫的操做:
    查看歷史版本:
        >: git log
        >: git reflog
    查看時間點以前|以後的日誌:
        >: git log --after 2018-6-1
        >: git log --before 2018-6-1
        >: git reflog --after 2018-6-1
        >: git reflog --before 2018-6-1
    查看指定開發者日誌
        >: git log --author author_name
        >: git reflog --author author_name
    回滾到指定版本:
        回滾到上一個版本:
            >: git reset --hard HEAD^
            >: git reset --hard HEAD~
        回滾到上三個版本:
            >: git reset --hard HEAD^^^
            >: git reset --hard HEAD~3
        回滾到指定版本號的版本:
            >: git reset --hard 版本號
            >: eg: git reset --hard 35cb292
"""

過濾文件

# .gitignore 文件
# 1)在倉庫根目錄下建立該文件
# 2)文件與文件夾都可以被過濾
# 3)文件過濾語法

""" 過濾文件內容
文件或文件夾名:表明全部目錄下的同名文件或文件夾都被過濾
/文件或文件夾名:表明倉庫根目錄下的文件或文件夾被過濾

eg:
a.txt:項目中全部a.txt文件和文件夾都會被過濾
/a.txt:項目中只有根目錄下a.txt文件和文件夾會被過濾
/b/a.txt:項目中只有根目錄下的b文件夾下的a.txt文件和文件夾會被過濾
*x*:名字中有一個x的都會被過濾(*表明0~n個任意字符)
空文件夾不會被提交,空包會被提交
"""

建立遠程gitee倉庫

選擇線上倉庫

"""
1.註冊碼雲帳號並登陸:https://gitee.com/
2.建立倉庫(課堂截圖)
3.本地與服務器倉庫創建鏈接
"""
"""
1)本地配置線上的帳號與郵箱
>: git config --global user.name "doctor_owen"
>: git config --global user.email "doctor_owen@163.com"

2)在本地初始化倉庫(git init),並完成項目的初步搭建(項目架構)(通常都是項目負責人完成項目啓動)
# 這個過程就是git的基礎部分的本地操做

3)採用 https協議 或 ssh協議 與遠程git倉庫通訊提交提交代碼(通常都是項目負責人完成)
	i) https協議方式,無需配置,可是每次提交都有驗證管理員帳號密碼
	>: git remote add origin https://gitee.com/doctor_owen/luffy.git  # 配置遠程源
	>: git push -u origin master  # 提交本地倉庫到遠程源
	
	ii) ssh協議,須要配置,配置完成以後就能夠正常提交代碼
	>: git remote add origin git@gitee.com:doctor_owen/luffy.git  # 配置遠程源
	>: git push -u origin master  # 提交本地倉庫到遠程源
	
	iii)查看源及源連接信息
	>: git remote
	>: git remote -v
	
	iv)刪除源連接
	>: git remote remove 源名字 
	
注:origin遠程源的源名,能夠自定義;master是分支名,是默認的主分支
"""

用本地倉庫首次初始化遠程倉庫

本地倉庫與遠程倉庫創建源鏈接
前提:本地倉庫已經建立且初始化完畢(代碼已經提交到本地版本庫)

本機命令,添加遠程源:git remote add origin ssh@*.git
	採用ssh協議的remote源
建立電腦的公鑰私鑰
官網:https://gitee.com/help/articles/4181#article-header0

本機命令,生成公鑰:ssh-keygen -t rsa -C "*@*.com"
	郵箱能夠任意填寫
本機命令,查看公鑰:cat ~/.ssh/id_rsa.pub

碼雲線上添加公鑰:項目倉庫 => 管理 => 部署公鑰管理 => 添加公鑰 => 添加我的公鑰
提交本地代碼到遠程倉庫
命令:git push origin master

remote源操做

"""
1)查看倉庫已配置的遠程源
>: git remote
>: git remote -v

2)查看remote命令幫助文檔
>: git remote -h

3)刪除遠程源
>: git remote remove 源名
eg: git remote remove origin

4)添加遠程源
>: git remote add 源名 源地址
>: git remote add orgin git@*.git
"""

多分支開發

分支操做
"""
1.建立分支
>: git branch 分支名

2.查看分支
>: git branch

3.切換分支
>: git checkout 分支名

4.建立並切換到分支
>: git checkout -b 分支名

5.刪除分支
>: git branch -d 分支名

6.查看遠程分支
>: git branch -a

7.合併分支
>: git merge 分支名
"""
線上分支合併

安裝

知識點複習與概括

知識複習

"""
一、項目準備:pip換源、項目虛擬環境

二、後臺項目建立,項目目錄重構,項目配置:環境變量、日誌、異常、響應、數據庫、媒體文件、國際化、先後臺跨越、xadmin
	環境變量:多app開發,將全部app放在apps中進行管理,全部apps文件夾要添加到環境變量 => app能夠直接用名字註冊
	import logging   logging.getLogger('django')
	用戶權限,只能給開發者提供本項目操做權限(及如下)的帳戶

三、前臺項目建立,項目目錄重構,項目配置:全局樣式與設置、axios、vue-cookies、element-ui、bs+jq
	
四、後臺建立用戶模塊user:自定義User表、配置User表、路由分發

五、前臺自定義主頁組件和頁頭、輪播圖小組件

六、核心:後臺重構、後臺配置、數據庫
"""

版本控制器:SVN、GIT

"""
版本控制器:操做開發階段代碼版本迭代的工具
爲何要用版本控制器:
	代碼不一樣階段需求完成,該代碼的時間節點應該被保留
	項目通常都是進行團隊開發,版本控制器能夠幫助自動整合代碼
	問題:只能整合代碼,可是不能避免衝突 => 衝突解決
	
使用:安裝 => 本地使用(基礎命令) => 線上使用(團隊開發) => 衝突解決
"""

知識點總結

"""
一、前臺導航組件完成各頁面跳轉

二、先後臺主頁輪播圖接口實現

三、版本控制器:GIT vs SVN(GIT的優點)
	
四、git本地基本操做:初始化倉庫、狀態查看、工做區 暫存區 版本庫基本操做、git倉庫過濾文件

五、線上倉庫的建立與鏈接:源操做

六、團隊開發、版本衝突解決
"""

A做業(必作)

"""
一、整理今天所學知識點

二、完成luffy導航欄的封裝、主頁輪播圖表的設計與接口的設計

三、安裝git、屬性git的基本操做、線上操做、團隊開發、衝突解決
"""

B做業(選作)

"""
一、安裝文檔測試git如何操做分支,完成多分支開發
二、根據菜鳥教育學校redis數據庫
"""

3、頁面設計

登陸頁面設計

註冊頁面設計

知識點回顧與概括

日考

1.核心關鍵字:版本管理器、集羣部署、多分支
操做開發階段代碼版本迭代的工具,每一個git應用均可以做爲客戶端或服務端,能夠擁有多分支

2.
git status 查看狀態
git init 初始化倉庫
git add . 添加全部文件到暫存區
git commit -m '' 暫存區信息完成提交到版本庫
git remote add 源名 地址 添加倉庫源

3.
同時開發相同文件
一個開發者先提交給服務器,第二個開發者拉取代碼時,若是出現同一文件同一位置會出現衝突
衝突須要開發者們線下溝通解決,保證全部開發者開發進度正常進行

知識點總結

"""
一、git實際開發版本衝突解決:
	更新代碼到本地版本庫 => 拉遠程 => 出現衝突 => 解決衝突並更新本地版本庫 => 拉遠程
		若是還出現衝突,就重複上方過程
		若是成功,就提交代碼到遠程倉庫

二、分支管理(branch):建立分支、切換分支、刪除分支、合併分支(線上線下)
	注:各個分支相互獨立

三、多方式登陸的接口
	
四、手機號註冊驗證接口

五、短信服務的開通與代碼的二次封裝

六、發送短信接口
"""

A做業(必作)

"""
一、整理今天所學知識點

二、熟練掌握git的線上線下全部操做,熟知git團隊開發,解決合併衝突

三、開通我的短信服務帳號,完成短信功能的二次封裝

四、完成多方式登陸、手機號驗證、發送驗證碼接口

五、完成短信驗證碼登陸、短信驗證碼註冊接口(必定認真用序列化類完成,有對比學習纔有質的改變)
"""

B做業(選作)

"""
一、完成前臺登陸頁面(多方式登陸與短信登陸)、註冊頁面
二、並方式先後臺登陸註冊頁面邏輯的交互、
三、根據10期預習視頻學習redis數據庫,在django中配置redis緩存數據庫,存儲短信驗證碼

四、預習接口緩存與celery異步任務框架
"""

總結

"""
一、git實際開發版本衝突解決:
	更新代碼到本地版本庫 => 拉遠程 => 出現衝突 => 解決衝突並更新本地版本庫 => 拉遠程
		若是還出現衝突,就重複上方過程
		若是成功,就提交代碼到遠程倉庫

二、分支管理(branch):建立分支、切換分支、刪除分支、合併分支(線上線下)
	注:各個分支相互獨立

三、多方式登陸的接口:username能夠攜帶不一樣信息,後臺校驗信息格式匹配登陸方式
	
四、手機號註冊驗證接口:提供手機、手機數據庫驗證

五、短信服務的開通與代碼的二次封裝:騰訊雲短信服務、將配置與發送短信的函數封裝成包

六、發送短信接口:手機號換驗證碼 - 本身的後臺產生驗證碼,交給第三方發送,後臺緩存驗證碼,返回前臺發送成功信息
"""

4、導航模態登陸註冊

前提:基於element-ui環境

模態登陸組件

<template>
    <div class="login">
        <div class="box">
            <i class="el-icon-close" @click="close_login"></i>
            <div class="content">
                <div class="nav">
                    <span :class="{active: login_method === 'is_pwd'}"
                          @click="change_login_method('is_pwd')">密碼登陸</span>
                    <span :class="{active: login_method === 'is_sms'}"
                          @click="change_login_method('is_sms')">短信登陸</span>
                </div>
                <el-form v-if="login_method === 'is_pwd'">
                    <el-input
                            placeholder="用戶名/手機號/郵箱"
                            prefix-icon="el-icon-user"
                            v-model="username"
                            clearable>
                    </el-input>
                    <el-input
                            placeholder="密碼"
                            prefix-icon="el-icon-key"
                            v-model="password"
                            clearable
                            show-password>
                    </el-input>
                    <el-button type="primary">登陸</el-button>
                </el-form>
                <el-form v-if="login_method === 'is_sms'">
                    <el-input
                            placeholder="手機號"
                            prefix-icon="el-icon-phone-outline"
                            v-model="mobile"
                            clearable
                            @blur="check_mobile">
                    </el-input>
                    <el-input
                            placeholder="驗證碼"
                            prefix-icon="el-icon-chat-line-round"
                            v-model="sms"
                            clearable>
                        <template slot="append">
                            <span class="sms" @click="send_sms">{{ sms_interval }}</span>
                        </template>
                    </el-input>
                    <el-button type="primary">登陸</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_register">當即註冊</span>
                </div>
            </div>
        </div>
    </div>
</template>

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

                if (!this.is_send) return;
                this.is_send = false;
                let sms_interval_time = 60;
                this.sms_interval = "發送中...";
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval = "獲取驗證碼";
                        this.is_send = true; // 從新回覆點擊發送功能的條件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval = `${sms_interval_time}秒後再發`;
                    }
                }, 1000);
            }
        }
    }
</script>

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

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

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

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

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

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

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

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

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

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

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

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

模態註冊組件

<template>
    <div class="register">
        <div class="box">
            <i class="el-icon-close" @click="close_register"></i>
            <div class="content">
                <div class="nav">
                    <span class="active">新用戶註冊</span>
                </div>
                <el-form>
                    <el-input
                            placeholder="手機號"
                            prefix-icon="el-icon-phone-outline"
                            v-model="mobile"
                            clearable
                            @blur="check_mobile">
                    </el-input>
                    <el-input
                            placeholder="密碼"
                            prefix-icon="el-icon-key"
                            v-model="password"
                            clearable
                            show-password>
                    </el-input>
                    <el-input
                            placeholder="驗證碼"
                            prefix-icon="el-icon-chat-line-round"
                            v-model="sms"
                            clearable>
                        <template slot="append">
                            <span class="sms" @click="send_sms">{{ sms_interval }}</span>
                        </template>
                    </el-input>
                    <el-button type="primary">註冊</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_login">當即登陸</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Register",
        data() {
            return {
                mobile: '',
                password: '',
                sms: '',
                sms_interval: '獲取驗證碼',
                is_send: false,
            }
        },
        methods: {
            close_register() {
                this.$emit('close', false)
            },
            go_login() {
                this.$emit('go')
            },
            check_mobile() {
                if (!this.mobile) return;
                if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
                    this.$message({
                        message: '手機號有誤',
                        type: 'warning',
                        duration: 1000,
                        onClose: () => {
                            this.mobile = '';
                        }
                    });
                    return false;
                }
                this.is_send = true;
            },
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                let sms_interval_time = 60;
                this.sms_interval = "發送中...";
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval = "獲取驗證碼";
                        this.is_send = true; // 從新回覆點擊發送功能的條件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval = `${sms_interval_time}秒後再發`;
                    }
                }, 1000);
            }
        }
    }
</script>

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

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

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

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

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

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

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

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

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

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

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

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

導航條:結合實際狀況完成樣式

<template>
    <div class="nav">
        <span @click="put_login">登陸</span>
        <span @click="put_register">註冊</span>
        <Login v-if="is_login" @close="close_login" @go="put_register" />
        <Register v-if="is_register" @close="close_register" @go="put_login" />
    </div>
</template>

<script>
    import Login from "./Login";
    import Register from "./Register";
    export default {
        name: "Nav",
        data() {
            return {
                is_login: false,
                is_register: false,
            }
        },
        methods: {
            put_login() {
                this.is_login = true;
                this.is_register = false;
            },
            put_register() {
                this.is_login = false;
                this.is_register = true;
            },
            close_login() {
                this.is_login = false;
            },
            close_register() {
                this.is_register = false;
            }
        },
        components: {
            Login,
            Register
        }
    }
</script>

<style scoped>

</style>

知識點概括

知識點總結

"""
一、手機驗證碼登陸接口

二、手機驗證碼註冊接口

三、登陸註冊模態頁面佈局
	
四、登陸註冊5個接口先後臺交互

五、前臺登陸狀態cookies緩存已經用戶註銷

六、接口緩存
"""

A做業(必作)

"""
一、整理今天所學知識點

二、完成後臺登陸請求的5個接口

三、依照課件,完成前臺登陸註冊頁面的佈局(Header組件能夠參考項目代碼)

四、完成前臺登陸、註冊、註銷業務

五、掌握並完成接口緩存

六、安裝並學習redis數據庫,掌握redis數據庫操做字符串的方法
"""

B做業(選作)

"""
一、預習redis數據庫操做五大數據類型,已經django中如何使用redis數據庫

二、搞清楚celery框架的概念(celery是怎麼工做的,解決什麼問題的),建一個celery測試的demo項目,跑一下celery
"""

5、celery和redis操做

Celery

官方

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

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

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

Celery架構

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

消息中間件

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

任務執行單元

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

任務結果存儲

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

使用場景

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

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

Celery的安裝配置

pip install celerywebpack

消息中間件: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   # 獲取結果

基本使用

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 = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
tasks.py
from .celery import app
import time
@app.task
def add(n, m):
    print(n)
    print(m)
    time.sleep(10)
    print('n+m的結果:%s' % (n + m))
    return n + m

@app.task
def low(n, m):
    print(n)
    print(m)
    print('n-m的結果:%s' % (n - m))
    return n - m
add_task.py
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.celery import app

from celery.result import AsyncResult

id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
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('任務已經開始被執行')

高級使用

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)添加任務:自動添加任務,因此要啓動一個添加任務的服務
# 命令:celery beat -A celery_task -l info

# 4)獲取結果


from celery import Celery

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


# 時區
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),
    }
}
tasks.py
from .celery import app

import time
@app.task
def add(n, m):
    print(n)
    print(m)
    time.sleep(10)
    print('n+m的結果:%s' % (n + m))
    return n + m


@app.task
def low(n, m):
    print(n)
    print(m)
    print('n-m的結果:%s' % (n - m))
    return n - m
get_result.py
from celery_task.celery import app

from celery.result import AsyncResult

id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
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('任務已經開始被執行')

django中使用

celery.py
# 重點:要將 項目名.settings 所佔的文件夾添加到環境變量
# import sys
# sys.path.append(r'項目絕對路徑')

# 開啓django支持
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '項目名.settings')
import django
django.setup()



# 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)添加任務:自動添加任務,因此要啓動一個添加任務的服務
# 命令:celery beat -A celery_task -l info

# 4)獲取結果


from celery import Celery

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


# 時區
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.test_django_celery',
        'schedule': timedelta(seconds=3),
        'args': (),
    }
}
tasks.py
from .celery import app
# 獲取項目中的模型類
from api.models import Banner
@app.task
def test_django_celery():
    banner_query = Banner.objects.filter(is_delete=False).all()
    print(banner_query)

redis操做

redis VS mysql
"""
redis: 內存數據庫(讀寫快)、非關係型(操做數據方便)
mysql: 硬盤數據庫(數據持久化)、關係型(操做數據間關係)

大量訪問的臨時數據,纔有redis數據庫更優
"""
redis VS memcache
"""
redis: 操做字符串、列表、字典、無序集合、有序集合 | 支持數據持久化(數據丟失能夠找回、能夠將數據同步給mysql) | 高併發支持
memcache: 操做字符串 | 不支持數據持久化 | 併發量小
"""
Redis操做
"""
基礎操做:
	啓動服務:redis-server &
	鏈接數據庫:redis-cli
    鏈接指定數據庫:redis-cli -h 127.0.0.1 -p 6379 -n 1
    切換數據庫:select 1

數據操做:字符串、列表、字典、無序集合、有序(排序)集合
	有序集合:遊戲排行榜
	
"""

redis數據庫

# 1.安裝redis與可視化操做工具

# 2.在服務中管理redis服務器的開啓關閉

# 3.命令行簡單使用redis:
	-- redis-cli  # 啓動客戶端
    -- set key value  # 設置值
    -- get key  # 取出值
    
# 4.redis支持:字符串、字典、列表、集合、有序集合
# https://www.runoob.com/redis/redis-tutorial.html

# 5.特色:可持久化、單線程單進程併發

python使用redis

依賴
>: pip3 install redis
直接使用
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=1)
鏈接池使用
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, db=10, max_connections=100)
r = redis.Redis(connection_pool=pool)
緩存使用:要額外安裝 django-redis
# 1.將緩存存儲位置配置到redis中:settings.py
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "CONNECTION_POOL_KWARGS": {"max_connections": 100}
        }
    }
}

# 2.操做cache模塊直接操做緩存:views.py
from django.core.cache import cache  # 結合配置文件實現插拔式
# 存放token,能夠直接設置過時時間
cache.set('token', 'header.payload.signature', 10)
# 取出token
token = cache.get('token')

知識點概括與練習

日考

"""
序列化:
	Serializer: 本身聲明序列化反序列化字段、自定義鉤子校驗規則、重寫create、update方法完成入庫
	ModelSerializer:model綁定、fields字段(extra_kwargs)、自定義鉤子校驗規則、繼承create、update
	ListSerializer:提供羣增羣改(必須重寫update)、在ModelSerializer中設置list_rerializer_class進行關聯

認證組件:
	校驗前臺攜帶的認證信息:沒有,返回None(遊客) | 認證失敗,拋異常(非法用戶403) | 認證成功,返回(user, token)
	
視圖基類:
	APIView:繼承View|as_view局部禁用csrf|dispatch封裝request、三大認證、響應模塊|類屬性完成局部配置
	GenericAPIView:繼承APIView|三個屬性三個方法
"""

知識點總結

"""
一、redis數據庫:優點、基礎使用、五種數據類型的操做

二、redis數據庫在Python中的使用、Django中的使用

三、celery異步任務框架:
	celery(broker、backend、tasks)封裝配置、
	添加 當即任務、延遲任務、週期任務(自動添加)
	worker服務的啓動命令:celery worker -A celery_task -l info -P eventlet
		worker服務是用來執行任務的服務器
	beat服務的啓動命令:celery beat -A celery_task -l info
		beat服務是用來自動添加app.conf.beat_schedule配置中配置的任務的
"""

A做業(必作)

"""
一、整理今天所學知識點

二、將項目的緩存配置成redis數據庫,來關聯緩存

三、利用celery框架完成異步定時更新輪播圖接口緩存
"""

B做業(選作)

"""
一、將發生短信接口,改寫爲讓celery來管理

二、工具路飛官網,設計課程業務相關表
"""

6、

1.課程頁頁面

課程組件

<template>
    <div class="course">
        <Header></Header>
        <div class="main">
            <!-- 篩選條件 -->
            <div class="condition">
                <ul class="cate-list">
                    <li class="title">課程分類:</li>
                    <li class="this">所有</li>
                    <li>Python</li>
                    <li>Linux運維</li>
                    <li>Python進階</li>
                    <li>開發工具</li>
                    <li>Go語言</li>
                    <li>機器學習</li>
                    <li>技術生涯</li>
                </ul>

                <div class="ordering">
                    <ul>
                        <li class="title">篩&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;選:</li>
                        <li class="default this">默認</li>
                        <li class="hot this">人氣</li>
                        <li class="price this">價格</li>
                    </ul>
                    <p class="condition-result">共21個課程</p>
                </div>

            </div>
            <!-- 課程列表 -->
            <div class="course-list">
                <div class="course-item">
                    <div class="course-image">
                        <img src="@/assets/img/course-cover.jpeg" alt="">
                    </div>
                    <div class="course-info">
                        <h3>
                            <router-link to="/course/detail/1">Python開發21天入門</router-link>
                            <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入學習</span></h3>
                        <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p>
                        <ul class="lesson-list">
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span>
                            </li>
                        </ul>
                        <div class="pay-box">
                            <span class="discount-type">限時免費</span>
                            <span class="discount-price">¥0.00元</span>
                            <span class="original-price">原價:9.00元</span>
                            <span class="buy-now">當即購買</span>
                        </div>
                    </div>
                </div>
                <div class="course-item">
                    <div class="course-image">
                        <img src="@/assets/img/course-cover.jpeg" alt="">
                    </div>
                    <div class="course-info">
                        <h3>Python開發21天入門 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入學習</span></h3>
                        <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p>
                        <ul class="lesson-list">
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span>
                            </li>
                        </ul>
                        <div class="pay-box">
                            <span class="discount-type">限時免費</span>
                            <span class="discount-price">¥0.00元</span>
                            <span class="original-price">原價:9.00元</span>
                            <span class="buy-now">當即購買</span>
                        </div>
                    </div>
                </div>
                <div class="course-item">
                    <div class="course-image">
                        <img src="@/assets/img/course-cover.jpeg" alt="">
                    </div>
                    <div class="course-info">
                        <h3>Python開發21天入門 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入學習</span></h3>
                        <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p>
                        <ul class="lesson-list">
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span>
                            </li>
                        </ul>
                        <div class="pay-box">
                            <span class="discount-type">限時免費</span>
                            <span class="discount-price">¥0.00元</span>
                            <span class="original-price">原價:9.00元</span>
                            <span class="buy-now">當即購買</span>
                        </div>
                    </div>
                </div>
                <div class="course-item">
                    <div class="course-image">
                        <img src="@/assets/img/course-cover.jpeg" alt="">
                    </div>
                    <div class="course-info">
                        <h3>Python開發21天入門 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入學習</span></h3>
                        <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p>
                        <ul class="lesson-list">
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li>
                            <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span>
                            </li>
                        </ul>
                        <div class="pay-box">
                            <span class="discount-type">限時免費</span>
                            <span class="discount-price">¥0.00元</span>
                            <span class="original-price">原價:9.00元</span>
                            <span class="buy-now">當即購買</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <Footer></Footer>
    </div>
</template>

<script>
    import Header from "@/components/Header"
    import Footer from "@/components/Footer"

    export default {
        name: "Course",
        data() {
            return {
                category: 0,
            }
        },
        components: {
            Header,
            Footer,
        }
    }
</script>


<style scoped>
    .course {
        background: #f6f6f6;
    }

    .course .main {
        width: 1100px;
        margin: 35px auto 0;
    }

    .course .condition {
        margin-bottom: 35px;
        padding: 25px 30px 25px 20px;
        background: #fff;
        border-radius: 4px;
        box-shadow: 0 2px 4px 0 #f0f0f0;
    }

    .course .cate-list {
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
        padding-bottom: 18px;
        margin-bottom: 17px;
    }

    .course .cate-list::after {
        content: "";
        display: block;
        clear: both;
    }

    .course .cate-list li {
        float: left;
        font-size: 16px;
        padding: 6px 15px;
        line-height: 16px;
        margin-left: 14px;
        position: relative;
        transition: all .3s ease;
        cursor: pointer;
        color: #4a4a4a;
        border: 1px solid transparent; /* transparent 透明 */
    }

    .course .cate-list .title {
        color: #888;
        margin-left: 0;
        letter-spacing: .36px;
        padding: 0;
        line-height: 28px;
    }

    .course .cate-list .this {
        color: #ffc210;
        border: 1px solid #ffc210 !important;
        border-radius: 30px;
    }

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

    .course .ordering ul {
        float: left;
    }

    .course .ordering ul::after {
        content: "";
        display: block;
        clear: both;
    }

    .course .ordering .condition-result {
        float: right;
        font-size: 14px;
        color: #9b9b9b;
        line-height: 28px;
    }

    .course .ordering ul li {
        float: left;
        padding: 6px 15px;
        line-height: 16px;
        margin-left: 14px;
        position: relative;
        transition: all .3s ease;
        cursor: pointer;
        color: #4a4a4a;
    }

    .course .ordering .title {
        font-size: 16px;
        color: #888;
        letter-spacing: .36px;
        margin-left: 0;
        padding: 0;
        line-height: 28px;
    }

    .course .ordering .this {
        color: #ffc210;
    }

    .course .ordering .price {
        position: relative;
    }

    .course .ordering .price::before,
    .course .ordering .price::after {
        cursor: pointer;
        content: "";
        display: block;
        width: 0px;
        height: 0px;
        border: 5px solid transparent;
        position: absolute;
        right: 0;
    }

    .course .ordering .price::before {
        border-bottom: 5px solid #aaa;
        margin-bottom: 2px;
        top: 2px;
    }

    .course .ordering .price::after {
        border-top: 5px solid #aaa;
        bottom: 2px;
    }

    .course .course-item:hover {
        box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);
    }

    .course .course-item {
        width: 1050px;
        background: #fff;
        padding: 20px 30px 20px 20px;
        margin-bottom: 35px;
        border-radius: 2px;
        cursor: pointer;
        box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);
        /* css3.0 過渡動畫 hover 事件操做 */
        transition: all .2s ease;
    }

    .course .course-item::after {
        content: "";
        display: block;
        clear: both;
    }

    /* 頂級元素 父級元素  當前元素{} */
    .course .course-item .course-image {
        float: left;
        width: 423px;
        height: 210px;
        margin-right: 30px;
    }

    .course .course-item .course-image img {
        width: 100%;
    }

    .course .course-item .course-info {
        float: left;
        width: 596px;
    }

    .course-item .course-info h3 {
        font-size: 26px;
        color: #333;
        font-weight: normal;
        margin-bottom: 8px;
    }

    .course-item .course-info h3 span {
        font-size: 14px;
        color: #9b9b9b;
        float: right;
        margin-top: 14px;
    }

    .course-item .course-info h3 span img {
        width: 11px;
        height: auto;
        margin-right: 7px;
    }

    .course-item .course-info .teather-info {
        font-size: 14px;
        color: #9b9b9b;
        margin-bottom: 14px;
        padding-bottom: 14px;
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
    }

    .course-item .course-info .teather-info span {
        float: right;
    }

    .course-item .lesson-list::after {
        content: "";
        display: block;
        clear: both;
    }

    .course-item .lesson-list li {
        float: left;
        width: 44%;
        font-size: 14px;
        color: #666;
        padding-left: 22px;
        /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */
        background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;
        margin-bottom: 15px;
    }

    .course-item .lesson-list li .lesson-title {
        /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */
        text-overflow: ellipsis;
        overflow: hidden;
        white-space: nowrap;
        display: inline-block;
        max-width: 200px;
    }

    .course-item .lesson-list li:hover {
        background-image: url("/src/assets/img/play-icon-yellow.svg");
        color: #ffc210;
    }

    .course-item .lesson-list li .free {
        width: 34px;
        height: 20px;
        color: #fd7b4d;
        vertical-align: super;
        margin-left: 10px;
        border: 1px solid #fd7b4d;
        border-radius: 2px;
        text-align: center;
        font-size: 13px;
        white-space: nowrap;
    }

    .course-item .lesson-list li:hover .free {
        color: #ffc210;
        border-color: #ffc210;
    }

    .course-item .pay-box::after {
        content: "";
        display: block;
        clear: both;
    }

    .course-item .pay-box .discount-type {
        padding: 6px 10px;
        font-size: 16px;
        color: #fff;
        text-align: center;
        margin-right: 8px;
        background: #fa6240;
        border: 1px solid #fa6240;
        border-radius: 10px 0 10px 0;
        float: left;
    }

    .course-item .pay-box .discount-price {
        font-size: 24px;
        color: #fa6240;
        float: left;
    }

    .course-item .pay-box .original-price {
        text-decoration: line-through;
        font-size: 14px;
        color: #9b9b9b;
        margin-left: 10px;
        float: left;
        margin-top: 10px;
    }

    .course-item .pay-box .buy-now {
        width: 120px;
        height: 38px;
        background: transparent;
        color: #fa6240;
        font-size: 16px;
        border: 1px solid #fd7b4d;
        border-radius: 3px;
        transition: all .2s ease-in-out;
        float: right;
        text-align: center;
        line-height: 38px;
    }

    .course-item .pay-box .buy-now:hover {
        color: #fff;
        background: #ffc210;
        border: 1px solid #ffc210;
    }
</style>

2.修訂課程主頁

3.課程詳情頁

1.詳情頁前臺

詳情頁組件

依賴:在luffycity目錄下的命令
>: cnpm install vue-video-player
配置:main.js
// vue-video播放器
require('video.js/dist/video-js.css');
require('vue-video-player/src/custom-theme.css');
import VideoPlayer from 'vue-video-player'
Vue.use(VideoPlayer);
資源:圖片放置assrts/img文件夾
"""
enum.svg
chapter-player.svg
cart-yellow.svg
"""
路由:router.js
import CourseDetail from './views/CourseDetail.vue'
export default new Router({
    routes: [
        // ...
        {
            path: '/course/detail/:pk',
            name: 'course-detail',
            component: CourseDetail
        }
    ]
}
組件
<template>
    <div class="detail">
        <Header/>
        <div class="main">
            <div class="course-info">
                <div class="wrap-left">
                    <videoPlayer class="video-player vjs-custom-skin"
                                 ref="videoPlayer"
                                 :playsinline="true"
                                 :options="playerOptions"
                                 @play="onPlayerPlay($event)"
                                 @pause="onPlayerPause($event)">
                    </videoPlayer>
                </div>
                <div class="wrap-right">
                    <h3 class="course-name">{{course_info.name}}</h3>
                    <p class="data">{{course_info.students}}人在學&nbsp;&nbsp;&nbsp;&nbsp;課程總時長:{{course_info.sections}}課時/{{course_info.pub_sections}}小時&nbsp;&nbsp;&nbsp;&nbsp;難度:{{course_info.level_name}}</p>
                    <div class="sale-time">
                        <p class="sale-type">價格 <span class="original_price">¥{{course_info.price}}</span></p>
                        <p class="expire"></p>
                    </div>
                    <div class="buy">
                        <div class="buy-btn">
                            <button class="buy-now">當即購買</button>
                            <button class="free">免費試學</button>
                        </div>
                        <!--<div class="add-cart" @click="add_cart(course_info.id)">-->
														<!--<img src="@/assets/img/cart-yellow.svg" alt="">加入購物車-->
                        <!--</div>-->
                    </div>
                </div>
            </div>
            <div class="course-tab">
                <ul class="tab-list">
                    <li :class="tabIndex==1?'active':''" @click="tabIndex=1">詳情介紹</li>
                    <li :class="tabIndex==2?'active':''" @click="tabIndex=2">課程章節 <span :class="tabIndex!=2?'free':''">(試學)</span>
                    </li>
                    <li :class="tabIndex==3?'active':''" @click="tabIndex=3">用戶評論</li>
                    <li :class="tabIndex==4?'active':''" @click="tabIndex=4">常見問題</li>
                </ul>
            </div>
            <div class="course-content">
                <div class="course-tab-list">
                    <div class="tab-item" v-if="tabIndex==1">
                        <div class="course-brief" v-html="course_info.brief_text"></div>
                    </div>
                    <div class="tab-item" v-if="tabIndex==2">
                        <div class="tab-item-title">
                            <p class="chapter">課程章節</p>
                            <p class="chapter-length">共{{course_chapters.length}}章 {{course_info.sections}}個課時</p>
                        </div>
                        <div class="chapter-item" v-for="chapter in course_chapters" :key="chapter.name">
                            <p class="chapter-title"><img src="@/assets/img/enum.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}
                            </p>
                            <ul class="section-list">
                                <li class="section-item" v-for="section in chapter.coursesections" :key="section.name">
                                    <p class="name"><span class="index">{{chapter.chapter}}-{{section.orders}}</span>
                                        {{section.name}}<span class="free" v-if="section.free_trail">免費</span></p>
                                    <p class="time">{{section.duration}} <img src="@/assets/img/chapter-player.svg"></p>
                                    <button class="try" v-if="section.free_trail">當即試學</button>
                                    <button class="try" v-else>當即購買</button>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <div class="tab-item" v-if="tabIndex==3">
                        用戶評論
                    </div>
                    <div class="tab-item" v-if="tabIndex==4">
                        常見問題
                    </div>
                </div>
                <div class="course-side">
                    <div class="teacher-info">
                        <h4 class="side-title"><span>授課老師</span></h4>
                        <div class="teacher-content">
                            <div class="cont1">
                                <img :src="course_info.teacher.image">
                                <div class="name">
                                    <p class="teacher-name">{{course_info.teacher.name}}
                                        {{course_info.teacher.title}}</p>
                                    <p class="teacher-title">{{course_info.teacher.signature}}</p>
                                </div>
                            </div>
                            <p class="narrative">{{course_info.teacher.brief}}</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <Footer/>
    </div>
</template>

<script>
    import Header from "@/components/Header"
    import Footer from "@/components/Footer"

    // 加載組件
    import {videoPlayer} from 'vue-video-player';

    export default {
        name: "Detail",
        data() {
            return {
                tabIndex: 2,   // 當前選項卡顯示的下標
                course_id: 0, // 當前課程信息的ID
                course_info: {
                    teacher: {},
                }, // 課程信息
                course_chapters: [], // 課程的章節課時列表
                playerOptions: {
                    aspectRatio: '16:9', // 將播放器置於流暢模式,並在計算播放器的動態大小時使用該值。值應該表明一個比例 - 用冒號分隔的兩個數字(例如"16:9"或"4:3")
                    sources: [{ // 播放資源和資源格式
                        type: "video/mp4",
                        src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的視頻地址(必填)
                    }],
                }
            }
        },
        created() {
            this.get_course_id();
            this.get_course_data();
            this.get_chapter();
        },
        methods: {
            onPlayerPlay() {
                // 當視頻播放時,執行的方法
            },
            onPlayerPause() {
                // 當視頻暫停播放時,執行的方法
            },
            get_course_id() {
                // 獲取地址欄上面的課程ID
                this.course_id = this.$route.params.pk;
                if (this.course_id < 1) {
                    let _this = this;
                    _this.$alert("對不起,當前視頻不存在!", "警告", {
                        callback() {
                            _this.$router.go(-1);
                        }
                    });
                }
            },
            get_course_data() {
                // ajax請求課程信息
                this.$axios.get(`${this.$settings.base_url}/course/${this.course_id}/`).then(response => {
                    // window.console.log(response.data);
                    this.course_info = response.data;
                }).catch(() => {
                    this.$message({
                        message: "對不起,訪問頁面出錯!請聯繫客服工做人員!"
                    });
                })
            },

            get_chapter() {
                // 獲取當前課程對應的章節課時信息
                // http://127.0.0.1:8000/course/chapters/?course=(pk)
                this.$axios.get(`${this.$settings.base_url}/course/chapters/`, {
                    params: {
                        "course": this.course_id,
                    }
                }).then(response => {
                    this.course_chapters = response.data;
                }).catch(error => {
                    window.console.log(error.response);
                })
            },
        },
        components: {
            Header,
            Footer,
            videoPlayer, // 註冊組件
        }
    }
</script>

<style scoped>
    .main {
        background: #fff;
        padding-top: 30px;
    }

    .course-info {
        width: 1200px;
        margin: 0 auto;
        overflow: hidden;
    }

    .wrap-left {
        float: left;
        width: 690px;
        height: 388px;
        background-color: #000;
    }

    .wrap-right {
        float: left;
        position: relative;
        height: 388px;
    }

    .course-name {
        font-size: 20px;
        color: #333;
        padding: 10px 23px;
        letter-spacing: .45px;
    }

    .data {
        padding-left: 23px;
        padding-right: 23px;
        padding-bottom: 16px;
        font-size: 14px;
        color: #9b9b9b;
    }

    .sale-time {
        width: 464px;
        background: #fa6240;
        font-size: 14px;
        color: #4a4a4a;
        padding: 10px 23px;
        overflow: hidden;
    }

    .sale-type {
        font-size: 16px;
        color: #fff;
        letter-spacing: .36px;
        float: left;
    }

    .sale-time .expire {
        font-size: 14px;
        color: #fff;
        float: right;
    }

    .sale-time .expire .second {
        width: 24px;
        display: inline-block;
        background: #fafafa;
        color: #5e5e5e;
        padding: 6px 0;
        text-align: center;
    }

    .course-price {
        background: #fff;
        font-size: 14px;
        color: #4a4a4a;
        padding: 5px 23px;
    }

    .discount {
        font-size: 26px;
        color: #fa6240;
        margin-left: 10px;
        display: inline-block;
        margin-bottom: -5px;
    }

    .original {
        font-size: 14px;
        color: #9b9b9b;
        margin-left: 10px;
        text-decoration: line-through;
    }

    .buy {
        width: 464px;
        padding: 0px 23px;
        position: absolute;
        left: 0;
        bottom: 20px;
        overflow: hidden;
    }

    .buy .buy-btn {
        float: left;
    }

    .buy .buy-now {
        width: 125px;
        height: 40px;
        border: 0;
        background: #ffc210;
        border-radius: 4px;
        color: #fff;
        cursor: pointer;
        margin-right: 15px;
        outline: none;
    }

    .buy .free {
        width: 125px;
        height: 40px;
        border-radius: 4px;
        cursor: pointer;
        margin-right: 15px;
        background: #fff;
        color: #ffc210;
        border: 1px solid #ffc210;
    }

    .add-cart {
        float: right;
        font-size: 14px;
        color: #ffc210;
        text-align: center;
        cursor: pointer;
        margin-top: 10px;
    }

    .add-cart img {
        width: 20px;
        height: 18px;
        margin-right: 7px;
        vertical-align: middle;
    }

    .course-tab {
        width: 100%;
        background: #fff;
        margin-bottom: 30px;
        box-shadow: 0 2px 4px 0 #f0f0f0;

    }

    .course-tab .tab-list {
        width: 1200px;
        margin: auto;
        color: #4a4a4a;
        overflow: hidden;
    }

    .tab-list li {
        float: left;
        margin-right: 15px;
        padding: 26px 20px 16px;
        font-size: 17px;
        cursor: pointer;
    }

    .tab-list .active {
        color: #ffc210;
        border-bottom: 2px solid #ffc210;
    }

    .tab-list .free {
        color: #fb7c55;
    }

    .course-content {
        width: 1200px;
        margin: 0 auto;
        background: #FAFAFA;
        overflow: hidden;
        padding-bottom: 40px;
    }

    .course-tab-list {
        width: 880px;
        height: auto;
        padding: 20px;
        background: #fff;
        float: left;
        box-sizing: border-box;
        overflow: hidden;
        position: relative;
        box-shadow: 0 2px 4px 0 #f0f0f0;
    }

    .tab-item {
        width: 880px;
        background: #fff;
        padding-bottom: 20px;
        box-shadow: 0 2px 4px 0 #f0f0f0;
    }

    .tab-item-title {
        justify-content: space-between;
        padding: 25px 20px 11px;
        border-radius: 4px;
        margin-bottom: 20px;
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
        overflow: hidden;
    }

    .chapter {
        font-size: 17px;
        color: #4a4a4a;
        float: left;
    }

    .chapter-length {
        float: right;
        font-size: 14px;
        color: #9b9b9b;
        letter-spacing: .19px;
    }

    .chapter-title {
        font-size: 16px;
        color: #4a4a4a;
        letter-spacing: .26px;
        padding: 12px;
        background: #eee;
        border-radius: 2px;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
    }

    .chapter-title img {
        width: 18px;
        height: 18px;
        margin-right: 7px;
        vertical-align: middle;
    }

    .section-list {
        padding: 0 20px;
    }

    .section-list .section-item {
        padding: 15px 20px 15px 36px;
        cursor: pointer;
        justify-content: space-between;
        position: relative;
        overflow: hidden;
    }

    .section-item .name {
        font-size: 14px;
        color: #666;
        float: left;
    }

    .section-item .index {
        margin-right: 5px;
    }

    .section-item .free {
        font-size: 12px;
        color: #fff;
        letter-spacing: .19px;
        background: #ffc210;
        border-radius: 100px;
        padding: 1px 9px;
        margin-left: 10px;
    }

    .section-item .time {
        font-size: 14px;
        color: #666;
        letter-spacing: .23px;
        opacity: 1;
        transition: all .15s ease-in-out;
        float: right;
    }

    .section-item .time img {
        width: 18px;
        height: 18px;
        margin-left: 15px;
        vertical-align: text-bottom;
    }

    .section-item .try {
        width: 86px;
        height: 28px;
        background: #ffc210;
        border-radius: 4px;
        font-size: 14px;
        color: #fff;
        position: absolute;
        right: 20px;
        top: 10px;
        opacity: 0;
        transition: all .2s ease-in-out;
        cursor: pointer;
        outline: none;
        border: none;
    }

    .section-item:hover {
        background: #fcf7ef;
        box-shadow: 0 0 0 0 #f3f3f3;
    }

    .section-item:hover .name {
        color: #333;
    }

    .section-item:hover .try {
        opacity: 1;
    }

    .course-side {
        width: 300px;
        height: auto;
        margin-left: 20px;
        float: right;
    }

    .teacher-info {
        background: #fff;
        margin-bottom: 20px;
        box-shadow: 0 2px 4px 0 #f0f0f0;
    }

    .side-title {
        font-weight: normal;
        font-size: 17px;
        color: #4a4a4a;
        padding: 18px 14px;
        border-bottom: 1px solid #333;
        border-bottom-color: rgba(51, 51, 51, .05);
    }

    .side-title span {
        display: inline-block;
        border-left: 2px solid #ffc210;
        padding-left: 12px;
    }

    .teacher-content {
        padding: 30px 20px;
        box-sizing: border-box;
    }

    .teacher-content .cont1 {
        margin-bottom: 12px;
        overflow: hidden;
    }

    .teacher-content .cont1 img {
        width: 54px;
        height: 54px;
        margin-right: 12px;
        float: left;
    }

    .teacher-content .cont1 .name {
        float: right;
    }

    .teacher-content .cont1 .teacher-name {
        width: 188px;
        font-size: 16px;
        color: #4a4a4a;
        padding-bottom: 4px;
    }

    .teacher-content .cont1 .teacher-title {
        width: 188px;
        font-size: 13px;
        color: #9b9b9b;
        white-space: nowrap;
    }

    .teacher-content .narrative {
        font-size: 14px;
        color: #666;
        line-height: 24px;
    }
</style>

2.詳情頁後臺

詳情頁後臺

路由:source/urls.py
re_path("(?P<pk>\d+)/", views.CourseRetrieveAPIView.as_view()),
path("chapters/", views.CourseChapterListAPIView.as_view()),
視圖:source/views.py
from .models import Course
from .models import CourseChapter
from rest_framework.generics import ListAPIView
from rest_framework.generics import RetrieveAPIView
from . import serializers
from django_filters.rest_framework.backends import DjangoFilterBackend

class CourseRetrieveAPIView(RetrieveAPIView):
    """課程詳情信息"""
    queryset = Course.objects.filter(is_delete=False, is_show=True)
    serializer_class = serializers.CourseRetrieveModelSSerializer

class CourseChapterListAPIView(ListAPIView):
    """課程詳情信息"""
    queryset = CourseChapter.objects.filter(is_delete=False, is_show=True).order_by("chapter")
    serializer_class = serializers.CourseChapterModelSerializer
    filter_backends = [DjangoFilterBackend]
    filter_fields = ['course', ]
序列化:source/serializers.py
from . import models
from rest_framework.serializers import ModelSerializer
class CourseRetrieveModelSSerializer(ModelSerializer):
    # 課程詳情的序列化器
    teacher = TeacherSerializer()
    class Meta:
        model = models.Course
        fields = ["id", "name", "course_img", "students", "sections", "pub_sections", "price", "teacher", "level_name"]

class CourseSessionModelSerializer(ModelSerializer):
    class Meta:
        model = models.CourseSection
        fields = ["id", "name", "duration", "free_trail", "orders"]

class CourseChapterModelSerializer(ModelSerializer):
    coursesections = CourseSessionModelSerializer(many=True)

    class Meta:
        model = models.CourseChapter
        fields = ["chapter", "name", "summary", "coursesections"]
視圖字段:source/model.py
class Course(BaseModel):
    # ...
    @property
    def level_name(self):
        # 難度名
        return self.level_choices[self.level][1]

運用圖片



支付

支付寶支付

# 一、在沙箱環境下實名認證:https://openhome.alipay.com/platform/appDaily.htm?tab=info

# 二、電腦網站支付API:https://docs.open.alipay.com/270/105898/

# 三、完成RSA密鑰生成:https://docs.open.alipay.com/291/105971

# 四、在開發中心的沙箱應用下設置應用公鑰:填入生成的公鑰文件中的內容

# 五、Python支付寶開源框架:https://github.com/fzlee/alipay
# >: pip install python-alipay-sdk --upgrade

# 七、公鑰私鑰設置
"""
# alipay_public_key.pem
-----BEGIN PUBLIC KEY-----
支付寶公鑰
-----END PUBLIC KEY-----

# app_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
用戶私鑰
-----END RSA PRIVATE KEY-----
"""

# 八、支付寶連接
"""
開發:https://openapi.alipay.com/gateway.do
沙箱:https://openapi.alipaydev.com/gateway.do
"""

支付流程

aliapy二次封裝包

依賴
>: pip install python-alipay-sdk --upgrade
結構
libs
    ├── iPay  							# aliapy二次封裝包
    │   ├── __init__.py 				# 包文件
    │   ├── keys						# 密鑰文件夾
    │   │   ├── alipay_public_key.pem  	# 支付寶公鑰
    │   │   └── app_private_key.pem  	# 應用私鑰
    └── └── settings.py  				# 應用配置
setting.py
import os
# 支付寶應用id
APP_ID = '2016093000631831'
# 默認異步回調的地址,一般設置None就行
APP_NOTIFY_URL = None
# 應用私鑰文件路徑
APP_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'app_private_key.pem')
# 支付寶公鑰文件路徑
ALIPAY_PUBLIC_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'alipay_public_key.pem')
# 簽名方式
SIGN_TYPE = 'RSA2'
# 是不是測試環境
DEBUG = True
_init_.py
from alipay import AliPay
from .settings import *
# 對外提供
from .settings import RETURN_URL, NOTIFY_URL
# 對外提供支付對象
alipay = AliPay(
    appid=APP_ID,
    app_notify_url=APP_NOTIFY_URL,
    app_private_key_path=APP_PRIVATE_KEY_PATH,
    alipay_public_key_path=ALIPAY_PUBLIC_KEY_PATH,
    sign_type=SIGN_TYPE,
    debug=DEBUG
)
alipay_public_key.pem
-----BEGIN PUBLIC KEY-----
支付寶公鑰
-----END PUBLIC KEY-----
app_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
應用私鑰
-----END RSA PRIVATE KEY-----
補充:dev.py
# 上線後必須換成官網地址
# 同步回調的接口(get),先後臺分離時通常設置前臺頁面url
RETURN_URL = 'http://127.0.0.1:8080/pay/success'
# 異步回調的接口(post),必定設置爲後臺服務器接口
NOTIFY_URL = 'http://127.0.0.1:8000/order/success/'

支付模塊

order/models.py
"""
訂單:訂單號、流水號、價格、用戶
訂單詳情(自定義關係表):訂單、課程
"""

from django.db import models
from utils.model import BaseModel
from user.models import User
from course.models import Course


class Order(BaseModel):
    """訂單模型"""
    status_choices = (
        (0, '未支付'),
        (1, '已支付'),
        (2, '已取消'),
        (3, '超時取消'),
    )
    pay_choices = (
        (1, '支付寶'),
        (2, '微信支付'),
    )
    subject = models.CharField(max_length=150, verbose_name="訂單標題")
    total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="訂單總價", default=0)
    out_trade_no = models.CharField(max_length=64, verbose_name="訂單號", unique=True)
    trade_no = models.CharField(max_length=64, null=True, verbose_name="流水號")
    order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="訂單狀態")
    pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
    pay_time = models.DateTimeField(null=True, verbose_name="支付時間")
    user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING, db_constraint=False,
                             verbose_name="下單用戶")

    # 多餘字段
    orders = models.IntegerField(verbose_name='顯示順序', default=0)

    class Meta:
        db_table = "luffy_order"
        verbose_name = "訂單記錄"
        verbose_name_plural = "訂單記錄"

    def __str__(self):
        return "%s - ¥%s" % (self.subject, self.total_amount)

    @property
    def courses(self):
        data_list = []
        for item in self.order_courses.all():
            data_list.append({
                "id": item.id,
                "course_name": item.course.name,
                "real_price": item.real_price,
            })

        return data_list


class OrderDetail(BaseModel):
    """訂單詳情"""
    order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,
                              verbose_name="訂單")
    course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False,
                               verbose_name="課程")
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價")
    real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程實價")

    class Meta:
        db_table = "luffy_order_detail"
        verbose_name = "訂單詳情"
        verbose_name_plural = "訂單詳情"

    def __str__(self):
        return "%s訂單(%s)" % (self.course.name, self.order.order_number)

後臺接口

from django.urls import path
from . import views
urlpatterns = [
    path('pay/', views.PayAPIView.as_view()),
    path('success/', views.SuccessAPIView.as_view()),
]

訂單序列化模塊

from rest_framework import serializers
from . import models
class OrderModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Order
        fields = ('subject', 'total_amount', 'out_trade_no', 'pay_type', 'user')
        extra_kwargs = {
            'pay_type': {
                'required': True
            },
            'total_amount': {
                'required': True
            },
        }

支付接口生成支付連接

import time
from rest_framework.views import APIView
from utils.response import APIResponse
from libs.iPay import alipay
from . import authentications, serializers
from rest_framework.permissions import IsAuthenticated
from django.conf import settings
# 獲取前臺 商品名、價格,產生 訂單、支付連接
class PayAPIView(APIView):
    authentication_classes = [authentications.JWTAuthentication]
    permission_classes = [IsAuthenticated]
    def post(self, request, *args, **kwargs):
        # 前臺提供:商品名、總價、支付方式
        request_data = request.data
        # 後臺產生:訂單號、用戶
        out_trade_no = '%d' % time.time() * 2
        request_data['out_trade_no'] = out_trade_no
        request_data['user'] = request.user.id

        # 反序列化數據,用於訂單生成前的校驗
        order_ser = serializers.OrderModelSerializer(data=request_data)
        if order_ser.is_valid():
            # 生成訂單,訂單默認狀態爲:未支付
            order = order_ser.save()
            # 支付連接的參數
            order_string = alipay.api_alipay_trade_page_pay(
                subject=order.subject,
                out_trade_no=order.out_trade_no,
                total_amount='%.2f' % order.total_amount,
                return_url=settings.RETURN_URL,
                notify_url=settings.NOTIFY_URL
            )
            # 造成支付連接:alipay._gateway根據字符環境DEBUG配置信息,決定是沙箱仍是真實支付環境
            pay_url = '%s?%s' % (alipay._gateway, order_string)
            return APIResponse(0, 'ok', pay_url=pay_url)


        return APIResponse(1, 'no ok', results=order_ser.errors)

前臺回調接口的頁面

{
  	path: '/pay/success',
    name: 'pay-success',
    component: PaySuccess
},
<template>
    <div class="pay-success">
        <Header/>
        <div class="main">
            <div class="title">
                <div class="success-tips">
                    <p class="tips">您已成功購買 1 門課程!</p>
                </div>
            </div>
            <div class="order-info">
                <p class="info"><b>訂單號:</b><span>{{ result.out_trade_no }}</span></p>
                <p class="info"><b>交易號:</b><span>{{ result.trade_no }}</span></p>
                <p class="info"><b>付款時間:</b><span><span>{{ result.timestamp }}</span></span></p>
            </div>
            <div class="study">
                <span>當即學習</span>
            </div>
        </div>
        <Footer/>
    </div>
</template>

<script>
    import Header from "@/components/Header"
    import Footer from "@/components/Footer"

    export default {
        name: "Success",
        data() {
            return {
                result: {},
            };
        },
        created() {
            // 判斷登陸狀態
            let token = this.$cookies.get('token');
            if (!token) {
                this.$message.error('非法請求');
                this.$router.go(-1)
            }


            localStorage.this_nav = '/';
            if (!location.search.length) return;
            let params = location.search.substring(1);
            let items = params.length ? params.split('&') : [];
            //逐個將每一項添加到args對象中
            for (let i = 0; i < items.length; i++) {
                let k_v = items[i].split('=');
                //解碼操做,由於查詢字符串通過編碼的
                let k = decodeURIComponent(k_v[0]);
                let v = decodeURIComponent(k_v[1]);
                this.result[k] = v;
                // this.result[k_v[0]] = k_v[1];
            }
            // console.log(this.result);

            // 把地址欄上面的支付結果,轉發給後端
            this.$axios({
                url: this.$settings.base_url + '/order/success/' + location.search,
                method: 'patch',
                headers: {
                    Authorization: token
                }
            }).then(response => {
                console.log(response.data);
            }).catch(() => {
                console.log('支付結果同步失敗');
            })
        },
        components: {
            Header,
            Footer,
        }
    }
</script>

<style scoped>

    .main {
        padding: 60px 0;
        margin: 0 auto;
        width: 1200px;
        background: #fff;
    }

    .main .title {
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        padding: 25px 40px;
        border-bottom: 1px solid #f2f2f2;
    }

    .main .title .success-tips {
        box-sizing: border-box;
    }

    .title img {
        vertical-align: middle;
        width: 60px;
        height: 60px;
        margin-right: 40px;
    }

    .title .success-tips {
        box-sizing: border-box;
    }

    .title .tips {
        font-size: 26px;
        color: #000;
    }


    .info span {
        color: #ec6730;
    }

    .order-info {
        padding: 25px 48px;
        padding-bottom: 15px;
        border-bottom: 1px solid #f2f2f2;
    }

    .order-info p {
        display: -ms-flexbox;
        display: flex;
        margin-bottom: 10px;
        font-size: 16px;
    }

    .order-info p b {
        font-weight: 400;
        color: #9d9d9d;
        white-space: nowrap;
    }

    .study {
        padding: 25px 40px;
    }

    .study span {
        display: block;
        width: 140px;
        height: 42px;
        text-align: center;
        line-height: 42px;
        cursor: pointer;
        background: #ffc210;
        border-radius: 6px;
        font-size: 16px;
        color: #fff;
    }
</style>

支付完成訂單校驗的接口

from . import models
from utils.logging import logger
from rest_framework.response import Response
class SuccessAPIView(APIView):
    # 不能認證,別人支付寶異步回調就進不來了
    # authentication_classes = [authentications.JWTAuthentication]
    # permission_classes = [IsAuthenticated]
    def patch(self, request, *args, **kwargs):
        # 默認是QueryDict類型,不能使用pop方法
        request_data = request.query_params.dict()
        # 必須將 sign、sign_type(內部有安全處理) 從數據中取出,拿sign與剩下的數據進行校驗
        sign = request_data.pop('sign')
        result = alipay.verify(request_data, sign)
        if result:  # 同步回調:修改訂單狀態
            try:
                out_trade_no = request_data.get('out_trade_no')
                order = models.Order.objects.get(out_trade_no=out_trade_no)
                if order.order_status != 1:
                    order.order_status = 1
                    order.save()
            except:
                pass
            return APIResponse(0, '支付成功')
        return APIResponse(1, '支付失敗')

    # 支付寶異步回調
    def post(self, request, *args, **kwargs):
        # 默認是QueryDict類型,不能使用pop方法
        request_data = request.data.dict()
        # 必須將 sign、sign_type(內部有安全處理) 從數據中取出,拿sign與剩下的數據進行校驗
        sign = request_data.pop('sign')
        result = alipay.verify(request_data, sign)
        # 異步回調:修改訂單狀態
        if result and request_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED" ):
            out_trade_no = request_data.get('out_trade_no')
            logger.critical('%s支付成功' % out_trade_no)
            try:
                order = models.Order.objects.get(out_trade_no=out_trade_no)
                if order.order_status != 1:
                    order.order_status = 1
                    order.save()
            except:
                pass
            # 支付寶八次異步通知,訂單成功必定要返回 success
            return Response('success')
        return Response('failed')

上線

購買服務器

# 購買阿里雲服務器
# 短時間或是測試使用,建立 按量收費 服務器,能夠隨時刪除,刪除後再也不計費,但要保證帳戶餘額100元以上

鏈接服務器

1)帳號
>: ssh root@39.98.144.221

2)密碼
>: ********

服務器命令

管理員權限
1)如下全部的服務器命令都可以在管理員權限下執行
>: sudo 命令
配置終端
1)編輯配置文件
>: vim ~/.bash_profile

2)將原來內容所有刪除掉
>: ggdG

3)進入編輯狀態:填入下方兩行
>: i

export PATH=$PATH:$HOME/bin
PS1='Path:\w\n>:'

4)退出編輯狀態
>: esc

5)保存修改並退出
>: :wq

6)生效配置
>: source ~/.bash_profile

重要

更新系統軟件包
>: yum update -y
安裝軟件管理包和可能使用的依賴
>: yum -y groupinstall "Development tools"
>: yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel

安裝Mysql

1)前往用戶根目錄
>: cd ~

2)下載mysql57
>: wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm

也能夠本地上傳,這條命令要在本地終端上執行
>: scp -r C:\Users\dell\Desktop\pkg\mysql57-community-release-el7-10.noarch.rpm root@39.98.144.221:~

3)安裝mysql57
>: yum -y install mysql57-community-release-el7-10.noarch.rpm
>: yum -y install mysql-community-server

4)啓動mysql57並查看啓動狀態
>: systemctl start mysqld.service
>: systemctl status mysqld.service

5)查看默認密碼並登陸
>: grep "password" /var/log/mysqld.log
>: mysql -uroot -p

6)修改密碼
>: ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password';
>: ALTER USER 'root'@'localhost' IDENTIFIED BY 'Owen1234?';

安裝Redis

1)前往用戶根目錄
>: cd ~

2)下載redis-5.0.5
>: wget http://download.redis.io/releases/redis-5.0.5.tar.gz
>: scp -r C:\Users\dell\Desktop\pkg\redis-5.0.5.tar.gz root@39.98.144.221:~

3)解壓安裝包
>: tar -xf redis-5.0.5.tar.gz

4)進入目標文件
>: cd redis-5.0.5

5)編譯環境
>: make

6)複製環境到指定路徑完成安裝
>: cp -r ~/redis-5.0.5 /usr/local/redis

7)配置redis能夠後臺啓動:修改下方內容
>: vim /usr/local/redis/redis.conf

daemonize yes

8)完成配置修改
>: esc
>: :wq

9)創建軟鏈接
>: ln -s /usr/local/redis/src/redis-server /usr/bin/redis-server
>: ln -s /usr/local/redis/src/redis-cli /usr/bin/redis-cli

10)後臺運行redis
>: redis-server &
ctrl + c

11)測試redis環境
>: redis-cli
ctrl + c

12)關閉redis服務
>: pkill -f redis -9

安裝Python3.6

1)前往用戶根目錄
>: cd ~

2)下載 或 上傳 Python3.6.7
>: wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz
>: scp -r 本地Python-3.6.7.tar.xz ssh root@39.98.144.221:服務器路徑
>: scp -r C:\Users\dell\Desktop\pkg\Python-3.6.7.tar.xz ssh root@39.98.144.221:~

3)解壓安裝包
>: tar -xf Python-3.6.7.tar.xz

4)進入目標文件
>: cd Python-3.6.7

5)配置安裝路徑:/usr/local/python3
>: ./configure --prefix=/usr/local/python3

6)編譯並安裝
>: make && sudo make install

7)創建軟鏈接:終端命令 python3,pip3
>: ln -s /usr/local/python3/bin/python3.6 /usr/bin/python3
>: ln -s /usr/local/python3/bin/pip3.6 /usr/bin/pip3

8)刪除安裝包與文件:
>: rm -rf Python-3.6.7
>: rm -rf Python-3.6.7.tar.xz

配置pip源:阿里雲不用配置,默認配置阿里源

1)建立pip配置路徑
>: mkdir ~/.pip

2)進入目錄編輯配置文件:填入下方內容
cd ~/.pip && vim pip.conf

[global]
index-url = http://pypi.douban.com/simple
[install]
use-mirrors =true
mirrors =http://pypi.douban.com/simple/
trusted-host =pypi.douban.com

安裝uwsgi

1)在真實環境下安裝
pip3 install uwsgi

2)創建軟鏈接
ln -s /usr/local/python3/bin/uwsgi /usr/bin/uwsgi

安裝虛擬環境

1)安裝依賴
>: pip3 install virtualenv
>: pip3 install virtualenvwrapper

2)創建虛擬環境軟鏈接
>: ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv

3)配置虛擬環境:填入下方內容
>: vim ~/.bash_profile

VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/local/python3/bin/virtualenvwrapper.sh

4)退出編輯狀態
>: esc

5)保存修改並退出
>: :wq

6)更新配置文件內容
>: source ~/.bash_profile

7)虛擬環境默認根目錄:~/.virtualenvs

服務器運行測試Django項目

1)建立虛擬環境
>: mkvirtualenv test_venv

2)安裝依賴
>: pip install django

3)前往目標目錄,建立項目工做目錄,再進入
>: cd /home
>: mkdir project
>: cd project

4)建立Django項目,並進入
>: django-admin startproject test_site
>: cd test_site

5)完成項目配置:修改下方几行內容
>: vim /home/project/test_site/test_site/settings.py

ALLOWED_HOSTS = ['*']
#DATABASES = {
#    'default': {
#        'ENGINE': 'django.db.backends.sqlite3',
#        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#    }
#}

6)跑原生服務
>: python3 manage.py runserver 0.0.0.0:80

安裝Nginx

1)前往用戶根目錄
>: cd ~

2)下載nginx1.13.7
>: wget http://nginx.org/download/nginx-1.13.7.tar.gz

3)解壓安裝包
>: tar -xf nginx-1.13.7.tar.gz

4)進入目標文件
>: cd nginx-1.13.7

5)配置安裝路徑:/usr/local/nginx
>: ./configure --prefix=/usr/local/nginx

6)編譯並安裝
>: make && sudo make install

7)創建軟鏈接:終端命令 nginx
>: ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx

8)刪除安裝包與文件:
>: rm -rf nginx-1.13.7
>: rm -rf nginx-1.13.7.tar.xz

9)測試Nginx環境,服務器運行nginx,本地訪問服務器ip
>: nginx
>: 服務器綁定的域名 或 ip:80

Nginx命令

1)啓動
>: nginx

2)關閉nginx
>: nginx -s stop

3)重啓nginx
>: nginx -s reload

4)查看端口,強行關閉
>: ps -aux|grep nginx
>: kill <pid:進程編號>

Nginx & uwsgi 運行Django

1)在項目的虛擬環境安裝uwsgi
>: workon test_venv
>: pip install uwsgi

2)項目根目錄配置uwsgi:填入下方內容
>: vim /home/project/test_site/test_site.xml

<uwsgi>    
   <socket>127.0.0.1:8808</socket> <!-- 內部端口,自定義 --> 
   <chdir>/home/project/test_site/</chdir> <!-- 項目路徑 -->            
   <module>test_site.wsgi</module>  <!-- test_site爲wsgi.py所在目錄名--> 
   <processes>4</processes> <!-- 進程數 -->     
   <daemonize>uwsgi.log</daemonize> <!-- 日誌文件 -->
</uwsgi>

3)完成項目配置:修改下方几行內容
>: vim /home/project/test_site/test_site/settings.py

DEBUG = False
ALLOWED_HOSTS = ['*']

4)去向Nginx配置目錄,備份配置,徹底更新配置:填入下方內容
>: cd /usr/local/nginx/conf
>: cp nginx.conf nginx.conf.bak
>: vim nginx.conf
>: ggdG
>: i

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    server {
        listen 8000;
        server_name  127.0.0.1; # 改成本身的域名,沒域名修改成127.0.0.1:80
        charset utf-8;
        location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi裏配置的同樣
           uwsgi_param UWSGI_SCRIPT test_site.wsgi;  #wsgi.py所在的目錄名+.wsgi
           uwsgi_param UWSGI_CHDIR /home/project/test_site/; # 項目路徑
        }
    }
}

5)啓動uwsgi
>: uwsgi -x /home/project/test_site/test_site.xml

6)啓動nginx
>: nginx

7)瀏覽器測試:http://39.98.144.221/admin

8)關閉uwsgi全部進程
>: pkill -f uwsgi -9

路飛項目部署:Nginx + uwsgi + django + vue

配置前臺項目

上線前配置

settings.js
base_url: 'http://39.98.144.221:8000',  // 設置公網ip

上線

1)本地項目打包,前往luffycity項目目錄下
>: cnpm run build

2)上傳
>: scp -r dist root@39.98.144.221:~

3)移動並重命名
mv ~/dist /home/html

4)去向Nginx配置目錄,備份配置,徹底更新配置:填入下方內容
>: cd /usr/local/nginx/conf
>: cp nginx.conf nginx.conf.bak
>: vim nginx.conf
>: ggdG
>: i

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    server {
        listen 80;
        server_name  127.0.0.1; # 改成本身的域名,沒域名修改成127.0.0.1:80
        charset utf-8;
        location / {
            root /home/html; # html訪問路徑
            index index.html; # html文件名稱
            try_files $uri $uri/ /index.html; # 解決單頁面應用刷新404問題
        }
    }
}

路飛後臺部署

上線前配置

prod.py:上線的配置文件,內容拷貝dev.py,前身就是settings.py

1)須要作上線修改的內容
DEBUG = False
ALLOWED_HOSTS = [
    '39.98.144.221'  # 公網ip地址
]

CORS_ORIGIN_ALLOW_ALL = True  # 容許全部跨域
CORS_ORIGIN_WHITELIST = [
]

wsgi.py 和 manage.py

1)須要作上線修改的內容
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod')

上線

1)在項目的虛擬環境安裝uwsgi
>: mkvirtualenv luffy
>: workon luffy
# 走下方 pip導入導出依賴 說明,將本地的環境依賴同步到服務器環境中
>: pip install uwsgi

2)項目根目錄配置uwsgi:填入下方內容
>: mkdir /home/project

# 注:將後臺項目移至到/home/project,能夠上傳,也能夠git,項目設置公開(私密須要配置ssl)
>: cd /home/project && git clone https://gitee.com/doctor_owen/luffyapi.git 
>: vim /home/project/luffyapi/luffyapi.xml

<uwsgi>    
   <socket>127.0.0.1:8808</socket> <!-- 內部端口,自定義 --> 
   <chdir>/home/project/luffyapi/</chdir> <!-- 項目路徑 -->            
   <module>luffyapi.wsgi</module>  <!-- luffyapi爲wsgi.py所在目錄名--> 
   <processes>4</processes> <!-- 進程數 -->     
   <daemonize>uwsgi.log</daemonize> <!-- 日誌文件 -->
</uwsgi>

####  3)配置上線項目的settings:見後臺項目部署準備視頻

4)去向Nginx配置目錄,備份配置,徹底更新配置:填入下方內容
>: vim /usr/local/nginx/conf/nginx.conf

5)在原來基礎上添加一個server
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    server {
        listen 8000;
        server_name  127.0.0.1; # 改成本身的域名,沒域名修改成127.0.0.1:80
        charset utf-8;
        location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi裏配置的同樣
           uwsgi_param UWSGI_SCRIPT luffyapi.wsgi;  #wsgi.py所在的目錄名+.wsgi
           uwsgi_param UWSGI_CHDIR /home/project/luffyapi/; # 項目路徑
        }
    }
}

見下方配置:pip環境 + 數據庫設置 + django2.0源碼

5)啓動uwsgi
>: uwsgi -x /home/project/luffyapi/luffyapi.xml

6)啓動nginx
>: nginx -s stop
>: nginx
>: nginx -s reload

7)瀏覽器測試:http://39.98.144.221:8000/xadmin

8)關閉uwsgi全部進程
>: pkill -f uwsgi -9

pip導入導出依賴

1) 本地導出項目環境,上傳線上,導入到線上環境中

本地操做
# 桌面新建env文件夾,開啓終端進入文件夾,執行下方命名
>: cd Desktop\env
>: pip3 freeze > packages.txt
# 注:把xadmin刪掉
>: scp -r packages.txt root@39.98.144.221:~

服務器操做
# 進入虛擬環境
>: workon luffy
# 導入
>: pip3 install -r packages.txt
# 安裝xadmin
>: pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2

數據庫設置 + django2.0源碼(2.0.7不用修改源碼)

1.管理員鏈接數據庫
>: mysql -uroot -pOwen1234?

2.建立數據庫
>: create database luffy default charset=utf8;

# 3.設置權限帳號密碼
# 擁有公網或局域網,其餘主機連mysql
>: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?';
# 要是本機連mysql連不上,再增長localhost域,本機就能夠登陸了
>: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?';
# 設置完有權限限制的帳號後必定要刷新權限
>: flush privileges;

4.退出mysql
quit

5.修改 prod.py | manage.py
>: vim /home/project/luffyapi/luffyapi/settings/prod.py

"PASSWORD": "Luffy123?"

>: vim /home/project/luffyapi/manage.py

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod')

6.源碼修改
>: vim /root/.virtualenvs/luffy/lib/python3.6/site-packages/django/db/backends/mysql/base.py

# 36,37行註釋

>: vim /root/.virtualenvs/luffy/lib/python3.6/site-packages/django/db/backends/mysql/operations.py

# 146行添加
	query = query.encode()

7.數據庫遷移
>: cd /home/project/luffyapi/
>: python manage.py makemigrations
>: python manage.py migrate

8.建立超級用戶
>: python manage.py createsuperuser
# 帳號密碼:admin|admin

後臺樣式問題

設置文件中配置STATIC_ROOT
# >: vim /home/project/luffyapi/luffyapi/settings/prod.py
# 在STATIC_URL下方再添加兩句
STATIC_URL = '/static/'
STATIC_ROOT = '/home/project/luffyapi/luffyapi/static'  # 服務器的絕對路徑
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
# >: esc
# >: :wq
遷移靜態樣式:項目目錄下
可能錯誤
>: mkdir /home/project/luffyapi/luffyapi/static
>: python /home/project/luffyapi/manage.py collectstatic
Nginx配置靜態路徑
>: vim /usr/local/nginx/conf/nginx.conf

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    server {
        listen 8000;
        server_name  127.0.0.1; # 改成本身的域名,沒域名修改成127.0.0.1:80
        charset utf-8;
        location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi裏配置的同樣
           uwsgi_param UWSGI_SCRIPT luffyapi.wsgi;  #wsgi.py所在的目錄名+.wsgi
           uwsgi_param UWSGI_CHDIR /home/project/luffyapi/; # 項目路徑
        }
        # 新增的配置靜態文件
        location /static {
            alias /home/project/luffyapi/luffyapi/static;
        }
    }
    server {
        listen 80;
        server_name  127.0.0.1; # 改成本身的域名,沒域名修改成127.0.0.1:80
        charset utf-8;
        location / {
            root /home/html; # html訪問路徑
            index index.html; # html文件名稱
            try_files $uri $uri/ /index.html; # 解決單頁面應用刷新404問題
        }
    }
}

>: esc
>: :wq
重啓服務
>: pkill -f uwsgi -9
>: uwsgi -x /home/project/luffyapi/luffyapi.xml
>: nginx -s reload

重點 重點 重點

# 一、真實環境和虛擬環境都要安裝uwsgi,將真實環境下的uwsgi創建軟鏈接

# 二、redis服務必定要後臺啓動:redis-server &

# 三、uwsgi啓動django項目必定要進入虛擬環境下,由於環境都是安裝在虛擬環境中

# 四、服務器的日誌都會被記錄在於uwsgi配置文件 luffyapi.xml 同類目下的 uwsgi.log 中

添加測試數據

>: mysql -uluffy -pLuffy123?
>: use luffy
INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (1, 1, 1, 0, '2019-07-14 13:44:19.661327', '2019-07-14 13:46:54.246271', 'Alex', 1, '老男孩Python教學總監', '金角大王', 'teacher/alex_icon.png', '老男孩教育CTO & CO-FOUNDER 國內知名PYTHON語言推廣者 51CTO學院2016\2017年度最受學員喜好10大講師之一 多款開源軟件做者 曾任職公安部、飛信、中金公司、NOKIA中國研究院、華爾街英語、ADVENT、汽車之家等公司');

INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (2, 2, 1, 0, '2019-07-14 13:45:25.092902', '2019-07-14 13:45:25.092936', 'Mjj', 0, '前美團前端項目組架構師', NULL, 'teacher/mjj_icon.png', '是馬JJ老師, 一個集美貌與才華於一身的男人,搞過幾年IOS,又轉了前端開發幾年,曾就任於美團網任高級前端開發,後來由於不一樣意王興(美團老闆)的戰略佈局而出家作老師去了,有豐富的教學經驗,開起車來也絕不含糊。一直專一在前端的前沿技術領域。同時,愛好抽菸、喝酒、燙頭(錫紙燙)。 個人最愛是前端,由於前端妹子多。');

INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (3, 3, 1, 0, '2019-07-14 13:46:21.997846', '2019-07-14 13:46:21.997880', 'Lyy', 0, '老男孩Linux學科帶頭人', NULL, 'teacher/lyy_icon.png', 'Linux運維技術專家,老男孩Linux金牌講師,講課風趣幽默、深刻淺出、聲音洪亮到爆炸');

INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (1, 1, 1, 0, '2019-07-14 13:40:58.690413', '2019-07-14 13:40:58.690477', 'Python');

INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (2, 2, 1, 0, '2019-07-14 13:41:08.249735', '2019-07-14 13:41:08.249817', 'Linux');

INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (1, 1, 1, 0, '2019-07-14 13:54:33.095201', '2019-07-14 13:54:33.095238', 'Python開發21天入門', 'courses/alex_python.png', 0, 'Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土&&&Python從入門到入土', 0, '2019-07-14', 21, '', 0, 231, 120, 120, 0.00, 1, 1);

INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (2, 2, 1, 0, '2019-07-14 13:56:05.051103', '2019-07-14 13:56:05.051142', 'Python項目實戰', 'courses/mjj_python.png', 0, '', 1, '2019-07-14', 30, '', 0, 340, 120, 120, 99.00, 1, 2);

INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (3, 3, 1, 0, '2019-07-14 13:57:21.190053', '2019-07-14 13:57:21.190095', 'Linux系統基礎5周入門精講', 'courses/lyy_linux.png', 0, '', 0, '2019-07-14', 25, '', 0, 219, 100, 100, 39.00, 2, 3);

INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (1, 1, 1, 0, '2019-07-14 13:58:34.867005', '2019-07-14 14:00:58.276541', 1, '計算機原理', '', '2019-07-14', 1);

INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (2, 2, 1, 0, '2019-07-14 13:58:48.051543', '2019-07-14 14:01:22.024206', 2, '環境搭建', '', '2019-07-14', 1);

INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (3, 3, 1, 0, '2019-07-14 13:59:09.878183', '2019-07-14 14:01:40.048608', 1, '項目建立', '', '2019-07-14', 2);

INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (4, 4, 1, 0, '2019-07-14 13:59:37.448626', '2019-07-14 14:01:58.709652', 1, 'Linux環境建立', '', '2019-07-14', 3);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (1, 1, 0, '2019-07-14 14:02:33.779098', '2019-07-14 14:02:33.779135', '計算機原理上', 1, 2, NULL, NULL, '2019-07-14 14:02:33.779193', 1, 1);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (2, 1, 0, '2019-07-14 14:02:56.657134', '2019-07-14 14:02:56.657173', '計算機原理下', 2, 2, NULL, NULL, '2019-07-14 14:02:56.657227', 1, 1);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (3, 1, 0, '2019-07-14 14:03:20.493324', '2019-07-14 14:03:52.329394', '環境搭建上', 1, 2, NULL, NULL, '2019-07-14 14:03:20.493420', 0, 2);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (4, 1, 0, '2019-07-14 14:03:36.472742', '2019-07-14 14:03:36.472779', '環境搭建下', 2, 2, NULL, NULL, '2019-07-14 14:03:36.472831', 0, 2);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (5, 1, 0, '2019-07-14 14:04:19.338153', '2019-07-14 14:04:19.338192', 'web項目的建立', 1, 2, NULL, NULL, '2019-07-14 14:04:19.338252', 1, 3);

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (6, 1, 0, '2019-07-14 14:04:52.895855', '2019-07-14 14:04:52.895890', 'Linux的環境搭建', 1, 2, NULL, NULL, '2019-07-14 14:04:52.895942', 1, 4);

課堂筆記

複習

"""
redis
	基本介紹:nosql數據庫、內存數據庫
	優點:讀寫效率高、操做方便、支持五種數據類型、數據持久化、數據丟失能夠還原、高併發
	缺點:不支持事務
	操做五種數據類型:
		set key value | setex key time value
		rpush key args | lpush key args
		hset key field value
		sadd key args
		zadd key score member
	Python使用:pip install redis
	Django使用:pip install django-redis => 配置緩存
	
celery
	基本介紹:異步任務框架(獨立的socket)
	組成:Broker(任務中間件:內存數據庫、內存隊列)、Worker(任務執行者)、Backend(任務結果倉庫)
	建立celery應用:app = Celery(broker, backend, includ)
	服務命令:
		worker:celery worker -A celery文件所在包 -l info -P eventlet
		beat:celery beat -A celery文件所在包 -l info
	celery應用場景:
		耗時任務
		延遲任務
		週期任務
"""

課程內容

"""
一、免費課、實戰課、輕課業務線獨立,因此設置三個數據庫表,相同字段用BaseModel處理
二、課程分類表、老師表只須要一個,三種課程公用
三、章節表、課時表、評論表、問題表要與具體分類的課程配套(陪三套表)

四、儘可能不連表
	主頁推薦課程,能夠就訪問課程表,課程表增長 推薦字段
	主頁模塊建立 課程推薦表,點擊跳轉的連接爲 課程詳情接口
	推薦關係表 => 接口緩存
	
   一下不是實時變化的數字結果(通常都是計算而來),能夠直接用一個字段存儲
   	總課時,熱度(學習學生數)
   	
五、免費課一條業務線五張表:分類、課程、老師、章節、課時

六、序列化:表字段、插拔字段、子序列化

七、過濾組件:排序、搜索、分組篩選、區間篩選、分頁
"""

A做業(必作)

"""
一、整理今天所學知識點

二、複習並掌握過濾組件:排序、搜索、分組篩選、區間篩選、分頁

三、完成課程主頁的 免費課程接口 與 前臺數據渲染

四、完成課程詳情頁 課程詳情接口 與 前臺數據渲染
"""

B做業(選作)

"""
一、完成 全局課程搜索頁面 的前臺佈局
二、完成 先後臺搜索課程 業務
"""

7、

上線

E:\上海python脫產13期\路飛學成項目day60-90\luffy\day89\課件\上線\上線.md (本身本地文件夾)

支付

E:\上海python脫產13期\路飛學成項目day60-90\luffy\day89\課件\支付 (本身本地文件夾)

搜索

Header搜索組件

<form class="search">
  <div class="tips" v-if="is_search_tip">
    <span>Python</span>
    <span>Linux</span>
  </div>
  <input type="text" :placeholder="search_placeholder" @focus="on_search" @blur="off_search">
  <button type="button" class="el-icon-search"></button>
</form>

<script>
    export default {
        data() {
            return {
                is_search_tip: true,
                search_placeholder: '',
            }
        },
        methods: {
            on_search() {
                this.search_placeholder = '請輸入想搜索的課程';
                this.is_search_tip = false;
            },
            off_search() {
                this.search_placeholder = '';
                this.is_search_tip = true;
            },
        },
    }
</script>

<style scoped>
    .search {
        float: right;
        position: relative;
        margin-top: 22px;
    }
    .search input, .search button {
        border: none;
        outline: none;
    }
    .search input {
        border-bottom: 1px solid black;
    }
    .search input:focus {
        border-bottom-color: orange;
    }
    .search input:focus + button {
        color: orange;
    }
    .search .tips {
        position: absolute;
        bottom: 3px;
        left: 0;
    }
    .search .tips span {
        border-radius: 11px;
        background-color: #eee;
        line-height: 22px;
        display: inline-block;
        padding: 0 3px;
        margin-right: 3px;
        cursor: pointer;
    }
</style>

課堂筆記

日考

"""
1.vue指令和成員:
	v-text、html、if、for、show、model、on、bind
	data、method、computed、watch、props、鉤子、filters、components
2.vue組件:
	template(一個根標籤) + script(export default) + style(scope)
3.先後臺交互:
	同源策略(跨域)
	ajax請求
"""

複習

"""
一、drf排序過濾器
class ListView:
	filter_backends = [OrderingFilter]
	ordering_fields = ['price', 'students']
	# 接口:?ordering=-price,students

二、drf搜索:SearchFilter  search_fields  search=*

三、自定義過濾器
class MyFilter:
	def filter_queryset(self, request, queryset, view):
		# request:從前臺請求獲取過濾的條件 query_params
		# queryset:要處理的數據
		# view:從視圖中反射過濾相關的配置
		return 處理後的queryset,沒有過濾就返回原樣queryset
	
四、分頁器
PageNumberPagination:基礎分頁器 page_size    page=1
LimitOffsetPagination:偏移分頁器 default_limit    offset=0&limit=3
CursorPagination:遊標分壓器 必定是基於某種排序規則下的分頁

五、django-filter

from django_filters.rest_framework.filterset import FilterSet
from django_filters import filters
from . import models
class CourseFilterSet(FilterSet):
    max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')
    min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
    class Meta:
        model = models.Course
        fields = ['course_category', 'max_price', 'min_price']
        
DjangoFilterBackend   filter_class|filter_fields=['type']   
接口:?type=1
接口:?course_category=0&min_price=30&max_price=60
"""

課程內容

"""
一、搜索頁面的實現與課程搜索數據展現
二、支付寶流程與二次封裝支付寶框架
三、訂單模塊建立與表設計
四、支付接口的實現,訂單表與訂單詳情表入庫操做
五、前臺完成支付請求
"""

A做業(必作)

"""
一、整理今天所學知識點

二、完成搜索頁面渲染搜索接口

三、二次封裝支付寶框架,並完成支付接口的建立

四、完成前臺的支付功能
"""

B做業(選作)

"""
一、預習上線課件,完成阿里雲服務器購買
二、預習往期上線視頻
"""

8、

上線

E:\上海python脫產13期\路飛學成項目day60-90\luffy\day90\課件\上線 (本身本地文件夾)

支付

E:\上海python脫產13期\路飛學成項目day60-90\luffy\day90\課件\支付 (本身本地文件夾)

課堂筆記

複習

"""
一、搜索頁面

二、支付寶支付
	支付流程:前臺下單 => 後臺生成訂單,返回支付連接(包含了回調接口) => 前臺訪問支付連接,跳轉到支付寶平臺,與支付寶後臺交互,完成支付 => 支付寶支付成功頁面同步回調前臺接口(跳轉回咱們本身的前臺頁面)(支付成功頁面能夠將回調參數同步傳給咱們本身的後臺,能夠完成訂單的修改) + 支付寶支付成功8次異步回調後臺接口,將支付的信息傳輸給咱們的後臺,後臺能夠作訂單狀態的修改,對支付寶異步回調響應 success 7個字符

三、異步回調流程
	支付寶支付成功在25小時內,分8次異步回調後臺接口,將支付的信息傳輸給咱們的後臺,後臺能夠作訂單狀態的修改,對支付寶異步回調響應 success 7個字符

四、訂單模塊:
	訂單表:訂單號、支付用戶、訂單總額
	訂單詳情表:訂單號外鍵、商品外鍵、商品價
"""

課程總結

"""
Vue
	基礎:指令、實例成員、組件及傳參
	開發:vue-router、vuex(localStorage|sessionStorage)、axios、vue-cookies
	
drf
	基礎模塊:請求、響應、渲染、解析、異常
	核心模塊:序列化、三大認證、視圖家族
	羣查模塊:搜索、排序、分頁、分類、區間
	
luffy
	先後臺項目重構
	跨越問題:django-cors-headers
	認證六表
	git:status、add、commit、reset、pull、push、merge、branch、remote、checkout
	短信接口
	redis:字符串、列表、哈希、集合、有序集合、django緩存配置
	celery:三個存成部分broker、worker、backend | 耗時任務、延遲任務、週期任務
	Alipay
	視頻
"""

vue

<template>
	<div class="main" v-if v-show v-text v-html :class @click>
        <input v-model />
        <Header />
    </div>
</template>
<script>
    import Header from '@/components/Header'
	export default {
        data() {
            return {
                
            }
        },
        methods: {},
        watch: {
            '$route': function() {
                this.$route  // 路由數據
                this.$router  // 路由路徑
                this.$cookies.set(k, v, exp)
                this.$cookies.get(k)
                
                this.$axios({
                    url: '',
                    method: 'post',
                    params: {},
                    data: {},
                    headers: {
                        authorization: 'jwt token'
                    }
                }).then(response => {
                    response.data
                }).catch(error => {
                    error.response.data
                })
            }
        },
        computed: {},
        components: {
            Header,
        }
    }
</script>
<style scope>

</style>
// main.js

import '@/assets/css/global.css'
require('@/assets/css/global.css')

import settings from '@/assets/js/settings.js'
Vue.prototype.$settings = settings

import 'bootstrap'

import ElementUI from 'element-ui';
Vue.use(ElementUI);

// jquery環境須要在 vue.config.js 中配置

drf

"""
請求:request._request、request.query_params、request.data、request.query_params.dict()、request.data.dict()、request.META.get('HTTP_AUTHORIZATION')、request.user、request.auth

響應:data、status(http_status)、exception
	data: {
		status,
		msg,
		results,
	}
	
渲染:響應的數據支持瀏覽器和json格式數據渲染(全局或局部配置)
解析:請求的數據包數據支持解析的類型:urlencoded、form-data、json(全局或局部配置)
異常:自定義exception_handler,系統處理了客戶端異常4xx,服務器異常須要手動處理5xx,記錄異常日誌


序列化:
	class myModel(models.Model):
		name = models.CharFields(max_length=64)
		@property
		def my_name(self):
			return self.name
		
	class MyModelSerializer(ModelSerializer):
		// 序列化還有子序列化
		re_pwd = serializers.CharFields(max_length=64)
		class Meta:
			model = myModel
			fields = ('name', 'my_name', 're_pwd')
			extra_kwargs={}
			list_serializer_class = ListSerialize
		def validate_name(self, value):
			if ...:
				raise serializers.ValidationError('...')
			return value
		def validate(self, attrs):
			request = self.context.get('request')
			
			obj
			self.obj = obj
			
			if ...:
				raise serializers.ValidationError({'...': '...'})
			return attrs
		
	class myAPIView(APIView):
		def get(self, request, *args, **kwargs):
			obj
			ser = MyModelSerializer(obj)
			
			objs
			ser = MyModelSerializer(objs, many=Ture)
	
		def post(self, request, *args, **kwargs):
			ser = MyModelSerializer(data=request.data, context={'request': request})
			ser.is_valid(raise_exception=True)
			ser.save()
			ser.obj
			
		def patch(self, request, *args, **kwargs):
			obj
			ser = MyModelSerializer(instance=obj,data=request.data, partial=True)
			ser.is_valid(raise_exception=True)
			ser.save()
	
三大認證:
	認證:就採用drf-jwt的認證類,局部或全局配置 - 遊客、合法用戶、非法用戶
	權限:就採用drf提供的權限類,局部或全局配置 - 有權限、無權限
	頻率:scope與配置文件完成配置3/min、get_cache_key提供緩存key
	
視圖家族:
	APIView(禁用csrf、各類功能模塊、全局局部配置)、GenericAPIView(model相關的三個屬性三個方法)
	mixin:五個工具類,六個工具方法
	工具視圖:ListAPIView,...
	視圖集:ViewSets - as_view({'get': 'my_get'})
	
	
	
	
羣查接口:
	SearchFilter
	OrderingFilter
	paginations
	django-filter插件
"""

課程內容

"""
一、支付模塊的同步回調接口
二、支付模塊的異步回調接口
三、阿里雲服務器購買與服務器環境搭建
四、Nginx實現先後臺項目上線
"""

A做業(必作)

"""
一、整理今天所學知識點

二、完成支付模塊的回調接口

三、購買服務器,並完成先後臺項目的上線

四、總結整理luffy課程的全部知識點
"""

B做業(選作)

"""
一、認證複習路飛項目涉及的全部知識點,將luffy項目完成
二、預習微信小程序視頻
"""
相關文章
相關標籤/搜索