Commit 6d512971 by 李根

1 添加网关规则持久化至nacos 2 修改网关限流 列表页 只调用一个接口,获取api分组数据和网关流控规则数据

parent a3289421
......@@ -17,9 +17,9 @@ public class Test {
public static void main(String[] args) {
// 测试限流
test(25, "/test2");
test(5, "/test/test2");
// 测试降级
test(5, "/test3");
// test(5, "/test3");
}
......@@ -37,7 +37,7 @@ public class Test {
Thread thread = new Thread(
() -> {
while (true) {
ListenableFuture<Response> execute = asyncHttpClient.prepareGet("http://localhost:19966" + resource).execute();
ListenableFuture<Response> execute = asyncHttpClient.prepareGet("http://localhost:7051/columbia" + resource).execute();
System.out.println("resources:"+resource+"-----"+Thread.currentThread().getName());
try {
Thread.sleep(random.nextInt(1000));
......
......@@ -20,13 +20,15 @@ import com.alibaba.csp.sentinel.init.InitExecutor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
/**
* Sentinel dashboard application.
*
* @author Carpenter Lee
*/
@SpringBootApplication
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class} )
public class DashboardApplication {
public static void main(String[] args) {
......
......@@ -15,6 +15,8 @@
*/
package com.alibaba.csp.sentinel.dashboard.config;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
......@@ -58,6 +60,26 @@ public class NacosConfig {
return s -> JSON.parseArray(s, DegradeRuleEntity.class);
}
@Bean
public Converter<List<ApiDefinitionEntity>, String> apiDefinitionEntryEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<ApiDefinitionEntity>> apiDefinitionEntryDecoder() {
return s -> JSON.parseArray(s, ApiDefinitionEntity.class);
}
@Bean
public Converter<List<GatewayFlowRuleEntity>, String> gatewayFlowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<GatewayFlowRuleEntity>> gatewayFlowRuleEntityDecoder() {
return s -> JSON.parseArray(s, GatewayFlowRuleEntity.class);
}
@Bean
public ConfigService nacosConfigService() throws Exception {
......
......@@ -32,11 +32,15 @@ public class NacosProperties {
private String groupId = "DEFAULT_GROUP";
private String flowDataIdPrefix = "-flow-rules";
private String flowDataIdPrefix = "flow-rules";
private String degradeDataIdPrefix = "-degrade-rules";
private String degradeDataIdPrefix = "degrade-rules";
private String paramFlowDataIdPrefix = "-param-rules";
private String paramFlowDataIdPrefix = "param-rules";
private String gatewayFlowRuleDataIdPrefix = "gateway-flow-rules";
private String gatewayApiDefineDataIdPrefix = "gateway-api-define-rules";
public String getServerAddr() {
return serverAddr;
......@@ -86,9 +90,23 @@ public class NacosProperties {
this.paramFlowDataIdPrefix = paramFlowDataIdPrefix;
}
public String getGatewayFlowRuleDataIdPrefix() {
return gatewayFlowRuleDataIdPrefix;
}
public void setGatewayFlowRuleDataIdPrefix(String gatewayFlowRuleDataIdPrefix) {
this.gatewayFlowRuleDataIdPrefix = gatewayFlowRuleDataIdPrefix;
}
public String getGatewayApiDefineDataIdPrefix() {
return gatewayApiDefineDataIdPrefix;
}
public void setGatewayApiDefineDataIdPrefix(String gatewayApiDefineDataIdPrefix) {
this.gatewayApiDefineDataIdPrefix = gatewayApiDefineDataIdPrefix;
}
// public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";
// public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map";
//
// /**
// * cc for `cluster-client`
......
......@@ -20,17 +20,17 @@ import java.util.List;
@Component
public class MetricEntityDao {
@Autowired
private MongoTemplate mongoTemplate;
// @Autowired
// private MongoTemplate mongoTemplate;
public void save(MetricEntity metric) {
mongoTemplate.insert(metric);
// mongoTemplate.insert(metric);
}
public void savaAll(List<MetricEntity> metricEntities) {
metricEntities.forEach(m -> mongoTemplate.save(m));
}
//
// public void savaAll(List<MetricEntity> metricEntities) {
// metricEntities.forEach(m -> mongoTemplate.save(m));
// }
//
public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource,
long startTime, long endTime) {
Criteria criteria = new Criteria();
......@@ -39,7 +39,8 @@ public class MetricEntityDao {
.and("timestamp").gt(new Date( startTime)).lt(new Date(endTime));
Query query = new Query(criteria);
return mongoTemplate.find(query, MetricEntity.class);
// return mongoTemplate.find(query, MetricEntity.class);
return null;
}
}
......@@ -22,22 +22,24 @@ import java.util.stream.Collectors;
@Component
public class ResourcesListMongoDao {
@Autowired
private MongoTemplate mongoTemplate;
// @Autowired
// private MongoTemplate mongoTemplate;
public void save(ResourceListMongo resource) {
mongoTemplate.save(resource);
// mongoTemplate.save(resource);
}
public List<ResourceListMongo> getByApp(String app) {
Query query = new Query(Criteria.where("app").is(app));
return mongoTemplate.find(query, ResourceListMongo.class);
// return mongoTemplate.find(query, ResourceListMongo.class);
return null;
}
public List<String> getResourceNameListByApp(String app) {
Query query = new Query(Criteria.where("app").is(app));
List<ResourceListMongo> resourceListMongos = mongoTemplate.find(query, ResourceListMongo.class);
return resourceListMongos.stream().map(a -> a.getResources()).collect(Collectors.toList());
// List<ResourceListMongo> resourceListMongos = mongoTemplate.find(query, ResourceListMongo.class);
// return resourceListMongos.stream().map(a -> a.getResources()).collect(Collectors.toList());
return null;
}
}
......@@ -23,9 +23,9 @@ import java.util.Date;
/**
* @author leyou
*/
@Document(collection = "MetricEntity")
//@Document(collection = "MetricEntity")
public class MetricEntity {
@Field("idd")
// @Field("idd")
private Long id;
private Date gmtCreate;
private Date gmtModified;
......
......@@ -83,7 +83,7 @@ public class MetricFetcher {
@Autowired
// @Qualifier("mongoMetricsRepository")
@Qualifier("mongoMetricsRepository")
@Qualifier("inMemoryMetricsRepository")
private MetricsRepository<MetricEntity> metricStore;
@Autowired
private AppManagement appManagement;
......
......@@ -56,6 +56,7 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
nextId();
MAX_ID = entity.getId();
}
// System.out.println(MAX_ID);
T processedEntity = preProcess(entity);
if (processedEntity != null) {
allRules.put(processedEntity.getId(), processedEntity);
......@@ -88,6 +89,7 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
for (T rule : rules) {
savedRules.add(save(rule));
}
System.out.println("--MAXNUM"+MAX_ID);
return savedRules;
}
......
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.rule.gateway;
import com.alibaba.csp.sentinel.dashboard.config.NacosProperties;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("gatewayApiNacosProvider")
public class GatewayApiGroupNacosProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> {
@Autowired
private NacosProperties nacosProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<ApiDefinitionEntity>> converter;
@Override
public List<ApiDefinitionEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName +'-'+ nacosProperties.getGatewayApiDefineDataIdPrefix(),
nacosProperties.getGroupId(), 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.rule.gateway;
import com.alibaba.csp.sentinel.dashboard.config.NacosProperties;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("gatewayApiNacosPublisher")
public class GatewayApiGroupNacosPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> {
@Autowired
private NacosProperties nacosProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<ApiDefinitionEntity>, String> converter;
@Override
public void publish(String app, List<ApiDefinitionEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + '-' + nacosProperties.getGatewayApiDefineDataIdPrefix(),
nacosProperties.getGroupId(), converter.convert(rules));
}
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.rule.gateway;
import com.alibaba.csp.sentinel.dashboard.config.NacosProperties;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("gatewayFlowRuleNacosProvider")
public class GatewayFlowRuleNacosProvider implements DynamicRuleProvider<List<GatewayFlowRuleEntity>> {
@Autowired
private NacosProperties nacosProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<GatewayFlowRuleEntity>> converter;
@Override
public List<GatewayFlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + '-' + nacosProperties.getGatewayFlowRuleDataIdPrefix(),
nacosProperties.getGroupId(), 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.dashboard.rule.gateway;
import com.alibaba.csp.sentinel.dashboard.config.NacosProperties;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("gatewayFlowRuleNacosPublisher")
public class GatewayFlowRuleNacosPublisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> {
@Autowired
private NacosProperties nacosProperties;
@Autowired
private ConfigService configService;
@Autowired
private Converter<List<GatewayFlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + '-' + nacosProperties.getGatewayFlowRuleDataIdPrefix(),
nacosProperties.getGroupId(), converter.convert(rules));
}
}
spring:
application:
name: sentinel-dashboard-plus
# data:
# mongodb:
# port: 27017
# database: db1
# uri: mongodb://lg:123456@xx.xx.xx.xx
# authentication-database: admin
data:
mongodb:
port: 27017
database: db1
uri: mongodb://lg:123456@xx.xx.xx.xx
authentication-database: admin
http:
encoding:
charset: utf-8
......
......@@ -379,6 +379,22 @@ angular
}
})
.state('dashboard.gatewayApiV2', {
templateUrl: 'app/views/gateway/api_v2.html',
url: '/v2/gateway/api/:app',
controller: 'GatewayApiCtlV2',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/gateway/api_v2.js',
]
});
}]
}
})
.state('dashboard.gatewayFlow', {
templateUrl: 'app/views/gateway/flow.html',
url: '/gateway/flow/:app',
......@@ -393,5 +409,21 @@ angular
});
}]
}
})
.state('dashboard.gatewayFlowV2', {
templateUrl: 'app/views/gateway/flow_v2.html',
url: '/v2/gateway/flow/:app',
controller: 'GatewayFlowCtlV2',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/gateway/flow_v2.js',
]
});
}]
}
});
}]);
\ No newline at end of file
var app = angular.module('sentinelDashboardApp');
app.controller('GatewayApiCtlV2', ['$scope', '$stateParams', 'GatewayApiServiceV2', 'ngDialog', 'MachineService',
function ($scope, $stateParams, GatewayApiService, ngDialog, MachineService) {
$scope.app = $stateParams.app;
$scope.apisPageConfig = {
pageSize: 10,
currentPageIndex: 1,
totalPage: 1,
totalCount: 0,
};
$scope.macsInputConfig = {
searchField: ['text', 'value'],
persist: true,
create: false,
maxItems: 1,
render: {
item: function (data, escape) {
return '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};
getApis();
function getApis() {
if (!$scope.macInputModel) {
return;
}
var mac = $scope.macInputModel.split(':');
GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success(
function (data) {
if (data.code == 0 && data.data) {
// To merge rows for api who has more than one predicateItems, here we build data manually
$scope.apis = [];
data.data.forEach(function(api) {
api["predicateItems"].forEach(function (item, index) {
var newItem = {};
newItem["id"] = api["id"];
newItem["app"] = api["app"];
newItem["ip"] = api["ip"];
newItem["port"] = api["port"];
newItem["apiName"] = api["apiName"];
newItem["pattern"] = item["pattern"];
newItem["matchStrategy"] = item["matchStrategy"];
// The itemSize indicates how many rows to merge, by using rowspan="{{api.itemSize}}" in <td> tag
newItem["itemSize"] = api["predicateItems"].length;
// Mark the flag of first item to zero, indicates the start row to merge
newItem["firstFlag"] = index == 0 ? 0 : 1;
// Still hold the data of predicateItems, in order to bind data in edit dialog html
newItem["predicateItems"] = api["predicateItems"];
$scope.apis.push(newItem);
});
});
$scope.apisPageConfig.totalCount = data.data.length;
} else {
$scope.apis = [];
$scope.apisPageConfig.totalCount = 0;
}
});
};
$scope.getApis = getApis;
var gatewayApiDialog;
$scope.editApi = function (api) {
$scope.currentApi = angular.copy(api);
$scope.gatewayApiDialog = {
title: '编辑自定义 API',
type: 'edit',
confirmBtnText: '保存'
};
gatewayApiDialog = ngDialog.open({
template: '/app/views/dialog/gateway/api-dialog.html',
width: 900,
overlay: true,
scope: $scope
});
};
$scope.addNewApi = function () {
var mac = $scope.macInputModel.split(':');
$scope.currentApi = {
grade: 0,
app: $scope.app,
ip: mac[0],
port: mac[1],
predicateItems: [{matchStrategy: 0, pattern: ''}]
};
$scope.gatewayApiDialog = {
title: '新增自定义 API',
type: 'add',
confirmBtnText: '新增'
};
gatewayApiDialog = ngDialog.open({
template: '/app/views/dialog/gateway/api-dialog.html',
width: 900,
overlay: true,
scope: $scope
});
};
$scope.saveApi = function () {
var apiNames = [];
if ($scope.gatewayApiDialog.type === 'add') {
apiNames = $scope.apis.map(function (item, index, array) {
return item["apiName"];
}).filter(function (item, index, array) {
return array.indexOf(item) === index;
});
}
if (!GatewayApiService.checkApiValid($scope.currentApi, apiNames)) {
return;
}
if ($scope.gatewayApiDialog.type === 'add') {
addNewApi($scope.currentApi);
} else if ($scope.gatewayApiDialog.type === 'edit') {
saveApi($scope.currentApi, true);
}
};
function addNewApi(api) {
GatewayApiService.newApi(api).success(function (data) {
if (data.code == 0) {
getApis();
gatewayApiDialog.close();
} else {
alert('新增自定义API失败!' + data.msg);
}
});
};
function saveApi(api, edit) {
GatewayApiService.saveApi(api).success(function (data) {
if (data.code == 0) {
getApis();
if (edit) {
gatewayApiDialog.close();
} else {
confirmDialog.close();
}
} else {
alert('修改自定义API失败!' + data.msg);
}
});
};
var confirmDialog;
$scope.deleteApi = function (api) {
$scope.currentApi = api;
$scope.confirmDialog = {
title: '删除自定义API',
type: 'delete_api',
attentionTitle: '请确认是否删除如下自定义API',
attention: 'API名称: ' + api.apiName,
confirmBtnText: '删除',
};
confirmDialog = ngDialog.open({
template: '/app/views/dialog/confirm-dialog.html',
scope: $scope,
overlay: true
});
};
$scope.confirm = function () {
if ($scope.confirmDialog.type == 'delete_api') {
deleteApi($scope.currentApi);
} else {
console.error('error');
}
};
function deleteApi(api) {
GatewayApiService.deleteApi(api).success(function (data) {
if (data.code == 0) {
getApis();
confirmDialog.close();
} else {
alert('删除自定义API失败!' + data.msg);
}
});
};
$scope.addNewMatchPattern = function() {
var total;
if ($scope.currentApi.predicateItems == null) {
$scope.currentApi.predicateItems = [];
total = 0;
} else {
total = $scope.currentApi.predicateItems.length;
}
$scope.currentApi.predicateItems.splice(total + 1, 0, {matchStrategy: 0, pattern: ''});
};
$scope.removeMatchPattern = function($index) {
if ($scope.currentApi.predicateItems.length <= 1) {
// Should never happen since no remove button will display when only one predicateItem.
alert('至少有一个匹配规则');
return;
}
$scope.currentApi.predicateItems.splice($index, 1);
};
queryAppMachines();
function queryAppMachines() {
MachineService.getAppMachines($scope.app).success(
function (data) {
if (data.code == 0) {
// $scope.machines = data.data;
if (data.data) {
$scope.machines = [];
$scope.macsInputOptions = [];
data.data.forEach(function (item) {
if (item.healthy) {
$scope.macsInputOptions.push({
text: item.ip + ':' + item.port,
value: item.ip + ':' + item.port
});
}
});
}
if ($scope.macsInputOptions.length > 0) {
$scope.macInputModel = $scope.macsInputOptions[0].value;
}
} else {
$scope.macsInputOptions = [];
}
}
);
};
$scope.$watch('macInputModel', function () {
if ($scope.macInputModel) {
getApis();
}
});
}]
);
var app = angular.module('sentinelDashboardApp');
app.controller('GatewayFlowCtlV2', ['$scope', '$stateParams', 'GatewayFlowServiceV2', 'GatewayApiServiceV2', 'ngDialog', 'MachineService',
function ($scope, $stateParams, GatewayFlowService, GatewayApiService, ngDialog, MachineService) {
$scope.app = $stateParams.app;
$scope.rulesPageConfig = {
pageSize: 10,
currentPageIndex: 1,
totalPage: 1,
totalCount: 0,
};
$scope.macsInputConfig = {
searchField: ['text', 'value'],
persist: true,
create: false,
maxItems: 1,
render: {
item: function (data, escape) {
return '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};
getMachineRules();
function getMachineRules() {
if (!$scope.macInputModel) {
return;
}
var mac = $scope.macInputModel.split(':');
GatewayFlowService.queryRules($scope.app, mac[0], mac[1]).success(
function (data) {
console.log(data);
if (data.code == 0 && data.data) {
// rule
$scope.rules = data.data.flow;
$scope.rulesPageConfig.totalCount = $scope.rules.length;
//api
if (data.data.api) {
$scope.apiNames = [];
data.data.api.forEach(function (api) {
$scope.apiNames.push(api["apiName"]);
});
}
} else {
$scope.rules = [];
$scope.rulesPageConfig.totalCount = 0;
}
});
// function (data) {
// if (data.code == 0 && data.data) {
// $scope.rules = data.data;
// $scope.rulesPageConfig.totalCount = $scope.rules.length;
// } else {
// $scope.rules = [];
// $scope.rulesPageConfig.totalCount = 0;
// }
// });
};
$scope.getMachineRules = getMachineRules;
// getApiNames();
// function getApiNames() {
// if (!$scope.macInputModel) {
// return;
// }
//
// var mac = $scope.macInputModel.split(':');
// GatewayApiServiceV2.queryApis($scope.app, mac[0], mac[1]).success(
// function (data) {
// if (data.code == 0 && data.data) {
// $scope.apiNames = [];
//
// data.data.forEach(function (api) {
// $scope.apiNames.push(api["apiName"]);
// });
// }
// });
// }
$scope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}];
var gatewayFlowRuleDialog;
$scope.editRule = function (rule) {
$scope.currentRule = angular.copy(rule);
$scope.gatewayFlowRuleDialog = {
title: '编辑网关流控规则',
type: 'edit',
confirmBtnText: '保存'
};
gatewayFlowRuleDialog = ngDialog.open({
template: '/app/views/dialog/gateway/flow-rule-dialog.html',
width: 780,
overlay: true,
scope: $scope
});
};
$scope.addNewRule = function () {
var mac = $scope.macInputModel.split(':');
$scope.currentRule = {
grade: 1,
app: $scope.app,
ip: mac[0],
port: mac[1],
resourceMode: 0,
interval: 1,
intervalUnit: 0,
controlBehavior: 0,
burst: 0,
maxQueueingTimeoutMs: 0
};
$scope.gatewayFlowRuleDialog = {
title: '新增网关流控规则',
type: 'add',
confirmBtnText: '新增'
};
gatewayFlowRuleDialog = ngDialog.open({
template: '/app/views/dialog/gateway/flow-rule-dialog.html',
width: 780,
overlay: true,
scope: $scope
});
};
$scope.saveRule = function () {
if (!GatewayFlowService.checkRuleValid($scope.currentRule)) {
return;
}
if ($scope.gatewayFlowRuleDialog.type === 'add') {
addNewRule($scope.currentRule);
} else if ($scope.gatewayFlowRuleDialog.type === 'edit') {
saveRule($scope.currentRule, true);
}
};
$scope.useRouteID = function () {
$scope.currentRule.resource = '';
};
$scope.useCustormAPI = function () {
$scope.currentRule.resource = '';
};
$scope.useParamItem = function () {
$scope.currentRule.paramItem = {
parseStrategy: 0,
matchStrategy: 0
};
};
$scope.notUseParamItem = function () {
$scope.currentRule.paramItem = null;
};
$scope.useParamItemVal = function () {
$scope.currentRule.paramItem.pattern = "";
$scope.currentRule.paramItem.matchStrategy = 0;
};
$scope.notUseParamItemVal = function () {
$scope.currentRule.paramItem.pattern = null;
$scope.currentRule.paramItem.matchStrategy = null;
};
function addNewRule(rule) {
GatewayFlowService.newRule(rule).success(function (data) {
if (data.code == 0) {
getMachineRules();
gatewayFlowRuleDialog.close();
} else {
alert('新增网关流控规则失败!' + data.msg);
}
});
};
function saveRule(rule, edit) {
GatewayFlowService.saveRule(rule).success(function (data) {
if (data.code == 0) {
getMachineRules();
if (edit) {
gatewayFlowRuleDialog.close();
} else {
confirmDialog.close();
}
} else {
alert('修改网关流控规则失败!' + data.msg);
}
});
};
var confirmDialog;
$scope.deleteRule = function (rule) {
$scope.currentRule = rule;
$scope.confirmDialog = {
title: '删除网关流控规则',
type: 'delete_rule',
attentionTitle: '请确认是否删除如下规则',
attention: 'API名称: ' + rule.resource + ', ' + (rule.grade == 1 ? 'QPS阈值' : '线程数') + ': ' + rule.count,
confirmBtnText: '删除',
};
confirmDialog = ngDialog.open({
template: '/app/views/dialog/confirm-dialog.html',
scope: $scope,
overlay: true
});
};
$scope.confirm = function () {
if ($scope.confirmDialog.type == 'delete_rule') {
deleteRule($scope.currentRule);
} else {
console.error('error');
}
};
function deleteRule(rule) {
GatewayFlowService.deleteRule(rule).success(function (data) {
if (data.code == 0) {
getMachineRules();
confirmDialog.close();
} else {
alert('删除网关流控规则失败!' + data.msg);
}
});
};
queryAppMachines();
function queryAppMachines() {
MachineService.getAppMachines($scope.app).success(
function (data) {
if (data.code == 0) {
if (data.data) {
$scope.machines = [];
$scope.macsInputOptions = [];
data.data.forEach(function (item) {
if (item.healthy) {
$scope.macsInputOptions.push({
text: item.ip + ':' + item.port,
value: item.ip + ':' + item.port
});
}
});
}
if ($scope.macsInputOptions.length > 0) {
$scope.macInputModel = $scope.macsInputOptions[0].value;
}
} else {
$scope.macsInputOptions = [];
}
}
);
};
$scope.$watch('macInputModel', function () {
if ($scope.macInputModel) {
getMachineRules();
// getApiNames();
}
});
}]
);
......@@ -44,13 +44,22 @@
<!--<a ui-sref="dashboard.flow({app: entry.app})">-->
<!--<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 V1</a>-->
<!--</li>-->
<!-- 网关 -->
<!-- <li ui-sref-active="active" ng-if="entry.isGateway">-->
<!-- <a ui-sref="dashboard.gatewayApi({app: entry.app})">-->
<!-- <i class="glyphicon glyphicon-tags"></i>&nbsp;&nbsp;&nbsp;API 管理</a>-->
<!-- </li>-->
<!-- <li ui-sref-active="active" ng-if="entry.isGateway">-->
<!-- <a ui-sref="dashboard.gatewayFlow({app: entry.app})">-->
<!-- <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>-->
<!-- </li>-->
<!-- 网关v2 -->
<li ui-sref-active="active" ng-if="entry.isGateway">
<a ui-sref="dashboard.gatewayApi({app: entry.app})">
<a ui-sref="dashboard.gatewayApiV2({app: entry.app})">
<i class="glyphicon glyphicon-tags"></i>&nbsp;&nbsp;&nbsp;API 管理</a>
</li>
<li ui-sref-active="active" ng-if="entry.isGateway">
<a ui-sref="dashboard.gatewayFlow({app: entry.app})">
<a ui-sref="dashboard.gatewayFlowV2({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则</a>
</li>
......
var app = angular.module('sentinelDashboardApp');
app.service('GatewayApiServiceV2', ['$http', function ($http) {
this.queryApis = function (app, ip, port) {
var param = {
app: app,
ip: ip,
port: port
};
return $http({
url: '/v2/gateway/api/list.json',
params: param,
method: 'GET'
});
};
this.newApi = function (api) {
return $http({
url: '/v2/gateway/api/new.json',
data: api,
method: 'POST'
});
};
this.saveApi = function (api) {
return $http({
url: '/v2/gateway/api/save.json',
data: api,
method: 'POST'
});
};
this.deleteApi = function (api) {
var param = {
id: api.id,
app: api.app
};
return $http({
url: '/v2/gateway/api/delete.json',
params: param,
method: 'POST'
});
};
this.checkApiValid = function (api, apiNames) {
if (api.apiName === undefined || api.apiName === '') {
alert('API名称不能为空');
return false;
}
if (api.predicateItems == null || api.predicateItems.length === 0) {
// Should never happen since no remove button will display when only one predicateItem.
alert('至少有一个匹配规则');
return false;
}
for (var i = 0; i < api.predicateItems.length; i++) {
var predicateItem = api.predicateItems[i];
var pattern = predicateItem.pattern;
if (pattern === undefined || pattern === '') {
alert('匹配串不能为空,请检查');
return false;
}
}
if (apiNames.indexOf(api.apiName) !== -1) {
alert('API名称(' + api.apiName + ')已存在');
return false;
}
return true;
};
}]);
var app = angular.module('sentinelDashboardApp');
app.service('GatewayFlowServiceV2', ['$http', function ($http) {
this.queryRules = function (app, ip, port) {
var param = {
app: app,
ip: ip,
port: port
};
return $http({
url: '/v2/gateway/flow/list.json',
params: param,
method: 'GET'
});
};
this.newRule = function (rule) {
return $http({
url: '/v2/gateway/flow/new.json',
data: rule,
method: 'POST'
});
};
this.saveRule = function (rule) {
return $http({
url: '/v2/gateway/flow/save.json',
data: rule,
method: 'POST'
});
};
this.deleteRule = function (rule) {
var param = {
id: rule.id,
app: rule.app
};
return $http({
url: '/v2/gateway/flow/delete.json',
params: param,
method: 'POST'
});
};
this.checkRuleValid = function (rule) {
if (rule.resource === undefined || rule.resource === '') {
alert('API名称不能为空');
return false;
}
if (rule.paramItem != null) {
if (rule.paramItem.parseStrategy == 2 ||
rule.paramItem.parseStrategy == 3 ||
rule.paramItem.parseStrategy == 4) {
if (rule.paramItem.fieldName === undefined || rule.paramItem.fieldName === '') {
alert('当参数属性为Header、URL参数、Cookie时,参数名称不能为空');
return false;
}
if (rule.paramItem.pattern === '') {
alert('匹配串不能为空');
return false;
}
}
}
if (rule.count === undefined || rule.count < 0) {
alert((rule.grade === 1 ? 'QPS阈值' : '线程数') + '必须大于等于 0');
return false;
}
return true;
};
}]);
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
<div class="col-md-6">
<button class="btn btn-outline-primary" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewApi()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增 API 分组</button>
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.gatewayApi({app: app})">
回到单机页面
</a>
</div>
</div>
<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">API 分组管理</span>
<button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getApis()">刷新</button>
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
<div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
<selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"
placeholder="机器"></selectize>
</div>
</div>
<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;">
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td>
API 名称
</td>
<td>
匹配模式
</td>
<td>
匹配串
</td>
<td>
操作
</td>
</tr>
</thead>
<tbody>
<tr dir-paginate="api in apis | filter: searchKey | itemsPerPage: apisPageConfig.pageSize" current-page="apisPageConfig.currentPageIndex"
pagination-id="entriesPagination">
<td ng-if="api.firstFlag==0" rowspan="{{api.itemSize}}" style="vertical-align: middle;">{{api.apiName}}</td>
<td>
<span ng-if="api.matchStrategy == 0">精确</span>
<span ng-if="api.matchStrategy == 1">前缀</span>
<span ng-if="api.matchStrategy == 2">正则</span>
</td>
<td>{{api.pattern}}</td>
<td ng-if="api.firstFlag==0" rowspan="{{api.itemSize}}" style="vertical-align: middle;">
<button class="btn btn-xs btn-outline-primary" type="button" ng-click="editApi(api)" style="font-size: 12px; height:25px;">编辑</button>
<button class="btn btn-xs btn-outline-danger" type="button" ng-click="deleteApi(api)" style="font-size: 12px; height:25px;">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
<div class="pagination-footer">
<dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
on-page-change="">
</dir-pagination-controls>
<div class="tools" style="">
<span>共 {{apisPageConfig.totalCount}} 条记录, </span>
<span>
每页
<input class="form-control" ng-model="apisPageConfig.pageSize"> 条记录
</span>
<!--<span>第 {{apisPageConfig.currentPageIndex}} / {{apisPageConfig.totalPage}} 页</span>-->
</div>
<!-- .tools -->
</div>
<!-- pagination-footer -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
<div class="col-md-6">
<button class="btn btn-outline-primary" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel"
ng-click="addNewRule()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增网关流控规则
</button>
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;"
ui-sref="dashboard.gatewayFlow({app: app})">
回到单机页面
</a>
</div>
</div>
<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">网关流控规则</span>
<button class="btn btn-primary"
style="float: right; margin-right: 10px; height: 30px;font-size: 12px;"
ng-click="getMachineRules()">刷新
</button>
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
<div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
<selectize id="gsInput" class="selectize-input-200" config="macsInputConfig"
options="macsInputOptions" ng-model="macInputModel"
placeholder="机器"></selectize>
</div>
</div>
<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;">
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td>
API 名称
</td>
<td>
API 类型
</td>
<td>
阈值类型
</td>
<td>
单机阈值
</td>
<td>
操作
</td>
</tr>
</thead>
<tbody>
<tr dir-paginate="rule in rules | filter: searchKey | itemsPerPage: rulesPageConfig.pageSize "
current-page="rulesPageConfig.currentPageIndex"
pagination-id="entriesPagination">
<td>{{rule.resource}}</td>
<td>
<span ng-if="rule.resourceMode == 0">Route ID</span>
<span ng-if="rule.resourceMode == 1">API 分组</span>
</td>
<td>
<span ng-if="rule.grade == 1">QPS</span>
<span ng-if="rule.grade == 0">线程数</span>
</td>
<td>{{rule.count}}</td>
<td>
<button class="btn btn-xs btn-outline-primary" type="button" ng-click="editRule(rule)"
style="font-size: 12px; height:25px;">编辑
</button>
<button class="btn btn-xs btn-outline-danger" type="button" ng-click="deleteRule(rule)"
style="font-size: 12px; height:25px;">删除
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
<div class="pagination-footer">
<dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html"
pagination-id="entriesPagination"
on-page-change="">
</dir-pagination-controls>
<div class="tools" style="">
<span>共 {{rulesPageConfig.totalCount}} 条记录, </span>
<span>
每页
<input class="form-control" ng-model="rulesPageConfig.pageSize"> 条记录
</span>
<!--<span>第 {{apisPageConfig.currentPageIndex}} / {{apisPageConfig.totalPage}} 页</span>-->
</div>
<!-- .tools -->
</div>
<!-- pagination-footer -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->
......@@ -22,10 +22,10 @@
<input class="form-control witdh-200" placeholder="关键字" ng-model="serviceQuery" ng-change="searchService()" style="margin-right: 20px;">
</div>
<div class="input-append date" id="datetimepicker" data-date="12-02-2012" data-date-format="dd-mm-yyyy">
<input size="16" type="text" value="12-02-2012" readonly>
<span class="add-on"><i class="icon-th"></i></span>
</div>
<!-- <div class="input-append date" id="datetimepicker" data-date="12-02-2012" data-date-format="dd-mm-yyyy">-->
<!-- <input size="16" type="text" value="12-02-2012" readonly>-->
<!-- <span class="add-on"><i class="icon-th"></i></span>-->
<!-- </div>-->
<!--.tools-header -->
<!--<div id="g2container"></div>-->
<div class="card-body">
......
......@@ -59,7 +59,9 @@ const JS_APP = [
'app/scripts/services/authority_service.js',
'app/scripts/services/cluster_state_service.js',
'app/scripts/services/gateway/api_service.js',
'app/scripts/services/gateway/api_service_v2.js',
'app/scripts/services/gateway/flow_service.js',
'app/scripts/services/gateway/flow_service_v2.js',
];
gulp.task('lib', function () {
......
......@@ -19,7 +19,9 @@
</div>
<script src="./lib/js/jquery.min.js"></script>
<script src="./lib/js/bootstrap.min.js"></script>
<script src="./lib/js/angular.min.js"></script>
<!-- <script src="./lib/js/angular.min.js"></script>-->
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<script src="./lib/js/g2.min.js"></script>
<script src="./dist/js/app.vendor.js"></script>
......
......@@ -17,10 +17,10 @@
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-core</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
......@@ -41,6 +41,70 @@
</dependency>
</dependencies>
<!-- <dependencies>-->
<!-- &lt;!&ndash;nacos config&ndash;&gt;-->
<!-- &lt;!&ndash;<dependency>&ndash;&gt;-->
<!-- &lt;!&ndash;<groupId>com.alibaba.cloud</groupId>&ndash;&gt;-->
<!-- &lt;!&ndash;<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>&ndash;&gt;-->
<!-- &lt;!&ndash;</dependency>&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;用nacos 和 sentinel 就用下面三个&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;做持久化用&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-datasource-nacos</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;<dependency>&ndash;&gt;-->
<!-- &lt;!&ndash;<groupId>org.springframework.cloud</groupId>&ndash;&gt;-->
<!-- &lt;!&ndash;<artifactId>spring-cloud-starter-openfeign</artifactId>&ndash;&gt;-->
<!-- &lt;!&ndash;</dependency>&ndash;&gt;-->
<!-- &lt;!&ndash;引入自己api&ndash;&gt;-->
<!--&lt;!&ndash; <dependency>&ndash;&gt;-->
<!--&lt;!&ndash; <groupId>cn.dongfang.clouddemo</groupId>&ndash;&gt;-->
<!--&lt;!&ndash; <artifactId>apicommons</artifactId>&ndash;&gt;-->
<!--&lt;!&ndash; <version>${project.version}</version>&ndash;&gt;-->
<!--&lt;!&ndash; </dependency>&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;热部署&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-devtools</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<!-- </dependencies>-->
......
......@@ -20,7 +20,6 @@
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-core:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-transport-simple-http:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-transport-common:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.62" level="project" />
......@@ -47,6 +46,7 @@
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.1.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-datasource-nacos:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-datasource-extension:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.csp:sentinel-core:1.7.1" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.nacos:nacos-client:1.1.4" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.nacos:nacos-common:1.1.4" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.7" level="project" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment