dashboard: add support for managing gateway flow rules (#699)
This commit is contained in:
parent
eda7fdc5c8
commit
856ff312e9
|
|
@ -38,6 +38,11 @@
|
|||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -30,9 +30,12 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.command.CommandConstants;
|
||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||
import com.alibaba.csp.sentinel.command.vo.NodeVo;
|
||||
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.util.AsyncUtils;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
|
||||
|
|
@ -109,6 +112,12 @@ public class SentinelApiClient {
|
|||
private static final String MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH = "cluster/server/modifyFlowConfig";
|
||||
private static final String MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH = "cluster/server/modifyNamespaceSet";
|
||||
|
||||
private static final String FETCH_GATEWAY_API_PATH = "gateway/getApiDefinitions";
|
||||
private static final String MODIFY_GATEWAY_API_PATH = "gateway/updateApiDefinitions";
|
||||
|
||||
private static final String FETCH_GATEWAY_FLOW_RULE_PATH = "gateway/getRules";
|
||||
private static final String MODIFY_GATEWAY_FLOW_RULE_PATH = "gateway/updateRules";
|
||||
|
||||
private static final String FLOW_RULE_TYPE = "flow";
|
||||
private static final String DEGRADE_RULE_TYPE = "degrade";
|
||||
private static final String SYSTEM_RULE_TYPE = "system";
|
||||
|
|
@ -693,4 +702,90 @@ public class SentinelApiClient {
|
|||
return AsyncUtils.newFailedFuture(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<List<ApiDefinitionEntity>> fetchApis(String app, String ip, int port) {
|
||||
if (StringUtil.isBlank(ip) || port <= 0) {
|
||||
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
|
||||
}
|
||||
|
||||
try {
|
||||
return executeCommand(ip, port, FETCH_GATEWAY_API_PATH, false)
|
||||
.thenApply(r -> {
|
||||
List<ApiDefinitionEntity> entities = JSON.parseArray(r, ApiDefinitionEntity.class);
|
||||
if (entities != null) {
|
||||
for (ApiDefinitionEntity entity : entities) {
|
||||
entity.setApp(app);
|
||||
entity.setIp(ip);
|
||||
entity.setPort(port);
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error when fetching gateway apis", ex);
|
||||
return AsyncUtils.newFailedFuture(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean modifyApis(String app, String ip, int port, List<ApiDefinitionEntity> apis) {
|
||||
if (apis == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
AssertUtil.notEmpty(app, "Bad app name");
|
||||
AssertUtil.notEmpty(ip, "Bad machine IP");
|
||||
AssertUtil.isTrue(port > 0, "Bad machine port");
|
||||
String data = JSON.toJSONString(
|
||||
apis.stream().map(r -> r.toApiDefinition()).collect(Collectors.toList()));
|
||||
Map<String, String> params = new HashMap<>(2);
|
||||
params.put("data", data);
|
||||
String result = executeCommand(app, ip, port, MODIFY_GATEWAY_API_PATH, params, true).get();
|
||||
logger.info("Modify gateway apis: {}", result);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error when modifying gateway apis", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<List<GatewayFlowRuleEntity>> fetchGatewayFlowRules(String app, String ip, int port) {
|
||||
if (StringUtil.isBlank(ip) || port <= 0) {
|
||||
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
|
||||
}
|
||||
|
||||
try {
|
||||
return executeCommand(ip, port, FETCH_GATEWAY_FLOW_RULE_PATH, false)
|
||||
.thenApply(r -> {
|
||||
List<GatewayFlowRule> gatewayFlowRules = JSON.parseArray(r, GatewayFlowRule.class);
|
||||
List<GatewayFlowRuleEntity> entities = gatewayFlowRules.stream().map(rule -> GatewayFlowRuleEntity.fromGatewayFlowRule(app, ip, port, rule)).collect(Collectors.toList());
|
||||
return entities;
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Error when fetching gateway flow rules", ex);
|
||||
return AsyncUtils.newFailedFuture(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean modifyGatewayFlowRules(String app, String ip, int port, List<GatewayFlowRuleEntity> rules) {
|
||||
if (rules == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
AssertUtil.notEmpty(app, "Bad app name");
|
||||
AssertUtil.notEmpty(ip, "Bad machine IP");
|
||||
AssertUtil.isTrue(port > 0, "Bad machine port");
|
||||
String data = JSON.toJSONString(
|
||||
rules.stream().map(r -> r.toGatewayFlowRule()).collect(Collectors.toList()));
|
||||
Map<String, String> params = new HashMap<>(2);
|
||||
params.put("data", data);
|
||||
String result = executeCommand(app, ip, port, MODIFY_GATEWAY_FLOW_RULE_PATH, params, true).get();
|
||||
logger.info("Modify gateway flow rules: {}", result);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error when modifying gateway apis", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,17 +63,7 @@ public class AppController {
|
|||
return Result.ofSuccess(null);
|
||||
}
|
||||
List<MachineInfo> list = new ArrayList<>(appInfo.getMachines());
|
||||
Collections.sort(list, (o1, o2) -> {
|
||||
int t = o1.getApp().compareTo(o2.getApp());
|
||||
if (t != 0) {
|
||||
return t;
|
||||
}
|
||||
t = o1.getIp().compareTo(o2.getIp());
|
||||
if (t != 0) {
|
||||
return t;
|
||||
}
|
||||
return o1.getPort().compareTo(o2.getPort());
|
||||
});
|
||||
Collections.sort(list, Comparator.comparing(MachineInfo::getApp).thenComparing(MachineInfo::getIp).thenComparingInt(MachineInfo::getPort));
|
||||
return Result.ofSuccess(MachineInfoVo.fromMachineInfoList(list));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
|
|
@ -40,7 +41,7 @@ public class MachineRegistryController {
|
|||
|
||||
@ResponseBody
|
||||
@RequestMapping("/machine")
|
||||
public Result<?> receiveHeartBeat(String app, Long version, String v, String hostname, String ip, Integer port) {
|
||||
public Result<?> receiveHeartBeat(String app, @RequestParam(value = "app_type", required = false, defaultValue = "0") Integer appType, Long version, String v, String hostname, String ip, Integer port) {
|
||||
if (app == null) {
|
||||
app = MachineDiscovery.UNKNOWN_APP_NAME;
|
||||
}
|
||||
|
|
@ -59,6 +60,7 @@ public class MachineRegistryController {
|
|||
try {
|
||||
MachineInfo machineInfo = new MachineInfo();
|
||||
machineInfo.setApp(app);
|
||||
machineInfo.setAppType(appType);
|
||||
machineInfo.setHostname(hostname);
|
||||
machineInfo.setIp(ip);
|
||||
machineInfo.setPort(port);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* 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.controller.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
|
||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
||||
|
||||
/**
|
||||
* Gateway api Controller for manage gateway api definitions.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/gateway/api")
|
||||
public class GatewayApiController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GatewayApiController.class);
|
||||
|
||||
@Autowired
|
||||
private InMemApiDefinitionStore repository;
|
||||
|
||||
@Autowired
|
||||
private SentinelApiClient sentinelApiClient;
|
||||
|
||||
@Autowired
|
||||
private AuthService<HttpServletRequest> authService;
|
||||
|
||||
@GetMapping("/list.json")
|
||||
public Result<List<ApiDefinitionEntity>> queryApis(HttpServletRequest request, String app, String ip, Integer port) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.READ_RULE);
|
||||
|
||||
if (StringUtil.isEmpty(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
if (StringUtil.isEmpty(ip)) {
|
||||
return Result.ofFail(-1, "ip can't be null or empty");
|
||||
}
|
||||
if (port == null) {
|
||||
return Result.ofFail(-1, "port can't be null");
|
||||
}
|
||||
|
||||
try {
|
||||
List<ApiDefinitionEntity> apis = sentinelApiClient.fetchApis(app, ip, port).get();
|
||||
repository.saveAll(apis);
|
||||
return Result.ofSuccess(apis);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("queryApis error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/new.json")
|
||||
public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
String app = reqVo.getApp();
|
||||
if (StringUtil.isBlank(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.WRITE_RULE);
|
||||
|
||||
ApiDefinitionEntity entity = new ApiDefinitionEntity();
|
||||
entity.setApp(app.trim());
|
||||
|
||||
String ip = reqVo.getIp();
|
||||
if (StringUtil.isBlank(ip)) {
|
||||
return Result.ofFail(-1, "ip can't be null or empty");
|
||||
}
|
||||
entity.setIp(ip.trim());
|
||||
|
||||
Integer port = reqVo.getPort();
|
||||
if (port == null) {
|
||||
return Result.ofFail(-1, "port can't be null");
|
||||
}
|
||||
entity.setPort(port);
|
||||
|
||||
// API名称
|
||||
String apiName = reqVo.getApiName();
|
||||
if (StringUtil.isBlank(apiName)) {
|
||||
return Result.ofFail(-1, "apiName can't be null or empty");
|
||||
}
|
||||
entity.setApiName(apiName.trim());
|
||||
|
||||
// 匹配规则列表
|
||||
List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
|
||||
if (CollectionUtils.isEmpty(predicateItems)) {
|
||||
return Result.ofFail(-1, "predicateItems can't empty");
|
||||
}
|
||||
|
||||
List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
|
||||
for (ApiPredicateItemVo predicateItem : predicateItems) {
|
||||
ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
|
||||
|
||||
// 匹配模式
|
||||
Integer matchStrategy = predicateItem.getMatchStrategy();
|
||||
if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
|
||||
return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
|
||||
}
|
||||
predicateItemEntity.setMatchStrategy(matchStrategy);
|
||||
|
||||
// 匹配串
|
||||
String pattern = predicateItem.getPattern();
|
||||
if (StringUtil.isBlank(pattern)) {
|
||||
return Result.ofFail(-1, "pattern can't be null or empty");
|
||||
}
|
||||
predicateItemEntity.setPattern(pattern);
|
||||
|
||||
predicateItemEntities.add(predicateItemEntity);
|
||||
}
|
||||
entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
|
||||
|
||||
// 检查API名称不能重复
|
||||
List<ApiDefinitionEntity> allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port));
|
||||
if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) {
|
||||
return Result.ofFail(-1, "apiName exists: " + apiName);
|
||||
}
|
||||
|
||||
Date date = new Date();
|
||||
entity.setGmtCreate(date);
|
||||
entity.setGmtModified(date);
|
||||
|
||||
try {
|
||||
entity = repository.save(entity);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("add gateway api error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishApis(app, ip, port)) {
|
||||
logger.warn("publish gateway apis fail after add");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(entity);
|
||||
}
|
||||
|
||||
@PostMapping("/save.json")
|
||||
public Result<ApiDefinitionEntity> updateApi(HttpServletRequest request, @RequestBody UpdateApiReqVo reqVo) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
String app = reqVo.getApp();
|
||||
if (StringUtil.isBlank(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.WRITE_RULE);
|
||||
|
||||
Long id = reqVo.getId();
|
||||
if (id == null) {
|
||||
return Result.ofFail(-1, "id can't be null");
|
||||
}
|
||||
|
||||
ApiDefinitionEntity entity = repository.findById(id);
|
||||
if (entity == null) {
|
||||
return Result.ofFail(-1, "api does not exist, id=" + id);
|
||||
}
|
||||
|
||||
// 匹配规则列表
|
||||
List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
|
||||
if (CollectionUtils.isEmpty(predicateItems)) {
|
||||
return Result.ofFail(-1, "predicateItems can't empty");
|
||||
}
|
||||
|
||||
List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
|
||||
for (ApiPredicateItemVo predicateItem : predicateItems) {
|
||||
ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();
|
||||
|
||||
// 匹配模式
|
||||
int matchStrategy = predicateItem.getMatchStrategy();
|
||||
if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
|
||||
return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy);
|
||||
}
|
||||
predicateItemEntity.setMatchStrategy(matchStrategy);
|
||||
|
||||
// 匹配串
|
||||
String pattern = predicateItem.getPattern();
|
||||
if (StringUtil.isBlank(pattern)) {
|
||||
return Result.ofFail(-1, "pattern can't be null or empty");
|
||||
}
|
||||
predicateItemEntity.setPattern(pattern);
|
||||
|
||||
predicateItemEntities.add(predicateItemEntity);
|
||||
}
|
||||
entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));
|
||||
|
||||
Date date = new Date();
|
||||
entity.setGmtModified(date);
|
||||
|
||||
try {
|
||||
entity = repository.save(entity);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("update gateway api error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishApis(app, entity.getIp(), entity.getPort())) {
|
||||
logger.warn("publish gateway apis fail after update");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(entity);
|
||||
}
|
||||
|
||||
@PostMapping("/delete.json")
|
||||
public Result<Long> deleteApi(HttpServletRequest request, Long id) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
if (id == null) {
|
||||
return Result.ofFail(-1, "id can't be null");
|
||||
}
|
||||
|
||||
ApiDefinitionEntity oldEntity = repository.findById(id);
|
||||
if (oldEntity == null) {
|
||||
return Result.ofSuccess(null);
|
||||
}
|
||||
|
||||
authUser.authTarget(oldEntity.getApp(), AuthService.PrivilegeType.DELETE_RULE);
|
||||
|
||||
try {
|
||||
repository.delete(id);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("delete gateway api error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
|
||||
logger.warn("publish gateway apis fail after delete");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(id);
|
||||
}
|
||||
|
||||
private boolean publishApis(String app, String ip, Integer port) {
|
||||
List<ApiDefinitionEntity> apis = repository.findAllByMachine(MachineInfo.of(app, ip, port));
|
||||
return sentinelApiClient.modifyApis(app, ip, port, apis);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* 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.controller.gateway;
|
||||
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
|
||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*;
|
||||
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
||||
import static com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity.*;
|
||||
|
||||
/**
|
||||
* Gateway flow rule Controller for manage gateway flow rules.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = "/gateway/flow")
|
||||
public class GatewayFlowRuleController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GatewayFlowRuleController.class);
|
||||
|
||||
@Autowired
|
||||
private InMemGatewayFlowRuleStore repository;
|
||||
|
||||
@Autowired
|
||||
private SentinelApiClient sentinelApiClient;
|
||||
|
||||
@Autowired
|
||||
private AuthService<HttpServletRequest> authService;
|
||||
|
||||
@GetMapping("/list.json")
|
||||
public Result<List<GatewayFlowRuleEntity>> queryFlowRules(HttpServletRequest request, String app, String ip, Integer port) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.READ_RULE);
|
||||
|
||||
if (StringUtil.isEmpty(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
if (StringUtil.isEmpty(ip)) {
|
||||
return Result.ofFail(-1, "ip can't be null or empty");
|
||||
}
|
||||
if (port == null) {
|
||||
return Result.ofFail(-1, "port can't be null");
|
||||
}
|
||||
|
||||
try {
|
||||
List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();
|
||||
repository.saveAll(rules);
|
||||
return Result.ofSuccess(rules);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("query gateway flow rules error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/new.json")
|
||||
public Result<GatewayFlowRuleEntity> addFlowRule(HttpServletRequest request, @RequestBody AddFlowRuleReqVo reqVo) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
String app = reqVo.getApp();
|
||||
if (StringUtil.isBlank(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.WRITE_RULE);
|
||||
|
||||
GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
|
||||
entity.setApp(app.trim());
|
||||
|
||||
String ip = reqVo.getIp();
|
||||
if (StringUtil.isBlank(ip)) {
|
||||
return Result.ofFail(-1, "ip can't be null or empty");
|
||||
}
|
||||
entity.setIp(ip.trim());
|
||||
|
||||
Integer port = reqVo.getPort();
|
||||
if (port == null) {
|
||||
return Result.ofFail(-1, "port can't be null");
|
||||
}
|
||||
entity.setPort(port);
|
||||
|
||||
// API类型, Route ID或API分组
|
||||
Integer resourceMode = reqVo.getResourceMode();
|
||||
if (resourceMode == null) {
|
||||
return Result.ofFail(-1, "resourceMode can't be null");
|
||||
}
|
||||
if (!Arrays.asList(RESOURCE_MODE_ROUTE_ID, RESOURCE_MODE_CUSTOM_API_NAME).contains(resourceMode)) {
|
||||
return Result.ofFail(-1, "invalid resourceMode: " + resourceMode);
|
||||
}
|
||||
entity.setResourceMode(resourceMode);
|
||||
|
||||
// API名称
|
||||
String resource = reqVo.getResource();
|
||||
if (StringUtil.isBlank(resource)) {
|
||||
return Result.ofFail(-1, "resource can't be null or empty");
|
||||
}
|
||||
entity.setResource(resource.trim());
|
||||
|
||||
// 针对请求属性
|
||||
GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
|
||||
if (paramItem != null) {
|
||||
GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
|
||||
entity.setParamItem(itemEntity);
|
||||
|
||||
// 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
|
||||
Integer parseStrategy = paramItem.getParseStrategy();
|
||||
if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
|
||||
, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
|
||||
return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
|
||||
}
|
||||
itemEntity.setParseStrategy(paramItem.getParseStrategy());
|
||||
|
||||
// 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
|
||||
if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
|
||||
// 参数名称
|
||||
String fieldName = paramItem.getFieldName();
|
||||
if (StringUtil.isBlank(fieldName)) {
|
||||
return Result.ofFail(-1, "fieldName can't be null or empty");
|
||||
}
|
||||
itemEntity.setFieldName(paramItem.getFieldName());
|
||||
|
||||
String pattern = paramItem.getPattern();
|
||||
// 如果匹配串不为空,验证匹配模式
|
||||
if (StringUtil.isNotEmpty(pattern)) {
|
||||
itemEntity.setPattern(pattern);
|
||||
|
||||
Integer matchStrategy = paramItem.getMatchStrategy();
|
||||
if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
|
||||
return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
|
||||
}
|
||||
itemEntity.setMatchStrategy(matchStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 阈值类型 0-线程数 1-QPS
|
||||
Integer grade = reqVo.getGrade();
|
||||
if (grade == null) {
|
||||
return Result.ofFail(-1, "grade can't be null");
|
||||
}
|
||||
if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
|
||||
return Result.ofFail(-1, "invalid grade: " + grade);
|
||||
}
|
||||
entity.setGrade(grade);
|
||||
|
||||
// QPS阈值
|
||||
Double count = reqVo.getCount();
|
||||
if (count == null) {
|
||||
return Result.ofFail(-1, "count can't be null");
|
||||
}
|
||||
if (count < 0) {
|
||||
return Result.ofFail(-1, "count should be at lease zero");
|
||||
}
|
||||
entity.setCount(count);
|
||||
|
||||
// 间隔
|
||||
Long interval = reqVo.getInterval();
|
||||
if (interval == null) {
|
||||
return Result.ofFail(-1, "interval can't be null");
|
||||
}
|
||||
if (interval <= 0) {
|
||||
return Result.ofFail(-1, "interval should be greater than zero");
|
||||
}
|
||||
entity.setInterval(interval);
|
||||
|
||||
// 间隔单位
|
||||
Integer intervalUnit = reqVo.getIntervalUnit();
|
||||
if (intervalUnit == null) {
|
||||
return Result.ofFail(-1, "intervalUnit can't be null");
|
||||
}
|
||||
if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
|
||||
return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
|
||||
}
|
||||
entity.setIntervalUnit(intervalUnit);
|
||||
|
||||
// 流控方式 0-快速失败 2-匀速排队
|
||||
Integer controlBehavior = reqVo.getControlBehavior();
|
||||
if (controlBehavior == null) {
|
||||
return Result.ofFail(-1, "controlBehavior can't be null");
|
||||
}
|
||||
if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
|
||||
return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
|
||||
}
|
||||
entity.setControlBehavior(controlBehavior);
|
||||
|
||||
if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
|
||||
// 0-快速失败, 则Burst size必填
|
||||
Integer burst = reqVo.getBurst();
|
||||
if (burst == null) {
|
||||
return Result.ofFail(-1, "burst can't be null");
|
||||
}
|
||||
if (burst < 0) {
|
||||
return Result.ofFail(-1, "invalid burst: " + burst);
|
||||
}
|
||||
entity.setBurst(burst);
|
||||
} else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
|
||||
// 1-匀速排队, 则超时时间必填
|
||||
Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
|
||||
if (maxQueueingTimeoutMs == null) {
|
||||
return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
|
||||
}
|
||||
if (maxQueueingTimeoutMs < 0) {
|
||||
return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
|
||||
}
|
||||
entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
|
||||
}
|
||||
|
||||
Date date = new Date();
|
||||
entity.setGmtCreate(date);
|
||||
entity.setGmtModified(date);
|
||||
|
||||
try {
|
||||
entity = repository.save(entity);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("add gateway flow rule error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishRules(app, ip, port)) {
|
||||
logger.warn("publish gateway flow rules fail after add");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(entity);
|
||||
}
|
||||
|
||||
@PostMapping("/save.json")
|
||||
public Result<GatewayFlowRuleEntity> updateFlowRule(HttpServletRequest request, @RequestBody UpdateFlowRuleReqVo reqVo) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
String app = reqVo.getApp();
|
||||
if (StringUtil.isBlank(app)) {
|
||||
return Result.ofFail(-1, "app can't be null or empty");
|
||||
}
|
||||
|
||||
authUser.authTarget(app, AuthService.PrivilegeType.WRITE_RULE);
|
||||
|
||||
Long id = reqVo.getId();
|
||||
if (id == null) {
|
||||
return Result.ofFail(-1, "id can't be null");
|
||||
}
|
||||
|
||||
GatewayFlowRuleEntity entity = repository.findById(id);
|
||||
if (entity == null) {
|
||||
return Result.ofFail(-1, "gateway flow rule does not exist, id=" + id);
|
||||
}
|
||||
|
||||
// 针对请求属性
|
||||
GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
|
||||
if (paramItem != null) {
|
||||
GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
|
||||
entity.setParamItem(itemEntity);
|
||||
|
||||
// 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
|
||||
Integer parseStrategy = paramItem.getParseStrategy();
|
||||
if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
|
||||
, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
|
||||
return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
|
||||
}
|
||||
itemEntity.setParseStrategy(paramItem.getParseStrategy());
|
||||
|
||||
// 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
|
||||
if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
|
||||
// 参数名称
|
||||
String fieldName = paramItem.getFieldName();
|
||||
if (StringUtil.isBlank(fieldName)) {
|
||||
return Result.ofFail(-1, "fieldName can't be null or empty");
|
||||
}
|
||||
itemEntity.setFieldName(paramItem.getFieldName());
|
||||
|
||||
String pattern = paramItem.getPattern();
|
||||
// 如果匹配串不为空,验证匹配模式
|
||||
if (StringUtil.isNotEmpty(pattern)) {
|
||||
itemEntity.setPattern(pattern);
|
||||
|
||||
Integer matchStrategy = paramItem.getMatchStrategy();
|
||||
if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
|
||||
return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
|
||||
}
|
||||
itemEntity.setMatchStrategy(matchStrategy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entity.setParamItem(null);
|
||||
}
|
||||
|
||||
// 阈值类型 0-线程数 1-QPS
|
||||
Integer grade = reqVo.getGrade();
|
||||
if (grade == null) {
|
||||
return Result.ofFail(-1, "grade can't be null");
|
||||
}
|
||||
if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
|
||||
return Result.ofFail(-1, "invalid grade: " + grade);
|
||||
}
|
||||
entity.setGrade(grade);
|
||||
|
||||
// QPS阈值
|
||||
Double count = reqVo.getCount();
|
||||
if (count == null) {
|
||||
return Result.ofFail(-1, "count can't be null");
|
||||
}
|
||||
if (count < 0) {
|
||||
return Result.ofFail(-1, "count should be at lease zero");
|
||||
}
|
||||
entity.setCount(count);
|
||||
|
||||
// 间隔
|
||||
Long interval = reqVo.getInterval();
|
||||
if (interval == null) {
|
||||
return Result.ofFail(-1, "interval can't be null");
|
||||
}
|
||||
if (interval <= 0) {
|
||||
return Result.ofFail(-1, "interval should be greater than zero");
|
||||
}
|
||||
entity.setInterval(interval);
|
||||
|
||||
// 间隔单位
|
||||
Integer intervalUnit = reqVo.getIntervalUnit();
|
||||
if (intervalUnit == null) {
|
||||
return Result.ofFail(-1, "intervalUnit can't be null");
|
||||
}
|
||||
if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
|
||||
return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
|
||||
}
|
||||
entity.setIntervalUnit(intervalUnit);
|
||||
|
||||
// 流控方式 0-快速失败 2-匀速排队
|
||||
Integer controlBehavior = reqVo.getControlBehavior();
|
||||
if (controlBehavior == null) {
|
||||
return Result.ofFail(-1, "controlBehavior can't be null");
|
||||
}
|
||||
if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
|
||||
return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
|
||||
}
|
||||
entity.setControlBehavior(controlBehavior);
|
||||
|
||||
if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
|
||||
// 0-快速失败, 则Burst size必填
|
||||
Integer burst = reqVo.getBurst();
|
||||
if (burst == null) {
|
||||
return Result.ofFail(-1, "burst can't be null");
|
||||
}
|
||||
if (burst < 0) {
|
||||
return Result.ofFail(-1, "invalid burst: " + burst);
|
||||
}
|
||||
entity.setBurst(burst);
|
||||
} else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
|
||||
// 2-匀速排队, 则超时时间必填
|
||||
Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
|
||||
if (maxQueueingTimeoutMs == null) {
|
||||
return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
|
||||
}
|
||||
if (maxQueueingTimeoutMs < 0) {
|
||||
return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
|
||||
}
|
||||
entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
|
||||
}
|
||||
|
||||
Date date = new Date();
|
||||
entity.setGmtModified(date);
|
||||
|
||||
try {
|
||||
entity = repository.save(entity);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("update gateway flow rule error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishRules(app, entity.getIp(), entity.getPort())) {
|
||||
logger.warn("publish gateway flow rules fail after update");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(entity);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/delete.json")
|
||||
public Result<Long> deleteFlowRule(HttpServletRequest request, Long id) {
|
||||
AuthService.AuthUser authUser = authService.getAuthUser(request);
|
||||
|
||||
if (id == null) {
|
||||
return Result.ofFail(-1, "id can't be null");
|
||||
}
|
||||
|
||||
GatewayFlowRuleEntity oldEntity = repository.findById(id);
|
||||
if (oldEntity == null) {
|
||||
return Result.ofSuccess(null);
|
||||
}
|
||||
|
||||
authUser.authTarget(oldEntity.getApp(), AuthService.PrivilegeType.DELETE_RULE);
|
||||
|
||||
try {
|
||||
repository.delete(id);
|
||||
} catch (Throwable throwable) {
|
||||
logger.error("delete gateway flow rule error:", throwable);
|
||||
return Result.ofThrowable(-1, throwable);
|
||||
}
|
||||
|
||||
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
|
||||
logger.warn("publish gateway flow rules fail after delete");
|
||||
}
|
||||
|
||||
return Result.ofSuccess(id);
|
||||
}
|
||||
|
||||
private boolean publishRules(String app, String ip, Integer port) {
|
||||
List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
|
||||
return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ public class ApplicationEntity {
|
|||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
private String app;
|
||||
private Integer appType;
|
||||
private String activeConsole;
|
||||
private Date lastFetch;
|
||||
|
||||
|
|
@ -63,6 +64,14 @@ public class ApplicationEntity {
|
|||
this.app = app;
|
||||
}
|
||||
|
||||
public Integer getAppType() {
|
||||
return appType;
|
||||
}
|
||||
|
||||
public void setAppType(Integer appType) {
|
||||
this.appType = appType;
|
||||
}
|
||||
|
||||
public String getActiveConsole() {
|
||||
return activeConsole;
|
||||
}
|
||||
|
|
@ -80,7 +89,7 @@ public class ApplicationEntity {
|
|||
}
|
||||
|
||||
public AppInfo toAppInfo() {
|
||||
return new AppInfo(app);
|
||||
return new AppInfo(app, appType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.datasource.entity.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Entity for {@link ApiDefinition}.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class ApiDefinitionEntity implements RuleEntity {
|
||||
|
||||
private Long id;
|
||||
private String app;
|
||||
private String ip;
|
||||
private Integer port;
|
||||
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
|
||||
private String apiName;
|
||||
private Set<ApiPredicateItemEntity> predicateItems;
|
||||
|
||||
public static ApiDefinitionEntity fromApiDefinition(String app, String ip, Integer port, ApiDefinition apiDefinition) {
|
||||
ApiDefinitionEntity entity = new ApiDefinitionEntity();
|
||||
entity.setApp(app);
|
||||
entity.setIp(ip);
|
||||
entity.setPort(port);
|
||||
entity.setApiName(apiDefinition.getApiName());
|
||||
|
||||
Set<ApiPredicateItemEntity> predicateItems = new LinkedHashSet<>();
|
||||
entity.setPredicateItems(predicateItems);
|
||||
|
||||
Set<ApiPredicateItem> apiPredicateItems = apiDefinition.getPredicateItems();
|
||||
if (apiPredicateItems != null) {
|
||||
for (ApiPredicateItem apiPredicateItem : apiPredicateItems) {
|
||||
ApiPredicateItemEntity itemEntity = new ApiPredicateItemEntity();
|
||||
predicateItems.add(itemEntity);
|
||||
ApiPathPredicateItem pathPredicateItem = (ApiPathPredicateItem) apiPredicateItem;
|
||||
itemEntity.setPattern(pathPredicateItem.getPattern());
|
||||
itemEntity.setMatchStrategy(pathPredicateItem.getMatchStrategy());
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public ApiDefinition toApiDefinition() {
|
||||
ApiDefinition apiDefinition = new ApiDefinition();
|
||||
apiDefinition.setApiName(apiName);
|
||||
|
||||
Set<ApiPredicateItem> apiPredicateItems = new LinkedHashSet<>();
|
||||
apiDefinition.setPredicateItems(apiPredicateItems);
|
||||
|
||||
if (predicateItems != null) {
|
||||
for (ApiPredicateItemEntity predicateItem : predicateItems) {
|
||||
ApiPathPredicateItem apiPredicateItem = new ApiPathPredicateItem();
|
||||
apiPredicateItems.add(apiPredicateItem);
|
||||
apiPredicateItem.setMatchStrategy(predicateItem.getMatchStrategy());
|
||||
apiPredicateItem.setPattern(predicateItem.getPattern());
|
||||
}
|
||||
}
|
||||
|
||||
return apiDefinition;
|
||||
}
|
||||
|
||||
public ApiDefinitionEntity() {
|
||||
|
||||
}
|
||||
|
||||
public ApiDefinitionEntity(String apiName, Set<ApiPredicateItemEntity> predicateItems) {
|
||||
this.apiName = apiName;
|
||||
this.predicateItems = predicateItems;
|
||||
}
|
||||
|
||||
public String getApiName() {
|
||||
return apiName;
|
||||
}
|
||||
|
||||
public void setApiName(String apiName) {
|
||||
this.apiName = apiName;
|
||||
}
|
||||
|
||||
public Set<ApiPredicateItemEntity> getPredicateItems() {
|
||||
return predicateItems;
|
||||
}
|
||||
|
||||
public void setPredicateItems(Set<ApiPredicateItemEntity> predicateItems) {
|
||||
this.predicateItems = predicateItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getGmtCreate() {
|
||||
return gmtCreate;
|
||||
}
|
||||
|
||||
public void setGmtCreate(Date gmtCreate) {
|
||||
this.gmtCreate = gmtCreate;
|
||||
}
|
||||
|
||||
public Date getGmtModified() {
|
||||
return gmtModified;
|
||||
}
|
||||
|
||||
public void setGmtModified(Date gmtModified) {
|
||||
this.gmtModified = gmtModified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rule toRule() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
ApiDefinitionEntity entity = (ApiDefinitionEntity) o;
|
||||
return Objects.equals(id, entity.id) &&
|
||||
Objects.equals(app, entity.app) &&
|
||||
Objects.equals(ip, entity.ip) &&
|
||||
Objects.equals(port, entity.port) &&
|
||||
Objects.equals(gmtCreate, entity.gmtCreate) &&
|
||||
Objects.equals(gmtModified, entity.gmtModified) &&
|
||||
Objects.equals(apiName, entity.apiName) &&
|
||||
Objects.equals(predicateItems, entity.predicateItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, app, ip, port, gmtCreate, gmtModified, apiName, predicateItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiDefinitionEntity{" +
|
||||
"id=" + id +
|
||||
", app='" + app + '\'' +
|
||||
", ip='" + ip + '\'' +
|
||||
", port=" + port +
|
||||
", gmtCreate=" + gmtCreate +
|
||||
", gmtModified=" + gmtModified +
|
||||
", apiName='" + apiName + '\'' +
|
||||
", predicateItems=" + predicateItems +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.datasource.entity.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entity for {@link ApiPredicateItem}.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class ApiPredicateItemEntity {
|
||||
|
||||
private String pattern;
|
||||
|
||||
private Integer matchStrategy;
|
||||
|
||||
public ApiPredicateItemEntity() {
|
||||
}
|
||||
|
||||
public ApiPredicateItemEntity(String pattern, int matchStrategy) {
|
||||
this.pattern = pattern;
|
||||
this.matchStrategy = matchStrategy;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Integer getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
public void setMatchStrategy(Integer matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
ApiPredicateItemEntity that = (ApiPredicateItemEntity) o;
|
||||
return Objects.equals(pattern, that.pattern) &&
|
||||
Objects.equals(matchStrategy, that.matchStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(pattern, matchStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiPredicateItemEntity{" +
|
||||
"pattern='" + pattern + '\'' +
|
||||
", matchStrategy=" + matchStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* 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.datasource.entity.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity;
|
||||
import com.alibaba.csp.sentinel.slots.block.Rule;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entity for {@link GatewayFlowRule}.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class GatewayFlowRuleEntity implements RuleEntity {
|
||||
|
||||
/**间隔单位*/
|
||||
/**0-秒*/
|
||||
public static final int INTERVAL_UNIT_SECOND = 0;
|
||||
/**1-分*/
|
||||
public static final int INTERVAL_UNIT_MINUTE = 1;
|
||||
/**2-时*/
|
||||
public static final int INTERVAL_UNIT_HOUR = 2;
|
||||
/**3-天*/
|
||||
public static final int INTERVAL_UNIT_DAY = 3;
|
||||
|
||||
private Long id;
|
||||
private String app;
|
||||
private String ip;
|
||||
private Integer port;
|
||||
|
||||
private Date gmtCreate;
|
||||
private Date gmtModified;
|
||||
|
||||
private String resource;
|
||||
private Integer resourceMode;
|
||||
|
||||
private Integer grade;
|
||||
private Double count;
|
||||
private Long interval;
|
||||
private Integer intervalUnit;
|
||||
|
||||
private Integer controlBehavior;
|
||||
private Integer burst;
|
||||
|
||||
private Integer maxQueueingTimeoutMs;
|
||||
|
||||
private GatewayParamFlowItemEntity paramItem;
|
||||
|
||||
public static Long calIntervalSec(Long interval, Integer intervalUnit) {
|
||||
switch (intervalUnit) {
|
||||
case INTERVAL_UNIT_SECOND:
|
||||
return interval;
|
||||
case INTERVAL_UNIT_MINUTE:
|
||||
return interval * 60;
|
||||
case INTERVAL_UNIT_HOUR:
|
||||
return interval * 60 * 60;
|
||||
case INTERVAL_UNIT_DAY:
|
||||
return interval * 60 * 60 * 24;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid intervalUnit: " + intervalUnit);
|
||||
}
|
||||
|
||||
public static Object[] parseIntervalSec(Long intervalSec) {
|
||||
if (intervalSec % (60 * 60 * 24) == 0) {
|
||||
return new Object[] {intervalSec / (60 * 60 * 24), INTERVAL_UNIT_DAY};
|
||||
}
|
||||
|
||||
if (intervalSec % (60 * 60 ) == 0) {
|
||||
return new Object[] {intervalSec / (60 * 60), INTERVAL_UNIT_HOUR};
|
||||
}
|
||||
|
||||
if (intervalSec % 60 == 0) {
|
||||
return new Object[] {intervalSec / 60, INTERVAL_UNIT_MINUTE};
|
||||
}
|
||||
|
||||
return new Object[] {intervalSec, INTERVAL_UNIT_SECOND};
|
||||
}
|
||||
|
||||
public GatewayFlowRule toGatewayFlowRule() {
|
||||
GatewayFlowRule rule = new GatewayFlowRule();
|
||||
rule.setResource(resource);
|
||||
rule.setResourceMode(resourceMode);
|
||||
|
||||
rule.setGrade(grade);
|
||||
rule.setCount(count);
|
||||
rule.setIntervalSec(calIntervalSec(interval, intervalUnit));
|
||||
|
||||
rule.setControlBehavior(controlBehavior);
|
||||
|
||||
if (burst != null) {
|
||||
rule.setBurst(burst);
|
||||
}
|
||||
|
||||
if (maxQueueingTimeoutMs != null) {
|
||||
rule.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
|
||||
}
|
||||
|
||||
if (paramItem != null) {
|
||||
GatewayParamFlowItem ruleItem = new GatewayParamFlowItem();
|
||||
rule.setParamItem(ruleItem);
|
||||
ruleItem.setParseStrategy(paramItem.getParseStrategy());
|
||||
ruleItem.setFieldName(paramItem.getFieldName());
|
||||
ruleItem.setPattern(paramItem.getPattern());
|
||||
|
||||
if (paramItem.getMatchStrategy() != null) {
|
||||
ruleItem.setMatchStrategy(paramItem.getMatchStrategy());
|
||||
}
|
||||
}
|
||||
|
||||
return rule;
|
||||
}
|
||||
|
||||
public static GatewayFlowRuleEntity fromGatewayFlowRule(String app, String ip, Integer port, GatewayFlowRule rule) {
|
||||
GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
|
||||
entity.setApp(app);
|
||||
entity.setIp(ip);
|
||||
entity.setPort(port);
|
||||
|
||||
entity.setResource(rule.getResource());
|
||||
entity.setResourceMode(rule.getResourceMode());
|
||||
|
||||
entity.setGrade(rule.getGrade());
|
||||
entity.setCount(rule.getCount());
|
||||
Object[] intervalSecResult = parseIntervalSec(rule.getIntervalSec());
|
||||
entity.setInterval((Long) intervalSecResult[0]);
|
||||
entity.setIntervalUnit((Integer) intervalSecResult[1]);
|
||||
|
||||
entity.setControlBehavior(rule.getControlBehavior());
|
||||
entity.setBurst(rule.getBurst());
|
||||
entity.setMaxQueueingTimeoutMs(rule.getMaxQueueingTimeoutMs());
|
||||
|
||||
GatewayParamFlowItem paramItem = rule.getParamItem();
|
||||
if (paramItem != null) {
|
||||
GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
|
||||
entity.setParamItem(itemEntity);
|
||||
itemEntity.setParseStrategy(paramItem.getParseStrategy());
|
||||
itemEntity.setFieldName(paramItem.getFieldName());
|
||||
itemEntity.setPattern(paramItem.getPattern());
|
||||
itemEntity.setMatchStrategy(paramItem.getMatchStrategy());
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getGmtCreate() {
|
||||
return gmtCreate;
|
||||
}
|
||||
|
||||
public void setGmtCreate(Date gmtCreate) {
|
||||
this.gmtCreate = gmtCreate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rule toRule() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getGmtModified() {
|
||||
return gmtModified;
|
||||
}
|
||||
|
||||
public void setGmtModified(Date gmtModified) {
|
||||
this.gmtModified = gmtModified;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItemEntity getParamItem() {
|
||||
return paramItem;
|
||||
}
|
||||
|
||||
public void setParamItem(GatewayParamFlowItemEntity paramItem) {
|
||||
this.paramItem = paramItem;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public Integer getResourceMode() {
|
||||
return resourceMode;
|
||||
}
|
||||
|
||||
public void setResourceMode(Integer resourceMode) {
|
||||
this.resourceMode = resourceMode;
|
||||
}
|
||||
|
||||
public Integer getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(Integer grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public Double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Double count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(Long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
public Integer getIntervalUnit() {
|
||||
return intervalUnit;
|
||||
}
|
||||
|
||||
public void setIntervalUnit(Integer intervalUnit) {
|
||||
this.intervalUnit = intervalUnit;
|
||||
}
|
||||
|
||||
public Integer getControlBehavior() {
|
||||
return controlBehavior;
|
||||
}
|
||||
|
||||
public void setControlBehavior(Integer controlBehavior) {
|
||||
this.controlBehavior = controlBehavior;
|
||||
}
|
||||
|
||||
public Integer getBurst() {
|
||||
return burst;
|
||||
}
|
||||
|
||||
public void setBurst(Integer burst) {
|
||||
this.burst = burst;
|
||||
}
|
||||
|
||||
public Integer getMaxQueueingTimeoutMs() {
|
||||
return maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) {
|
||||
this.maxQueueingTimeoutMs = maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
GatewayFlowRuleEntity that = (GatewayFlowRuleEntity) o;
|
||||
return Objects.equals(id, that.id) &&
|
||||
Objects.equals(app, that.app) &&
|
||||
Objects.equals(ip, that.ip) &&
|
||||
Objects.equals(port, that.port) &&
|
||||
Objects.equals(gmtCreate, that.gmtCreate) &&
|
||||
Objects.equals(gmtModified, that.gmtModified) &&
|
||||
Objects.equals(resource, that.resource) &&
|
||||
Objects.equals(resourceMode, that.resourceMode) &&
|
||||
Objects.equals(grade, that.grade) &&
|
||||
Objects.equals(count, that.count) &&
|
||||
Objects.equals(interval, that.interval) &&
|
||||
Objects.equals(intervalUnit, that.intervalUnit) &&
|
||||
Objects.equals(controlBehavior, that.controlBehavior) &&
|
||||
Objects.equals(burst, that.burst) &&
|
||||
Objects.equals(maxQueueingTimeoutMs, that.maxQueueingTimeoutMs) &&
|
||||
Objects.equals(paramItem, that.paramItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, app, ip, port, gmtCreate, gmtModified, resource, resourceMode, grade, count, interval, intervalUnit, controlBehavior, burst, maxQueueingTimeoutMs, paramItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GatewayFlowRuleEntity{" +
|
||||
"id=" + id +
|
||||
", app='" + app + '\'' +
|
||||
", ip='" + ip + '\'' +
|
||||
", port=" + port +
|
||||
", gmtCreate=" + gmtCreate +
|
||||
", gmtModified=" + gmtModified +
|
||||
", resource='" + resource + '\'' +
|
||||
", resourceMode=" + resourceMode +
|
||||
", grade=" + grade +
|
||||
", count=" + count +
|
||||
", interval=" + interval +
|
||||
", intervalUnit=" + intervalUnit +
|
||||
", controlBehavior=" + controlBehavior +
|
||||
", burst=" + burst +
|
||||
", maxQueueingTimeoutMs=" + maxQueueingTimeoutMs +
|
||||
", paramItem=" + paramItem +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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.datasource.entity.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entity for {@link GatewayParamFlowItem}.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class GatewayParamFlowItemEntity {
|
||||
|
||||
private Integer parseStrategy;
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private String pattern;
|
||||
|
||||
private Integer matchStrategy;
|
||||
|
||||
public Integer getParseStrategy() {
|
||||
return parseStrategy;
|
||||
}
|
||||
|
||||
public void setParseStrategy(Integer parseStrategy) {
|
||||
this.parseStrategy = parseStrategy;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public void setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Integer getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
public void setMatchStrategy(Integer matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
GatewayParamFlowItemEntity that = (GatewayParamFlowItemEntity) o;
|
||||
return Objects.equals(parseStrategy, that.parseStrategy) &&
|
||||
Objects.equals(fieldName, that.fieldName) &&
|
||||
Objects.equals(pattern, that.pattern) &&
|
||||
Objects.equals(matchStrategy, that.matchStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(parseStrategy, fieldName, pattern, matchStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GatewayParamFlowItemEntity{" +
|
||||
"parseStrategy=" + parseStrategy +
|
||||
", fieldName='" + fieldName + '\'' +
|
||||
", pattern='" + pattern + '\'' +
|
||||
", matchStrategy=" + matchStrategy +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ public class AppInfo {
|
|||
|
||||
private String app = "";
|
||||
|
||||
private Integer appType = 0;
|
||||
|
||||
private Set<MachineInfo> machines = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public AppInfo() {}
|
||||
|
|
@ -36,6 +38,11 @@ public class AppInfo {
|
|||
this.app = app;
|
||||
}
|
||||
|
||||
public AppInfo(String app, Integer appType) {
|
||||
this.app = app;
|
||||
this.appType = appType;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
|
@ -44,6 +51,14 @@ public class AppInfo {
|
|||
this.app = app;
|
||||
}
|
||||
|
||||
public Integer getAppType() {
|
||||
return appType;
|
||||
}
|
||||
|
||||
public void setAppType(Integer appType) {
|
||||
this.appType = appType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current machines.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.alibaba.csp.sentinel.util.StringUtil;
|
|||
public class MachineInfo implements Comparable<MachineInfo> {
|
||||
|
||||
private String app = "";
|
||||
private Integer appType = 0;
|
||||
private String hostname = "";
|
||||
private String ip = "";
|
||||
private Integer port = -1;
|
||||
|
|
@ -62,6 +63,14 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
|||
this.app = app;
|
||||
}
|
||||
|
||||
public Integer getAppType() {
|
||||
return appType;
|
||||
}
|
||||
|
||||
public void setAppType(Integer appType) {
|
||||
this.appType = appType;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
|
@ -139,6 +148,7 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
|||
public String toString() {
|
||||
return new StringBuilder("MachineInfo {")
|
||||
.append("app='").append(app).append('\'')
|
||||
.append(",appType='").append(appType).append('\'')
|
||||
.append(", hostname='").append(hostname).append('\'')
|
||||
.append(", ip='").append(ip).append('\'')
|
||||
.append(", port=").append(port)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class SimpleMachineDiscovery implements MachineDiscovery {
|
|||
@Override
|
||||
public long addMachine(MachineInfo machineInfo) {
|
||||
AssertUtil.notNull(machineInfo, "machineInfo cannot be null");
|
||||
AppInfo appInfo = apps.computeIfAbsent(machineInfo.getApp(), AppInfo::new);
|
||||
AppInfo appInfo = apps.computeIfAbsent(machineInfo.getApp(), o -> new AppInfo(machineInfo.getApp(), machineInfo.getAppType()));
|
||||
appInfo.addMachine(machineInfo);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Value Object for add gateway api.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class AddApiReqVo {
|
||||
|
||||
private String app;
|
||||
|
||||
private String ip;
|
||||
|
||||
private Integer port;
|
||||
|
||||
private String apiName;
|
||||
|
||||
private List<ApiPredicateItemVo> predicateItems;
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getApiName() {
|
||||
return apiName;
|
||||
}
|
||||
|
||||
public void setApiName(String apiName) {
|
||||
this.apiName = apiName;
|
||||
}
|
||||
|
||||
public List<ApiPredicateItemVo> getPredicateItems() {
|
||||
return predicateItems;
|
||||
}
|
||||
|
||||
public void setPredicateItems(List<ApiPredicateItemVo> predicateItems) {
|
||||
this.predicateItems = predicateItems;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.api;
|
||||
|
||||
/**
|
||||
* Value Object for add or update gateway api.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class ApiPredicateItemVo {
|
||||
|
||||
private String pattern;
|
||||
|
||||
private Integer matchStrategy;
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Integer getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
public void setMatchStrategy(Integer matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Value Object for update gateway api.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class UpdateApiReqVo {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String app;
|
||||
|
||||
private List<ApiPredicateItemVo> predicateItems;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public List<ApiPredicateItemVo> getPredicateItems() {
|
||||
return predicateItems;
|
||||
}
|
||||
|
||||
public void setPredicateItems(List<ApiPredicateItemVo> predicateItems) {
|
||||
this.predicateItems = predicateItems;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.rule;
|
||||
|
||||
/**
|
||||
* Value Object for add gateway flow rule.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class AddFlowRuleReqVo {
|
||||
|
||||
private String app;
|
||||
|
||||
private String ip;
|
||||
|
||||
private Integer port;
|
||||
|
||||
private String resource;
|
||||
|
||||
private Integer resourceMode;
|
||||
|
||||
private Integer grade;
|
||||
|
||||
private Double count;
|
||||
|
||||
private Long interval;
|
||||
|
||||
private Integer intervalUnit;
|
||||
|
||||
private Integer controlBehavior;
|
||||
|
||||
private Integer burst;
|
||||
|
||||
private Integer maxQueueingTimeoutMs;
|
||||
|
||||
private GatewayParamFlowItemVo paramItem;
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public Integer getResourceMode() {
|
||||
return resourceMode;
|
||||
}
|
||||
|
||||
public void setResourceMode(Integer resourceMode) {
|
||||
this.resourceMode = resourceMode;
|
||||
}
|
||||
|
||||
public Integer getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(Integer grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public Double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Double count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(Long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
public Integer getIntervalUnit() {
|
||||
return intervalUnit;
|
||||
}
|
||||
|
||||
public void setIntervalUnit(Integer intervalUnit) {
|
||||
this.intervalUnit = intervalUnit;
|
||||
}
|
||||
|
||||
public Integer getControlBehavior() {
|
||||
return controlBehavior;
|
||||
}
|
||||
|
||||
public void setControlBehavior(Integer controlBehavior) {
|
||||
this.controlBehavior = controlBehavior;
|
||||
}
|
||||
|
||||
public Integer getBurst() {
|
||||
return burst;
|
||||
}
|
||||
|
||||
public void setBurst(Integer burst) {
|
||||
this.burst = burst;
|
||||
}
|
||||
|
||||
public Integer getMaxQueueingTimeoutMs() {
|
||||
return maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) {
|
||||
this.maxQueueingTimeoutMs = maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItemVo getParamItem() {
|
||||
return paramItem;
|
||||
}
|
||||
|
||||
public void setParamItem(GatewayParamFlowItemVo paramItem) {
|
||||
this.paramItem = paramItem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.rule;
|
||||
|
||||
/**
|
||||
* Value Object for add or update gateway flow rule.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class GatewayParamFlowItemVo {
|
||||
|
||||
private Integer parseStrategy;
|
||||
|
||||
private String fieldName;
|
||||
|
||||
private String pattern;
|
||||
|
||||
private Integer matchStrategy;
|
||||
|
||||
public Integer getParseStrategy() {
|
||||
return parseStrategy;
|
||||
}
|
||||
|
||||
public void setParseStrategy(Integer parseStrategy) {
|
||||
this.parseStrategy = parseStrategy;
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public void setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Integer getMatchStrategy() {
|
||||
return matchStrategy;
|
||||
}
|
||||
|
||||
public void setMatchStrategy(Integer matchStrategy) {
|
||||
this.matchStrategy = matchStrategy;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.domain.vo.gateway.rule;
|
||||
|
||||
/**
|
||||
* Value Object for update gateway flow rule.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class UpdateFlowRuleReqVo {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String app;
|
||||
|
||||
private Integer grade;
|
||||
|
||||
private Double count;
|
||||
|
||||
private Long interval;
|
||||
|
||||
private Integer intervalUnit;
|
||||
|
||||
private Integer controlBehavior;
|
||||
|
||||
private Integer burst;
|
||||
|
||||
private Integer maxQueueingTimeoutMs;
|
||||
|
||||
private GatewayParamFlowItemVo paramItem;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public Integer getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(Integer grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public Double getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Double count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public Long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(Long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
public Integer getIntervalUnit() {
|
||||
return intervalUnit;
|
||||
}
|
||||
|
||||
public void setIntervalUnit(Integer intervalUnit) {
|
||||
this.intervalUnit = intervalUnit;
|
||||
}
|
||||
|
||||
public Integer getControlBehavior() {
|
||||
return controlBehavior;
|
||||
}
|
||||
|
||||
public void setControlBehavior(Integer controlBehavior) {
|
||||
this.controlBehavior = controlBehavior;
|
||||
}
|
||||
|
||||
public Integer getBurst() {
|
||||
return burst;
|
||||
}
|
||||
|
||||
public void setBurst(Integer burst) {
|
||||
this.burst = burst;
|
||||
}
|
||||
|
||||
public Integer getMaxQueueingTimeoutMs() {
|
||||
return maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public void setMaxQueueingTimeoutMs(Integer maxQueueingTimeoutMs) {
|
||||
this.maxQueueingTimeoutMs = maxQueueingTimeoutMs;
|
||||
}
|
||||
|
||||
public GatewayParamFlowItemVo getParamItem() {
|
||||
return paramItem;
|
||||
}
|
||||
|
||||
public void setParamItem(GatewayParamFlowItemVo paramItem) {
|
||||
this.paramItem = paramItem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.repository.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Store {@link ApiDefinitionEntity} in memory.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
@Component
|
||||
public class InMemApiDefinitionStore extends InMemoryRuleRepositoryAdapter<ApiDefinitionEntity> {
|
||||
|
||||
private static AtomicLong ids = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
protected long nextId() {
|
||||
return ids.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.repository.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Store {@link GatewayFlowRuleEntity} in memory.
|
||||
*
|
||||
* @author cdfive
|
||||
* @since 1.7.0
|
||||
*/
|
||||
@Component
|
||||
public class InMemGatewayFlowRuleStore extends InMemoryRuleRepositoryAdapter<GatewayFlowRuleEntity> {
|
||||
|
||||
private static AtomicLong ids = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
protected long nextId() {
|
||||
return ids.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +110,12 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
|
|||
return new ArrayList<>(entities.values());
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
allRules.clear();
|
||||
machineRules.clear();
|
||||
appRules.clear();
|
||||
}
|
||||
|
||||
protected T preProcess(T entity) {
|
||||
return entity;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ angular
|
|||
.factory('AuthInterceptor', ['$window', '$state', function ($window, $state) {
|
||||
var authInterceptor = {
|
||||
'responseError' : function(response) {
|
||||
if (response.status == 401) {
|
||||
if (response.status === 401) {
|
||||
// If not auth, clear session in localStorage and jump to the login page
|
||||
$window.localStorage.removeItem("session_sentinel_admin");
|
||||
$window.localStorage.removeItem('session_sentinel_admin');
|
||||
$state.go('login');
|
||||
}
|
||||
|
||||
|
|
@ -123,21 +123,21 @@ angular
|
|||
}
|
||||
})
|
||||
|
||||
.state('dashboard.flow', {
|
||||
templateUrl: 'app/views/flow_v2.html',
|
||||
url: '/v2/flow/:app',
|
||||
controller: 'FlowControllerV2',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/flow_v2.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('dashboard.flow', {
|
||||
templateUrl: 'app/views/flow_v2.html',
|
||||
url: '/v2/flow/:app',
|
||||
controller: 'FlowControllerV2',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/flow_v2.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.paramFlow', {
|
||||
templateUrl: 'app/views/param_flow.html',
|
||||
|
|
@ -155,69 +155,69 @@ angular
|
|||
}
|
||||
})
|
||||
|
||||
.state('dashboard.clusterAppAssignManage', {
|
||||
templateUrl: 'app/views/cluster_app_assign_manage.html',
|
||||
url: '/cluster/assign_manage/:app',
|
||||
controller: 'SentinelClusterAppAssignManageController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_assign_manage.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('dashboard.clusterAppAssignManage', {
|
||||
templateUrl: 'app/views/cluster_app_assign_manage.html',
|
||||
url: '/cluster/assign_manage/:app',
|
||||
controller: 'SentinelClusterAppAssignManageController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_assign_manage.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.clusterAppServerList', {
|
||||
templateUrl: 'app/views/cluster_app_server_list.html',
|
||||
url: '/cluster/server/:app',
|
||||
controller: 'SentinelClusterAppServerListController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_server_list.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('dashboard.clusterAppServerList', {
|
||||
templateUrl: 'app/views/cluster_app_server_list.html',
|
||||
url: '/cluster/server/:app',
|
||||
controller: 'SentinelClusterAppServerListController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_server_list.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.clusterAppClientList', {
|
||||
templateUrl: 'app/views/cluster_app_client_list.html',
|
||||
url: '/cluster/client/:app',
|
||||
controller: 'SentinelClusterAppTokenClientListController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_token_client_list.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('dashboard.clusterAppClientList', {
|
||||
templateUrl: 'app/views/cluster_app_client_list.html',
|
||||
url: '/cluster/client/:app',
|
||||
controller: 'SentinelClusterAppTokenClientListController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_app_token_client_list.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.clusterSingle', {
|
||||
templateUrl: 'app/views/cluster_single_config.html',
|
||||
url: '/cluster/single/:app',
|
||||
controller: 'SentinelClusterSingleController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_single.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('dashboard.clusterSingle', {
|
||||
templateUrl: 'app/views/cluster_single_config.html',
|
||||
url: '/cluster/single/:app',
|
||||
controller: 'SentinelClusterSingleController',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/cluster_single.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.authority', {
|
||||
templateUrl: 'app/views/authority.html',
|
||||
|
|
@ -298,6 +298,23 @@ angular
|
|||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.gatewayIdentity', {
|
||||
templateUrl: 'app/views/gateway/identity.html',
|
||||
url: '/gateway/identity/:app',
|
||||
controller: 'GatewayIdentityCtl',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/gateway/identity.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.metric', {
|
||||
templateUrl: 'app/views/metric.html',
|
||||
url: '/metric/:app',
|
||||
|
|
@ -312,5 +329,37 @@ angular
|
|||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.gatewayApi', {
|
||||
templateUrl: 'app/views/gateway/api.html',
|
||||
url: '/gateway/api/:app',
|
||||
controller: 'GatewayApiCtl',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/gateway/api.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
.state('dashboard.gatewayFlow', {
|
||||
templateUrl: 'app/views/gateway/flow.html',
|
||||
url: '/gateway/flow/:app',
|
||||
controller: 'GatewayFlowCtl',
|
||||
resolve: {
|
||||
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
|
||||
return $ocLazyLoad.load({
|
||||
name: 'sentinelDashboardApp',
|
||||
files: [
|
||||
'app/scripts/controllers/gateway/flow.js',
|
||||
]
|
||||
});
|
||||
}]
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.controller('GatewayApiCtl', ['$scope', '$stateParams', 'GatewayApiService', '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: 840,
|
||||
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: 840,
|
||||
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();
|
||||
}
|
||||
});
|
||||
}]
|
||||
);
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.controller('GatewayFlowCtl', ['$scope', '$stateParams', 'GatewayFlowService', 'GatewayApiService', '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) {
|
||||
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(':');
|
||||
GatewayApiService.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();
|
||||
}
|
||||
});
|
||||
}]
|
||||
);
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.controller('GatewayIdentityCtl', ['$scope', '$stateParams', 'IdentityService',
|
||||
'ngDialog', 'GatewayFlowService', 'GatewayApiService', 'DegradeService', 'MachineService',
|
||||
'$interval', '$location', '$timeout',
|
||||
function ($scope, $stateParams, IdentityService, ngDialog,
|
||||
GatewayFlowService, GatewayApiService, DegradeService, MachineService, $interval, $location, $timeout) {
|
||||
|
||||
$scope.app = $stateParams.app;
|
||||
|
||||
$scope.currentPage = 1;
|
||||
$scope.pageSize = 16;
|
||||
$scope.totalPage = 1;
|
||||
$scope.totalCount = 0;
|
||||
$scope.identities = [];
|
||||
|
||||
$scope.searchKey = '';
|
||||
|
||||
$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;
|
||||
}
|
||||
};
|
||||
$scope.table = null;
|
||||
|
||||
getApiNames();
|
||||
function getApiNames() {
|
||||
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) {
|
||||
$scope.apiNames = [];
|
||||
|
||||
data.data.forEach(function (api) {
|
||||
$scope.apiNames.push(api["apiName"]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var gatewayFlowRuleDialog;
|
||||
var gatewayFlowRuleDialogScope;
|
||||
$scope.addNewGatewayFlowRule = function (resource) {
|
||||
if (!$scope.macInputModel) {
|
||||
return;
|
||||
}
|
||||
var mac = $scope.macInputModel.split(':');
|
||||
gatewayFlowRuleDialogScope = $scope.$new(true);
|
||||
|
||||
gatewayFlowRuleDialogScope.apiNames = $scope.apiNames;
|
||||
|
||||
gatewayFlowRuleDialogScope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}];
|
||||
|
||||
gatewayFlowRuleDialogScope.currentRule = {
|
||||
grade: 1,
|
||||
app: $scope.app,
|
||||
ip: mac[0],
|
||||
port: mac[1],
|
||||
resourceMode: gatewayFlowRuleDialogScope.apiNames.indexOf(resource) == -1 ? 0 : 1,
|
||||
resource: resource,
|
||||
interval: 1,
|
||||
intervalUnit: 0,
|
||||
controlBehavior: 0,
|
||||
burst: 0,
|
||||
maxQueueingTimeoutMs: 0
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.gatewayFlowRuleDialog = {
|
||||
title: '新增网关流控规则',
|
||||
type: 'add',
|
||||
confirmBtnText: '新增',
|
||||
saveAndContinueBtnText: '新增并继续添加',
|
||||
showAdvanceButton: true
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.useRouteID = function() {
|
||||
gatewayFlowRuleDialogScope.currentRule.resource = '';
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.useCustormAPI = function() {
|
||||
gatewayFlowRuleDialogScope.currentRule.resource = '';
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.useParamItem = function () {
|
||||
gatewayFlowRuleDialogScope.currentRule.paramItem = {
|
||||
parseStrategy: 0,
|
||||
matchStrategy: 0
|
||||
};
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.notUseParamItem = function () {
|
||||
gatewayFlowRuleDialogScope.currentRule.paramItem = null;
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.useParamItemVal = function() {
|
||||
gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = "";
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.notUseParamItemVal = function() {
|
||||
gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = null;
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialogScope.saveRule = saveGatewayFlowRule;
|
||||
gatewayFlowRuleDialogScope.saveRuleAndContinue = saveGatewayFlowRuleAndContinue;
|
||||
gatewayFlowRuleDialogScope.onOpenAdvanceClick = function () {
|
||||
gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = false;
|
||||
};
|
||||
gatewayFlowRuleDialogScope.onCloseAdvanceClick = function () {
|
||||
gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = true;
|
||||
};
|
||||
|
||||
gatewayFlowRuleDialog = ngDialog.open({
|
||||
template: '/app/views/dialog/gateway/flow-rule-dialog.html',
|
||||
width: 780,
|
||||
overlay: true,
|
||||
scope: gatewayFlowRuleDialogScope
|
||||
});
|
||||
};
|
||||
|
||||
function saveGatewayFlowRule() {
|
||||
if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) {
|
||||
return;
|
||||
}
|
||||
GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) {
|
||||
if (data.code === 0) {
|
||||
gatewayFlowRuleDialog.close();
|
||||
let url = '/dashboard/gateway/flow/' + $scope.app;
|
||||
$location.path(url);
|
||||
} else {
|
||||
alert('失败!');
|
||||
}
|
||||
}).error((data, header, config, status) => {
|
||||
alert('未知错误');
|
||||
});
|
||||
}
|
||||
|
||||
function saveGatewayFlowRuleAndContinue() {
|
||||
if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) {
|
||||
return;
|
||||
}
|
||||
GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
gatewayFlowRuleDialog.close();
|
||||
} else {
|
||||
alert('失败!');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var degradeRuleDialog;
|
||||
$scope.addNewDegradeRule = function (resource) {
|
||||
if (!$scope.macInputModel) {
|
||||
return;
|
||||
}
|
||||
var mac = $scope.macInputModel.split(':');
|
||||
degradeRuleDialogScope = $scope.$new(true);
|
||||
degradeRuleDialogScope.currentRule = {
|
||||
enable: false,
|
||||
grade: 0,
|
||||
strategy: 0,
|
||||
resource: resource,
|
||||
limitApp: 'default',
|
||||
app: $scope.app,
|
||||
ip: mac[0],
|
||||
port: mac[1]
|
||||
};
|
||||
|
||||
degradeRuleDialogScope.degradeRuleDialog = {
|
||||
title: '新增降级规则',
|
||||
type: 'add',
|
||||
confirmBtnText: '新增',
|
||||
saveAndContinueBtnText: '新增并继续添加'
|
||||
};
|
||||
degradeRuleDialogScope.saveRule = saveDegradeRule;
|
||||
degradeRuleDialogScope.saveRuleAndContinue = saveDegradeRuleAndContinue;
|
||||
|
||||
degradeRuleDialog = ngDialog.open({
|
||||
template: '/app/views/dialog/degrade-rule-dialog.html',
|
||||
width: 680,
|
||||
overlay: true,
|
||||
scope: degradeRuleDialogScope
|
||||
});
|
||||
};
|
||||
|
||||
function saveDegradeRule() {
|
||||
if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) {
|
||||
return;
|
||||
}
|
||||
DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
degradeRuleDialog.close();
|
||||
var url = '/dashboard/degrade/' + $scope.app;
|
||||
$location.path(url);
|
||||
} else {
|
||||
alert('失败!');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveDegradeRuleAndContinue() {
|
||||
if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) {
|
||||
return;
|
||||
}
|
||||
DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
degradeRuleDialog.close();
|
||||
} else {
|
||||
alert('失败!');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var searchHandler;
|
||||
$scope.searchChange = function (searchKey) {
|
||||
$timeout.cancel(searchHandler);
|
||||
searchHandler = $timeout(function () {
|
||||
$scope.searchKey = searchKey;
|
||||
reInitIdentityDatas();
|
||||
}, 600);
|
||||
};
|
||||
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch all machines by current app name.
|
||||
queryAppMachines();
|
||||
|
||||
$scope.$watch('macInputModel', function () {
|
||||
if ($scope.macInputModel) {
|
||||
reInitIdentityDatas();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$interval.cancel(intervalId);
|
||||
});
|
||||
|
||||
var intervalId;
|
||||
function reInitIdentityDatas() {
|
||||
getApiNames();
|
||||
queryIdentities();
|
||||
};
|
||||
|
||||
function queryIdentities() {
|
||||
var mac = $scope.macInputModel.split(':');
|
||||
if (mac == null || mac.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityService.fetchClusterNodeOfMachine(mac[0], mac[1], $scope.searchKey).success(
|
||||
function (data) {
|
||||
if (data.code == 0 && data.data) {
|
||||
$scope.identities = data.data;
|
||||
$scope.totalCount = $scope.identities.length;
|
||||
} else {
|
||||
$scope.identities = [];
|
||||
$scope.totalCount = 0;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
$scope.queryIdentities = queryIdentities;
|
||||
}]);
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService',
|
||||
function ($scope, $state, $window, LoginService) {
|
||||
// If auth, jump to the index page directly
|
||||
function ($scope, $state, $window, AuthService) {
|
||||
// If auth passed, jump to the index page directly
|
||||
if ($window.localStorage.getItem('session_sentinel_admin')) {
|
||||
$state.go('dashboard');
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService',
|
|||
|
||||
var param = {"username": $scope.username, "password": $scope.password};
|
||||
|
||||
LoginService.login(param).success(function (data) {
|
||||
AuthService.login(param).success(function (data) {
|
||||
if (data.code == 0) {
|
||||
$window.localStorage.setItem('session_sentinel_admin', {
|
||||
username: data.data
|
||||
|
|
|
|||
|
|
@ -28,24 +28,41 @@
|
|||
<a ui-sref="dashboard.metric({app: entry.app})">
|
||||
<i class="fa fa-bar-chart"></i> 实时监控</a>
|
||||
</li>
|
||||
<li ui-sref-active="active">
|
||||
|
||||
<li ui-sref-active="active" ng-if="entry.appType==0">
|
||||
<a ui-sref="dashboard.identity({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-list-alt"></i> 簇点链路</a>
|
||||
</li>
|
||||
|
||||
<li ui-sref-active="active">
|
||||
<a ui-sref="dashboard.flowV1({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-filter"></i> 流控规则</a>
|
||||
<li ui-sref-active="active" ng-if="entry.appType==1">
|
||||
<a ui-sref="dashboard.gatewayIdentity({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-filter"></i> 请求链路</a>
|
||||
</li>
|
||||
<!--<li ui-sref-active="active">-->
|
||||
|
||||
<!--<li ui-sref-active="active" ng-if="entry.appType==0">-->
|
||||
<!--<a ui-sref="dashboard.flow({app: entry.app})">-->
|
||||
<!--<i class="glyphicon glyphicon-filter"></i> 流控规则 V1</a>-->
|
||||
<!--</li>-->
|
||||
|
||||
<li ui-sref-active="active" ng-if="entry.appType==1">
|
||||
<a ui-sref="dashboard.gatewayApi({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-tags"></i> API管理</a>
|
||||
</li>
|
||||
<li ui-sref-active="active" ng-if="entry.appType==1">
|
||||
<a ui-sref="dashboard.gatewayFlow({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-filter"></i> 流控规则</a>
|
||||
</li>
|
||||
|
||||
<li ui-sref-active="active" ng-if="entry.appType==0">
|
||||
<a ui-sref="dashboard.flowV1({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-filter"></i> 流控规则</a>
|
||||
</li>
|
||||
|
||||
<li ui-sref-active="active">
|
||||
<a ui-sref="dashboard.degrade({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-flash"></i> 降级规则</a>
|
||||
</li>
|
||||
<li ui-sref-active="active">
|
||||
<li ui-sref-active="active" ng-if="entry.appType==0">
|
||||
<a ui-sref="dashboard.paramFlow({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-fire"></i> 热点规则</a>
|
||||
</li>
|
||||
|
|
@ -53,19 +70,19 @@
|
|||
<a ui-sref="dashboard.system({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-lock"></i> 系统规则</a>
|
||||
</li>
|
||||
<li ui-sref-active="active">
|
||||
<li ui-sref-active="active" ng-if="entry.appType==0">
|
||||
<a ui-sref="dashboard.authority({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-check"></i> 授权规则</a>
|
||||
</li>
|
||||
<li ui-sref-active="active">
|
||||
<li ui-sref-active="active" ng-if="entry.appType==0">
|
||||
<a ui-sref="dashboard.clusterAppServerList({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-cloud"></i> 集群流控</a>
|
||||
</li>
|
||||
|
||||
<li ui-sref-active="active">
|
||||
<a ui-sref="dashboard.machine({app: entry.app})">
|
||||
<i class="glyphicon glyphicon-th-list"></i> 机器列表</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<!-- /.nav-second-level -->
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ angular.module('sentinelDashboardApp')
|
|||
AppService.getApps().success(
|
||||
function (data) {
|
||||
if (data.code === 0) {
|
||||
let initHashApp = $location.path().split('/')[3];
|
||||
var path = $location.path().split('/');
|
||||
let initHashApp = path[path.length - 1];
|
||||
$scope.apps = data.data;
|
||||
$scope.apps = $scope.apps.map(function (item) {
|
||||
if (item.app === initHashApp) {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ app.service('AuthService', ['$http', function ($http) {
|
|||
url: '/auth/login',
|
||||
params: param,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.logout = function () {
|
||||
return $http({
|
||||
url: '/auth/logout',
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.service('GatewayApiService', ['$http', function ($http) {
|
||||
this.queryApis = function (app, ip, port) {
|
||||
var param = {
|
||||
app: app,
|
||||
ip: ip,
|
||||
port: port
|
||||
};
|
||||
return $http({
|
||||
url: '/gateway/api/list.json',
|
||||
params: param,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
this.newApi = function (api) {
|
||||
return $http({
|
||||
url: '/gateway/api/new.json',
|
||||
data: api,
|
||||
method: 'POST'
|
||||
});
|
||||
};
|
||||
|
||||
this.saveApi = function (api) {
|
||||
return $http({
|
||||
url: '/gateway/api/save.json',
|
||||
data: api,
|
||||
method: 'POST'
|
||||
});
|
||||
};
|
||||
|
||||
this.deleteApi = function (api) {
|
||||
var param = {
|
||||
id: api.id,
|
||||
app: api.app
|
||||
};
|
||||
return $http({
|
||||
url: '/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;
|
||||
};
|
||||
}]);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.service('GatewayFlowService', ['$http', function ($http) {
|
||||
this.queryRules = function (app, ip, port) {
|
||||
var param = {
|
||||
app: app,
|
||||
ip: ip,
|
||||
port: port
|
||||
};
|
||||
|
||||
return $http({
|
||||
url: '/gateway/flow/list.json',
|
||||
params: param,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
this.newRule = function (rule) {
|
||||
return $http({
|
||||
url: '/gateway/flow/new.json',
|
||||
data: rule,
|
||||
method: 'POST'
|
||||
});
|
||||
};
|
||||
|
||||
this.saveRule = function (rule) {
|
||||
return $http({
|
||||
url: '/gateway/flow/save.json',
|
||||
data: rule,
|
||||
method: 'POST'
|
||||
});
|
||||
};
|
||||
|
||||
this.deleteRule = function (rule) {
|
||||
var param = {
|
||||
id: rule.id,
|
||||
app: rule.app
|
||||
};
|
||||
|
||||
return $http({
|
||||
url: '/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;
|
||||
};
|
||||
}]);
|
||||
|
|
@ -1,23 +1,25 @@
|
|||
var app = angular.module('sentinelDashboardApp');
|
||||
|
||||
app.service('MachineService', ['$http', '$httpParamSerializerJQLike', function ($http, $httpParamSerializerJQLike) {
|
||||
this.getAppMachines = function (app) {
|
||||
return $http({
|
||||
url: 'app/' + app + '/machines.json',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
this.removeAppMachine = function (app, ip, port) {
|
||||
return $http({
|
||||
url: 'app/' + app + '/machine/remove.json',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-type": 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
data: $httpParamSerializerJQLike({
|
||||
ip: ip,
|
||||
port: port
|
||||
})
|
||||
});
|
||||
};
|
||||
}]);
|
||||
app.service('MachineService', ['$http', '$httpParamSerializerJQLike',
|
||||
function ($http, $httpParamSerializerJQLike) {
|
||||
this.getAppMachines = function (app) {
|
||||
return $http({
|
||||
url: 'app/' + app + '/machines.json',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
this.removeAppMachine = function (app, ip, port) {
|
||||
return $http({
|
||||
url: 'app/' + app + '/machine/remove.json',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
data: $httpParamSerializerJQLike({
|
||||
ip: ip,
|
||||
port: port
|
||||
})
|
||||
});
|
||||
};
|
||||
}]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<div>
|
||||
<span class="brand" style="font-weight:bold;">{{gatewayApiDialog.title}}</span>
|
||||
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<form role="form" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">API名称</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" ng-if="gatewayApiDialog.type == 'edit'" class="form-control" placeholder="请输入" ng-model='currentApi.apiName'
|
||||
disabled="" />
|
||||
<input type="text" ng-if="gatewayApiDialog.type == 'add'" class="form-control highlight-border" placeholder="请输入" ng-model='currentApi.apiName' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-repeat="predicateItem in currentApi.predicateItems track by $index">
|
||||
<label class="col-sm-2 control-label">匹配模式</label>
|
||||
<div class="col-sm-4 control-label">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" value="0" checked ng-model="predicateItem.matchStrategy" title="精确" /> 精确
|
||||
<input type="radio" value="1" ng-model="predicateItem.matchStrategy" title="前缀" /> 前缀 
|
||||
<input type="radio" value="2" ng-model="predicateItem.matchStrategy" title="正则" /> 正则 
|
||||
</div>
|
||||
</div>
|
||||
<label class="col-sm-2 control-label">匹配串</label>
|
||||
<div class="col-sm-3 control-label">
|
||||
<input type='text' ng-model="predicateItem.pattern" class="form-control highlight-border" placeholder="请输入" />
|
||||
</div>
|
||||
<div class="col-sm-1 control-label" align="center">
|
||||
<button class="btn btn-default-inverse" ng-click="removeMatchPattern($index)" align="center" ng-if="currentApi.predicateItems.length>1">X</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-11 control-label" style="text-align: center;">
|
||||
<button class="btn btn-default-inverse" ng-click="addNewMatchPattern()" align="center">
|
||||
<i class="fa fa-plus"></i>新增匹配规则
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator"></div>
|
||||
<div clss="row" style="margin-top: 20px;">
|
||||
<button class="btn btn-outline-danger" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="closeThisDialog()">取消</button>
|
||||
<button class="btn btn-outline-success" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="saveApi()">{{gatewayApiDialog.confirmBtnText}}</button>
|
||||
<button ng-if="gatewayApiDialog.saveAndContinueBtnText" class="btn btn-default" style="float:right; height: 30px;font-size: 12px;"
|
||||
ng-click="saveApiAndContinue()">{{gatewayApiDialog.saveAndContinueBtnText}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<div>
|
||||
<span class="brand" style="font-weight:bold;">{{gatewayFlowRuleDialog.title}}</span>
|
||||
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<form role="form" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">API类型</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" ng-if="gatewayFlowRuleDialog.type == 'edit'" value="0" checked ng-model='currentRule.resourceMode'
|
||||
disabled="" title="Route ID" /><span ng-if="gatewayFlowRuleDialog.type == 'edit'"> Route ID </span>
|
||||
<input type="radio" ng-if="gatewayFlowRuleDialog.type == 'add'" value="0" checked ng-model='currentRule.resourceMode'
|
||||
title="Route ID" ng-click="useRouteID()"/><span ng-if="gatewayFlowRuleDialog.type == 'add'"> Route ID </span>
|
||||
<input type="radio" ng-if="gatewayFlowRuleDialog.type == 'edit'" value="1" ng-model='currentRule.resourceMode'
|
||||
disabled="" title="API分组" /><span ng-if="gatewayFlowRuleDialog.type == 'edit'"> API分组  </span>
|
||||
<input type="radio" ng-if="gatewayFlowRuleDialog.type == 'add'" value="1" ng-model='currentRule.resourceMode'
|
||||
title="API分组" ng-click="useCustormAPI()"/><span ng-if="gatewayFlowRuleDialog.type == 'add'"> API分组  </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">API名称</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" ng-if="currentRule.resourceMode == 0 && gatewayFlowRuleDialog.type == 'edit'" class="form-control" placeholder="请输入Route ID" ng-model='currentRule.resource'
|
||||
disabled="" />
|
||||
<input type="text" ng-if="currentRule.resourceMode == 0 && gatewayFlowRuleDialog.type == 'add'" class="form-control highlight-border" placeholder="请输入Route ID" ng-model='currentRule.resource' />
|
||||
|
||||
<select ng-if="currentRule.resourceMode == 1 && gatewayFlowRuleDialog.type == 'edit'" ng-model="currentRule.resource" ng-init="selectedName = currentRule.resource"
|
||||
disabled="" ng-options="name for name in apiNames" class="form-control">
|
||||
</select>
|
||||
<select ng-if="currentRule.resourceMode == 1 && gatewayFlowRuleDialog.type == 'add'" ng-model="currentRule.resource" ng-init="currentRule.resource"
|
||||
ng-options="name for name in apiNames" class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">针对请求属性</label>
|
||||
<div class="col-sm-2">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" ng-if="currentRule.paramItem != null" checked ng-click="notUseParamItem()" />
|
||||
<input type="checkbox" ng-if="currentRule.paramItem == null" ng-click="useParamItem()" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.paramItem != null">
|
||||
<label class="col-sm-2 control-label">参数属性</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" name="parseStrategy" value="0" checked ng-model='currentRule.paramItem.parseStrategy' title="Client IP" /> Client IP
|
||||
<input type="radio" name="parseStrategy" value="1" ng-model='currentRule.paramItem.parseStrategy' title="Remote Host" /> Remote Host
|
||||
<input type="radio" name="parseStrategy" value="2" ng-model='currentRule.paramItem.parseStrategy' title="Header" /> Header
|
||||
<input type="radio" name="parseStrategy" value="3" ng-model='currentRule.paramItem.parseStrategy' title="URL参数" /> URL参数
|
||||
<input type="radio" name="parseStrategy" value="4" ng-model='currentRule.paramItem.parseStrategy' title="Cookie" /> Cookie
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.paramItem != null && (currentRule.paramItem.parseStrategy == 2 || currentRule.paramItem.parseStrategy == 3 || currentRule.paramItem.parseStrategy == 4)">
|
||||
<label class="col-sm-2 control-label">
|
||||
<span ng-if="currentRule.paramItem.parseStrategy==2">Header名称</span>
|
||||
<span ng-if="currentRule.paramItem.parseStrategy==3">URL参数名称</span>
|
||||
<span ng-if="currentRule.paramItem.parseStrategy==4">Cookie名称</span>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" name="fieldName" class="form-control highlight-border" placeholder="请输入" ng-model='currentRule.paramItem.fieldName' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.paramItem != null && (currentRule.paramItem.parseStrategy == 2 || currentRule.paramItem.parseStrategy == 3 || currentRule.paramItem.parseStrategy == 4)">
|
||||
<label class="col-sm-2 control-label">属性值匹配</label>
|
||||
<div class="col-sm-2">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" ng-if="currentRule.paramItem.pattern != null" checked ng-click="notUseParamItemVal()"/>
|
||||
<input type="checkbox" ng-if="currentRule.paramItem.pattern == null" ng-click="useParamItemVal()"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.paramItem.pattern != null">
|
||||
<label class="col-sm-2 control-label">匹配模式</label>
|
||||
<div class="col-sm-4 control-label">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" value="0" checked ng-model="currentRule.paramItem.matchStrategy" title="精确" /> 精确
|
||||
<input type="radio" value="3" ng-model="currentRule.paramItem.matchStrategy" title="子串" /> 子串
|
||||
<input type="radio" value="2" ng-model="currentRule.paramItem.matchStrategy" title="正则" /> 正则
|
||||
</div>
|
||||
</div>
|
||||
<label class="col-sm-2 control-label">匹配串</label>
|
||||
<div class="col-sm-3 control-label">
|
||||
<input type='text' ng-model="currentRule.paramItem.pattern" class="form-control highlight-border" placeholder="匹配串" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">阈值类型</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" name="grade" value="1" checked ng-model="currentRule.grade" title="QPS" /> QPS
|
||||
<input type="radio" name="grade" value="0" ng-model="currentRule.grade" title="线程数" /> 线程数
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label class="col-sm-2 control-label">
|
||||
<span ng-if="currentRule.grade==1">QPS阈值</span>
|
||||
<span ng-if="currentRule.grade==0">线程数</span>
|
||||
</label>
|
||||
<div class="col-sm-3">
|
||||
<input type='number' min="0" class="form-control highlight-border" ng-model='currentRule.count' placeholder="阈值" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.grade==1">
|
||||
<div>
|
||||
<label class="col-sm-2 control-label">间隔</label>
|
||||
<div class="col-sm-3">
|
||||
<input type='number' id="txtInterval" min="1" class="form-control highlight-border" ng-model='currentRule.interval' placeholder="统计窗口时间长度" />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<select ng-model="currentRule.intervalUnit" ng-init="currentRule.intervalUnit"
|
||||
ng-options="intervalUnit.val as intervalUnit.desc for intervalUnit in intervalUnits" class="form-control" ng-click="changeIntervalUnit()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.grade==1">
|
||||
<label class="col-sm-2 control-label">流控方式</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-control highlight-border" align="center">
|
||||
<input type="radio" name="controlBehavior" value="0" checked ng-model='currentRule.controlBehavior' title="快速失败" /> 快速失败
|
||||
<input type="radio" name="controlBehavior" value="2" ng-model='currentRule.controlBehavior' title="匀速排队" /> 匀速排队 
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.grade==1 && currentRule.controlBehavior==0">
|
||||
<div>
|
||||
<label class="col-sm-2 control-label">Burst size</label>
|
||||
<div class="col-sm-3">
|
||||
<input type='number' min="0" class="form-control highlight-border" ng-model='currentRule.burst' placeholder="突发请求额外允许数" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="currentRule.grade==1 && currentRule.controlBehavior==2">
|
||||
<div>
|
||||
<label class="col-sm-2 control-label">超时时间</label>
|
||||
<div class="col-sm-3">
|
||||
<input type='number' min="0" class="form-control highlight-border" ng-model='currentRule.maxQueueingTimeoutMs' placeholder="排队等待时间(ms)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator"></div>
|
||||
<div clss="row" style="margin-top: 20px;">
|
||||
<button class="btn btn-outline-danger" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="closeThisDialog()">取消</button>
|
||||
<button class="btn btn-outline-success" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="saveRule()">{{gatewayFlowRuleDialog.confirmBtnText}}</button>
|
||||
<button ng-if="gatewayFlowRuleDialog.saveAndContinueBtnText" class="btn btn-default" style="float:right; height: 30px;font-size: 12px;"
|
||||
ng-click="saveRuleAndContinue()">{{gatewayFlowRuleDialog.saveAndContinueBtnText}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<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-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewApi()">
|
||||
<i class="fa fa-plus"></i> 新增API</button>
|
||||
</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-default" type="button" ng-click="editApi(api)" style="font-size: 12px; height:25px;">编辑</button>
|
||||
<button class="btn btn-xs btn-default" 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 -->
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<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-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
|
||||
<i class="fa fa-plus"></i> 新增流控规则</button>
|
||||
</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-default" type="button" ng-click="editRule(rule)" style="font-size: 12px; height:25px;">编辑</button>
|
||||
<button class="btn btn-xs btn-default" 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 -->
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<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>
|
||||
|
||||
<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="queryIdentities()">刷新</button>
|
||||
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey" ng-change="searchChange(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 rz-table id="identities" class="table" style="border-left: none; border-right:none;margin-top: 10px;">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr style="background: #F3F5F7;">
|
||||
<td style="width: 34%;">
|
||||
资源名
|
||||
</td>
|
||||
<td style="width: 6%;">
|
||||
资源类型
|
||||
</td>
|
||||
<td style="width: 7%;">通过QPS</td>
|
||||
<td style="width: 7%;">拒绝QPS</td>
|
||||
<td style="width: 5%;">线程数</td>
|
||||
<td style="width: 6%;">平均RT</td>
|
||||
<td style="width: 6%;">分钟通过</td>
|
||||
<td style="width: 6%;">分钟拒绝</td>
|
||||
<td style="width: 23%">操作</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="resource in identities | itemsPerPage: pageSize" current-page="currentPage" pagination-id="entriesPagination"
|
||||
data-tt-id="{{resource.ttId}}" data-tt-parent-id="{{resource.parentTtId}}" data-tt-visible="{{resource.visible}}">
|
||||
<td style="white-space: normal; text-align: left;">
|
||||
<span style="word-wrap:break-word;word-break:break-all;">{{resource.resource}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="apiNames.indexOf(resource.resource) == -1">Route ID</span>
|
||||
<span ng-if="apiNames.indexOf(resource.resource) != -1">自定义API</span>
|
||||
</td>
|
||||
<td>{{resource.passQps}}</td>
|
||||
<td>{{resource.blockQps}}</td>
|
||||
<td>{{resource.threadNum}}</td>
|
||||
<td>{{resource.averageRt}}</td>
|
||||
<td>{{resource.oneMinutePass}}</td>
|
||||
<td ng-if="$index==pageSize-1 || (currentPage>=identities.length/pageSize && $index==identities.length%pageSize-1)" ng-init="initTreeTable()">
|
||||
{{resource.oneMinuteBlock}}</td>
|
||||
<td ng-if="!($index==pageSize-1 || (currentPage>=identities.length/pageSize && $index==identities.length%pageSize-1))"> {{resource.oneMinuteBlock}}</td>
|
||||
<td>
|
||||
<div class="control-group">
|
||||
<button class="btn btn-xs btn-default" type="button" ng-click="addNewGatewayFlowRule(resource.resource)" style="font-size: 12px; height:25px;">
|
||||
<i class="fa fa-plus"></i> 流控</button>
|
||||
<button class="btn btn-xs btn-default" type="button" ng-click="addNewDegradeRule(resource.resource)" style="font-size: 12px; height:25px;">
|
||||
<i class="fa fa-plus"></i> 降级</button>
|
||||
</div>
|
||||
</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">
|
||||
<span>共 {{totalCount}} 条记录, </span>
|
||||
<span>
|
||||
每页
|
||||
<input class="form-control" ng-model="pageSize"> 条记录
|
||||
</span>
|
||||
<!--<span>第 {{currentPage}} / {{totalPage}} 页</span>-->
|
||||
</div>
|
||||
<!-- .tools -->
|
||||
</div>
|
||||
<!-- pagination-footer -->
|
||||
</div>
|
||||
<!-- .card -->
|
||||
</div>
|
||||
<!-- .col-md-12 -->
|
||||
</div>
|
||||
<!-- -->
|
||||
</div>
|
||||
<!-- .container-fluid -->
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -55,6 +55,8 @@ const JS_APP = [
|
|||
'app/scripts/services/param_flow_service.js',
|
||||
'app/scripts/services/authority_service.js',
|
||||
'app/scripts/services/cluster_state_service.js',
|
||||
'app/scripts/services/gateway/api_service.js',
|
||||
'app/scripts/services/gateway/flow_service.js',
|
||||
];
|
||||
|
||||
gulp.task('lib', function () {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* 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.controller.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl;
|
||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link GatewayApiController}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebMvcTest(GatewayApiController.class)
|
||||
@Import({FakeAuthServiceImpl.class, InMemApiDefinitionStore.class, AppManagement.class, SimpleMachineDiscovery.class})
|
||||
public class GatewayApiControllerTest {
|
||||
|
||||
private static final String TEST_APP = "test_app";
|
||||
|
||||
private static final String TEST_IP = "localhost";
|
||||
|
||||
private static final Integer TEST_PORT = 8719;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private InMemApiDefinitionStore repository;
|
||||
|
||||
@MockBean
|
||||
private SentinelApiClient sentinelApiClient;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
repository.clearAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryApis() throws Exception {
|
||||
String path = "/gateway/api/list.json";
|
||||
|
||||
List<ApiDefinitionEntity> entities = new ArrayList<>();
|
||||
|
||||
// Mock two entities
|
||||
ApiDefinitionEntity entity = new ApiDefinitionEntity();
|
||||
entity.setId(1L);
|
||||
entity.setApp(TEST_APP);
|
||||
entity.setIp(TEST_IP);
|
||||
entity.setPort(TEST_PORT);
|
||||
entity.setApiName("foo");
|
||||
Date date = new Date();
|
||||
entity.setGmtCreate(date);
|
||||
entity.setGmtModified(date);
|
||||
|
||||
Set<ApiPredicateItemEntity> itemEntities = new LinkedHashSet<>();
|
||||
entity.setPredicateItems(itemEntities);
|
||||
ApiPredicateItemEntity itemEntity = new ApiPredicateItemEntity();
|
||||
itemEntity.setPattern("/aaa");
|
||||
itemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
|
||||
|
||||
itemEntities.add(itemEntity);
|
||||
entities.add(entity);
|
||||
|
||||
ApiDefinitionEntity entity2 = new ApiDefinitionEntity();
|
||||
entity2.setId(2L);
|
||||
entity2.setApp(TEST_APP);
|
||||
entity2.setIp(TEST_IP);
|
||||
entity2.setPort(TEST_PORT);
|
||||
entity2.setApiName("biz");
|
||||
entity.setGmtCreate(date);
|
||||
entity.setGmtModified(date);
|
||||
|
||||
Set<ApiPredicateItemEntity> itemEntities2 = new LinkedHashSet<>();
|
||||
entity2.setPredicateItems(itemEntities2);
|
||||
ApiPredicateItemEntity itemEntity2 = new ApiPredicateItemEntity();
|
||||
itemEntity2.setPattern("/bbb");
|
||||
itemEntity2.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX);
|
||||
|
||||
itemEntities2.add(itemEntity2);
|
||||
entities.add(entity2);
|
||||
|
||||
CompletableFuture<List<ApiDefinitionEntity>> completableFuture = mock(CompletableFuture.class);
|
||||
given(completableFuture.get()).willReturn(entities);
|
||||
given(sentinelApiClient.fetchApis(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path);
|
||||
requestBuilder.param("app", TEST_APP);
|
||||
requestBuilder.param("ip", TEST_IP);
|
||||
requestBuilder.param("port", String.valueOf(TEST_PORT));
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the fetchApis method has been called
|
||||
verify(sentinelApiClient).fetchApis(TEST_APP, TEST_IP, TEST_PORT);
|
||||
|
||||
// Verify if two same entities are got
|
||||
Result<List<ApiDefinitionEntity>> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<List<ApiDefinitionEntity>>>(){});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
List<ApiDefinitionEntity> data = result.getData();
|
||||
assertEquals(2, data.size());
|
||||
assertEquals(entities, data);
|
||||
|
||||
// Verify the entities are add into memory repository
|
||||
List<ApiDefinitionEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(2, entitiesInMem.size());
|
||||
assertEquals(entities, entitiesInMem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddApi() throws Exception {
|
||||
String path = "/gateway/api/new.json";
|
||||
|
||||
AddApiReqVo reqVo = new AddApiReqVo();
|
||||
reqVo.setApp(TEST_APP);
|
||||
reqVo.setIp(TEST_IP);
|
||||
reqVo.setPort(TEST_PORT);
|
||||
|
||||
reqVo.setApiName("customized_api");
|
||||
|
||||
List<ApiPredicateItemVo> itemVos = new ArrayList<>();
|
||||
ApiPredicateItemVo itemVo = new ApiPredicateItemVo();
|
||||
itemVo.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
|
||||
itemVo.setPattern("/product");
|
||||
itemVos.add(itemVo);
|
||||
reqVo.setPredicateItems(itemVos);
|
||||
|
||||
given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyApis method has been called
|
||||
verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
Result<ApiDefinitionEntity> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<ApiDefinitionEntity>>() {});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
// Verify the result
|
||||
ApiDefinitionEntity entity = result.getData();
|
||||
assertNotNull(entity);
|
||||
assertEquals(TEST_APP, entity.getApp());
|
||||
assertEquals(TEST_IP, entity.getIp());
|
||||
assertEquals(TEST_PORT, entity.getPort());
|
||||
assertEquals("customized_api", entity.getApiName());
|
||||
assertNotNull(entity.getId());
|
||||
assertNotNull(entity.getGmtCreate());
|
||||
assertNotNull(entity.getGmtModified());
|
||||
|
||||
Set<ApiPredicateItemEntity> predicateItemEntities = entity.getPredicateItems();
|
||||
assertEquals(1, predicateItemEntities.size());
|
||||
ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next();
|
||||
assertEquals(URL_MATCH_STRATEGY_EXACT, predicateItemEntity.getMatchStrategy().intValue());
|
||||
assertEquals("/product", predicateItemEntity.getPattern());
|
||||
|
||||
// Verify the entity which is add in memory repository
|
||||
List<ApiDefinitionEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(1, entitiesInMem.size());
|
||||
assertEquals(entity, entitiesInMem.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateApi() throws Exception {
|
||||
String path = "/gateway/api/save.json";
|
||||
|
||||
// Add one entity to memory repository for update
|
||||
ApiDefinitionEntity addEntity = new ApiDefinitionEntity();
|
||||
addEntity.setApp(TEST_APP);
|
||||
addEntity.setIp(TEST_IP);
|
||||
addEntity.setPort(TEST_PORT);
|
||||
addEntity.setApiName("bbb");
|
||||
Date date = new Date();
|
||||
// To make the gmtModified different when do update
|
||||
date = DateUtils.addSeconds(date, -1);
|
||||
addEntity.setGmtCreate(date);
|
||||
addEntity.setGmtModified(date);
|
||||
Set<ApiPredicateItemEntity> addRedicateItemEntities = new HashSet<>();
|
||||
addEntity.setPredicateItems(addRedicateItemEntities);
|
||||
ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity();
|
||||
addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
|
||||
addPredicateItemEntity.setPattern("/order");
|
||||
addEntity = repository.save(addEntity);
|
||||
|
||||
UpdateApiReqVo reqVo = new UpdateApiReqVo();
|
||||
reqVo.setId(addEntity.getId());
|
||||
reqVo.setApp(TEST_APP);
|
||||
List<ApiPredicateItemVo> itemVos = new ArrayList<>();
|
||||
ApiPredicateItemVo itemVo = new ApiPredicateItemVo();
|
||||
itemVo.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX);
|
||||
itemVo.setPattern("/my_order");
|
||||
itemVos.add(itemVo);
|
||||
reqVo.setPredicateItems(itemVos);
|
||||
|
||||
given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyApis method has been called
|
||||
verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
Result<ApiDefinitionEntity> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<ApiDefinitionEntity>>() {});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
ApiDefinitionEntity entity = result.getData();
|
||||
assertNotNull(entity);
|
||||
assertEquals("bbb", entity.getApiName());
|
||||
assertEquals(date, entity.getGmtCreate());
|
||||
// To make sure gmtModified has been set and it's different from gmtCreate
|
||||
assertNotNull(entity.getGmtModified());
|
||||
assertNotEquals(entity.getGmtCreate(), entity.getGmtModified());
|
||||
|
||||
Set<ApiPredicateItemEntity> predicateItemEntities = entity.getPredicateItems();
|
||||
assertEquals(1, predicateItemEntities.size());
|
||||
ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next();
|
||||
assertEquals(URL_MATCH_STRATEGY_PREFIX, predicateItemEntity.getMatchStrategy().intValue());
|
||||
assertEquals("/my_order", predicateItemEntity.getPattern());
|
||||
|
||||
// Verify the entity which is update in memory repository
|
||||
List<ApiDefinitionEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(1, entitiesInMem.size());
|
||||
assertEquals(entity, entitiesInMem.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteApi() throws Exception {
|
||||
String path = "/gateway/api/delete.json";
|
||||
|
||||
// Add one entity into memory repository for delete
|
||||
ApiDefinitionEntity addEntity = new ApiDefinitionEntity();
|
||||
addEntity.setApp(TEST_APP);
|
||||
addEntity.setIp(TEST_IP);
|
||||
addEntity.setPort(TEST_PORT);
|
||||
addEntity.setApiName("ccc");
|
||||
Date date = new Date();
|
||||
addEntity.setGmtCreate(date);
|
||||
addEntity.setGmtModified(date);
|
||||
Set<ApiPredicateItemEntity> addRedicateItemEntities = new HashSet<>();
|
||||
addEntity.setPredicateItems(addRedicateItemEntities);
|
||||
ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity();
|
||||
addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
|
||||
addPredicateItemEntity.setPattern("/user/add");
|
||||
addEntity = repository.save(addEntity);
|
||||
|
||||
given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.param("id", String.valueOf(addEntity.getId()));
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyApis method has been called
|
||||
verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
// Verify the result
|
||||
Result<Long> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<Long>>() {});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
assertEquals(addEntity.getId(), result.getData());
|
||||
|
||||
// Now no entities in memory
|
||||
List<ApiDefinitionEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(0, entitiesInMem.size());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* 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.controller.gateway;
|
||||
|
||||
import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl;
|
||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
||||
import com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo;
|
||||
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*;
|
||||
import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.BDDMockito.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link GatewayFlowRuleController}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebMvcTest(GatewayFlowRuleController.class)
|
||||
@Import({FakeAuthServiceImpl.class, InMemGatewayFlowRuleStore.class, AppManagement.class, SimpleMachineDiscovery.class})
|
||||
public class GatewayFlowRuleControllerTest {
|
||||
|
||||
private static final String TEST_APP = "test_app";
|
||||
|
||||
private static final String TEST_IP = "localhost";
|
||||
|
||||
private static final Integer TEST_PORT = 8719;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private InMemGatewayFlowRuleStore repository;
|
||||
|
||||
@MockBean
|
||||
private SentinelApiClient sentinelApiClient;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
repository.clearAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryFlowRules() throws Exception {
|
||||
String path = "/gateway/flow/list.json";
|
||||
|
||||
List<GatewayFlowRuleEntity> entities = new ArrayList<>();
|
||||
|
||||
// Mock two entities
|
||||
GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
|
||||
entity.setId(1L);
|
||||
entity.setApp(TEST_APP);
|
||||
entity.setIp(TEST_IP);
|
||||
entity.setPort(TEST_PORT);
|
||||
entity.setResource("httpbin_route");
|
||||
entity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
|
||||
entity.setGrade(FLOW_GRADE_QPS);
|
||||
entity.setCount(5D);
|
||||
entity.setInterval(30L);
|
||||
entity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
|
||||
entity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
|
||||
entity.setBurst(0);
|
||||
entity.setMaxQueueingTimeoutMs(0);
|
||||
|
||||
GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
|
||||
entity.setParamItem(itemEntity);
|
||||
itemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
|
||||
entities.add(entity);
|
||||
|
||||
GatewayFlowRuleEntity entity2 = new GatewayFlowRuleEntity();
|
||||
entity2.setId(2L);
|
||||
entity2.setApp(TEST_APP);
|
||||
entity2.setIp(TEST_IP);
|
||||
entity2.setPort(TEST_PORT);
|
||||
entity2.setResource("some_customized_api");
|
||||
entity2.setResourceMode(RESOURCE_MODE_CUSTOM_API_NAME);
|
||||
entity2.setCount(30D);
|
||||
entity2.setInterval(2L);
|
||||
entity2.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE);
|
||||
entity2.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
|
||||
entity2.setBurst(0);
|
||||
entity2.setMaxQueueingTimeoutMs(0);
|
||||
|
||||
GatewayParamFlowItemEntity itemEntity2 = new GatewayParamFlowItemEntity();
|
||||
entity2.setParamItem(itemEntity2);
|
||||
itemEntity2.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
|
||||
entities.add(entity2);
|
||||
|
||||
CompletableFuture<List<GatewayFlowRuleEntity>> completableFuture = mock(CompletableFuture.class);
|
||||
given(completableFuture.get()).willReturn(entities);
|
||||
given(sentinelApiClient.fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path);
|
||||
requestBuilder.param("app", TEST_APP);
|
||||
requestBuilder.param("ip", TEST_IP);
|
||||
requestBuilder.param("port", String.valueOf(TEST_PORT));
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the fetchGatewayFlowRules method has been called
|
||||
verify(sentinelApiClient).fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT);
|
||||
|
||||
// Verify if two same entities are got
|
||||
Result<List<GatewayFlowRuleEntity>> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<List<GatewayFlowRuleEntity>>>(){});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
List<GatewayFlowRuleEntity> data = result.getData();
|
||||
assertEquals(2, data.size());
|
||||
assertEquals(entities, data);
|
||||
|
||||
// Verify the entities are add into memory repository
|
||||
List<GatewayFlowRuleEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(2, entitiesInMem.size());
|
||||
assertEquals(entities, entitiesInMem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddFlowRule() throws Exception {
|
||||
String path = "/gateway/flow/new.json";
|
||||
|
||||
AddFlowRuleReqVo reqVo = new AddFlowRuleReqVo();
|
||||
reqVo.setApp(TEST_APP);
|
||||
reqVo.setIp(TEST_IP);
|
||||
reqVo.setPort(TEST_PORT);
|
||||
|
||||
reqVo.setResourceMode(RESOURCE_MODE_ROUTE_ID);
|
||||
reqVo.setResource("httpbin_route");
|
||||
|
||||
reqVo.setGrade(FLOW_GRADE_QPS);
|
||||
reqVo.setCount(5D);
|
||||
reqVo.setInterval(30L);
|
||||
reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
|
||||
reqVo.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
|
||||
reqVo.setBurst(0);
|
||||
reqVo.setMaxQueueingTimeoutMs(0);
|
||||
|
||||
given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyGatewayFlowRules method has been called
|
||||
verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
Result<GatewayFlowRuleEntity> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<GatewayFlowRuleEntity>>() {});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
// Verify the result
|
||||
GatewayFlowRuleEntity entity = result.getData();
|
||||
assertNotNull(entity);
|
||||
assertEquals(TEST_APP, entity.getApp());
|
||||
assertEquals(TEST_IP, entity.getIp());
|
||||
assertEquals(TEST_PORT, entity.getPort());
|
||||
assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue());
|
||||
assertEquals("httpbin_route", entity.getResource());
|
||||
assertNotNull(entity.getId());
|
||||
assertNotNull(entity.getGmtCreate());
|
||||
assertNotNull(entity.getGmtModified());
|
||||
|
||||
// Verify the entity which is add in memory repository
|
||||
List<GatewayFlowRuleEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(1, entitiesInMem.size());
|
||||
assertEquals(entity, entitiesInMem.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateFlowRule() throws Exception {
|
||||
String path = "/gateway/flow/save.json";
|
||||
|
||||
// Add one entity into memory repository for update
|
||||
GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity();
|
||||
addEntity.setId(1L);
|
||||
addEntity.setApp(TEST_APP);
|
||||
addEntity.setIp(TEST_IP);
|
||||
addEntity.setPort(TEST_PORT);
|
||||
addEntity.setResource("httpbin_route");
|
||||
addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
|
||||
addEntity.setGrade(FLOW_GRADE_QPS);
|
||||
addEntity.setCount(5D);
|
||||
addEntity.setInterval(30L);
|
||||
addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
|
||||
addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
|
||||
addEntity.setBurst(0);
|
||||
addEntity.setMaxQueueingTimeoutMs(0);
|
||||
Date date = new Date();
|
||||
// To make the gmtModified different when do update
|
||||
date = DateUtils.addSeconds(date, -1);
|
||||
addEntity.setGmtCreate(date);
|
||||
addEntity.setGmtModified(date);
|
||||
|
||||
GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity();
|
||||
addEntity.setParamItem(addItemEntity);
|
||||
addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
|
||||
|
||||
repository.save(addEntity);
|
||||
|
||||
UpdateFlowRuleReqVo reqVo = new UpdateFlowRuleReqVo();
|
||||
reqVo.setId(addEntity.getId());
|
||||
reqVo.setApp(TEST_APP);
|
||||
reqVo.setGrade(FLOW_GRADE_QPS);
|
||||
reqVo.setCount(6D);
|
||||
reqVo.setInterval(2L);
|
||||
reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE);
|
||||
reqVo.setControlBehavior(CONTROL_BEHAVIOR_RATE_LIMITER);
|
||||
reqVo.setMaxQueueingTimeoutMs(500);
|
||||
|
||||
GatewayParamFlowItemVo itemVo = new GatewayParamFlowItemVo();
|
||||
reqVo.setParamItem(itemVo);
|
||||
itemVo.setParseStrategy(PARAM_PARSE_STRATEGY_URL_PARAM);
|
||||
itemVo.setFieldName("pa");
|
||||
|
||||
given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
||||
.andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyGatewayFlowRules method has been called
|
||||
verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
Result<GatewayFlowRuleEntity> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<GatewayFlowRuleEntity>>() {
|
||||
});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
GatewayFlowRuleEntity entity = result.getData();
|
||||
assertNotNull(entity);
|
||||
assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue());
|
||||
assertEquals("httpbin_route", entity.getResource());
|
||||
assertEquals(6D, entity.getCount().doubleValue(), 0);
|
||||
assertEquals(2L, entity.getInterval().longValue());
|
||||
assertEquals(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE, entity.getIntervalUnit().intValue());
|
||||
assertEquals(CONTROL_BEHAVIOR_RATE_LIMITER, entity.getControlBehavior().intValue());
|
||||
assertEquals(0, entity.getBurst().intValue());
|
||||
assertEquals(500, entity.getMaxQueueingTimeoutMs().intValue());
|
||||
assertEquals(date, entity.getGmtCreate());
|
||||
// To make sure gmtModified has been set and it's different from gmtCreate
|
||||
assertNotNull(entity.getGmtModified());
|
||||
assertNotEquals(entity.getGmtCreate(), entity.getGmtModified());
|
||||
|
||||
// Verify the entity which is update in memory repository
|
||||
GatewayParamFlowItemEntity itemEntity = entity.getParamItem();
|
||||
assertEquals(PARAM_PARSE_STRATEGY_URL_PARAM, itemEntity.getParseStrategy().intValue());
|
||||
assertEquals("pa", itemEntity.getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteFlowRule() throws Exception {
|
||||
String path = "/gateway/flow/delete.json";
|
||||
|
||||
// Add one entity into memory repository for delete
|
||||
GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity();
|
||||
addEntity.setId(1L);
|
||||
addEntity.setApp(TEST_APP);
|
||||
addEntity.setIp(TEST_IP);
|
||||
addEntity.setPort(TEST_PORT);
|
||||
addEntity.setResource("httpbin_route");
|
||||
addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
|
||||
addEntity.setGrade(FLOW_GRADE_QPS);
|
||||
addEntity.setCount(5D);
|
||||
addEntity.setInterval(30L);
|
||||
addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
|
||||
addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
|
||||
addEntity.setBurst(0);
|
||||
addEntity.setMaxQueueingTimeoutMs(0);
|
||||
Date date = new Date();
|
||||
date = DateUtils.addSeconds(date, -1);
|
||||
addEntity.setGmtCreate(date);
|
||||
addEntity.setGmtModified(date);
|
||||
|
||||
GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity();
|
||||
addEntity.setParamItem(addItemEntity);
|
||||
addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
|
||||
|
||||
repository.save(addEntity);
|
||||
|
||||
given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
|
||||
|
||||
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
|
||||
requestBuilder.param("id", String.valueOf(addEntity.getId()));
|
||||
|
||||
// Do controller logic
|
||||
MvcResult mvcResult = mockMvc.perform(requestBuilder)
|
||||
.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
|
||||
|
||||
// Verify the modifyGatewayFlowRules method has been called
|
||||
verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
|
||||
|
||||
// Verify the result
|
||||
Result<Long> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference<Result<Long>>() {});
|
||||
assertTrue(result.isSuccess());
|
||||
|
||||
assertEquals(addEntity.getId(), result.getData());
|
||||
|
||||
// Now no entities in memory
|
||||
List<GatewayFlowRuleEntity> entitiesInMem = repository.findAllByApp(TEST_APP);
|
||||
assertEquals(0, entitiesInMem.size());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue