个人博客
https://blog.csdn.net/cPen_web

ViewUI官网 https://www.iviewui.com/docs/introduce

用vue写的 ui框架:View UI

安装ViewUI

步骤:首先 创建新的项目

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview
$ vue create viewui-proj

步骤:然后 安装 viewui

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview
$ cd viewui-proj/
15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/viewui-proj (master)
$ npm install view-design
#注:或 写在网页里面
使用 <script> 全局引用
<script type="text/javascript" src="iview.min.js"></script>

步骤:仅仅只是安装了,需要全局加载 src/main.js文件

import ViewUI from 'view-design'
import 'view-design/dist/styles/iview.css'	// 导入样式

Vue.use(ViewUI);					// 激活它

步骤:安装好了后, 运行

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/viewui-proj (master)
$ npm run serve
#注:之间bootstrap里面全是样式,VueUI里面都写成了组件

步骤:在父组件里,添加修改 VueUI官网给的样式代码
网站地址 https://www.iviewui.com/components/button
图示

#注:在单文件组件里用这类<Button></Button>(组件化开发用),但HTML里用i-button(网页上开发)
知识点:一行分成24列。采用了24栅格系统,将区域进行24等分

[官网教程](https://www.iviewui.com/components/grid)
我们采用了24栅格系统,将区域进行24等分,这样可以轻松应对大部分布局问题。使用栅格系统进行网页布局,可以使页面排版美观、舒适。
我们定义了两个概念,行row和列col,具体使用方法如下:
使用row在水平方向创建一行
将一组col插入在row中
在每个col中,键入自己的内容
通过设置col的span参数,指定跨越的范围,其范围是1到24
每个row中的col总和应该为24

col必须放在row里面
通过给 row 添加 gutter 属性,可以给下属的 col 添加间距,推荐使用 (16+8n)px 作为栅格间隔。

等分成2分

	    <Row>
	        <Col span="12">col-12</Col>
	        <Col span="12">col-12</Col>
	    </Row>
	    ……

官网教程

Table 表格
Render 写法 #
通过给 columns 数据的项,设置一个函数 render,可以自定义渲染当前列,包括渲染自定义组件,它基于 Vue 的 Render 函数。
render 函数传入两个参数,第一个是 h,第二个是对象,包含 row、column 和 index,分别指当前单元格数据,当前列数据,当前是第几行。

https://www.iviewui.com/components/table#Render_XF

iview-admin

步骤:下载iview admin 源码,放到本地,再运行iview admin

码云 https://gitee.com/icarusion/iview-admin

步骤:克隆仓库地址 别人写好的 iview-admin

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview
$ git clone https://gitee.com/icarusion/iview-admin

步骤:启动服务 npm run dev

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview
$ cd iview-admin/
15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ less package.json
  ……
  "scripts": {
    "dev": "vue-cli-service serve --open",
  ……
15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ npm install				#注:下载依赖关系
15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ npm run dev				#注:启动服务
#注:自动弹出到 登录界面上去  输入任意的用户名和密码 进去

图示
步骤:本地运行ivew-admin

1. 克隆项目
git clone https://gitee.com/icarusion/iview-admin

2. 切换到iview-admin的目录下
cd iview-admin

3. 安装依赖
npm install

4. 运行项目
npm run dev

运行成功后就会在浏览器打开iview-admin的登陆页面, 输入任意密码即可登陆成功

#注:Vuex是实现多个组件共享数据的一种方案

目录结构

iview-admin
	* node_modules	安装下来的依赖模板
	* public		公共资源
	* src			源码
		* api		存放api请求
		* assets	静态资源
		* components	业务组件
		* config		配置文件
		* directive		自定义指令
		* libs			封装好的工具函数
		* locale		多语言文件
		* mock			虚拟数据
		* plugin		插件
		* router		路由
		* store			Vuex配置
		* view			页面文件
		* App.vue		根组件
		* main.js		入口js
	* tests			测试相关
	* package.json	包的配置依赖
	* vue.config.js	全局配置
#注:做成符合CMDB平台的页面,应该首先改 左边菜单栏里面的

步骤:找”QQ群”字符串

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ cd src
15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ grep "QQ群" * -R				#注:递归查找  档期目录以及当前目录下面子目录下面的所有文件
locale/lang/zh-CN.js:  join_page: 'QQ群',
locale/lang/zh-TW.js:  join_page: 'QQ群',
router/routers.js: *  hideInBread: (false) 设为true后此级路由将不会出现在面包屑中,示例看QQ群路由配置
router/routers.js:          title: 'QQ群'
#注:在router 和 locale下面,先看router 点QQ群 链接到QQ群

#注:router下 routers.js 可以看到 父子组件

步骤:修改”QQ群”内容

#注:看router下 routers.js文件
export default [
    ……
component: () => import('@/view/login/login.vue')		#注:@是别名  在vue.config.js 里设置的
    ……
#注:在view目录下面 创建sanchuang.vue文件  输入
<template>
	<p>这是三创</p>
</template>

<script>
	export default {
		name: "sanchuang"
	}
</script>
#注:src/router/routers.js文件 修改”QQ群”字符 (和原视频不一样)
export default [{
		path: '/login',
		name: 'login',
		meta: {
			title: 'Login - 登录',
			hideInMenu: true
		},
		component: () => import('@/view/login/login.vue')
	},
……
	{
		path: "/cmdb",
		name: "cmdb",
		meta: {
			title: 'CMDB',
		},
		component: Main,
		children: [{
				path: "Servers",
				name: "Servers",
				meta: {
					title: '服务列表',
				},
				component: () => import('@/view/cmdb/Servers.vue')
			},
……
#注:src/locale/lang/zh-CN.js	#注:路由和中文的对应关系
export default {
  ……
  sanchuang: '三创',			// 这个和 url名字对应 (设定的name属性)
  ……

步骤:定义父子路由 相当于这里的下拉菜单

#注:隐藏属性 src/router/routers.js文件  路由不会出现在面包屑中
		……
		meta: {
			hideInMenu: true,
			notCache: true
		},
		……
#注:写下拉菜单  模拟组件块(在组件下面 加一个可以下拉的选择的)  (src/router/routers.js文件) (复制粘贴  进行更改)

#注:更改path 、name 、title
#注:用二级路由的话,一级路由都得是Main  ,一级路由组件和Main做绑定
export default [{
		……
		path: "/cmdb",
		name: "cmdb",
		meta: {
			title: 'CMDB',
		},
		component: Main,
		children: [{								// 写children
				path: "Servers",
				name: "Servers",
				meta: {
					title: '服务列表',				// 二级路由
				},
				component: () => import('@/view/cmdb/Servers.vue')
			},
		……

https://www.iviewui.com/components/layout
在网上找好 iview 的布局 代码。接着填充里面的内容
https://www.iviewui.com/components/alert
填充 警告提示 的图标 代码 到对应位置
搜索框+2个按钮 (input输入框、Button按钮)
https://www.iviewui.com/components/table
Table表格

<Row></Row> 表示一行
……
<script>
    export default {
        data () {
            return {
                columns7: [
                    {
                        title: '姓名',		// 标题
                        // key: 'name',		// data6中元素的name,列中显示的数据
						//render  params  ==》该行的数据
// render   返回的是数据自定义的渲染内容
// return h("定义的元素标签",{元素的属性},"元素的内容"/[元素的内容])
                        render: (h, params) => {		// h习惯叫它 create element 创建元素
                            return h('div', [
								// <div><Icon type="persion"></Icon><strong>params.row.name</strong></div>
                                h('Icon', {			// 图标
                                    props: {
                                        type: 'ios-person'
                                    }
                                }),
                                h('strong', params.row.name)		// key可以不写因为这里指定了
                            ]);
                        }
                    },
                    {
                        title: 'Age',
                        key: 'age',
						render: (h,params) => {
							var ageColor = params.row.age>20?"red":"black"
							return h(
							'span',
							{
								style: {
									color: ageColor
								}
							},
							params.row.age);
						}
                    },
……
                        align: 'center',
						// render   返回的是数据自定义的渲染内容
						// return h("定义的元素标签",{元素的属性},"元素的内容"/[元素的内容])
                        render: (h, params) => {
							// <div><Button type="primary" size="small" style="marginRight:5px" onclick></Button></div>
                            return h('div', [
                                h('Button', {
                                    props: {		// props自定义属性
                                        type: 'primary',
                                        size: 'small'
                                    },
                                    style: {
                                        marginRight: '5px'
                                    },
                                    on: {
                                        click: () => {
                                            this.show(params.index)
                                        }
                                    }
                                }, 'View'),			// view元素内容
……
                data6: [
                    {
                        name: 'John Brown',
                        age: 18,
                        address: 'New York No. 1 Lake Park'
                    },
                    {
……
#注:render 是一个渲染函数(动态的创建页面)

#注:为age添加样式  大于二十就标红  (为它创建render)
……
                    {
                        title: 'Age',
                        key: 'age',
						render: (h,params) => {
							var ageColor = params.row.age>20?"red":"black"
							return h(
							'span',
							{
								style: {
									color: ageColor
								}
							},
							params.row.age);		// params.row.age元素内容
						}
                    },
……
#注:iview-admin是别人用viewui写好的一个模型
#注:平台项目运维,重点是使用iview-admin的前端,稍微改一下

#步骤:修改左侧菜单栏定制
#注:左侧菜单栏的定制,通过router显示不同的东西,locale 改变语言,编写组件,展示到不同的组件上去

#步骤:
#注:做前端项目前,先规划好路由

运维平台菜单定制

URL功能
/login登录
/logout登出
/首页
/cmdbCMDB
/cmdb/servers/主机管理
/cmdb/oper_auit操作审计
/cmdb/idcs机房管理
/task任务
/ops运维工作
/system系统管理
/uc用户中心

首页 CMDB 任务 运维工作 系统管理 用户中心

备份router目录 下的 routers.js文件
#注:view目录 页面直接做展示的 组件,components目录 公共的组件

#注:留下path: ‘*’  error_404…… 前面的url都不匹配的情况下 跳到404。把这些状态码和首页、登录 留着,其他的都删了

hideInMenu: true		#注:在Menu菜单里隐藏

步骤:建设菜单 创建组件 src/view目录下 --> 新建 cmdb目录 --> 创建Server.vue文件、OpsAudit.vue文件、Idc.vue文件

#注:src/view 一般是 展示页面的组件
#注:src/components 一般放公共的 重复复用的组件

步骤:设置路由 router目录 --> routers.js文件

#注:一级路由、二级路由、三级路由
#注:Main 是一级菜单下面,显示二级菜单用的
#注:parentView 是二级菜单往下,显示更多级的菜单用的
import Main from '@/components/main'				// 做二级菜单
import parentView from '@/components/parent-view'	// 做三级菜单

export default [{
		path: '/login',
		name: 'login',
		meta: {
			title: 'Login - 登录',
			/* hideInMenu: true */
		},
		component: () => import('@/view/login/login.vue')
	},
	{
		path: '/',
		name: '_home',
		redirect: '/home',
		component: Main,
		meta: {
			hideInMenu: true,
			notCache: true
		},
		children: [{
			path: '/home',
			name: 'home',
			meta: {
				/* hideInMenu: true, */
				title: '首页',
				notCache: true,
				icon: 'md-home'
			},
			component: () => import('@/view/single-page/home')
		}]
	},
	{
		path: '/cmdb',					// 一级菜单
		name: 'cmdb',
		meta: {
			title: 'CMDB'
		},
		component: Main,				// CMDB有二级菜单,所以指定的组件指定到Main
		children: [{					// 写二级菜单,children是个list
				path: '/servers',
				name: 'servers',
				meta: {
					title: '服务列表'	// 需要设置title,否则不显示
				},
				component: () => import('@/view/cmdb/Servers.vue')
			},
			{
				path: '/opsaudit',		// audit 审计,审查操作是否有问题 
				name: 'opsaudit',
				meta: {
					title: '操作审计'
				},
				component: parentView,	// 做三级菜单
// 下面再是三级菜单、四级菜单、五级菜单…… component都是parentView
				children: [{
						path: 'ops1',		// 三级菜单
						name: 'ops1',
						meta: {
							title: '操作审计1'
						}
					},
					{
						path: 'ops2',
						name: 'ops2',
						meta: {
							title: '操作审计2'
						}
					}

				]
				/* component: () => import('@/view/cmdb/OpsAudit.vue') */
			},
			{
				path: '/idc',
				name: 'idc',
				meta: {
					title: '机房管理'
				},
				component: () => import('@/view/cmdb/Idc.vue')
			}

		]
	},
	{
		path: '/tasks',
		name: 'tasks',
		meta: {
			title: '任务列表'
		},
		component: () => import('@/view/error-page/401.vue')
	},
	{
		path: '/ops',
		name: 'ops',
		meta: {
			title: '运维管理'
		},
		component: () => import('@/view/error-page/401.vue')
	},
	{
		path: '/system',
		name: 'system',
		meta: {
			title: '系统管理'
		},
		component: () => import('@/view/error-page/401.vue')
	},
	{
		path: '/uc',
		name: 'uc',
		meta: {
			title: '用户操作'
		},
		component: () => import('@/view/error-page/401.vue')
	},
	{
		path: '*',
		name: 'error_404',
		meta: {
			hideInMenu: true
		},
		component: () => import('@/view/error-page/404.vue')
	}
]

步骤:设置中文显示 添加映射关系 src/locale/lang/zh-CN.js文件

调用api,前后端联调
步骤:打开后端 使用flask 调用接口

打开mysql服务器,然后启动flask,前后端联调
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:Sanchuang123#@192.168.1.37:3306/sanchuang"
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py runserver -d -h 0.0.0.0 -p 8000
#注:启动flask

步骤:后端flask起来后,前端调用api接口

#注:src/view/cmdb/Servers.vue 文件  服务器管理页面  放表格 (有很多服务器)
#注:拿到的服务器的list从接口获取
#注:所以先把页面写好,复制以前写的 表格的代码,放到Servers.vue组件 template里
<template>
	<div>
		<Card>
			<Table border :columns="servercolumn" :data="serverlist"></Table>
		</Card>
	</div>
</template>
#注:接下来 获取 serverlist  data,先把columns定义好,把以前的代码复制粘贴+修改
# src/view/cmdb/Servers.vue  前后端联调
<script>
	import {getServerList} from "@/api/cmdb/servers.js"		// 导入请求url的函数
	import {delServer} from "@/api/cmdb/servers.js"
	export default{
        data () {
            return {
                servercolumn: [					// 先把表头写好
                    {
                        title: '主机名序列号',		// 表格里放 sn号
                        key: 'sn',					// 跟从api传过来的key值保持一致
                    },
                    {
                        title: '内网IP',
                        key: 'ip'
                    },
                    {
                        title: '外网IP',
                        key: 'public_ip',
					},
					{
						title: 'IDC机房',
						key: 'idc'
					},
					{
						title: '操作',
						key: 'action',
						width: 150,
						align: 'center',
						// render   返回的是数据自定义的渲染内容
						// return h("定义的元素标签",{元素的属性},"元素的内容"/[元素的内容])
						render: (h, params) => {
							// <div><Button type="primary" size="samll" style="marginRight:5px" onclick></Button></div>
							return h('div', [
								h('Button', {
									props: {
										type: 'primary',
										size: 'small'
									},
									style: {
										marginRight: '5px'
									},
									on: {
										click: () => {
											this.show(params.index)
										}
									}
								}, '查看'),
								h('Button', {
									props: {
										type: 'error',
										size: 'small'
									},
									on: {
										click: () => {
											this.remove(params.index)
										}
									}
								}, '删除')
							]);
						}
					}
				],
                serverlist: []
            }
        },
        methods: {
            show (index) {
                this.$Modal.info({
                    title: 'User Info',
                    // content: `Name:${this.data6[index].name}<br>Age:${this.data6[index].age}<br>Address:${this.data6[index].address}`
					content:`
					主机名: ${this.serverlist[index].hostname}<br>
					内网ip:${this.serverlist[index].ip}
					`
				})
            },
            remove (index) {
                var id = this.serverlist[index].id;
				delServer(id)
				.then(
				res => {
					this.serverlist.splice(index,1)
				}
				).catch(
				err => {
					console.log(err)
				}
				)
            }
        },
		beforeMount: function() {	// 挂载前发起请求  (本质是axios请求)写成匿名函数
			getServerList()			// 使用定义好的函数去请求(定义的步骤在下面)  @/api/cmdb/servers.js
			.then(				// 请求成功。函数里调用函数,最好用箭头函数
				res => {
					this.serverlist = res.data.data		// 成功之后把数据赋给serverlist
					console.log(res.data.data)		
// data是发起这个请求给返回的数据		// 但是只需要这个数据下面的data属性
				}
			).catch(				// 请求失败。箭头函数
				err => {
					console.log(err)
				}
			)
		}
    }
</script>

步骤:接下来 从api里获取数据

#注:请求都放在api目录里

#注:在 src/api 目录下 新建cmdb目录 下面 新建servers.js文件
src/api/cmdb/servers.js文件
#注:复制别人写的代码(user.js 文件),导入servers.js文件。写好url请求
import axios from '@/libs/api.request'

export const getServerList = () => {				// 写好函数 发送axios请求
	return axios.request({
		url: 'cmdb/servers/',		// 请求的url,会自动拼接baseUrl和这个url 去发送请求
		method: 'get'				// get方法去请求
	})
}
#注:网址的域名 一般放在config里面  (注:会自动找)
src/config目录 index.js文件
……
  baseUrl: {					// 基本url (写通用的部分)
    dev: 'http://127.0.0.1/v1/api/',	// 目前 前后端都在同一台机器上。dev 开发环境
    pro: 'https://produce.com'		// pro 生产环境
  },
……
#注:会读取 src/main.js文件 的 Vue.config.productionTip , 看它是生产环境还是开发环境。生产环境和开发环境 一般用不同的url
Vue.config.productionTip = false

#注:写好url请求函数后,需要使用它 (src/view/cmdb/Servers.vue文件)

步骤:数据挂载前发起请求

src/view/cmdb/Servers.vue
……
        methods: {
……
		beforeMount: function() {	// 挂载前发起请求  (本质是axios请求)写成匿名函数
			getServerList()			// 使用定义好的函数去请求(定义的步骤在下面)  @/api/cmdb/servers.js
			.then(				// 请求成功。函数里调用函数,最好用箭头函数
				res => {
					this.serverlist = res.data.data		// 成功之后把数据赋给serverlist
					console.log(res.data.data)		
// data是发起这个请求给返回的数据		// 但是只需要这个数据下面的data属性
				}
			).catch(				// 请求失败。箭头函数
				err => {
					console.log(err)
				}
			)
		}
    }
</script>

步骤:点击 查看(查看到信息的详细信息)

src/view/cmdb/Servers.vue
……
						render: (h, params) => {
							// <div><Button type="primary" size="samll" style="marginRight:5px" onclick></Button></div>
							return h('div', [
								h('Button', {
									props: {
										type: 'primary',
										size: 'small'
									},
									style: {
										marginRight: '5px'
									},
									on: {
										click: () => {
											this.show(params.index)
										}
									}
								}, '查看'),
								h('Button', {
									props: {
										type: 'error',
										size: 'small'
									},
									on: {
										click: () => {
											this.remove(params.index)
										}
									}
								}, '删除')
							]);
						}
					}
				],
                serverlist: []
            }
        },
        methods: {
            show (index) {
                this.$Modal.info({					// 使用this.$Model.info属性,会弹出模态框
                    title: 'User Info',
                    // content: `Name:${this.data6[index].name}<br>Age:${this.data6[index].age}<br>Address:${this.data6[index].address}`
					content:`
					主机名: ${this.serverlist[index].hostname}<br>		// 通过下标获取
					内网ip:${this.serverlist[index].ip}
					`
				})
            },
            remove (index) {
                var id = this.serverlist[index].id;
				delServer(id)
				.then(
				res => {
					this.serverlist.splice(index,1)
				}
				).catch(
				err => {
					console.log(err)
				}
				)
            }
        },
……

步骤:删除操作

后端 devopscmdb/router/v1/server.py
# 动态url
class ServerView(Resource):
    ……
    def delete(self,id):
        servers = Server.query.get(id)
        db.session.delete(servers)
        db.session.commit()
        return generate_response()

api.add_resource(ServerView,"/servers/<int:id>",endpoint = 'serverview1')		# 添加动态url
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py runserver -d -h 0.0.0.0 -p 8000
DELETE  127.0.0.1:8000/v1/api/cmdb/servers/1

步骤:前端 发起请求

src/api/cmdb/servers.js文件
import axios from '@/libs/api.request'	

export const delServer = (id) => {					// 请求函数
	return axios.request({
		url: 'cmdb/servers/'+id,
		method: 'delete'
	})
}


src/view/cmdb/Servers.vue文件					// 使用请求函数

<script>
	import {delServer} from "@/api/cmdb/servers.js"		// 导入请求函数
		……
		methods: {
			remove(index) {
				var id = this.serverlist[index].id;
				delServer(id)
					.then(
						res => {
							this.serverlist.splice(index, 1)		// 删除界面上的
						}
					).catch(
						err => {
							console.log(err)
						}
					)
			}
		},
		……

用户的注册与登录

后端,授权写在libs/authorized.py文件里

api授权
签名认证
1、接口方提供给对方appid和appsecretkey
2、调用方根据appid和appsecretkey以及其他字符串,按照接口方提供的算法生成签名
3、接口方接收调用方的参数验证签名

用户的注册功能

首先实现用户的信息,对密码进行加密,加密之后再存储

步骤:存储加密之后的密文 使用模块generate_password_hash

文件 devopscmdb/model/user.py
from werkzeug.security import generate_password_hash

class UserProfile(db.Model):
    _password = db.Column('password',db.String(256), nullable=False)

    @property
    def password(self):
        return self._password
    
    @password.setter
    def password(self,value):
        self._password = generate_password_hash(value)
#注:对password进行加密,然后给_password,然后再传到数据库。(在UserProfile类里 定义方法)
#注:把方法定义成属性  使用@property装饰器 进行属性包装
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db migrate -m "addpasswd"
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db upgrade	#注:提交

(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py shell		#注:进入交互环境

>>> from model.user import UserProfile
>>> from model.base import db
>>> user1 = UserProfile.query.get(3)				#注:获取对象
>>> user1
<UserProfile 3>
>>> user1._password = "123456"
>>> de.session.add(user1)
>>> db.session.commit()

图示

对属性包装的_password进行修改
>>> user1.password = "123456"
>>> db.session.add(user1)
>>> db.session.commit()

图示

#注:数据库定义好了,下一步写 接口,通过接口界面插入数据
(用户注册使用 post)
#注:定义好api 路由

步骤:router/v1 新建user.py文件

# post  /v1/api/user/register/
from flask import Blueprint             # 导入蓝图
from flask_restful import Resource, Api
from model.user import UserProfile
from model.base import db

# 创建蓝图
user_bp = Blueprint('user'.__name__, url_prefix="/user")
# 把蓝图绑到api上
api = Api(user_bp)

# 写视图
class RegisterView(Resource):
    def post(self):
        return "post is ok"

# 添加路由,绑定到 RegisterView
api.add_resource(RegisterView, '/register/')

步骤:注册路由 router/v1 __init__.py文件

from .user import user_bp

v1_bp.register_blueprint(user_bp)
POST		127.0.0.1:8000/v1/api/user/register/

postman

接口完成了。接下来 接收它提交的数据 (json格式 获取传递过来的参数)
#注:request里面有请求的一切对象
from flask import Blueprint, request

class RegisterView(Resource):
    # 动态url  /user/<int:id>
    # url带参数 /user/?id=1 --> request.args["id"]
    # form表单数据  data body  --> request.form["id"]
    # json      data body   --> request,json
    def post(self):
        # 获取传递过来的参数(传递过来为json参数)
        data = request.json
        return "post is ok"
对参数进行验证(用户名长度等……)
# 数据验证的方式
# RequestParser:  主要是验证参数的类型,是否为必传项
# WTForms         更加灵活的验证参数
#注:安装wtforms模块
(venv) E:\web_cpen\flask_proj\devopscmdb>pip install wtforms
做数据校验,写在其他地方

步骤:新建目录forms,在下面做user的数据校验 新建user.py文件

#注:从wtforms导入验证器

from wtforms import Form, StringField
from wtforms.validators import DataRequired, Email, Regexp, ValidationError

class UserForm(Form):
    # name必填
    name = StringField(validators=[DataRequired()])
    password = StringField(validators=[DataRequired(),Regexp(r'^\w{6,18}$',message = "密码复杂度不符合要求")])
    email = StringField(validators=[DataRequired(), Email(message="邮箱不合法")])
DataRequired	# 数据必填
Regexp			# 正则匹配
Email			# 邮箱验证

步骤:导入、使用它

#注:导入它
#注:使用它。把data交给Userform
#注:提交到
#注:并做标准化返回
router/v1/user.py文件
from forms.user import UserForm
from libs.response import generate_response

class RegisterView(Resource):
    # 动态url  /user/<int:id>
    # url带参数 /user/?id=1 --> request.args["id"]
    # form表单数据  data body  --> request.form["id"]
    # json      data body   --> request,json
    def post(self):
        # 获取传递过来的参数(传递过来为json参数)
        data = request.json
        form = UserForm(data=data)
        if form.validate():		#注:validate
            userinfo = UserProfile(
                user_profile_name = form.name.data,
                password = form.password.data,
                user_profile_email = form.email.data
            )
            db.session.add(userinfo)
            db.session.commit()
        else:
            print(form.errors)
            return generate_response(message=form.errors)
        return generate_response(message="添加成功")

步骤:启动服务

(venv) E:\web_cpen\flask_proj\devopscmdb>pip install email_validator	#注:email认证缺少这个模块

(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db migrate -m "add email not null"

(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py shell	#注:为没有密码的 添加密码
>>> from model.user import UserProfile
>>> from model.base import db
>>> user1 = UserProfile.query.get(4)
>>> user1.password = "123456"
>>> db.session.add(user1)
>>> db.session.commit()

(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db upgrade

(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py runserver -d -h 0.0.0.0 -p 8000	#注:启动服务

步骤:表单验证 添加用户

POST	127.0.0.1:8000/v1/api/user/register/

postman

传参用的,通过body传参,json格式

图示
自定义检测方法

forms/user.py文件  如果邮箱已存在 就报错  (在提交数据库之前)
from model.user import UserProfile		#注:先导入数据库

class UserForm(Form):
    ……
    # 自定义检测方法 (validate+你要检查的字段名)
    def validate_email(self, value):		#注:这里一定要写validate
        if UserProfile.query.filter_by(user_profile_email=value.data).first():
            raise ValidationError("邮箱已存在!")
POST	127.0.0.1:8000/v1/api/user/register/

postman

以后做登录认证 可以直接在这里验证 用户名、密码是否合法

步骤:在原本的名字前面添加 sanle-

forms/user.py文件
class UserForm(Form):
    ……
    def validate_name(self, value):
        value.data = "sanle-" + value.data

图示
图示
使用自定义异常做返回。对此类东西做异常标准化返回

步骤:指定表单认证失败的异常

自定义异常  libs/error_code.py文件
class FormValidateException(APIException):
    message = "表单验证失败"
    status_code = 10002

class ArgsTypeException(APIException):
    message = "表单提交数据格式不正确"
    status_code = 10003

步骤:在router/v1/user.py文件 抛出异常 (做异常处理标准化)

from libs.error_code import FormValidateException, ArgsTypeException	#注:导入异常类
from libs.handler import default_error_handler			#注:导入异常处理函数

api.handle_error = default_error_handler		#注:交给异常处理函数,不然不会显示标准化返回

class RegisterView(Resource):
    def post(self):
        # 获取传递过来的参数(传递过来为json参数)
        try:
            data = request.json
        except:
            raise ArgsTypeException
            ……
        if form.validate():
            ……
        else:
            raise FormValidateException()

postman
写注册界面,第1步 添加路由

components/router/routers.js文件

	{
		path: '/register',
		name: 'register',
		meta: {
			title: 'Login - 注册',
			hideInMenu: true
		},
		component: () => import('@/view/login/register.vue')
	},

components/router/index.js文件

const REGISTER_PAGE_NAME = 'register'
……

  if (!token && to.name === REGISTER_PAGE_NAME) {
	// 未登录且要跳转的界面是注册页面,就直接跳转
    next()
  } else if (!token && to.name !== LOGIN_PAGE_NAME) {
// 未登录且要跳转的页面不是登录页

步骤:写注册界面
/src/view/login/register.vue文件

步骤:注册,写api
/src/api/user.js文件

export const Register = registerinfo => {
	const data = registerinfo
  return axios.request({
    url: 'user/register/',
	data,
    method: 'post',
  })
}

步骤:注册界面导入

<script>
	import {Register} from '../../api/user.js'
    export default {
        data () {
……		// 传值操作,定义
        methods: {
            handleSubmit (name) {
				Register(this.formValidate)
				.then( res => {
				  this.$router.push({
					name: this.$config.LoginName		// LoginName是在config文件的index.js文件定义的首页
					  })
				})
            },

#注:登录:认证的思维。前端访问后端,怎么去把前端和后端的任务做一下
#注:后端怎么运用前端的请求
#注:要登录的,怎么知道这个人有没有登录
#注:HTTP是1种无状态协议,每次请求都是独立的,每次请求都是毫无关联的
#注:携带token的认证
#注:登录的时候 后端去认证的,跟数据的交互 交给flask去处理
#注:api授权 是给 调用接口的程序用的

流程
前端(vue/client)向后端(flask/server)发起请求,当登录验证通过,flask向vue返回token,后续 vue 每次向flask发起请求,都要带上token。flask需要做vue发起请求 token的认证(合法性认证)

#注:步骤1 server(flask) 开启验证接口(验证用户名和密码),验证成功返回token
#注:步骤2 client (vue) 接收保存token。
#注:步骤3 client (vue) 以后每次请求 要携带token
#注:步骤4 server(flask) 验证token

兼容更多的认证方式:api授权、(用户名密码登录、)token认证,支持这2种认证方式

步骤:定义接口
router/v1/user.py文件 视图

from forms.user import UserForm,LoginForm
from libs.authorize import create_token

#用户登录视图
class LoginView(Resource):
    def post(self):
        try:
            data = request.json
        except:
            raise ArgsTypeException(message="登录参数格式不对")
        form = LoginForm(data=data)
        user = form.validate()
        if user:
            token = create_token(uid = user.user_profile_id)
            return generate_response(data={"token":token})
        else:
            raise FormValidateException(message=form.errors)

api.add_resource(LoginView, '/login/')

步骤:定义Login.form表单验证 forms/user.py文件

from werkzeug.security import check_password_hash
from libs.error_code import AuthorizedExceptopm

class LoginForm(Form):
    userName = StringField(validators=[DataRequired(),Email(message="邮箱不合法")])
    password = StringField(validators=[DataRequired()])
  
def validate(self): # 全局检测函数
        super().validate()  # 先访问父类validate
        if self.errors:
            return False
        # 校验登录逻辑
        user = UserProfile.query.filter_by(user_profile_email = self.userName.data).first()
        if user and check_password_hash(user.password, self.password.data):
            return user
        else:
            raise AuthorizedExceptopm

用户认证失败 自定义异常 libs/error_code.py文件

class AuthorizedExceptopm(APIException):
    message = "用户或密码错误"
    status_code = 10005
    code = 401

libs/authorize.py文件

from flask import request,current_app
from itsdangerous import TimedJSONWebSignatureSerializer as TS

def create_token(uid):
    #第一个参数传递加密的私钥
    #第二个传递过期时间 (单位 s)
    s = TS(current_app.config["SECRET_KEY"], expires_in=current_app.config["EXPIRES_IN"])
    #接受用户id,结果为bytes类型,需要转换为ascii格式
    token = s.dumps({"uid":uid}).decode("ascii")
    return token

conf/settings.py文件

SECRET_KEY = "123456"
EXPIRES_IN = 3600
POST	127.0.0.1:8000/v1/api/user/login/

postman

实现 生成token,接下来改前端,把token给前端

步骤:登录界面 src/view/login/login.vue文件

userName和password通过vuex处理
#注:vuex 公共存储、交换信息的地方
<script>
import LoginForm from '_c/login-form'
import { mapActions } from 'vuex'
export default {
  components: {
    LoginForm
  },
  methods: {
    ...mapActions([
      'handleLogin',
      'getUserInfo'
    ]),
    handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {		// 一层一层来的
          this.$router.push({
            name: this.$config.homeName
          })
        })
      })
    }
  }
}
</script>

步骤:登录的处理逻辑 在 src/store/module/user.js文件

import {
  login,
  ……
} from '@/api/user'				// 从这里导入
import { setToken, getToken } from '@/libs/util'

  ……
  actions: {
    // 登录
    handleLogin ({ commit }, { userName, password }) {
      userName = userName.trim()
      return new Promise((resolve, reject) => {
        login({					// 真正的请求
          userName,
          password
        }).then(res => {			// 登录成功后,把token保存在vuex里面
          const data = res.data.data
          commit('setToken', data.token)	// 登录成功后,把token保存在vuex里面
          resolve()
        }).catch(err => {
          reject(err)
        })
      })
},
……
    // 获取用户相关信息
    getUserInfo ({ state, commit }) {
      return new Promise((resolve, reject) => {
        try {			// 真正执行的是这个api,从src/api/user.js导入
          getUserInfo(state.token).then(res => {
            const data = res.data
……

步骤:更改src/api/user.js文件 login函数

export const login = ({ userName, password }) => {
  const data = {
    userName,
    password
  }
  return axios.request({
    url: 'user/login/',
    data,
    method: 'post'
  })
}

步骤:更改Mock src/mock/index.js文件 注释掉mock的虚拟信息

// 登录相关和获取用户信息
// Mock.mock(/\/login/, login)
// Mock.mock(/\/get_info/, getUserInfo)

步骤:不用Mock的接口,自己写getinfo的接口,返回数据

#注:写 获取登录user信息的接口  router/v1/user.py文件
#获取用户信息接口
class UserView(Resource):
    def get(self):
        result = {
                "name": 'sc@tl.com',
                "user_id": '1',
                "access": ['sc@tl.com', 'admin'],
                "token": 'sc@tl.com',
                "avatar": 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png'
                }
        return generate_response(data=result)

api.add_resource(UserView,"/getinfo")		# 添加路由

步骤:获取接口数据 src/api/user.js文件

#注:修改api下的请求 url
export const getUserInfo = (token) => {
  return axios.request({
    url: 'user/getinfo',
    params: {
      token
    },
    method: 'get'
  })
}

整个流程

首先:通过组件src/view/login/login.vue  知道它的处理函数在哪里  vuex(不同组件之间需要传递数据的公共存储部分)
vuex在src/store/module/user.js文件里
import {
  login,
  ……
} from '@/api/user'
  ……
  actions: {
    // 登录
    handleLogin ({ commit }, { userName, password }) {
      userName = userName.trim()		// 去除userName的空白
      return new Promise((resolve, reject) => {
        login({
          userName,						// 传递数据
          password
        }).then(res => {
          const data = res.data.data	// 成功时,获取它的返回的data
          commit('setToken', data.token)	// 把token保存在cookie和vuex里
          resolve()
        }).catch(err => {
          reject(err)
        })
      })
    },
  ……
login从src/api/user.js文件导入,这个文件里 把userName,password传递过去
export const login = ({ userName, password }) => {
  const data = {
    userName,
    password
  }
  return axios.request({
    url: 'user/login/',				// 这里写login的接口
    data,								// 然后把userName、password传递过去
    method: 'post'
  })
}
//注:这里就是请求后端接口登录的验证接口
}
src/view/login/login.vue文件
……
    handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {		// 登录成功后 获取用户的详细信息
          this.$router.push({
            name: this.$config.homeName
          });
		  console.log(this.$router)
		 })
        }).catch( err => {
			console.log("err")
			this.login_err = true
		})
    }
  }
}
getUserInfo写在src/store/module/user.js文件里
把这些信息都保存在vuex里面

完成登录
步骤:当用户名输入错误时,提示弹框

#注:先把后端(服务端)token的验证做好,兼容更多种的验证
在原来api的基础上,兼容token的认证

http基本认证 httpauth
https://www.jianshu.com/p/4cd42f7359f4

步骤:安装

(venv) E:\web_cpen\flask_proj\devopscmdb>pip install flask_httpauth

步骤:导入 libs/authorize.py文件

from flask import request,current_app, g
from .error_code import APIAuthorizedException, AuthorizedException
from flask_httpauth import HTTPBasicAuth
from itsdangerous import BadSignature, SignatureExpired

auth = HTTPBasicAuth()
# Authorized: 用户名:密码		#base64加密传的

#当执行auth.login_required时,会执行auth.verify_password对应的函数  return True就表示认证成功
@auth.verify_password
def verify_password(token, password):
    """多种认证方式"""
    if token and password:
        # 如果token和password都有值表示是用户名密码认证
        return True
    #客户端请求的时候,携带头信息 authorization: basic base64(api:)
    elif token and token == 'api':
        # 如果token为api表示是api的请求
        return api_authorize()
    elif token:
        # 如果只有token有值表示是token认证
        #{"uid":1}
        user_info = verify_token(token)
        #g 就是当前请求内的全局变量,每个请求都拥有各自的g
        g.user = user_info
        return True
    else:
        raise AuthorizedException(message="参数传递不完整")

def verify_token(token):
    s = TS(current_app.config["SECRET_KEY"])
    try:
        data = s.loads(token)
        # raise AuthorizedException(data)
    except BadSignature:
        raise AuthorizedException(message="token不合法")
    except SignatureExpired:
        raise AuthorizedException(message = "token is expired")
    return data

当执行auth.login_required时,会执行auth.verify_password对应的函数 return True就表示认证成功

步骤:router/v1/user.py文件

from flask import Blueprint, request, g
from libs.authorize import create_token, auth

……
#获取用户信息接口
class UserView(Resource):

    @auth.login_required
    def get(self):
        uid = g.user.get('uid')
        result = {
                "name": 'sc@tl.com',
                "user_id": '1',
                "
……

客户端请求的时候,携带头信息 authorization: basic base64(api:)

步骤:src/libs/axios.js文件

class HttpRequest {
……
  getInsideConfig () {
    const config = {
      baseURL: this.baseUrl,
      headers: {
        //
      },
}
// window.btoa  进行base64编码
	config.headers['Authorization'] = 'Basic ' + window.btoa(store.state.user.token+":")
    return config
  }
……

客户端请求的时候,携带头信息 authorization: basic base64(api:)

headers = {
    ‘Authorization’: ’Basic YXBpOg==}
content = requests.get(API,params=params,headers=headers).text

步骤:登录失败,提示邮箱或密码错误
src/view/login/login.vue文件

<template>
……
		  <Alert type="error" v-show="login_err">邮箱或密码错误</Alert>
……
</template>

<script>
……
    handleSubmit ({ userName, password }) {
      this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {
          this.$router.push({
            name: this.$config.homeName
          });
		  console.log(this.$router)
		 })
        }).catch( err => {
			console.log("err")
			this.login_err = true
		})
     }
  }
}
</script>

示例:做任务列表

#注:使用到celery工具去做
#注:celery简单灵活的 分布式任务队列
#注:分布式 把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给多个计算机进行处理,最后把这些计算结果综合起来得到最终的结果
#注:celery 典型的生产者消费者模型
celery

#注:写一个任务列表。点击运行任务 把任务放到消息中间件broker,后端celery从消息中间件里面获取任务
生产者挂掉了,不影响消息中间件;消息中间件挂掉了,会影响生产者。
#celery  分布式任务处理模块
#典型的消费者和生产者模型

#异步任务
#定时任务

#celery的组件
# Beat: 任务调度器,用来做定时任务
# worker: 执行任务的消费者
# broken: 消息中间件
# Produceer: 调用celery的api,函数等产生任务给消息中间件,任务的生产者
# result backend: 存放任务处理完成之后的结果

#消息中间件
#redis服务  缓存,消息中间件,数据库

代码同步到linux下

[root@localhost opt]# yum install redis

[root@localhost cmdb]# vim /etc/redis.conf 
bind 0.0.0.0										#注:监听0.0.0.0
[root@localhost cmdb]# redis-server 					#注:启动服务
[root@localhost cmdb]# redis-server /etc/redis.conf &	#注:后台运行 &
[1] 2618
[root@localhost cmdb]# yum install psmisc -y
[root@localhost cmdb]# killall redis-server
[root@localhost cmdb]# redis-server /etc/redis.conf &

创建虚拟环境

[root@localhost cmdb]# python3 -m venv venv
[root@localhost cmdb]# source venv/bin/activate		#注:进入虚拟环境
(venv) [root@localhost cmdb]# 

flask发布任务,放到消息中间件redis里面

步骤:安装python里连接redis的模块

(venv) [root@localhost cmdb]# pip install redis
(venv) [root@localhost cmdb]# pip install celery

步骤:把celery和flask整合在一起

celery_app/__init__.py文件
from celery import Celery

celery = Celery('celery_app')
celery.config_from_object('conf.celery_config')	#注:读取配置

配置文件 conf/celery_config.py文件

from celery.schedules import crontab

# 配置消息中间件的地址
BROKER_URL = "redis://192.168.0.65:6379/1"

# 结果存放地址
CELERY_RESULT_BACKEND = "redis://192.168.0.65:6379/2"

# 启动Celery时,导入任务
CELERY_IMPORTS = (
    'celery_app.tasks',
)

CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULE = {
    'every-minute': {
        'task': 'celery_app.tasks.scheduled_task',
        'schedule': crontab(minute='*/1'),
        # 'args': (1,2),
        #'schedule': timedelta(seconds=5)
    },
}

创建任务文件 celery_app/tasks.py文件

from . import celery
import time
import random
import paramiko

@celery.task
def celery_task(sth1):
    print("celery_app.task start")
print(sth1)
    delay_time = random.randint(5, 20)
    time.sleep(delay_time)
    print("celery_app.task end")
    return True

manage.py文件

@app.route('/index/')
def index():
    # 调用一个异步任务
    from celery_app.tasks import celery_task
    # 立即发送任务,立即执行任务
    celery_task.delay("hello world!")
    return "This is index"

列出当前pip安装的包,虚拟环境的包

(venv) E:\web_cpen\flask_proj\devopscmdb>pip freeze
(venv) E:\web_cpen\flask_proj\devopscmdb>pip freeze >requirements.txt
#注:生成requirements.txt文件,点击Upload to cmdb 同步

(venv) [root@localhost cmdb]# pip install -r requirements.txt	#注:Linux下安装

(venv) [root@localhost cmdb]# python manage.py runserver -d -h 0.0.0.0 -p 8000

引入消息中间件 为了实现业务的解耦

在celery_app/tasks.py定义了任务的模板,没有被使用。生产者(flask)(manage.py文件)使用函数 传递参数,把任务发送到消息中间件(broken)(因为conf/celery_config.py做了配置),redis-server启动服务(消息中间件)。Flask访问接口的形式,每次访问一次页面,产生一个任务。

示例:进程后端运行

#1
(venv) [root@localhost cmdb]# redis-server /etc/redis.conf &		#注:这个终端不能关掉
#注:指定配置文件 /etc/redis.conf 以这个配置文件启动
#2 tmux下
(venv) [root@localhost cmdb]# redis-server /etc/redis.conf 
tmux  ctrl+b+d	#注:保存会话
tmux ls
tmux a -t 0		#注:进图会话。按照tmux索引0
#3.作为后台进程去运行
使用Supervisor
https://www.jianshu.com/p/0226b7c59ae2

步骤:启动redis

(venv) [root@localhost cmdb]# redis-server /etc/redis.conf &

步骤:启动服务

(venv) [root@localhost cmdb]# python manage.py runserver -d -h 0.0.0.0 -p 8000

步骤:启动celery

(venv) [root@localhost cmdb]# celery -A celery_app.celery worker --loglevel=info -n node1

步骤:生产者要工作了,通过访问index去生产任务

http://192.168.0.46:8000/index/

示例:分布式处理

[root@cPen_B ~]# pip3 install celery

步骤:把文件传过去

[root@cPen_B ~]# pip3 install redis
[root@cPen_B ~]# celery -A celery_app.celery worker --loglevel=info -n node2

示例:自己配套的celery监控平台 flower

[root@localhost ~]# pip3 install flower

步骤:指定celery的版本 (5版本出错)

(venv) [root@localhost cmdb]# pip install flower redis celery==4.4.7

步骤:启动

(venv) [root@localhost cmdb]# ./venv/bin/flower -A celery_app.celery --loglevel=info --address=0.0.0.0 --port=5555

步骤:访问路由 http://192.168.0.46:5555/
flower
示例:定时任务
步骤:启动定时任务管理器 beat

(venv) [root@localhost cmdb]# celery beat -A celery_app.celery --loglevel=info
#注:beat自己就是生成者,按照时间 每分钟去发送任务 放到消息中间件里,交给work去处理

配置 conf/celery_config.py文件

CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULE = {
    'every-minute': {
        'task': 'celery_app.tasks.scheduled_task',
        'schedule': crontab(minute='*/1'),
        # 'args': (1,2),
        #'schedule': timedelta(seconds=5)
    },
}

任务 celery_app/tasks.py文件

@celery.task
def scheduled_task(*args, **kwargs):
    print(args, kwargs)
    print(time.strftime('%Y.%m.%d %H:%M:%S', time.localtime(time.time())))
    print("scheduled_task")

示例:写task任务列表

#注:打开前端,写task任务列表

15334@LAPTOP-5GGR0QTF MINGW64 /e/cpen-frond/cpen-front/8、iview/iview-admin (master)
$ npm run dev

#使用paramiko去执行命令

步骤:前端 添加二级路由 src/router/routers.js文件

	{
		path: '/tasks',
		name: 'tasks',
		meta: {
			title: '任务管理',
		},
		component: Main,
		children: [{
				path: "list",
				name: "list",
				meta: {
					title: '任务列表',
				},
				component: () => import('@/view/task/list.vue')
			},
			{
				path: "log",
				name: "log",
				meta: {
					title: '任务日志',
				},
				component: () => import('@/view/task/log.vue')
			}],
	},

组件 src/view/task/list.vue文件

<template>
	<div>
		<Button type="primary" @click="modal1 = true">新建参数</Button>
		<Modal
			v-model="modal1"
			title="新建"
			@on-ok="ok"
			@on-cancel="cancel">
			任务名称<Input v-model="value" placeholder="Enter something..." style="width: 300px" /></br><br>
			任务命令<Input v-model="value" placeholder="Enter something..." style="width: 300px" /></br><br>
			命令参数<Input v-model="value" placeholder="Enter something..." style="width: 300px" /></br><br>
			执行主机<Input v-model="value" placeholder="Enter something..." style="width: 300px" /></br><br>
		</Modal>
		<Card>
			<Table border :columns="servercolumn" :data="serverlist"></Table>
		</Card>
	</div>
</template>

建立表模型,生效到数据库 model/task.py文件

from .base import db

class Task(db.Model):
    __tablename__ = "task"
    task_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    task_name = db.Column(db.String(32), nullable=False)
    task_command = db.Column(db.String(32),nullable=False)
    task_args = db.Column(db.String(32), nullable=False)
    task_host = db.Column(db.String(32))
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db upgrade -m "task"
(venv) E:\web_cpen\flask_proj\devopscmdb>python manage.py db upgrade

项目上线
步骤:先安装 gunicorn

[root@localhost ~]# pip3 install gunicorn

后端启动

1、安装gunicorn

sudo pip3 install gunicorn

2、在项目目录下启动服务
启动manage文件中的app核心对象

gunicorn -b 0.0.0.0:5000 manage:app -w 6

前端启动

1、打包前端代码

修改main.js, 改成生产环境
Vue.config.productionTip = true

修改config/index.js
	baseUrl: {
	  dev: 'http://192.168.0.32:8000/v1/api/',
	  pro: 'http://192.168.0.32/v1/api/'
	},
 
打包生成html页面静态文件
npm  run build

打包完成,代码会放在当前目录的dist目录下

nginx部署动静分离

1、将dist目录拷贝到nginx服务器上 放到/opt/www/ 下
2、配置nginx反向代理
修改/etc/nginx/conf.d/sc.conf

server {
   listen  80 default_server;
   server_name  www.sc.com;

   location / {
         root /opt/www/dist;
   } 

   location /v1/api/ {
       proxy_pass http://192.168.0.32:5000;
   }
}
Logo

前往低代码交流专区

更多推荐