目录
  • 路飞学城之 luffy (2 )
  • 一、各类配置
    • 1.pip源
    • pip安装源
      • 介绍
      • 永久配置安装源
        • Windows
        • MacOS、Linux
        • 配置文件内容
    • 2.虚拟环境的搭建
      • 虚拟环境的搭建
      • 优点
      • windows
        • 安装
        • 配置虚拟环境管理器工作目录
      • MacOS、Linux
        • 安装
        • 工作文件
        • 配置
      • 使用
      • pycharm使用
        • 新建项目
        • 添加环境
        • 使用环境
    • 3.luffy后台
      • 后台:Django项目创建
      • 环境
      • 创建项目
      • 重构项目目录
      • 配置开发环境
      • 配置日志
    • 4.luffy后台配置
      • 环境变量

        • dev.py
        • 在写项目直接导入utils文件夹也不''错误提示''
      • 封装logger
        • dev.py
        • utils/logging.py
      • 封装项目异常处理
        • utils/exception.py
        • settings.py
      • 二次封装Response模块
        • utils/response.py
    • 5.luffy数据库
      • 数据库配置
      • 创建数据库
      • 为指定数据库配置指定账户
      • Django 2.x 一些版本pymysql兼容问题
        • Django不采用2.0.7版本很可能出现以下问题,需要修改源代码
    • 6.user模块User表
      • user模块User表
      • 创建user模块
      • 创建User表对应的model:user/models.py
      • 注册user模块,配置User表:dev.py
      • 配置media
        • media配置:dev.py
        • media目录配置
        • 主路由:luffyapi/urls.py
        • 子路由:user/urls.py
    • 7.luffy前台
    • 前台
      • vue环境
      • 创建项目
      • 重构项目目录
      • 文件修订:目录中非配置文件的多余文件可以移除
        • App.vue
        • router/index.js
        • Home.vue
      • 全局配置:全局样式、配置文件
        • global.css
        • settings.js
        • main.js
    • 8.luffy前台配置
    • luffy前台配置
      • axios前后台交互

        • 安装:前端项目目录下的终端
        • 配置:main.js
      • cookies操作
        • 安装:前端项目目录下的终端
        • 配置:main.js
      • element-ui页面组件框架
        • 安装:前端项目目录下的终端
        • 配置:main.js
      • bootstrap页面组件框架
        • 安装:前端项目目录下的终端
        • 配置jquery:vue.config.js
        • 配置bootstrap:main.js
    • 9.luffy前台主页
    • 前端主页
      • 图片准备
      • 页头组件:components/Header.vue
      • 轮播图组件:components/Banner.vue
      • 页脚组件:components/Footer.vue
      • 主页组件:views/Home.vue
    • 10.后台主页模块设计
    • home模块
      • 创建home模块
      • 路由分发
        • 主路由:luffyapi/urls.py
        • 子路由:home/urls.py
      • Banner数据表model设计
        • utils/model.py
        • home/models.py
        • 数据迁移:在大luffyapi路径下的终端
      • 注册home模块:dev.py
      • 设计Banner数据接口
        • home/serializers.py
        • home/views.py
        • home/urls.py
        • 接口
    • 11.前后台分离跨域交互
    • 分离的前后台交互
      • 后台处理跨域

        • 安装插件
        • 项目配置:dev.py
      • 前台请求Banner数据
        • 修订Banner.vue
    • 12.xadmin后台管理
    • xadmin后台管理
      • 安装:luffy虚拟环境下
      • 注册app:dev.py
      • xadmin:需要自己的数据库模型类,完成数据库迁移
      • 设置主路由替换掉admin:主urls.py
      • 创建超级用户:大luffyapi路径终端
      • 完成xadmin全局配置:新建home/adminx.py
      • 在adminx.py中注册model:home/adminx.px
      • 修改app:home的名字:xadmin页面上的显示效果
    • 笔记巩固
      • 知识点巩固
      • 总结
      • A作业(必做)
      • B作业(选做)
  • 二、git
    • 版本控制器
    • git
      • 简介
      • git与svn比较
      • git的工作流程
      • git分支管理
    • git使用
      • 安装
      • 基础命令
        • 将已有的文件夹 - 初始化为git仓库
        • 在指定目录下 - 初始化git仓库
        • 在仓库目录终端下 - 设置全局用户
        • 在仓库目录终端下 - 设置局部用户
        • 查看仓库状态
        • 工作区操作
        • 撤销工作区操作:改、删
        • 工作区内容提交到暂存区
        • 撤销暂存区提交:add的逆运算
        • 提交暂存区内容到版本库
        • 撤销版本库提交:commit的逆运算
      • 过滤文件
    • 创建远程gitee仓库
      • 选择线上仓库
    • 用本地仓库首次初始化远程仓库
      • 本地仓库与远程仓库建立源连接
      • 创建电脑的公钥私钥
      • 提交本地代码到远程仓库
    • remote源操作
      • 多分支开发

        • 分支操作
        • 线上分支合并
    • 安装
    • 知识点复习与归纳
      • 知识复习
      • 版本控制器:SVN、GIT
      • 知识点总结
      • A作业(必做)
      • B作业(选做)
  • 三、页面设计
    • 登录页面设计
    • 注册页面设计
    • 知识点回顾与归纳
      • 日考
      • 知识点总结
      • A作业(必做)
      • B作业(选做)
      • 总结
  • 四、导航模态登录注册
    • 前提:基于element-ui环境
    • 模态登录组件
    • 模态注册组件
    • 导航条:结合实际情况完成样式
    • 知识点归纳
      • 知识点总结
      • A作业(必做)
      • B作业(选做)
  • 五、celery和redis操作
  • Celery
    • 官方
    • Celery架构

      • 消息中间件
      • 任务执行单元
      • 任务结果存储
    • 使用场景
    • Celery的安装配置
    • Celery执行异步任务
      • 包架构封装
      • 基本使用

        • celery.py
        • tasks.py
        • add_task.py
        • get_result.py
      • 高级使用
        • celery.py
        • tasks.py
        • get_result.py
      • django中使用
        • celery.py
        • tasks.py
  • redis操作
    • redis VS mysql
    • redis VS memcache
    • Redis操作
    • redis数据库
    • python使用redis
      • 依赖
      • 直接使用
      • 连接池使用
      • 缓存使用:要额外安装 django-redis
    • 知识点归纳与练习
      • 日考
      • 知识点总结
      • A作业(必做)
      • B作业(选做)
  • 六、
    • 1.课程页页面

      • 课程组件
    • 2.修订课程主页
    • 3.课程详情页
      • 1.详情页前台
      • 详情页组件
        • 依赖:在luffycity目录下的命令
        • 配置:main.js
        • 资源:图片放置assrts/img文件夹
        • 路由:router.js
        • 组件
      • 2.详情页后台
      • 详情页后台
        • 路由:source/urls.py
        • 视图:source/views.py
        • 序列化:source/serializers.py
        • 视图字段:source/model.py
      • 运用图片
    • 支付
      • 支付宝支付
      • 支付流程
      • aliapy二次封装包
        • 依赖
        • 结构
        • setting.py
        • _init_.py
        • alipay_public_key.pem
        • app_private_key.pem
        • 补充:dev.py
      • 支付模块
        • order/models.py
      • 后台接口
      • 订单序列化模块
      • 支付接口生成支付链接
      • 前台回调接口的页面
      • 支付完成订单校验的接口
    • 上线
      • 购买服务器
      • 连接服务器
      • 服务器命令
        • 管理员权限
        • 配置终端
    • 重要
      • 更新系统软件包
      • 安装软件管理包和可能使用的依赖
      • 安装Mysql
      • 安装Redis
      • 安装Python3.6
      • 配置pip源:阿里云不用配置,默认配置阿里源
      • 安装uwsgi
      • 安装虚拟环境
      • 服务器运行测试Django项目
      • 安装Nginx
      • Nginx命令
      • Nginx & uwsgi 运行Django
      • 路飞项目部署:Nginx + uwsgi + django + vue
      • 配置前台项目
        • 上线前配置

          • settings.js
        • 上线
      • 路飞后台部署
        • 上线前配置
        • 上线
      • pip导入导出依赖
      • 数据库设置 + django2.0源码(2.0.7不用修改源码)
      • 后台样式问题
        • 设置文件中配置STATIC_ROOT
        • 迁移静态样式:项目目录下
        • Nginx配置静态路径
        • 重启服务
      • 重点 重点 重点
      • 添加测试数据
    • 课堂笔记
      • 复习
      • 课程内容
      • A作业(必做)
      • B作业(选做)
  • 七、
    • 上线
    • 支付
    • 搜索
      • Header搜索组件
    • 课堂笔记
      • 日考
      • 复习
      • 课程内容
      • A作业(必做)
      • B作业(选做)
  • 八、
    • 上线
    • 支付
    • 课堂笔记
      • 复习
      • 课程总结
      • vue
      • drf
      • 课程内容
      • A作业(必做)
      • B作业(选做)

