10_前端笔记-ViewUI
作者:互联网
文章目录
个人博客
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 | 登出 |
/ | 首页 |
/cmdb | CMDB |
/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/
接口完成了。接下来 接收它提交的数据 (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/
传参用的,通过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/
以后做登录认证 可以直接在这里验证 用户名、密码是否合法
步骤:在原本的名字前面添加 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()
写注册界面,第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/
实现 生成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 典型的生产者消费者模型
#注:写一个任务列表。点击运行任务 把任务放到消息中间件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/
示例:定时任务
步骤:启动定时任务管理器 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;
}
}
标签:10,ViewUI,name,笔记,api,user,import,data,cmdb 来源: https://blog.csdn.net/cPen_web/article/details/111999443