限流,支持用户.

This commit is contained in:
许晓东
2023-02-05 23:08:14 +08:00
parent 608f7cdc47
commit 5a87e9cad8
9 changed files with 585 additions and 19 deletions

View File

@@ -11,6 +11,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.common.config.internals.QuotaConfigs;
import org.apache.kafka.common.quota.ClientQuotaEntity;
import org.springframework.stereotype.Service;
import scala.Tuple2;
import java.util.*;
import java.util.stream.Collectors;
@@ -98,10 +99,16 @@ public class ClientQuotaServiceImpl implements ClientQuotaService {
configsToBeAddedMap.put(QuotaConfigs.REQUEST_PERCENTAGE_OVERRIDE_CONFIG, String.valueOf(Math.floor(Double.valueOf(request.getRequestPercentage()))));
}
clientQuotaConsole.addQuotaConfigs(types, names, configsToBeAddedMap);
Tuple2<Object, String> tuple2 = clientQuotaConsole.addQuotaConfigs(types, names, configsToBeAddedMap);
if (!(Boolean) tuple2._1) {
return ResponseData.create().failed(tuple2._2);
}
if (CollectionUtils.isNotEmpty(request.getDeleteConfigs())) {
List<String> delete = request.getDeleteConfigs().stream().map(key -> configDict.get(key)).collect(Collectors.toList());
clientQuotaConsole.deleteQuotaConfigs(types, names, delete);
Tuple2<Object, String> tuple2Del = clientQuotaConsole.deleteQuotaConfigs(types, names, delete);
if (!(Boolean) tuple2Del._1) {
return ResponseData.create().failed(tuple2Del._2);
}
}
return ResponseData.create().success();
}
@@ -118,7 +125,10 @@ public class ClientQuotaServiceImpl implements ClientQuotaService {
configs.add(QuotaConfigs.PRODUCER_BYTE_RATE_OVERRIDE_CONFIG);
configs.add(QuotaConfigs.CONSUMER_BYTE_RATE_OVERRIDE_CONFIG);
configs.add(QuotaConfigs.REQUEST_PERCENTAGE_OVERRIDE_CONFIG);
clientQuotaConsole.deleteQuotaConfigs(types, names, configs);
Tuple2<Object, String> tuple2 = clientQuotaConsole.deleteQuotaConfigs(types, names, configs);
if (!(Boolean) tuple2._1) {
return ResponseData.create().failed(tuple2._2);
}
return ResponseData.create().success();
}

View File

@@ -145,28 +145,29 @@ export default {
params.producerRate = params.producerRate * unitMap[this.producerRateUnit];
}
params.types = [];
params.names = [];
if (this.showUser) {
params.types.push("user");
if (params.user) {
params.names = [params.user.trim()];
params.names.push(params.user.trim());
} else {
params.names = [""];
params.names.push("");
}
}
if (this.showClientId) {
params.types.push("client-id");
if (params.client) {
params.names = [params.client.trim()];
params.names.push(params.client.trim());
} else {
params.names = [""];
params.names.push("");
}
}
if (this.showIP) {
params.types.push("ip");
if (params.ip) {
params.names = [params.ip.trim()];
params.names.push(params.ip.trim());
} else {
params.names = [""];
params.names.push("");
}
}
this.loading = true;

View File

