Introducing new features. 支持 sentinel

This commit is contained in:
lengleng 2020-06-11 16:52:05 +08:00
parent bc32c9f280
commit 6f904f8649
274 changed files with 30590 additions and 91 deletions

View File

@ -52,21 +52,23 @@ pig-ui -- https://github.com/pigxcloud/pig-ui
pig
├── pig-auth -- oauth-server[3000]
├── pig-codegen -- graphical code generation[5002]
└── pig-common
├── pig-common-core -- tool core package
├── pig-common-datasource -- dynamic data source package
├── pig-common-log -- Log service package
├── pig-common-mybatis -- mybatis expand
├── pig-common-security -- security tools
└── pig-common-swagger -- api documentation
├── pig-common-swagger -- api documentation
└── pig-common-sentinel -- sentinel auto fallbak
├── pig-register -- nacos server[8848]
├── pig-gateway -- spring cloud gateway[9999]
├── pig-monitor -- spring boot admin[5001]
└── pig-upms
└── pig-upms-api -- user management system api
└── pig-upms-biz -- user management system biz[4000]
└── pig-visual
└── pig-monitor -- spring boot admin[5001]
├── pig-codegen -- graphical code generation[5002]
└── pig-sentinel-dashboard -- sentinel dashboard [5003]
```
#### Open source co-construction

View File

@ -50,21 +50,23 @@ pig-ui -- https://gitee.com/log4j/pig-ui
pig
├── pig-auth -- 授权服务提供[3000]
├── pig-codegen -- 图形化代码生成[5002]
└── pig-common -- 系统公共模块
├── pig-common-core -- 公共工具类核心包
├── pig-common-datasource -- 动态数据源包
├── pig-common-log -- 日志服务
├── pig-common-mybatis -- mybatis 扩展封装
├── pig-common-security -- 安全工具类
└── pig-common-swagger -- 接口文档
├── pig-common-swagger -- 接口文档
└── pig-common-sentinel -- sentinel 扩展封装
├── pig-register -- Nacos Server[8848]
├── pig-gateway -- Spring Cloud Gateway网关[9999]
├── pig-monitor -- Spring Boot Admin监控 [5001]
└── pig-upms -- 通用用户权限管理模块
└── pig-upms-api -- 通用用户权限管理系统公共api模块
└── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000]
└── pig-visual
└── pig-monitor -- 服务监控 [5001]
├── pig-codegen -- 图形化代码生成 [5002]
└── pig-sentinel-dashboard -- 流量高可用 [5003]
```
#### 开源共建

View File

@ -36,7 +36,7 @@ CREATE TABLE `config_info` (
-- Records of config_info
-- ----------------------------
BEGIN;
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 加解密根密码\njasypt:\n encryptor:\n password: pig #根密码\n\n# Spring 相关\nspring:\n redis:\n password:\n host: pig-redis\n\n# 暴露监控端点\nmanagement:\n endpoints:\n web:\n exposure:\n include: \'*\'\n\n# feign 配置\nfeign:\n hystrix:\n enabled: true\n okhttp:\n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: true\n response:\n enabled: true\n\n# hystrix 配置\nhystrix:\n command:\n default:\n execution:\n isolation:\n strategy: SEMAPHORE\n thread:\n timeoutInMilliseconds: 60000\n shareSecurityContext: true\n\n#请求处理的超时时间\nribbon:\n ReadTimeout: 10000\n ConnectTimeout: 10000\n\n# mybaits-plus配置\nmybatis-plus:\n mapper-locations: classpath:/mapper/*Mapper.xml\n global-config:\n banner: false\n db-config:\n id-type: auto\n table-underline: true\n logic-delete-value: 1\n logic-not-delete-value: 0\n configuration:\n map-underscore-to-camel-case: true\n\n# spring security 配置\nsecurity:\n oauth2:\n resource:\n loadBalanced: true\n token-info-uri: http://pig-auth/oauth/check_token\n # 通用放行URL服务个性化请在对应配置文件覆盖\n ignore:\n urls:\n - /v2/api-docs\n - /actuator/**\n# swagger 配置\nswagger:\n title: Pig Swagger API\n license: Powered By pig4cloud\n licenseUrl: https://pig4cloud.com\n terms-of-service-url: https://pig4cloud.com\n contact:\n email: wangiegie@gmail.com\n url: https://pig4cloud.com\n authorization:\n name: pig4cloud OAuth\n auth-regex: ^.*$\n authorization-scope-list:\n - scope: server\n description: server all\n token-url-list:\n - http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}/auth/oauth/token', '08678a260d44e4ca86b23829076cec98', '2019-11-29 16:31:20', '2020-03-14 16:27:14', NULL, '172.17.0.125', '', '', '通用配置', 'null', 'null', 'yaml', 'null');
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 加解密根密码\njasypt:\n encryptor:\n password: pig #根密码\n\n# Spring 相关\nspring:\n redis:\n password:\n host: pig-redis\n cloud:\n sentinel:\n eager: true\n transport:\n dashboard: pig-sentinel:5003\n\n# 暴露监控端点\nmanagement:\n endpoints:\n web:\n exposure:\n include: \'*\'\n\n# feign 配置\nfeign:\n sentinel:\n enabled: true\n okhttp:\n enabled: true\n httpclient:\n enabled: false\n client:\n config:\n default:\n connectTimeout: 10000\n readTimeout: 10000\n compression:\n request:\n enabled: true\n response:\n enabled: true\n\n#请求处理的超时时间\nribbon:\n ReadTimeout: 10000\n ConnectTimeout: 10000\n\n# mybaits-plus配置\nmybatis-plus:\n mapper-locations: classpath:/mapper/*Mapper.xml\n global-config:\n banner: false\n db-config:\n id-type: auto\n table-underline: true\n logic-delete-value: 1\n logic-not-delete-value: 0\n configuration:\n map-underscore-to-camel-case: true\n\n# spring security 配置\nsecurity:\n oauth2:\n resource:\n loadBalanced: true\n token-info-uri: http://pig-auth/oauth/check_token\n # 通用放行URL服务个性化请在对应配置文件覆盖\n ignore:\n urls:\n - /v2/api-docs\n - /actuator/**\n# swagger 配置\nswagger:\n title: Pig Swagger API\n license: Powered By pig4cloud\n licenseUrl: https://pig4cloud.com\n terms-of-service-url: https://pig4cloud.com\n contact:\n email: wangiegie@gmail.com\n url: https://pig4cloud.com\n authorization:\n name: pig4cloud OAuth\n auth-regex: ^.*$\n authorization-scope-list:\n - scope: server\n description: server all\n token-url-list:\n - http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}/auth/oauth/token', '841de89cde20a1a045f46001f9a89266', '2019-11-29 16:31:20', '2020-06-11 16:28:09', NULL, '127.0.0.1', '', '', '通用配置', 'null', 'null', 'yaml', 'null');
INSERT INTO `config_info` VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: root\n password: root\n url: jdbc:mysql://pig-mysql:3306/pig?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai\n freemarker:\n allow-request-override: false\n allow-session-override: false\n cache: true\n charset: UTF-8\n check-template-location: true\n content-type: text/html\n enabled: true\n expose-request-attributes: false\n expose-session-attributes: false\n expose-spring-macro-helpers: true\n prefer-file-system-access: true\n suffix: .ftl\n template-loader-path: classpath:/templates/', '58b1b48a2888f49e667864be32edf9c1', '2019-11-29 16:31:48', '2020-01-01 18:30:58', NULL, '127.0.0.1', '', '', '认证中心配置', 'null', 'null', 'yaml', 'null');
INSERT INTO `config_info` VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '## spring security 配置\nsecurity:\n oauth2:\n client:\n client-id: ENC(27v1agvAug87ANOVnbKdsw==)\n client-secret: ENC(VbnkopxrwgbFVKp+UxJ2pg==)\n scope: server\n\n# 数据源配置\nspring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: root\n password: root\n url: jdbc:mysql://pig-mysql:3306/pig_codegen?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai\n resources:\n static-locations: classpath:/static/,classpath:/views/\n\n# 直接放行URL\nignore:\n urls:\n - /v2/api-docs\n - /actuator/**\n', 'abc702838b34d11b46e96143ccd9f367', '2019-11-29 16:32:12', '2019-11-29 16:32:12', NULL, '127.0.0.1', '', '', '代码生成配置', NULL, NULL, 'yaml', NULL);
INSERT INTO `config_info` VALUES (4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n locator:\n enabled: true\n routes:\n # 认证中心\n - id: pig-auth\n uri: lb://pig-auth\n predicates:\n - Path=/auth/**\n filters:\n # 验证码处理\n - ValidateCodeGatewayFilter\n # 前端密码解密\n - PasswordDecoderFilter\n #UPMS 模块\n - id: pig-upms-biz\n uri: lb://pig-upms-biz\n predicates:\n - Path=/admin/**\n filters:\n # 限流配置\n - name: RequestRateLimiter\n args:\n key-resolver: \'#{@remoteAddrKeyResolver}\'\n redis-rate-limiter.replenishRate: 10\n redis-rate-limiter.burstCapacity: 20\n # 降级配置\n - name: Hystrix\n args:\n name: default\n fallbackUri: \'forward:/fallback\'\n # 代码生成模块\n - id: pig-codegen\n uri: lb://pig-codegen\n predicates:\n - Path=/gen/**\n\n\nsecurity:\n encode:\n # 前端密码密钥必须16位\n key: \'thanks,pig4cloud\'\n\n# 不校验验证码终端\nignore:\n clients:\n - test\n', '32ce953f48c958bb869a7e3e442a4a11', '2019-11-29 16:32:42', '2019-11-29 16:32:42', NULL, '127.0.0.1', '', '', '网关配置', NULL, NULL, 'yaml', NULL);

View File

@ -57,7 +57,7 @@ services:
pig-monitor:
build:
context: ./pig-monitor
context: ./pig-visual/pig-monitor
restart: always
ports:
- 5001:5001
@ -65,9 +65,18 @@ services:
hostname: pig-monitor
image: pig-monitor
pig-sentinel:
build:
context: ./pig-visual/pig-sentinel-dashboard
restart: always
image: pig-sentinel
container_name: pig-sentinel
ports:
- 5003:5003
pig-codegen:
build:
context: ./pig-codegen
context: ./pig-visual/pig-codegen
restart: always
container_name: pig-codegen
hostname: pig-codegen

View File

@ -42,6 +42,12 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-sentinel</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--upms api、model 模块-->
<dependency>
<groupId>com.pig4cloud</groupId>

View File

@ -24,7 +24,6 @@ import com.pig4cloud.pig.common.core.exception.CheckedException;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common</artifactId>
<version>2.7.8.snapshot</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>pig-common-sentinel</artifactId>
<description>sentinel服务降级熔断、限流组件</description>
<dependencies>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-core</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pig.common.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.pig4cloud.pig.common.sentinel.feign.PigSentinelFeign;
import com.pig4cloud.pig.common.sentinel.handle.PigUrlBlockHandler;
import com.pig4cloud.pig.common.sentinel.parser.PigHeaderRequestOriginParser;
import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* sentinel 配置
*
* @author lengleng
* @date 2020-02-12
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return PigSentinelFeign.builder();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new PigUrlBlockHandler();
}
@Bean
@ConditionalOnMissingBean
public RequestOriginParser requestOriginParser() {
return new PigHeaderRequestOriginParser();
}
}

View File

