luffycity项目
详见:
1 pip3 install pymysql 国外很慢 2 pip3 install pymysql -i 地址 3 配置,以后pip3 install全走配好的源 -来到C:\Users\oldboy\AppData\Roaming %APPDATA% -创建一个pip文件夹 -新建一个文件pip.ini -写入 [global] index-url = http://pypi.douban.com/simple [install] use-mirrors =true mirrors =http://pypi.douban.com/simple/ trusted-host =pypi.douban.com
详见:
1 不同的项目依赖不同的模块版本,不能共用一套环境,虚拟环境 2 在系统的python环境中安装 -下载 pip3 install virtualenv pip3 install virtualenvwrapper-win -修改环境变量 WORKON_HOME: D:\Virtualenvs -python安装路径,scripts文件下执行virtualenvwrapper.bat 3 使用: -workon #列出有的虚拟环境(aaa) -workon aaa #使用这个虚拟环境 -mkvirtualenv -p python3 luffy # 创建虚拟环境 -rmvirtualenv 虚拟环境名字 # 删除虚拟环境 4 安装django环境 pip install django==2.2.2 5 基于虚拟环境创建项目(不要再pycharm中选择虚拟环境了)
# 在控制台直接指向项目 python manage.py runserver ---》manage.py的内容要改 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev') # 项目上线,走的不是manage.py---》uwsgi.py,需要修改 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev') # 国际化 # 创建app:startapp在哪执行,就把app创建在哪 python ../../manage.py startapp home # 注册app
详见:
# 项目依赖的数据库,luffyapi -create database luffyapi; # 创建数据库用户,并且授予luffyapi这个库的权限 -grant all privileges on luffyapi.* to 'luffyapi'@'%' identified by 'Luffy123?'; -grant all privileges on luffyapi.* to 'luffyapi'@'localhost' identified by 'Luffy123?'; -flush privileges; # 项目连接 -setting中配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'luffyapi', 'USER':'luffyapi', 'PASSWORD':'Luffy123?', 'HOST':'127.0.0.1', 'PORT':3306 } } import pymysql pymysql.install_as_MySQLdb() -用pymysql连接数据库 -django超过:2.0.7 -需要改源码,两个地方
详见:
# 用户要基于auth的user表,必须在数据库迁移命令之前操作好,后期如果再做,会出错 -把所有app下的迁移文件,全删除 from django.contrib import auth -admin,auth app下的迁移文件删除 -删除数据库(数据一定要导出来),重新迁移 (自己尝试一下)
- 卸载django 然后重新安装django # 继承AbstractUser,增加两个字段,telephone,icon(依附于pillow) # 在setting中配置 MEDIA_URL='/media/' MEDIA_ROOT=os.path.join(BASE_DIR,'media') #现在的BASEDIR是luffyapi下的luffyapi AUTH_USER_MODEL='user.user' # 路由,打开media文件夹 from django.views.static import serve from django.conf import settings re_path('media/(?P.*) ', serve,{'document_root':settings.MEDIA_ROOT}),
详见:
#1 安装node,官网下载,一路下一步 node -v v10.16.3 #2 安装模块 npm install 模块名 #npm比较慢,用淘宝的cnpm来替换npm #3 npm install -g cnpm --registry=https://registry.npm.taobao.org #4 创建vue的工程(需要一个vue脚手架) cnpm install -g @vue/cli #5 如果出问题执行如下,重新走3,4 npm cache clean --force #5 命令行下敲 vue 就会有提示 # 6 创建vue项目 vue create luffycity 选Manually #7 用pycharm打开 在terminal下敲:npm run serve #8 配置pycharm
详见:
public -favicon.ico # 标题栏小图标 -index.html # 整个项目的单页面 src -assets # 静态文件,js,css,img -components # 小组件,头部组件,尾部组件 -router # 路由相关 -store # vuex相关,状态管理器,临时存储数据的地方 -views # 页面组件 -App.vue # 根组件 -main.js # 配置文件(跟django的setting一样) #任何一个组件都有三部分 #html相关
总结
# 1 pip换源---》提高下载模块的速度() # 2 每个python项目,都应该有个requirement.txt--->项目依赖的模块 pip freeze >requirement.txt #生成 pip install -r requirement.txt -i 豆瓣源 # 下载 使用whl文件装,windows上你装的模块,都是 xxx.whl文件 -直接去网站搜:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted -指定版本和平台下载Twisted?20.3.0?cp36?cp36m?win_amd64.whl -cmd下cd到指定的虚拟环境路径:pip3 install 文件的路径 # 离线安装 pip download -r requirement.txt -d "D:\pipDownloadTest" 切换终端在待安装文件目录下(D:\pipDownloadTest) pip install PyMySQL-0.9.3-py2.py3-none-any.whl # 3 虚拟环境 -因为机器上多个程序依赖的第三方模块不一样,版本不一样 -pip3 install virtualenv -pip3 install virtualenvwrapper-win -# 解释:windows下的批处理文件xx.bat,linux和mac上是xx.sh -配置环境变量,执行一下bat,让你配置的生效 -workon 虚拟环境 -mkvirtualenv -p python3.6 虚拟环境名称 -deactivate -rmvirtualenv 虚拟环境名称 # 4 目录结构调整 ├── luffyapi ├── logs/ # 项目运行时/开发时日志目录 - 包 ├── manage.py # 脚本文件 ├── luffyapi/ # 项目主应用,开发时的代码保存 - 包 ├── apps/ # 开发者的代码保存目录,以模块[子应用]为目录保存 - 包 ├── libs/ # 第三方类库的保存目录[第三方组件、模块] - 包 ├── settings/ # 配置目录 - 包 ├── dev.py # 项目开发时的本地配置 └── prod.py # 项目上线时的运行配置 ├── urls.py # 总路由 └── utils/ # 多个模块[子应用]的公共函数类库[自己开发的组件] └── scripts/ # 保存项目运营时的脚本文件 - 文件夹 -创建app,需要到apps目录下创建 python ../../manage.py startapp user -luffyapi--》下的luffyapi也加入到了环境变量,以后导入模块,从luffyapi--》下的luffyapi开始导入即可,pycharm会飘红,右键变成source -在setting中注册前,要把apps的路径加入到环境变量 #5 数据库配置 -创建了一个用户,授予了某个数据库的权限,给他设了一个密码 grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?'; grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?'; -在setting中配置 #6 User表配置 -继承AbstractUser,要在执行迁移命令之前完成 # 7 路飞前台 -mvvm架构:M:script里写的东西 vm:viewmodel v:template, 数据驱动页面 -单页面开发:以后只需要写xx.vue组件:小组件和页面组件
# utils/response.py from rest_framework.response import Response class APIResponse(Response): def __init__(self,code=1,msg='成功',result=None,status=None,headers=None,content_type=None,**kwargs): dic={ 'code':code, 'msg':msg } if result: dic['result']=result dic.update(kwargs) #对象来调用对象的绑定方法,会自动传值 super().__init__(data=dic,status=status,headers=headers,content_type=content_type) # 类来调用对象的绑定方法,这个方法就是一个普通函数,有几个参数就要传几个参数 # Response.__init__(data=dic,status=status,headers=headers,content_type=content_type)
#utils/exceptions.py #方法,加日志 from rest_framework.views import exception_handler # from luffyapi.utils import response from .response import APIResponse from .logger import log def common_exception_handler(exc, context): log.error('view是:%s ,错误是%s'%(context['view'].__class__.__name__,str(exc))) #context['view'] 是TextView的对象,想拿出这个对象对应的类名 print(context['view'].__class__.__name__) ret=exception_handler(exc, context) # 是Response对象,它内部有个data if not ret: #drf内置处理不了,丢给django 的,我们自己来处理 # 好多逻辑,更具体的捕获异常 if isinstance(exc,KeyError): return APIResponse(code=0, msg='key error') return APIResponse(code=0,msg='error',result=str(exc)) else: return APIResponse(code=0,msg='error',result=ret.data)
# setting/dev.py # rest_framework配置 REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'luffyapi.utils.exceptions.common_exception', } #日志的配置 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(module)s %(lineno)d %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { # 实际开发建议使用WARNING 'level': 'DEBUG', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { # 实际开发建议使用ERROR 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi 'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"), # 日志文件的最大值,这里我们设置300M 'maxBytes': 300 * 1024 * 1024, # 日志文件的数量,设置最大日志数量为10 'backupCount': 10, # 日志格式:详细格式 'formatter': 'verbose', # 文件内容编码 'encoding': 'utf-8' }, }, # 日志对象 'loggers': { 'django': { 'handlers': ['console', 'file'], 'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统 }, } }
#utils/logger.py import logging # log=logging.getLogger('名字') # 跟配置文件中loggers日志对象下的名字对应 log=logging.getLogger('django') # 以后其他地方需要获取日志信息,导入直接用 from luffyapi.utils.logger import log log.info('xxxxxx') # 不要写print
# xss(又叫css攻击:Cross Site Scripting):跨站脚本攻击, # cors(Cross-Origin Resource Sharing):跨域资源共享, # csrf(Cross-site request forgery):跨站请求伪造 # 1 同源策略:请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同. # 2 CORS:跨域资源共享,允许不同的域来我的服务器拿数据 # 3 CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request) 只要同时满足以下两大条件,就属于简单请求 (1) 请求方法是以下三种方法之一: HEAD GET POST (2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain #如果发送post请求,数据格式是json---》非简单请求,非简单请求发两次,一次OPTIONS请求,一次真正的请求 # 4 后端处理,开启cors,跨域资源共享(在中间中写) class MyMiddle(MiddlewareMixin): def process_response(self, request, response): response['Access-Control-Allow-Origin'] = '*' if request.method == "OPTIONS": # 可以加* response["Access-Control-Allow-Headers"] = "Content-Type" response["Access-Control-Allow-Headers"] = "authorization" return response #5 在setting的中间件中配置 #6使用第三方,django-cors-headers -pip install django-cors-headers -注册app:'corsheaders', -配置中间件:corsheaders.middleware.CorsMiddleware -setting中配置: CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'authorization', 'content-type', )
#1 前台可以发送ajax的请求,axios #2 cnpm install axios #3 配置在main.js中 import axios from 'axios' # 导入安装的axios Vue.prototype.$axios = axios; # 相当于把axios这个对象放到了vue对象中,以后用 vue对象.$axios #4 使用(某个函数中) this.$axios.get('http://127.0.0.1:8000/home/home/'). 向某个地址发送get请求 then(function (response) { 如果请求成功,返回的数据再response中 console.log(response) }).catch(function (error) { console.log(error) }) #5 es6的箭头函数 function (response) { console.log(response)} response=>{ console.log(response)}
simpleui后台管理
详见:(推荐)
详见:
总结
# 1 同源策略:浏览器的安全策略,不允许不同域(ip+端口+协议) # 2 跨域资源共享,cors,本质就是在响应头部加入允许,允许某些域,允许某些头 # 3 咱的项目要处理跨域 -两种方式: -django-cors-headers:下载--》注册app——》配置中间件--》setting配置 -自己处理,就在中间件中允许域和头 # 4 跨域:简单请求和非简单请求,非简单请求发送两次,一次OPTIONS预检请求 -(1) 请求方法是以下三种方法之一: HEAD GET POST -(2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain # 5 单页面开发:小组件,页面组件 -组件:三部分,template,script,style -vue-router: const routes = [ { path: '/', name: 'Home', component: Home }, ] #6 vue跟后端交互(ajax) -axios -npm install axios -在main.js中配置 import axios from 'axios' //导入安装的axios //相当于把axios这个对象放到了vue对象中,以后用 vue对象.$axios Vue.prototype.$axios = axios; -使用:在组件中 -this.$axios.get(url).then(function(response){}).catch(function(error){}) -this.$axios.get(url).then(response=>{}).catch(error=>{})
#html中路由跳转"/"> #js中控制路由跳转 this.$router.push('/');"../assets/img/head-logo.svg" alt="">
# 在components内新建Head.vueclass="header">class="slogan">老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活
class="nav">class="left-part">
- class="logo">
"/"> "../assets/img/head-logo.svg" alt="">
- class="ele"> "goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课
- class="ele"> "goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课
- class="ele"> "goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课
class="right-part">登录 class="line">| 注册
# 在main.js中配置 // 配置全局样式 @ 符号,代指src路径 import '@/assets/css/global.css' // 配置全局自定义设置 import settings from '@/assets/js/settings' Vue.prototype.$settings = settings; // 在所有需要与后台交互的组件中:this.$settings.base_url + '再拼接具体后台路由' # 在assets下的css中加入global.css /* 声明全局样式和项目的初始化样式 */ body, h1, h2, h3, h4, h5, h6, 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; /* 合并边框 */ } # 在assets下的js中加入settings.js export default { base_url: 'http://127.0.0.1:8000' }
# 安装 cnpm install axios cnpm install vue-cookies cnpm install element-ui cnpm install jquery cnpm install bootstrap@3 #在main.js中配置 #axios配置 import axios from 'axios' Vue.prototype.$axios = axios; # vue-cookies配置 import cookies from 'vue-cookies' Vue.prototype.$cookies = cookies; # ElementUI的配置 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); # bootstrap配置 import 'bootstrap' import 'bootstrap/dist/css/bootstrap.min.css' # jquery的配置,不太一样 项目根路径创一个 vue.config.js const webpack = require("webpack"); module.exports = { configureWebpack: { plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "window.jQuery": "jquery", "window.$": "jquery", Popper: ["popper.js", "default"] }) ] } };
Footer.vue
class="footer">
- 关于我们
- 联系我们
- 商务合作
- 帮助中心
- 意见反馈
- 新手指南
Copyright ? luffycity.com版权所有 | 京ICP备17072161号-1
####1 urls.py from rest_framework.routers import SimpleRouter router=SimpleRouter() router.register('banner',views.BannerView,'banner') urlpatterns = [ # path('banner/', views.BannerView.as_view()), path('', include(router.urls)), ] ####2 views.py from django.conf import settings class BannerView(GenericViewSet,ListModelMixin): # 无论有多少条待展示的数据,最多就展示3条 queryset = models.Banner.objects.filter(is_delete=False,is_show=True).order_by('display_order')[:settings.BANNER_COUNTER] serializer_class = serializer.BannerModelSerilaizer ### 3 serializer.py from rest_framework import serializers from . import models class BannerModelSerilaizer(serializers.ModelSerializer): class Meta: model=models.Banner fields=['name','link','img'] ### 4 models.py from utils.models import BaseModel class Banner(BaseModel): name=models.CharField(max_length=32,verbose_name='图片名字') img=models.ImageField(upload_to='banner',verbose_name='轮播图',help_text='图片尺寸必须是:3840*800',null=True) link=models.CharField(max_length=32,verbose_name='跳转连接') info=models.TextField(verbose_name='图片简介') # type=models.IntegerField(choices=) def __str__(self): return self.name ####5 utils/models.py class BaseModel(models.Model): created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间') is_delete = models.BooleanField(default=False, verbose_name='是否删除') is_show = models.BooleanField(default=True, verbose_name='是否展示') orders = models.IntegerField() class Meta: abstract=True # 一定不要忘了 ### 6 settings/const.py # 首页轮播图个数 BANNER_COUNTER=3 #### 7 settings/dev.py from .const import *
"banner">
"400px">
for="item in banner_list">
"item.link">
"item.img" :alt="item.name">
前端路由配置
const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/free-course', name: 'FreeCourse', component: FreeCourse }, { path: '/light-course', name: 'LightCourse', component: LightCourse }, { path: '/actual-course', name: 'ActualCourse', component: ActualCourse }, ]
#1 协同开发,版本管理 #2 svn(集中式管理),git(分布式管理) #3 git装完,既有客户端,又有服务端 #4 git工作流程 -工作区,暂存区,版本库 #5 远程仓库:github,码云,公司内部(gitlab) # 6 安装:一路下一步 # 7 右键--git bash here # 8 git 命令 -初始化:git init 文件夹名 -初始化:git init #当前路径全被管理 -git status # 查看状态 -git add a.txt # 把a提交到暂存区 -git add . # 批量提交到暂存区 -git commit -m '注释,我新增了a' # 把暂存区的所有数据都提交到版本库 需先添加作者信息 -需要增加作者信息 git config --global user.email "lqz@qq.com" git config --global user.name "lqz" # 如果在全局添加则会保存到C:\>用户>xxx>.gitconfig配置文件中 git config user.email "egon@qq.com" git config user.name "egon" # 如果在局部添加则会保存到当前目录的.git文件夹下的config文件中 -把a的新增提交到版本管理 -新建b,在a中新增一行 -git checkout . # 回复到提交版本的位置,a是空的,b没有被git管理,所以,是什么样,还是什么样 -git log # 查看版本管理的日志 -git reflog # 查看日志,条数更多,内容更少 -git reset --hard 版本号 # 指定恢复到对应的版本 # 红色表示未被管理 # 绿色表示提交到暂存区了 # 忽略文件 -空文件夹不被管理 -指定某些文件或者文件夹不被git管理 -在项目根路径,跟.git文件夹一个路径,新建.gitignore.,在里面配置 - 语法: # 号是注释,没有用 文件夹名字,表示文件夹忽略,不被管理 /dist 表示根路径下的dist文件夹,不被管理 *.py 表示后缀名为py的文件,都被忽略 *.log* 表示后缀名中带log的都被忽略 # 分支操作 -查看分支 git branch 查看所有分支,分支是绿的,表示在当前分支上 -创建分支 git branch dev -创建并切换到 git checkout -b dev -删除分支 git branch -d dev -切换分支 git checkout dev -合并分支 git merge 分支名 # 把dev分支合并到master分支:切换到master分支,执行合并dev分支的命令
总结
# 1 轮播图接口 -封装一个BaseModel基类,utils/model.py下 -home的app下的models.py创建Banner表 -序列化类 -视图(继承哪个视图类) queryset对象[:3] -路由自动生成 -返回多少张,通过setting的配置实现 -在setting包下新建const.py 变量名=变量值 -在dev.py中 from .const import * # 2 前端轮播图 -elementui的跑马灯 v-for :src -axios发送请求 # 3 git -工作区,暂存区,版本库 -git init # 生成一个.git 的文件夹 -git stauts # 查看状态(红色和绿色),必须全绿再提交到版本库 -git add . # 提交到暂存区 -git commit -m '文字' # 从暂存区提交到版本库 -git checkout . # 把工作区的代码恢复到版本库中最先的代码 -git reset . # 把暂存区的东西拉回到工作区(原来是绿的变红了) -git reset --hard 版本号 # 回退到某个版本 分支 -git branch # 查看所有分支,如果分支是绿色,是再当前分支是 -git branch 分支名 # 创建分支 -git chekout dev # 切换到dev分支 -git chekout -b dev2 # 创建并切换到dev2分支 -git branch -d dev2 # 删除dev2分支,不能再这个分支上 -git merge dev # 你必须再其他分支(master),合并dev # 4 重现冲突就两种情况 -分支合并 -你和同时再同一条分支上改了相同的代码
# 1 码云(国内,快) # 2 新建仓库的时候,不要勾选Readme初始化这个仓库 否则就不是空仓库 # 3 现在什么都没有,新建仓库 mkdir lqz_test cd lqz_test git init touch a.txt git add a.txt git commit -m "first commit" git remote add origin https://gitee.com/liuqingzheng/lqz_test.git # 连接远程 git push origin master # 4 已经有了仓库 cd b git remote add origin https://gitee.com/liuqingzheng/lqz_test.git git push origin master # 5 git 远程操作命令 -git remote # 查看远程仓库(没有就看不到) -git remote add origin https://gitee.com/liuqingzheng/lqz_test.git # 跟远程仓库建立连接 -git push origin master # 把本地的master分支提交到远程的origin,需要输入用户名和密码(之前存的需要删掉)
# 1 项目创始者,如上 # 2 项目开发者,参与者(换了一台电脑),把代码拉下来继续开发 -git clone https://gitee.com/liuqingzheng/lqz_test.git -能看到完整的版本和日志控制 -可以回复到任意版本 -git rest --hard 版本号 -git checkout . # 表示回到指针指向的版本,因为已经用git rest --hard把指针移动了,所以checkout .就是当前再的版本上的东西 # 3 本地新增c.txt文件 -提交到暂存区 -提交到版本库(没有提交到远程,远程看不到) -提交到远程:git push origin master # 4 让a文件夹中的代码成为最新的 -git pull origin master # 5 重点:每次再提交代码之前,一定要先更新代码(拉),如果不拉提不上去
# 1 公司内部大部分用ssh连接 -领导给你一个git地址(项目地址) -你 git clone 地址 到本地 -改代码,改完了---》提交(禁止) -ssh配置,以后都不用输密码了 # 2 配置如何做 -对称加密(加密和解密用同一套秘密) -非对称加密(公钥和私钥),公钥加密,私钥解密 -生成一对公钥和私钥(用命令) -https://gitee.com/help/articles/4181 -ssh-keygen -t rsa -C "lqz@qq.com" 生成到用户家目录的.ssh文件夹下(一个公钥,一个私钥) -把公钥复制出来,再码云上配置 # 跟远程操作有三个命令 git pull git push -git fecth(一般不用,他跟pull是一个东西,当作不知道)
# 1 安装git # 2 再pycharm中配置,setting---》git--->git.exe的地址 # 3 git clone --->等同于下图 # 4 使用pycharm创建本地分支(见下图) # 5 拉取代码,如下图 # 6 push代码,如下图
# 1 协同开发出现冲突,如何解决 # 2 git pull origin master 拉下远程代码,同事和你修改了同一个位置,会冲突,如下 <<<<<<< HEAD 你的代码 ======= 别人代码 >>>>>>> origin/master # 3 处理方案 -删除你的代码 -删除同时代码 -合并你们的代码
# 1 分支:本地分支,远程分支 # 2 创建本地分支 -git checkout -b dev # 3 把本地分支提交到远程 -git push origin dev
# 如果看到可自动合并,表示合并后不会有冲突,正常操作即可 # 测试人员点通过,直接合并即可
# 1 切换到master分支 # 2 回滚打某个版本 git reset --hard 版本号 # 3 强制提交代码 git push origin master -f
# 1 远端创建一个dev分支 -在远端直接创建 -本地创建,提交到远端 git checkout -b dev git push origin dev # 2 本地新建一个dev_bug分支 -dev_bug分支改了文件 -dev分支改了同样的文件 -合并就出冲突 -git merge dev_bug (在dev分支上操作) -解决冲突(删你的,同事的,合并起来) -git add . git commit -正常了,冲突解决 # 3 手动线下合并代码并提交到远程 -git checkout master -git merge dev -如果出冲突,解决 -git add . -git commit -git push..
# Login.vueclass="login"> "close_login">X# Head.vue #template中class="right-part">"pull_login">登录 class="line">| 注册
git rebase
总结
# 1 git远程 git remote 远程地址(https/ssh) # 2 ssh 的公钥和私钥,配在公司的git服务器上(gitlab,gitlab) # 3 git status 没有红色和绿的才能提交到远程 # 4 提交代码之前,一定要先拉代码(如果本地不是最新的,提交不上去) # 5 每天一到公司,先拉一下代码,你要提交之前,一定要拉 # 6 冲突出现的原因:1 多人同时操作同一个分支 2 分支合并的冲突 # 7 线上分支合并(了解) # 8 线下分支合并,可能会出冲突,解决,直接提交 # 9 创建远程分支:本地建完,push上去 远端直接创建 # 10 git clone 远程已经有了仓库和代码了 # 11 vue 登录页面分析
# Login.vueclass="login">class="box"> class="el-icon-close" @click="close_login">class="content">class="nav"> class="{active: login_method === 'is_pwd'}" @click="change_login_method('is_pwd')">密码登录 class="{active: login_method === 'is_sms'}" @click="change_login_method('is_sms')">短信登录if="login_method === 'is_pwd'"> input placeholder="用户名/手机号/邮箱" prefix-icon="el-icon-user" v-model="username" clearable> input placeholder="密码" prefix-icon="el-icon-key" v-model="password" clearable show-password> "primary">登录 if="login_method === 'is_sms'"> input placeholder="手机号" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> input placeholder="验证码" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> "append"> class="sms" @click="send_sms">{{ sms_interval }} "primary">登录 class="foot"> "go_register">立即注册
# Register.vueclass="register">class="box"> class="el-icon-close" @click="close_register">class="content">class="nav"> class="active">新用户注册input placeholder="手机号" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> input placeholder="密码" prefix-icon="el-icon-key" v-model="password" clearable show-password> input placeholder="验证码" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> "append"> class="sms" @click="send_sms">{{ sms_interval }} "primary">注册 class="foot"> "go_login">立即登录
#Head.vue #template底部class="right-part">"put_login">登录 class="line">| "put_register">注册if="is_login" @close="close_login" @go="put_register"/> if="is_register" @close="close_register" @go="put_login"/> # js
# 1 多方式登录接口 # 2 手机号登录接口 # 3 发送验证码接口 # 4 注册接口 # 5 校验手机号是否存在接口
# 路由 from django.urls import path,include from . import views from rest_framework.routers import SimpleRouter router=SimpleRouter() router.register('',views.LoginView,'login') urlpatterns = [ path('',include(router.urls) ), ] # 视图类 from rest_framework.viewsets import ViewSet from . import serializer from luffyapi.utils.response import APIResponse from rest_framework.decorators import action class LoginView(ViewSet): @action(methods=['POST'],detail=False) def login(self,request,*args,**kwargs): ser=serializer.UserSerilaizer(data=request.data) if ser.is_valid(): token=ser.context['token'] username=ser.context['user'].username return APIResponse(token=token,username=username) else: return APIResponse(code=0,msg=ser.errors) # 序列化类 from rest_framework import serializers from . import models from rest_framework.exceptions import ValidationError class UserSerilaizer(serializers.ModelSerializer): username=serializers.CharField() class Meta: model=models.User fields=['username','password','id'] extra_kwargs={ 'id':{'read_only':True}, 'password':{'write_only':True} } def validate(self, attrs): # 多种登录方式 user=self._get_user(attrs) # 签发token token=self._get_token(user) # 放到context中,我在视图类中可以取出来 self.context['token']=token self.context['user']=user return attrs def _get_user(self,attrs): username = attrs.get('username') password = attrs.get('password') import re if re.match('^1[3-9][0-9]{9}$',username): user=models.User.objects.filter(telephone=username).first() elif re.match('^.+@.+$',username): #邮箱 user=models.User.objects.filter(email=username).first() else: user=models.User.objects.filter(username=username).first() if user: ret=user.check_password(password) if ret: return user else: raise ValidationError('密码错误') else: raise ValidationError('用户不存在') def _get_token(self,user): from rest_framework_jwt.serializers import jwt_payload_handler,jwt_encode_handler payload=jwt_payload_handler(user) # 通过user对象获得payload token=jwt_encode_handler(payload) # 通过payload获得token return token
1 重点,cookie操作 this.$cookies.set('key',value,过期时间秒) this.$cookies.get('key') this.$cookies.remove('key') 2 # template"primary" @click="login_password">登录 #js login_password() { if (this.username && this.password) { //发送请求 this.$axios.post(this.$settings.base_url + '/user/login/', { username: this.username, password: this.password }).then(response => { console.log(response.data) //把用户信息保存到cookie中 // this.$cookies.set('key','value','过期时间,按s计') this.$cookies.set('token',response.data.token,'7d') this.$cookies.set('username',response.data.username,'7d') //关闭登录窗口(子传父) this.$emit('close') //给父组件,Head传递一个事件,让它从cookie中取出token和username this.$emit('loginsuccess') }).catch(errors => { }) } else { this.$message({ message: '用户名或密码必须填哦', type: 'warning', }); } },
# Head.vue ### template "logout">注销 ### js logout(){ //清除cookie this.$cookies.remove('token') this.$cookies.remove('username') //把两个变量值为空 this.username='' this.token='' },
# urls.py 如果不重写视图类不需要动 # views.py class LoginView(ViewSet): @action(methods=['GET'], detail=False) def check_telephone(self,request,*args,**kwargs): import re telephone=request.query_params.get('telephone') if not re.match('^1[3-9][0-9]{9}',telephone): return APIResponse(code=0,msg='手机号不合法') try: models.User.objects.get(telephone=telephone) return APIResponse(code=1) except: return APIResponse(code=0,msg='手机号不存在')
#0 注册一个公众号() -https://mp.weixin.qq.com/ -注册订阅号,一路下一步,申请个人 -截一个图(首页) #1 腾讯云,---注册---实名:-https://console.cloud.tencent.com/smsv2 #2 https://console.cloud.tencent.com/smsv2/csms-sign/create -把图传上去,认证 #3 创建模板(审核) #4 应用管理(创建一个应用,记住appid和App Key) #5 扣代码(https://cloud.tencent.com/document/product/382/11672) # 7 帮助文档 -API:一堆web接口,基于API接口来写 -SDK:软件开发工具包软件,别人基于api接口,用不同语言封装的工具包,我们可以直接调用方法完成某些事
# lib/tx_sms #__init__.py from .send import get_code,send_message #send.py from qcloudsms_py import SmsSingleSender from luffyapi.utils.logger import log from . import settings # 生成一个四位随机验证码 def get_code(): import random s_code='' for i in range(4): s_code+=str(random.randint(0,9)) return s_code def send_message(phone,code): ssender = SmsSingleSender(settings.appid, settings.appkey) params = [code, '3'] # 当模板没有参数时,`params = []` try: result = ssender.send_with_param(86, phone, settings.template_id, params, sign=settings.sms_sign, extend="", ext="") if result.get('result') == 0: return True else: return False except Exception as e: log.error('手机号:%s,短信发送失败,错误为:%s'%(phone,str(e))) #settings.py # 短信的配置 # 短信应用 SDK AppID appid = 140039846 # SDK AppID 以1400开头 # 短信应用 SDK AppKey appkey = "fd972f6da15add4de47b50b8dbe930" # 短信模板ID,需要在短信控制台中申请 template_id = 66935 # NOTE: 这里的模板 ID`7839`只是示例,真实的模板 ID 需要在短信控制台中申请 # 签名 sms_sign = "小猿取经" # NOTE: 签名参数使用的是`签名内容`,而不是`签名ID`。这里的签名"腾讯云"只是示例,真实的签名需要在短信控制台中申请 # views.py @action(methods=['GET'], detail=False) def send(self,request,*args,**kwargs): ''' 发送验证码接口 :return: ''' import re from luffyapi.libs.tx_sms import get_code,send_message from django.core.cache import cache from django.conf import settings telephone = request.query_params.get('telephone') if not re.match('^1[3-9][0-9]{9}', telephone): return APIResponse(code=0, msg='手机号不合法') code=get_code() result=send_message(telephone,code) # 验证码保存(保存到哪?) # sms_cache_%s cache.set(settings.PHONE_CACHE_KEY%telephone,code,180) if result: return APIResponse(code=1,msg='验证码发送成功') else: return APIResponse(code=0, msg='验证码发送失败')
总结
# 1 多方式登录接口,路由配置 # 2 前端登录,判断了用户名密码是否填写,弹窗 # 3 vue操作cookie -this.$cookie.set(key,value,过期时间) -this.$cookie.get(key) -this.$cookie.remove(key) # 4 子传父 //给父组件,Head传递一个事件,让它从cookie中取出token和username this.$emit('loginsuccess') //在父组件中写"login_success"/> # 5 腾讯短信接入 -API:接口,不同语言都可以操作 -SDK:需要有不同语言的sdk -封装成了工具包 # 6 发送短信验证码接口 # 7 校验手机号
# user/throttlings.py # 继承SimpleRateThrottle 重写get_cache_key方法 返回什么就以什么作为限制 from rest_framework.throttling import SimpleRateThrottle class SMSThrotting(SimpleRateThrottle): scope = 'sms' def get_cache_key(self, request, view): telephone = request.query_params.get('telephone') #'throttle_%(scope)s_%(ident)s'%{} return self.cache_format%{'scope':self.scope,'ident':telephone} # 配置在视图类上 from .throttlings import SMSThrotting class SendSmSView(ViewSet): throttle_classes = [SMSThrotting,] # 在settings/dev中配置 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES':{ 'sms':'1/m' # key要跟类中的scop对应 } }
# views.py @action(methods=['POST'],detail=False) def code_login(self,request,*args,**kwargs): ser = serializer.CodeUserSerilaizer(data=request.data) if ser.is_valid(): token = ser.context['token'] username = ser.context['user'].username return APIResponse(token=token, username=username) else: return APIResponse(code=0,msg=ser.errors) # serializer class CodeUserSerilaizer(serializers.ModelSerializer): code=serializers.CharField() class Meta: model = models.User fields = ['telephone', 'code'] def validate(self, attrs): user=self._get_user(attrs) # 用户存在,签发token token = self._get_token(user) self.context['token'] = token self.context['user'] = user return attrs def _get_user(self, attrs): from django.core.cache import cache from django.conf import settings import re telephone = attrs.get('telephone') code = attrs.get('code') # 取出原来的code cache_code=cache.get(settings.PHONE_CACHE_KEY%telephone) if code ==cache_code: # 验证码通过 if re.match('^1[3-9][0-9]{9}$', telephone): user = models.User.objects.filter(telephone=telephone).first() if user: # 把使用过的验证码删除 cache.set(settings.PHONE_CACHE_KEY % telephone,'') return user else: raise ValidationError('用户不存在') else: raise ValidationError('手机号不合法') else: raise ValidationError('验证码错误') def _get_token(self, user): from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler payload = jwt_payload_handler(user) # 通过user对象获得payload token = jwt_encode_handler(payload) # 通过payload获得token return token
class="login">
class="box">
class="el-icon-close" @click="close_login">
class="content">
class="nav">
class="{active: login_method === 'is_pwd'}"
@click="change_login_method('is_pwd')">密码登录
class="{active: login_method === 'is_sms'}"
@click="change_login_method('is_sms')">短信登录
if="login_method === 'is_pwd'">
input
placeholder="用户名/手机号/邮箱"
prefix-icon="el-icon-user"
v-model="username"
clearable>
input
placeholder="密码"
prefix-icon="el-icon-key"
v-model="password"
clearable
show-password>
"primary" @click="login_password">登录
if="login_method === 'is_sms'">
input
placeholder="手机号"
prefix-icon="el-icon-phone-outline"
v-model="mobile"
clearable
@blur="check_mobile">
input
placeholder="验证码"
prefix-icon="el-icon-chat-line-round"
v-model="sms"
clearable>
"append">
class="sms" @click="send_sms">{{ sms_interval }}
"primary" @click="code_login">登录
class="foot">
"go_register">立即注册
#详细代码看上面一条,前台发送验证码
# urls.py router.register('register', views.RegisterView, 'register') # /user/register post请求就是新增 # views.py class RegisterView(GenericViewSet,CreateModelMixin): queryset = models.User.objects.all() serializer_class = serializer.UserRegisterSerilaizer def create(self, request, *args, **kwargs): response=super().create(request, *args, **kwargs) username=response.data.get('username') return APIResponse(code=1,msg='注册成功',username=username) # serializer.py class UserRegisterSerilaizer(serializers.ModelSerializer): code=serializers.CharField(max_length=4,min_length=4,write_only=True) class Meta: model = models.User fields = ['telephone', 'code','password','username'] extra_kwargs = { 'password': {'max_length': 18,'min_length':8}, 'username': {'read_only':True} } def validate(self, attrs): telephone = attrs.get('telephone') code = attrs.get('code') # 取出原来的code cache_code = cache.get(settings.PHONE_CACHE_KEY % telephone) if code == cache_code: # 验证码通过 if re.match('^1[3-9][0-9]{9}$', telephone): attrs['username']=telephone # 把用户的名字设成手机号 attrs.pop('code') return attrs else: raise ValidationError('手机号不合法') else: raise ValidationError('验证码错误') # 重写create方法 def create(self, validated_data): user=models.User.objects.create_user(**validated_data) return user
# Register.vueclass="register">class="box"> class="el-icon-close" @click="close_register">class="content">class="nav"> class="active">新用户注册input placeholder="手机号" prefix-icon="el-icon-phone-outline" v-model="mobile" clearable @blur="check_mobile"> input placeholder="密码" prefix-icon="el-icon-key" v-model="password" clearable show-password> input placeholder="验证码" prefix-icon="el-icon-chat-line-round" v-model="sms" clearable> "append"> class="sms" @click="send_sms">{{ sms_interval }} "primary" @click="register">注册 class="foot"> "go_login">立即登录
#1 key-value的存储方式,value有很多数据类型:5大:string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型 #2 内存数据库 #3 与Memcached比较: -1 Memcached只支持一种数据类型字符串 -2 Memcached不支持持久化(不支持存到硬盘上,只要一断电,数据就没了) # 4 使用Redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string,list,set,sorted set,hash (3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 # 5 单线程,单进程,不存在并发访问的问题(新版本已经不是了) -单线程为什么这么快 -数据在内存(最重要的) -io多路复用技术 -因为没有进程,线程间的切换 # 6 redis适合的场景 1 排行榜 2 网站访问量,文章访问量 3 缓存数据库(用的最多,就是做缓存) 4 发布订阅 5 去重 6 分布式(blpop) # 7 安装 -Redis-x64-3.2.100.msi 安装包 -redis-desktop-manager-0.9.3.817.exe 等同于navicate # 8 使用 -服务端和客户端 -安装完,服务端自动启动:redis-server 配置文件.conf -redis-cli :客户端连接服务端(redis-cli -h 127.0.0.1 -p 6379)
总结
# 1 频率限制类 (继承SimpleRateThrottle,scop,重写getcachekey方法:方法返回什么,就以什么作为key限制,setting中配置) # 2 手机号验证码登录,手机号和验证码 # 3 注册接口 # 4 redis:安装和启动 -5大数据类型:str:接口缓存,list:分布式,hash:缓存,set:去重,zset:排行榜 -redis-server 以某个配置文件启动 启动(windows上在服务中右键启动也可以) -redis-cli -h -p
# 1 pip3 install redis # 简单使用 from redis import Redis # conn=Redis() #连接对象 conn=Redis(host='127.0.0.1', port=6379) ret=conn.get('name') print(ret)
###### t_redis_pool.py #redis连接池 import redis #pool必须是单例的 POOL = redis.ConnectionPool(host='127.0.0.1', port=6379,max_connections=100) # 造一个池子,最多能放100个连接 #######t_redis_conn.py #redis连接池 # import redis # #pool必须是单例的 所以利用模块导入(单例) # pool = redis.ConnectionPool(host='127.0.0.1', port=6379,max_connections=100) # 造一个池子,最多能放100个连接 import redis #包内的py文件,如果想右键运行,导包的时候不能带点 from t_redis_pool import POOL # pycharm提示的错 r = redis.Redis(connection_pool=POOL) # 只要执行这一句话,就是从池中拿出一个连接 ret=r.get('name') print(ret)
#####字符串操作 ####1 set的用法 # conn.set('height',180) #基本使用 # conn.set('height','190',nx=True) # conn.set('height','190',xx=True) # conn.set('height1','190',xx=True) ''' ex,过期时间(秒) px,过期时间(毫秒) nx,如果设置为True,则只有name不存在时,当前set操作才执行,值存在,就修改不了,执行没效果 xx,如果设置为True,则只有name存在时,当前set操作才执行,值存在才能修改,值不存在,不会设置新值 ''' ### 2 # setnx(name, value) # # 设置值,只有name不存在时,执行设置操作(添加),如果存在,不会修改 # setex(name, value, time) # # 设置值 # # 参数: # # time,过期时间(数字秒 或 timedelta对象) # psetex(name, time_ms, value) # # 设置值 # # 参数: # # time_ms,过期时间(数字毫秒 或 timedelta对象 # mset # conn.mset({'name1':'11','name3':'dasfd'}) # ret=conn.mget(['name1','name','name3']) # print(ret) # ret=conn.getset('name1', '999') # print(ret) # ret=conn.getrange('name1',0,0) # 前闭后闭区间 # print(ret) # conn.setrange('name1',1,88888) # ret=conn.getbit('name1',9) # print(ret) #incr :统计网站访问量,页面访问量,接口访问量 # conn.incr('name1') # 只要一执行,数字加1 # conn.incr('name1') # 只要一执行,数字加1 # conn.incr('name1') # 只要一执行,数字加1 # conn.incr('name1') # 只要一执行,数字加1 #decr # conn.incr('name1',-2) # conn.decr('name1',3) # conn.append('name1','oo') # conn.incr('name1') ##重点: #set :很多参数 #get #mset #mget #incr #decr #append
# hash操作 # conn.hset('hash1','name','lqz') # conn.hset('hash1','name2','lqz') # conn.hset('hash1','name','lqz444') # key不可以重复, # ret=conn.hget('hash1','name') #只能取一个 # print(ret) # conn.hmset('hash2',{'key1':'value1','key2':'value2'}) # ret=conn.hmget('hash1','name','name2') # ret=conn.hmget('hash1',['name','name2']) # print(ret) # ret=conn.hgetall('hash1') # 尽量少用 # print(ret) # ret=conn.hlen('hash1') # ret=conn.hkeys('hash1') # ret=conn.hexists('hash1','name1') # ret=conn.hdel('hash1','name') # conn.hset('hash1','name',12) # ret=conn.hincrby('hash1','name') # # print(ret) # 以后想取出hash类型内所有的数据,不建议用hgetall,建议用hscan_iter # 一次性先取一部分回来(假设有1w条,先取回100条,把这100条做成了生成器) # ret=conn.hscan_iter('hash1') # print(ret) # for i in ret: # print(i) ##重点掌握 # hset # hget #hmset #hmget # hincrby # 区分hgetall和hscan_iter
### 列表操作 # ret=conn.lpush('list1',1,2,3,4,5) # ret=conn.rpush('list1',999) # ret=conn.lpushx('list2',1) # ret=conn.lpushx('list1',888) # 必须有这个key才能放 # ret=conn.rpushx('list1',666) # 我们猜,返回总长度 # ret=conn.llen('list1') # ret=conn.linsert('list1','before','3','77777777') # ret=conn.linsert('list1','after','3','66666666') # ret=conn.lset('list1',3,'22222') #从0开始计数 # ret=conn.lset('list1',0,'11111') # ret=conn.lrem('list1',2,'5') # 从前往后删除两个5 # ret=conn.lrem('list1',-1,'5') # 从后往前删除1个5 # ret=conn.lrem('list1',0,'5') # 删除所有5 # ret=conn.lpop('list1') # ret=conn.rpop('list1') # ret=conn.lindex('list1',0) # ret=conn.lrange('list1',0,2) # 前闭后闭 # ret=conn.ltrim('list1',1,2) # 重点block,阻塞,可以写一个超时时间 # ret=conn.blpop('list1',timeout=10) # print(ret) # 自定制分批取列表的数据 # conn.lpush('test',*[1,2,3,4,45,5,6,7,7,8,43,5,6,768,89,9,65,4,23,54,6757,8,68]) # conn.flushall() def scan_list(name,count=2): index=0 while True: data_list=conn.lrange(name,index,count+index-1) if not data_list: return index+=count for item in data_list: yield item # print(conn.lrange('test',0,100)) for item in scan_list('test',5): print('---') print(item) # 重点 lpush lpop blpop lrange llen
# 其他操作 # conn.delete('list1') # ret=conn.delete('hash1') # ret=conn.exists('hash2') # ret=conn.keys('cache*') #查询以cache开头的所有key # ret=conn.expire('hash2',2) # ret=conn.type('name3') # ret=conn.type('test') # ret=conn.type('test') print(ret)
# redis支持事务 # 管道。实现事务 # import redis # pool = redis.ConnectionPool(host='127.0.0.1', port=6379) # # conn = redis.Redis(connection_pool=pool) # # # pipe = r.pipeline(transaction=False) # pipe = conn.pipeline(transaction=True) # pipe.multi() # pipe.set('name', 'alex') # # pipe.set('role', 'sb') # # pipe.execute() # 这句话,才真正的去执行
# 方式一(通用方式) # 方式二:django-redis -pip install django-redis -setting中配置 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} # "PASSWORD": "123", } } } -使用两种方式 1 使用cache from django.core.cache import cache cache.set('name',user) 2 直接使用conn对象 from django_redis import get_redis_connection conn = get_redis_connection('default') print(conn.hgetall('xxx'))
# 首页轮播图数据缓存到redis中 def list(self, request, *args, **kwargs): # response=super().list(request, *args, **kwargs) # 把data的数据加缓存 # 1 先去缓存拿数据 banner_list=cache.get('banner_list') if not banner_list: print('走数据库了') # 缓存中没有,去数据库拿 response = super().list(request, *args, **kwargs) # 加到缓存 cache.set('banner_list',response.data,60*60*24) return response return Response(data=banner_list)
# 1 异步任务框架,执行异步任务,执行延迟任务,执行定时任务 # 2 Celery is a project with minimal funding,
so we don’t support Microsoft Windows.
Please don’t open any issues related to that platform. # 3 使用 -pip install celery # 4 两种结构
#1 只写一个py文件,内容如下celery_task.py: from celery import Celery broker='redis://127.0.0.1:6379/1' #broker任务队列 backend='redis://127.0.0.1:6379/2' # 结构存储,执行完的结果存在这 app=Celery(__name__,broker=broker,backend=backend) #添加任务(使用这个装饰器装饰,@app.task) @app.task def add(x,y): print(x,y) return x+y
# 2启动worker # 用命令来执行 非windows 命令:celery worker -A celery_task -l info windows: pip3 install eventlet celery worker -A celery_task -l info -P eventlet # 注意:版本区别: # 命令:celery -A 模块名 worker -l info -P eventlet # 3.x及以前: # celery worker -A 模块名 -l info -P eventlet celery worker -A celery_task -l info -P eventlet # 需要切换到指定文件夹 # 4.x及以后: # celery -A 模块名 worker -l info -P eventlet celery -A celery_task worker -l info -P eventlet
# 3 添加任务 from celery_task import add # add(3,4) # 直接执行,不会被添加到broker中 ret=add.delay(5,4) #向broker中添加一个任务 print(ret)
# 4 查看任务执行结果 from celery_task import app from celery.result import AsyncResult id = '3e397fd7-e0c1-4c5c-999c-2655a96793bb' 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 新建一个包,叫celery_task -celery_task -__init__.py -celery.py -task1.py -task2.py # 2 celery.py from celery import Celery broker='redis://127.0.0.1:6379/1' #broker任务队列 backend='redis://127.0.0.1:6379/2'# 结构存储,执行完的结果存在这 app=Celery(__name__,broker=broker,backend=backend,include=['celery_task.task1','celery_task.task2']) # 3 task1.py from .celery import app @app.task def add(x,y): print(x,y) return x+y # 4 task2.py from .celery import app @app.task def mutile(x,y): print(x,y) return x*y # 5 添加任务(异步任务,延迟任务) from celery_task.task1 import add from celery_task.task2 import mutile # 提交异步 ret=add.delay(6,7) print(ret) # 2d4ad592-9548-4c7c-8df4-7f8583e8a1b1 # 提交延迟任务 from datetime import datetime, timedelta # 需要utc时间 eta=datetime.utcnow() + timedelta(seconds=10) ret=add.apply_async(args=(240, 50), eta=eta) print(ret) # 6获取结果同上
#1 celery.py from celery import Celery broker='redis://127.0.0.1:6379/1' #broker任务队列 backend='redis://127.0.0.1:6379/2' # 结构存储,执行完的结果存在这 app=Celery(__name__,broker=broker,backend=backend,include=['celery_task.task1','celery_task.task2']) # 执行定时任务 # 时区 app.conf.timezone = 'Asia/Shanghai' # 是否使用UTC app.conf.enable_utc = False # 任务的定时配置 from datetime import timedelta from celery.schedules import crontab app.conf.beat_schedule = { 'add-task': { 'task': 'celery_task.task1.add', # 'schedule': timedelta(seconds=3), 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点 'args': (300, 150), } } # 2 启动worker,启动beat 这两个都必须cd到celery包所在的位置才能启动 # celery worker -A celery_task -l info -P eventlet # 3.x版本以前 celery -A celery_task worker -l info -P eventlet # 4.x版本以后 # celery beat -A celery_task -l info # 3.x版本以前 celery -A celery_task beat -l info # 4.x版本以后
celery框架django项目工作流程 1)加载django配置环境 2)创建Celery框架对象app,配置broker和backend,得到的app就是worker 3)给worker对应的app添加可处理的任务函数,用include配置给worker的app 4)完成提供的任务的定时配置app.conf.beat_schedule 5)启动celery服务,运行worker,执行任务 6)启动beat服务,运行beat,添加任务
# 重点:由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置项目的根目录下
Celery配置
# 一、加载django配置环境 import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffyapi.settings.dev") django.setup() # 二、加载celery配置环境 from celery import Celery # broker broker = 'redis://127.0.0.1:6379/0' # backend backend = 'redis://127.0.0.1:6379/1' # worker 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 = { 'update-banner-list': { 'task': 'celery_task.tasks.update_banner_list', 'schedule': timedelta(seconds=10), 'args': (), } }
celery在django项目中的目录结构
必须放在更目录路径下(大luffyapi文件夹下)
1.加载django环境 # celery.py from celery import Celery # 加载django环境 import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffyapi.settings.dev") django.setup() broker='redis://127.0.0.1:6379/1' #broker任务队列 backend='redis://127.0.0.1:6379/2' # 结构存储,执行完的结果存在这 app=Celery(__name__,broker=broker,backend=backend,include=['celery_task.home_task',]) # 执行定时任务 # 时区 app.conf.timezone = 'Asia/Shanghai' # 是否使用UTC app.conf.enable_utc = False # 任务的定时配置 from datetime import timedelta from celery.schedules import crontab app.conf.beat_schedule = { 'add-task': { 'task': 'celery_task.home_task.banner_update', 'schedule': timedelta(seconds=30), } } 2.cd到大luffyapi路径下:启动worker,启动beat 3.提交任务 # home_task.py from .celery import app @app.task def banner_update(): from home import serializer from home import models from django.conf import settings from django.core.cache import cache queryset_banner = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('display_order')[ :settings.BANNER_COUNTER] serializer_banner=serializer.BannerModelSerilaizer(instance=queryset_banner,many=True) # print(serializer_banner.data) for banner in serializer_banner.data: banner['img']='http://127.0.0.1:8000'+banner['img'] cache.set('banner_list',serializer_banner.data) # banner_list=cache.get('banner_list') # print(banner_list) return True
# ForeignKey的字段: -related_name 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。 -related_query_name :反向查询操作时,使用的连接前缀,用于替换表名。 ################1 course/models.py from luffyapi.utils.models import BaseModel # 实际路飞课程相关表,以免费课为例 class CourseCategory(BaseModel): """分类 python,linux,go, 网络安全 跟课程是一对多的关系 """ name = models.CharField(max_length=64, unique=True, verbose_name="分类名称") class Meta: db_table = "luffy_course_category" verbose_name = "分类" verbose_name_plural = verbose_name def __str__(self): return "%s" % self.name class Course(BaseModel): """课程""" course_type = ( (0, '付费'), (1, 'VIP专享'), (2, '学位课程') ) level_choices = ( (0, '初级'), (1, '中级'), (2, '高级'), ) status_choices = ( (0, '上线'), (1, '下线'), (2, '预上线'), ) # 原始字段 name = models.CharField(max_length=128, verbose_name="课程名称") course_img = models.ImageField(upload_to="courses", max_length=255, verbose_name="封面图片", blank=True, null=True) course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付费类型") # 使用这个字段的原因 brief = models.TextField(max_length=2048, verbose_name="详情介绍", null=True, blank=True) level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name="难度等级") pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) period = models.IntegerField(verbose_name="建议学习周期(day)", default=7) attachment_path = models.FileField(upload_to="attachment", max_length=128, verbose_name="课件路径", blank=True, null=True) status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0) # 优化字段 students = models.IntegerField(verbose_name="学习人数", default=0) sections = models.IntegerField(verbose_name="总课时数量", default=0) pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0) # 关联字段 teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师",db_constraint=False) course_category = models.ForeignKey("CourseCategory", on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True,verbose_name="课程分类") class Meta: db_table = "luffy_course" verbose_name = "课程" verbose_name_plural = "课程" def __str__(self): return "%s" % self.name class Teacher(BaseModel): """导师 跟课程一对多,关联字段写在课程表中 """ role_choices = ( (0, '讲师'), (1, '导师'), (2, '班主任'), ) name = models.CharField(max_length=32, verbose_name="导师名") role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="导师身份") title = models.CharField(max_length=64, verbose_name="职位、职称") signature = models.CharField(max_length=255, verbose_name="导师签名", help_text="导师签名", blank=True, null=True) image = models.ImageField(upload_to="teacher", null=True, verbose_name="导师封面") brief = models.TextField(max_length=1024, verbose_name="导师描述") class Meta: db_table = "luffy_teacher" verbose_name = "导师" verbose_name_plural = verbose_name def __str__(self): return "%s" % self.name class CourseChapter(BaseModel): """章节 章节跟课程是一(课程)对多(章节多) """ course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE, verbose_name="课程名称",db_constraint=False) chapter = models.SmallIntegerField(verbose_name="第几章", default=1) name = models.CharField(max_length=128, verbose_name="章节标题") summary = models.TextField(verbose_name="章节介绍", blank=True, null=True) pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) class Meta: db_table = "luffy_course_chapter" verbose_name = "章节" verbose_name_plural = verbose_name def __str__(self): return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) class CourseSection(BaseModel): """课时 章节和课时是一对多的关系,关联字段写在多的一方,课时 """ section_type_choices = ( (0, '文档'), (1, '练习'), (2, '视频') ) chapter = models.ForeignKey("CourseChapter", related_name='coursesections', on_delete=models.CASCADE,verbose_name="课程章节",db_constraint=False) name = models.CharField(max_length=128, verbose_name="课时标题") orders = models.PositiveSmallIntegerField(verbose_name="课时排序") section_type = models.SmallIntegerField(default=2, choices=section_type_choices, verbose_name="课时种类") section_link = models.CharField(max_length=255, blank=True, null=True, verbose_name="课时链接",help_text="若是video,填vid,若是文档,填link") duration = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) # 仅在前端展示使用 pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True) free_trail = models.BooleanField(verbose_name="是否可试看", default=False) class Meta: db_table = "luffy_course_Section" verbose_name = "课时" verbose_name_plural = verbose_name def __str__(self): return "%s-%s" % (self.chapter, self.name) ################2 utils/models/BaseModel from django.db import models # 后期课程表,轮播图表,都会用到这些字段 class BaseModel(models.Model): is_delete = models.BooleanField(default=False, verbose_name='是否删除') is_show = models.BooleanField(default=True, verbose_name='是否展示') # 修改成这样 created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间') orders = models.IntegerField() class Meta: abstract=True # 一定不要忘了 ################3 数据迁移 python manage.py makemigrations python manage.py migrate
# 不要忘了注册路由 # FreeCourse.vueclass="course"> class="main">class="condition">class="cate-list">
- class="title">课程分类:
- class="this">全部
- Python
- Linux运维
- Python进阶
- 开发工具
- Go语言
- 机器学习
- 技术生涯
class="ordering">
- class="title">筛 选:
- class="default this">默认
- class="hot">人气
- class="price">价格
class="condition-result">共21个课程
class="course-list">class="course-item">class="course-image">"@/assets/img/course-cover.jpeg" alt="">
class="course-info">Python开发21天入门
"@/assets/img/avatar1.svg" alt="">100人已加入学习
class="teather-info">Alex 金角大王 老男孩Python教学总监 共154课时/更新完成
class="lesson-list">
- class="lesson-title">01 | 第1节:初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码
- class="lesson-title">01 | 第1节:初识编码初识编码
class="pay-box"> class="discount-type">限时免费 class="discount-price">¥0.00元 class="original-price">原价:9.00元 class="buy-now">立即购买class="course-item">class="course-image">"@/assets/img/course-cover.jpeg" alt="">
class="course-info">Python开发21天入门
"@/assets/img/avatar1.svg" alt="">100人已加入学习
class="teather-info">Alex 金角大王 老男孩Python教学总监 共154课时/更新完成
class="lesson-list">
- class="lesson-title">01 | 第1节:初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码
- class="lesson-title">01 | 第1节:初识编码初识编码
class="pay-box"> class="discount-type">限时免费 class="discount-price">¥0.00元 class="original-price">原价:9.00元 class="buy-now">立即购买class="course-item">class="course-image">"@/assets/img/course-cover.jpeg" alt="">
class="course-info">Python开发21天入门
"@/assets/img/avatar1.svg" alt="">100人已加入学习
class="teather-info">Alex 金角大王 老男孩Python教学总监 共154课时/更新完成
class="lesson-list">
- class="lesson-title">01 | 第1节:初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码
- class="lesson-title">01 | 第1节:初识编码初识编码
class="pay-box"> class="discount-type">限时免费 class="discount-price">¥0.00元 class="original-price">原价:9.00元 class="buy-now">立即购买class="course-item">class="course-image">"@/assets/img/course-cover.jpeg" alt="">
class="course-info">Python开发21天入门
"@/assets/img/avatar1.svg" alt="">100人已加入学习
class="teather-info">Alex 金角大王 老男孩Python教学总监 共154课时/更新完成
class="lesson-list">
- class="lesson-title">01 | 第1节:初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码初识编码 class="free">免费
- class="lesson-title">01 | 第1节:初识编码
- class="lesson-title">01 | 第1节:初识编码初识编码
class="pay-box"> class="discount-type">限时免费 class="discount-price">¥0.00元 class="original-price">原价:9.00元 class="buy-now">立即购买
-- 老师表 -- 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);
# 路由 router = SimpleRouter() router.register('categories', views.CourseCategoryView, 'categories') router.register('free', views.CouresView, 'free') urlpatterns = [ path('', include(router.urls)), ] # 视图views.py from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin from . import models from . import serializer class CourseCategoryView(GenericViewSet,ListModelMixin): queryset = models.CourseCategory.objects.filter(is_delete=False,is_show=True).order_by('orders') serializer_class = serializer.CourseCategorySerializer # 序列化类 from rest_framework import serializers from . import models class CourseCategorySerializer(serializers.ModelSerializer): class Meta: model=models.CourseCategory fields=['id','name']
# 路由 router = SimpleRouter() router.register('categories', views.CourseCategoryView, 'categories') router.register('free', views.CouresView, 'free') urlpatterns = [ path('', include(router.urls)), ] # 视图 views.py from .paginations import PageNumberPagination from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter,SearchFilter from .filters import MyFilter class CouresView(GenericViewSet,ListModelMixin,RetrieveModelMixin): queryset = models.Course.objects.filter(is_delete=False,is_show=True).order_by('orders') serializer_class = serializer.CourseModelSerializer pagination_class = PageNumberPagination filter_backends=[DjangoFilterBackend,OrderingFilter] ordering_fields=['id', 'price', 'students'] filter_fields=['course_category','students'] ###serializer from rest_framework import serializers from . import models class CourseCategorySerializer(serializers.ModelSerializer): class Meta: model = models.CourseCategory fields = ['id', 'name'] class TeacherSerializer(serializers.ModelSerializer): class Meta: model = models.Teacher fields = ('name', 'role_name', 'title', 'signature', 'image', 'brief') class CourseModelSerializer(serializers.ModelSerializer): # 子序列化的方式 teacher = TeacherSerializer() class Meta: model = models.Course fields=[ 'id', 'name', 'course_img', 'brief', 'attachment_path', 'pub_sections', 'price', 'students', 'period', 'sections', 'course_type_name', 'level_name', 'status_name', 'teacher', 'section_list', ] # models.py class Course(BaseModel): ... @property def course_type_name(self): return self.get_course_type_display() @property def level_name(self): return self.get_level_display() @property def status_name(self): return self.get_status_display() @property def section_list(self): ll=[] # 根据课程取出所有章节(正向查询,字段名.all()) course_chapter_list=self.coursechapters.all() for course_chapter in course_chapter_list: # 通过章节对象,取到章节下所有的课时(反向查询) # course_chapter.表名小写_set.all() 现在变成了course_chapter.coursesections.all() course_sections_list=course_chapter.coursesections.all() for course_section in course_sections_list: ll.append({ 'name': course_section.name, 'section_link': course_section.section_link, 'duration': course_section.duration, 'free_trail': course_section.free_trail, }) if len(ll)>=4: return ll return ll class Teacher(BaseModel): ... def role_name(self): # 返回角色的中文 return self.get_role_display()
''' 排序: from rest_framework.filters import OrderingFilter,SearchFilter 按id正序倒叙排序,按price正序倒叙排列 使用:http://127.0.0.1:8000/course/free/?ordering=-id 配置类: filter_backends=[OrderingFilter] 配置字段: ordering_fields=['id','price'] 内置过滤: from rest_framework.filters import OrderingFilter,SearchFilter 使用:http://127.0.0.1:8000/course/free/?search=39 按照price过滤(表自有的字段直接过滤) 配置类: filter_backends=[SearchFilter] 配置字段: search_fields=['price'] 扩展:django-filter 安装: 支持自由字段的过滤还支持外键字段的过滤 from django_filters.rest_framework import DjangoFilterBackend http://127.0.0.1:8000/course/free/?course_category=1 # 过滤分类为1 (python的所有课程) 配置类: filter_backends=[DjangoFilterBackend] 配置字段: filter_fields=['course_category'] '''
# filters.py # 自定义过滤规则 # OrderingFilter,SearchFilter都继承BaseFilterBackend # 所以我们写的类也需要继承BaseFilterBackend from rest_framework.filters import BaseFilterBackend class MyFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): # 写我们需要的过滤规则 # params=request.GET.get('teacher') # queryset.filter('''''') return queryset[:1] # 使用:在视图类中配置 filter_backends=[DjangoFilterBackend,OrderingFilter,MyFilter]
总结
# 1 celery定时更新首页轮播图接口(只要涉及到加入缓存的接口,一定涉及到更新---(双写一致性)),执行异步任务,定时任务(apscheduler)和延时任务 # 2 课程群查接口 -继承哪个类: -如果想改路由(自动生成路由:ViewSetMixin), -如果想跟数据库和序列化类打交道:GenericAPIView -增删查改(5个视图扩展类) -序列化类怎么写 -子序列化:teacher = TeacherSerializer() -想序列化某个没有的字段,可以写在model类种或者序列化类中 -可能会重写validata或者create或者update方法 # 3 排序(继承:GenericAPIView,ListModelMixin) -在视图类种配置 filter_backends=[OrderingFilter] ordering_fields=['id', 'price', 'students'] -使用:http://127.0.0.1:8000/course/free/?ordering=-id # 4 过滤 (继承:GenericAPIView,ListModelMixin) -在视图类种配置 filter_backends=[SearchFilter] search_fields=['id','name'] -使用:http://127.0.0.1:8000/course/free/?search=39 # 5 自定义过滤 -写一个类,继承BaseFilterBackend,重写filter_queryset方法,返回queryset对象,返回的对象就是过滤完的 -使用跟过滤配置一样 # 6 django-filter -下载,配置在视图类中filter_backends=[DjangoFilterBackend] -配置字段:filter_fields=['course_category'],支持自由字段和连表字段
# 借助django-filter实现区间过滤 # 实现区间过滤 ##########1 filters.py from django_filters import filters from django_filters import FilterSet class CourseFilterSet(FilterSet): # 课程的价格范围要大于min_price,小于max_price min_price = filters.NumberFilter(field_name='price', lookup_expr='gt') max_price = filters.NumberFilter(field_name='price', lookup_expr='lt') class Meta: model=models.Course fields=['course_category'] #####2 视图类中配置 -filter_backends=[DjangoFilterBackend] # 配置类:(自己写的类) -filter_class = CourseFilterSet
class="course">
class="main">
class="condition">
class="cate-list">
- class="title">课程分类:
- class="filter.course_category==0?'this':''" @click="filter.course_category=0">全部
- class="filter.course_category==category.id?'this':''" v-for="category in category_list"
@click="filter.course_category=category.id" :key="category.name">{{category.name}}
class="ordering">
- class="title">筛 选:
- class="default" :class="(filter.ordering=='id' || filter.ordering=='-id')?'this':''"
@click="filter.ordering='-id'">默认
- class="hot" :class="(filter.ordering=='students' || filter.ordering=='-students')?'this':''"
@click="filter.ordering=(filter.ordering=='-students'?'students':'-students')">人气
- class="price"
:class="filter.ordering=='price'?'price_up this':(filter.ordering=='-price'?'price_down this':'')"
@click="filter.ordering=(filter.ordering=='-price'?'price':'-price')">价格
class="condition-result">共{{course_total}}个课程
class="course-list">
class="course-item" v-for="course in course_list" :key="course.name">
class="course-image">
"course.course_img" alt="">
class="course-info">
"'/free/detail/'+course.id">{{course.name}}
"@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习
class="teather-info">
{{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}
if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时
else>共{{course.sections}}课时/更新完成
class="section-list">
- for="(section, key) in course.section_list" :key="section.name"><span
class="section-title">0{{key+1}} | {{section.name}}
class="free" v-if="section.free_trail">免费
class="pay-box">
if="course.discount_type">
class="discount-type">{{course.discount_type}}
class="discount-price">¥{{course.real_price}}元
class="original-price">原价:{{course.price}}元
else class="discount-price">¥{{course.price}}元
class="buy-now">立即购买
class="course_pagination block">
pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="filter.page"
:page-sizes="[2, 3, 5, 10]"
:page-size="filter.page_size"
layout="sizes, prev, pager, next"
:total="course_total">
# 1 新建FreeCourseDetail.vue # 2 配置路由 { path: '/free/detail/:pk', name: 'FreeCourseDetail', component: FreeCourseDetail }, #3 安装 vue-video播放器 -cnpm install vue-video-player -在main.js中配置 // vue-video播放器 require('video.js/dist/video-js.css'); require('vue-video-player/src/custom-theme.css'); import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer); #4 组件class="detail"> class="main">class="course-info">class="wrap-left">class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions" @play="onPlayerPlay($event)" @pause="onPlayerPause($event)"> class="wrap-right">class="course-name">{{course_info.name}}
class="data">{{course_info.students}}人在学 课程总时长:{{course_info.sections}}课时/{{course_info.pub_sections}}小时 难度:{{course_info.level_name}}
class="sale-time">class="sale-type">价格 class="original_price">¥{{course_info.price}}
class="expire">
class="buy">class="buy-btn">class="course-tab">class="tab-list">
- class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍
- class="tabIndex==2?'active':''" @click="tabIndex=2">课程章节 class="tabIndex!=2?'free':''">(试学)
- class="tabIndex==3?'active':''" @click="tabIndex=3">用户评论
- class="tabIndex==4?'active':''" @click="tabIndex=4">常见问题
class="course-content">class="course-tab-list">class="tab-item" v-if="tabIndex==1">class="course-brief" v-html="course_info.brief_text">class="tab-item" v-if="tabIndex==2">class="tab-item-title">class="chapter">课程章节
class="chapter-length">共{{course_chapters.length}}章 {{course_info.sections}}个课时
class="chapter-item" v-for="chapter in course_chapters" :key="chapter.name">class="chapter-title">
"@/assets/img/enum.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}
class="section-list">
- class="section-item" v-for="section in chapter.coursesections" :key="section.name">
class="name">class="index">{{chapter.chapter}}-{{section.orders}} {{section.name}}class="free" v-if="section.free_trail">免费
class="time">{{section.duration}}
"@/assets/img/chapter-player.svg">
class="tab-item" v-if="tabIndex==3"> 用户评论class="tab-item" v-if="tabIndex==4"> 常见问题class="course-side">class="teacher-info">class="side-title">授课老师
class="teacher-content">class="cont1">"course_info.teacher.image">
class="name">class="teacher-name">{{course_info.teacher.name}} {{course_info.teacher.title}}
class="teacher-title">{{course_info.teacher.signature}}
class="narrative">{{course_info.teacher.brief}}
#1 urls.py router.register('chapters', views.CourseChapterView, 'coursechapter') # 2 views.py class CourseChapterView(GenericViewSet,ListModelMixin): queryset = models.CourseChapter.objects.filter(is_delete=False,is_show=True) serializer_class = serializer.CourseChapterSerializer # 可以按照课程id来查 因为course属于外键字段 所以查到的是课程的id filter_backends = [DjangoFilterBackend] filter_fields = ['course'] # 3 serializer.py class CourseSectionSerializer(serializers.ModelSerializer): class Meta: model=models.CourseSection fields=['name','orders','duration','free_trail','section_link','section_type_name'] class CourseChapterSerializer(serializers.ModelSerializer): # 子序列化的方式 coursesections=CourseSectionSerializer(many=True) class Meta: model=models.CourseChapter fields=['name','summary','chapter','coursesections'] # 4 models.py class CourseSection(BaseModel): ... def section_type_name(self): return self.get_section_type_display()
# 1 注册七牛云,实名认证 https://www.qiniu.com/ # 2 登录---》管理控制台---》对象存储 # 3 新建空间----》上传视频--》拿到视频连接
# urls.py router.register('search', views.CouresSearchView, 'search') # views.py class CouresSearchView(GenericViewSet,ListModelMixin): queryset = models.Course.objects.filter(is_delete=False,is_show=True) serializer_class = serializer.CourseModelSerializer pagination_class = PageNumberPagination filter_backends=[SearchFilter] search_fields=['name']
# 0 在Head.vue中相应的配置 # 1 vue带给另一个页面值 -路由:path: '/free/detail/:pk',this.$route.params.pk 从路径中取值 -search?word=dsa 去this.$route.query.word 从?后面的取 # 2 样式class="search-course course">总共搜索到 {{course_total}}课程
class="main">if="course_list.length > 0" class="course-list">class="course-item" v-for="course in course_list" :key="course.name">class="course-image">"course.course_img" alt="">
class="course-info">
"'/free/detail/'+course.id">{{course.name}} "@/assets/img/avatar1.svg" alt="">{{course.students}}人已加入学习
class="teather-info"> {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}} if="course.sections>course.pub_sections">共{{course.sections}}课时/已更新{{course.pub_sections}}课时 else>共{{course.sections}}课时/更新完成
class="section-list">
- for="(section, key) in course.section_list" :key="section.name"><span class="section-title">0{{key+1}} | {{section.name}} class="free" v-if="section.free_trail">免费
class="pay-box">if="course.discount_type"> class="discount-type">{{course.discount_type}} class="discount-price">¥{{course.real_price}}元 class="original-price">原价:{{course.price}}元else class="discount-price">¥{{course.price}}元 class="buy-now">立即购买else style="text-align: center; line-height: 60px"> 没有搜索结果class="course_pagination block">pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="filter.page" :page-sizes="[2, 3, 5, 10]" :page-size="filter.page_size" layout="sizes, prev, pager, next" :total="course_total">
# 沙箱环境:https://openhome.alipay.com/platform/appDaily.htm # https://opendocs.alipay.com/open/291/106097/ 下载软件,生成秘钥(公钥私钥) # 取出公钥,配置再支付宝平台,它会生成一个支付宝公钥 # 安装alipay模块 pip install python-alipay-sdk # Alipay的sdk文档 https://github.com/fzlee/alipay/ from alipay import AliPay # app_private_key_string = open("/path/to/your/private/key.pem").read() # alipay_public_key_string = open("/path/to/alipay/public/key.pem").read() app_private_key_string = """-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEApkFnp38PSslhn4wvRarL40+WtVpan6tVT55FWzAzLfQ8PfqZE2qW7mCJy1/zM07+S5L+X0pC+F7BlT+0z4rlxraG5D23khWfHb4x+uDi0Wd94eaVB0PzQXCYYBChuORvNN5YLTspeP7rNQ2OcdpGx2KUzo9w59dVW+AweblFl7GqPkRSC0P5XbkJtwnEAd0Nc08HzJR0OWZT9HRFOA7coaude9uSwDsXmsdkSNGsGDcaGEY6aGk2Tj9Fyni9LJJcOXwGrUuGDpdcP2tYePrFUGMDml/feFR9QGUbExWh/ZNpb9lKLwOtXOhQj9slilp+YJek/rLlSgW9K1WrYpuuBwIDAQABAoIBADN1mRzKAjS2wmW84UDiDbus/cviTJyRTpWXOoZwE9dMen0AnPLakh70eJIff8pI0AMaW2upM7NmuOp2ToPSzS5FftkUlUY9NQPiw9uQUgRY0Sjj0wrtqFSAAlnxq+zrn9QwYgCWCE8wMCM6r/Vjh3bdd4u78EmCaCRI7xguFXFPB9NY9oCFiwsTFJtXPZo+DSIQjqIDnh8YtV3NzrA3Ln3DsKMAr+vnPMeljAL5US6Rt5tOQY0qa9bUi6RqQwTLADcANd49YOvlr7OLjwi5jkeJ16j1nKgpasAIg9V+rF085u+PEKkLL3WJHwmhiIxrA11bxCnm1fRdfHbyWZKY9yECgYEA0k3wS7A7rzhtpznc5ZfVGWIEQ5IahRkqVGbc6wFIXKXnypOtGKAFiRmEPwm9xRurlo8TJ1bjK4D4ljEANJhX2NnIZfK3YDdNEN6D4GnEvPFR641M8M47Z7ItvdRKre8P2/ThXhkMnq5/1y7e87xvE6/a1Tr44+hclHvQWPRrsPcCgYEAymFEy0/2xAX+si4S8pzrgLfBg3Ehjan8T6JZZNCKhR7b/IT+3/AWJimzEL6jYQMlhy7QzZbRb1ssIIzqPFcFQYjQE0n6fswJuN4+jayW9Jtjvar/zhHFjW3EODR5yEDTvS3CHqQLeG3ce+srMJSxVDJR2V9ortFeb+VPbWZ+t3ECgYEAxPYvvoNwcpuzvvGnW/RGpb4x5iL46Xz3MxMfho2t+u961jRW4oBEjvGx9OQnsmpG2vxm4Oo0WnMw3mFIIvonFDZrxGd8rQU+DTWJZ21Hz/lnUugEjmdoJacvxeEEjEAgp02CoQFu21Ls8li4gKgTk+mYVyojHjhqNLp9GELadWMCgYEAi0RSXgLCEnT5p03zdgcsPOC3ByfT6jO+0GItWCX2HNN2mRhAeIQ0CcEKW4yEy56ptZQu1jtiFlpMTH4MNse/czCd15hCC/2G9zPhIgdRvjQsd/nznLA4HTIbJH5gC8EotHeHrSRATHh1kMTtbLn2KbWTA54XYK3tad0IQoWUz9ECgYBBc+kCJsb0shaRIRP5nmeb5tZCH7Yq9DgriwcIlK74cxW9fvL94n91Y6M8vSZ6MC7vF5wmcXMj3K1of04qizKbbKxCCYeOIHhTh0dB+ualDxu5WBG/6mLWR3HVHazqRVri9qoZ/3zNDx5CRNi81VpzOPmVtuos5d9sZZaNR3lkRQ== -----END RSA PRIVATE KEY----- """ alipay_public_key_string = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwU9mMZHHlQPE9FcxVtXOXhbWCtuDZLJRVCiofdbTVmRXrx47yGniPehwcKIsqhzhEaXBG2QhpIZUL8YsCav0mkrppoRvWOytuGyxNRESo8I6DWRs0aCq6P3AuiD9kSXET4dpAuRYT/+JrMXIZTycEts6vYYNAT9QivXJoa2FmiCQBAL3HP7F36pby9VstObilxXQcoBBJwEYGf2TK6moFFZ1dkloRr5Cfk/G82DpuVfrt1gr4OuIDWtcE3MZTrvDgTqtkRuwGF76FY3+8xUCUbJs1dL5cXYN7/b3jPcXVcdKXFj4WrOQd42ofE1BJWMxBW7L3Qlxue1vy+NGx/CuKwIDAQAB -----END PUBLIC KEY----- """ alipay = AliPay( appid="2016092000554611", app_notify_url='http://127.0.0.1:8000/home/', # the default notify path app_private_key_string=app_private_key_string, # alipay public key, do not use your own public key! alipay_public_key_string=alipay_public_key_string, sign_type="RSA2", # RSA or RSA2 debug=True # False by default ) alipay_url='https://openapi.alipaydev.com/gateway.do?' order_string = alipay.api_alipay_trade_page_pay ( out_trade_no="20161112www4334", total_amount=9999, subject='韩红版充气娃娃', return_url="https://www.luffycity.com/free-course", notify_url="https://www.luffycity.com/free-course" ) print(alipay_url+order_string)
补充
# 1 全文检索引擎:elasticsearch,java封装的一个数据库,专注于大数据存储和搜索
总结
# 1 区间过滤: -自己写过滤类 -使用django-filter -在视图类中配置filter_backends=[DjangoFilterBackend] -写一个类,继承FilterSet min_price = filters.NumberFilter(field_name='price', lookup_expr='gt') max_price = filters.NumberFilter(field_name='price', lookup_expr='lt') -在视图类中配置:filter_class = CourseFilterSet # 2 章节群查 -群查接口+过滤 filter_backends = [DjangoFilterBackend] filter_fields = ['course'] # 3 为了解耦合,把搜索接口单独写的 # 4 支付宝支付
al_pay -pem -__init__.py -pay.py -setting.py #__init__.py from .pay import alipay,gateway #pay.py from alipay import AliPay from . import setting alipay = AliPay( appid=setting.APPID, app_notify_url=None, # the default notify path app_private_key_string=setting.APP_PRIVATE_KEY_STRING, # alipay public key, do not use your own public key! alipay_public_key_string=setting.ALIPAY_PUBLIC_KEY_STRING, sign_type=setting.SIGN_TYPE, # RSA or RSA2 debug=setting.DEBUG # False by default ) gateway=setting.GATEWAY # setting.py import os APPID="2016092000554611" APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','private_key.pem')).read() ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','al_public_key.pem')).read() SIGN_TYPE='RSA2' DEBUG=True GATEWAY='https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
# 订单表分析 -订单表 -订单详情 from django.db import models from user.models import User from course.models import Course # 订单表 class Order(models.Model): """订单模型""" 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="支付时间") # 一个用户可以下多个订单,一个订单只属于一个用户,一对多的关系,关联字段写在多个一方,写在order方 user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户") created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间') 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(models.Model): """订单详情""" 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.SET_NULL, db_constraint=False, verbose_name="课程",null=True) 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): try: return "%s的订单:%s" % (self.course.name, self.order.out_trade_no) except: return super().__str__()
# 1 支付接口(生成订单,,生成支付连接,返回支付连接) -order表和orderdetail表插入数据,重写create方法 -生成订单号(uuid) -登录后才能做(jwt认证) -当前登录用户就是下单用户,存到order表中 -下了三个课程,总价格100,前端提交的价格是99,异常处理 ''' #1)订单总价校验 # 2)生成订单号 # 3)支付用户:request.user # 4)支付链接生成 # 5)入库(两个表)的信息准备 ''' # 2 支付宝异步回调的post接口(验证签名,修改订单状态) # 3 当支付宝get回调前端,vue组件一创建,立马向后端发一个请求(get)
# 1 前端传什么格式数据 -{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,} # 2 后端接到数据要校验 -course:[1,2,3]===》course:[obj1,obj2,obj3] -在序列化类中: course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True) # 3 在序列化类的validate中处理一堆逻辑 ''' # 1)订单总价校验: -一个个课程价格取出来累加看是否等于传入的总价格 # 2)生成订单号 -通过uuid生成 # 3)支付用户:request.user -通过视图和序列化类之间的桥梁context对象传递 -重新视图类中的create方法,把request对象放入context -self.context.get('request').user # 4)支付链接生成 -导入封装的支付宝支付,生成 order_string = alipay.api_alipay_trade_page_pay ( out_trade_no=out_trade_no, total_amount=total_amout, subject=subject, return_url=settings.RETURN_URL, # get回调,前台地址 notify_url=settings.NOTIFY_URL # post回调,后台地址 ) # 5)入库(两个表)的信息准备 -把user放入attrs中 -把订单号,放入attrs中 attrs['user']=user attrs['out_trade_no']=out_trade_no self.context['pay_url']=pay_url ''' # 4 重写序列化类的create方法 -把course_list弹出来 -order=models.Order.objects.create(**validated_data)
# urls.py router = SimpleRouter() router.register('pay', views.PayView, 'pay') urlpatterns = [ path('', include(router.urls)), ] # views.py class PayView(GenericViewSet,CreateModelMixin): authentication_classes = [JSONWebTokenAuthentication,] permission_classes = [IsAuthenticated,] queryset = models.Order.objects.all() serializer_class = serializer.OrderSerializer # 重写create方法 def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data,context={'request':request}) serializer.is_valid(raise_exception=True) self.perform_create(serializer) return Response(serializer.context.get('pay_url')) # serializer.py class OrderSerializer(serializers.ModelSerializer): # 前端传什么数据过来{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,} # user字段需要,但是不是传的,使用了jwt # 需要把course:[1,2,3] 处理成 course:[obj1,obj2,obj3] # 课时:[1,4,6,]===>课时:[obj1,obj4,obj6,] # course=serializers.CharField() course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True) class Meta: model = models.Order fields = ['total_amount','subject','pay_type','course'] extra_kwargs={ 'total_amount':{'required':True}, 'pay_type': {'required': True}, } def _check_price(self,attrs): total_amount=attrs.get('total_amount') course_list=attrs.get('course') total_price=0 for course in course_list: total_price+=course.price if total_price!=total_amount: raise ValidationError('价格不合法') return total_amount def _gen_out_trade_no(self): import uuid return str(uuid.uuid4()).replace('-','') def _get_user(self): # 需要request对象(需要视图通过context把reuqest对象传入。重写create方法) request=self.context.get('request') return request.user def _gen_pay_url(self,out_trade_no,total_amout,subject): # total_amout是Decimal类型,识别不了,需要转换成float类型 from luffyapi.libs.al_pay import alipay,gateway order_string = alipay.api_alipay_trade_page_pay ( out_trade_no=out_trade_no, total_amount=float(total_amout), subject=subject, return_url=settings.RETURN_URL, # get回调,前台地址 notify_url=settings.NOTIFY_URL # post回调,后台地址 ) return gateway+order_string def _before_create(self,attrs,user,pay_url,out_trade_no): # 将user和订单号存到attrs中 后面直接存入数据库 attrs['user']=user attrs['out_trade_no']=out_trade_no # 这里需要通过context将支付链接传给视图类 self.context['pay_url']=pay_url def validate(self, attrs): ''' # 1)订单总价校验 # 2)生成订单号 # 3)支付用户:request.user # 4)支付链接生成 # 5)入库(两个表)的信息准备 ''' # 1)订单总价校验 total_amout = self._check_price(attrs) # 2)生成订单号 out_trade_no=self._gen_out_trade_no() # 3)支付用户:request.user user=self._get_user() # 4)支付链接生成 pay_url=self._gen_pay_url(out_trade_no,total_amout,attrs.get('subject')) # 5)入库(两个表)的信息准备 self._before_create(attrs,user,pay_url,out_trade_no) return attrs def create(self, validated_data): course_list=validated_data.pop('course') order=models.Order.objects.create(**validated_data) for course in course_list: models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price) return order
# 后端dev.py # 上线后必须换成公网地址 # 后台基URL BASE_URL = 'http://127.0.0.1:8000' # 前台基URL LUFFY_URL = 'http://127.0.0.1:8080' # 支付宝同步异步回调接口配置 # 后台异步回调接口 NOTIFY_URL = BASE_URL + "/order/success/" # 前台同步回调接口,没有 / 结尾 RETURN_URL = LUFFY_URL + "/pay/success"
# FreeCourse.vue class="buy-now" @click="buy_now(course)">立即购买 # script buy_now(course) { let token = this.$cookies.get('token') if (!token) { this.$message({ message: "您还没有登录,请先登录", }) return false } this.$axios({ method: 'post', url: this.$settings.base_url + '/order/pay/', data: { "total_amount": course.price, "subject": course.name, "pay_type": 1, "course": [ course.id, ] }, headers: {Authorization: 'jwt ' + token} } ).then(response => { console.log(response.data) let pay_url=response.data //前端发送get请求 open(pay_url,'_self') }).catch(error => { }) },
# 路由 { path: '/pay/success', name: 'PaySuccess', component: PaySuccess }, #PaySuccess.vueclass="pay-success"> class="main">class="title">class="success-tips">class="tips">您已成功购买 1 门课程!
class="order-info">class="info">订单号:{{ result.out_trade_no }}
class="info">交易号:{{ result.trade_no }}
class="info">付款时间:{{ result.timestamp }}
class="study"> 立即学习
charset=utf-8& out_trade_no=17fcf8cac17c442bbbc114df6004afff& method=alipay.trade.page.pay.return& total_amount=99.00& sign=WfK2beWFKvVaTHXpREi8HqZtFRH3JbeIvkliReYvfuhAsqaxHguARKtW6jUqUdZinm7ZSaYE1NrBRQa3%2BLquMk6uMnxE0i%2FTXIu4%2FmNTCEqSUlG8fTRPwC2%2BuU4nN1Ym0eM4puzAc2TUnEJnXCGKP9UxMifN3cjqR5BP%2B3RRngZSS4IQeogjurpfdiIolLzed%2FHTWbc4HqvWlWn9JuLmFGTtKHvRRKFr1hqq8Pj%2Fe3Al8kieDN9Q7JhEdC6F5ROo9rLlmUJtevkjI22oRScrfJl5hb%2BeYosxNg3WktmYKlF5vsKeZKKnLayAvKGoySLvaWk90x0LijHzzf2%2F8a9s3w%3D%3D& trade_no=2020072922001480160500851368& auth_app_id=2016092000554611& version=1.0& app_id=2016092000554611& sign_type=RSA2& seller_id=2088102176466324& timestamp=2020-07-29%2015%3A06%3A44
class SuccessView(APIView): def get(self,request,*args,**kwargs): out_trade_no=request.query_params.get('out_trade_no') order=models.Order.objects.filter(out_trade_no=out_trade_no).first() if order.order_status==1: return Response(True) else: return Response(False) def post(self,request,*args,**kwargs): ''' 支付宝回调接口 ''' from luffyapi.libs.al_pay import alipay from luffyapi.utils.logger import log data = request.data out_trade_no=data.get('out_trade_no',None) gmt_payment=data.get('gmt_payment',None) signature = data.pop("sign") # 验证签名 success = alipay.verify(data, signature) if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"): models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,pay_time=gmt_payment) log.info('%s订单支付成功'%out_trade_no) return Response('success') else: log.info('%s订单有问题' % out_trade_no) return Response('error')
# 前端执行 npm run build 把你写的vue代码编译成html,css,js # 后端代码 -修改setting中的pro.py -项目根路径新建一个manage_pro.py(把原来的manage.py复制改动上线的配置文件) -wsgi.py:改成线上的dev