@@ -55,7 +55,7 @@ export default {
data() {
return {
loading: false,
form: this.$form.createForm(this, {name: "client_id_search_offset"}),
form: this.$form.createForm(this, {name: "client_id_quota"}),
data: [],
showAlterQuotaDialog: false,
showAddQuotaDialog: false,
@@ -64,7 +64,6 @@ export default {
title: "客户端ID",
dataIndex: "client",
key: "client",
width: 300,
slots: {title: "client"},
scopedSlots: {customRender: "client"},
},

View File

@@ -2,16 +2,19 @@
<div class="content">
<a-spin :spinning="loading">
<a-tabs default-active-key="1" size="large" tabPosition="top">
<a-tab-pane key="1" tab="客户端ID">
<ClientIDQuota></ClientIDQuota>
<a-tab-pane key="1" tab="使用说明">
</a-tab-pane>
<a-tab-pane key="2" tab="用户">
<UserQuota></UserQuota>
</a-tab-pane>
<a-tab-pane key="3" tab="客户端ID和用户">
<a-tab-pane key="3" tab="客户端ID">
<ClientIDQuota></ClientIDQuota>
</a-tab-pane>
<a-tab-pane key="4" tab="IP">
<a-tab-pane key="4" tab="用户_客户端ID">
<UserAndClientIDQuota></UserAndClientIDQuota>
</a-tab-pane>
<a-tab-pane key="5" tab="使用说明">
<a-tab-pane key="5" tab="IP">
<IpQuota></IpQuota>
</a-tab-pane>
</a-tabs>
</a-spin>
@@ -20,10 +23,13 @@
<script>
import ClientIDQuota from "@/views/quota/ClientIDQuota.vue";
import UserQuota from "@/views/quota/UserQuota.vue";
import UserAndClientIDQuota from "@/views/quota/UserAndClientIDQuota.vue";
import IpQuota from "@/views/quota/IpQuota.vue";
export default {
name: "ClientQuota",
components: {ClientIDQuota},
components: {ClientIDQuota, UserQuota, UserAndClientIDQuota, IpQuota},
data() {
return {
loading: false,

View File

@@ -0,0 +1,180 @@
<template>
<div class="tab-content">
<a-spin :spinning="loading">
<div id="search-offset-form-advanced-search">
<a-form
class="ant-advanced-search-form"
:form="form"
@submit="handleSearch"
>
<a-row :gutter="24">
<a-col :span="16">
<a-form-item label="IP">
<a-input
v-decorator="[
'ip',
]"
placeholder="请输入ip!"
/>
</a-form-item>
</a-col>
<a-col :span="2" :style="{ textAlign: 'right' }">
<a-form-item>
<a-button type="primary" html-type="submit"> 搜索</a-button>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
<div class="operation-row-button">
<a-button type="primary" @click="openAddQuotaDialog"
>新增配置
</a-button>
</div>
<QuotaList type="ip" :columns="columns" :data="data" @refreshQuotaList="refresh"></QuotaList>
<AddQuotaConfig type="ip" :visible="showAddQuotaDialog" :showIP="true" @closeAddQuotaDialog="closeAddQuotaDialog"></AddQuotaConfig>
</a-spin>
</div>
</template>
<script>
import request from "@/utils/request";
import {KafkaClientQuotaApi} from "@/utils/api";
import notification from "ant-design-vue/lib/notification";
import QuotaList from "@/views/quota/QuotaList.vue";
import AddQuotaConfig from "@/views/quota/AddQuotaConfig.vue";
export default {
name: "IpQuota",
components: {QuotaList, AddQuotaConfig},
props: {
topicList: {
type: Array,
},
},
data() {
return {
loading: false,
form: this.$form.createForm(this, {name: "ip_quota"}),
data: [],
showAlterQuotaDialog: false,
showAddQuotaDialog: false,
columns: [
{
title: "IP",
dataIndex: "ip",
key: "ip",
slots: {title: "ip"},
scopedSlots: {customRender: "ip"},
},
{
title: "生产速率(带宽/秒)",
dataIndex: "producerRate",
key: "producerRate",
},
{
title: "消费速率(带宽/秒)",
dataIndex: "consumerRate",
key: "consumerRate",
},
{
title: "吞吐量(请求占比*100)",
dataIndex: "requestPercentage",
key: "requestPercentage",
},
],
};
},
methods: {
handleSearch() {
this.form.validateFields((err, values) => {
if (!err) {
this.loading = true;
const params = {types: ["ip"]};
if (values.ip) {
params.names = [values.ip.trim()];
}
request({
url: KafkaClientQuotaApi.getClientQuotaConfigs.url,
method: KafkaClientQuotaApi.getClientQuotaConfigs.method,
data: params,
}).then((res) => {
this.loading = false;
if (res.code == 0) {
this.data = res.data;
} else {
notification.error({
message: "error",
description: res.msg,
});
}
});
}
});
},
openAddQuotaDialog() {
this.showAddQuotaDialog = true;
},
closeAddQuotaDialog(p) {
if (p.refresh) {
this.handleSearch();
}
this.showAddQuotaDialog = false;
},
refresh() {
this.handleSearch();
},
},
created() {
this.handleSearch();
},
};
</script>
<style scoped>
.tab-content {
width: 100%;
height: 100%;
}
.ant-advanced-search-form {
padding: 24px;
background: #fbfbfb;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.ant-advanced-search-form .ant-form-item {
display: flex;
}
.ant-advanced-search-form input {
width: 400px;
}
.ant-advanced-search-form .ant-form-item-control-wrapper {
flex: 1;
}
#components-form-topic-advanced-search .ant-form {
max-width: none;
margin-bottom: 1%;
}
#search-offset-form-advanced-search .search-result-list {
margin-top: 16px;
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
padding-top: 80px;
}
.operation-row-button {
height: 4%;
text-align: left;
margin-bottom: 5px;
margin-top: 5px;
}
</style>

View File

@@ -15,6 +15,9 @@
<div slot="client" slot-scope="text">
<span v-if="text">{{ text }}</span><span v-else style="color: red">默认配置</span>
</div>
<div slot="user" slot-scope="text">
<span v-if="text">{{ text }}</span><span v-else style="color: red">默认配置</span>
</div>
<div slot="operation" slot-scope="record">
<a-popconfirm

View File

@@ -148,7 +148,7 @@ export default {
params.deleteConfigs.push("consumerRate");
}
if (values.producerRate) {
const num = typeof (values.producerRate) && values.producerRate.indexOf(" ") > 0 ? values.producerRate.split(" ")[0] : values.producerRate;
const num = typeof (values.producerRate) == "string" && values.producerRate.indexOf(" ") > 0 ? values.producerRate.split(" ")[0] : values.producerRate;
params.producerRate = num * unitMap[this.producerRateUnit];
} else {
params.deleteConfigs.push("producerRate");
@@ -183,7 +183,6 @@ export default {
params.names = [""];
}
}
console.log(params)
this.loading = true;
request({
url: KafkaClientQuotaApi.alterClientQuotaConfigs.url,

View File

@@ -0,0 +1,187 @@
<template>
<div class="tab-content">
<a-spin :spinning="loading">
<div id="search-offset-form-advanced-search">
<a-form
class="ant-advanced-search-form"
:form="form"
@submit="handleSearch"
>
<a-row :gutter="24">
<a-col :span="16">
<a-form-item label="用户标识">
<a-input
v-decorator="[
'user',
]"
placeholder="请输入用户标识,如:用户名!"
/>
</a-form-item>
</a-col>
<a-col :span="2" :style="{ textAlign: 'right' }">
<a-form-item>
<a-button type="primary" html-type="submit"> 搜索</a-button>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
<div class="operation-row-button">
<a-button type="primary" @click="openAddQuotaDialog"
>新增配置
</a-button>
</div>
<QuotaList type="user&client-id" :columns="columns" :data="data" @refreshQuotaList="refresh"></QuotaList>
<AddQuotaConfig type="user&client-id" :visible="showAddQuotaDialog" :showUser="true" :showClientId="true" @closeAddQuotaDialog="closeAddQuotaDialog"></AddQuotaConfig>
</a-spin>
</div>
</template>
<script>
import request from "@/utils/request";
import {KafkaClientQuotaApi} from "@/utils/api";
import notification from "ant-design-vue/lib/notification";
import QuotaList from "@/views/quota/QuotaList.vue";
import AddQuotaConfig from "@/views/quota/AddQuotaConfig.vue";
export default {
name: "UserAndClientIDQuota",
components: {QuotaList, AddQuotaConfig},
props: {
topicList: {
type: Array,
},
},
data() {
return {
loading: false,
form: this.$form.createForm(this, {name: "user_client_id_quota"}),
data: [],
showAlterQuotaDialog: false,
showAddQuotaDialog: false,
columns: [
{
title: "用户标识",
dataIndex: "user",
key: "user",
slots: {title: "user"},
scopedSlots: {customRender: "user"},
},
{
title: "客户端ID",
dataIndex: "client",
key: "client",
slots: {title: "client"},
scopedSlots: {customRender: "client"},
},
{
title: "生产速率(带宽/秒)",
dataIndex: "producerRate",
key: "producerRate",
},
{
title: "消费速率(带宽/秒)",
dataIndex: "consumerRate",
key: "consumerRate",
},
{
title: "吞吐量(请求占比*100)",
dataIndex: "requestPercentage",
key: "requestPercentage",
},
],
};
},
methods: {
handleSearch() {
this.form.validateFields((err, values) => {
if (!err) {
this.loading = true;
const params = {types: ["user"]};
if (values.user) {
params.names = [values.user.trim()];
}
request({
url: KafkaClientQuotaApi.getClientQuotaConfigs.url,
method: KafkaClientQuotaApi.getClientQuotaConfigs.method,
data: params,
}).then((res) => {
this.loading = false;
if (res.code == 0) {
this.data = res.data;
} else {
notification.error({
message: "error",
description: res.msg,
});
}
});
}
});
},
openAddQuotaDialog() {
this.showAddQuotaDialog = true;
},
closeAddQuotaDialog(p) {
if (p.refresh) {
this.handleSearch();
}
this.showAddQuotaDialog = false;
},
refresh() {
this.handleSearch();
},
},
created() {
this.handleSearch();
},
};
</script>
<style scoped>
.tab-content {
width: 100%;
height: 100%;
}
.ant-advanced-search-form {
padding: 24px;
background: #fbfbfb;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.ant-advanced-search-form .ant-form-item {
display: flex;
}
.ant-advanced-search-form input {
width: 400px;
}
.ant-advanced-search-form .ant-form-item-control-wrapper {
flex: 1;
}
#components-form-topic-advanced-search .ant-form {
max-width: none;
margin-bottom: 1%;
}
#search-offset-form-advanced-search .search-result-list {
margin-top: 16px;
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
padding-top: 80px;
}
.operation-row-button {
height: 4%;
text-align: left;
margin-bottom: 5px;
margin-top: 5px;
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<div class="tab-content">
<a-spin :spinning="loading">
<div id="search-offset-form-advanced-search">
<a-form
class="ant-advanced-search-form"
:form="form"
@submit="handleSearch"
>
<a-row :gutter="24">
<a-col :span="16">
<a-form-item label="用户标识">
<a-input
v-decorator="[
'user',
]"
placeholder="请输入用户标识,如:用户名!"
/>
</a-form-item>
</a-col>
<a-col :span="2" :style="{ textAlign: 'right' }">
<a-form-item>
<a-button type="primary" html-type="submit"> 搜索</a-button>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
<div class="operation-row-button">
<a-button type="primary" @click="openAddQuotaDialog"
>新增配置
</a-button>
</div>
<QuotaList type="user" :columns="columns" :data="data" @refreshQuotaList="refresh"></QuotaList>
<AddQuotaConfig type="user" :visible="showAddQuotaDialog" :showUser="true" @closeAddQuotaDialog="closeAddQuotaDialog"></AddQuotaConfig>
</a-spin>
</div>
</template>
<script>
import request from "@/utils/request";
import {KafkaClientQuotaApi} from "@/utils/api";
import notification from "ant-design-vue/lib/notification";
import QuotaList from "@/views/quota/QuotaList.vue";
import AddQuotaConfig from "@/views/quota/AddQuotaConfig.vue";
export default {
name: "UserQuota",
components: {QuotaList, AddQuotaConfig},
props: {
topicList: {
type: Array,
},
},
data() {
return {
loading: false,
form: this.$form.createForm(this, {name: "user_quota"}),
data: [],
showAlterQuotaDialog: false,
showAddQuotaDialog: false,
columns: [
{
title: "用户标识",
dataIndex: "user",
key: "user",
width: 300,
slots: {title: "user"},
scopedSlots: {customRender: "user"},
},
{
title: "生产速率(带宽/秒)",
dataIndex: "producerRate",
key: "producerRate",
},
{
title: "消费速率(带宽/秒)",
dataIndex: "consumerRate",
key: "consumerRate",
},
{
title: "吞吐量(请求占比*100)",
dataIndex: "requestPercentage",
key: "requestPercentage",
},
],
};
},
methods: {
handleSearch() {
this.form.validateFields((err, values) => {
if (!err) {
this.loading = true;
const params = {types: ["user"]};
if (values.user) {
params.names = [values.user.trim()];
}
request({
url: KafkaClientQuotaApi.getClientQuotaConfigs.url,
method: KafkaClientQuotaApi.getClientQuotaConfigs.method,
data: params,
}).then((res) => {
this.loading = false;
if (res.code == 0) {
this.data = res.data;
} else {
notification.error({
message: "error",
description: res.msg,
});
}
});
}
});
},
openAddQuotaDialog() {
this.showAddQuotaDialog = true;
},
closeAddQuotaDialog(p) {
if (p.refresh) {
this.handleSearch();
}
this.showAddQuotaDialog = false;
},
refresh() {
this.handleSearch();
},
},
created() {
this.handleSearch();
},
};
</script>
<style scoped>
.tab-content {
width: 100%;
height: 100%;
}
.ant-advanced-search-form {
padding: 24px;
background: #fbfbfb;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.ant-advanced-search-form .ant-form-item {
display: flex;
}
.ant-advanced-search-form input {
width: 400px;
}
.ant-advanced-search-form .ant-form-item-control-wrapper {
flex: 1;
}
#components-form-topic-advanced-search .ant-form {
max-width: none;
margin-bottom: 1%;
}
#search-offset-form-advanced-search .search-result-list {
margin-top: 16px;
border: 1px dashed #e9e9e9;
border-radius: 6px;
background-color: #fafafa;
min-height: 200px;
text-align: center;
padding-top: 80px;
}
.operation-row-button {
height: 4%;
text-align: left;
margin-bottom: 5px;
margin-top: 5px;
}
</style>