@ -0,0 +1,142 @@
package com.pig4cloud.pig.common.sentinel.feign;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import feign.hystrix.FallbackFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 支持自动降级注入
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
*
* @author lengleng
* @date 2020/6/9
*/
public final class PigSentinelFeign {
private PigSentinelFeign() {
}
public static PigSentinelFeign.Builder builder() {
return new PigSentinelFeign.Builder();
}
public static final class Builder extends Feign.Builder
implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
@Override
public Feign.Builder invocationHandlerFactory(
InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public PigSentinelFeign.Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
// using reflect get fallback and fallbackFactory properties from
// FeignClientFactoryBean because FeignClientFactoryBean is a package
// level class, we can not use it in our package
Object feignClientFactoryBean = PigSentinelFeign.Builder.this.applicationContext
.getBean("&" + target.type().getName());
Class fallback = (Class) getFieldValue(feignClientFactoryBean,
"fallback");
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
"fallbackFactory");
String beanName = (String) getFieldValue(feignClientFactoryBean,
"contextId");
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback,
target.type());
return new PigSentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(
beanName, "fallbackFactory", fallbackFactory,
FallbackFactory.class);
return new PigSentinelInvocationHandler(target, dispatch,
fallbackFactoryInstance);
}
return new PigSentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type,
Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name,
fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s",
type, fallbackType, name));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
private Object getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
field.setAccessible(true);
try {
return field.get(instance);
} catch (IllegalAccessException e) {
// ignore
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignContext.class);
}
}
}

View File

@ -0,0 +1,165 @@
package com.pig4cloud.pig.common.sentinel.feign;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pig4cloud.pig.common.core.util.R;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
* 支持自动降级注入
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler}
*
* @author lengleng
* @date 2020/6/9
*/
@Slf4j
public class PigSentinelInvocationHandler implements InvocationHandler {
public static final String EQUALS = "equals";
public static final String HASH_CODE = "hashCode";
public static final String TO_STRING = "toString";
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
PigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
PigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if (EQUALS.equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if (HASH_CODE.equals(method.getName())) {
return hashCode();
} else if (TO_STRING.equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
} else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
// 若是R类型 执行自动降级返回R
if (R.class == method.getReturnType()) {
log.error("feign 服务间调用异常", ex);
return R.failed(ex.getLocalizedMessage());
} else {
throw ex;
}
}
} finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
} else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SentinelInvocationHandler) {
PigSentinelInvocationHandler other = (PigSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pig.common.sentinel.handle;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pig4cloud.pig.common.core.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* sentinel统一降级限流策略
* <p>
* {@link com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler}
*
* @author lengleng
* @date 2020-06-11
*/
@Slf4j
public class PigUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
log.error("sentinel 降级 资源名称{}", e.getRule().getResource(), e);
response.setContentType(ContentType.JSON.toString());
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().print(
JSONUtil.toJsonStr(R.failed(e.getMessage())));
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pig.common.sentinel.parser;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import javax.servlet.http.HttpServletRequest;
/**
* sentinel 请求头解析判断
*
* @author lengleng
* @date 2020-06-11
*/
public class PigHeaderRequestOriginParser implements RequestOriginParser {
/**
* 请求头获取allow
*/
private static final String ALLOW = "Allow";
/**
* Parse the origin from given HTTP request.
*
* @param request HTTP request
* @return parsed origin
*/
@Override
public String parseOrigin(HttpServletRequest request) {
return request.getHeader(ALLOW);
}
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pig.common.sentinel.SentinelAutoConfiguration

View File

@ -37,6 +37,7 @@
<module>pig-common-log</module>
<module>pig-common-mybatis</module>
<module>pig-common-security</module>
<module>pig-common-sentinel</module>
<module>pig-common-swagger</module>
</modules>
</project>

View File

@ -51,18 +51,18 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-sentinel</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--验证码-->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<!--common-core-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-core</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--接口文档-->
<dependency>
<groupId>io.springfox</groupId>

View File

@ -20,7 +20,6 @@ package com.pig4cloud.pig.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cloud.client.SpringCloudApplication;
/**
@ -30,7 +29,6 @@ import org.springframework.cloud.client.SpringCloudApplication;
* 网关应用
*/
@SpringCloudApplication
@ConfigurationPropertiesScan
public class PigGatewayApplication {
public static void main(String[] args) {

View File

@ -19,10 +19,9 @@
package com.pig4cloud.pig.gateway.config;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@ -33,6 +32,7 @@ import java.util.List;
* 放行参数配置
*/
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "ignore")
public class IgnoreClientConfiguration {

View File

@ -18,7 +18,10 @@
package com.pig4cloud.pig.gateway.config;
import com.pig4cloud.pig.gateway.handler.*;
import com.pig4cloud.pig.gateway.handler.ImageCodeHandler;
import com.pig4cloud.pig.gateway.handler.SwaggerResourceHandler;
import com.pig4cloud.pig.gateway.handler.SwaggerSecurityHandler;
import com.pig4cloud.pig.gateway.handler.SwaggerUiHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
@ -29,15 +32,15 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
/**
* @author lengleng
* @date 2019/2/1
* 路由配置信息
*
* @author lengleng
* @date 2020-06-11
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class RouterFunctionConfiguration {
private final HystrixFallbackHandler hystrixFallbackHandler;
private final ImageCodeHandler imageCodeHandler;
private final SwaggerResourceHandler swaggerResourceHandler;
private final SwaggerSecurityHandler swaggerSecurityHandler;
@ -47,9 +50,7 @@ public class RouterFunctionConfiguration {
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(
RequestPredicates.path("/fallback")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), hystrixFallbackHandler)
.andRoute(RequestPredicates.GET("/code")
RequestPredicates.path("/code")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), imageCodeHandler)
.andRoute(RequestPredicates.GET("/swagger-resources")
.and(RequestPredicates.accept(MediaType.ALL)), swaggerResourceHandler)

View File

@ -1,52 +0,0 @@
/*
*
* * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).
* * <p>
* * Licensed under the GNU Lesser General Public License 3.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* * <p>
* * https://www.gnu.org/licenses/lgpl.html
* * <p>
* * 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.pig4cloud.pig.gateway.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.Optional;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
/**
* @author lengleng
* @date 2019/2/1
* Hystrix 降级处理
*/
@Slf4j
@Component
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> {
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
originalUris.ifPresent(originalUri -> log.error("网关执行请求:{}失败,hystrix服务降级处理", originalUri));
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromValue("服务异常"));
}
}

View File

@ -72,6 +72,12 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-sentinel</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -23,7 +23,7 @@
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pig</artifactId>
<artifactId>pig-visual</artifactId>
<version>2.7.8.snapshot</version>
</parent>
@ -49,6 +49,12 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-sentinel</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--数据源-->
<dependency>
<groupId>com.pig4cloud</groupId>

View File

@ -48,6 +48,12 @@
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--断路器依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-common-sentinel</artifactId>
<version>2.7.8.snapshot</version>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -0,0 +1,27 @@
# pig4cloud/java:8-jre镜像增加了中文字体与wait-for-it.sh的支持
# 镜像链接:https://hub.docker.com/r/pig4cloud/java/
# wait-for-it.sh采用https://github.com/vishnubob/wait-for-it作为解决方案
FROM pig4cloud/java:8-jre
MAINTAINER wangiegie@gmail.com
ARG JAR_FILE=./target/pig-sentinel-biz.jar
# JVM调优参数等额外参数
ENV PARAMS ""
ENV NACOS_HOST pig-register
ENV NACOS_PORT 8848
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /tmp
EXPOSE 5003
ADD ${JAR_FILE} ./app.jar
ENTRYPOINT ["wait-for-it.sh","$NACOS_HOST:$NACOS_PORT","--","java","-jar","app.jar","-Djava.security.egd=file:/dev/./urandom","$PARAMS"]

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pig-visual</artifactId>
<version>2.7.8.snapshot</version>
</parent>
<artifactId>pig-sentinel-dashboard</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-api-gateway-adapter-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>
<build>
<finalName>pigx-sentinel-dashboard</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/webapp/</directory>
<excludes>
<exclude>resources/node_modules/**</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
/*
* 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;
import com.alibaba.csp.sentinel.init.InitExecutor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author nacos
* <p>
* sentinel console 源码运行方便开发
* 生产建议从官网下载最新版配置运行
*/
@SpringBootApplication
public class PigSentinelApplication {
public static void main(String[] args) {
triggerSentinelInit();
SpringApplication.run(PigSentinelApplication.class, args);
}
private static void triggerSentinelInit() {
new Thread(InitExecutor::doInit).start();
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.auth;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author lkxiaolou
* @since 1.7.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD})
public @interface AuthAction {
/**
* @return the privilege type
*/
AuthService.PrivilegeType value();
/**
* @return the target name to control
*/
String targetName() default "app";
/**
* @return the message when permission is denied
*/
String message() default "Permission denied";
}

View File

@ -0,0 +1,113 @@
/*
* 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.auth;
/**
* Interface for authentication and authorization.
*
* @author Carpenter Lee
* @since 1.5.0
*/
public interface AuthService<R> {
/**
* Get the authentication user.
*
* @param request the request contains the user information
* @return the auth user represent the current user, when the user is illegal, a null value will return.
*/
AuthUser getAuthUser(R request);
/**
* Privilege type.
*/
enum PrivilegeType {
/**
* Read rule
*/
READ_RULE,
/**
* Create or modify rule
*/
WRITE_RULE,
/**
* Delete rule
*/
DELETE_RULE,
/**
* Read metrics
*/
READ_METRIC,
/**
* Add machine
*/
ADD_MACHINE,
/**
* All privileges above are granted.
*/
ALL
}
/**
* Represents the current user.
*/
interface AuthUser {
/**
* Query whether current user has the specific privilege to the target, the target
* may be an app name or an ip address, or other destination.
* <p>
* This method will use return value to represent whether user has the specific
* privileges to the target, but to throw a RuntimeException to represent no auth
* is also a good way.
* </p>
*
* @param target the target to check
* @param privilegeType the privilege type to check
* @return if current user has the specific privileges to the target, return true,
* otherwise return false.
*/
boolean authTarget(String target, PrivilegeType privilegeType);
/**
* Check whether current user is a super-user.
*
* @return if current user is super user return true, else return false.
*/
boolean isSuperUser();
/**
* Get current user's nick name.
*
* @return current user's nick name.
*/
String getNickName();
/**
* Get current user's login name.
*
* @return current user's login name.
*/
String getLoginName();
/**
* Get current user's ID.
*
* @return ID of current user
*/
String getId();
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.auth;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* The web interceptor for privilege-based authorization.
*
* @author lkxiaolou
* @since 1.7.1
*/
@Component
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
private AuthService<HttpServletRequest> authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
Method method = ((HandlerMethod) handler).getMethod();
AuthAction authAction = method.getAnnotation(AuthAction.class);
if (authAction != null) {
AuthService.AuthUser authUser = authService.getAuthUser(request);
if (authUser == null) {
responseNoPrivilegeMsg(response, authAction.message());
return false;
}
String target = request.getParameter(authAction.targetName());
if (!authUser.authTarget(target, authAction.value())) {
responseNoPrivilegeMsg(response, authAction.message());
return false;
}
}
}
return true;
}
private void responseNoPrivilegeMsg(HttpServletResponse response, String message) throws IOException {
Result result = Result.ofFail(-1, message);
response.addHeader("Content-Type", "application/json;charset=UTF-8");
response.getOutputStream().write(JSON.toJSONBytes(result));
}
}

View File

@ -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.auth;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* A fake AuthService implementation, which will pass all user auth checking.
*
* @author Carpenter Lee
* @since 1.5.0
*/
@Component
public class FakeAuthServiceImpl implements AuthService<HttpServletRequest> {
@Override
public AuthUser getAuthUser(HttpServletRequest request) {
return new AuthUserImpl();
}
static final class AuthUserImpl implements AuthUser {
@Override
public boolean authTarget(String target, PrivilegeType privilegeType) {
// fake implementation, always return true
return true;
}
@Override
public boolean isSuperUser() {
// fake implementation, always return true
return true;
}
@Override
public String getNickName() {
return "FAKE_NICK_NAME";
}
@Override
public String getLoginName() {
return "FAKE_LOGIN_NAME";
}
@Override
public String getId() {
return "FAKE_EMP_ID";
}
}
}

View File

@ -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.auth;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* <p>The Servlet filter for authentication.</p>
*
* <p>Note: some urls are excluded as they needn't auth, such as:</p>
* <ul>
* <li>index url: {@code /}</li>
* <li>authentication request url: {@code /login}, {@code /logout}</li>
* <li>machine registry: {@code /registry/machine}</li>
* <li>static resources</li>
* </ul>
*
* The excluded urls and urlSuffixes could be configured in {@code application.properties} file.
*
* @author cdfive
* @since 1.6.0
*/
@Component
public class LoginAuthenticationFilter implements Filter {
private static final String URL_SUFFIX_DOT = ".";
/**
* Some urls which needn't auth, such as /auth/login, /registry/machine and so on.
*/
@Value("#{'${auth.filter.exclude-urls}'.split(',')}")
private List<String> authFilterExcludeUrls;
/**
* Some urls with suffixes which needn't auth, such as htm, html, js and so on.
*/
@Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}")
private List<String> authFilterExcludeUrlSuffixes;
/**
* Authentication using AuthService interface.
*/
@Autowired
private AuthService<HttpServletRequest> authService;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String servletPath = httpRequest.getServletPath();
// Exclude the urls which needn't auth
if (authFilterExcludeUrls.contains(servletPath)) {
chain.doFilter(request, response);
return;
}
// Exclude the urls with suffixes which needn't auth
for (String authFilterExcludeUrlSuffix : authFilterExcludeUrlSuffixes) {
if (StringUtils.isBlank(authFilterExcludeUrlSuffix)) {
continue;
}
// Add . for url suffix so that we needn't add . in property file
if (!authFilterExcludeUrlSuffix.startsWith(URL_SUFFIX_DOT)) {
authFilterExcludeUrlSuffix = URL_SUFFIX_DOT + authFilterExcludeUrlSuffix;
}
if (servletPath.endsWith(authFilterExcludeUrlSuffix)) {
chain.doFilter(request, response);
return;
}
}
AuthService.AuthUser authUser = authService.getAuthUser(httpRequest);
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (authUser == null) {
// If auth fail, set response status code to 401
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.auth;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* @author cdfive
* @since 1.6.0
*/
@Component
@Primary
@ConditionalOnProperty(name = "auth.enabled", matchIfMissing = true)
public class SimpleWebAuthServiceImpl implements AuthService<HttpServletRequest> {
public static final String WEB_SESSION_KEY = "session_sentinel_admin";
@Override
public AuthUser getAuthUser(HttpServletRequest request) {
HttpSession session = request.getSession();
Object sentinelUserObj = session.getAttribute(SimpleWebAuthServiceImpl.WEB_SESSION_KEY);
if (sentinelUserObj != null && sentinelUserObj instanceof AuthUser) {
return (AuthUser) sentinelUserObj;
}
return null;
}
public static final class SimpleWebAuthUserImpl implements AuthUser {
private String username;
public SimpleWebAuthUserImpl(String username) {
this.username = username;
}
@Override
public boolean authTarget(String target, PrivilegeType privilegeType) {
return true;
}
@Override
public boolean isSuperUser() {
return true;
}
@Override
public String getNickName() {
return username;
}
@Override
public String getLoginName() {
return username;
}
@Override
public String getId() {
return username;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.client;
/**
* @author Eric Zhao
*/
public class CommandFailedException extends RuntimeException {
public CommandFailedException() {}
public CommandFailedException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.client;
/**
* @author Eric Zhao
* @since 0.2.1
*/
public class CommandNotFoundException extends Exception {
public CommandNotFoundException() { }
public CommandNotFoundException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}

View File

@ -0,0 +1,818 @@
/*
* 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.client;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
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;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterClientInfoVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterStateSimpleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.dashboard.util.VersionUtils;
import org.apache.http.Consts;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
/**
* Communicate with Sentinel client.
*
* @author leyou
*/
@Component
public class SentinelApiClient {
private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class);
private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset());
private static final String RESOURCE_URL_PATH = "jsonTree";
private static final String CLUSTER_NODE_PATH = "clusterNode";
private static final String GET_RULES_PATH = "getRules";
private static final String SET_RULES_PATH = "setRules";
private static final String GET_PARAM_RULE_PATH = "getParamFlowRules";
private static final String SET_PARAM_RULE_PATH = "setParamFlowRules";
private static final String FETCH_CLUSTER_MODE_PATH = "getClusterMode";
private static final String MODIFY_CLUSTER_MODE_PATH = "setClusterMode";
private static final String FETCH_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/fetchConfig";
private static final String MODIFY_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/modifyConfig";
private static final String FETCH_CLUSTER_SERVER_ALL_CONFIG_PATH = "cluster/server/fetchConfig";
private static final String FETCH_CLUSTER_SERVER_BASIC_INFO_PATH = "cluster/server/info";
private static final String MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH = "cluster/server/modifyTransportConfig";
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";
private static final String AUTHORITY_TYPE = "authority";
private CloseableHttpAsyncClient httpClient;
private static final SentinelVersion version160 = new SentinelVersion(1, 6, 0);
@Autowired
private AppManagement appManagement;
public SentinelApiClient() {
IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(10000)
.setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2).build();
httpClient = HttpAsyncClients.custom().setRedirectStrategy(new DefaultRedirectStrategy() {
@Override
protected boolean isRedirectable(final String method) {
return false;
}
}).setMaxConnTotal(4000).setMaxConnPerRoute(1000).setDefaultIOReactorConfig(ioConfig).build();
httpClient.start();
}
private boolean isSuccess(int statusCode) {
return statusCode >= 200 && statusCode < 300;
}
private boolean isCommandNotFound(int statusCode, String body) {
return statusCode == 400 && StringUtil.isNotEmpty(body) && body.contains(CommandConstants.MSG_UNKNOWN_COMMAND_PREFIX);
}
private StringBuilder queryString(Map<String, String> params) {
StringBuilder queryStringBuilder = new StringBuilder();
for (Entry<String, String> entry : params.entrySet()) {
if (StringUtil.isEmpty(entry.getValue())) {
continue;
}
String name = urlEncode(entry.getKey());
String value = urlEncode(entry.getValue());
if (name != null && value != null) {
if (queryStringBuilder.length() > 0) {
queryStringBuilder.append('&');
}
queryStringBuilder.append(name).append('=').append(value);
}
}
return queryStringBuilder;
}
private HttpUriRequest postRequest(String url, Map<String, String> params) {
HttpPost httpPost = new HttpPost(url);
if (params != null && params.size() > 0) {
List<NameValuePair> list = new ArrayList<>(params.size());
for (Entry<String, String> entry : params.entrySet()) {
list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
httpPost.setEntity(new UrlEncodedFormEntity(list, Consts.UTF_8));
}
return httpPost;
}
private String urlEncode(String str) {
try {
return URLEncoder.encode(str, DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException e) {
logger.info("encode string error: {}", str, e);
return null;
}
}
private String getBody(HttpResponse response) throws Exception {
Charset charset = null;
try {
String contentTypeStr = response.getFirstHeader("Content-type").getValue();
if (StringUtil.isNotEmpty(contentTypeStr)) {
ContentType contentType = ContentType.parse(contentTypeStr);
charset = contentType.getCharset();
}
} catch (Exception ignore) {
}
return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET);
}
/**
* With no param
*
* @param ip
* @param port
* @param api
* @return
*/
private CompletableFuture<String> executeCommand(String ip, int port, String api, boolean useHttpPost) {
return executeCommand(ip, port, api, null, useHttpPost);
}
/**
* No app specified, force to GET
*
* @param ip
* @param port
* @param api
* @param params
* @return
*/
private CompletableFuture<String> executeCommand(String ip, int port, String api, Map<String, String> params, boolean useHttpPost) {
return executeCommand(null, ip, port, api, params, useHttpPost);
}
/**
* Prefer to execute request using POST
*
* @param app
* @param ip
* @param port
* @param api
* @param params
* @return
*/
private CompletableFuture<String> executeCommand(String app, String ip, int port, String api, Map<String, String> params, boolean useHttpPost) {
CompletableFuture<String> future = new CompletableFuture<>();
if (StringUtil.isBlank(ip) || StringUtil.isBlank(api)) {
future.completeExceptionally(new IllegalArgumentException("Bad URL or command name"));
return future;
}
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append("http://");
urlBuilder.append(ip).append(':').append(port).append('/').append(api);
if (params == null) {
params = Collections.emptyMap();
}
boolean supportPost = StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version160)))
.orElse(false);
if (!useHttpPost || !supportPost) {
// Using GET in older versions, append parameters after url
if (!params.isEmpty()) {
if (urlBuilder.indexOf("?") == -1) {
urlBuilder.append('?');
} else {
urlBuilder.append('&');
}
urlBuilder.append(queryString(params));
}
return executeCommand(new HttpGet(urlBuilder.toString()));
} else {
// Using POST
return executeCommand(postRequest(urlBuilder.toString(), params));
}
}
private CompletableFuture<String> executeCommand(HttpUriRequest request) {
CompletableFuture<String> future = new CompletableFuture<>();
httpClient.execute(request, new FutureCallback<HttpResponse>() {
@Override
public void completed(final HttpResponse response) {
int statusCode = response.getStatusLine().getStatusCode();
try {
String value = getBody(response);
if (isSuccess(statusCode)) {
future.complete(value);
} else {
if (isCommandNotFound(statusCode, value)) {
future.completeExceptionally(new CommandNotFoundException(request.getURI().getPath()));
} else {
future.completeExceptionally(new CommandFailedException(value));
}
}
} catch (Exception ex) {
future.completeExceptionally(ex);
logger.error("HTTP request failed: {}", request.getURI().toString(), ex);
}
}
@Override
public void failed(final Exception ex) {
future.completeExceptionally(ex);
logger.error("HTTP request failed: {}", request.getURI().toString(), ex);
}
@Override
public void cancelled() {
future.complete(null);
}
});
return future;
}
public void close() throws Exception {
httpClient.close();
}
@Nullable
private <T> CompletableFuture<List<T>> fetchItemsAsync(String ip, int port, String api, String type, Class<T> ruleType) {
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
Map<String, String> params = null;
if (StringUtil.isNotEmpty(type)) {
params = new HashMap<>(1);
params.put("type", type);
}
return executeCommand(ip, port, api, params, false)
.thenApply(json -> JSON.parseArray(json, ruleType));
}
@Nullable
private <T> List<T> fetchItems(String ip, int port, String api, String type, Class<T> ruleType) {
try {
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
Map<String, String> params = null;
if (StringUtil.isNotEmpty(type)) {
params = new HashMap<>(1);
params.put("type", type);
}
return fetchItemsAsync(ip, port, api, type, ruleType).get();
} catch (InterruptedException | ExecutionException e) {
logger.error("Error when fetching items from api: {} -> {}", api, type, e);
return null;
} catch (Exception e) {
logger.error("Error when fetching items: {} -> {}", api, type, e);
return null;
}
}
private <T extends Rule> List<T> fetchRules(String ip, int port, String type, Class<T> ruleType) {
return fetchItems(ip, port, GET_RULES_PATH, type, ruleType);
}
private boolean setRules(String app, String ip, int port, String type, List<? extends RuleEntity> entities) {
if (entities == 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(
entities.stream().map(r -> r.toRule()).collect(Collectors.toList()));
Map<String, String> params = new HashMap<>(2);
params.put("type", type);
params.put("data", data);
String result = executeCommand(app, ip, port, SET_RULES_PATH, params, true).get();
logger.info("setRules result: {}, type={}", result, type);
return true;
} catch (InterruptedException e) {
logger.warn("setRules API failed: {}", type, e);
return false;
} catch (ExecutionException e) {
logger.warn("setRules API failed: {}", type, e.getCause());
return false;
} catch (Exception e) {
logger.error("setRules API failed, type={}", type, e);
return false;
}
}
private CompletableFuture<Void> setRulesAsync(String app, String ip, int port, String type, List<? extends RuleEntity> entities) {
try {
AssertUtil.notNull(entities, "rules cannot be null");
AssertUtil.notEmpty(app, "Bad app name");
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
String data = JSON.toJSONString(
entities.stream().map(r -> r.toRule()).collect(Collectors.toList()));
Map<String, String> params = new HashMap<>(2);
params.put("type", type);
params.put("data", data);
return executeCommand(app, ip, port, SET_RULES_PATH, params, true)
.thenCompose(r -> {
if ("success".equalsIgnoreCase(r.trim())) {
return CompletableFuture.completedFuture(null);
}
return AsyncUtils.newFailedFuture(new CommandFailedException(r));
});
} catch (Exception e) {
logger.error("setRulesAsync API failed, type={}", type, e);
return AsyncUtils.newFailedFuture(e);
}
}
public List<NodeVo> fetchResourceOfMachine(String ip, int port, String type) {
return fetchItems(ip, port, RESOURCE_URL_PATH, type, NodeVo.class);
}
/**
* Fetch cluster node.
*
* @param ip ip to fetch
* @param port port of the ip
* @param includeZero whether zero value should in the result list.
* @return
*/
public List<NodeVo> fetchClusterNodeOfMachine(String ip, int port, boolean includeZero) {
String type = "notZero";
if (includeZero) {
type = "zero";
}
return fetchItems(ip, port, CLUSTER_NODE_PATH, type, NodeVo.class);
}
public List<FlowRuleEntity> fetchFlowRuleOfMachine(String app, String ip, int port) {
List<FlowRule> rules = fetchRules(ip, port, FLOW_RULE_TYPE, FlowRule.class);
if (rules != null) {
return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule))
.collect(Collectors.toList());
} else {
return null;
}
}
public List<DegradeRuleEntity> fetchDegradeRuleOfMachine(String app, String ip, int port) {
List<DegradeRule> rules = fetchRules(ip, port, DEGRADE_RULE_TYPE, DegradeRule.class);
if (rules != null) {
return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule))
.collect(Collectors.toList());
} else {
return null;
}
}
public List<SystemRuleEntity> fetchSystemRuleOfMachine(String app, String ip, int port) {
List<SystemRule> rules = fetchRules(ip, port, SYSTEM_RULE_TYPE, SystemRule.class);
if (rules != null) {
return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule))
.collect(Collectors.toList());
} else {
return null;
}
}
/**
* Fetch all parameter flow rules from provided machine.
*
* @param app application name
* @param ip machine client IP
* @param port machine client port
* @return all retrieved parameter flow rules
* @since 0.2.1
*/
public CompletableFuture<List<ParamFlowRuleEntity>> fetchParamFlowRulesOfMachine(String app, String ip, int port) {
try {
AssertUtil.notEmpty(app, "Bad app name");
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
return fetchItemsAsync(ip, port, GET_PARAM_RULE_PATH, null, ParamFlowRule.class)
.thenApply(rules -> rules.stream()
.map(e -> ParamFlowRuleEntity.fromAuthorityRule(app, ip, port, e))
.collect(Collectors.toList())
);
} catch (Exception e) {
logger.error("Error when fetching parameter flow rules", e);
return AsyncUtils.newFailedFuture(e);
}
}
/**
* Fetch all authority rules from provided machine.
*
* @param app application name
* @param ip machine client IP
* @param port machine client port
* @return all retrieved authority rules
* @since 0.2.1
*/
public List<AuthorityRuleEntity> fetchAuthorityRulesOfMachine(String app, String ip, int port) {
AssertUtil.notEmpty(app, "Bad app name");
AssertUtil.notEmpty(ip, "Bad machine IP");
AssertUtil.isTrue(port > 0, "Bad machine port");
Map<String, String> params = new HashMap<>(1);
params.put("type", AUTHORITY_TYPE);
List<AuthorityRule> rules = fetchRules(ip, port, AUTHORITY_TYPE, AuthorityRule.class);
return Optional.ofNullable(rules).map(r -> r.stream()
.map(e -> AuthorityRuleEntity.fromAuthorityRule(app, ip, port, e))
.collect(Collectors.toList())
).orElse(null);
}
/**
* set rules of the machine. rules == null will return immediately;
* rules.isEmpty() means setting the rules to empty.
*
* @param app
* @param ip
* @param port
* @param rules
* @return whether successfully set the rules.
*/
public boolean setFlowRuleOfMachine(String app, String ip, int port, List<FlowRuleEntity> rules) {
return setRules(app, ip, port, FLOW_RULE_TYPE, rules);
}
public CompletableFuture<Void> setFlowRuleOfMachineAsync(String app, String ip, int port, List<FlowRuleEntity> rules) {
return setRulesAsync(app, ip, port, FLOW_RULE_TYPE, rules);
}
/**
* set rules of the machine. rules == null will return immediately;
* rules.isEmpty() means setting the rules to empty.
*
* @param app
* @param ip
* @param port
* @param rules
* @return whether successfully set the rules.
*/
public boolean setDegradeRuleOfMachine(String app, String ip, int port, List<DegradeRuleEntity> rules) {
return setRules(app, ip, port, DEGRADE_RULE_TYPE, rules);
}
/**
* set rules of the machine. rules == null will return immediately;
* rules.isEmpty() means setting the rules to empty.
*
* @param app
* @param ip
* @param port
* @param rules
* @return whether successfully set the rules.
*/
public boolean setSystemRuleOfMachine(String app, String ip, int port, List<SystemRuleEntity> rules) {
return setRules(app, ip, port, SYSTEM_RULE_TYPE, rules);
}
public boolean setAuthorityRuleOfMachine(String app, String ip, int port, List<AuthorityRuleEntity> rules) {
return setRules(app, ip, port, AUTHORITY_TYPE, rules);
}
public CompletableFuture<Void> setParamFlowRuleOfMachine(String app, String ip, int port, List<ParamFlowRuleEntity> rules) {
if (rules == null) {
return CompletableFuture.completedFuture(null);
}
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
String data = JSON.toJSONString(
rules.stream().map(ParamFlowRuleEntity::getRule).collect(Collectors.toList())
);
Map<String, String> params = new HashMap<>(1);
params.put("data", data);
return executeCommand(app, ip, port, SET_PARAM_RULE_PATH, params, true)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Push parameter flow rules to client failed: " + e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when setting parameter flow rule", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
// Cluster related
public CompletableFuture<ClusterStateSimpleEntity> fetchClusterMode(String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
return executeCommand(ip, port, FETCH_CLUSTER_MODE_PATH, false)
.thenApply(r -> JSON.parseObject(r, ClusterStateSimpleEntity.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster mode", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<Void> modifyClusterMode(String ip, int port, int mode) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
Map<String, String> params = new HashMap<>(1);
params.put("mode", String.valueOf(mode));
return executeCommand(ip, port, MODIFY_CLUSTER_MODE_PATH, params, false)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster mode: " + e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster mode", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<ClusterClientInfoVO> fetchClusterClientInfoAndConfig(String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
return executeCommand(ip, port, FETCH_CLUSTER_CLIENT_CONFIG_PATH, false)
.thenApply(r -> JSON.parseObject(r, ClusterClientInfoVO.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster client config", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<Void> modifyClusterClientConfig(String app, String ip, int port, ClusterClientConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
Map<String, String> params = new HashMap<>(1);
params.put("data", JSON.toJSONString(config));
return executeCommand(app, ip, port, MODIFY_CLUSTER_CLIENT_CONFIG_PATH, params, true)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster client config: " + e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster client config", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<Void> modifyClusterServerFlowConfig(String app, String ip, int port, ServerFlowConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
Map<String, String> params = new HashMap<>(1);
params.put("data", JSON.toJSONString(config));
return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, params, true)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server flow config: " + e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server flow config", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<Void> modifyClusterServerTransportConfig(String app, String ip, int port, ServerTransportConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
Map<String, String> params = new HashMap<>(2);
params.put("port", config.getPort().toString());
params.put("idleSeconds", config.getIdleSeconds().toString());
return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, params, false)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server transport config: " + e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server transport config", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<Void> modifyClusterServerNamespaceSet(String app, String ip, int port, Set<String> set) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
Map<String, String> params = new HashMap<>(1);
params.put("data", JSON.toJSONString(set));
return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, params, true)
.thenCompose(e -> {
if (CommandConstants.MSG_SUCCESS.equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server NamespaceSet", e);
return AsyncUtils.newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server NamespaceSet", ex);
return AsyncUtils.newFailedFuture(ex);
}
}
public CompletableFuture<ClusterServerStateVO> fetchClusterServerBasicInfo(String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
return executeCommand(ip, port, FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, false)
.thenApply(r -> JSON.parseObject(r, ClusterServerStateVO.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster sever all config and basic info", ex);
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;
}
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.config;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.lang.NonNull;
/**
* <p>Dashboard local config support.</p>
* <p>
* Dashboard supports configuration loading by several ways by order:<br>
* 1. System.properties<br>
* 2. Env
* </p>
*
* @author jason
* @since 1.5.0
*/
public class DashboardConfig {
public static final int DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS = 60_000;
/**
* Login username
*/
public static final String CONFIG_AUTH_USERNAME = "sentinel.dashboard.auth.username";
/**
* Login password
*/
public static final String CONFIG_AUTH_PASSWORD = "sentinel.dashboard.auth.password";
/**
* Hide application name in sidebar when it has no healthy machines after specific period in millisecond.
*/
public static final String CONFIG_HIDE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.app.hideAppNoMachineMillis";
/**
* Remove application when it has no healthy machines after specific period in millisecond.
*/
public static final String CONFIG_REMOVE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.removeAppNoMachineMillis";
/**
* Timeout
*/
public static final String CONFIG_UNHEALTHY_MACHINE_MILLIS = "sentinel.dashboard.unhealthyMachineMillis";
/**
* Auto remove unhealthy machine after specific period in millisecond.
*/
public static final String CONFIG_AUTO_REMOVE_MACHINE_MILLIS = "sentinel.dashboard.autoRemoveMachineMillis";
private static final ConcurrentMap<String, Object> cacheMap = new ConcurrentHashMap<>();
@NonNull
private static String getConfig(String name) {
// env
String val = System.getenv(name);
if (StringUtils.isNotEmpty(val)) {
return val;
}
// properties
val = System.getProperty(name);
if (StringUtils.isNotEmpty(val)) {
return val;
}
return "";
}
protected static String getConfigStr(String name) {
if (cacheMap.containsKey(name)) {
return (String) cacheMap.get(name);
}
String val = getConfig(name);
if (StringUtils.isBlank(val)) {
return null;
}
cacheMap.put(name, val);
return val;
}
protected static int getConfigInt(String name, int defaultVal, int minVal) {
if (cacheMap.containsKey(name)) {
return (int)cacheMap.get(name);
}
int val = NumberUtils.toInt(getConfig(name));
if (val == 0) {
val = defaultVal;
} else if (val < minVal) {
val = minVal;
}
cacheMap.put(name, val);
return val;
}
public static String getAuthUsername() {
return getConfigStr(CONFIG_AUTH_USERNAME);
}
public static String getAuthPassword() {
return getConfigStr(CONFIG_AUTH_PASSWORD);
}
public static int getHideAppNoMachineMillis() {
return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000);
}
public static int getRemoveAppNoMachineMillis() {
return getConfigInt(CONFIG_REMOVE_APP_NO_MACHINE_MILLIS, 0, 120000);
}
public static int getAutoRemoveMachineMillis() {
return getConfigInt(CONFIG_AUTO_REMOVE_MACHINE_MILLIS, 0, 300000);
}
public static int getUnhealthyMachineMillis() {
return getConfigInt(CONFIG_UNHEALTHY_MACHINE_MILLIS, DEFAULT_MACHINE_HEALTHY_TIMEOUT_MS, 30000);
}
public static void clearCache() {
cacheMap.clear();
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.config;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.dashboard.auth.AuthorizationInterceptor;
import com.alibaba.csp.sentinel.dashboard.auth.LoginAuthenticationFilter;
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.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
/**
* @author leyou
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final Logger logger = LoggerFactory.getLogger(WebConfig.class);
@Autowired
private LoginAuthenticationFilter loginAuthenticationFilter;
@Autowired
private AuthorizationInterceptor authorizationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.htm");
}
/**
* Add {@link CommonFilter} to the server, this is the simplest way to use Sentinel
* for Web application.
*/
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.setName("sentinelFilter");
registration.setOrder(1);
// If this is enabled, the entrance of all Web URL resources will be unified as a single context name.
// In most scenarios that's enough, and it could reduce the memory footprint.
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "true");
logger.info("Sentinel servlet CommonFilter registered");
return registration;
}
@PostConstruct
public void doInit() {
Set<String> suffixSet = new HashSet<>(Arrays.asList(".js", ".css", ".html", ".ico", ".txt",
".woff", ".woff2"));
// Example: register a UrlCleaner to exclude URLs of common static resources.
WebCallbackManager.setUrlCleaner(url -> {
if (StringUtil.isEmpty(url)) {
return url;
}
if (suffixSet.stream().anyMatch(url::endsWith)) {
return null;
}
return url;
});
}
@Bean
public FilterRegistrationBean authenticationFilterRegistration() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(loginAuthenticationFilter);
registration.addUrlPatterns("/*");
registration.setName("authenticationFilter");
registration.setOrder(0);
return registration;
}
}

View File

@ -0,0 +1,85 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.domain.vo.MachineInfoVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Carpenter Lee
*/
@RestController
@RequestMapping(value = "/app")
public class AppController {
@Autowired
private AppManagement appManagement;
@GetMapping("/names.json")
public Result<List<String>> queryApps(HttpServletRequest request) {
return Result.ofSuccess(appManagement.getAppNames());
}
@GetMapping("/briefinfos.json")
public Result<List<AppInfo>> queryAppInfos(HttpServletRequest request) {
List<AppInfo> list = new ArrayList<>(appManagement.getBriefApps());
Collections.sort(list, Comparator.comparing(AppInfo::getApp));
return Result.ofSuccess(list);
}
@GetMapping(value = "/{app}/machines.json")
public Result<List<MachineInfoVo>> getMachinesByApp(@PathVariable("app") String app) {
AppInfo appInfo = appManagement.getDetailApp(app);
if (appInfo == null) {
return Result.ofSuccess(null);
}
List<MachineInfo> list = new ArrayList<>(appInfo.getMachines());
Collections.sort(list, Comparator.comparing(MachineInfo::getApp).thenComparing(MachineInfo::getIp).thenComparingInt(MachineInfo::getPort));
return Result.ofSuccess(MachineInfoVo.fromMachineInfoList(list));
}
@RequestMapping(value = "/{app}/machine/remove.json")
public Result<String> removeMachineById(
@PathVariable("app") String app,
@RequestParam(name = "ip") String ip,
@RequestParam(name = "port") int port) {
AppInfo appInfo = appManagement.getDetailApp(app);
if (appInfo == null) {
return Result.ofSuccess(null);
}
if (appManagement.removeMachine(app, ip, port)) {
return Result.ofSuccessMsg("success");
} else {
return Result.ofFail(1, "remove failed");
}
}
}

View File

@ -0,0 +1,92 @@
/*
* 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;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.auth.SimpleWebAuthServiceImpl;
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author cdfive
* @since 1.6.0
*/
@RestController
@RequestMapping("/auth")
public class AuthController {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthController.class);
@Value("${auth.username:sentinel}")
private String authUsername;
@Value("${auth.password:sentinel}")
private String authPassword;
@Autowired
private AuthService<HttpServletRequest> authService;
@PostMapping("/login")
public Result<AuthService.AuthUser> login(HttpServletRequest request, String username, String password) {
if (StringUtils.isNotBlank(DashboardConfig.getAuthUsername())) {
authUsername = DashboardConfig.getAuthUsername();
}
if (StringUtils.isNotBlank(DashboardConfig.getAuthPassword())) {
authPassword = DashboardConfig.getAuthPassword();
}
/*
* If auth.username or auth.password is blank(set in application.properties or VM arguments),
* auth will pass, as the front side validate the input which can't be blank,
* so user can input any username or password(both are not blank) to login in that case.
*/
if (StringUtils.isNotBlank(authUsername) && !authUsername.equals(username)
|| StringUtils.isNotBlank(authPassword) && !authPassword.equals(password)) {
LOGGER.error("Login failed: Invalid username or password, username=" + username);
return Result.ofFail(-1, "Invalid username or password");
}
AuthService.AuthUser authUser = new SimpleWebAuthServiceImpl.SimpleWebAuthUserImpl(username);
request.getSession().setAttribute(SimpleWebAuthServiceImpl.WEB_SESSION_KEY, authUser);
return Result.ofSuccess(authUser);
}
@PostMapping(value = "/logout")
public Result<?> logout(HttpServletRequest request) {
request.getSession().invalidate();
return Result.ofSuccess(null);
}
@PostMapping(value = "/check")
public Result<?> check(HttpServletRequest request) {
AuthService.AuthUser authUser = authService.getAuthUser(request);
if (authUser == null) {
return Result.ofFail(-1, "Not logged in");
}
return Result.ofSuccess(authUser);
}
}

View File

@ -0,0 +1,191 @@
/*
* 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;
import java.util.Date;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/authority")
public class AuthorityRuleController {
private final Logger logger = LoggerFactory.getLogger(AuthorityRuleController.class);
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private RuleRepository<AuthorityRuleEntity, Long> repository;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<AuthorityRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
try {
List<AuthorityRuleEntity> rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying authority rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private <R> Result<R> checkEntityInternal(AuthorityRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp should be valid");
}
if (entity.getStrategy() != RuleConstant.AUTHORITY_WHITE
&& entity.getStrategy() != RuleConstant.AUTHORITY_BLACK) {
return Result.ofFail(-1, "Unknown strategy (must be blacklist or whitelist)");
}
return null;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<AuthorityRuleEntity> apiAddAuthorityRule(@RequestBody AuthorityRuleEntity entity) {
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("Failed to add authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("Publish authority rules failed after rule add");
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<AuthorityRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody AuthorityRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(null);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "Failed to save authority rule");
}
} catch (Throwable throwable) {
logger.error("Failed to save authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("Publish authority rules failed after rule update");
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
AuthorityRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.error("Publish authority rules failed after rule delete");
}
return Result.ofSuccess(id);
}
private boolean publishRules(String app, String ip, Integer port) {
List<AuthorityRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules);
}
}

View File

@ -0,0 +1,214 @@
/*
* 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;
import java.util.Date;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemDegradeRuleStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ResponseBody;
/**
* @author leyou
*/
@Controller
@RequestMapping(value = "/degrade", produces = MediaType.APPLICATION_JSON_VALUE)
public class DegradeController {
private final Logger logger = LoggerFactory.getLogger(DegradeController.class);
@Autowired
private InMemDegradeRuleStore repository;
@Autowired
private SentinelApiClient sentinelApiClient;
@ResponseBody
@RequestMapping("/rules.json")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<DegradeRuleEntity>> queryMachineRules(String app, String ip, Integer port) {
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<DegradeRuleEntity> rules = sentinelApiClient.fetchDegradeRuleOfMachine(app, ip, port);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("queryApps error:", throwable);
return Result.ofThrowable(-1, throwable);
}
}
@ResponseBody
@RequestMapping("/new.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<DegradeRuleEntity> add(String app, String ip, Integer port, String limitApp, String resource,
Double count, Integer timeWindow, Integer grade) {
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(ip)) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
if (StringUtil.isBlank(limitApp)) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(resource)) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (count == null) {
return Result.ofFail(-1, "count can't be null");
}
if (timeWindow == null) {
return Result.ofFail(-1, "timeWindow can't be null");
}
if (grade == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
return Result.ofFail(-1, "Invalid grade: " + grade);
}
DegradeRuleEntity entity = new DegradeRuleEntity();
entity.setApp(app.trim());
entity.setIp(ip.trim());
entity.setPort(port);
entity.setLimitApp(limitApp.trim());
entity.setResource(resource.trim());
entity.setCount(count);
entity.setTimeWindow(timeWindow);
entity.setGrade(grade);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("add error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(app, ip, port)) {
logger.info("publish degrade rules fail after rule add");
}
return Result.ofSuccess(entity);
}
@ResponseBody
@RequestMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<DegradeRuleEntity> updateIfNotNull(Long id, String app, String limitApp, String resource,
Double count, Integer timeWindow, Integer grade) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
if (grade != null) {
if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
return Result.ofFail(-1, "Invalid grade: " + grade);
}
}
DegradeRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (StringUtil.isNotBlank(limitApp)) {
entity.setLimitApp(limitApp.trim());
}
if (StringUtil.isNotBlank(resource)) {
entity.setResource(resource.trim());
}
if (count != null) {
entity.setCount(count);
}
if (timeWindow != null) {
entity.setTimeWindow(timeWindow);
}
if (grade != null) {
entity.setGrade(grade);
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("save error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("publish degrade rules fail after rule update");
}
return Result.ofSuccess(entity);
}
@ResponseBody
@RequestMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> delete(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
DegradeRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Throwable throwable) {
logger.error("delete error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.info("publish degrade rules fail after rule delete");
}
return Result.ofSuccess(id);
}
private boolean publishRules(String app, String ip, Integer port) {
List<DegradeRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules);
}
}

