""" 一、採用國內源,加速下載模塊的速度 二、經常使用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項目完成 二、預習微信小程序視頻 """