Files
SOP/sop-admin/sop-admin-vue/src/views/service/limit.vue
2019-08-21 12:35:07 +08:00

530 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-container>
<el-aside style="min-height: 300px;width: 250px;">
<el-input v-model="filterText" prefix-icon="el-icon-search" placeholder="搜索服务..." style="margin-bottom:20px;" size="mini" clearable />
<el-tree
ref="tree2"
:data="treeData"
:props="defaultProps"
:filter-node-method="filterNode"
:highlight-current="true"
:expand-on-click-node="false"
empty-text="无数据"
node-key="id"
class="filter-tree"
default-expand-all
@node-click="onNodeClick"
>
<span slot-scope="{ node, data }" class="custom-tree-node">
<div>
<el-tooltip v-show="data.custom" content="自定义服务" class="item" effect="light" placement="left">
<i class="el-icon-warning-outline"></i>
</el-tooltip>
<span v-if="data.label.length < serviceTextLimitSize">{{ data.label }}</span>
<span v-else>
<el-tooltip :content="data.label" class="item" effect="light" placement="right">
<span>{{ data.label.substring(0, serviceTextLimitSize) + '...' }}</span>
</el-tooltip>
</span>
</div>
<span>
<el-button
v-if="data.custom === 1"
type="text"
size="mini"
icon="el-icon-delete"
title="删除服务"
@click.stop="() => onDelService(data)"/>
</span>
</span>
</el-tree>
</el-aside>
<el-main style="padding-top:0">
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
<el-form-item label="路由ID">
<el-input v-model="searchFormData.routeId" placeholder="接口名,支持模糊查询" clearable />
</el-form-item>
<el-form-item label="AppKey">
<el-input v-model="searchFormData.appKey" placeholder="AppKey支持模糊查询" clearable />
</el-form-item>
<el-form-item label="IP">
<el-input v-model="searchFormData.limitIp" placeholder="ip支持模糊查询" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="onSearchTable">查询</el-button>
</el-form-item>
</el-form>
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增限流</el-button>
<el-table
:data="pageInfo.list"
border
>
<el-table-column
prop="limitKey"
label="限流维度"
width="400"
>
<template slot-scope="scope">
<div v-html="limitRender(scope.row)"></div>
</template>
</el-table-column>
<el-table-column
prop="limitType"
label="限流策略"
width="120"
>
<template slot="header" slot-scope>
限流策略 <i class="el-icon-question" style="cursor: pointer" @click="onLimitTypeTipClick"></i>
</template>
<template slot-scope="scope">
<span v-if="scope.row.limitType === 1">窗口策略</span>
<span v-if="scope.row.limitType === 2">令牌桶策略</span>
</template>
</el-table-column>
<el-table-column
prop="info"
label="限流信息"
width="250"
>
<template slot-scope="scope">
<span v-html="infoRender(scope.row)"></span>
</template>
</el-table-column>
<el-table-column
prop="limitStatus"
label="状态"
width="80"
>
<template slot-scope="scope">
<span v-if="scope.row.limitStatus === 1" style="color:#67C23A">已开启</span>
<span v-if="scope.row.limitStatus === 0" style="color:#909399">已关闭</span>
</template>
</el-table-column>
<el-table-column
prop="orderIndex"
label="排序"
width="80"
/>
<el-table-column
prop="remark"
label="备注"
width="150"
:show-overflow-tooltip="true"
/>
<el-table-column
prop="gmtCreate"
label="创建时间"
width="160"
/>
<el-table-column
prop="gmtModified"
label="修改时间"
width="160"
/>
<el-table-column
label="操作"
fixed="right"
width="80"
>
<template slot-scope="scope">
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
style="margin-top: 5px"
:current-page="searchFormData.pageIndex"
:page-size="searchFormData.pageSize"
:page-sizes="[5, 10, 20, 40]"
:total="pageInfo.total"
layout="total, sizes, prev, pager, next"
@size-change="onSizeChange"
@current-change="onPageIndexChange"
/>
<!-- dialog -->
<el-dialog
:title="dlgTitle"
:visible.sync="limitDialogVisible"
:close-on-click-modal="false"
@close="onLimitDialogClose"
>
<el-form
ref="limitDialogForm"
:model="limitDialogFormData"
:rules="rulesLimit"
label-width="150px"
size="mini"
>
<el-form-item label="限流维度" prop="typeKey">
<el-checkbox-group v-model="limitDialogFormData.typeKey">
<el-checkbox v-model="limitDialogFormData.typeKey[0]" :label="1" name="typeKey">路由ID</el-checkbox>
<el-checkbox v-model="limitDialogFormData.typeKey[1]" :label="2" name="typeKey">AppKey</el-checkbox>
<el-checkbox v-model="limitDialogFormData.typeKey[2]" :label="3" name="typeKey">IP</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item v-show="checkTypeKey(1)" prop="routeId" label="路由ID" :rules="checkTypeKey(1) ? rulesLimit.routeId : []">
<el-select v-model="limitDialogFormData.routeId" filterable placeholder="可筛选" style="width: 300px;">
<el-option
v-for="item in routeList"
:key="item.id"
:label="item.id"
:value="item.id"
>
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.version }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="checkTypeKey(2)" prop="appKey" label="AppKey" :rules="checkTypeKey(2) ? rulesLimit.appKey : []">
<el-input v-model="limitDialogFormData.appKey" placeholder="需要限流的appKey" />
</el-form-item>
<el-form-item v-show="checkTypeKey(3)" label="限流IP" prop="limitIp" :rules="checkTypeKey(3) ? rulesLimit.ip : []">
<el-input v-model="limitDialogFormData.limitIp" type="textarea" :rows="2" placeholder="多个用英文逗号隔开" />
</el-form-item>
<el-form-item label="限流策略">
<el-radio-group v-model="limitDialogFormData.limitType">
<el-radio :label="1">窗口策略</el-radio>
<el-radio :label="2">令牌桶策略</el-radio>
</el-radio-group>
<i class="el-icon-question limit-tip" @click="onLimitTypeTipClick"></i>
</el-form-item>
<el-form-item label="开启状态">
<el-switch
v-model="limitDialogFormData.limitStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
<el-form-item label="排序" prop="orderIndex">
<el-input-number v-model="limitDialogFormData.orderIndex" controls-position="right" :min="0" />
<el-tooltip class="item" content="值小优先执行" placement="top">
<i class="el-icon-question limit-tip"></i>
</el-tooltip>
</el-form-item>
<el-form-item v-show="isWindowType()" label="每秒可处理请求数" prop="execCountPerSecond" :rules="isWindowType() ? rulesLimit.execCountPerSecond : []">
<el-input-number v-model="limitDialogFormData.execCountPerSecond" controls-position="right" :min="1" />
</el-form-item>
<el-form-item v-show="isWindowType()" label="错误码" prop="limitCode" :rules="isWindowType() ? rulesLimit.limitCode : []">
<el-input v-model="limitDialogFormData.limitCode" />
</el-form-item>
<el-form-item v-show="isWindowType()" label="错误信息" prop="limitMsg" :rules="isWindowType() ? rulesLimit.limitMsg : []">
<el-input v-model="limitDialogFormData.limitMsg" />
</el-form-item>
<el-form-item v-show="isTokenType()" label="令牌桶容量" prop="tokenBucketCount" :rules="isTokenType() ? rulesLimit.tokenBucketCount : []">
<el-input-number v-model="limitDialogFormData.tokenBucketCount" controls-position="right" :min="1" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="limitDialogFormData.remark" type="textarea" :rows="2" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="limitDialogVisible = false"> </el-button>
<el-button type="primary" @click="onLimitDialogSave"> </el-button>
</div>
</el-dialog>
</el-main>
</el-container>
</div>
</template>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
.el-input.is-disabled .el-input__inner {color: #909399;}
.el-radio__input.is-disabled+span.el-radio__label {color: #909399;}
</style>
<script>
export default {
data() {
return {
serviceTextLimitSize: 20,
filterText: '',
treeData: [],
tableData: [],
serviceId: '',
searchFormData: {
pageIndex: 1,
pageSize: 5
},
pageInfo: {
list: [],
total: 0
},
routeList: [],
defaultProps: {
children: 'children',
label: 'label'
},
// dialog
dlgTitle: '设置限流',
limitDialogVisible: false,
limitDialogFormData: {
id: 0,
routeId: '',
appKey: '',
limitIp: '',
limitKey: '',
execCountPerSecond: 5,
limitCode: '',
limitMsg: '',
tokenBucketCount: 5,
limitStatus: 0, // 0: 停用1启用
limitType: 1,
orderIndex: 0,
remark: '',
typeKey: []
},
rulesLimit: {
typeKey: [
{ type: 'array', required: true, message: '请至少选择一个', trigger: 'change' }
],
routeId: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
appKey: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
ip: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 500, message: '长度在 1 到 500 个字符', trigger: 'blur' }
],
// window
execCountPerSecond: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
limitCode: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 64, message: '长度在 1 到 64 个字符', trigger: 'blur' }
],
limitMsg: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
// token
tokenBucketCount: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
orderIndex: [
{ required: true, message: '不能为空', trigger: 'blur' }
],
remark: [
{ max: 128, message: '长度不能超过128字符', trigger: 'blur' }
]
}
}
},
watch: {
filterText(val) {
this.$refs.tree2.filter(val)
}
},
created() {
this.loadTree()
},
methods: {
// 加载树
loadTree: function() {
this.post('registry.service.list', {}, function(resp) {
const respData = resp.data
this.treeData = this.convertToTreeData(respData, 0)
})
},
// 树搜索
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
// 树点击事件
onNodeClick(data, node, tree) {
if (data.parentId) {
this.serviceId = data.label
this.searchFormData.serviceId = this.serviceId
this.loadTable()
this.loadRouteList(this.serviceId)
}
},
/**
* 数组转成树状结构
* @param data 数据结构 [{
"_parentId": 14,
"gmtCreate": "2019-01-15 09:44:38",
"gmtUpdate": "2019-01-15 09:44:38",
"id": 15,
"isShow": 1,
"name": "用户注册",
"orderIndex": 10000,
"parentId": 14
},...]
* @param pid 初始父节点id一般是0
* @return 返回结果 [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1'
}]
}]
}
*/
convertToTreeData(data, pid) {
const result = []
const root = {
label: '服务列表',
parentId: pid
}
const children = []
for (let i = 0; i < data.length; i++) {
data[i].parentId = 1
data[i].label = data[i].serviceId
children.push(data[i])
}
root.children = children
result.push(root)
return result
},
// table
loadTable: function() {
this.post('config.limit.list', this.searchFormData, function(resp) {
this.pageInfo = resp.data
})
},
loadRouteList: function(serviceId) {
this.post('route.list/1.2', { serviceId: serviceId }, function(resp) {
this.routeList = resp.data
})
},
onAdd: function() {
if (!this.serviceId) {
this.tip('请选择服务', 'info')
return
}
this.dlgTitle = '新增限流'
this.limitDialogFormData.id = 0
this.limitDialogVisible = true
},
onSearchTable: function() {
this.loadTable()
},
onTableUpdate: function(row) {
this.dlgTitle = '修改限流'
this.limitDialogVisible = true
this.$nextTick(() => {
Object.assign(this.limitDialogFormData, row)
if (row.routeId) {
this.limitDialogFormData.typeKey.push(1)
}
if (row.appKey) {
this.limitDialogFormData.typeKey.push(2)
}
if (row.limitIp) {
this.limitDialogFormData.typeKey.push(3)
}
})
},
onLimitDialogClose: function() {
this.resetForm('limitDialogForm')
this.limitDialogFormData.limitStatus = 0
},
infoRender: function(row) {
const html = []
if (row.limitType === 1) {
html.push('每秒可处理请求数:' + row.execCountPerSecond)
html.push('<br>subCode' + row.limitCode)
html.push('<br>subMsg' + row.limitMsg)
} else if (row.limitType === 2) {
html.push('令牌桶容量:' + row.tokenBucketCount)
}
return html.join('')
},
onLimitDialogSave: function() {
this.$refs['limitDialogForm'].validate((valid) => {
if (valid) {
this.cleanCheckboxData()
this.limitDialogFormData.serviceId = this.serviceId
const uri = this.limitDialogFormData.id ? 'config.limit.update' : 'config.limit.add'
this.post(uri, this.limitDialogFormData, function(resp) {
this.limitDialogVisible = false
this.loadTable()
})
}
})
},
cleanCheckboxData: function() {
// 如果没有勾选则清空
if (!this.checkTypeKey(1)) {
this.limitDialogFormData.routeId = ''
}
if (!this.checkTypeKey(2)) {
this.limitDialogFormData.appKey = ''
}
if (!this.checkTypeKey(3)) {
this.limitDialogFormData.limitIp = ''
}
},
onLimitTypeTipClick: function() {
const windowRemark = '窗口策略:每秒处理固定数量的请求,超出请求数量返回错误信息。'
const tokenRemark = '令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。'
const content = windowRemark + '<br>' + tokenRemark
this.$alert(content, '限流策略', {
dangerouslyUseHTMLString: true
})
},
onSizeChange: function(size) {
this.searchFormData.pageSize = size
this.loadTable()
},
onPageIndexChange: function(pageIndex) {
this.searchFormData.pageIndex = pageIndex
this.loadTable()
},
checkTypeKey: function(val) {
return this.limitDialogFormData.typeKey.find((value, index, arr) => {
return value === val
})
},
isWindowType: function() {
return this.limitDialogFormData.limitType === 1
},
isTokenType: function() {
return this.limitDialogFormData.limitType === 2
},
limitRender: function(row) {
const html = []
const val = []
if (row.routeId) {
val.push(row.routeId)
html.push('路由ID')
}
if (row.appKey) {
val.push(row.appKey)
html.push('AppKey')
}
if (row.limitIp) {
val.push(row.limitIp)
html.push('IP')
}
return val.join(' + ') + '<br>(' + html.join(' + ') + ')'
}
}
}
</script>
<style scoped>
.limit-tip {
cursor: pointer;
margin-left: 10px;
}
</style>