View File

@ -0,0 +1,135 @@
/*
* 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;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
@Controller
@RequestMapping(value = "/demo", produces = MediaType.APPLICATION_JSON_VALUE)
public class DemoController {
Logger logger = LoggerFactory.getLogger(MachineRegistryController.class);
@RequestMapping("/greeting")
public String greeting() {
return "index";
}
@RequestMapping("/link")
@ResponseBody
public String link() throws BlockException {
Entry entry = SphU.entry("head1", EntryType.IN);
Entry entry1 = SphU.entry("head2", EntryType.IN);
Entry entry2 = SphU.entry("head3", EntryType.IN);
Entry entry3 = SphU.entry("head4", EntryType.IN);
entry3.exit();
entry2.exit();
entry1.exit();
entry.exit();
return "successfully create a call link";
}
@RequestMapping("/loop")
@ResponseBody
public String loop(String name, int time) throws BlockException {
for (int i = 0; i < 10; i++) {
Thread timer = new Thread(new RunTask(name, time, false));
timer.setName("false");
timer.start();
}
return "successfully create a loop thread";
}
@RequestMapping("/slow")
@ResponseBody
public String slow(String name, int time) throws BlockException {
for (int i = 0; i < 10; i++) {
Thread timer = new Thread(new RunTask(name, time, true));
timer.setName("false");
timer.start();
}
return "successfully create a loop thread";
}
static class RunTask implements Runnable {
int time;
boolean stop = false;
String name;
boolean slow = false;
public RunTask(String name, int time, boolean slow) {
super();
this.time = time;
this.name = name;
this.slow = slow;
}
@Override
public void run() {
long startTime = System.currentTimeMillis();
ContextUtil.enter(String.valueOf(startTime));
while (!stop) {
long now = System.currentTimeMillis();
if (now - startTime > time * 1000) {
stop = true;
}
Entry e1 = null;
try {
e1 = SphU.entry(name);
if (slow == true) {
TimeUnit.MILLISECONDS.sleep(3000);
}
} catch (Exception e) {
} finally {
if (e1 != null) {
e1.exit();
}
}
Random random2 = new Random();
try {
TimeUnit.MILLISECONDS.sleep(random2.nextInt(200));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
ContextUtil.exit();
}
}
}

View File

@ -0,0 +1,273 @@
/*
* 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;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Flow rule controller.
*
* @author leyou
* @author Eric Zhao
*/
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
private SentinelApiClient sentinelApiClient;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
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<FlowRuleEntity> rules = sentinelApiClient.fetchFlowRuleOfMachine(app, ip, port);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null) {
return Result.ofFail(-1, "port can't be null");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
try {
entity = repository.save(entity);
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
return Result.ofSuccess(entity);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Failed to add new flow rule, app={}, ip={}", entity.getApp(), entity.getIp(), e);
return Result.ofFail(-1, e.getMessage());
}
}
@PutMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiUpdateFlowRule(Long id, String app,
String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec,
Integer maxQueueingTimeMs) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (StringUtil.isNotBlank(limitApp)) {
entity.setLimitApp(limitApp.trim());
}
if (StringUtil.isNotBlank(resource)) {
entity.setResource(resource.trim());
}
if (grade != null) {
if (grade != 0 && grade != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
}
entity.setGrade(grade);
}
if (count != null) {
entity.setCount(count);
}
if (strategy != null) {
if (strategy != 0 && strategy != 1 && strategy != 2) {
return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
}
entity.setStrategy(strategy);
if (strategy != 0) {
if (StringUtil.isBlank(refResource)) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
entity.setRefResource(refResource.trim());
}
}
if (controlBehavior != null) {
if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
}
if (controlBehavior == 1 && warmUpPeriodSec == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && maxQueueingTimeMs == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
entity.setControlBehavior(controlBehavior);
if (warmUpPeriodSec != null) {
entity.setWarmUpPeriodSec(warmUpPeriodSec);
}
if (maxQueueingTimeMs != null) {
entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
}
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail: null");
}
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get(5000, TimeUnit.MILLISECONDS);
return Result.ofSuccess(entity);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Error when updating flow rules, app={}, ip={}, ruleId={}", entity.getApp(),
entity.getIp(), id, e);
return Result.ofFail(-1, e.getMessage());
}
}
@DeleteMapping("/delete.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<Long> apiDeleteFlowRule(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
try {
publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get(5000, TimeUnit.MILLISECONDS);
return Result.ofSuccess(id);
} catch (Throwable t) {
Throwable e = t instanceof ExecutionException ? t.getCause() : t;
logger.error("Error when deleting flow rules, app={}, ip={}, id={}", oldEntity.getApp(),
oldEntity.getIp(), id, e);
return Result.ofFail(-1, e.getMessage());
}
}
private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setFlowRuleOfMachineAsync(app, ip, port, rules);
}
}