路飞学城之 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\电脑用户\AppData\Roaming 文件夹中
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 = False4.修改启动配置:见插图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 Responseclass 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'] = resultsdata.update(kwargs)super().__init__(data=data, status=http_status, headers=headers, exception=exception)

5.luffy数据库

数据库配置

创建数据库

"""
1.管理员连接数据库
>: mysql -uroot -proot2.创建数据库
>: 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 apps2.创建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_namedef __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.org3.安装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 {width: 100%;height: 80px;box-shadow: 0 0.5px 0.5px 0 #c9c9c9;position: fixed;top: 0;left: 0;right: 0;margin: auto;z-index: 99;background: #fff;}.header .content {max-width: 1200px;width: 100%;margin: 0 auto;}.header .content .logo {height: 80px;line-height: 80px;margin-right: 50px;cursor: pointer;}.header .content .logo img {vertical-align: middle;}.header .nav li {float: left;height: 80px;line-height: 80px;margin-right: 30px;font-size: 16px;color: #4a4a4a;cursor: pointer;}.header .nav li span {padding-bottom: 16px;padding-left: 5px;padding-right: 5px;}.header .nav li span a {display: inline-block;}.header .nav li .this {color: #4a4a4a;border-bottom: 4px solid #ffc210;}.header .nav li:hover span {color: #000;}.header .login-bar {height: 80px;}.header .login-bar .shop-cart {margin-right: 20px;border-radius: 17px;background: #f7f7f7;cursor: pointer;font-size: 14px;height: 28px;width: 88px;margin-top: 30px;line-height: 32px;text-align: center;}.header .login-bar .shop-cart:hover {background: #f0f0f0;}.header .login-bar .shop-cart img {width: 15px;margin-right: 4px;margin-left: 6px;}.header .login-bar .shop-cart span {margin-right: 6px;}.header .login-bar .login-box {margin-top: 33px;}.header .login-bar .login-box span {color: #4a4a4a;cursor: pointer;}.header .login-bar .login-box span:hover {color: #000000;}.full-left {float: left !important;}.full-right {float: right !important;}.el-carousel__arrow {width: 120px;height: 120px;}.el-checkbox__input.is-checked .el-checkbox__inner,.el-checkbox__input.is-indeterminate .el-checkbox__inner {background: #ffc210;border-color: #ffc210;border: none;}.el-checkbox__inner:hover {border-color: #9b9b9b;}.el-checkbox__inner {width: 16px;height: 16px;border: 1px solid #9b9b9b;border-radius: 0;}.el-checkbox__inner::after {height: 9px;width: 5px;}
</style>

轮播图组件:components/Banner.vue

<template><el-carousel height="520px" :interval="3000" arrow="always"><el-carousel-item><img src="@/assets/img/banner1.png" alt=""></el-carousel-item><el-carousel-item><img src="@/assets/img/banner2.png" alt=""></el-carousel-item><el-carousel-item><img src="@/assets/img/banner3.png" alt=""></el-carousel-item></el-carousel>
</template>
<script>export default {name: "Banner",}
</script><style scoped>.el-carousel__item h3 {color: #475669;font-size: 18px;opacity: 0.75;line-height: 300px;margin: 0;}.el-carousel__item:nth-child(2n) {background-color: #99a9bf;}.el-carousel__item:nth-child(2n+1) {background-color: #d3dce6;}.el-carousel__item img {text-align: center;height: 520px;margin: 0 auto;display: block;}
</style>

页脚组件:components/Footer.vue

<template><div class="footer"><ul><li>关于我们</li><li>联系我们</li><li>商务合作</li><li>帮助中心</li><li>意见反馈</li><li>新手指南</li></ul><p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p></div>
</template><script>export default {name: "Footer"}
</script><style scoped>.footer {width: 100%;height: 128px;background: #25292e;color: #fff;}.footer ul {margin: 0 auto 16px;padding-top: 38px;width: 810px;}.footer ul li {float: left;width: 112px;margin: 0 10px;text-align: center;font-size: 14px;}.footer ul::after {content: "";display: block;clear: both;}.footer p {text-align: center;font-size: 12px;}
</style>

主页组件:views/Home.vue

<template><div class="home"><Header /><Banner /><Footer /></div>
</template><script>import Header from '@/components/Header'import Banner from '@/components/Banner'import Footer from '@/components/Footer'export default {name: 'home',components: {Header,Banner,Footer},}
</script>

10.后台主页模块设计

home模块

创建home模块

前提:在 luffy 虚拟环境下1.终端从项目根目录进入apps目录
>: cd luffyapi & cd apps2.创建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 modelsclass 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 BaseModelclass 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_namedef __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.Bannerfields = ('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 viewsclass 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、后台项目创建,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、前后台跨越、xadmin3、前台项目创建,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq4、后台创建用户模块user:自定义User表、配置User表、路由分发5、前台自定义主页组件和页头、轮播图小组件6、核心:后台重构、后台配置、数据库
"""

A作业(必做)

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

B作业(选做)

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

二、git

版本控制器

