mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
530 lines
18 KiB
Vue
530 lines
18 KiB
Vue
<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>
|