View File

@ -0,0 +1,77 @@
/*
* 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;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineDiscovery;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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
@RequestMapping(value = "/registry", produces = MediaType.APPLICATION_JSON_VALUE)
public class MachineRegistryController {
private final Logger logger = LoggerFactory.getLogger(MachineRegistryController.class);
@Autowired
private AppManagement appManagement;
@ResponseBody
@RequestMapping("/machine")
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;
}
if (ip == null) {
return Result.ofFail(-1, "ip can't be null");
}
if (port == null) {
return Result.ofFail(-1, "port can't be null");
}
if (port == -1) {
logger.info("Receive heartbeat from " + ip + " but port not set yet");
return Result.ofFail(-1, "your port not set yet");
}
String sentinelVersion = StringUtil.isEmpty(v) ? "unknown" : v;
version = version == null ? System.currentTimeMillis() : version;
try {
MachineInfo machineInfo = new MachineInfo();
machineInfo.setApp(app);
machineInfo.setAppType(appType);
machineInfo.setHostname(hostname);
machineInfo.setIp(ip);
machineInfo.setPort(port);
machineInfo.setHeartbeatVersion(version);
machineInfo.setLastHeartbeat(System.currentTimeMillis());
machineInfo.setVersion(sentinelVersion);
appManagement.addMachine(machineInfo);
return Result.ofSuccessMsg("success");
} catch (Exception e) {
logger.error("Receive heartbeat error", e);
return Result.ofFail(-1, e.getMessage());
}
}
}

View File

@ -0,0 +1,175 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ResponseBody;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.dashboard.domain.vo.MetricVo;
/**
* @author leyou
*/
@Controller
@RequestMapping(value = "/metric", produces = MediaType.APPLICATION_JSON_VALUE)
public class MetricController {
private static Logger logger = LoggerFactory.getLogger(MetricController.class);
private static final long maxQueryIntervalMs = 1000 * 60 * 60;
@Autowired
private MetricsRepository<MetricEntity> metricStore;
@ResponseBody
@RequestMapping("/queryTopResourceMetric.json")
public Result<?> queryTopResourceMetric(final String app,
Integer pageIndex,
Integer pageSize,
Boolean desc,
Long startTime, Long endTime, String searchKey) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (pageIndex == null || pageIndex <= 0) {
pageIndex = 1;
}
if (pageSize == null) {
pageSize = 6;
}
if (pageSize >= 20) {
pageSize = 20;
}
if (desc == null) {
desc = true;
}
if (endTime == null) {
endTime = System.currentTimeMillis();
}
if (startTime == null) {
startTime = endTime - 1000 * 60 * 5;
}
if (endTime - startTime > maxQueryIntervalMs) {
return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
}
List<String> resources = metricStore.listResourcesOfApp(app);
logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size());
if (resources == null || resources.isEmpty()) {
return Result.ofSuccess(null);
}
if (!desc) {
Collections.reverse(resources);
}
if (StringUtil.isNotEmpty(searchKey)) {
List<String> searched = new ArrayList<>();
for (String resource : resources) {
if (resource.contains(searchKey)) {
searched.add(resource);
}
}
resources = searched;
}
int totalPage = (resources.size() + pageSize - 1) / pageSize;
List<String> topResource = new ArrayList<>();
if (pageIndex <= totalPage) {
topResource = resources.subList((pageIndex - 1) * pageSize,
Math.min(pageIndex * pageSize, resources.size()));
}
final Map<String, Iterable<MetricVo>> map = new ConcurrentHashMap<>();
logger.debug("topResource={}", topResource);
long time = System.currentTimeMillis();
for (final String resource : topResource) {
List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(
app, resource, startTime, endTime);
logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size());
List<MetricVo> vos = MetricVo.fromMetricEntities(entities, resource);
Iterable<MetricVo> vosSorted = sortMetricVoAndDistinct(vos);
map.put(resource, vosSorted);
}
logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time);
Map<String, Object> resultMap = new HashMap<>(16);
resultMap.put("totalCount", resources.size());
resultMap.put("totalPage", totalPage);
resultMap.put("pageIndex", pageIndex);
resultMap.put("pageSize", pageSize);
Map<String, Iterable<MetricVo>> map2 = new LinkedHashMap<>();
// order matters.
for (String identity : topResource) {
map2.put(identity, map.get(identity));
}
resultMap.put("metric", map2);
return Result.ofSuccess(resultMap);
}
@ResponseBody
@RequestMapping("/queryByAppAndResource.json")
public Result<?> queryByAppAndResource(String app, String identity, Long startTime, Long endTime) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isEmpty(identity)) {
return Result.ofFail(-1, "identity can't be null or empty");
}
if (endTime == null) {
endTime = System.currentTimeMillis();
}
if (startTime == null) {
startTime = endTime - 1000 * 60;
}
if (endTime - startTime > maxQueryIntervalMs) {
return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
}
List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(
app, identity, startTime, endTime);
List<MetricVo> vos = MetricVo.fromMetricEntities(entities, identity);
return Result.ofSuccess(sortMetricVoAndDistinct(vos));
}
private Iterable<MetricVo> sortMetricVoAndDistinct(List<MetricVo> vos) {
if (vos == null) {
return null;
}
Map<Long, MetricVo> map = new TreeMap<>();
for (MetricVo vo : vos) {
MetricVo oldVo = map.get(vo.getTimestamp());
if (oldVo == null || vo.getGmtCreate() > oldVo.getGmtCreate()) {
map.put(vo.getTimestamp(), vo);
}
}
return map.values();
}
}

