mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-12 07:02:14 +08:00
1.13.0
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# changelog
|
# changelog
|
||||||
|
|
||||||
|
|
||||||
|
## 1.13.0
|
||||||
|
|
||||||
|
- 新增IP黑名单
|
||||||
|
|
||||||
## 1.12.4
|
## 1.12.4
|
||||||
|
|
||||||
- 优化属性文件配置
|
- 优化属性文件配置
|
||||||
|
9
sop-1.13.0.sql
Normal file
9
sop-1.13.0.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE `config_ip_blacklist` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`ip` varchar(64) NOT NULL DEFAULT '' COMMENT 'ip',
|
||||||
|
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
|
||||||
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_ip` (`ip`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP黑名单';
|
@@ -64,26 +64,32 @@ export const constantRoutes = [
|
|||||||
{
|
{
|
||||||
path: 'list',
|
path: 'list',
|
||||||
name: 'ServiceList',
|
name: 'ServiceList',
|
||||||
component: () => import('@/views/service/list/index'),
|
component: () => import('@/views/service/serviceList'),
|
||||||
meta: { title: '服务列表' }
|
meta: { title: '服务列表' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'route',
|
path: 'route',
|
||||||
name: 'Route',
|
name: 'Route',
|
||||||
component: () => import('@/views/service/route/index'),
|
component: () => import('@/views/service/route'),
|
||||||
meta: { title: '路由管理' }
|
meta: { title: '路由管理' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'limit',
|
path: 'limit',
|
||||||
name: 'Limit',
|
name: 'Limit',
|
||||||
component: () => import('@/views/service/limit/index2'),
|
component: () => import('@/views/service/limit'),
|
||||||
meta: { title: '限流管理' }
|
meta: { title: '限流管理' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'log',
|
path: 'log',
|
||||||
name: 'Log',
|
name: 'Log',
|
||||||
component: () => import('@/views/service/log/index'),
|
component: () => import('@/views/service/log'),
|
||||||
meta: { title: '监控日志' }
|
meta: { title: '监控日志' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'blacklist',
|
||||||
|
name: 'Blacklist',
|
||||||
|
component: () => import('@/views/service/ipBlacklist'),
|
||||||
|
meta: { title: 'IP黑名单' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -108,17 +108,11 @@ export default {
|
|||||||
roleDialogFormRules: {
|
roleDialogFormRules: {
|
||||||
roleCode: [
|
roleCode: [
|
||||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||||
{ min: 1, max: 64, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
{ min: 1, max: 64, message: '长度在 1 到 64 个字符', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
description: [
|
description: [
|
||||||
{ max: 64, message: '不能超过 64 个字符', trigger: 'blur' }
|
{ max: 64, message: '不能超过 64 个字符', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
},
|
|
||||||
rulesIsvForm: {
|
|
||||||
appKey: [
|
|
||||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
|
||||||
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
184
sop-admin/sop-admin-vue/src/views/service/ipBlacklist.vue
Normal file
184
sop-admin/sop-admin-vue/src/views/service/ipBlacklist.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini">
|
||||||
|
<el-form-item label="IP">
|
||||||
|
<el-input v-model="searchFormData.ip" :clearable="true" placeholder="输入IP" style="width: 250px;" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" @click="loadTable">查询</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button type="primary" size="mini" icon="el-icon-plus" style="margin-bottom: 10px;" @click="onAdd">新增IP</el-button>
|
||||||
|
<el-table
|
||||||
|
:data="pageInfo.rows"
|
||||||
|
border
|
||||||
|
highlight-current-row
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
prop="ip"
|
||||||
|
label="IP"
|
||||||
|
width="200"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="remark"
|
||||||
|
label="备注"
|
||||||
|
width="300"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="gmtCreate"
|
||||||
|
label="添加时间"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="gmtModified"
|
||||||
|
label="修改时间"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
width="150"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
|
||||||
|
<el-button type="text" size="mini" @click="onTableDelete(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="dialogTitle"
|
||||||
|
:visible.sync="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="resetForm('dialogForm')"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="dialogForm"
|
||||||
|
:rules="dialogFormRules"
|
||||||
|
:model="dialogFormData"
|
||||||
|
label-width="120px"
|
||||||
|
size="mini"
|
||||||
|
>
|
||||||
|
<el-form-item prop="ip" label="IP">
|
||||||
|
<el-input v-show="dialogFormData.id === 0" v-model="dialogFormData.ip" />
|
||||||
|
<span v-show="dialogFormData.id > 0">{{ dialogFormData.ip }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="remark" label="备注">
|
||||||
|
<el-input v-model="dialogFormData.remark" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="onDialogSave">保 存</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
const ipValidator = (rule, value, callback) => {
|
||||||
|
if (value === '') {
|
||||||
|
callback(new Error('请输入IP'))
|
||||||
|
} else {
|
||||||
|
const regexIP = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||||
|
if (!regexIP.test(value)) {
|
||||||
|
callback(new Error('IP格式不正确'))
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
searchFormData: {
|
||||||
|
ip: '',
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10
|
||||||
|
},
|
||||||
|
pageInfo: {
|
||||||
|
rows: [],
|
||||||
|
total: 0
|
||||||
|
},
|
||||||
|
dialogVisible: false,
|
||||||
|
dialogTitle: '',
|
||||||
|
dialogFormData: {
|
||||||
|
id: 0,
|
||||||
|
ip: '',
|
||||||
|
remark: ''
|
||||||
|
},
|
||||||
|
dialogFormRules: {
|
||||||
|
ip: [
|
||||||
|
{ validator: ipValidator, trigger: 'blur' },
|
||||||
|
{ min: 1, max: 64, message: '长度在 1 到 64 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
remark: [
|
||||||
|
{ max: 100, message: '不能超过 100 个字符', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadTable()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadTable: function() {
|
||||||
|
this.post('ip.blacklist.page', this.searchFormData, function(resp) {
|
||||||
|
this.pageInfo = resp.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onTableUpdate: function(row) {
|
||||||
|
this.dialogTitle = '修改IP'
|
||||||
|
this.dialogVisible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
Object.assign(this.dialogFormData, row)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onTableDelete: function(row) {
|
||||||
|
this.confirm(`确认要移除IP【${row.ip}】吗?`, function(done) {
|
||||||
|
const data = {
|
||||||
|
id: row.id
|
||||||
|
}
|
||||||
|
this.post('ip.blacklist.del', data, function() {
|
||||||
|
done()
|
||||||
|
this.tip('删除成功')
|
||||||
|
this.loadTable()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDialogSave: function() {
|
||||||
|
this.$refs.dialogForm.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
const uri = this.dialogFormData.id ? 'ip.blacklist.update' : 'ip.blacklist.add'
|
||||||
|
this.post(uri, this.dialogFormData, function() {
|
||||||
|
this.dialogVisible = false
|
||||||
|
this.loadTable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSizeChange: function(size) {
|
||||||
|
this.searchFormData.pageSize = size
|
||||||
|
this.loadTable()
|
||||||
|
},
|
||||||
|
onAdd: function() {
|
||||||
|
this.dialogTitle = '新增IP'
|
||||||
|
this.dialogVisible = true
|
||||||
|
this.dialogFormData.id = 0
|
||||||
|
},
|
||||||
|
onPageIndexChange: function(pageIndex) {
|
||||||
|
this.searchFormData.pageIndex = pageIndex
|
||||||
|
this.loadTable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@@ -1,347 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-container>
|
|
||||||
<el-aside style="min-height: 300px;width: 200px;">
|
|
||||||
<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">
|
|
||||||
<span v-if="data.label.length < 15">{{ data.label }}</span>
|
|
||||||
<span v-else>
|
|
||||||
<el-tooltip :content="data.label" class="item" effect="light" placement="right">
|
|
||||||
<span>{{ data.label.substring(0, 15) + '...' }}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</el-tree>
|
|
||||||
</el-aside>
|
|
||||||
<el-main style="padding-top:0">
|
|
||||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline">
|
|
||||||
<el-form-item label="路由名称">
|
|
||||||
<el-input v-model="searchFormData.id" placeholder="输入接口名或版本号" size="mini" />
|
|
||||||
</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-table
|
|
||||||
:data="tableData"
|
|
||||||
border
|
|
||||||
max-height="500"
|
|
||||||
>
|
|
||||||
<el-table-column
|
|
||||||
prop="name"
|
|
||||||
label="接口名 (版本号)"
|
|
||||||
width="200"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
prop="limitType"
|
|
||||||
label="限流策略"
|
|
||||||
width="120"
|
|
||||||
>
|
|
||||||
<template slot-scope slot="header">
|
|
||||||
限流策略 <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="500"
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
label="操作"
|
|
||||||
width="80"
|
|
||||||
>
|
|
||||||
<template slot-scope="scope">
|
|
||||||
<el-button type="text" size="mini" @click="onTableUpdate(scope.row)">修改</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<!-- dialog -->
|
|
||||||
<el-dialog
|
|
||||||
title="设置限流"
|
|
||||||
:visible.sync="limitDialogVisible"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
@close="onLimitDialogClose"
|
|
||||||
>
|
|
||||||
<el-form ref="limitDialogFormMain" :model="limitDialogFormData">
|
|
||||||
<el-form-item label="id" :label-width="formLabelWidth">
|
|
||||||
<el-input v-model="limitDialogFormData.routeId" readonly="readonly"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="限流策略" :label-width="formLabelWidth">
|
|
||||||
<el-radio-group v-model="limitDialogFormData.limitType">
|
|
||||||
<el-radio :label="1">漏桶策略</el-radio>
|
|
||||||
<el-radio :label="2">令牌桶策略</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="开启状态" :label-width="formLabelWidth">
|
|
||||||
<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>
|
|
||||||
<el-form
|
|
||||||
v-show="limitDialogFormData.limitType === 1 && limitDialogFormData.limitStatus"
|
|
||||||
ref="limitDialogFormLeaky"
|
|
||||||
:rules="rulesLeaky"
|
|
||||||
:model="limitDialogFormData"
|
|
||||||
>
|
|
||||||
<el-form-item label="每秒可处理请求数" prop="execCountPerSecond" :label-width="formLabelWidth">
|
|
||||||
<el-input-number v-model="limitDialogFormData.execCountPerSecond" controls-position="right" :min="1" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="错误码" prop="limitCode" :label-width="formLabelWidth">
|
|
||||||
<el-input v-model="limitDialogFormData.limitCode" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="错误信息" prop="limitMsg" :label-width="formLabelWidth">
|
|
||||||
<el-input v-model="limitDialogFormData.limitMsg" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-form
|
|
||||||
v-show="limitDialogFormData.limitType === 2 && limitDialogFormData.limitStatus"
|
|
||||||
ref="limitDialogFormToken"
|
|
||||||
:rules="rulesToken"
|
|
||||||
:model="limitDialogFormData"
|
|
||||||
>
|
|
||||||
<el-form-item label="令牌桶容量" prop="tokenBucketCount" :label-width="formLabelWidth">
|
|
||||||
<el-input-number v-model="limitDialogFormData.tokenBucketCount" controls-position="right" :min="1" />
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
filterText: '',
|
|
||||||
treeData: [],
|
|
||||||
tableData: [],
|
|
||||||
serviceId: '',
|
|
||||||
searchFormData: {},
|
|
||||||
defaultProps: {
|
|
||||||
children: 'children',
|
|
||||||
label: 'label'
|
|
||||||
},
|
|
||||||
// dialog
|
|
||||||
limitDialogFormData: {
|
|
||||||
routeId: '',
|
|
||||||
execCountPerSecond: 5,
|
|
||||||
limitCode: '',
|
|
||||||
limitMsg: '',
|
|
||||||
tokenBucketCount: 5,
|
|
||||||
limitStatus: 0, // 0: 停用,1:启用
|
|
||||||
limitType: 1
|
|
||||||
},
|
|
||||||
rulesLeaky: {
|
|
||||||
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' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
rulesToken: {
|
|
||||||
tokenBucketCount: [
|
|
||||||
{ required: true, message: '不能为空', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
formLabelWidth: '150px',
|
|
||||||
limitDialogVisible: false
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
filterText(val) {
|
|
||||||
this.$refs.tree2.filter(val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadTree()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 加载树
|
|
||||||
loadTree: function() {
|
|
||||||
this.post('zookeeper.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()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 数组转成树状结构
|
|
||||||
* @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++) {
|
|
||||||
const item = { label: data[i].serviceId, parentId: 1 }
|
|
||||||
children.push(item)
|
|
||||||
}
|
|
||||||
root.children = children
|
|
||||||
result.push(root)
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
// table
|
|
||||||
loadTable: function() {
|
|
||||||
this.post('route.limit.list', this.searchFormData, function(resp) {
|
|
||||||
this.tableData = resp.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onSearchTable: function() {
|
|
||||||
this.loadTable()
|
|
||||||
},
|
|
||||||
onTableUpdate: function(row) {
|
|
||||||
this.limitDialogVisible = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
Object.assign(this.limitDialogFormData, row)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
resetForm(formName) {
|
|
||||||
const frm = this.$refs[formName]
|
|
||||||
frm && frm.resetFields()
|
|
||||||
},
|
|
||||||
onLimitDialogClose: function() {
|
|
||||||
this.resetForm('limitDialogFormLeaky')
|
|
||||||
this.resetForm('limitDialogFormToken')
|
|
||||||
this.limitDialogVisible = false
|
|
||||||
},
|
|
||||||
infoRender: function(row) {
|
|
||||||
if (!row.hasRecord) {
|
|
||||||
return '--'
|
|
||||||
}
|
|
||||||
const html = []
|
|
||||||
if (row.limitType === 1) {
|
|
||||||
html.push('每秒可处理请求数:' + row.execCountPerSecond)
|
|
||||||
html.push('subCode:' + row.limitCode)
|
|
||||||
html.push('subMsg:' + row.limitMsg)
|
|
||||||
} else if (row.limitType === 2) {
|
|
||||||
html.push('令牌桶容量:' + row.tokenBucketCount)
|
|
||||||
}
|
|
||||||
return html.join(',')
|
|
||||||
},
|
|
||||||
onLimitDialogSave: function() {
|
|
||||||
this.doValidate(function() {
|
|
||||||
this.limitDialogFormData.serviceId = this.serviceId
|
|
||||||
this.post('route.limit.update', this.limitDialogFormData, function(resp) {
|
|
||||||
this.limitDialogVisible = false
|
|
||||||
this.loadTable()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
doValidate: function(callback) {
|
|
||||||
const that = this
|
|
||||||
if (this.limitDialogFormData.limitStatus === 0) {
|
|
||||||
callback.call(this)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.limitDialogFormData.limitType === 1) {
|
|
||||||
this.$refs['limitDialogFormLeaky'].validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
callback.call(that)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.$refs['limitDialogFormToken'].validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
callback.call(that)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLimitTypeTipClick: function() {
|
|
||||||
const leakyRemark = '漏桶策略:每秒处理固定数量的请求,超出请求返回错误信息。'
|
|
||||||
const tokenRemark = '令牌桶策略:每秒放置固定数量的令牌数,每个请求进来后先去拿令牌,拿到了令牌才能继续,拿不到则等候令牌重新生成了再拿。'
|
|
||||||
const content = leakyRemark + '<br>' + tokenRemark
|
|
||||||
this.$alert(content, '限流策略', {
|
|
||||||
dangerouslyUseHTMLString: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -26,7 +26,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- eureka 服务发现 -->
|
<!-- eureka 服务发现 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-service-common</artifactId>
|
<artifactId>sop-service-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
<artifactId>sop-gateway-common</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
|
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
package com.gitee.sop.gateway.manager;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.gitee.sop.gateway.mapper.IPBlacklistMapper;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限流配置管理
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IPBlacklistMapper ipBlacklistMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Environment environment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load() {
|
||||||
|
List<String> ipList = ipBlacklistMapper.listAllIP();
|
||||||
|
log.info("加载IP黑名单, size:{}", ipList.size());
|
||||||
|
ipList.stream().forEach(this::add);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
protected void after() throws Exception {
|
||||||
|
ZookeeperContext.setEnvironment(environment);
|
||||||
|
String path = ZookeeperContext.getIpBlacklistChannelPath();
|
||||||
|
ZookeeperContext.listenPath(path, nodeCache -> {
|
||||||
|
String nodeData = new String(nodeCache.getCurrentData().getData());
|
||||||
|
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
||||||
|
final IPDto ipDto = JSON.parseObject(channelMsg.getData(), IPDto.class);
|
||||||
|
String ip = ipDto.getIp();
|
||||||
|
switch (channelMsg.getOperation()) {
|
||||||
|
case "add":
|
||||||
|
log.info("添加IP黑名单,ip:{}", ip);
|
||||||
|
add(ip);
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
log.info("移除IP黑名单,ip:{}", ip);
|
||||||
|
remove(ip);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class IPDto {
|
||||||
|
private String ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -13,5 +13,6 @@ public class ManagerInitializer {
|
|||||||
apiConfig.setIsvRoutePermissionManager(new DbIsvRoutePermissionManager());
|
apiConfig.setIsvRoutePermissionManager(new DbIsvRoutePermissionManager());
|
||||||
apiConfig.setRouteConfigManager(new DbRouteConfigManager());
|
apiConfig.setRouteConfigManager(new DbRouteConfigManager());
|
||||||
apiConfig.setLimitConfigManager(new DbLimitConfigManager());
|
apiConfig.setLimitConfigManager(new DbLimitConfigManager());
|
||||||
|
apiConfig.setIpBlacklistManager(new DbIPBlacklistManager());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
package com.gitee.sop.gateway.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP黑名单
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface IPBlacklistMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有IP
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Select("SELECT ip FROM config_ip_blacklist")
|
||||||
|
List<String> listAllIP();
|
||||||
|
|
||||||
|
}
|
@@ -25,7 +25,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-registry-api</artifactId>
|
<artifactId>sop-registry-api</artifactId>
|
||||||
<version>1.12.4-SNAPSHOT</version>
|
<version>1.13.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
11
sop.sql
11
sop.sql
@@ -14,6 +14,7 @@ DROP TABLE IF EXISTS `config_limit`;
|
|||||||
DROP TABLE IF EXISTS `admin_user_info`;
|
DROP TABLE IF EXISTS `admin_user_info`;
|
||||||
DROP TABLE IF EXISTS `config_common`;
|
DROP TABLE IF EXISTS `config_common`;
|
||||||
DROP TABLE IF EXISTS `isv_keys`;
|
DROP TABLE IF EXISTS `isv_keys`;
|
||||||
|
DROP TABLE IF EXISTS `config_ip_blacklist`;
|
||||||
|
|
||||||
CREATE TABLE `admin_user_info` (
|
CREATE TABLE `admin_user_info` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
@@ -164,6 +165,16 @@ CREATE TABLE `isv_keys` (
|
|||||||
UNIQUE KEY `uk_appkey` (`app_key`) USING BTREE
|
UNIQUE KEY `uk_appkey` (`app_key`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ISV秘钥';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='ISV秘钥';
|
||||||
|
|
||||||
|
CREATE TABLE `config_ip_blacklist` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`ip` varchar(64) NOT NULL DEFAULT '' COMMENT 'ip',
|
||||||
|
`remark` varchar(128) DEFAULT NULL COMMENT '备注',
|
||||||
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_ip` (`ip`) USING BTREE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP黑名单';
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;
|
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user