""" 一、采用国内源,加速下载模块的速度 二、经常使用pip源: -- 豆瓣:https://pypi.douban.com/simple -- 阿里:https://mirrors.aliyun.com/pypi/simple 三、加速安装的命令: -- >: pip install -i https://pypi.douban.com/simple 模块名 """
""" 一、文件管理器文件路径地址栏敲:%APPDATA% 回车,快速进入 C:\Users\电脑用户\AppData\Roaming 文件夹中 二、新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件 三、新增 pip.ini 配置文件内容 """
""" 一、在用户根目录下 ~ 下建立 .pip 隐藏文件夹,若是已经有了能够跳过 -- mkdir ~/.pip 二、进入 .pip 隐藏文件夹并建立 pip.conf 配置文件 -- cd ~/.pip && touch pip.conf 三、启动 Finder(访达) 按 cmd+shift+g 来的进入,输入 ~/.pip 回车进入 四、新增 pip.conf 配置文件内容 """
""" [global] index-url = http://pypi.douban.com/simple [install] use-mirrors =true mirrors =http://pypi.douban.com/simple/ trusted-host =pypi.douban.com """
一、使不一样应用开发环境相互独立 二、环境升级不影响其余应用,也不会影响全局的python环境 三、防止出现包管理混乱及包版本冲突
# 建议使用pip3安装到python3环境下 pip3 install virtualenv pip3 install virtualenvwrapper-win
# 配置环境变量: # 控制面板 => 系统和安全 => 系统 => 高级系统设置 => 环境变量 => 系统变量 => 点击新建 => 填入变量名与值 变量名:WORKON_HOME 变量值:自定义存放虚拟环境的绝对路径 eg: WORKON_HOME: D:\Virtualenvs # 同步配置信息: # 去向Python3的安装目录 => Scripts文件夹 => virtualenvwrapper.bat => 双击
# 建议使用pip3安装到python3环境下 pip3 install -i https://pypi.douban.com/simple virtualenv pip3 install -i https://pypi.douban.com/simple virtualenvwrapper
# 先找到virtualenvwrapper的工做文件 virtualenvwrapper.sh,该文件能够刷新自定义配置,但须要找到它 # MacOS可能存在的位置 /Library/Frameworks/Python.framework/Versions/版本号文件夹/bin # Linux可能所在的位置 /usr/local/bin | ~/.local/bin | /usr/bin # 建议无论virtualenvwrapper.sh在哪一个目录,保证在 /usr/local/bin 目录下有一份 # 若是不在 /usr/local/bin 目录,如在 ~/.local/bin 目录,则复制一份到 /usr/local/bin 目录 -- sudo cp -rf ~/.local/bin/virtualenvwrapper.sh /usr/local/bin
# 在 ~/.bash_profile 完成配置,virtualenvwrapper的默认默认存放虚拟环境路径是 ~/.virtualenvs # WORKON_HOME=自定义存放虚拟环境的绝对路径,须要自定义就解注 VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3 source /usr/local/bin/virtualenvwrapper.sh # 在终端让配置生效: -- source ~/.bash_profile
# 在终端工做的命令 # 一、建立虚拟环境到配置的WORKON_HOME路径下 # 选取默认Python环境建立虚拟环境: -- mkvirtualenv 虚拟环境名称 # 基于某Python环境建立虚拟环境: -- mkvirtualenv -p python2.7 虚拟环境名称 -- mkvirtualenv -p python3.6 虚拟环境名称 # 二、查看已有的虚拟环境 -- workon # 三、使用某个虚拟环境 -- workon 虚拟环境名称 # 四、进入|退出 该虚拟环境的Python环境 -- python | exit() # 五、为虚拟环境安装模块 -- pip或pip3 install 模块名 # 六、退出当前虚拟环境 -- deactivate # 七、删除虚拟环境(删除当前虚拟环境要先退出) -- rmvirtualenv 虚拟环境名称
""" 为luffy项目建立一个虚拟环境 >: mkvirtualenv luffy """ """ 按照基础环境依赖 >: pip install django==2.0.7 >: pip install djangorestframework >: pip install pymysql """
""" 前提:在目标目录新建luffy文件夹 >: cd 创建的luffy文件夹 >: django-admin startproject luffyapi 开发:用pycharm打开项目,并选择提早备好的虚拟环境 """
""" ├── luffyapi ├── logs/ # 项目运行时/开发时日志目录 - 包 ├── manage.py # 脚本文件 ├── luffyapi/ # 项目主应用,开发时的代码保存 - 包 ├── apps/ # 开发者的代码保存目录,以模块[子应用]为目录保存 - 包 ├── libs/ # 第三方类库的保存目录[第三方组件、模块] - 包 ├── settings/ # 配置目录 - 包 ├── dev.py # 项目开发时的本地配置 └── prod.py # 项目上线时的运行配置 ├── urls.py # 总路由 └── utils/ # 多个模块[子应用]的公共函数类库[本身开发的组件] └── scripts/ # 保存项目运营时的脚本文件 - 文件夹 """
""" 1.修改 wsgi.py 与 manage.py 两个文件: os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev') 2.将settings.py删除或更名,内容拷贝到settings/dev.py中 3.修改dev.py文件内容 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = False 4.修改启动配置:见插图 5.在任何一个__init__.py文件中测试默认配置文件是不是dev.py文件 from django.conf import settings print(settings) """
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 实际开发建议使用WARNING 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', # 日志位置,日志文件名,日志保存目录必须手动建立,注:这里的文件路径要注意BASE_DIR表明的是小luffyapi 'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"), # 日志文件的最大值,这里咱们设置300M 'maxBytes': 300 * 1024 * 1024, # 日志文件的数量,设置最大日志数量为10 'backupCount': 10, # 日志格式:详细格式 'formatter': 'verbose', # 文件内容编码 'encoding': 'utf-8' }, }, # 日志对象 'loggers': { 'django': { 'handlers': ['console', 'file'], 'propagate': True, # 是否让日志信息继续冒泡给其余的日志处理系统 }, } }
# 环境变量操做:小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)
# 真实项目上线后,日志文件打印级别不能太低,由于一第二天志记录就是一次文件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, # 是否让日志信息继续冒泡给其余的日志处理系统 }, } }
import logging logger = logging.getLogger('django')
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
REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'utils.exception.exception_handler', }
from rest_framework.response import Response class APIResponse(Response): def __init__(self, data_status=0, data_msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs): data = { 'status': data_status, 'msg': data_msg, } if results is not None: data['results'] = results data.update(kwargs) super().__init__(data=data, status=http_status, headers=headers, exception=exception)
""" 1.管理员链接数据库 >: mysql -uroot -proot 2.建立数据库 >: create database luffy default charset=utf8; 3.查看用户 >: select user,host,password from mysql.user; """
""" 设置权限帐号密码 # 受权帐号命令:grant 权限(create, update) on 库.表 to '帐号'@'host' identified by '密码' 1.配置任意ip均可以连入数据库的帐户 >: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?'; 2.因为数据库版本的问题,可能本地还链接不上,就给本地用户单独配置 >: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?'; 3.刷新一下权限 >: flush privileges; 只能操做luffy数据库的帐户 帐号:luffy 密码:Luffy123? """
前提:在 luffy 虚拟环境下 1.终端从项目根目录进入apps目录 >: cd luffyapi & cd apps 2.建立app >: python ../../manage.py startapp user
from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): mobile = models.CharField(max_length=11, unique=True) icon = models.ImageField(upload_to='icon', default='icon/default.png') class Meta: db_table = 'luffy_user' verbose_name = '用户表' verbose_name_plural = verbose_name def __str__(self): return self.username
INSTALLED_APPS = [ # ... 'user', ] # 自定义User表 AUTH_USER_MODEL = 'user.User'
MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
""" ├── luffyapi └── luffyapi/ └── media/ └── icon └── default.png """
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}) ]
from django.urls import path, re_path urlpatterns = [ ]
1.傻瓜式安装node: 官网下载:https://nodejs.org/zh-cn/ 2.安装cnpm: >: npm install -g cnpm --registry=https://registry.npm.taobao.org 3.安装vue最新脚手架: >: cnpm install -g @vue/cli 注:若是二、3步报错,清除缓存后从新走二、3步 >: npm cache clean --force
""" 前提:在目标目录新建luffy文件夹 >: cd 创建的luffy文件夹 >: vue create luffycity """
""" ├── luffycity ├── public/ # 项目共有资源 ├── favicon.ico # 站点图标 └── index.html # 主页 ├── src/ # 项目主应用,开发时的代码保存 ├── assets/ # 前台静态资源总目录 ├── css/ # 自定义css样式 └── global.css # 自定义全局样式 ├── js/ # 自定义js样式 └── settings.js # 自定义配置文件 └── img/ # 前台图片资源 ├── components/ # 小组件目录 ├── views/ # 页面组件目录 ├── App.vue # 根路由 ├── main.js # 入口脚本文件 ├── router └── index.js # 路由脚本文件 store └── index.js # 仓库脚本文件 ├── vue.config.js # 项目配置文件 └── *.* # 其余配置文件 """
<template> <div id="app"> <router-view/> </div> </template>
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
<template> <div class="home"> </div> </template> <script> export default { name: 'home', components: { }, } </script>
/* 声明全局样式和项目的初始化样式 */ 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; /* 合并边框 */ }
export default { base_url: 'http://127.0.0.1:8000' }
// 配置全局样式 import '@/assets/css/global.css' // 配置全局自定义设置 import settings from '@/assets/js/settings' Vue.prototype.$settings = settings; // 在全部须要与后台交互的组件中:this.$settings.base_url + '再拼接具体后台路由'
>: cnpm install axios
import axios from 'axios' Vue.prototype.$axios = axios;
>: cnpm install vue-cookies
import cookies from 'vue-cookies' Vue.prototype.$cookies = cookies;
>: cnpm install element-ui
import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
>: cnpm install jquery >: cnpm install bootstrap@3
const webpack = require("webpack"); module.exports = { configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery", "window.$": "jquery", Popper: ["popper.js", "default"] }) ] } };
import 'bootstrap' import 'bootstrap/dist/css/bootstrap.min.css'
将提供的资料中的图片移植到项目的img文件夹下
<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> | <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>
<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>
<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>
<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>
前提:在 luffy 虚拟环境下 1.终端从项目根目录进入apps目录 >: cd luffyapi & cd apps 2.建立app >: python ../../manage.py startapp home
from django.urls import path, re_path, include urlpatterns = [ # ... path('user/', include('home.urls')), # ... ]
from django.urls import path, re_path urlpatterns = [ ]
from django.db import models class BaseModel(models.Model): orders = models.IntegerField(verbose_name='显示顺序') is_show = models.BooleanField(verbose_name="是否上架", default=False) is_delete = models.BooleanField(verbose_name="逻辑删除", default=False) class Meta: abstract = True
from django.db import models from utils.model import BaseModel class Banner(BaseModel): image = models.ImageField(upload_to='banner', verbose_name='轮播图', null=True, blank=True) name = models.CharField(max_length=150, verbose_name='轮播图名称') note = models.CharField(max_length=150, verbose_name='备注信息') link = models.CharField(max_length=150, verbose_name='轮播图广告地址') class Meta: db_table = 'luffy_banner' verbose_name = '轮播图' verbose_name_plural = verbose_name def __str__(self): return self.name
>: python manage.py makemigrations >: python manage.py migrate
INSTALLED_APPS = [ # ... 'rest_framework', 'home', ]
from rest_framework.serializers import ModelSerializer from . import models class BannerModelSerializer(ModelSerializer): class Meta: model = models.Banner fields = ('name', 'note', 'image', 'link')
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
from django.urls import path, re_path from . import views urlpatterns = [ path('banners/', views.BannerListAPIView.as_view()) ]
http://api.luffy.cn:8000/home/banner/
>: pip install django-cors-headers 插件参考地址:https://github.com/ottoyiu/django-cors-headers/
# 注册app INSTALLED_APPS = [ ... 'corsheaders' ] # 添加中间件 MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware' ] # 容许跨域源 CORS_ORIGIN_ALLOW_ALL = True
<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>
# >: pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
INSTALLED_APPS = [ # ... # xamin主体模块 'xadmin', # 渲染表格模块 'crispy_forms', # 为模型经过版本控制,能够回滚数据 'reversion', ]
python manage.py makemigrations python manage.py migrate
# xadmin的依赖 import xadmin xadmin.autodiscover() # xversion模块自动注册须要版本控制的 Model from xadmin.plugins import xversion xversion.register_models() urlpatterns = [ # ... path(r'xadmin/', xadmin.site.urls), ]
# 在项目根目录下的终端 python manage.py createsuperuser # 帐号密码设置:admin | Admin123
# home/adminx.py # xadmin全局配置 import xadmin from xadmin import views class GlobalSettings(object): """xadmin的全局配置""" site_title = "路飞学城" # 设置站点标题 site_footer = "路飞学城有限公司" # 设置站点的页脚 menu_style = "accordion" # 设置菜单折叠 xadmin.site.register(views.CommAdminView, GlobalSettings)
from . import models # 注册 xadmin.site.register(models.Banner)
# home/__init__.py default_app_config = "home.apps.HomeConfig" # home/apps.py from django.apps import AppConfig class HomeConfig(AppConfig): name = 'home' verbose_name = '个人首页'
""" 一、jwt认证:三段式的格式、每一段的内容、由后台签发到前台存储再到传给后台校验的认证流水线 头.载荷.签名:头、载荷(base64)| 签名(HS256) 头:{基础信息:公司项目组等信息,加密方式} 载荷:{核心信息:用户信息,过时时间} 签名:{安全信息:头+载荷+秘钥的md5加密结果} 服务器签发(login) -> web传给前端存储 -> 请求须要登陆的结果再携带给后台 -> 服务器校验(认证组件) => 权限管理 服务器压力小,集群部署更友善 二、drf-jwt插件: 三个接口:签发token、校验token、刷新token 自定义jwt插件的配置:在路由配置签发token的视图类接口、在全局或局部配置认证类 三、使用jwt插件完成多方式登陆 视图类:将请求数据交给序列化类完成校验,而后返回用户信息和token(从序列化对象中拿到) 序列化类:自定义反序列化字段,全局钩子校验数据获得user和token,并保存在序列化类对象中 token能够用jwt插件的rest_framework_jwt.serializers中 jwt_payload_handler,jwt_encode_handler 完成签发 四、自定义频率类完成视图类的频率限制 1)定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性 2)scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置 3)get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key """ """ 请求生命周期:as_view、dispatch 基础模块:请求、响应、解析、渲染、异常 核心模块:序列化、视图家族、三大认证、过滤 """ """ 路飞项目 先后台项目建立 => 项目目录规范(项目管理)=> 先后台跨域交互 => 主页页面逻辑接口与展现 => 课程页 => 用户的登陆注册 => 课程购买(支付模块) => 项目上线(线上测试支付回调) 开发技术点:git、redis、celery、短信接口、支付宝支付、阿里云服务器 """
""" 一、项目准备:pip换源、项目虚拟环境 二、后台项目建立,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、先后台跨越、xadmin 三、前台项目建立,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq 四、后台建立用户模块user:自定义User表、配置User表、路由分发 五、前台自定义主页组件和页头、轮播图小组件 六、核心:后台重构、后台配置、数据库 """
""" 一、整理今天所学知识点 二、创建先后台luffy项目:luffyapi、luffycity,并按照课堂要求完成先后台配置 三、打通先后台跨越交互,按照课堂内容完成主页设计 """
""" 一、后台设计轮播图表,肯定都要哪些字段(核心:必定要先本身设计一下,而后明天对比一下) 二、完善主页的前台设计,在后台设计一个Banner表,轮播图数据由后台提供给前台 """
""" 完成 协同开发 项目,帮助程序员整合代码 软件:SVN 、 GIT git:集群化、多分支 """
""" 什么是git:版本控制器 - 控制的对象是开发的项目代码 代码开发时间轴:需求1 > 版本库1 > 需求2 > 版本库2 > 版本库1 > 版本库2 """
# 1.下载对应版本:https://git-scm.com/download # 2.安装git:在选取安装路径的下一步选取 Use a TrueType font in all console windows 选项
""" >: cd 目标文件夹内部 >: git init """
""" >: 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 文件名 -- 添加指定文件 """
""" >: git reset HEAD . -- 撤销全部暂存区的提交 >: git reset 文件名 -- 撤销某一文件的暂存区提交 """
# git commit -m "版本描述信息"
""" 回滚暂存区已经提交到版本库的操做: 查看历史版本: >: 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个任意字符) 空文件夹不会被提交,空包会被提交 """
""" 1.注册码云帐号并登陆:https://gitee.com/ 2.建立仓库(课堂截图) 3.本地与服务器仓库创建链接 """ """ 1)本地配置线上的帐号与邮箱 >: git config --global user.name "doctor_owen" >: git config --global user.email "doctor_owen@163.com" 2)在本地初始化仓库(git init),并完成项目的初步搭建(项目架构)(通常都是项目负责人完成项目启动) # 这个过程就是git的基础部分的本地操做 3)采用 https协议 或 ssh协议 与远程git仓库通讯提交提交代码(通常都是项目负责人完成) i) https协议方式,无需配置,可是每次提交都有验证管理员帐号密码 >: git remote add origin https://gitee.com/doctor_owen/luffy.git # 配置远程源 >: git push -u origin master # 提交本地仓库到远程源 ii) ssh协议,须要配置,配置完成以后就能够正常提交代码 >: git remote add origin git@gitee.com:doctor_owen/luffy.git # 配置远程源 >: git push -u origin master # 提交本地仓库到远程源 iii)查看源及源连接信息 >: git remote >: git remote -v iv)删除源连接 >: git remote remove 源名字 注:origin远程源的源名,能够自定义;master是分支名,是默认的主分支 """
前提:本地仓库已经建立且初始化完毕(代码已经提交到本地版本库) 本机命令,添加远程源:git remote add origin ssh@*.git 采用ssh协议的remote源
官网:https://gitee.com/help/articles/4181#article-header0 本机命令,生成公钥:ssh-keygen -t rsa -C "*@*.com" 邮箱能够任意填写 本机命令,查看公钥:cat ~/.ssh/id_rsa.pub 码云线上添加公钥:项目仓库 => 管理 => 部署公钥管理 => 添加公钥 => 添加我的公钥
命令:git push origin master
""" 1)查看仓库已配置的远程源 >: git remote >: git remote -v 2)查看remote命令帮助文档 >: git remote -h 3)删除远程源 >: git remote remove 源名 eg: git remote remove origin 4)添加远程源 >: git remote add 源名 源地址 >: git remote add orgin git@*.git """
""" 1.建立分支 >: git branch 分支名 2.查看分支 >: git branch 3.切换分支 >: git checkout 分支名 4.建立并切换到分支 >: git checkout -b 分支名 5.删除分支 >: git branch -d 分支名 6.查看远程分支 >: git branch -a 7.合并分支 >: git merge 分支名 """
""" 一、项目准备:pip换源、项目虚拟环境 二、后台项目建立,项目目录重构,项目配置:环境变量、日志、异常、响应、数据库、媒体文件、国际化、先后台跨越、xadmin 环境变量:多app开发,将全部app放在apps中进行管理,全部apps文件夹要添加到环境变量 => app能够直接用名字注册 import logging logging.getLogger('django') 用户权限,只能给开发者提供本项目操做权限(及如下)的帐户 三、前台项目建立,项目目录重构,项目配置:全局样式与设置、axios、vue-cookies、element-ui、bs+jq 四、后台建立用户模块user:自定义User表、配置User表、路由分发 五、前台自定义主页组件和页头、轮播图小组件 六、核心:后台重构、后台配置、数据库 """
""" 版本控制器:操做开发阶段代码版本迭代的工具 为何要用版本控制器: 代码不一样阶段需求完成,该代码的时间节点应该被保留 项目通常都是进行团队开发,版本控制器能够帮助自动整合代码 问题:只能整合代码,可是不能避免冲突 => 冲突解决 使用:安装 => 本地使用(基础命令) => 线上使用(团队开发) => 冲突解决 """
""" 一、前台导航组件完成各页面跳转 二、先后台主页轮播图接口实现 三、版本控制器:GIT vs SVN(GIT的优点) 四、git本地基本操做:初始化仓库、状态查看、工做区 暂存区 版本库基本操做、git仓库过滤文件 五、线上仓库的建立与链接:源操做 六、团队开发、版本冲突解决 """
""" 一、整理今天所学知识点 二、完成luffy导航栏的封装、主页轮播图表的设计与接口的设计 三、安装git、属性git的基本操做、线上操做、团队开发、冲突解决 """
""" 一、安装文档测试git如何操做分支,完成多分支开发 二、根据菜鸟教育学校redis数据库 """
1.核心关键字:版本管理器、集群部署、多分支 操做开发阶段代码版本迭代的工具,每一个git应用均可以做为客户端或服务端,能够拥有多分支 2. git status 查看状态 git init 初始化仓库 git add . 添加全部文件到暂存区 git commit -m '' 暂存区信息完成提交到版本库 git remote add 源名 地址 添加仓库源 3. 同时开发相同文件 一个开发者先提交给服务器,第二个开发者拉取代码时,若是出现同一文件同一位置会出现冲突 冲突须要开发者们线下沟通解决,保证全部开发者开发进度正常进行
""" 一、git实际开发版本冲突解决: 更新代码到本地版本库 => 拉远程 => 出现冲突 => 解决冲突并更新本地版本库 => 拉远程 若是还出现冲突,就重复上方过程 若是成功,就提交代码到远程仓库 二、分支管理(branch):建立分支、切换分支、删除分支、合并分支(线上线下) 注:各个分支相互独立 三、多方式登陆的接口 四、手机号注册验证接口 五、短信服务的开通与代码的二次封装 六、发送短信接口 """
""" 一、整理今天所学知识点 二、熟练掌握git的线上线下全部操做,熟知git团队开发,解决合并冲突 三、开通我的短信服务帐号,完成短信功能的二次封装 四、完成多方式登陆、手机号验证、发送验证码接口 五、完成短信验证码登陆、短信验证码注册接口(必定认真用序列化类完成,有对比学习才有质的改变) """
""" 一、完成前台登陆页面(多方式登陆与短信登陆)、注册页面 二、并方式先后台登陆注册页面逻辑的交互、 三、根据10期预习视频学习redis数据库,在django中配置redis缓存数据库,存储短信验证码 四、预习接口缓存与celery异步任务框架 """
""" 一、git实际开发版本冲突解决: 更新代码到本地版本库 => 拉远程 => 出现冲突 => 解决冲突并更新本地版本库 => 拉远程 若是还出现冲突,就重复上方过程 若是成功,就提交代码到远程仓库 二、分支管理(branch):建立分支、切换分支、删除分支、合并分支(线上线下) 注:各个分支相互独立 三、多方式登陆的接口:username能够携带不一样信息,后台校验信息格式匹配登陆方式 四、手机号注册验证接口:提供手机、手机数据库验证 五、短信服务的开通与代码的二次封装:腾讯云短信服务、将配置与发送短信的函数封装成包 六、发送短信接口:手机号换验证码 - 本身的后台产生验证码,交给第三方发送,后台缓存验证码,返回前台发送成功信息 """
<template> <div class="login"> <div class="box"> <i class="el-icon-close" @click="close_login"></i> <div class="content"> <div class="nav"> <span :class="{active: login_method === 'is_pwd'}" @click="change_login_method('is_pwd')">密码登陆</span> <span :class="{active: login_method === 'is_sms'}" @click="change_login_method('is_sms')">短信登陆</span> </div> <el-form v-if="login_method === 'is_pwd'"> <el-input placeholder="用户名/手机号/邮箱" prefix-icon="el-icon-user" v-model="username" clearable> </el-input> <el-input placeholder="密码" prefix-icon="el-icon-key" v-model="password" clearable show-password> </el-input> <el-button type="primary">登陆</el-button> </el-form> <el-form v-if="login_method === 'is_sms'"> <el-input placeholder="手机号" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> </el-input> <el-input placeholder="验证码" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> <template slot="append"> <span class="sms" @click="send_sms">{{ sms_interval }}</span> </template> </el-input> <el-button type="primary">登陆</el-button> </el-form> <div class="foot"> <span @click="go_register">当即注册</span> </div> </div> </div> </div> </template> <script> export default { name: "Login", data() { return { username: '', password: '', mobile: '', sms: '', login_method: 'is_pwd', sms_interval: '获取验证码', is_send: false, } }, methods: { close_login() { this.$emit('close') }, go_register() { this.$emit('go') }, change_login_method(method) { this.login_method = method; }, check_mobile() { if (!this.mobile) return; if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) { this.$message({ message: '手机号有误', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); return false; } this.is_send = true; }, send_sms() { if (!this.is_send) return; this.is_send = false; let sms_interval_time = 60; this.sms_interval = "发送中..."; let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); this.sms_interval = "获取验证码"; this.is_send = true; // 从新回复点击发送功能的条件 } else { sms_interval_time -= 1; this.sms_interval = `${sms_interval_time}秒后再发`; } }, 1000); } } } </script> <style scoped> .login { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.3); } .box { width: 400px; height: 420px; background-color: white; border-radius: 10px; position: relative; top: calc(50vh - 210px); left: calc(50vw - 200px); } .el-icon-close { position: absolute; font-weight: bold; font-size: 20px; top: 10px; right: 10px; cursor: pointer; } .el-icon-close:hover { color: darkred; } .content { position: absolute; top: 40px; width: 280px; left: 60px; } .nav { font-size: 20px; height: 38px; border-bottom: 2px solid darkgrey; } .nav > span { margin: 0 20px 0 35px; color: darkgrey; user-select: none; cursor: pointer; padding-bottom: 10px; border-bottom: 2px solid darkgrey; } .nav > span.active { color: black; border-bottom: 3px solid black; padding-bottom: 9px; } .el-input, .el-button { margin-top: 40px; } .el-button { width: 100%; font-size: 18px; } .foot > span { float: right; margin-top: 20px; color: orange; cursor: pointer; } .sms { color: orange; cursor: pointer; display: inline-block; width: 70px; text-align: center; user-select: none; } </style>
<template> <div class="register"> <div class="box"> <i class="el-icon-close" @click="close_register"></i> <div class="content"> <div class="nav"> <span class="active">新用户注册</span> </div> <el-form> <el-input placeholder="手机号" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> </el-input> <el-input placeholder="密码" prefix-icon="el-icon-key" v-model="password" clearable show-password> </el-input> <el-input placeholder="验证码" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> <template slot="append"> <span class="sms" @click="send_sms">{{ sms_interval }}</span> </template> </el-input> <el-button type="primary">注册</el-button> </el-form> <div class="foot"> <span @click="go_login">当即登陆</span> </div> </div> </div> </div> </template> <script> export default { name: "Register", data() { return { mobile: '', password: '', sms: '', sms_interval: '获取验证码', is_send: false, } }, methods: { close_register() { this.$emit('close', false) }, go_login() { this.$emit('go') }, check_mobile() { if (!this.mobile) return; if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) { this.$message({ message: '手机号有误', type: 'warning', duration: 1000, onClose: () => { this.mobile = ''; } }); return false; } this.is_send = true; }, send_sms() { if (!this.is_send) return; this.is_send = false; let sms_interval_time = 60; this.sms_interval = "发送中..."; let timer = setInterval(() => { if (sms_interval_time <= 1) { clearInterval(timer); this.sms_interval = "获取验证码"; this.is_send = true; // 从新回复点击发送功能的条件 } else { sms_interval_time -= 1; this.sms_interval = `${sms_interval_time}秒后再发`; } }, 1000); } } } </script> <style scoped> .register { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.3); } .box { width: 400px; height: 480px; background-color: white; border-radius: 10px; position: relative; top: calc(50vh - 240px); left: calc(50vw - 200px); } .el-icon-close { position: absolute; font-weight: bold; font-size: 20px; top: 10px; right: 10px; cursor: pointer; } .el-icon-close:hover { color: darkred; } .content { position: absolute; top: 40px; width: 280px; left: 60px; } .nav { font-size: 20px; height: 38px; border-bottom: 2px solid darkgrey; } .nav > span { margin-left: 90px; color: darkgrey; user-select: none; cursor: pointer; padding-bottom: 10px; border-bottom: 2px solid darkgrey; } .nav > span.active { color: black; border-bottom: 3px solid black; padding-bottom: 9px; } .el-input, .el-button { margin-top: 40px; } .el-button { width: 100%; font-size: 18px; } .foot > span { float: right; margin-top: 20px; color: orange; cursor: pointer; } .sms { color: orange; cursor: pointer; display: inline-block; width: 70px; text-align: center; user-select: none; } </style>
<template> <div class="nav"> <span @click="put_login">登陆</span> <span @click="put_register">注册</span> <Login v-if="is_login" @close="close_login" @go="put_register" /> <Register v-if="is_register" @close="close_register" @go="put_login" /> </div> </template> <script> import Login from "./Login"; import Register from "./Register"; export default { name: "Nav", data() { return { is_login: false, is_register: false, } }, methods: { put_login() { this.is_login = true; this.is_register = false; }, put_register() { this.is_login = false; this.is_register = true; }, close_login() { this.is_login = false; }, close_register() { this.is_register = false; } }, components: { Login, Register } } </script> <style scoped> </style>
""" 一、手机验证码登陆接口 二、手机验证码注册接口 三、登陆注册模态页面布局 四、登陆注册5个接口先后台交互 五、前台登陆状态cookies缓存已经用户注销 六、接口缓存 """
""" 一、整理今天所学知识点 二、完成后台登陆请求的5个接口 三、依照课件,完成前台登陆注册页面的布局(Header组件能够参考项目代码) 四、完成前台登陆、注册、注销业务 五、掌握并完成接口缓存 六、安装并学习redis数据库,掌握redis数据库操做字符串的方法 """
""" 一、预习redis数据库操做五大数据类型,已经django中如何使用redis数据库 二、搞清楚celery框架的概念(celery是怎么工做的,解决什么问题的),建一个celery测试的demo项目,跑一下celery """
Celery 官网:http://www.celeryproject.org/css
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.htmlhtml
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/前端
Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(task result store)组成。vue
Celery自己不提供消息服务,可是能够方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等node
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。python
Task result store用来存储Worker执行的任务的结果,Celery支持以不一样方式存储任务的结果,包括AMQP, redis等mysql
异步任务:将耗时操做任务提交给Celery去异步执行,好比发送短信/邮件、消息推送、音视频处理等等jquery
定时任务:定时执行某件事情,好比天天数据统计linux
pip install celerywebpack
消息中间件:RabbitMQ/Redis
app=Celery('任务名', broker='xxx', backend='xxx')
project ├── celery_task # celery包 │ ├── __init__.py # 包文件 │ ├── celery.py # celery链接和配置相关文件,且名字必须交celery.py │ └── tasks.py # 全部任务函数 ├── add_task.py # 添加任务 └── get_result.py # 获取结果
# 1)建立app + 任务 # 2)启动celery(app)服务: # 非windows # 命令:celery worker -A celery_task -l info # windows: # pip3 install eventlet # celery worker -A celery_task -l info -P eventlet # 3)添加任务:手动添加,要自定义添加任务的脚本,右键执行脚本 # 4)获取结果:手动获取,要自定义获取任务的脚本,右键执行脚本 from celery import Celery broker = 'redis://127.0.0.1:6379/1' backend = 'redis://127.0.0.1:6379/2' app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
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
from celery_task import tasks # 添加当即执行任务 t1 = tasks.add.delay(10, 20) t2 = tasks.low.delay(100, 50) print(t1.id) # 添加延迟任务 from datetime import datetime, timedelta def eta_second(second): ctime = datetime.now() utc_ctime = datetime.utcfromtimestamp(ctime.timestamp()) time_delay = timedelta(seconds=second) return utc_ctime + time_delay tasks.low.apply_async(args=(200, 50), eta=eta_second(10))
from celery_task.celery import app from celery.result import AsyncResult id = '21325a40-9d32-44b5-a701-9a31cc3c74b5' if __name__ == '__main__': async = AsyncResult(id=id, app=app) if async.successful(): result = async.get() print(result) elif async.failed(): print('任务失败') elif async.status == 'PENDING': print('任务等待中被执行') elif async.status == 'RETRY': print('任务异常后正在重试') elif async.status == 'STARTED': print('任务已经开始被执行')
# 1)建立app + 任务 # 2)启动celery(app)服务: # 非windows # 命令:celery worker -A celery_task -l info # windows: # pip3 install eventlet # celery worker -A celery_task -l info -P eventlet # 3)添加任务:自动添加任务,因此要启动一个添加任务的服务 # 命令:celery beat -A celery_task -l info # 4)获取结果 from celery import Celery broker = 'redis://127.0.0.1:6379/1' backend = 'redis://127.0.0.1:6379/2' app = Celery(broker=broker, backend=backend, include=['celery_task.tasks']) # 时区 app.conf.timezone = 'Asia/Shanghai' # 是否使用UTC app.conf.enable_utc = False # 任务的定时配置 from datetime import timedelta from celery.schedules import crontab app.conf.beat_schedule = { 'low-task': { 'task': 'celery_task.tasks.low', 'schedule': timedelta(seconds=3), # 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点 'args': (300, 150), } }
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
from celery_task.celery import app from celery.result import AsyncResult id = '21325a40-9d32-44b5-a701-9a31cc3c74b5' if __name__ == '__main__': async = AsyncResult(id=id, app=app) if async.successful(): result = async.get() print(result) elif async.failed(): print('任务失败') elif async.status == 'PENDING': print('任务等待中被执行') elif async.status == 'RETRY': print('任务异常后正在重试') elif async.status == 'STARTED': print('任务已经开始被执行')
# 重点:要将 项目名.settings 所占的文件夹添加到环境变量 # import sys # sys.path.append(r'项目绝对路径') # 开启django支持 import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings') import django django.setup() # 1)建立app + 任务 # 2)启动celery(app)服务: # 非windows # 命令:celery worker -A celery_task -l info # windows: # pip3 install eventlet # celery worker -A celery_task -l info -P eventlet # 3)添加任务:自动添加任务,因此要启动一个添加任务的服务 # 命令:celery beat -A celery_task -l info # 4)获取结果 from celery import Celery broker = 'redis://127.0.0.1:6379/1' backend = 'redis://127.0.0.1:6379/2' app = Celery(broker=broker, backend=backend, include=['celery_task.tasks']) # 时区 app.conf.timezone = 'Asia/Shanghai' # 是否使用UTC app.conf.enable_utc = False # 任务的定时配置 from datetime import timedelta from celery.schedules import crontab app.conf.beat_schedule = { 'django-task': { 'task': 'celery_task.tasks.test_django_celery', 'schedule': timedelta(seconds=3), 'args': (), } }
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: 内存数据库(读写快)、非关系型(操做数据方便) mysql: 硬盘数据库(数据持久化)、关系型(操做数据间关系) 大量访问的临时数据,才有redis数据库更优 """
""" redis: 操做字符串、列表、字典、无序集合、有序集合 | 支持数据持久化(数据丢失能够找回、能够将数据同步给mysql) | 高并发支持 memcache: 操做字符串 | 不支持数据持久化 | 并发量小 """
""" 基础操做: 启动服务:redis-server & 链接数据库:redis-cli 链接指定数据库:redis-cli -h 127.0.0.1 -p 6379 -n 1 切换数据库:select 1 数据操做:字符串、列表、字典、无序集合、有序(排序)集合 有序集合:游戏排行榜 """
# 1.安装redis与可视化操做工具 # 2.在服务中管理redis服务器的开启关闭 # 3.命令行简单使用redis: -- redis-cli # 启动客户端 -- set key value # 设置值 -- get key # 取出值 # 4.redis支持:字符串、字典、列表、集合、有序集合 # https://www.runoob.com/redis/redis-tutorial.html # 5.特色:可持久化、单线程单进程并发
>: 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)
# 1.将缓存存储位置配置到redis中:settings.py CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} } } } # 2.操做cache模块直接操做缓存:views.py from django.core.cache import cache # 结合配置文件实现插拔式 # 存放token,能够直接设置过时时间 cache.set('token', 'header.payload.signature', 10) # 取出token token = cache.get('token')
""" 序列化: Serializer: 本身声明序列化反序列化字段、自定义钩子校验规则、重写create、update方法完成入库 ModelSerializer:model绑定、fields字段(extra_kwargs)、自定义钩子校验规则、继承create、update ListSerializer:提供群增群改(必须重写update)、在ModelSerializer中设置list_rerializer_class进行关联 认证组件: 校验前台携带的认证信息:没有,返回None(游客) | 认证失败,抛异常(非法用户403) | 认证成功,返回(user, token) 视图基类: APIView:继承View|as_view局部禁用csrf|dispatch封装request、三大认证、响应模块|类属性完成局部配置 GenericAPIView:继承APIView|三个属性三个方法 """
""" 一、redis数据库:优点、基础使用、五种数据类型的操做 二、redis数据库在Python中的使用、Django中的使用 三、celery异步任务框架: celery(broker、backend、tasks)封装配置、 添加 当即任务、延迟任务、周期任务(自动添加) worker服务的启动命令:celery worker -A celery_task -l info -P eventlet worker服务是用来执行任务的服务器 beat服务的启动命令:celery beat -A celery_task -l info beat服务是用来自动添加app.conf.beat_schedule配置中配置的任务的 """
""" 一、整理今天所学知识点 二、将项目的缓存配置成redis数据库,来关联缓存 三、利用celery框架完成异步定时更新轮播图接口缓存 """
""" 一、将发生短信接口,改写为让celery来管理 二、工具路飞官网,设计课程业务相关表 """
<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">筛 选:</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>
>: cnpm install vue-video-player
// 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);
""" enum.svg chapter-player.svg cart-yellow.svg """
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}}人在学 课程总时长:{{course_info.sections}}课时/{{course_info.pub_sections}}小时 难度:{{course_info.level_name}}</p> <div class="sale-time"> <p class="sale-type">价格 <span class="original_price">¥{{course_info.price}}</span></p> <p class="expire"></p> </div> <div class="buy"> <div class="buy-btn"> <button class="buy-now">当即购买</button> <button class="free">免费试学</button> </div> <!--<div class="add-cart" @click="add_cart(course_info.id)">--> <!--<img src="@/assets/img/cart-yellow.svg" alt="">加入购物车--> <!--</div>--> </div> </div> </div> <div class="course-tab"> <ul class="tab-list"> <li :class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍</li> <li :class="tabIndex==2?'active':''" @click="tabIndex=2">课程章节 <span :class="tabIndex!=2?'free':''">(试学)</span> </li> <li :class="tabIndex==3?'active':''" @click="tabIndex=3">用户评论</li> <li :class="tabIndex==4?'active':''" @click="tabIndex=4">常见问题</li> </ul> </div> <div class="course-content"> <div class="course-tab-list"> <div class="tab-item" v-if="tabIndex==1"> <div class="course-brief" v-html="course_info.brief_text"></div> </div> <div class="tab-item" v-if="tabIndex==2"> <div class="tab-item-title"> <p class="chapter">课程章节</p> <p class="chapter-length">共{{course_chapters.length}}章 {{course_info.sections}}个课时</p> </div> <div class="chapter-item" v-for="chapter in course_chapters" :key="chapter.name"> <p class="chapter-title"><img src="@/assets/img/enum.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}} </p> <ul class="section-list"> <li class="section-item" v-for="section in chapter.coursesections" :key="section.name"> <p class="name"><span class="index">{{chapter.chapter}}-{{section.orders}}</span> {{section.name}}<span class="free" v-if="section.free_trail">免费</span></p> <p class="time">{{section.duration}} <img src="@/assets/img/chapter-player.svg"></p> <button class="try" v-if="section.free_trail">当即试学</button> <button class="try" v-else>当即购买</button> </li> </ul> </div> </div> <div class="tab-item" v-if="tabIndex==3"> 用户评论 </div> <div class="tab-item" v-if="tabIndex==4"> 常见问题 </div> </div> <div class="course-side"> <div class="teacher-info"> <h4 class="side-title"><span>授课老师</span></h4> <div class="teacher-content"> <div class="cont1"> <img :src="course_info.teacher.image"> <div class="name"> <p class="teacher-name">{{course_info.teacher.name}} {{course_info.teacher.title}}</p> <p class="teacher-title">{{course_info.teacher.signature}}</p> </div> </div> <p class="narrative">{{course_info.teacher.brief}}</p> </div> </div> </div> </div> </div> <Footer/> </div> </template> <script> import Header from "@/components/Header" import Footer from "@/components/Footer" // 加载组件 import {videoPlayer} from 'vue-video-player'; export default { name: "Detail", data() { return { tabIndex: 2, // 当前选项卡显示的下标 course_id: 0, // 当前课程信息的ID course_info: { teacher: {}, }, // 课程信息 course_chapters: [], // 课程的章节课时列表 playerOptions: { aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该表明一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") sources: [{ // 播放资源和资源格式 type: "video/mp4", src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填) }], } } }, created() { this.get_course_id(); this.get_course_data(); this.get_chapter(); }, methods: { onPlayerPlay() { // 当视频播放时,执行的方法 }, onPlayerPause() { // 当视频暂停播放时,执行的方法 }, get_course_id() { // 获取地址栏上面的课程ID this.course_id = this.$route.params.pk; if (this.course_id < 1) { let _this = this; _this.$alert("对不起,当前视频不存在!", "警告", { callback() { _this.$router.go(-1); } }); } }, get_course_data() { // ajax请求课程信息 this.$axios.get(`${this.$settings.base_url}/course/${this.course_id}/`).then(response => { // window.console.log(response.data); this.course_info = response.data; }).catch(() => { this.$message({ message: "对不起,访问页面出错!请联系客服工做人员!" }); }) }, get_chapter() { // 获取当前课程对应的章节课时信息 // http://127.0.0.1:8000/course/chapters/?course=(pk) this.$axios.get(`${this.$settings.base_url}/course/chapters/`, { params: { "course": this.course_id, } }).then(response => { this.course_chapters = response.data; }).catch(error => { window.console.log(error.response); }) }, }, components: { Header, Footer, videoPlayer, // 注册组件 } } </script> <style scoped> .main { background: #fff; padding-top: 30px; } .course-info { width: 1200px; margin: 0 auto; overflow: hidden; } .wrap-left { float: left; width: 690px; height: 388px; background-color: #000; } .wrap-right { float: left; position: relative; height: 388px; } .course-name { font-size: 20px; color: #333; padding: 10px 23px; letter-spacing: .45px; } .data { padding-left: 23px; padding-right: 23px; padding-bottom: 16px; font-size: 14px; color: #9b9b9b; } .sale-time { width: 464px; background: #fa6240; font-size: 14px; color: #4a4a4a; padding: 10px 23px; overflow: hidden; } .sale-type { font-size: 16px; color: #fff; letter-spacing: .36px; float: left; } .sale-time .expire { font-size: 14px; color: #fff; float: right; } .sale-time .expire .second { width: 24px; display: inline-block; background: #fafafa; color: #5e5e5e; padding: 6px 0; text-align: center; } .course-price { background: #fff; font-size: 14px; color: #4a4a4a; padding: 5px 23px; } .discount { font-size: 26px; color: #fa6240; margin-left: 10px; display: inline-block; margin-bottom: -5px; } .original { font-size: 14px; color: #9b9b9b; margin-left: 10px; text-decoration: line-through; } .buy { width: 464px; padding: 0px 23px; position: absolute; left: 0; bottom: 20px; overflow: hidden; } .buy .buy-btn { float: left; } .buy .buy-now { width: 125px; height: 40px; border: 0; background: #ffc210; border-radius: 4px; color: #fff; cursor: pointer; margin-right: 15px; outline: none; } .buy .free { width: 125px; height: 40px; border-radius: 4px; cursor: pointer; margin-right: 15px; background: #fff; color: #ffc210; border: 1px solid #ffc210; } .add-cart { float: right; font-size: 14px; color: #ffc210; text-align: center; cursor: pointer; margin-top: 10px; } .add-cart img { width: 20px; height: 18px; margin-right: 7px; vertical-align: middle; } .course-tab { width: 100%; background: #fff; margin-bottom: 30px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course-tab .tab-list { width: 1200px; margin: auto; color: #4a4a4a; overflow: hidden; } .tab-list li { float: left; margin-right: 15px; padding: 26px 20px 16px; font-size: 17px; cursor: pointer; } .tab-list .active { color: #ffc210; border-bottom: 2px solid #ffc210; } .tab-list .free { color: #fb7c55; } .course-content { width: 1200px; margin: 0 auto; background: #FAFAFA; overflow: hidden; padding-bottom: 40px; } .course-tab-list { width: 880px; height: auto; padding: 20px; background: #fff; float: left; box-sizing: border-box; overflow: hidden; position: relative; box-shadow: 0 2px 4px 0 #f0f0f0; } .tab-item { width: 880px; background: #fff; padding-bottom: 20px; box-shadow: 0 2px 4px 0 #f0f0f0; } .tab-item-title { justify-content: space-between; padding: 25px 20px 11px; border-radius: 4px; margin-bottom: 20px; border-bottom: 1px solid #333; border-bottom-color: rgba(51, 51, 51, .05); overflow: hidden; } .chapter { font-size: 17px; color: #4a4a4a; float: left; } .chapter-length { float: right; font-size: 14px; color: #9b9b9b; letter-spacing: .19px; } .chapter-title { font-size: 16px; color: #4a4a4a; letter-spacing: .26px; padding: 12px; background: #eee; border-radius: 2px; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; } .chapter-title img { width: 18px; height: 18px; margin-right: 7px; vertical-align: middle; } .section-list { padding: 0 20px; } .section-list .section-item { padding: 15px 20px 15px 36px; cursor: pointer; justify-content: space-between; position: relative; overflow: hidden; } .section-item .name { font-size: 14px; color: #666; float: left; } .section-item .index { margin-right: 5px; } .section-item .free { font-size: 12px; color: #fff; letter-spacing: .19px; background: #ffc210; border-radius: 100px; padding: 1px 9px; margin-left: 10px; } .section-item .time { font-size: 14px; color: #666; letter-spacing: .23px; opacity: 1; transition: all .15s ease-in-out; float: right; } .section-item .time img { width: 18px; height: 18px; margin-left: 15px; vertical-align: text-bottom; } .section-item .try { width: 86px; height: 28px; background: #ffc210; border-radius: 4px; font-size: 14px; color: #fff; position: absolute; right: 20px; top: 10px; opacity: 0; transition: all .2s ease-in-out; cursor: pointer; outline: none; border: none; } .section-item:hover { background: #fcf7ef; box-shadow: 0 0 0 0 #f3f3f3; } .section-item:hover .name { color: #333; } .section-item:hover .try { opacity: 1; } .course-side { width: 300px; height: auto; margin-left: 20px; float: right; } .teacher-info { background: #fff; margin-bottom: 20px; box-shadow: 0 2px 4px 0 #f0f0f0; } .side-title { font-weight: normal; font-size: 17px; color: #4a4a4a; padding: 18px 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51, 51, 51, .05); } .side-title span { display: inline-block; border-left: 2px solid #ffc210; padding-left: 12px; } .teacher-content { padding: 30px 20px; box-sizing: border-box; } .teacher-content .cont1 { margin-bottom: 12px; overflow: hidden; } .teacher-content .cont1 img { width: 54px; height: 54px; margin-right: 12px; float: left; } .teacher-content .cont1 .name { float: right; } .teacher-content .cont1 .teacher-name { width: 188px; font-size: 16px; color: #4a4a4a; padding-bottom: 4px; } .teacher-content .cont1 .teacher-title { width: 188px; font-size: 13px; color: #9b9b9b; white-space: nowrap; } .teacher-content .narrative { font-size: 14px; color: #666; line-height: 24px; } </style>
re_path("(?P<pk>\d+)/", views.CourseRetrieveAPIView.as_view()), path("chapters/", views.CourseChapterListAPIView.as_view()),
from .models import Course from .models import CourseChapter from rest_framework.generics import ListAPIView from rest_framework.generics import RetrieveAPIView from . import serializers from django_filters.rest_framework.backends import DjangoFilterBackend class CourseRetrieveAPIView(RetrieveAPIView): """课程详情信息""" queryset = Course.objects.filter(is_delete=False, is_show=True) serializer_class = serializers.CourseRetrieveModelSSerializer class CourseChapterListAPIView(ListAPIView): """课程详情信息""" queryset = CourseChapter.objects.filter(is_delete=False, is_show=True).order_by("chapter") serializer_class = serializers.CourseChapterModelSerializer filter_backends = [DjangoFilterBackend] filter_fields = ['course', ]
from . import models from rest_framework.serializers import ModelSerializer class CourseRetrieveModelSSerializer(ModelSerializer): # 课程详情的序列化器 teacher = TeacherSerializer() class Meta: model = models.Course fields = ["id", "name", "course_img", "students", "sections", "pub_sections", "price", "teacher", "level_name"] class CourseSessionModelSerializer(ModelSerializer): class Meta: model = models.CourseSection fields = ["id", "name", "duration", "free_trail", "orders"] class CourseChapterModelSerializer(ModelSerializer): coursesections = CourseSessionModelSerializer(many=True) class Meta: model = models.CourseChapter fields = ["chapter", "name", "summary", "coursesections"]
class Course(BaseModel): # ... @property def level_name(self): # 难度名 return self.level_choices[self.level][1]
# 一、在沙箱环境下实名认证:https://openhome.alipay.com/platform/appDaily.htm?tab=info # 二、电脑网站支付API:https://docs.open.alipay.com/270/105898/ # 三、完成RSA密钥生成:https://docs.open.alipay.com/291/105971 # 四、在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容 # 五、Python支付宝开源框架:https://github.com/fzlee/alipay # >: pip install python-alipay-sdk --upgrade # 七、公钥私钥设置 """ # alipay_public_key.pem -----BEGIN PUBLIC KEY----- 支付宝公钥 -----END PUBLIC KEY----- # app_private_key.pem -----BEGIN RSA PRIVATE KEY----- 用户私钥 -----END RSA PRIVATE KEY----- """ # 八、支付宝连接 """ 开发:https://openapi.alipay.com/gateway.do 沙箱:https://openapi.alipaydev.com/gateway.do """
>: pip install python-alipay-sdk --upgrade
libs ├── iPay # aliapy二次封装包 │ ├── __init__.py # 包文件 │ ├── keys # 密钥文件夹 │ │ ├── alipay_public_key.pem # 支付宝公钥 │ │ └── app_private_key.pem # 应用私钥 └── └── settings.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
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 )
-----BEGIN PUBLIC KEY----- 支付宝公钥 -----END PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY----- 应用私钥 -----END RSA PRIVATE KEY-----
# 上线后必须换成官网地址 # 同步回调的接口(get),先后台分离时通常设置前台页面url RETURN_URL = 'http://127.0.0.1:8080/pay/success' # 异步回调的接口(post),必定设置为后台服务器接口 NOTIFY_URL = 'http://127.0.0.1:8000/order/success/'
""" 订单:订单号、流水号、价格、用户 订单详情(自定义关系表):订单、课程 """ from django.db import models from utils.model import BaseModel from user.models import User from course.models import Course class Order(BaseModel): """订单模型""" status_choices = ( (0, '未支付'), (1, '已支付'), (2, '已取消'), (3, '超时取消'), ) pay_choices = ( (1, '支付宝'), (2, '微信支付'), ) subject = models.CharField(max_length=150, verbose_name="订单标题") total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0) out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True) trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号") order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态") pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式") pay_time = models.DateTimeField(null=True, verbose_name="支付时间") user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户") # 多余字段 orders = models.IntegerField(verbose_name='显示顺序', default=0) class Meta: db_table = "luffy_order" verbose_name = "订单记录" verbose_name_plural = "订单记录" def __str__(self): return "%s - ¥%s" % (self.subject, self.total_amount) @property def courses(self): data_list = [] for item in self.order_courses.all(): data_list.append({ "id": item.id, "course_name": item.course.name, "real_price": item.real_price, }) return data_list class OrderDetail(BaseModel): """订单详情""" order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单") course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价") real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价") class Meta: db_table = "luffy_order_detail" verbose_name = "订单详情" verbose_name_plural = "订单详情" def __str__(self): return "%s订单(%s)" % (self.course.name, self.order.order_number)
from django.urls import path from . import views urlpatterns = [ path('pay/', views.PayAPIView.as_view()), path('success/', views.SuccessAPIView.as_view()), ]
from rest_framework import serializers from . import models class OrderModelSerializer(serializers.ModelSerializer): class Meta: model = models.Order fields = ('subject', 'total_amount', 'out_trade_no', 'pay_type', 'user') extra_kwargs = { 'pay_type': { 'required': True }, 'total_amount': { 'required': True }, }
import time from rest_framework.views import APIView from utils.response import APIResponse from libs.iPay import alipay from . import authentications, serializers from rest_framework.permissions import IsAuthenticated from django.conf import settings # 获取前台 商品名、价格,产生 订单、支付连接 class PayAPIView(APIView): authentication_classes = [authentications.JWTAuthentication] permission_classes = [IsAuthenticated] def post(self, request, *args, **kwargs): # 前台提供:商品名、总价、支付方式 request_data = request.data # 后台产生:订单号、用户 out_trade_no = '%d' % time.time() * 2 request_data['out_trade_no'] = out_trade_no request_data['user'] = request.user.id # 反序列化数据,用于订单生成前的校验 order_ser = serializers.OrderModelSerializer(data=request_data) if order_ser.is_valid(): # 生成订单,订单默认状态为:未支付 order = order_ser.save() # 支付连接的参数 order_string = alipay.api_alipay_trade_page_pay( subject=order.subject, out_trade_no=order.out_trade_no, total_amount='%.2f' % order.total_amount, return_url=settings.RETURN_URL, notify_url=settings.NOTIFY_URL ) # 造成支付连接:alipay._gateway根据字符环境DEBUG配置信息,决定是沙箱仍是真实支付环境 pay_url = '%s?%s' % (alipay._gateway, order_string) return APIResponse(0, 'ok', pay_url=pay_url) return APIResponse(1, 'no ok', results=order_ser.errors)
{ path: '/pay/success', name: 'pay-success', component: PaySuccess },
<template> <div class="pay-success"> <Header/> <div class="main"> <div class="title"> <div class="success-tips"> <p class="tips">您已成功购买 1 门课程!</p> </div> </div> <div class="order-info"> <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p> <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p> <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p> </div> <div class="study"> <span>当即学习</span> </div> </div> <Footer/> </div> </template> <script> import Header from "@/components/Header" import Footer from "@/components/Footer" export default { name: "Success", data() { return { result: {}, }; }, created() { // 判断登陆状态 let token = this.$cookies.get('token'); if (!token) { this.$message.error('非法请求'); this.$router.go(-1) } localStorage.this_nav = '/'; if (!location.search.length) return; let params = location.search.substring(1); let items = params.length ? params.split('&') : []; //逐个将每一项添加到args对象中 for (let i = 0; i < items.length; i++) { let k_v = items[i].split('='); //解码操做,由于查询字符串通过编码的 let k = decodeURIComponent(k_v[0]); let v = decodeURIComponent(k_v[1]); this.result[k] = v; // this.result[k_v[0]] = k_v[1]; } // console.log(this.result); // 把地址栏上面的支付结果,转发给后端 this.$axios({ url: this.$settings.base_url + '/order/success/' + location.search, method: 'patch', headers: { Authorization: token } }).then(response => { console.log(response.data); }).catch(() => { console.log('支付结果同步失败'); }) }, components: { Header, Footer, } } </script> <style scoped> .main { padding: 60px 0; margin: 0 auto; width: 1200px; background: #fff; } .main .title { display: flex; -ms-flex-align: center; align-items: center; padding: 25px 40px; border-bottom: 1px solid #f2f2f2; } .main .title .success-tips { box-sizing: border-box; } .title img { vertical-align: middle; width: 60px; height: 60px; margin-right: 40px; } .title .success-tips { box-sizing: border-box; } .title .tips { font-size: 26px; color: #000; } .info span { color: #ec6730; } .order-info { padding: 25px 48px; padding-bottom: 15px; border-bottom: 1px solid #f2f2f2; } .order-info p { display: -ms-flexbox; display: flex; margin-bottom: 10px; font-size: 16px; } .order-info p b { font-weight: 400; color: #9d9d9d; white-space: nowrap; } .study { padding: 25px 40px; } .study span { display: block; width: 140px; height: 42px; text-align: center; line-height: 42px; cursor: pointer; background: #ffc210; border-radius: 6px; font-size: 16px; color: #fff; } </style>
from . import models from utils.logging import logger from rest_framework.response import Response class SuccessAPIView(APIView): # 不能认证,别人支付宝异步回调就进不来了 # authentication_classes = [authentications.JWTAuthentication] # permission_classes = [IsAuthenticated] def patch(self, request, *args, **kwargs): # 默认是QueryDict类型,不能使用pop方法 request_data = request.query_params.dict() # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验 sign = request_data.pop('sign') result = alipay.verify(request_data, sign) if result: # 同步回调:修改订单状态 try: out_trade_no = request_data.get('out_trade_no') order = models.Order.objects.get(out_trade_no=out_trade_no) if order.order_status != 1: order.order_status = 1 order.save() except: pass return APIResponse(0, '支付成功') return APIResponse(1, '支付失败') # 支付宝异步回调 def post(self, request, *args, **kwargs): # 默认是QueryDict类型,不能使用pop方法 request_data = request.data.dict() # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验 sign = request_data.pop('sign') result = alipay.verify(request_data, sign) # 异步回调:修改订单状态 if result and request_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED" ): out_trade_no = request_data.get('out_trade_no') logger.critical('%s支付成功' % out_trade_no) try: order = models.Order.objects.get(out_trade_no=out_trade_no) if order.order_status != 1: order.order_status = 1 order.save() except: pass # 支付宝八次异步通知,订单成功必定要返回 success return Response('success') return Response('failed')
# 购买阿里云服务器 # 短时间或是测试使用,建立 按量收费 服务器,能够随时删除,删除后再也不计费,但要保证帐户余额100元以上
1)帐号 >: ssh root@39.98.144.221 2)密码 >: ********
1)如下全部的服务器命令都可以在管理员权限下执行 >: sudo 命令
1)编辑配置文件 >: vim ~/.bash_profile 2)将原来内容所有删除掉 >: ggdG 3)进入编辑状态:填入下方两行 >: i export PATH=$PATH:$HOME/bin PS1='Path:\w\n>:' 4)退出编辑状态 >: esc 5)保存修改并退出 >: :wq 6)生效配置 >: source ~/.bash_profile
>: yum update -y
>: yum -y groupinstall "Development tools" >: yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel
1)前往用户根目录 >: cd ~ 2)下载mysql57 >: wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm 也能够本地上传,这条命令要在本地终端上执行 >: scp -r C:\Users\dell\Desktop\pkg\mysql57-community-release-el7-10.noarch.rpm root@39.98.144.221:~ 3)安装mysql57 >: yum -y install mysql57-community-release-el7-10.noarch.rpm >: yum -y install mysql-community-server 4)启动mysql57并查看启动状态 >: systemctl start mysqld.service >: systemctl status mysqld.service 5)查看默认密码并登陆 >: grep "password" /var/log/mysqld.log >: mysql -uroot -p 6)修改密码 >: ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password'; >: ALTER USER 'root'@'localhost' IDENTIFIED BY 'Owen1234?';
1)前往用户根目录 >: cd ~ 2)下载redis-5.0.5 >: wget http://download.redis.io/releases/redis-5.0.5.tar.gz >: scp -r C:\Users\dell\Desktop\pkg\redis-5.0.5.tar.gz root@39.98.144.221:~ 3)解压安装包 >: tar -xf redis-5.0.5.tar.gz 4)进入目标文件 >: cd redis-5.0.5 5)编译环境 >: make 6)复制环境到指定路径完成安装 >: cp -r ~/redis-5.0.5 /usr/local/redis 7)配置redis能够后台启动:修改下方内容 >: vim /usr/local/redis/redis.conf daemonize yes 8)完成配置修改 >: esc >: :wq 9)创建软链接 >: ln -s /usr/local/redis/src/redis-server /usr/bin/redis-server >: ln -s /usr/local/redis/src/redis-cli /usr/bin/redis-cli 10)后台运行redis >: redis-server & ctrl + c 11)测试redis环境 >: redis-cli ctrl + c 12)关闭redis服务 >: pkill -f redis -9
1)前往用户根目录 >: cd ~ 2)下载 或 上传 Python3.6.7 >: wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz >: scp -r 本地Python-3.6.7.tar.xz ssh root@39.98.144.221:服务器路径 >: scp -r C:\Users\dell\Desktop\pkg\Python-3.6.7.tar.xz ssh root@39.98.144.221:~ 3)解压安装包 >: tar -xf Python-3.6.7.tar.xz 4)进入目标文件 >: cd Python-3.6.7 5)配置安装路径:/usr/local/python3 >: ./configure --prefix=/usr/local/python3 6)编译并安装 >: make && sudo make install 7)创建软链接:终端命令 python3,pip3 >: ln -s /usr/local/python3/bin/python3.6 /usr/bin/python3 >: ln -s /usr/local/python3/bin/pip3.6 /usr/bin/pip3 8)删除安装包与文件: >: rm -rf Python-3.6.7 >: rm -rf Python-3.6.7.tar.xz
1)建立pip配置路径 >: mkdir ~/.pip 2)进入目录编辑配置文件:填入下方内容 cd ~/.pip && vim pip.conf [global] index-url = http://pypi.douban.com/simple [install] use-mirrors =true mirrors =http://pypi.douban.com/simple/ trusted-host =pypi.douban.com
1)在真实环境下安装 pip3 install uwsgi 2)创建软链接 ln -s /usr/local/python3/bin/uwsgi /usr/bin/uwsgi
1)安装依赖 >: pip3 install virtualenv >: pip3 install virtualenvwrapper 2)创建虚拟环境软链接 >: ln -s /usr/local/python3/bin/virtualenv /usr/bin/virtualenv 3)配置虚拟环境:填入下方内容 >: vim ~/.bash_profile VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 source /usr/local/python3/bin/virtualenvwrapper.sh 4)退出编辑状态 >: esc 5)保存修改并退出 >: :wq 6)更新配置文件内容 >: source ~/.bash_profile 7)虚拟环境默认根目录:~/.virtualenvs
1)建立虚拟环境 >: mkvirtualenv test_venv 2)安装依赖 >: pip install django 3)前往目标目录,建立项目工做目录,再进入 >: cd /home >: mkdir project >: cd project 4)建立Django项目,并进入 >: django-admin startproject test_site >: cd test_site 5)完成项目配置:修改下方几行内容 >: vim /home/project/test_site/test_site/settings.py ALLOWED_HOSTS = ['*'] #DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } #} 6)跑原生服务 >: python3 manage.py runserver 0.0.0.0:80
1)前往用户根目录 >: cd ~ 2)下载nginx1.13.7 >: wget http://nginx.org/download/nginx-1.13.7.tar.gz 3)解压安装包 >: tar -xf nginx-1.13.7.tar.gz 4)进入目标文件 >: cd nginx-1.13.7 5)配置安装路径:/usr/local/nginx >: ./configure --prefix=/usr/local/nginx 6)编译并安装 >: make && sudo make install 7)创建软链接:终端命令 nginx >: ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx 8)删除安装包与文件: >: rm -rf nginx-1.13.7 >: rm -rf nginx-1.13.7.tar.xz 9)测试Nginx环境,服务器运行nginx,本地访问服务器ip >: nginx >: 服务器绑定的域名 或 ip:80
1)启动 >: nginx 2)关闭nginx >: nginx -s stop 3)重启nginx >: nginx -s reload 4)查看端口,强行关闭 >: ps -aux|grep nginx >: kill <pid:进程编号>
1)在项目的虚拟环境安装uwsgi >: workon test_venv >: pip install uwsgi 2)项目根目录配置uwsgi:填入下方内容 >: vim /home/project/test_site/test_site.xml <uwsgi> <socket>127.0.0.1:8808</socket> <!-- 内部端口,自定义 --> <chdir>/home/project/test_site/</chdir> <!-- 项目路径 --> <module>test_site.wsgi</module> <!-- test_site为wsgi.py所在目录名--> <processes>4</processes> <!-- 进程数 --> <daemonize>uwsgi.log</daemonize> <!-- 日志文件 --> </uwsgi> 3)完成项目配置:修改下方几行内容 >: vim /home/project/test_site/test_site/settings.py DEBUG = False ALLOWED_HOSTS = ['*'] 4)去向Nginx配置目录,备份配置,彻底更新配置:填入下方内容 >: cd /usr/local/nginx/conf >: cp nginx.conf nginx.conf.bak >: vim nginx.conf >: ggdG >: i events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; server { listen 8000; server_name 127.0.0.1; # 改成本身的域名,没域名修改成127.0.0.1:80 charset utf-8; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8808; # 端口要和uwsgi里配置的同样 uwsgi_param UWSGI_SCRIPT test_site.wsgi; #wsgi.py所在的目录名+.wsgi uwsgi_param UWSGI_CHDIR /home/project/test_site/; # 项目路径 } } } 5)启动uwsgi >: uwsgi -x /home/project/test_site/test_site.xml 6)启动nginx >: nginx 7)浏览器测试:http://39.98.144.221/admin 8)关闭uwsgi全部进程 >: pkill -f uwsgi -9
base_url: 'http://39.98.144.221:8000', // 设置公网ip
1)本地项目打包,前往luffycity项目目录下 >: cnpm run build 2)上传 >: scp -r dist root@39.98.144.221:~ 3)移动并重命名 mv ~/dist /home/html 4)去向Nginx配置目录,备份配置,彻底更新配置:填入下方内容 >: cd /usr/local/nginx/conf >: cp nginx.conf nginx.conf.bak >: vim nginx.conf >: ggdG >: i events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; server { listen 80; server_name 127.0.0.1; # 改成本身的域名,没域名修改成127.0.0.1:80 charset utf-8; location / { root /home/html; # html访问路径 index index.html; # html文件名称 try_files $uri $uri/ /index.html; # 解决单页面应用刷新404问题 } } }
prod.py:上线的配置文件,内容拷贝dev.py,前身就是settings.py
1)须要作上线修改的内容 DEBUG = False ALLOWED_HOSTS = [ '39.98.144.221' # 公网ip地址 ] CORS_ORIGIN_ALLOW_ALL = True # 容许全部跨域 CORS_ORIGIN_WHITELIST = [ ]
wsgi.py 和 manage.py
1)须要作上线修改的内容 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod')
1)在项目的虚拟环境安装uwsgi >: mkvirtualenv luffy >: workon luffy # 走下方 pip导入导出依赖 说明,将本地的环境依赖同步到服务器环境中 >: pip install uwsgi 2)项目根目录配置uwsgi:填入下方内容 >: mkdir /home/project # 注:将后台项目移至到/home/project,能够上传,也能够git,项目设置公开(私密须要配置ssl) >: cd /home/project && git clone https://gitee.com/doctor_owen/luffyapi.git >: vim /home/project/luffyapi/luffyapi.xml <uwsgi> <socket>127.0.0.1:8808</socket> <!-- 内部端口,自定义 --> <chdir>/home/project/luffyapi/</chdir> <!-- 项目路径 --> <module>luffyapi.wsgi</module> <!-- luffyapi为wsgi.py所在目录名--> <processes>4</processes> <!-- 进程数 --> <daemonize>uwsgi.log</daemonize> <!-- 日志文件 --> </uwsgi> #### 3)配置上线项目的settings:见后台项目部署准备视频 4)去向Nginx配置目录,备份配置,彻底更新配置:填入下方内容 >: vim /usr/local/nginx/conf/nginx.conf 5)在原来基础上添加一个server events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; server { listen 8000; server_name 127.0.0.1; # 改成本身的域名,没域名修改成127.0.0.1:80 charset utf-8; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8808; # 端口要和uwsgi里配置的同样 uwsgi_param UWSGI_SCRIPT luffyapi.wsgi; #wsgi.py所在的目录名+.wsgi uwsgi_param UWSGI_CHDIR /home/project/luffyapi/; # 项目路径 } } } 见下方配置:pip环境 + 数据库设置 + django2.0源码 5)启动uwsgi >: uwsgi -x /home/project/luffyapi/luffyapi.xml 6)启动nginx >: nginx -s stop >: nginx >: nginx -s reload 7)浏览器测试:http://39.98.144.221:8000/xadmin 8)关闭uwsgi全部进程 >: pkill -f uwsgi -9
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
1.管理员链接数据库 >: mysql -uroot -pOwen1234? 2.建立数据库 >: create database luffy default charset=utf8; # 3.设置权限帐号密码 # 拥有公网或局域网,其余主机连mysql >: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?'; # 要是本机连mysql连不上,再增长localhost域,本机就能够登陆了 >: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?'; # 设置完有权限限制的帐号后必定要刷新权限 >: flush privileges; 4.退出mysql quit 5.修改 prod.py | manage.py >: vim /home/project/luffyapi/luffyapi/settings/prod.py "PASSWORD": "Luffy123?" >: vim /home/project/luffyapi/manage.py os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod') 6.源码修改 >: vim /root/.virtualenvs/luffy/lib/python3.6/site-packages/django/db/backends/mysql/base.py # 36,37行注释 >: vim /root/.virtualenvs/luffy/lib/python3.6/site-packages/django/db/backends/mysql/operations.py # 146行添加 query = query.encode() 7.数据库迁移 >: cd /home/project/luffyapi/ >: python manage.py makemigrations >: python manage.py migrate 8.建立超级用户 >: python manage.py createsuperuser # 帐号密码:admin|admin
# >: 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
>: vim /usr/local/nginx/conf/nginx.conf events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; server { listen 8000; server_name 127.0.0.1; # 改成本身的域名,没域名修改成127.0.0.1:80 charset utf-8; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8808; # 端口要和uwsgi里配置的同样 uwsgi_param UWSGI_SCRIPT luffyapi.wsgi; #wsgi.py所在的目录名+.wsgi uwsgi_param UWSGI_CHDIR /home/project/luffyapi/; # 项目路径 } # 新增的配置静态文件 location /static { alias /home/project/luffyapi/luffyapi/static; } } server { listen 80; server_name 127.0.0.1; # 改成本身的域名,没域名修改成127.0.0.1:80 charset utf-8; location / { root /home/html; # html访问路径 index index.html; # html文件名称 try_files $uri $uri/ /index.html; # 解决单页面应用刷新404问题 } } } >: esc >: :wq
>: pkill -f uwsgi -9 >: uwsgi -x /home/project/luffyapi/luffyapi.xml >: nginx -s reload
# 一、真实环境和虚拟环境都要安装uwsgi,将真实环境下的uwsgi创建软链接 # 二、redis服务必定要后台启动:redis-server & # 三、uwsgi启动django项目必定要进入虚拟环境下,由于环境都是安装在虚拟环境中 # 四、服务器的日志都会被记录在于uwsgi配置文件 luffyapi.xml 同类目下的 uwsgi.log 中
>: mysql -uluffy -pLuffy123? >: use luffy
INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (1, 1, 1, 0, '2019-07-14 13:44:19.661327', '2019-07-14 13:46:54.246271', 'Alex', 1, '老男孩Python教学总监', '金角大王', 'teacher/alex_icon.png', '老男孩教育CTO & CO-FOUNDER 国内知名PYTHON语言推广者 51CTO学院2016\2017年度最受学员喜好10大讲师之一 多款开源软件做者 曾任职公安部、飞信、中金公司、NOKIA中国研究院、华尔街英语、ADVENT、汽车之家等公司'); INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (2, 2, 1, 0, '2019-07-14 13:45:25.092902', '2019-07-14 13:45:25.092936', 'Mjj', 0, '前美团前端项目组架构师', NULL, 'teacher/mjj_icon.png', '是马JJ老师, 一个集美貌与才华于一身的男人,搞过几年IOS,又转了前端开发几年,曾就任于美团网任高级前端开发,后来由于不一样意王兴(美团老板)的战略布局而出家作老师去了,有丰富的教学经验,开起车来也绝不含糊。一直专一在前端的前沿技术领域。同时,爱好抽烟、喝酒、烫头(锡纸烫)。 个人最爱是前端,由于前端妹子多。'); INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (3, 3, 1, 0, '2019-07-14 13:46:21.997846', '2019-07-14 13:46:21.997880', 'Lyy', 0, '老男孩Linux学科带头人', NULL, 'teacher/lyy_icon.png', 'Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深刻浅出、声音洪亮到爆炸'); INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (1, 1, 1, 0, '2019-07-14 13:40:58.690413', '2019-07-14 13:40:58.690477', 'Python'); INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (2, 2, 1, 0, '2019-07-14 13:41:08.249735', '2019-07-14 13:41:08.249817', 'Linux'); INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (1, 1, 1, 0, '2019-07-14 13:54:33.095201', '2019-07-14 13:54:33.095238', 'Python开发21天入门', 'courses/alex_python.png', 0, 'Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土', 0, '2019-07-14', 21, '', 0, 231, 120, 120, 0.00, 1, 1); INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (2, 2, 1, 0, '2019-07-14 13:56:05.051103', '2019-07-14 13:56:05.051142', 'Python项目实战', 'courses/mjj_python.png', 0, '', 1, '2019-07-14', 30, '', 0, 340, 120, 120, 99.00, 1, 2); INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (3, 3, 1, 0, '2019-07-14 13:57:21.190053', '2019-07-14 13:57:21.190095', 'Linux系统基础5周入门精讲', 'courses/lyy_linux.png', 0, '', 0, '2019-07-14', 25, '', 0, 219, 100, 100, 39.00, 2, 3); INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (1, 1, 1, 0, '2019-07-14 13:58:34.867005', '2019-07-14 14:00:58.276541', 1, '计算机原理', '', '2019-07-14', 1); INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (2, 2, 1, 0, '2019-07-14 13:58:48.051543', '2019-07-14 14:01:22.024206', 2, '环境搭建', '', '2019-07-14', 1); INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (3, 3, 1, 0, '2019-07-14 13:59:09.878183', '2019-07-14 14:01:40.048608', 1, '项目建立', '', '2019-07-14', 2); INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (4, 4, 1, 0, '2019-07-14 13:59:37.448626', '2019-07-14 14:01:58.709652', 1, 'Linux环境建立', '', '2019-07-14', 3); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (1, 1, 0, '2019-07-14 14:02:33.779098', '2019-07-14 14:02:33.779135', '计算机原理上', 1, 2, NULL, NULL, '2019-07-14 14:02:33.779193', 1, 1); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (2, 1, 0, '2019-07-14 14:02:56.657134', '2019-07-14 14:02:56.657173', '计算机原理下', 2, 2, NULL, NULL, '2019-07-14 14:02:56.657227', 1, 1); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (3, 1, 0, '2019-07-14 14:03:20.493324', '2019-07-14 14:03:52.329394', '环境搭建上', 1, 2, NULL, NULL, '2019-07-14 14:03:20.493420', 0, 2); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (4, 1, 0, '2019-07-14 14:03:36.472742', '2019-07-14 14:03:36.472779', '环境搭建下', 2, 2, NULL, NULL, '2019-07-14 14:03:36.472831', 0, 2); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (5, 1, 0, '2019-07-14 14:04:19.338153', '2019-07-14 14:04:19.338192', 'web项目的建立', 1, 2, NULL, NULL, '2019-07-14 14:04:19.338252', 1, 3); INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (6, 1, 0, '2019-07-14 14:04:52.895855', '2019-07-14 14:04:52.895890', 'Linux的环境搭建', 1, 2, NULL, NULL, '2019-07-14 14:04:52.895942', 1, 4);
""" redis 基本介绍:nosql数据库、内存数据库 优点:读写效率高、操做方便、支持五种数据类型、数据持久化、数据丢失能够还原、高并发 缺点:不支持事务 操做五种数据类型: set key value | setex key time value rpush key args | lpush key args hset key field value sadd key args zadd key score member Python使用:pip install redis Django使用:pip install django-redis => 配置缓存 celery 基本介绍:异步任务框架(独立的socket) 组成:Broker(任务中间件:内存数据库、内存队列)、Worker(任务执行者)、Backend(任务结果仓库) 建立celery应用:app = Celery(broker, backend, includ) 服务命令: worker:celery worker -A celery文件所在包 -l info -P eventlet beat:celery beat -A celery文件所在包 -l info celery应用场景: 耗时任务 延迟任务 周期任务 """
""" 一、免费课、实战课、轻课业务线独立,因此设置三个数据库表,相同字段用BaseModel处理 二、课程分类表、老师表只须要一个,三种课程公用 三、章节表、课时表、评论表、问题表要与具体分类的课程配套(陪三套表) 四、尽可能不连表 主页推荐课程,能够就访问课程表,课程表增长 推荐字段 主页模块建立 课程推荐表,点击跳转的连接为 课程详情接口 推荐关系表 => 接口缓存 一下不是实时变化的数字结果(通常都是计算而来),能够直接用一个字段存储 总课时,热度(学习学生数) 五、免费课一条业务线五张表:分类、课程、老师、章节、课时 六、序列化:表字段、插拔字段、子序列化 七、过滤组件:排序、搜索、分组筛选、区间筛选、分页 """
""" 一、整理今天所学知识点 二、复习并掌握过滤组件:排序、搜索、分组筛选、区间筛选、分页 三、完成课程主页的 免费课程接口 与 前台数据渲染 四、完成课程详情页 课程详情接口 与 前台数据渲染 """
""" 一、完成 全局课程搜索页面 的前台布局 二、完成 先后台搜索课程 业务 """
E:\上海python脱产13期\路飞学成项目day60-90\luffy\day89\课件\上线\上线.md (本身本地文件夹)
E:\上海python脱产13期\路飞学成项目day60-90\luffy\day89\课件\支付 (本身本地文件夹)
<form class="search"> <div class="tips" v-if="is_search_tip"> <span>Python</span> <span>Linux</span> </div> <input type="text" :placeholder="search_placeholder" @focus="on_search" @blur="off_search"> <button type="button" class="el-icon-search"></button> </form> <script> export default { data() { return { is_search_tip: true, search_placeholder: '', } }, methods: { on_search() { this.search_placeholder = '请输入想搜索的课程'; this.is_search_tip = false; }, off_search() { this.search_placeholder = ''; this.is_search_tip = true; }, }, } </script> <style scoped> .search { float: right; position: relative; margin-top: 22px; } .search input, .search button { border: none; outline: none; } .search input { border-bottom: 1px solid black; } .search input:focus { border-bottom-color: orange; } .search input:focus + button { color: orange; } .search .tips { position: absolute; bottom: 3px; left: 0; } .search .tips span { border-radius: 11px; background-color: #eee; line-height: 22px; display: inline-block; padding: 0 3px; margin-right: 3px; cursor: pointer; } </style>
""" 1.vue指令和成员: v-text、html、if、for、show、model、on、bind data、method、computed、watch、props、钩子、filters、components 2.vue组件: template(一个根标签) + script(export default) + style(scope) 3.先后台交互: 同源策略(跨域) ajax请求 """
""" 一、drf排序过滤器 class ListView: filter_backends = [OrderingFilter] ordering_fields = ['price', 'students'] # 接口:?ordering=-price,students 二、drf搜索:SearchFilter search_fields search=* 三、自定义过滤器 class MyFilter: def filter_queryset(self, request, queryset, view): # request:从前台请求获取过滤的条件 query_params # queryset:要处理的数据 # view:从视图中反射过滤相关的配置 return 处理后的queryset,没有过滤就返回原样queryset 四、分页器 PageNumberPagination:基础分页器 page_size page=1 LimitOffsetPagination:偏移分页器 default_limit offset=0&limit=3 CursorPagination:游标分压器 必定是基于某种排序规则下的分页 五、django-filter from django_filters.rest_framework.filterset import FilterSet from django_filters import filters from . import models class CourseFilterSet(FilterSet): max_price = filters.NumberFilter(field_name='price', lookup_expr='lte') min_price = filters.NumberFilter(field_name='price', lookup_expr='gte') class Meta: model = models.Course fields = ['course_category', 'max_price', 'min_price'] DjangoFilterBackend filter_class|filter_fields=['type'] 接口:?type=1 接口:?course_category=0&min_price=30&max_price=60 """
""" 一、搜索页面的实现与课程搜索数据展现 二、支付宝流程与二次封装支付宝框架 三、订单模块建立与表设计 四、支付接口的实现,订单表与订单详情表入库操做 五、前台完成支付请求 """
""" 一、整理今天所学知识点 二、完成搜索页面渲染搜索接口 三、二次封装支付宝框架,并完成支付接口的建立 四、完成前台的支付功能 """
""" 一、预习上线课件,完成阿里云服务器购买 二、预习往期上线视频 """
E:\上海python脱产13期\路飞学成项目day60-90\luffy\day90\课件\上线 (本身本地文件夹)
E:\上海python脱产13期\路飞学成项目day60-90\luffy\day90\课件\支付 (本身本地文件夹)
""" 一、搜索页面 二、支付宝支付 支付流程:前台下单 => 后台生成订单,返回支付连接(包含了回调接口) => 前台访问支付连接,跳转到支付宝平台,与支付宝后台交互,完成支付 => 支付宝支付成功页面同步回调前台接口(跳转回咱们本身的前台页面)(支付成功页面能够将回调参数同步传给咱们本身的后台,能够完成订单的修改) + 支付宝支付成功8次异步回调后台接口,将支付的信息传输给咱们的后台,后台能够作订单状态的修改,对支付宝异步回调响应 success 7个字符 三、异步回调流程 支付宝支付成功在25小时内,分8次异步回调后台接口,将支付的信息传输给咱们的后台,后台能够作订单状态的修改,对支付宝异步回调响应 success 7个字符 四、订单模块: 订单表:订单号、支付用户、订单总额 订单详情表:订单号外键、商品外键、商品价 """
""" Vue 基础:指令、实例成员、组件及传参 开发:vue-router、vuex(localStorage|sessionStorage)、axios、vue-cookies drf 基础模块:请求、响应、渲染、解析、异常 核心模块:序列化、三大认证、视图家族 群查模块:搜索、排序、分页、分类、区间 luffy 先后台项目重构 跨越问题:django-cors-headers 认证六表 git:status、add、commit、reset、pull、push、merge、branch、remote、checkout 短信接口 redis:字符串、列表、哈希、集合、有序集合、django缓存配置 celery:三个存成部分broker、worker、backend | 耗时任务、延迟任务、周期任务 Alipay 视频 """
<template> <div class="main" v-if v-show v-text v-html :class @click> <input v-model /> <Header /> </div> </template> <script> import Header from '@/components/Header' export default { data() { return { } }, methods: {}, watch: { '$route': function() { this.$route // 路由数据 this.$router // 路由路径 this.$cookies.set(k, v, exp) this.$cookies.get(k) this.$axios({ url: '', method: 'post', params: {}, data: {}, headers: { authorization: 'jwt token' } }).then(response => { response.data }).catch(error => { error.response.data }) } }, computed: {}, components: { Header, } } </script> <style scope> </style>
// main.js import '@/assets/css/global.css' require('@/assets/css/global.css') import settings from '@/assets/js/settings.js' Vue.prototype.$settings = settings import 'bootstrap' import ElementUI from 'element-ui'; Vue.use(ElementUI); // jquery环境须要在 vue.config.js 中配置
""" 请求:request._request、request.query_params、request.data、request.query_params.dict()、request.data.dict()、request.META.get('HTTP_AUTHORIZATION')、request.user、request.auth 响应:data、status(http_status)、exception data: { status, msg, results, } 渲染:响应的数据支持浏览器和json格式数据渲染(全局或局部配置) 解析:请求的数据包数据支持解析的类型:urlencoded、form-data、json(全局或局部配置) 异常:自定义exception_handler,系统处理了客户端异常4xx,服务器异常须要手动处理5xx,记录异常日志 序列化: class myModel(models.Model): name = models.CharFields(max_length=64) @property def my_name(self): return self.name class MyModelSerializer(ModelSerializer): // 序列化还有子序列化 re_pwd = serializers.CharFields(max_length=64) class Meta: model = myModel fields = ('name', 'my_name', 're_pwd') extra_kwargs={} list_serializer_class = ListSerialize def validate_name(self, value): if ...: raise serializers.ValidationError('...') return value def validate(self, attrs): request = self.context.get('request') obj self.obj = obj if ...: raise serializers.ValidationError({'...': '...'}) return attrs class myAPIView(APIView): def get(self, request, *args, **kwargs): obj ser = MyModelSerializer(obj) objs ser = MyModelSerializer(objs, many=Ture) def post(self, request, *args, **kwargs): ser = MyModelSerializer(data=request.data, context={'request': request}) ser.is_valid(raise_exception=True) ser.save() ser.obj def patch(self, request, *args, **kwargs): obj ser = MyModelSerializer(instance=obj,data=request.data, partial=True) ser.is_valid(raise_exception=True) ser.save() 三大认证: 认证:就采用drf-jwt的认证类,局部或全局配置 - 游客、合法用户、非法用户 权限:就采用drf提供的权限类,局部或全局配置 - 有权限、无权限 频率:scope与配置文件完成配置3/min、get_cache_key提供缓存key 视图家族: APIView(禁用csrf、各类功能模块、全局局部配置)、GenericAPIView(model相关的三个属性三个方法) mixin:五个工具类,六个工具方法 工具视图:ListAPIView,... 视图集:ViewSets - as_view({'get': 'my_get'}) 群查接口: SearchFilter OrderingFilter paginations django-filter插件 """
""" 一、支付模块的同步回调接口 二、支付模块的异步回调接口 三、阿里云服务器购买与服务器环境搭建 四、Nginx实现先后台项目上线 """
""" 一、整理今天所学知识点 二、完成支付模块的回调接口 三、购买服务器,并完成先后台项目的上线 四、总结整理luffy课程的全部知识点 """
""" 一、认证复习路飞项目涉及的全部知识点,将luffy项目完成 二、预习微信小程序视频 """