View File

@ -0,0 +1,259 @@
/*
* 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;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.alibaba.csp.sentinel.dashboard.util.VersionUtils;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
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 java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {
private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository<ParamFlowRuleEntity, Long> repository;
private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version020)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
.thenApply(repository::saveAll)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when querying parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when querying parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}
@PostMapping("/rule")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<ParamFlowRuleEntity> apiAddParamFlowRule(@RequestBody ParamFlowRuleEntity entity) {
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(null);
entity.getRule().setResource(entity.getResource().trim());
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when adding new parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when adding new parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (entity.getCount() < 0) {
return Result.ofFail(-1, "count should be valid");
}
if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) {
return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
}
if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
return Result.ofFail(-1, "paramIdx should be valid");
}
if (entity.getDurationInSec() <= 0) {
return Result.ofFail(-1, "durationInSec should be valid");
}
if (entity.getControlBehavior() < 0) {
return Result.ofFail(-1, "controlBehavior should be valid");
}
return null;
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody ParamFlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when updating parameter flow rules, id=" + id, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();
return Result.ofSuccess(id);
} catch (ExecutionException ex) {
logger.error("Error when deleting parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when deleting parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
}
private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041,
"Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
}
private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}

View File

@ -0,0 +1,91 @@
/*
* 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;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.command.vo.NodeVo;
import com.alibaba.csp.sentinel.dashboard.domain.ResourceTreeNode;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.domain.vo.ResourceVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Carpenter Lee
*/
@RestController
@RequestMapping(value = "/resource")
public class ResourceController {
private static Logger logger = LoggerFactory.getLogger(ResourceController.class);
@Autowired
private SentinelApiClient httpFetcher;
/**
* Fetch real time statistics info of the machine.
*
* @param ip ip to fetch
* @param port port of the ip
* @param type one of [root, default, cluster], 'root' means fetching from tree root node, 'default' means
* fetching from tree default node, 'cluster' means fetching from cluster node.
* @param searchKey key to search
* @return node statistics info.
*/
@GetMapping("/machineResource.json")
public Result<List<ResourceVo>> fetchResourceChainListOfMachine(String ip, Integer port, String type,
String searchKey) {
if (StringUtil.isEmpty(ip) || port == null) {
return Result.ofFail(-1, "invalid param, give ip, port");
}
final String ROOT = "root";
final String DEFAULT = "default";
if (StringUtil.isEmpty(type)) {
type = ROOT;
}
if (ROOT.equalsIgnoreCase(type) || DEFAULT.equalsIgnoreCase(type)) {
List<NodeVo> nodeVos = httpFetcher.fetchResourceOfMachine(ip, port, type);
if (nodeVos == null) {
return Result.ofSuccess(null);
}
ResourceTreeNode treeNode = ResourceTreeNode.fromNodeVoList(nodeVos);
treeNode.searchIgnoreCase(searchKey);
return Result.ofSuccess(ResourceVo.fromResourceTreeNode(treeNode));
} else {
// Normal (cluster node).
List<NodeVo> nodeVos = httpFetcher.fetchClusterNodeOfMachine(ip, port, true);
if (nodeVos == null) {
return Result.ofSuccess(null);
}
if (StringUtil.isNotEmpty(searchKey)) {
nodeVos = nodeVos.stream().filter(node -> node.getResource()
.toLowerCase().contains(searchKey.toLowerCase()))
.collect(Collectors.toList());
}
return Result.ofSuccess(ResourceVo.fromNodeVoList(nodeVos));
}
}
}