"""
完成 协同开发 项目,帮助程序员整合代码软件:SVN 、 GITgit:集群化、多分支
"""

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 -viv)删除源链接>: 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 -v2)查看remote命令帮助文档
>: git remote -h3)删除远程源
>: git remote remove 源名
eg: git remote remove origin4)添加远程源
>: git remote add 源名 源地址
>: git remote add orgin git@*.git
"""

多分支开发

分支操作
"""
1.创建分支
>: git branch 分支名2.查看分支
>: git branch3.切换分支
>: git checkout 分支名4.创建并切换到分支
>: git checkout -b 分支名5.删除分支
>: git branch -d 分支名6.查看远程分支
>: git branch -a7.合并分支
>: git merge 分支名
"""
线上分支合并

安装

知识点复习与归纳

知识复习

"""
1、项目准备:pip换源、项目虚拟环境2、后台项目创建,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、前后台跨越、xadmin环境变量:多app开发,将所有app放在apps中进行管理,所有apps文件夹要添加到环境变量 => app可以直接用名字注册import logging   logging.getLogger('django')用户权限,只能给开发者提供本项目操作权限(及以下)的账户3、前台项目创建,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq4、后台创建用户模块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-inputplaceholder="用户名/手机号/邮箱"prefix-icon="el-icon-user"v-model="username"clearable></el-input><el-inputplaceholder="密码"prefix-icon="el-icon-key"v-model="password"clearableshow-password></el-input><el-button type="primary">登录</el-button></el-form><el-form v-if="login_method === 'is_sms'"><el-inputplaceholder="手机号"prefix-icon="el-icon-phone-outline"v-model="mobile"clearable@blur="check_mobile"></el-input><el-inputplaceholder="验证码"prefix-icon="el-icon-chat-line-round"v-model="sms"clearable><template slot="append"><span class="sms" @click="send_sms">{{ sms_interval }}</span></template></el-input><el-button type="primary">登录</el-button></el-form><div class="foot"><span @click="go_register">立即注册</span></div></div></div></div>
</template><script>export default {name: "Login",data() {return {username: '',password: '',mobile: '',sms: '',login_method: 'is_pwd',sms_interval: '获取验证码',is_send: false,}},methods: {close_login() {this.$emit('close')},go_register() {this.$emit('go')},change_login_method(method) {this.login_method = method;},check_mobile() {if (!this.mobile) return;if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {this.$message({message: '手机号有误',type: 'warning',duration: 1000,onClose: () => {this.mobile = '';}});return false;}this.is_send = true;},send_sms() {if (!this.is_send) return;this.is_send = false;let sms_interval_time = 60;this.sms_interval = "发送中...";let timer = setInterval(() => {if (sms_interval_time <= 1) {clearInterval(timer);this.sms_interval = "获取验证码";this.is_send = true; // 重新回复点击发送功能的条件} else {sms_interval_time -= 1;this.sms_interval = `${sms_interval_time}秒后再发`;}}, 1000);}}}
</script><style scoped>.login {width: 100vw;height: 100vh;position: fixed;top: 0;left: 0;z-index: 10;background-color: rgba(0, 0, 0, 0.3);}.box {width: 400px;height: 420px;background-color: white;border-radius: 10px;position: relative;top: calc(50vh - 210px);left: calc(50vw - 200px);}.el-icon-close {position: absolute;font-weight: bold;font-size: 20px;top: 10px;right: 10px;cursor: pointer;}.el-icon-close:hover {color: darkred;}.content {position: absolute;top: 40px;width: 280px;left: 60px;}.nav {font-size: 20px;height: 38px;border-bottom: 2px solid darkgrey;}.nav > span {margin: 0 20px 0 35px;color: darkgrey;user-select: none;cursor: pointer;padding-bottom: 10px;border-bottom: 2px solid darkgrey;}.nav > span.active {color: black;border-bottom: 3px solid black;padding-bottom: 9px;}.el-input, .el-button {margin-top: 40px;}.el-button {width: 100%;font-size: 18px;}.foot > span {float: right;margin-top: 20px;color: orange;cursor: pointer;}.sms {color: orange;cursor: pointer;display: inline-block;width: 70px;text-align: center;user-select: none;}
</style>

模态注册组件

<template><div class="register"><div class="box"><i class="el-icon-close" @click="close_register"></i><div class="content"><div class="nav"><span class="active">新用户注册</span></div><el-form><el-inputplaceholder="手机号"prefix-icon="el-icon-phone-outline"v-model="mobile"clearable@blur="check_mobile"></el-input><el-inputplaceholder="密码"prefix-icon="el-icon-key"v-model="password"clearableshow-password></el-input><el-inputplaceholder="验证码"prefix-icon="el-icon-chat-line-round"v-model="sms"clearable><template slot="append"><span class="sms" @click="send_sms">{{ sms_interval }}</span></template></el-input><el-button type="primary">注册</el-button></el-form><div class="foot"><span @click="go_login">立即登录</span></div></div></div></div>
</template><script>export default {name: "Register",data() {return {mobile: '',password: '',sms: '',sms_interval: '获取验证码',is_send: false,}},methods: {close_register() {this.$emit('close', false)},go_login() {this.$emit('go')},check_mobile() {if (!this.mobile) return;if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {this.$message({message: '手机号有误',type: 'warning',duration: 1000,onClose: () => {this.mobile = '';}});return false;}this.is_send = true;},send_sms() {if (!this.is_send) return;this.is_send = false;let sms_interval_time = 60;this.sms_interval = "发送中...";let timer = setInterval(() => {if (sms_interval_time <= 1) {clearInterval(timer);this.sms_interval = "获取验证码";this.is_send = true; // 重新回复点击发送功能的条件} else {sms_interval_time -= 1;this.sms_interval = `${sms_interval_time}秒后再发`;}}, 1000);}}}
</script><style scoped>.register {width: 100vw;height: 100vh;position: fixed;top: 0;left: 0;z-index: 10;background-color: rgba(0, 0, 0, 0.3);}.box {width: 400px;height: 480px;background-color: white;border-radius: 10px;position: relative;top: calc(50vh - 240px);left: calc(50vw - 200px);}.el-icon-close {position: absolute;font-weight: bold;font-size: 20px;top: 10px;right: 10px;cursor: pointer;}.el-icon-close:hover {color: darkred;}.content {position: absolute;top: 40px;width: 280px;left: 60px;}.nav {font-size: 20px;height: 38px;border-bottom: 2px solid darkgrey;}.nav > span {margin-left: 90px;color: darkgrey;user-select: none;cursor: pointer;padding-bottom: 10px;border-bottom: 2px solid darkgrey;}.nav > span.active {color: black;border-bottom: 3px solid black;padding-bottom: 9px;}.el-input, .el-button {margin-top: 40px;}.el-button {width: 100%;font-size: 18px;}.foot > span {float: right;margin-top: 20px;color: orange;cursor: pointer;}.sms {color: orange;cursor: pointer;display: inline-block;width: 70px;text-align: center;user-select: none;}
</style>

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

<template><div class="nav"><span @click="put_login">登录</span><span @click="put_register">注册</span><Login v-if="is_login" @close="close_login" @go="put_register" /><Register v-if="is_register" @close="close_register" @go="put_login" /></div>
</template><script>import Login from "./Login";import Register from "./Register";export default {name: "Nav",data() {return {is_login: false,is_register: false,}},methods: {put_login() {this.is_login = true;this.is_register = false;},put_register() {this.is_login = false;this.is_register = true;},close_login() {this.is_login = false;},close_register() {this.is_register = false;}},components: {Login,Register}}
</script><style scoped></style>

知识点归纳

知识点总结

"""
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 Celerybroker = '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_delaytasks.low.apply_async(args=(200, 50), eta=eta_second(10))
get_result.py
from celery_task.celery import appfrom celery.result import AsyncResultid = '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 Celerybroker = '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 appimport 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 appfrom celery.result import AsyncResultid = '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 Celerybroker = '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、updateListSerializer:提供群增群改(必须重写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 eventletworker服务是用来执行任务的服务器beat服务的启动命令:celery beat -A celery_task -l infobeat服务是用来自动添加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 {width: 1100px;margin: 35px auto 0;}.course .condition {margin-bottom: 35px;padding: 25px 30px 25px 20px;background: #fff;border-radius: 4px;box-shadow: 0 2px 4px 0 #f0f0f0;}.course .cate-list {border-bottom: 1px solid #333;border-bottom-color: rgba(51, 51, 51, .05);padding-bottom: 18px;margin-bottom: 17px;}.course .cate-list::after {content: "";display: block;clear: both;}.course .cate-list li {float: left;font-size: 16px;padding: 6px 15px;line-height: 16px;margin-left: 14px;position: relative;transition: all .3s ease;cursor: pointer;color: #4a4a4a;border: 1px solid transparent; /* transparent 透明 */}.course .cate-list .title {color: #888;margin-left: 0;letter-spacing: .36px;padding: 0;line-height: 28px;}.course .cate-list .this {color: #ffc210;border: 1px solid #ffc210 !important;border-radius: 30px;}.course .ordering::after {content: "";display: block;clear: both;}.course .ordering ul {float: left;}.course .ordering ul::after {content: "";display: block;clear: both;}.course .ordering .condition-result {float: right;font-size: 14px;color: #9b9b9b;line-height: 28px;}.course .ordering ul li {float: left;padding: 6px 15px;line-height: 16px;margin-left: 14px;position: relative;transition: all .3s ease;cursor: pointer;color: #4a4a4a;}.course .ordering .title {font-size: 16px;color: #888;letter-spacing: .36px;margin-left: 0;padding: 0;line-height: 28px;}.course .ordering .this {color: #ffc210;}.course .ordering .price {position: relative;}.course .ordering .price::before,.course .ordering .price::after {cursor: pointer;content: "";display: block;width: 0px;height: 0px;border: 5px solid transparent;position: absolute;right: 0;}.course .ordering .price::before {border-bottom: 5px solid #aaa;margin-bottom: 2px;top: 2px;}.course .ordering .price::after {border-top: 5px solid #aaa;bottom: 2px;}.course .course-item:hover {box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);}.course .course-item {width: 1050px;background: #fff;padding: 20px 30px 20px 20px;margin-bottom: 35px;border-radius: 2px;cursor: pointer;box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);/* css3.0 过渡动画 hover 事件操作 */transition: all .2s ease;}.course .course-item::after {content: "";display: block;clear: both;}/* 顶级元素 父级元素  当前元素{} */.course .course-item .course-image {float: left;width: 423px;height: 210px;margin-right: 30px;}.course .course-item .course-image img {width: 100%;}.course .course-item .course-info {float: left;width: 596px;}.course-item .course-info h3 {font-size: 26px;color: #333;font-weight: normal;margin-bottom: 8px;}.course-item .course-info h3 span {font-size: 14px;color: #9b9b9b;float: right;margin-top: 14px;}.course-item .course-info h3 span img {width: 11px;height: auto;margin-right: 7px;}.course-item .course-info .teather-info {font-size: 14px;color: #9b9b9b;margin-bottom: 14px;padding-bottom: 14px;border-bottom: 1px solid #333;border-bottom-color: rgba(51, 51, 51, .05);}.course-item .course-info .teather-info span {float: right;}.course-item .lesson-list::after {content: "";display: block;clear: both;}.course-item .lesson-list li {float: left;width: 44%;font-size: 14px;color: #666;padding-left: 22px;/* background: url("路径") 是否平铺 x轴位置 y轴位置 */background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px;margin-bottom: 15px;}.course-item .lesson-list li .lesson-title {/* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */text-overflow: ellipsis;overflow: hidden;white-space: nowrap;display: inline-block;max-width: 200px;}.course-item .lesson-list li:hover {background-image: url("/src/assets/img/play-icon-yellow.svg");color: #ffc210;}.course-item .lesson-list li .free {width: 34px;height: 20px;color: #fd7b4d;vertical-align: super;margin-left: 10px;border: 1px solid #fd7b4d;border-radius: 2px;text-align: center;font-size: 13px;white-space: nowrap;}.course-item .lesson-list li:hover .free {color: #ffc210;border-color: #ffc210;}.course-item .pay-box::after {content: "";display: block;clear: both;}.course-item .pay-box .discount-type {padding: 6px 10px;font-size: 16px;color: #fff;text-align: center;margin-right: 8px;background: #fa6240;border: 1px solid #fa6240;border-radius: 10px 0 10px 0;float: left;}.course-item .pay-box .discount-price {font-size: 24px;color: #fa6240;float: left;}.course-item .pay-box .original-price {text-decoration: line-through;font-size: 14px;color: #9b9b9b;margin-left: 10px;float: left;margin-top: 10px;}.course-item .pay-box .buy-now {width: 120px;height: 38px;background: transparent;color: #fa6240;font-size: 16px;border: 1px solid #fd7b4d;border-radius: 3px;transition: all .2s ease-in-out;float: right;text-align: center;line-height: 38px;}.course-item .pay-box .buy-now:hover {color: #fff;background: #ffc210;border: 1px solid #ffc210;}
</style>

2.修订课程主页

3.课程详情页

1.详情页前台

详情页组件

依赖:在luffycity目录下的命令
>: cnpm install vue-video-player
配置:main.js
// vue-video播放器
require('video.js/dist/video-js.css');
require('vue-video-player/src/custom-theme.css');
import VideoPlayer from 'vue-video-player'
Vue.use(VideoPlayer);
资源:图片放置assrts/img文件夹
"""
enum.svg
chapter-player.svg
cart-yellow.svg
"""
路由:router.js
import CourseDetail from './views/CourseDetail.vue'
export default new Router({routes: [// ...{path: '/course/detail/:pk',name: 'course-detail',component: CourseDetail}]
}
组件
<template><div class="detail"><Header/><div class="main"><div class="course-info"><div class="wrap-left"><videoPlayer class="video-player vjs-custom-skin"ref="videoPlayer":playsinline="true":options="playerOptions"@play="onPlayerPlay($event)"@pause="onPlayerPause($event)"></videoPlayer></div><div class="wrap-right"><h3 class="course-name">{{course_info.name}}</h3><p class="data">{{course_info.students}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course_info.sections}}课时/{{course_info.pub_sections}}小时&nbsp;&nbsp;&nbsp;&nbsp;难度:{{course_info.level_name}}</p><div class="sale-time"><p class="sale-type">价格 <span class="original_price">¥{{course_info.price}}</span></p><p class="expire"></p></div><div class="buy"><div class="buy-btn"><button class="buy-now">立即购买</button><button class="free">免费试学</button></div><!--<div class="add-cart" @click="add_cart(course_info.id)">--><!--<img src="@/assets/img/cart-yellow.svg" alt="">加入购物车--><!--</div>--></div></div></div><div class="course-tab"><ul class="tab-list"><li :class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍</li><li :class="tabIndex==2?'active':''" @click="tabIndex=2">课程章节 <span :class="tabIndex!=2?'free':''">(试学)</span></li><li :class="tabIndex==3?'active':''" @click="tabIndex=3">用户评论</li><li :class="tabIndex==4?'active':''" @click="tabIndex=4">常见问题</li></ul></div><div class="course-content"><div class="course-tab-list"><div class="tab-item" v-if="tabIndex==1"><div class="course-brief" v-html="course_info.brief_text"></div></div><div class="tab-item" v-if="tabIndex==2"><div class="tab-item-title"><p class="chapter">课程章节</p><p class="chapter-length">共{{course_chapters.length}}章 {{course_info.sections}}个课时</p></div><div class="chapter-item" v-for="chapter in course_chapters" :key="chapter.name"><p class="chapter-title"><img src="@/assets/img/enum.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}</p><ul class="section-list"><li class="section-item" v-for="section in chapter.coursesections" :key="section.name"><p class="name"><span class="index">{{chapter.chapter}}-{{section.orders}}</span>{{section.name}}<span class="free" v-if="section.free_trail">免费</span></p><p class="time">{{section.duration}} <img src="@/assets/img/chapter-player.svg"></p><button class="try" v-if="section.free_trail">立即试学</button><button class="try" v-else>立即购买</button></li></ul></div></div><div class="tab-item" v-if="tabIndex==3">用户评论</div><div class="tab-item" v-if="tabIndex==4">常见问题</div></div><div class="course-side"><div class="teacher-info"><h4 class="side-title"><span>授课老师</span></h4><div class="teacher-content"><div class="cont1"><img :src="course_info.teacher.image"><div class="name"><p class="teacher-name">{{course_info.teacher.name}}{{course_info.teacher.title}}</p><p class="teacher-title">{{course_info.teacher.signature}}</p></div></div><p class="narrative">{{course_info.teacher.brief}}</p></div></div></div></div></div><Footer/></div>
</template><script>import Header from "@/components/Header"import Footer from "@/components/Footer"// 加载组件import {videoPlayer} from 'vue-video-player';export default {name: "Detail",data() {return {tabIndex: 2,   // 当前选项卡显示的下标course_id: 0, // 当前课程信息的IDcourse_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() {// 获取地址栏上面的课程IDthis.course_id = this.$route.params.pk;if (this.course_id < 1) {let _this = this;_this.$alert("对不起,当前视频不存在!", "警告", {callback() {_this.$router.go(-1);}});}},get_course_data() {// ajax请求课程信息this.$axios.get(`${this.$settings.base_url}/course/${this.course_id}/`).then(response => {// window.console.log(response.data);this.course_info = response.data;}).catch(() => {this.$message({message: "对不起,访问页面出错!请联系客服工作人员!"});})},get_chapter() {// 获取当前课程对应的章节课时信息// http://127.0.0.1:8000/course/chapters/?course=(pk)this.$axios.get(`${this.$settings.base_url}/course/chapters/`, {params: {"course": this.course_id,}}).then(response => {this.course_chapters = response.data;}).catch(error => {window.console.log(error.response);})},},components: {Header,Footer,videoPlayer, // 注册组件}}
</script><style scoped>.main {background: #fff;padding-top: 30px;}.course-info {width: 1200px;margin: 0 auto;overflow: hidden;}.wrap-left {float: left;width: 690px;height: 388px;background-color: #000;}.wrap-right {float: left;position: relative;height: 388px;}.course-name {font-size: 20px;color: #333;padding: 10px 23px;letter-spacing: .45px;}.data {padding-left: 23px;padding-right: 23px;padding-bottom: 16px;font-size: 14px;color: #9b9b9b;}.sale-time {width: 464px;background: #fa6240;font-size: 14px;color: #4a4a4a;padding: 10px 23px;overflow: hidden;}.sale-type {font-size: 16px;color: #fff;letter-spacing: .36px;float: left;}.sale-time .expire {font-size: 14px;color: #fff;float: right;}.sale-time .expire .second {width: 24px;display: inline-block;background: #fafafa;color: #5e5e5e;padding: 6px 0;text-align: center;}.course-price {background: #fff;font-size: 14px;color: #4a4a4a;padding: 5px 23px;}.discount {font-size: 26px;color: #fa6240;margin-left: 10px;display: inline-block;margin-bottom: -5px;}.original {font-size: 14px;color: #9b9b9b;margin-left: 10px;text-decoration: line-through;}.buy {width: 464px;padding: 0px 23px;position: absolute;left: 0;bottom: 20px;overflow: hidden;}.buy .buy-btn {float: left;}.buy .buy-now {width: 125px;height: 40px;border: 0;background: #ffc210;border-radius: 4px;color: #fff;cursor: pointer;margin-right: 15px;outline: none;}.buy .free {width: 125px;height: 40px;border-radius: 4px;cursor: pointer;margin-right: 15px;background: #fff;color: #ffc210;border: 1px solid #ffc210;}.add-cart {float: right;font-size: 14px;color: #ffc210;text-align: center;cursor: pointer;margin-top: 10px;}.add-cart img {width: 20px;height: 18px;margin-right: 7px;vertical-align: middle;}.course-tab {width: 100%;background: #fff;margin-bottom: 30px;box-shadow: 0 2px 4px 0 #f0f0f0;}.course-tab .tab-list {width: 1200px;margin: auto;color: #4a4a4a;overflow: hidden;}.tab-list li {float: left;margin-right: 15px;padding: 26px 20px 16px;font-size: 17px;cursor: pointer;}.tab-list .active {color: #ffc210;border-bottom: 2px solid #ffc210;}.tab-list .free {color: #fb7c55;}.course-content {width: 1200px;margin: 0 auto;background: #FAFAFA;overflow: hidden;padding-bottom: 40px;}.course-tab-list {width: 880px;height: auto;padding: 20px;background: #fff;float: left;box-sizing: border-box;overflow: hidden;position: relative;box-shadow: 0 2px 4px 0 #f0f0f0;}.tab-item {width: 880px;background: #fff;padding-bottom: 20px;box-shadow: 0 2px 4px 0 #f0f0f0;}.tab-item-title {justify-content: space-between;padding: 25px 20px 11px;border-radius: 4px;margin-bottom: 20px;border-bottom: 1px solid #333;border-bottom-color: rgba(51, 51, 51, .05);overflow: hidden;}.chapter {font-size: 17px;color: #4a4a4a;float: left;}.chapter-length {float: right;font-size: 14px;color: #9b9b9b;letter-spacing: .19px;}.chapter-title {font-size: 16px;color: #4a4a4a;letter-spacing: .26px;padding: 12px;background: #eee;border-radius: 2px;display: -ms-flexbox;display: flex;-ms-flex-align: center;align-items: center;}.chapter-title img {width: 18px;height: 18px;margin-right: 7px;vertical-align: middle;}.section-list {padding: 0 20px;}.section-list .section-item {padding: 15px 20px 15px 36px;cursor: pointer;justify-content: space-between;position: relative;overflow: hidden;}.section-item .name {font-size: 14px;color: #666;float: left;}.section-item .index {margin-right: 5px;}.section-item .free {font-size: 12px;color: #fff;letter-spacing: .19px;background: #ffc210;border-radius: 100px;padding: 1px 9px;margin-left: 10px;}.section-item .time {font-size: 14px;color: #666;letter-spacing: .23px;opacity: 1;transition: all .15s ease-in-out;float: right;}.section-item .time img {width: 18px;height: 18px;margin-left: 15px;vertical-align: text-bottom;}.section-item .try {width: 86px;height: 28px;background: #ffc210;border-radius: 4px;font-size: 14px;color: #fff;position: absolute;right: 20px;top: 10px;opacity: 0;transition: all .2s ease-in-out;cursor: pointer;outline: none;border: none;}.section-item:hover {background: #fcf7ef;box-shadow: 0 0 0 0 #f3f3f3;}.section-item:hover .name {color: #333;}.section-item:hover .try {opacity: 1;}.course-side {width: 300px;height: auto;margin-left: 20px;float: right;}.teacher-info {background: #fff;margin-bottom: 20px;box-shadow: 0 2px 4px 0 #f0f0f0;}.side-title {font-weight: normal;font-size: 17px;color: #4a4a4a;padding: 18px 14px;border-bottom: 1px solid #333;border-bottom-color: rgba(51, 51, 51, .05);}.side-title span {display: inline-block;border-left: 2px solid #ffc210;padding-left: 12px;}.teacher-content {padding: 30px 20px;box-sizing: border-box;}.teacher-content .cont1 {margin-bottom: 12px;overflow: hidden;}.teacher-content .cont1 img {width: 54px;height: 54px;margin-right: 12px;float: left;}.teacher-content .cont1 .name {float: right;}.teacher-content .cont1 .teacher-name {width: 188px;font-size: 16px;color: #4a4a4a;padding-bottom: 4px;}.teacher-content .cont1 .teacher-title {width: 188px;font-size: 13px;color: #9b9b9b;white-space: nowrap;}.teacher-content .narrative {font-size: 14px;color: #666;line-height: 24px;}
</style>

2.详情页后台

详情页后台

路由:source/urls.py
re_path("(?P<pk>\d+)/", views.CourseRetrieveAPIView.as_view()),
path("chapters/", views.CourseChapterListAPIView.as_view()),
视图:source/views.py
from .models import Course
from .models import CourseChapter
from rest_framework.generics import ListAPIView
from rest_framework.generics import RetrieveAPIView
from . import serializers
from django_filters.rest_framework.backends import DjangoFilterBackendclass CourseRetrieveAPIView(RetrieveAPIView):"""课程详情信息"""queryset = Course.objects.filter(is_delete=False, is_show=True)serializer_class = serializers.CourseRetrieveModelSSerializerclass CourseChapterListAPIView(ListAPIView):"""课程详情信息"""queryset = CourseChapter.objects.filter(is_delete=False, is_show=True).order_by("chapter")serializer_class = serializers.CourseChapterModelSerializerfilter_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.Coursefields = ["id", "name", "course_img", "students", "sections", "pub_sections", "price", "teacher", "level_name"]class CourseSessionModelSerializer(ModelSerializer):class Meta:model = models.CourseSectionfields = ["id", "name", "duration", "free_trail", "orders"]class CourseChapterModelSerializer(ModelSerializer):coursesections = CourseSessionModelSerializer(many=True)class Meta:model = models.CourseChapterfields = ["chapter", "name", "summary", "coursesections"]
视图字段:source/model.py
class Course(BaseModel):# ...@propertydef 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 Courseclass 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)@propertydef 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_listclass 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.Orderfields = ('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() * 2request_data['out_trade_no'] = out_trade_norequest_data['user'] = request.user.id# 反序列化数据,用于订单生成前的校验order_ser = serializers.OrderModelSerializer(data=request_data)if order_ser.is_valid():# 生成订单,订单默认状态为:未支付order = order_ser.save()# 支付链接的参数order_string = alipay.api_alipay_trade_page_pay(subject=order.subject,out_trade_no=order.out_trade_no,total_amount='%.2f' % order.total_amount,return_url=settings.RETURN_URL,notify_url=settings.NOTIFY_URL)# 形成支付链接:alipay._gateway根据字符环境DEBUG配置信息,决定是沙箱还是真实支付环境pay_url = '%s?%s' % (alipay._gateway, order_string)return APIResponse(0, 'ok', pay_url=pay_url)return APIResponse(1, 'no ok', results=order_ser.errors)

前台回调接口的页面

{path: '/pay/success',name: 'pay-success',component: PaySuccess
},
<template><div class="pay-success"><Header/><div class="main"><div class="title"><div class="success-tips"><p class="tips">您已成功购买 1 门课程!</p></div></div><div class="order-info"><p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p><p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p><p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p></div><div class="study"><span>立即学习</span></div></div><Footer/></div>
</template><script>import Header from "@/components/Header"import Footer from "@/components/Footer"export default {name: "Success",data() {return {result: {},};},created() {// 判断登录状态let token = this.$cookies.get('token');if (!token) {this.$message.error('非法请求');this.$router.go(-1)}localStorage.this_nav = '/';if (!location.search.length) return;let params = location.search.substring(1);let items = params.length ? params.split('&') : [];//逐个将每一项添加到args对象中for (let i = 0; i < items.length; i++) {let k_v = items[i].split('=');//解码操作,因为查询字符串经过编码的let k = decodeURIComponent(k_v[0]);let v = decodeURIComponent(k_v[1]);this.result[k] = v;// this.result[k_v[0]] = k_v[1];}// console.log(this.result);// 把地址栏上面的支付结果,转发给后端this.$axios({url: this.$settings.base_url + '/order/success/' + location.search,method: 'patch',headers: {Authorization: token}}).then(response => {console.log(response.data);}).catch(() => {console.log('支付结果同步失败');})},components: {Header,Footer,}}
</script><style scoped>.main {padding: 60px 0;margin: 0 auto;width: 1200px;background: #fff;}.main .title {display: flex;-ms-flex-align: center;align-items: center;padding: 25px 40px;border-bottom: 1px solid #f2f2f2;}.main .title .success-tips {box-sizing: border-box;}.title img {vertical-align: middle;width: 60px;height: 60px;margin-right: 40px;}.title .success-tips {box-sizing: border-box;}.title .tips {font-size: 26px;color: #000;}.info span {color: #ec6730;}.order-info {padding: 25px 48px;padding-bottom: 15px;border-bottom: 1px solid #f2f2f2;}.order-info p {display: -ms-flexbox;display: flex;margin-bottom: 10px;font-size: 16px;}.order-info p b {font-weight: 400;color: #9d9d9d;white-space: nowrap;}.study {padding: 25px 40px;}.study span {display: block;width: 140px;height: 42px;text-align: center;line-height: 42px;cursor: pointer;background: #ffc210;border-radius: 6px;font-size: 16px;color: #fff;}
</style>

支付完成订单校验的接口

from . import models
from utils.logging import logger
from rest_framework.response import Response
class SuccessAPIView(APIView):# 不能认证,别人支付宝异步回调就进不来了# authentication_classes = [authentications.JWTAuthentication]# permission_classes = [IsAuthenticated]def patch(self, request, *args, **kwargs):# 默认是QueryDict类型,不能使用pop方法request_data = request.query_params.dict()# 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验sign = request_data.pop('sign')result = alipay.verify(request_data, sign)if result:  # 同步回调:修改订单状态try:out_trade_no = request_data.get('out_trade_no')order = models.Order.objects.get(out_trade_no=out_trade_no)if order.order_status != 1:order.order_status = 1order.save()except:passreturn 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 = 1order.save()except:pass# 支付宝八次异步通知,订单成功一定要返回 successreturn Response('success')return Response('failed')

上线

购买服务器

# 购买阿里云服务器
# 短期或是测试使用,创建 按量收费 服务器,可以随时删除,删除后不再计费,但要保证账户余额100元以上

连接服务器

1)账号
>: ssh root@39.98.144.2212)密码
>: ********

服务器命令

管理员权限
1)以下所有的服务器命令均可以在管理员权限下执行
>: sudo 命令
配置终端
1)编辑配置文件
>: vim ~/.bash_profile2)将原来内容全部删除掉
>: ggdG3)进入编辑状态:填入下方两行
>: iexport PATH=$PATH:$HOME/bin
PS1='Path:\w\n>:'4)退出编辑状态
>: esc5)保存修改并退出
>: :wq6)生效配置
>: source ~/.bash_profile

重要

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

安装Mysql

1)前往用户根目录
>: cd ~2)下载mysql57
>: wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm也可以本地上传,这条命令要在本地终端上执行
>: scp -r C:\Users\dell\Desktop\pkg\mysql57-community-release-el7-10.noarch.rpm root@39.98.144.221:~3)安装mysql57
>: yum -y install mysql57-community-release-el7-10.noarch.rpm
>: yum -y install mysql-community-server4)启动mysql57并查看启动状态
>: systemctl start mysqld.service
>: systemctl status mysqld.service5)查看默认密码并登录
>: grep "password" /var/log/mysqld.log
>: mysql -uroot -p6)修改密码
>: ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password';
>: ALTER USER 'root'@'localhost' IDENTIFIED BY 'Owen1234?';

安装Redis

1)前往用户根目录
>: cd ~2)下载redis-5.0.5
>: wget http://download.redis.io/releases/redis-5.0.5.tar.gz
>: scp -r C:\Users\dell\Desktop\pkg\redis-5.0.5.tar.gz root@39.98.144.221:~3)解压安装包
>: tar -xf redis-5.0.5.tar.gz4)进入目标文件
>: cd redis-5.0.55)编译环境
>: make6)复制环境到指定路径完成安装
>: cp -r ~/redis-5.0.5 /usr/local/redis7)配置redis可以后台启动:修改下方内容
>: vim /usr/local/redis/redis.confdaemonize yes8)完成配置修改
>: esc
>: :wq9)建立软连接
>: ln -s /usr/local/redis/src/redis-server /usr/bin/redis-server
>: ln -s /usr/local/redis/src/redis-cli /usr/bin/redis-cli10)后台运行redis
>: redis-server &
ctrl + c11)测试redis环境
>: redis-cli
ctrl + c12)关闭redis服务
>: pkill -f redis -9

安装Python3.6

1)前往用户根目录
>: cd ~2)下载 或 上传 Python3.6.7
>: wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz
>: scp -r 本地Python-3.6.7.tar.xz ssh root@39.98.144.221:服务器路径
>: scp -r C:\Users\dell\Desktop\pkg\Python-3.6.7.tar.xz ssh root@39.98.144.221:~3)解压安装包
>: tar -xf Python-3.6.7.tar.xz4)进入目标文件
>: cd Python-3.6.75)配置安装路径:/usr/local/python3
>: ./configure --prefix=/usr/local/python36)编译并安装
>: make && sudo make install7)建立软连接:终端命令 python3,pip3
>: ln -s /usr/local/python3/bin/python3.6 /usr/bin/python3
>: ln -s /usr/local/python3/bin/pip3.6 /usr/bin/pip38)删除安装包与文件:
>: rm -rf Python-3.6.7
>: rm -rf Python-3.6.7.tar.xz

配置pip源:阿里云不用配置,默认配置阿里源

1)创建pip配置路径
>: mkdir ~/.pip2)进入目录编辑配置文件:填入下方内容
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 uwsgi2)建立软连接
ln -s /usr/local/python3/bin/uwsgi /usr/bin/uwsgi

安装虚拟环境

1)安装依赖
>: pip3 install virtualenv
>: pip3 install virtualenvwrapper2)建立虚拟环境软连接
>: ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv3)配置虚拟环境:填入下方内容
>: vim ~/.bash_profileVIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/local/python3/bin/virtualenvwrapper.sh4)退出编辑状态
>: esc5)保存修改并退出
>: :wq6)更新配置文件内容
>: source ~/.bash_profile7)虚拟环境默认根目录:~/.virtualenvs

服务器运行测试Django项目

1)创建虚拟环境
>: mkvirtualenv test_venv2)安装依赖
>: pip install django3)前往目标目录,创建项目工作目录,再进入
>: cd /home
>: mkdir project
>: cd project4)创建Django项目,并进入
>: django-admin startproject test_site
>: cd test_site5)完成项目配置:修改下方几行内容
>: vim /home/project/test_site/test_site/settings.pyALLOWED_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.gz3)解压安装包
>: tar -xf nginx-1.13.7.tar.gz4)进入目标文件
>: cd nginx-1.13.75)配置安装路径:/usr/local/nginx
>: ./configure --prefix=/usr/local/nginx6)编译并安装
>: make && sudo make install7)建立软连接:终端命令 nginx
>: ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx8)删除安装包与文件:
>: rm -rf nginx-1.13.7
>: rm -rf nginx-1.13.7.tar.xz9)测试Nginx环境,服务器运行nginx,本地访问服务器ip
>: nginx
>: 服务器绑定的域名 或 ip:80

Nginx命令

1)启动
>: nginx2)关闭nginx
>: nginx -s stop3)重启nginx
>: nginx -s reload4)查看端口,强行关闭
>: ps -aux|grep nginx
>: kill <pid:进程编号>

Nginx & uwsgi 运行Django

1)在项目的虚拟环境安装uwsgi
>: workon test_venv
>: pip install uwsgi2)项目根目录配置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.pyDEBUG = False
ALLOWED_HOSTS = ['*']4)去向Nginx配置目录,备份配置,完全更新配置:填入下方内容
>: cd /usr/local/nginx/conf
>: cp nginx.conf nginx.conf.bak
>: vim nginx.conf
>: ggdG
>: ievents {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:80charset utf-8;location / {include uwsgi_params;uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi里配置的一样uwsgi_param UWSGI_SCRIPT test_site.wsgi;  #wsgi.py所在的目录名+.wsgiuwsgi_param UWSGI_CHDIR /home/project/test_site/; # 项目路径}}
}5)启动uwsgi
>: uwsgi -x /home/project/test_site/test_site.xml6)启动nginx
>: nginx7)浏览器测试:http://39.98.144.221/admin8)关闭uwsgi所有进程
>: pkill -f uwsgi -9

路飞项目部署:Nginx + uwsgi + django + vue

配置前台项目

上线前配置

settings.js
base_url: 'http://39.98.144.221:8000',  // 设置公网ip

上线

1)本地项目打包,前往luffycity项目目录下
>: cnpm run build2)上传
>: scp -r dist root@39.98.144.221:~3)移动并重命名
mv ~/dist /home/html4)去向Nginx配置目录,备份配置,完全更新配置:填入下方内容
>: cd /usr/local/nginx/conf
>: cp nginx.conf nginx.conf.bak
>: vim nginx.conf
>: ggdG
>: ievents {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:80charset 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 uwsgi2)项目根目录配置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.conf5)在原来基础上添加一个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:80charset utf-8;location / {include uwsgi_params;uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi里配置的一样uwsgi_param UWSGI_SCRIPT luffyapi.wsgi;  #wsgi.py所在的目录名+.wsgiuwsgi_param UWSGI_CHDIR /home/project/luffyapi/; # 项目路径}}
}见下方配置:pip环境 + 数据库设置 + django2.0源码5)启动uwsgi
>: uwsgi -x /home/project/luffyapi/luffyapi.xml6)启动nginx
>: nginx -s stop
>: nginx
>: nginx -s reload7)浏览器测试:http://39.98.144.221:8000/xadmin8)关闭uwsgi所有进程
>: pkill -f uwsgi -9

pip导入导出依赖

1) 本地导出项目环境,上传线上,导入到线上环境中本地操作
# 桌面新建env文件夹,开启终端进入文件夹,执行下方命名
>: cd Desktop\env
>: pip3 freeze > packages.txt
# 注:把xadmin删掉
>: scp -r packages.txt root@39.98.144.221:~服务器操作
# 进入虚拟环境
>: workon luffy
# 导入
>: pip3 install -r packages.txt
# 安装xadmin
>: pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2

数据库设置 + django2.0源码(2.0.7不用修改源码)

1.管理员连接数据库
>: mysql -uroot -pOwen1234?2.创建数据库
>: create database luffy default charset=utf8;# 3.设置权限账号密码
# 拥有公网或局域网,其他主机连mysql
>: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?';
# 要是本机连mysql连不上,再增加localhost域,本机就可以登录了
>: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?';
# 设置完有权限限制的账号后一定要刷新权限
>: flush privileges;4.退出mysql
quit5.修改 prod.py | manage.py
>: vim /home/project/luffyapi/luffyapi/settings/prod.py"PASSWORD": "Luffy123?">: vim /home/project/luffyapi/manage.pyos.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 migrate8.创建超级用户
>: 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.confevents {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:80charset utf-8;location / {include uwsgi_params;uwsgi_pass 127.0.0.1:8808;  # 端口要和uwsgi里配置的一样uwsgi_param UWSGI_SCRIPT luffyapi.wsgi;  #wsgi.py所在的目录名+.wsgiuwsgi_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:80charset 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学院2016\2017年度最受学员喜爱10大讲师之一 多款开源软件作者 曾任职公安部、飞信、中金公司、NOKIA中国研究院、华尔街英语、ADVENT、汽车之家等公司');INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (2, 2, 1, 0, '2019-07-14 13:45:25.092902', '2019-07-14 13:45:25.092936', 'Mjj', 0, '前美团前端项目组架构师', NULL, 'teacher/mjj_icon.png', '是马JJ老师, 一个集美貌与才华于一身的男人,搞过几年IOS,又转了前端开发几年,曾就职于美团网任高级前端开发,后来因为不同意王兴(美团老板)的战略布局而出家做老师去了,有丰富的教学经验,开起车来也毫不含糊。一直专注在前端的前沿技术领域。同时,爱好抽烟、喝酒、烫头(锡纸烫)。 我的最爱是前端,因为前端妹子多。');INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (3, 3, 1, 0, '2019-07-14 13:46:21.997846', '2019-07-14 13:46:21.997880', 'Lyy', 0, '老男孩Linux学科带头人', NULL, 'teacher/lyy_icon.png', 'Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸');INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (1, 1, 1, 0, '2019-07-14 13:40:58.690413', '2019-07-14 13:40:58.690477', 'Python');INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (2, 2, 1, 0, '2019-07-14 13:41:08.249735', '2019-07-14 13:41:08.249817', 'Linux');INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (1, 1, 1, 0, '2019-07-14 13:54:33.095201', '2019-07-14 13:54:33.095238', 'Python开发21天入门', 'courses/alex_python.png', 0, 'Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土', 0, '2019-07-14', 21, '', 0, 231, 120, 120, 0.00, 1, 1);INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (2, 2, 1, 0, '2019-07-14 13:56:05.051103', '2019-07-14 13:56:05.051142', 'Python项目实战', 'courses/mjj_python.png', 0, '', 1, '2019-07-14', 30, '', 0, 340, 120, 120, 99.00, 1, 2);INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (3, 3, 1, 0, '2019-07-14 13:57:21.190053', '2019-07-14 13:57:21.190095', 'Linux系统基础5周入门精讲', 'courses/lyy_linux.png', 0, '', 0, '2019-07-14', 25, '', 0, 219, 100, 100, 39.00, 2, 3);INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (1, 1, 1, 0, '2019-07-14 13:58:34.867005', '2019-07-14 14:00:58.276541', 1, '计算机原理', '', '2019-07-14', 1);INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (2, 2, 1, 0, '2019-07-14 13:58:48.051543', '2019-07-14 14:01:22.024206', 2, '环境搭建', '', '2019-07-14', 1);INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (3, 3, 1, 0, '2019-07-14 13:59:09.878183', '2019-07-14 14:01:40.048608', 1, '项目创建', '', '2019-07-14', 2);INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (4, 4, 1, 0, '2019-07-14 13:59:37.448626', '2019-07-14 14:01:58.709652', 1, 'Linux环境创建', '', '2019-07-14', 3);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (1, 1, 0, '2019-07-14 14:02:33.779098', '2019-07-14 14:02:33.779135', '计算机原理上', 1, 2, NULL, NULL, '2019-07-14 14:02:33.779193', 1, 1);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (2, 1, 0, '2019-07-14 14:02:56.657134', '2019-07-14 14:02:56.657173', '计算机原理下', 2, 2, NULL, NULL, '2019-07-14 14:02:56.657227', 1, 1);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (3, 1, 0, '2019-07-14 14:03:20.493324', '2019-07-14 14:03:52.329394', '环境搭建上', 1, 2, NULL, NULL, '2019-07-14 14:03:20.493420', 0, 2);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (4, 1, 0, '2019-07-14 14:03:36.472742', '2019-07-14 14:03:36.472779', '环境搭建下', 2, 2, NULL, NULL, '2019-07-14 14:03:36.472831', 0, 2);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (5, 1, 0, '2019-07-14 14:04:19.338153', '2019-07-14 14:04:19.338192', 'web项目的创建', 1, 2, NULL, NULL, '2019-07-14 14:04:19.338252', 1, 3);INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (6, 1, 0, '2019-07-14 14:04:52.895855', '2019-07-14 14:04:52.895890', 'Linux的环境搭建', 1, 2, NULL, NULL, '2019-07-14 14:04:52.895942', 1, 4);

课堂笔记

复习

"""
redis基本介绍:nosql数据库、内存数据库优势:读写效率高、操作方便、支持五种数据类型、数据持久化、数据丢失可以还原、高并发缺点:不支持事务操作五种数据类型:set key value | setex key time valuerpush key args | lpush key argshset key field valuesadd key argszadd key score memberPython使用:pip install redisDjango使用:pip install django-redis => 配置缓存celery基本介绍:异步任务框架(独立的socket)组成:Broker(任务中间件:内存数据库、内存队列)、Worker(任务执行者)、Backend(任务结果仓库)创建celery应用:app = Celery(broker, backend, includ)服务命令:worker:celery worker -A celery文件所在包 -l info -P eventletbeat:celery beat -A celery文件所在包 -l infocelery应用场景:耗时任务延迟任务周期任务
"""

课程内容

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

A作业(必做)

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

B作业(选做)

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

七、

上线

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

支付

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

搜索

Header搜索组件

<form class="search"><div class="tips" v-if="is_search_tip"><span>Python</span><span>Linux</span></div><input type="text" :placeholder="search_placeholder" @focus="on_search" @blur="off_search"><button type="button" class="el-icon-search"></button>
</form><script>export default {data() {return {is_search_tip: true,search_placeholder: '',}},methods: {on_search() {this.search_placeholder = '请输入想搜索的课程';this.is_search_tip = false;},off_search() {this.search_placeholder = '';this.is_search_tip = true;},},}
</script><style scoped>.search {float: right;position: relative;margin-top: 22px;}.search input, .search button {border: none;outline: none;}.search input {border-bottom: 1px solid black;}.search input:focus {border-bottom-color: orange;}.search input:focus + button {color: orange;}.search .tips {position: absolute;bottom: 3px;left: 0;}.search .tips span {border-radius: 11px;background-color: #eee;line-height: 22px;display: inline-block;padding: 0 3px;margin-right: 3px;cursor: pointer;}
</style>

课堂笔记

日考

"""
1.vue指令和成员:v-text、html、if、for、show、model、on、binddata、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,students2、drf搜索:SearchFilter  search_fields  search=*3、自定义过滤器
class MyFilter:def filter_queryset(self, request, queryset, view):# request:从前台请求获取过滤的条件 query_params# queryset:要处理的数据# view:从视图中反射过滤相关的配置return 处理后的queryset,没有过滤就返回原样queryset4、分页器
PageNumberPagination:基础分页器 page_size    page=1
LimitOffsetPagination:偏移分页器 default_limit    offset=0&limit=3
CursorPagination:游标分压器 一定是基于某种排序规则下的分页5、django-filterfrom 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.Coursefields = ['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-90\luffy\day90\课件\上线 (自己本地文件夹)

支付

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

课堂笔记

复习

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

课程总结

"""
Vue基础:指令、实例成员、组件及传参开发:vue-router、vuex(localStorage|sessionStorage)、axios、vue-cookiesdrf基础模块:请求、响应、渲染、解析、异常核心模块:序列化、三大认证、视图家族群查模块:搜索、排序、分页、分类、区间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.jsimport '@/assets/css/global.css'
require('@/assets/css/global.css')import settings from '@/assets/js/settings.js'
Vue.prototype.$settings = settingsimport '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)、exceptiondata: {status,msg,results,}渲染:响应的数据支持浏览器和json格式数据渲染(全局或局部配置)
解析:请求的数据包数据支持解析的类型:urlencoded、form-data、json(全局或局部配置)
异常:自定义exception_handler,系统处理了客户端异常4xx,服务器异常需要手动处理5xx,记录异常日志序列化:class myModel(models.Model):name = models.CharFields(max_length=64)@propertydef my_name(self):return self.nameclass MyModelSerializer(ModelSerializer):// 序列化还有子序列化re_pwd = serializers.CharFields(max_length=64)class Meta:model = myModelfields = ('name', 'my_name', 're_pwd')extra_kwargs={}list_serializer_class = ListSerializedef validate_name(self, value):if ...:raise serializers.ValidationError('...')return valuedef validate(self, attrs):request = self.context.get('request')objself.obj = objif ...:raise serializers.ValidationError({'...': '...'})return attrsclass myAPIView(APIView):def get(self, request, *args, **kwargs):objser = MyModelSerializer(obj)objsser = 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.objdef patch(self, request, *args, **kwargs):objser = 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'})群查接口:SearchFilterOrderingFilterpaginationsdjango-filter插件
"""

课程内容

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

A作业(必做)

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

B作业(选做)

"""
1、认证复习路飞项目涉及的所有知识点,将luffy项目完成
2、预习微信小程序视频
"""

路飞学城之 luffy (2 )相关推荐

  1. 路飞学城之 luffy(1)

    目录 路飞学城之 luffy(1) 一.内容 二.内容大纲 小结 三.内容大纲 小结 四.内容大纲 自定义模态框 componses/Login.vue componses/Header.vue vu ...

  2. vue+uwsgi+nginx部署路飞学城

    路飞学城django代码 https://files.cnblogs.com/files/pyyu/luffy_boy.zip vue代码 https://files.cnblogs.com/file ...

  3. 路飞学城Python-Day46

    16-如何正确的使用类选择器及总结 一般情况下尽量不要去使用id选择器,因为id选择器有很大的限制性,id一般都是JS配合使用的,类选择器都是和CSS配合使用的,特殊性情况可以用id选择器. 类的使用 ...

  4. 路飞学城—Python爬虫实战密训班 第三章

    路飞学城-Python爬虫实战密训班 第三章 一.scrapy-redis插件实现简单分布式爬虫 scrapy-redis插件用于将scrapy和redis结合实现简单分布式爬虫:- 定义调度器- 定 ...

  5. Python高级全栈开发实战 老男孩课程S16+路飞学城项目+女神串讲 Python全栈直通车课程

    python高级全栈开发实战 老男孩课程S16+路飞学城项目+女神串讲 Python全栈直通车课程 Python高级全栈开发实战老男孩课程,是可以帮助同学们从零基础开始到项目开发实战的全栈课程,内容非 ...

  6. 路飞学城python全栈开发_[Python] 老男孩路飞学城Python全栈开发重点班 骑士计划最新100G...

    简介 老男孩&路飞学城Python全栈开发重点班课程,作为Python全栈教学系列的重头戏,投入了全新的课程研发和教学精力,也是Python骑士计划的核心教学,由ALEX老师开班镇守,一线技术 ...

  7. 路飞学城结算中心实现

    路飞学城结算中心的优惠券表结构 # ########################### 优惠券 ################################ class Coupon(mode ...

  8. b站路飞学城python课梨视频项目代码

    @b站路飞学城python课梨视频项目代码 # -*- coding:utf-8 -*- import requests from lxml import etree import random im ...

  9. 路飞学城Python-Day14

    转载:python之路-路飞学城-python-book [25.常用模块-logging模块详解] [26.常用模块-logging模块详解2] [27.常用模块-logging模块日志过滤和日志文 ...

  10. drf框架使用之 路飞学城(第一天)

    1. 路飞学城第一天: 知识点 使用的是序列化与反序列化的使用: 1.创建一个公共相应的Json方法: #创建一个公共响应的类:class LuffyResponse():def __init__(s ...

最新文章

  1. 简述python程序结构_python架构的概念讲解
  2. 互联网协议 — 物理介质层
  3. VMWare假造机上装配Ubuntu Linux体例-1
  4. C++ leetCode 1. 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个
  5. OpenCV SURF检测的实例(附完整代码)
  6. asprise java_使用asprise进行图片验证码识别
  7. shell脚本每日一练(三)
  8. Linux/U-Boot Git Repo
  9. opencv4.10不能使用sift = cv2.xfeatures2d.SIFT_create()
  10. mysql的service name_安装MYSQL出错:a windows service with the name MYSQL already...service解决...
  11. Python 打败 JavaScript 只需要 5 年?
  12. MATLAB注释快捷键、绘图选项
  13. python读取bmp文件,go 读取BMP文件头二进制读取方式
  14. (最新版 易卷/自动出题平台)自动阅卷系统 | 自动阅卷机 | 网络阅卷系统
  15. php网页框架模板,用PHP制作静态网站的模板框架(四)
  16. 数据结构——栈与队列的异同
  17. android 连接usb扫码枪
  18. 【AutoCAD For Mac 手动删除步骤】
  19. 设计模式是什么鬼(抽象工厂)
  20. TWS ChargerBox 驱动篇(一):Charger

热门文章

  1. 周末做了一个区块链导航工具-Barfor导航
  2. cad二次开发c#学习记录2——画一条线
  3. 双样本T检验-P-T和T-T检验
  4. 计算机 host id是指,关于如何修改hostid的问题Windows系统 -电脑资料
  5. 均价 和 最新价格 是啥意思 什么是MACD DIFF DEA 指标?
  6. macd是什么意思,详析MACD是什么意思
  7. python中如何将矩阵合并并多一个维度
  8. 中小学教师计算机培训心得,小学教师信息技术培训心得体会2篇
  9. 地产大佬潘石屹宣布入坑Python,这是要来抢我们的饭碗了吗?
  10. SageMath常用函数