zoukankan      html  css  js  c++  java
  • 路飞学城之 luffy (2 )

    目录

    路飞学城之 luffy (2 )

    一、各类配置

    1.pip源

    pip安装源

    介绍

    """
    1、采用国内源,加速下载模块的速度
    2、常用pip源:
    	-- 豆瓣:https://pypi.douban.com/simple
    	-- 阿里:https://mirrors.aliyun.com/pypi/simple
    3、加速安装的命令:
    	-- >: pip install -i https://pypi.douban.com/simple 模块名
    """
    

    永久配置安装源

    Windows
    """
    1、文件管理器文件路径地址栏敲:%APPDATA% 回车,快速进入 C:Users电脑用户AppDataRoaming 文件夹中
    2、新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件
    3、新增 pip.ini 配置文件内容
    """
    
    MacOS、Linux
    """
    1、在用户根目录下 ~ 下创建 .pip 隐藏文件夹,如果已经有了可以跳过
    	-- mkdir ~/.pip
    2、进入 .pip 隐藏文件夹并创建 pip.conf 配置文件
    	-- cd ~/.pip && touch pip.conf
    3、启动 Finder(访达) 按 cmd+shift+g 来的进入,输入 ~/.pip 回车进入
    4、新增 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.虚拟环境的搭建

    虚拟环境的搭建

    优点

    1、使不同应用开发环境相互独立
    2、环境升级不影响其他应用,也不会影响全局的python环境
    3、防止出现包管理混乱及包版本冲突
    

    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
    
    

    使用

    # 在终端工作的命令
    
    # 1、创建虚拟环境到配置的WORKON_HOME路径下
    # 选取默认Python环境创建虚拟环境:
    	-- mkvirtualenv 虚拟环境名称
    # 基于某Python环境创建虚拟环境:
    	-- mkvirtualenv -p python2.7 虚拟环境名称
    	-- mkvirtualenv -p python3.6 虚拟环境名称
    
    # 2、查看已有的虚拟环境
    	-- workon
    
    # 3、使用某个虚拟环境
    	-- workon 虚拟环境名称
    	
    # 4、进入|退出 该虚拟环境的Python环境
    	-- python | exit()
    
    # 5、为虚拟环境安装模块
    	-- pip或pip3 install 模块名
    
    # 6、退出当前虚拟环境
    	-- deactivate
    
    # 7、删除虚拟环境(删除当前虚拟环境要先退出)
    	-- 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
    
    注:如果2、3步报错,清除缓存后重新走2、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 {
             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- 1200px;
             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;
             88px;
            margin-top: 30px;
            line-height: 32px;
            text-align: center;
        }
    
        .header .login-bar .shop-cart:hover {
            background: #f0f0f0;
        }
    
        .header .login-bar .shop-cart img {
             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 {
             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 {
             16px;
            height: 16px;
            border: 1px solid #9b9b9b;
            border-radius: 0;
        }
    
        .el-checkbox__inner::after {
            height: 9px;
             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 {
             100%;
            height: 128px;
            background: #25292e;
            color: #fff;
        }
    
        .footer ul {
            margin: 0 auto 16px;
            padding-top: 38px;
             810px;
        }
    
        .footer ul li {
            float: left;
             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 = '我的首页'
    
    

    笔记巩固

    知识点巩固

    """
    1、jwt认证:三段式的格式、每一段的内容、由后台签发到前台存储再到传给后台校验的认证流水线
    	头.载荷.签名:头、载荷(base64)| 签名(HS256)
    	
    				头:{基础信息:公司项目组等信息,加密方式}
    				载荷:{核心信息:用户信息,过期时间}
    				签名:{安全信息:头+载荷+秘钥的md5加密结果}
    				
    				服务器签发(login) -> web传给前端存储 -> 请求需要登录的结果再携带给后台 -> 服务器校验(认证组件) => 权限管理
    				
    				服务器压力小,集群部署更友善
    
    2、drf-jwt插件:
    	三个接口:签发token、校验token、刷新token
    	自定义jwt插件的配置:在路由配置签发token的视图类接口、在全局或局部配置认证类
    	
    3、使用jwt插件完成多方式登录
    	视图类:将请求数据交给序列化类完成校验,然后返回用户信息和token(从序列化对象中拿到)
    	序列化类:自定义反序列化字段,全局钩子校验数据得到user和token,并保存在序列化类对象中
    		token可以用jwt插件的rest_framework_jwt.serializers中
    			jwt_payload_handler,jwt_encode_handler
    		完成签发
    
    4、自定义频率类完成视图类的频率限制
    	1)定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
    	2)scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
    	3)get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key	
    """
    
    """
    请求生命周期:as_view、dispatch
    基础模块:请求、响应、解析、渲染、异常
    核心模块:序列化、视图家族、三大认证、过滤
    """
    
    """
    路飞项目
    
    前后台项目创建 => 项目目录规范(项目管理)=> 前后台跨域交互 => 
    	主页页面逻辑接口与展示 => 课程页 => 用户的登录注册 => 课程购买(支付模块) 
    => 项目上线(线上测试支付回调)
    
    开发技术点:git、redis、celery、短信接口、支付宝支付、阿里云服务器
    """
    
    

    总结

    """
    1、项目准备:pip换源、项目虚拟环境
    
    2、后台项目创建,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、前后台跨越、xadmin
    
    3、前台项目创建,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq
    	
    4、后台创建用户模块user:自定义User表、配置User表、路由分发
    
    5、前台自定义主页组件和页头、轮播图小组件
    
    6、核心:后台重构、后台配置、数据库
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、建立前后台luffy项目:luffyapi、luffycity,并按照课堂要求完成前后台配置
    
    3、打通前后台跨越交互,按照课堂内容完成主页设计
    """
    
    

    B作业(选做)

    """
    1、后台设计轮播图表,确定都要哪些字段(核心:一定要先自己设计一下,然后明天对比一下)
    2、完善主页的前台设计,在后台设计一个Banner表,轮播图数据由后台提供给前台
    """
    
    

    二、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 分支名
    """
    
    
    
    线上分支合并

    安装

    知识点复习与归纳

    知识复习

    """
    1、项目准备:pip换源、项目虚拟环境
    
    2、后台项目创建,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、前后台跨越、xadmin
    	环境变量:多app开发,将所有app放在apps中进行管理,所有apps文件夹要添加到环境变量 => app可以直接用名字注册
    	import logging   logging.getLogger('django')
    	用户权限,只能给开发者提供本项目操作权限(及以下)的账户
    
    3、前台项目创建,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq
    	
    4、后台创建用户模块user:自定义User表、配置User表、路由分发
    
    5、前台自定义主页组件和页头、轮播图小组件
    
    6、核心:后台重构、后台配置、数据库
    """
    
    

    版本控制器:SVN、GIT

    """
    版本控制器:操作开发阶段代码版本迭代的工具
    为什么要用版本控制器:
    	代码不同阶段需求完成,该代码的时间节点应该被保留
    	项目一般都是进行团队开发,版本控制器可以帮助自动整合代码
    	问题:只能整合代码,但是不能避免冲突 => 冲突解决
    	
    使用:安装 => 本地使用(基础命令) => 线上使用(团队开发) => 冲突解决
    """
    
    

    知识点总结

    """
    1、前台导航组件完成各页面跳转
    
    2、前后台主页轮播图接口实现
    
    3、版本控制器:GIT vs SVN(GIT的优势)
    	
    4、git本地基本操作:初始化仓库、状态查看、工作区 暂存区 版本库基本操作、git仓库过滤文件
    
    5、线上仓库的创建与连接:源操作
    
    6、团队开发、版本冲突解决
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、完成luffy导航栏的封装、主页轮播图表的设计与接口的设计
    
    3、安装git、属性git的基本操作、线上操作、团队开发、冲突解决
    """
    
    

    B作业(选做)

    """
    1、安装文档测试git如何操作分支,完成多分支开发
    2、根据菜鸟教育学校redis数据库
    """
    
    

    三、页面设计

    登录页面设计

    注册页面设计

    知识点回顾与归纳

    日考

    1.核心关键字:版本管理器、集群部署、多分支
    操作开发阶段代码版本迭代的工具,每个git应用都可以作为客户端或服务端,可以拥有多分支
    
    2.
    git status 查看状态
    git init 初始化仓库
    git add . 添加所有文件到暂存区
    git commit -m '' 暂存区信息完成提交到版本库
    git remote add 源名 地址 添加仓库源
    
    3.
    同时开发相同文件
    一个开发者先提交给服务器,第二个开发者拉取代码时,如果出现同一文件同一位置会出现冲突
    冲突需要开发者们线下沟通解决,保证所有开发者开发进度正常进行
    
    

    知识点总结

    """
    1、git实际开发版本冲突解决:
    	更新代码到本地版本库 => 拉远程 => 出现冲突 => 解决冲突并更新本地版本库 => 拉远程
    		如果还出现冲突,就重复上方过程
    		如果成功,就提交代码到远程仓库
    
    2、分支管理(branch):创建分支、切换分支、删除分支、合并分支(线上线下)
    	注:各个分支相互独立
    
    3、多方式登录的接口
    	
    4、手机号注册验证接口
    
    5、短信服务的开通与代码的二次封装
    
    6、发送短信接口
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、熟练掌握git的线上线下所有操作,熟知git团队开发,解决合并冲突
    
    3、开通个人短信服务账号,完成短信功能的二次封装
    
    4、完成多方式登录、手机号验证、发送验证码接口
    
    5、完成短信验证码登录、短信验证码注册接口(一定认真用序列化类完成,有对比学习才有质的改变)
    """
    
    

    B作业(选做)

    """
    1、完成前台登录页面(多方式登录与短信登录)、注册页面
    2、并方式前后台登录注册页面逻辑的交互、
    3、根据10期预习视频学习redis数据库,在django中配置redis缓存数据库,存储短信验证码
    
    4、预习接口缓存与celery异步任务框架
    """
    
    

    总结

    """
    1、git实际开发版本冲突解决:
    	更新代码到本地版本库 => 拉远程 => 出现冲突 => 解决冲突并更新本地版本库 => 拉远程
    		如果还出现冲突,就重复上方过程
    		如果成功,就提交代码到远程仓库
    
    2、分支管理(branch):创建分支、切换分支、删除分支、合并分支(线上线下)
    	注:各个分支相互独立
    
    3、多方式登录的接口:username可以携带不同信息,后台校验信息格式匹配登录方式
    	
    4、手机号注册验证接口:提供手机、手机数据库验证
    
    5、短信服务的开通与代码的二次封装:腾讯云短信服务、将配置与发送短信的函数封装成包
    
    6、发送短信接口:手机号换验证码 - 自己的后台产生验证码,交给第三方发送,后台缓存验证码,返回前台发送成功信息
    """
    
    

    四、导航模态登录注册

    前提:基于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 {
             100vw;
            height: 100vh;
            position: fixed;
            top: 0;
            left: 0;
            z-index: 10;
            background-color: rgba(0, 0, 0, 0.3);
        }
    
        .box {
             400px;
            height: 420px;
            background-color: white;
            border-radius: 10px;
            position: relative;
            top: calc(50vh - 210px);
            left: calc(50vw - 200px);
        }
    
        .el-icon-close {
            position: absolute;
            font-weight: bold;
            font-size: 20px;
            top: 10px;
            right: 10px;
            cursor: pointer;
        }
    
        .el-icon-close:hover {
            color: darkred;
        }
    
        .content {
            position: absolute;
            top: 40px;
             280px;
            left: 60px;
        }
    
        .nav {
            font-size: 20px;
            height: 38px;
            border-bottom: 2px solid darkgrey;
        }
    
        .nav > span {
            margin: 0 20px 0 35px;
            color: darkgrey;
            user-select: none;
            cursor: pointer;
            padding-bottom: 10px;
            border-bottom: 2px solid darkgrey;
        }
    
        .nav > span.active {
            color: black;
            border-bottom: 3px solid black;
            padding-bottom: 9px;
        }
    
        .el-input, .el-button {
            margin-top: 40px;
        }
    
        .el-button {
             100%;
            font-size: 18px;
        }
    
        .foot > span {
            float: right;
            margin-top: 20px;
            color: orange;
            cursor: pointer;
        }
    
        .sms {
            color: orange;
            cursor: pointer;
            display: inline-block;
             70px;
            text-align: center;
            user-select: none;
        }
    </style>
    
    

    模态注册组件

    <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 {
             100vw;
            height: 100vh;
            position: fixed;
            top: 0;
            left: 0;
            z-index: 10;
            background-color: rgba(0, 0, 0, 0.3);
        }
    
        .box {
             400px;
            height: 480px;
            background-color: white;
            border-radius: 10px;
            position: relative;
            top: calc(50vh - 240px);
            left: calc(50vw - 200px);
        }
    
        .el-icon-close {
            position: absolute;
            font-weight: bold;
            font-size: 20px;
            top: 10px;
            right: 10px;
            cursor: pointer;
        }
    
        .el-icon-close:hover {
            color: darkred;
        }
    
        .content {
            position: absolute;
            top: 40px;
             280px;
            left: 60px;
        }
    
        .nav {
            font-size: 20px;
            height: 38px;
            border-bottom: 2px solid darkgrey;
        }
    
        .nav > span {
            margin-left: 90px;
            color: darkgrey;
            user-select: none;
            cursor: pointer;
            padding-bottom: 10px;
            border-bottom: 2px solid darkgrey;
        }
    
        .nav > span.active {
            color: black;
            border-bottom: 3px solid black;
            padding-bottom: 9px;
        }
    
        .el-input, .el-button {
            margin-top: 40px;
        }
    
        .el-button {
             100%;
            font-size: 18px;
        }
    
        .foot > span {
            float: right;
            margin-top: 20px;
            color: orange;
            cursor: pointer;
        }
    
        .sms {
            color: orange;
            cursor: pointer;
            display: inline-block;
             70px;
            text-align: center;
            user-select: none;
        }
    </style>
    
    

    导航条:结合实际情况完成样式

    <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>
    
    

    知识点归纳

    知识点总结

    """
    1、手机验证码登录接口
    
    2、手机验证码注册接口
    
    3、登录注册模态页面布局
    	
    4、登录注册5个接口前后台交互
    
    5、前台登录状态cookies缓存已经用户注销
    
    6、接口缓存
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、完成后台登录请求的5个接口
    
    3、依照课件,完成前台登录注册页面的布局(Header组件可以参考项目代码)
    
    4、完成前台登录、注册、注销业务
    
    5、掌握并完成接口缓存
    
    6、安装并学习redis数据库,掌握redis数据库操作字符串的方法
    """
    
    

    B作业(选做)

    """
    1、预习redis数据库操作五大数据类型,已经django中如何使用redis数据库
    
    2、搞清楚celery框架的概念(celery是怎么工作的,解决什么问题的),建一个celery测试的demo项目,跑一下celery
    """
    
    

    五、celery和redis操作

    Celery

    官方

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

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

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

    Celery架构

    Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(task result store)组成。

    消息中间件

    Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等

    任务执行单元

    Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。

    任务结果存储

    Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等

    使用场景

    异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等

    定时任务:定时执行某件事情,比如每天数据统计

    Celery的安装配置

    pip install celery

    消息中间件:RabbitMQ/Redis

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

    Celery执行异步任务

    包架构封装

    project
        ├── celery_task  	# celery包
        │   ├── __init__.py # 包文件
        │   ├── celery.py   # celery连接和配置相关文件,且名字必须交celery.py
        │   └── tasks.py    # 所有任务函数
        ├── add_task.py  	# 添加任务
        └── get_result.py   # 获取结果
    
    

    基本使用

    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|三个属性三个方法
    """
    
    

    知识点总结

    """
    1、redis数据库:优势、基础使用、五种数据类型的操作
    
    2、redis数据库在Python中的使用、Django中的使用
    
    3、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作业(必做)

    """
    1、整理今天所学知识点
    
    2、将项目的缓存配置成redis数据库,来关联缓存
    
    3、利用celery框架完成异步定时更新轮播图接口缓存
    """
    
    

    B作业(选做)

    """
    1、将发生短信接口,改写为让celery来管理
    
    2、工具路飞官网,设计课程业务相关表
    """
    
    

    六、

    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 {
             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;
             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 {
             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;
             423px;
            height: 210px;
            margin-right: 30px;
        }
    
        .course .course-item .course-image img {
             100%;
        }
    
        .course .course-item .course-info {
            float: left;
             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 {
             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;
             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- 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 {
             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 {
             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 {
             1200px;
            margin: 0 auto;
            overflow: hidden;
        }
    
        .wrap-left {
            float: left;
             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 {
             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 {
             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 {
             464px;
            padding: 0px 23px;
            position: absolute;
            left: 0;
            bottom: 20px;
            overflow: hidden;
        }
    
        .buy .buy-btn {
            float: left;
        }
    
        .buy .buy-now {
             125px;
            height: 40px;
            border: 0;
            background: #ffc210;
            border-radius: 4px;
            color: #fff;
            cursor: pointer;
            margin-right: 15px;
            outline: none;
        }
    
        .buy .free {
             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 {
             20px;
            height: 18px;
            margin-right: 7px;
            vertical-align: middle;
        }
    
        .course-tab {
             100%;
            background: #fff;
            margin-bottom: 30px;
            box-shadow: 0 2px 4px 0 #f0f0f0;
    
        }
    
        .course-tab .tab-list {
             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 {
             1200px;
            margin: 0 auto;
            background: #FAFAFA;
            overflow: hidden;
            padding-bottom: 40px;
        }
    
        .course-tab-list {
             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 {
             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 {
             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 {
             18px;
            height: 18px;
            margin-left: 15px;
            vertical-align: text-bottom;
        }
    
        .section-item .try {
             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 {
             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 {
             54px;
            height: 54px;
            margin-right: 12px;
            float: left;
        }
    
        .teacher-content .cont1 .name {
            float: right;
        }
    
        .teacher-content .cont1 .teacher-name {
             188px;
            font-size: 16px;
            color: #4a4a4a;
            padding-bottom: 4px;
        }
    
        .teacher-content .cont1 .teacher-title {
             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]
    
    

    运用图片



    支付

    支付宝支付

    # 1、在沙箱环境下实名认证:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    
    # 2、电脑网站支付API:https://docs.open.alipay.com/270/105898/
    
    # 3、完成RSA密钥生成:https://docs.open.alipay.com/291/105971
    
    # 4、在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容
    
    # 5、Python支付宝开源框架:https://github.com/fzlee/alipay
    # >: pip install python-alipay-sdk --upgrade
    
    # 7、公钥私钥设置
    """
    # alipay_public_key.pem
    -----BEGIN PUBLIC KEY-----
    支付宝公钥
    -----END PUBLIC KEY-----
    
    # app_private_key.pem
    -----BEGIN RSA PRIVATE KEY-----
    用户私钥
    -----END RSA PRIVATE KEY-----
    """
    
    # 8、支付宝链接
    """
    开发: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;
             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;
             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;
             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
    >:'
    
    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:UsersdellDesktoppkgmysql57-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:UsersdellDesktoppkg
    edis-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:UsersdellDesktoppkgPython-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 Desktopenv
    >: 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
    
    

    重点 重点 重点

    # 1、真实环境和虚拟环境都要安装uwsgi,将真实环境下的uwsgi建立软连接
    
    # 2、redis服务一定要后台启动:redis-server &
    
    # 3、uwsgi启动django项目一定要进入虚拟环境下,因为环境都是安装在虚拟环境中
    
    # 4、服务器的日志都会被记录在于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学院20162017年度最受学员喜爱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应用场景:
    		耗时任务
    		延迟任务
    		周期任务
    """
    
    

    课程内容

    """
    1、免费课、实战课、轻课业务线独立,所以设置三个数据库表,相同字段用BaseModel处理
    2、课程分类表、老师表只需要一个,三种课程公用
    3、章节表、课时表、评论表、问题表要与具体分类的课程配套(陪三套表)
    
    4、尽量不连表
    	主页推荐课程,可以就访问课程表,课程表增加 推荐字段
    	主页模块创建 课程推荐表,点击跳转的链接为 课程详情接口
    	推荐关系表 => 接口缓存
    	
       一下不是实时变化的数字结果(一般都是计算而来),可以直接用一个字段存储
       	总课时,热度(学习学生数)
       	
    5、免费课一条业务线五张表:分类、课程、老师、章节、课时
    
    6、序列化:表字段、插拔字段、子序列化
    
    7、过滤组件:排序、搜索、分组筛选、区间筛选、分页
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、复习并掌握过滤组件:排序、搜索、分组筛选、区间筛选、分页
    
    3、完成课程主页的 免费课程接口 与 前台数据渲染
    
    4、完成课程详情页 课程详情接口 与 前台数据渲染
    """
    
    

    B作业(选做)

    """
    1、完成 全局课程搜索页面 的前台布局
    2、完成 前后台搜索课程 业务
    """
    
    

    七、

    上线

    E:上海python脱产13期路飞学成项目day60-90luffyday89课件上线上线.md (自己本地文件夹)

    支付

    E:上海python脱产13期路飞学成项目day60-90luffyday89课件支付 (自己本地文件夹)

    搜索

    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请求
    """
    
    

    复习

    """
    1、drf排序过滤器
    class ListView:
    	filter_backends = [OrderingFilter]
    	ordering_fields = ['price', 'students']
    	# 接口:?ordering=-price,students
    
    2、drf搜索:SearchFilter  search_fields  search=*
    
    3、自定义过滤器
    class MyFilter:
    	def filter_queryset(self, request, queryset, view):
    		# request:从前台请求获取过滤的条件 query_params
    		# queryset:要处理的数据
    		# view:从视图中反射过滤相关的配置
    		return 处理后的queryset,没有过滤就返回原样queryset
    	
    4、分页器
    PageNumberPagination:基础分页器 page_size    page=1
    LimitOffsetPagination:偏移分页器 default_limit    offset=0&limit=3
    CursorPagination:游标分压器 一定是基于某种排序规则下的分页
    
    5、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
    """
    
    

    课程内容

    """
    1、搜索页面的实现与课程搜索数据展示
    2、支付宝流程与二次封装支付宝框架
    3、订单模块创建与表设计
    4、支付接口的实现,订单表与订单详情表入库操作
    5、前台完成支付请求
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、完成搜索页面渲染搜索接口
    
    3、二次封装支付宝框架,并完成支付接口的创建
    
    4、完成前台的支付功能
    """
    
    

    B作业(选做)

    """
    1、预习上线课件,完成阿里云服务器购买
    2、预习往期上线视频
    """
    
    

    八、

    上线

    E:上海python脱产13期路飞学成项目day60-90luffyday90课件上线 (自己本地文件夹)

    支付

    E:上海python脱产13期路飞学成项目day60-90luffyday90课件支付 (自己本地文件夹)

    课堂笔记

    复习

    """
    1、搜索页面
    
    2、支付宝支付
    	支付流程:前台下单 => 后台生成订单,返回支付链接(包含了回调接口) => 前台访问支付链接,跳转到支付宝平台,与支付宝后台交互,完成支付 => 支付宝支付成功页面同步回调前台接口(跳转回我们自己的前台页面)(支付成功页面可以将回调参数同步传给我们自己的后台,可以完成订单的修改) + 支付宝支付成功8次异步回调后台接口,将支付的信息传输给我们的后台,后台可以做订单状态的修改,对支付宝异步回调响应 success 7个字符
    
    3、异步回调流程
    	支付宝支付成功在25小时内,分8次异步回调后台接口,将支付的信息传输给我们的后台,后台可以做订单状态的修改,对支付宝异步回调响应 success 7个字符
    
    4、订单模块:
    	订单表:订单号、支付用户、订单总额
    	订单详情表:订单号外键、商品外键、商品价
    """
    
    

    课程总结

    """
    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插件
    """
    
    

    课程内容

    """
    1、支付模块的同步回调接口
    2、支付模块的异步回调接口
    3、阿里云服务器购买与服务器环境搭建
    4、Nginx实现前后台项目上线
    """
    
    

    A作业(必做)

    """
    1、整理今天所学知识点
    
    2、完成支付模块的回调接口
    
    3、购买服务器,并完成前后台项目的上线
    
    4、总结整理luffy课程的所有知识点
    """
    
    

    B作业(选做)

    """
    1、认证复习路飞项目涉及的所有知识点,将luffy项目完成
    2、预习微信小程序视频
    """
    
    
  • 相关阅读:
    git处理冲突提交,撤销提交命令
    RocketMQ(4.7.0)单机与集群搭建,控制台搭建,并与springboot整合
    JVM进程的优雅关闭
    MySQL字段等值查询时,尾部有空格也能匹配上的坑
    带有连接池的Http客户端工具类HttpClientUtil
    比较两个jar包的版本号
    源码解读SLF4J绑定日志实现的原理
    log4j输出到控制台的性能问题
    多表查询
    分页优化
  • 原文地址:https://www.cnblogs.com/WQ577098649/p/12633746.html
Copyright © 2011-2022 走看看