View File

@ -0,0 +1,250 @@
/*
* 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;
import java.util.Date;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author leyou(lihao)
*/
@RestController
@RequestMapping("/system")
public class SystemController {
private final Logger logger = LoggerFactory.getLogger(SystemController.class);
@Autowired
private RuleRepository<SystemRuleEntity, Long> repository;
@Autowired
private SentinelApiClient sentinelApiClient;
private <R> Result<R> checkBasicParams(String app, String ip, Integer port) {
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");
}
if (port <= 0 || port > 65535) {
return Result.ofFail(-1, "port should be in (0, 65535)");
}
return null;
}
@GetMapping("/rules.json")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<SystemRuleEntity>> apiQueryMachineRules(String app, String ip,
Integer port) {
Result<List<SystemRuleEntity>> checkResult = checkBasicParams(app, ip, port);
if (checkResult != null) {
return checkResult;
}
try {
List<SystemRuleEntity> rules = sentinelApiClient.fetchSystemRuleOfMachine(app, ip, port);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Query machine system rules error", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private int countNotNullAndNotNegative(Number... values) {
int notNullCount = 0;
for (int i = 0; i < values.length; i++) {
if (values[i] != null && values[i].doubleValue() >= 0) {
notNullCount++;
}
}
return notNullCount;
}
@RequestMapping("/new.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<SystemRuleEntity> apiAdd(String app, String ip, Integer port,
Double highestSystemLoad, Double highestCpuUsage, Long avgRt,
Long maxThread, Double qps) {
Result<SystemRuleEntity> checkResult = checkBasicParams(app, ip, port);
if (checkResult != null) {
return checkResult;
}
int notNullCount = countNotNullAndNotNegative(highestSystemLoad, avgRt, maxThread, qps, highestCpuUsage);
if (notNullCount != 1) {
return Result.ofFail(-1, "only one of [highestSystemLoad, avgRt, maxThread, qps,highestCpuUsage] "
+ "value must be set > 0, but " + notNullCount + " values get");
}
if (null != highestCpuUsage && highestCpuUsage > 1) {
return Result.ofFail(-1, "highestCpuUsage must between [0.0, 1.0]");
}
SystemRuleEntity entity = new SystemRuleEntity();
entity.setApp(app.trim());
entity.setIp(ip.trim());
entity.setPort(port);
// -1 is a fake value
if (null != highestSystemLoad) {
entity.setHighestSystemLoad(highestSystemLoad);
} else {
entity.setHighestSystemLoad(-1D);
}
if (null != highestCpuUsage) {
entity.setHighestCpuUsage(highestCpuUsage);
} else {
entity.setHighestCpuUsage(-1D);
}
if (avgRt != null) {
entity.setAvgRt(avgRt);
} else {
entity.setAvgRt(-1L);
}
if (maxThread != null) {
entity.setMaxThread(maxThread);
} else {
entity.setMaxThread(-1L);
}
if (qps != null) {
entity.setQps(qps);
} else {
entity.setQps(-1D);
}
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("Add SystemRule error", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(app, ip, port)) {
logger.warn("Publish system rules fail after rule add");
}
return Result.ofSuccess(entity);
}
@GetMapping("/save.json")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<SystemRuleEntity> apiUpdateIfNotNull(Long id, String app, Double highestSystemLoad,
Double highestCpuUsage, Long avgRt, Long maxThread, Double qps) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
SystemRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (highestSystemLoad != null) {
if (highestSystemLoad < 0) {
return Result.ofFail(-1, "highestSystemLoad must >= 0");
}
entity.setHighestSystemLoad(highestSystemLoad);
}
if (highestCpuUsage != null) {
if (highestCpuUsage < 0) {
return Result.ofFail(-1, "highestCpuUsage must >= 0");
}
if (highestCpuUsage > 1) {
return Result.ofFail(-1, "highestCpuUsage must <= 1");
}
entity.setHighestCpuUsage(highestCpuUsage);
}
if (avgRt != null) {
if (avgRt < 0) {
return Result.ofFail(-1, "avgRt must >= 0");
}
entity.setAvgRt(avgRt);
}
if (maxThread != null) {
if (maxThread < 0) {
return Result.ofFail(-1, "maxThread must >= 0");
}
entity.setMaxThread(maxThread);
}
if (qps != null) {
if (qps < 0) {
return Result.ofFail(-1, "qps must >= 0");
}
entity.setQps(qps);
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("save error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("publish system rules fail after rule update");
}
return Result.ofSuccess(entity);
}
@RequestMapping("/delete.json")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<?> delete(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
SystemRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Throwable throwable) {
logger.error("delete error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.info("publish system rules fail after rule delete");
}
return Result.ofSuccess(id);
}
private boolean publishRules(String app, String ip, Integer port) {
List<SystemRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setSystemRuleOfMachine(app, ip, port, rules);
}
}

View File

@ -0,0 +1,49 @@
/*
* 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;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author hisenyuan
* @since 1.7.0
*/
@RestController
public class VersionController {
private static final String VERSION_PATTERN = "-";
@Value("${sentinel.dashboard.version:}")
private String sentinelDashboardVersion;
@GetMapping("/version")
public Result<String> apiGetVersion() {
if (StringUtil.isNotBlank(sentinelDashboardVersion)) {
String res = sentinelDashboardVersion;
if (sentinelDashboardVersion.contains(VERSION_PATTERN)) {
res = sentinelDashboardVersion.substring(0, sentinelDashboardVersion.indexOf(VERSION_PATTERN));
}
return Result.ofSuccess(res);
} else {
return Result.ofFail(1, "getVersion failed: empty version");
}
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.cluster;
import java.util.Collections;
import java.util.Set;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppFullAssignRequest;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppSingleServerAssignRequest;
import com.alibaba.csp.sentinel.dashboard.service.ClusterAssignService;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Eric Zhao
* @since 1.4.1
*/
@RestController
@RequestMapping("/cluster/assign")
public class ClusterAssignController {
private final Logger logger = LoggerFactory.getLogger(ClusterAssignController.class);
@Autowired
private ClusterAssignService clusterAssignService;
@PostMapping("/all_server/{app}")
public Result<ClusterAppAssignResultVO> apiAssignAllClusterServersOfApp(@PathVariable String app,
@RequestBody
ClusterAppFullAssignRequest assignRequest) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (assignRequest == null || assignRequest.getClusterMap() == null
|| assignRequest.getRemainingList() == null) {
return Result.ofFail(-1, "bad request body");
}
try {
return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, assignRequest.getClusterMap(),
assignRequest.getRemainingList()));
} catch (Throwable throwable) {
logger.error("Error when assigning full cluster servers for app: " + app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@PostMapping("/single_server/{app}")
public Result<ClusterAppAssignResultVO> apiAssignSingleClusterServersOfApp(@PathVariable String app,
@RequestBody ClusterAppSingleServerAssignRequest assignRequest) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (assignRequest == null || assignRequest.getClusterMap() == null) {
return Result.ofFail(-1, "bad request body");
}
try {
return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, Collections.singletonList(assignRequest.getClusterMap()),
assignRequest.getRemainingList()));
} catch (Throwable throwable) {
logger.error("Error when assigning single cluster servers for app: " + app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@PostMapping("/unbind_server/{app}")
public Result<ClusterAppAssignResultVO> apiUnbindClusterServersOfApp(@PathVariable String app,
@RequestBody Set<String> machineIds) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (machineIds == null || machineIds.isEmpty()) {
return Result.ofFail(-1, "bad request body");
}
try {
return Result.ofSuccess(clusterAssignService.unbindClusterServers(app, machineIds));
} catch (Throwable throwable) {
logger.error("Error when unbinding cluster server {} for app <{}>", machineIds, app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
}

View File

@ -0,0 +1,245 @@
/*
* 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.cluster;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterClientModifyRequest;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterModifyRequest;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStateVO;
import com.alibaba.csp.sentinel.dashboard.service.ClusterConfigService;
import com.alibaba.csp.sentinel.dashboard.util.ClusterEntityUtils;
import com.alibaba.csp.sentinel.dashboard.util.VersionUtils;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/cluster")
public class ClusterConfigController {
private final Logger logger = LoggerFactory.getLogger(ClusterConfigController.class);
private final SentinelVersion version140 = new SentinelVersion().setMajorVersion(1).setMinorVersion(4);
@Autowired
private AppManagement appManagement;
@Autowired
private ClusterConfigService clusterConfigService;
@PostMapping("/config/modify_single")
public Result<Boolean> apiModifyClusterConfig(@RequestBody String payload) {
if (StringUtil.isBlank(payload)) {
return Result.ofFail(-1, "empty request body");
}
try {
JSONObject body = JSON.parseObject(payload);
if (body.containsKey(KEY_MODE)) {
int mode = body.getInteger(KEY_MODE);
switch (mode) {
case ClusterStateManager.CLUSTER_CLIENT:
ClusterClientModifyRequest data = JSON.parseObject(payload, ClusterClientModifyRequest.class);
Result<Boolean> res = checkValidRequest(data);
if (res != null) {
return res;
}
clusterConfigService.modifyClusterClientConfig(data).get();
return Result.ofSuccess(true);
case ClusterStateManager.CLUSTER_SERVER:
ClusterServerModifyRequest d = JSON.parseObject(payload, ClusterServerModifyRequest.class);
Result<Boolean> r = checkValidRequest(d);
if (r != null) {
return r;
}
// TODO: bad design here, should refactor!
clusterConfigService.modifyClusterServerConfig(d).get();
return Result.ofSuccess(true);
default:
return Result.ofFail(-1, "invalid mode");
}
}
return Result.ofFail(-1, "invalid parameter");
} catch (ExecutionException ex) {
logger.error("Error when modifying cluster config", ex.getCause());
return errorResponse(ex);
} catch (Throwable ex) {
logger.error("Error when modifying cluster config", ex);
return Result.ofFail(-1, ex.getMessage());
}
}
private <T> Result<T> errorResponse(ExecutionException ex) {
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
}
@GetMapping("/state_single")
public Result<ClusterUniversalStateVO> apiGetClusterState(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
return clusterConfigService.getClusterUniversalState(app, ip, port)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when fetching cluster state", ex.getCause());
return errorResponse(ex);
} catch (Throwable throwable) {
logger.error("Error when fetching cluster state", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@GetMapping("/server_state/{app}")
public Result<List<AppClusterServerStateWrapVO>> apiGetClusterServerStateOfApp(@PathVariable String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
try {
return clusterConfigService.getClusterUniversalState(app)
.thenApply(ClusterEntityUtils::wrapToAppClusterServerState)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when fetching cluster server state of app: " + app, ex.getCause());
return errorResponse(ex);
} catch (Throwable throwable) {
logger.error("Error when fetching cluster server state of app: " + app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@GetMapping("/client_state/{app}")
public Result<List<AppClusterClientStateWrapVO>> apiGetClusterClientStateOfApp(@PathVariable String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
try {
return clusterConfigService.getClusterUniversalState(app)
.thenApply(ClusterEntityUtils::wrapToAppClusterClientState)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when fetching cluster token client state of app: " + app, ex.getCause());
return errorResponse(ex);
} catch (Throwable throwable) {
logger.error("Error when fetching cluster token client state of app: " + app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@GetMapping("/state/{app}")
public Result<List<ClusterUniversalStatePairVO>> apiGetClusterStateOfApp(@PathVariable String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
try {
return clusterConfigService.getClusterUniversalState(app)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when fetching cluster state of app: " + app, ex.getCause());
return errorResponse(ex);
} catch (Throwable throwable) {
logger.error("Error when fetching cluster state of app: " + app, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}
private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version140)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}
private Result<Boolean> checkValidRequest(ClusterModifyRequest request) {
if (StringUtil.isEmpty(request.getApp())) {
return Result.ofFail(-1, "app cannot be empty");
}
if (StringUtil.isEmpty(request.getIp())) {
return Result.ofFail(-1, "ip cannot be empty");
}
if (request.getPort() == null || request.getPort() < 0) {
return Result.ofFail(-1, "invalid port");
}
if (request.getMode() == null || request.getMode() < 0) {
return Result.ofFail(-1, "invalid mode");
}
if (!checkIfSupported(request.getApp(), request.getIp(), request.getPort())) {
return unsupportedVersion();
}
return null;
}
private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041, "Sentinel client not supported for cluster flow control (unsupported version or dependency absent)");
}
private static final String KEY_MODE = "mode";
}

View File

@ -0,0 +1,260 @@
/*
* 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.AuthAction;
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;
@GetMapping("/list.json")
@AuthAction(AuthService.PrivilegeType.READ_RULE)
public Result<List<ApiDefinitionEntity>> queryApis(String app, String ip, Integer port) {
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")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
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")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<ApiDefinitionEntity> updateApi(@RequestBody UpdateApiReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
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")
@AuthAction(AuthService.PrivilegeType.DELETE_RULE)
public Result<Long> deleteApi(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
ApiDefinitionEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
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);
}
}

View File

@ -0,0 +1,433 @@
/*
* 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.AuthAction;
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 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;
@GetMapping("/list.json")
@AuthAction(AuthService.PrivilegeType.READ_RULE)
public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) {
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")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<GatewayFlowRuleEntity> addFlowRule(@RequestBody AddFlowRuleReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
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")
@AuthAction(AuthService.PrivilegeType.WRITE_RULE)
public Result<GatewayFlowRuleEntity> updateFlowRule(@RequestBody UpdateFlowRuleReqVo reqVo) {
String app = reqVo.getApp();
if (StringUtil.isBlank(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
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")
@AuthAction(AuthService.PrivilegeType.DELETE_RULE)
public Result<Long> deleteFlowRule(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
GatewayFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
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);
}
}

View File

@ -0,0 +1,226 @@
/*
* 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.v2;
import java.util.Date;
import java.util.List;
import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Flow rule controller (v2).
*
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
@GetMapping("/rules")
@AuthAction(PrivilegeType.READ_RULE)
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
try {
List<FlowRuleEntity> rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
@AuthAction(value = PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
try {
entity = repository.save(entity);
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
@AuthAction(PrivilegeType.WRITE_RULE)
public Result<FlowRuleEntity> apiUpdateFlowRule(@PathVariable("id") Long id,
@RequestBody FlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
@AuthAction(PrivilegeType.DELETE_RULE)
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<FlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}

View File

@ -0,0 +1,106 @@
/*
* 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;
import java.util.Date;
import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo;
/**
* @author leyou
*/
public class ApplicationEntity {
private Long id;
private Date gmtCreate;
private Date gmtModified;
private String app;
private Integer appType;
private String activeConsole;
private Date lastFetch;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
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;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public Integer getAppType() {
return appType;
}
public void setAppType(Integer appType) {
this.appType = appType;
}
public String getActiveConsole() {
return activeConsole;
}
public Date getLastFetch() {
return lastFetch;
}
public void setLastFetch(Date lastFetch) {
this.lastFetch = lastFetch;
}
public void setActiveConsole(String activeConsole) {
this.activeConsole = activeConsole;
}
public AppInfo toAppInfo() {
return new AppInfo(app, appType);
}
@Override
public String toString() {
return "ApplicationEntity{" +
"id=" + id +
", gmtCreate=" + gmtCreate +
", gmtModified=" + gmtModified +
", app='" + app + '\'' +
", activeConsole='" + activeConsole + '\'' +
", lastFetch=" + lastFetch +
'}';
}
}

View File

@ -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.datasource.entity;
import java.util.Date;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
/**
* @author leyou
*/
public class MachineEntity {
private Long id;
private Date gmtCreate;
private Date gmtModified;
private String app;
private String ip;
private String hostname;
private Date timestamp;
private Integer port;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
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;
}
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 String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public MachineInfo toMachineInfo() {
MachineInfo machineInfo = new MachineInfo();
machineInfo.setApp(app);
machineInfo.setHostname(hostname);
machineInfo.setIp(ip);
machineInfo.setPort(port);
machineInfo.setLastHeartbeat(timestamp.getTime());
machineInfo.setHeartbeatVersion(timestamp.getTime());
return machineInfo;
}
@Override
public String toString() {
return "MachineEntity{" +
"id=" + id +
", gmtCreate=" + gmtCreate +
", gmtModified=" + gmtModified +
", app='" + app + '\'' +
", ip='" + ip + '\'' +
", hostname='" + hostname + '\'' +
", timestamp=" + timestamp +
", port=" + port +
'}';
}
}

View File

@ -0,0 +1,220 @@
/*
* 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;
import java.util.Date;
/**
* @author leyou
*/
public class MetricEntity {
private Long id;
private Date gmtCreate;
private Date gmtModified;
private String app;
/**
* 监控信息的时间戳
*/
private Date timestamp;
private String resource;
private Long passQps;
private Long successQps;
private Long blockQps;
private Long exceptionQps;
/**
* summary rt of all success exit qps.
*/
private double rt;
/**
* 本次聚合的总条数
*/
private int count;
private int resourceCode;
public static MetricEntity copyOf(MetricEntity oldEntity) {
MetricEntity entity = new MetricEntity();
entity.setId(oldEntity.getId());
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(oldEntity.getGmtModified());
entity.setApp(oldEntity.getApp());
entity.setTimestamp(oldEntity.getTimestamp());
entity.setResource(oldEntity.getResource());
entity.setPassQps(oldEntity.getPassQps());
entity.setBlockQps(oldEntity.getBlockQps());
entity.setSuccessQps(oldEntity.getSuccessQps());
entity.setExceptionQps(oldEntity.getExceptionQps());
entity.setRt(oldEntity.getRt());
entity.setCount(oldEntity.getCount());
entity.setResource(oldEntity.getResource());
return entity;
}
public synchronized void addPassQps(Long passQps) {
this.passQps += passQps;
}
public synchronized void addBlockQps(Long blockQps) {
this.blockQps += blockQps;
}
public synchronized void addExceptionQps(Long exceptionQps) {
this.exceptionQps += exceptionQps;
}
public synchronized void addCount(int count) {
this.count += count;
}
public synchronized void addRtAndSuccessQps(double avgRt, Long successQps) {
this.rt += avgRt * successQps;
this.successQps += successQps;
}
/**
* {@link #rt} = {@code avgRt * successQps}
*
* @param avgRt average rt of {@code successQps}
* @param successQps
*/
public synchronized void setRtAndSuccessQps(double avgRt, Long successQps) {
this.rt = avgRt * successQps;
this.successQps = successQps;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public String getResource() {
return resource;
}
public void setResource(String resource) {
this.resource = resource;
this.resourceCode = resource.hashCode();
}
public Long getPassQps() {
return passQps;
}
public void setPassQps(Long passQps) {
this.passQps = passQps;
}
public Long getBlockQps() {
return blockQps;
}
public void setBlockQps(Long blockQps) {
this.blockQps = blockQps;
}
public Long getExceptionQps() {
return exceptionQps;
}
public void setExceptionQps(Long exceptionQps) {
this.exceptionQps = exceptionQps;
}
public double getRt() {
return rt;
}
public void setRt(double rt) {
this.rt = rt;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getResourceCode() {
return resourceCode;
}
public Long getSuccessQps() {
return successQps;
}
public void setSuccessQps(Long successQps) {
this.successQps = successQps;
}
@Override
public String toString() {
return "MetricEntity{" +
"id=" + id +
", gmtCreate=" + gmtCreate +
", gmtModified=" + gmtModified +
", app='" + app + '\'' +
", timestamp=" + timestamp +
", resource='" + resource + '\'' +
", passQps=" + passQps +
", blockQps=" + blockQps +
", successQps=" + successQps +
", exceptionQps=" + exceptionQps +
", rt=" + rt +
", count=" + count +
", resourceCode=" + resourceCode +
'}';
}
}

View File

@ -0,0 +1,121 @@
/*
* 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;
import java.util.Date;
/**
* @author leyou
*/
public class MetricPositionEntity {
private long id;
private Date gmtCreate;
private Date gmtModified;
private String app;
private String ip;
/**
* Sentinel在该应用上使用的端口
*/
private int port;
/**
* 机器名冗余字段
*/
private String hostname;
/**
* 上一次拉取的最晚时间戳
*/
private Date lastFetch;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
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;
}
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 int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public Date getLastFetch() {
return lastFetch;
}
public void setLastFetch(Date lastFetch) {
this.lastFetch = lastFetch;
}
@Override
public String toString() {
return "MetricPositionEntity{" +
"id=" + id +
", gmtCreate=" + gmtCreate +
", gmtModified=" + gmtModified +
", app='" + app + '\'' +
", ip='" + ip + '\'' +
", port=" + port +
", hostname='" + hostname + '\'' +
", lastFetch=" + lastFetch +
'}';
}
}

View File

@ -0,0 +1,129 @@
/*
* 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;
/**
* @author Eric Zhao
* @since 0.2.1
*/
public class SentinelVersion {
private int majorVersion;
private int minorVersion;
private int fixVersion;
private String postfix;
public SentinelVersion() {
this(0, 0, 0);
}
public SentinelVersion(int major, int minor, int fix) {
this(major, minor, fix, null);
}
public SentinelVersion(int major, int minor, int fix, String postfix) {
this.majorVersion = major;
this.minorVersion = minor;
this.fixVersion = fix;
this.postfix = postfix;
}
/**
* 000, 000, 000
*/
public int getFullVersion() {
return majorVersion * 1000000 + minorVersion * 1000 + fixVersion;
}
public int getMajorVersion() {
return majorVersion;
}
public SentinelVersion setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
return this;
}
public int getMinorVersion() {
return minorVersion;
}
public SentinelVersion setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
return this;
}
public int getFixVersion() {
return fixVersion;
}
public SentinelVersion setFixVersion(int fixVersion) {
this.fixVersion = fixVersion;
return this;
}
public String getPostfix() {
return postfix;
}
public SentinelVersion setPostfix(String postfix) {
this.postfix = postfix;
return this;
}
public boolean greaterThan(SentinelVersion version) {
if (version == null) {
return true;
}
return getFullVersion() > version.getFullVersion();
}
public boolean greaterOrEqual(SentinelVersion version) {
if (version == null) {
return true;
}
return getFullVersion() >= version.getFullVersion();
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
SentinelVersion that = (SentinelVersion)o;
if (getFullVersion() != that.getFullVersion()) { return false; }
return postfix != null ? postfix.equals(that.postfix) : that.postfix == null;
}
@Override
public int hashCode() {
int result = majorVersion;
result = 31 * result + minorVersion;
result = 31 * result + fixVersion;
result = 31 * result + (postfix != null ? postfix.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "SentinelVersion{" +
"majorVersion=" + majorVersion +
", minorVersion=" + minorVersion +
", fixVersion=" + fixVersion +
", postfix='" + postfix + '\'' +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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 +
'}';
}
}

Some files were not shown because too many files have changed in this diff Show More