mirror of
https://gitee.com/log4j/pig.git
synced 2024-12-22 20:54:25 +08:00
✨ Introducing new features. up springboot 3.1.0
This commit is contained in:
parent
4aaf739854
commit
8e2a302a73
@ -1 +0,0 @@
|
|||||||
java-baseline=8
|
|
55
README.md
55
README.md
@ -1,54 +1,11 @@
|
|||||||
<p align="center">
|
|
||||||
<img src="https://img.shields.io/badge/Pig-3.6-success.svg" alt="Build Status">
|
|
||||||
<img src="https://img.shields.io/badge/Spring%20Cloud-2021-blue.svg" alt="Coverage Status">
|
|
||||||
<img src="https://img.shields.io/badge/Spring%20Boot-2.7-blue.svg" alt="Downloads">
|
|
||||||
<img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
|
|
||||||
<img src="https://img.shields.io/github/license/pig-mesh/pig"/>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## 系统说明
|
|
||||||
|
|
||||||
- 基于 Spring Cloud 2021 、Spring Boot 2.7、 OAuth2 的 RBAC **权限管理系统**
|
|
||||||
- 基于数据驱动视图的理念封装 element-plus,即使没有 vue 的使用经验也能快速上手
|
|
||||||
- 提供对常见容器化支持 Docker、Kubernetes、Rancher2 支持
|
|
||||||
- 提供 lambda 、stream api 、webflux 的生产实践
|
|
||||||
|
|
||||||
|
|
||||||
## 文档视频
|
|
||||||
|
|
||||||
[ 🚀🚀🚀 低代码数据可视化](http://datav.avuejs.com)
|
|
||||||
|
|
||||||
[ 配套文档 wiki.pigx.vip](https://wiki.pigx.vip)
|
|
||||||
|
|
||||||
[ 配套视频 tv.pigx.vip](https://www.bilibili.com/video/BV12t411B7e9)
|
|
||||||
|
|
||||||
[PIGX 在线体验 pigx.pigx.vip](http://pigx.pigx.vip)
|
|
||||||
|
|
||||||
[产品白皮书 paper.pigx.vip](https://paper.pigx.vip)
|
|
||||||
|
|
||||||
## 微信群 [禁广告]
|
|
||||||
|
|
||||||
![](https://minio.pigx.vip/oss/1648184189.png)
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
### 分支说明
|
|
||||||
|
|
||||||
|
|
||||||
| 分支 | 说明 |
|
|
||||||
|-----------------|-------------------------------------------------------------------|
|
|
||||||
| master | java8 + springboot 2.7 + springcloud 2021 |
|
|
||||||
| sca-springboot3 | java17 + springboot 3.0 + springcloud 2022 + spring cloud alibaba |
|
|
||||||
| sct-springboot3 | java17 + springboot 3.0 + springcloud 2022 + spring cloud tencent |
|
|
||||||
|
|
||||||
### 核心依赖
|
### 核心依赖
|
||||||
|
|
||||||
| 依赖 | 版本 |
|
| 依赖 | 版本 |
|
||||||
| ---------------------- |----------------|
|
| ---------------------- |----------------|
|
||||||
| Spring Boot | 3.0.6 |
|
| Spring Boot | 3.1.0 |
|
||||||
| Spring Cloud | 2022.0.2 |
|
| Spring Cloud | 2022.0.3 |
|
||||||
| Spring Cloud Alibaba | 2022.0.0.0-RC1 |
|
| Spring Cloud Alibaba | 2022.0.0.0-RC2 |
|
||||||
| Spring Authorization Server | 1.0.2 |
|
| Spring Authorization Server | 1.1.0 |
|
||||||
| Mybatis Plus | 3.5.3.1 |
|
| Mybatis Plus | 3.5.3.1 |
|
||||||
| hutool | 5.8.18 |
|
| hutool | 5.8.18 |
|
||||||
|
|
||||||
@ -78,9 +35,7 @@ pig
|
|||||||
└── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000]
|
└── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000]
|
||||||
└── pig-visual
|
└── pig-visual
|
||||||
└── pig-monitor -- 服务监控 [5001]
|
└── pig-monitor -- 服务监控 [5001]
|
||||||
├── pig-codegen -- 图形化代码生成 [5002]
|
└── pig-codegen -- 图形化代码生成 [5002]
|
||||||
├── pig-sentinel-dashboard -- 流量高可用 [5003]
|
|
||||||
└── pig-xxl-job-admin -- 分布式定时任务管理台 [5004]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 本地开发 运行
|
### 本地开发 运行
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM mysql/mysql-server:8.0.31
|
FROM mysql/mysql-server:8.0.33
|
||||||
|
|
||||||
MAINTAINER lengleng(wangiegie@gmail.com)
|
MAINTAINER lengleng(wangiegie@gmail.com)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ CREATE TABLE `config_info` (
|
|||||||
-- Records of config_info
|
-- Records of config_info
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 加解密根密码\njasypt:\n encryptor:\n password: pig\n algorithm: PBEWithMD5AndDES\n iv-generator-classname: org.jasypt.iv.NoIvGenerator\n \n# Spring 相关\nspring:\n cache:\n type: redis\n data:\n redis:\n host: pig-redis\n port: 6379\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 endpoint:\n health:\n show-details: ALWAYS\n\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# 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 # 通用放行URL,服务个性化,请在对应配置文件覆盖\n ignore:\n urls:\n - /v3/api-docs\n - /actuator/**\n\n# swagger 配置\nswagger:\n enabled: true\n title: Pig Swagger API\n gateway: http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}\n token-url: ${swagger.gateway}/auth/oauth2/token\n scope: server\n services:\n pig-upms-biz: admin\n pig-codegen: gen', 'f2d0080b18e6872e694764b687d936f8', '2022-05-08 12:10:37', '2022-12-23 16:24:33', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
INSERT INTO `config_info` VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 配置文件加密根密码\njasypt:\n encryptor:\n password: pig\n algorithm: PBEWithMD5AndDES\n iv-generator-classname: org.jasypt.iv.NoIvGenerator\n \n# Spring 相关\nspring:\n cache:\n type: redis\n redis:\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 endpoint:\n health:\n show-details: ALWAYS\n\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# 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# swagger 配置\nswagger:\n enabled: true\n title: Pig Swagger API\n gateway: http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}\n token-url: ${swagger.gateway}/auth/oauth2/token\n scope: server', 'c9b41bce3b5ce5802d6592cab001f791', '2022-05-08 12:10:37', '2023-05-26 13:57:59', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
||||||
INSERT INTO `config_info` VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\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/', '74f53b71c7799aa754da75662378b93c', '2022-05-08 12:10:37', '2022-06-04 14:15:35', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
INSERT INTO `config_info` VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\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/', '74f53b71c7799aa754da75662378b93c', '2022-05-08 12:10:37', '2022-06-04 14:15:35', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
||||||
INSERT INTO `config_info` VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '## spring security 配置1\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&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n resources:\n static-locations: classpath:/static/,classpath:/views/\n', '6c6e48b793671dc2a0adffe2e2c27fb0', '2022-05-08 12:10:37', '2022-12-15 23:05:35', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
INSERT INTO `config_info` VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '## spring security 配置1\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&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n resources:\n static-locations: classpath:/static/,classpath:/views/\n', '6c6e48b793671dc2a0adffe2e2c27fb0', '2022-05-08 12:10:37', '2022-12-15 23:05:35', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
||||||
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: 100\n redis-rate-limiter.burstCapacity: 200\n # 代码生成模块\n - id: pig-codegen\n uri: lb://pig-codegen\n predicates:\n - Path=/gen/**\n # 固定路由转发配置 无修改\n - id: openapi\n uri: lb://pig-gateway\n predicates:\n - Path=/v3/api-docs/**\n filters:\n - RewritePath=/v3/api-docs/(?<path>.*), /$\\{path}/$\\{path}/v3/api-docs\n\ngateway:\n encode-key: \'thanks,pig4cloud\'\n ignore-clients:\n - test\n - client', '000988cf0102382d3f23df35027b47fd', '2022-05-08 12:10:37', '2022-06-07 14:00:11', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
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: 100\n redis-rate-limiter.burstCapacity: 200\n # 代码生成模块\n - id: pig-codegen\n uri: lb://pig-codegen\n predicates:\n - Path=/gen/**\n # 固定路由转发配置 无修改\n - id: openapi\n uri: lb://pig-gateway\n predicates:\n - Path=/v3/api-docs/**\n filters:\n - RewritePath=/v3/api-docs/(?<path>.*), /$\\{path}/$\\{path}/v3/api-docs\n\ngateway:\n encode-key: \'thanks,pig4cloud\'\n ignore-clients:\n - test\n - client', '000988cf0102382d3f23df35027b47fd', '2022-05-08 12:10:37', '2022-06-07 14:00:11', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
|
||||||
|
120
db/pig_job.sql
120
db/pig_job.sql
@ -1,120 +0,0 @@
|
|||||||
DROP DATABASE IF EXISTS `pig_job`;
|
|
||||||
|
|
||||||
CREATE DATABASE `pig_job` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
|
|
||||||
|
|
||||||
use `pig_job`;
|
|
||||||
|
|
||||||
SET NAMES utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_info` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
|
||||||
`job_desc` varchar(255) NOT NULL,
|
|
||||||
`add_time` datetime DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
`author` varchar(64) DEFAULT NULL COMMENT '作者',
|
|
||||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件',
|
|
||||||
`schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型',
|
|
||||||
`schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型',
|
|
||||||
`misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略',
|
|
||||||
`executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略',
|
|
||||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
|
||||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
|
||||||
`executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略',
|
|
||||||
`executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
|
|
||||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
|
||||||
`glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型',
|
|
||||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
|
||||||
`glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注',
|
|
||||||
`glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间',
|
|
||||||
`child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔',
|
|
||||||
`trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
|
|
||||||
`trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间',
|
|
||||||
`trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间',
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_log` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
||||||
`job_group` int(11) NOT NULL COMMENT '执行器主键ID',
|
|
||||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
|
||||||
`executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址',
|
|
||||||
`executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler',
|
|
||||||
`executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数',
|
|
||||||
`executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '执行器任务分片参数,格式如 1/2',
|
|
||||||
`executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
|
|
||||||
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
|
|
||||||
`trigger_code` int(11) NOT NULL COMMENT '调度-结果',
|
|
||||||
`trigger_msg` text COMMENT '调度-日志',
|
|
||||||
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
|
|
||||||
`handle_code` int(11) NOT NULL COMMENT '执行-状态',
|
|
||||||
`handle_msg` text COMMENT '执行-日志',
|
|
||||||
`alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `I_trigger_time` (`trigger_time`),
|
|
||||||
KEY `I_handle_code` (`handle_code`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_log_report` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
|
|
||||||
`running_count` int(11) NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
|
|
||||||
`suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
|
|
||||||
`fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_logglue` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`job_id` int(11) NOT NULL COMMENT '任务,主键ID',
|
|
||||||
`glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型',
|
|
||||||
`glue_source` mediumtext COMMENT 'GLUE源代码',
|
|
||||||
`glue_remark` varchar(128) NOT NULL COMMENT 'GLUE备注',
|
|
||||||
`add_time` datetime DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_registry` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`registry_group` varchar(50) NOT NULL,
|
|
||||||
`registry_key` varchar(255) NOT NULL,
|
|
||||||
`registry_value` varchar(255) NOT NULL,
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_group` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
|
|
||||||
`title` varchar(12) NOT NULL COMMENT '执行器名称',
|
|
||||||
`address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
|
|
||||||
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_user` (
|
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`username` varchar(50) NOT NULL COMMENT '账号',
|
|
||||||
`password` varchar(50) NOT NULL COMMENT '密码',
|
|
||||||
`role` tinyint(4) NOT NULL COMMENT '角色:0-普通用户、1-管理员',
|
|
||||||
`permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `i_username` (`username`) USING BTREE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
CREATE TABLE `xxl_job_lock` (
|
|
||||||
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
|
|
||||||
PRIMARY KEY (`lock_name`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
||||||
|
|
||||||
INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31' );
|
|
||||||
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '测试任务1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2018-11-03 22:21:31', '');
|
|
||||||
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
|
|
||||||
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');
|
|
||||||
|
|
||||||
commit;
|
|
@ -66,15 +66,6 @@ services:
|
|||||||
hostname: pig-monitor
|
hostname: pig-monitor
|
||||||
image: 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:
|
pig-codegen:
|
||||||
build:
|
build:
|
||||||
context: ./pig-visual/pig-codegen
|
context: ./pig-visual/pig-codegen
|
||||||
@ -82,13 +73,3 @@ services:
|
|||||||
container_name: pig-codegen
|
container_name: pig-codegen
|
||||||
hostname: pig-codegen
|
hostname: pig-codegen
|
||||||
image: pig-codegen
|
image: pig-codegen
|
||||||
|
|
||||||
pig-job:
|
|
||||||
build:
|
|
||||||
context: ./pig-visual/pig-xxl-job-admin
|
|
||||||
restart: always
|
|
||||||
container_name: pig-job
|
|
||||||
hostname: pig-job
|
|
||||||
image: pig-job
|
|
||||||
ports:
|
|
||||||
- 5004:5004
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-auth
|
RUN mkdir -p /pig-auth
|
||||||
|
|
||||||
|
@ -78,11 +78,15 @@ public class AuthorizationServerConfiguration {
|
|||||||
// 自定义接口、端点暴露
|
// 自定义接口、端点暴露
|
||||||
authorizeRequests.requestMatchers("/token/**", "/actuator/**", "/css/**", "/error").permitAll();
|
authorizeRequests.requestMatchers("/token/**", "/actuator/**", "/css/**", "/error").permitAll();
|
||||||
authorizeRequests.anyRequest().authenticated();
|
authorizeRequests.anyRequest().authenticated();
|
||||||
}).apply(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
|
})
|
||||||
|
.apply(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
|
||||||
.authorizationServerSettings(
|
.authorizationServerSettings(
|
||||||
AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()))
|
AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()))
|
||||||
// 授权码登录的登录页个性化
|
// 授权码登录的登录页个性化
|
||||||
.and().apply(new FormIdentityLoginConfigurer()).and().build();
|
.and()
|
||||||
|
.apply(new FormIdentityLoginConfigurer())
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
|
||||||
// 注入自定义授权模式实现
|
// 注入自定义授权模式实现
|
||||||
addCustomOAuth2GrantAuthenticationProvider(http);
|
addCustomOAuth2GrantAuthenticationProvider(http);
|
||||||
|
@ -22,9 +22,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,11 +41,15 @@ public class WebSecurityConfiguration {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers("/token/*").permitAll()// 开放自定义的部分端点
|
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers("/token/*")
|
||||||
.anyRequest().authenticated()).headers(httpSecurityHeadersConfigurer -> {
|
.permitAll()// 开放自定义的部分端点
|
||||||
// 避免iframe同源无法登录
|
.anyRequest()
|
||||||
httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin);
|
.authenticated())
|
||||||
}).apply(new FormIdentityLoginConfigurer()); // 表单登录个性化
|
.headers()
|
||||||
|
.frameOptions()
|
||||||
|
.sameOrigin()// 避免iframe同源无法登录
|
||||||
|
.and()
|
||||||
|
.apply(new FormIdentityLoginConfigurer()); // 表单登录个性化
|
||||||
// 处理 UsernamePasswordAuthenticationToken
|
// 处理 UsernamePasswordAuthenticationToken
|
||||||
http.authenticationProvider(new PigDaoAuthenticationProvider());
|
http.authenticationProvider(new PigDaoAuthenticationProvider());
|
||||||
return http.build();
|
return http.build();
|
||||||
@ -56,7 +57,7 @@ public class WebSecurityConfiguration {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 暴露静态资源
|
* 暴露静态资源
|
||||||
* <p>
|
*
|
||||||
* https://github.com/spring-projects/spring-security/issues/10938
|
* https://github.com/spring-projects/spring-security/issues/10938
|
||||||
* @param http
|
* @param http
|
||||||
* @return
|
* @return
|
||||||
@ -67,8 +68,12 @@ public class WebSecurityConfiguration {
|
|||||||
SecurityFilterChain resources(HttpSecurity http) throws Exception {
|
SecurityFilterChain resources(HttpSecurity http) throws Exception {
|
||||||
http.securityMatchers((matchers) -> matchers.requestMatchers("/actuator/**", "/css/**", "/error"))
|
http.securityMatchers((matchers) -> matchers.requestMatchers("/actuator/**", "/css/**", "/error"))
|
||||||
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll())
|
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll())
|
||||||
.requestCache(RequestCacheConfigurer::disable).securityContext(AbstractHttpConfigurer::disable)
|
.requestCache()
|
||||||
.sessionManagement(AbstractHttpConfigurer::disable);
|
.disable()
|
||||||
|
.securityContext()
|
||||||
|
.disable()
|
||||||
|
.sessionManagement()
|
||||||
|
.disable();
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,8 @@ public class PigTokenEndpoint {
|
|||||||
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
|
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
|
||||||
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
|
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
|
||||||
@RequestParam(OAuth2ParameterNames.STATE) String state) {
|
@RequestParam(OAuth2ParameterNames.STATE) String state) {
|
||||||
SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId)).getData()
|
SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))
|
||||||
|
.getData()
|
||||||
.orElseThrow(() -> new OAuthClientException("clientId 不合法"));
|
.orElseThrow(() -> new OAuthClientException("clientId 不合法"));
|
||||||
|
|
||||||
Set<String> authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());
|
Set<String> authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());
|
||||||
|
@ -83,7 +83,8 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationConverter<T extends O
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 扩展信息
|
// 扩展信息
|
||||||
Map<String, Object> additionalParameters = parameters.entrySet().stream()
|
Map<String, Object> additionalParameters = parameters.entrySet()
|
||||||
|
.stream()
|
||||||
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
|
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
|
||||||
&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
|
&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||||
|
@ -152,7 +152,8 @@ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OA
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization
|
||||||
.withRegisteredClient(registeredClient).principalName(usernamePasswordAuthentication.getName())
|
.withRegisteredClient(registeredClient)
|
||||||
|
.principalName(usernamePasswordAuthentication.getName())
|
||||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||||
// 0.4.0 新增的方法
|
// 0.4.0 新增的方法
|
||||||
.authorizedScopes(authorizedScopes);
|
.authorizedScopes(authorizedScopes);
|
||||||
|
@ -8,7 +8,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
|
|||||||
/**
|
/**
|
||||||
* @author lengleng
|
* @author lengleng
|
||||||
* @data 2022-06-04
|
* @data 2022-06-04
|
||||||
* <p>
|
*
|
||||||
* 基于授权码模式 统一认证登录 spring security & sas 都可以使用 所以抽取成 HttpConfigurer
|
* 基于授权码模式 统一认证登录 spring security & sas 都可以使用 所以抽取成 HttpConfigurer
|
||||||
*/
|
*/
|
||||||
public final class FormIdentityLoginConfigurer
|
public final class FormIdentityLoginConfigurer
|
||||||
@ -21,13 +21,14 @@ public final class FormIdentityLoginConfigurer
|
|||||||
formLogin.loginProcessingUrl("/token/form");
|
formLogin.loginProcessingUrl("/token/form");
|
||||||
formLogin.failureHandler(new FormAuthenticationFailureHandler());
|
formLogin.failureHandler(new FormAuthenticationFailureHandler());
|
||||||
|
|
||||||
}).logout(httpSecurityLogoutConfigurer -> {
|
})
|
||||||
// SSO登出成功处理
|
.logout() // SSO登出成功处理
|
||||||
httpSecurityLogoutConfigurer.logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
|
.logoutSuccessHandler(new SsoLogoutSuccessHandler())
|
||||||
.invalidateHttpSession(true);
|
.deleteCookies("JSESSIONID")
|
||||||
}
|
.invalidateHttpSession(true)
|
||||||
|
.and()
|
||||||
).csrf(AbstractHttpConfigurer::disable);
|
.csrf()
|
||||||
|
.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,8 @@ public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticat
|
|||||||
|
|
||||||
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
|
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
|
||||||
prepareTimingAttackProtection();
|
prepareTimingAttackProtection();
|
||||||
HttpServletRequest request = WebUtils.getRequest().orElseThrow(
|
HttpServletRequest request = WebUtils.getRequest()
|
||||||
|
.orElseThrow(
|
||||||
(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));
|
(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));
|
||||||
|
|
||||||
String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||||
@ -106,7 +107,8 @@ public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticat
|
|||||||
.getBeansOfType(PigUserDetailsService.class);
|
.getBeansOfType(PigUserDetailsService.class);
|
||||||
|
|
||||||
String finalClientId = clientId;
|
String finalClientId = clientId;
|
||||||
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
|
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values()
|
||||||
|
.stream()
|
||||||
.filter(service -> service.support(finalClientId, grantType))
|
.filter(service -> service.support(finalClientId, grantType))
|
||||||
.max(Comparator.comparingInt(Ordered::getOrder));
|
.max(Comparator.comparingInt(Ordered::getOrder));
|
||||||
|
|
||||||
|
@ -99,7 +99,8 @@ public class PigAuthenticationSuccessEventHandler implements AuthenticationSucce
|
|||||||
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
|
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
|
||||||
|
|
||||||
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||||
.tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes());
|
.tokenType(accessToken.getTokenType())
|
||||||
|
.scopes(accessToken.getScopes());
|
||||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<springdoc.version>2.0.0</springdoc.version>
|
<springdoc.version>2.0.0</springdoc.version>
|
||||||
<swagger.core.version>2.2.7</swagger.core.version>
|
<swagger.core.version>2.2.7</swagger.core.version>
|
||||||
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||||
<mysql.version>8.0.31</mysql.version>
|
<mysql.version>8.0.33</mysql.version>
|
||||||
<seata.version>1.6.1</seata.version>
|
<seata.version>1.6.1</seata.version>
|
||||||
<excel.version>3.0.0</excel.version>
|
<excel.version>3.0.0</excel.version>
|
||||||
<asm.version>7.1</asm.version>
|
<asm.version>7.1</asm.version>
|
||||||
|
@ -106,8 +106,8 @@ public final class PigSentinelFeign {
|
|||||||
private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
|
private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
|
||||||
Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType);
|
Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType);
|
||||||
if (fallbackInstance == null) {
|
if (fallbackInstance == null) {
|
||||||
throw new IllegalStateException(String.format(
|
throw new IllegalStateException(String
|
||||||
"No %s instance of type %s found for feign client %s", type, fallbackType, name));
|
.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetType.isAssignableFrom(fallbackType)) {
|
if (!targetType.isAssignableFrom(fallbackType)) {
|
||||||
|
@ -88,8 +88,8 @@ public class GlobalBizExceptionHandler {
|
|||||||
@ExceptionHandler(AccessDeniedException.class)
|
@ExceptionHandler(AccessDeniedException.class)
|
||||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||||
public R handleAccessDeniedException(AccessDeniedException e) {
|
public R handleAccessDeniedException(AccessDeniedException e) {
|
||||||
String msg = SpringSecurityMessageSource.getAccessor().getMessage("AbstractAccessDecisionManager.accessDenied",
|
String msg = SpringSecurityMessageSource.getAccessor()
|
||||||
e.getMessage());
|
.getMessage("AbstractAccessDecisionManager.accessDenied", e.getMessage());
|
||||||
log.warn("拒绝授权异常信息 ex={}", msg);
|
log.warn("拒绝授权异常信息 ex={}", msg);
|
||||||
return R.failed(e.getLocalizedMessage());
|
return R.failed(e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
@ -61,9 +61,12 @@ public class XxlJobAutoConfiguration {
|
|||||||
|
|
||||||
// 如果配置为空则获取注册中心的服务列表 "http://pig-xxl:9080/xxl-job-admin"
|
// 如果配置为空则获取注册中心的服务列表 "http://pig-xxl:9080/xxl-job-admin"
|
||||||
if (!StringUtils.hasText(xxlJobProperties.getAdmin().getAddresses())) {
|
if (!StringUtils.hasText(xxlJobProperties.getAdmin().getAddresses())) {
|
||||||
String serverList = discoveryClient.getServices().stream().filter(s -> s.contains(XXL_JOB_ADMIN))
|
String serverList = discoveryClient.getServices()
|
||||||
.flatMap(s -> discoveryClient.getInstances(s).stream()).map(instance -> String
|
.stream()
|
||||||
.format("http://%s:%s/%s", instance.getHost(), instance.getPort(), XXL_JOB_ADMIN))
|
.filter(s -> s.contains(XXL_JOB_ADMIN))
|
||||||
|
.flatMap(s -> discoveryClient.getInstances(s).stream())
|
||||||
|
.map(instance -> String.format("http://%s:%s/%s", instance.getHost(), instance.getPort(),
|
||||||
|
XXL_JOB_ADMIN))
|
||||||
.collect(Collectors.joining(","));
|
.collect(Collectors.joining(","));
|
||||||
xxlJobSpringExecutor.setAdminAddresses(serverList);
|
xxlJobSpringExecutor.setAdminAddresses(serverList);
|
||||||
}
|
}
|
||||||
|
@ -88,9 +88,11 @@ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<OrderItem> orderItemList = new ArrayList<>();
|
List<OrderItem> orderItemList = new ArrayList<>();
|
||||||
Optional.ofNullable(ascs).ifPresent(s -> orderItemList.addAll(
|
Optional.ofNullable(ascs)
|
||||||
|
.ifPresent(s -> orderItemList.addAll(
|
||||||
Arrays.stream(s).filter(sqlInjectPredicate()).map(OrderItem::asc).collect(Collectors.toList())));
|
Arrays.stream(s).filter(sqlInjectPredicate()).map(OrderItem::asc).collect(Collectors.toList())));
|
||||||
Optional.ofNullable(descs).ifPresent(s -> orderItemList.addAll(
|
Optional.ofNullable(descs)
|
||||||
|
.ifPresent(s -> orderItemList.addAll(
|
||||||
Arrays.stream(s).filter(sqlInjectPredicate()).map(OrderItem::desc).collect(Collectors.toList())));
|
Arrays.stream(s).filter(sqlInjectPredicate()).map(OrderItem::desc).collect(Collectors.toList())));
|
||||||
page.addOrder(orderItemList);
|
page.addOrder(orderItemList);
|
||||||
|
|
||||||
|
@ -46,7 +46,9 @@ public class PermissionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||||
return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
|
return authorities.stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.filter(StringUtils::hasText)
|
||||||
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
|
.anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,13 +61,17 @@ public class PermitAllUrlProperties implements InitializingBean {
|
|||||||
|
|
||||||
// 获取方法上边的注解 替代path variable 为 *
|
// 获取方法上边的注解 替代path variable 为 *
|
||||||
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
|
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
|
||||||
Optional.ofNullable(method).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
Optional.ofNullable(method)
|
||||||
.getPatternValues().forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
||||||
|
.getPatternValues()
|
||||||
|
.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
||||||
|
|
||||||
// 获取类上边的注解, 替代path variable 为 *
|
// 获取类上边的注解, 替代path variable 为 *
|
||||||
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
|
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
|
||||||
Optional.ofNullable(controller).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
Optional.ofNullable(controller)
|
||||||
.getPatternValues().forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
|
||||||
|
.getPatternValues()
|
||||||
|
.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ public class PigBearerTokenExtractor implements BearerTokenResolver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String resolve(HttpServletRequest request) {
|
public String resolve(HttpServletRequest request) {
|
||||||
boolean match = urlProperties.getUrls().stream()
|
boolean match = urlProperties.getUrls()
|
||||||
|
.stream()
|
||||||
.anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
|
.anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
|
@ -50,7 +50,8 @@ public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector
|
|||||||
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
|
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
|
||||||
.getBeansOfType(PigUserDetailsService.class);
|
.getBeansOfType(PigUserDetailsService.class);
|
||||||
|
|
||||||
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
|
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values()
|
||||||
|
.stream()
|
||||||
.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
|
.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
|
||||||
oldAuthorization.getAuthorizationGrantType().getValue()))
|
oldAuthorization.getAuthorizationGrantType().getValue()))
|
||||||
.max(Comparator.comparingInt(Ordered::getOrder));
|
.max(Comparator.comparingInt(Ordered::getOrder));
|
||||||
|
@ -24,15 +24,13 @@ import org.springframework.core.Ordered;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lengleng
|
* @author lengleng
|
||||||
* @date 2022-06-04
|
* @date 2022-06-04
|
||||||
* <p>
|
*
|
||||||
* 资源服务器认证授权配置
|
* 资源服务器认证授权配置
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -53,15 +51,20 @@ public class PigResourceServerConfiguration {
|
|||||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
|
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
|
||||||
.requestMatchers(ArrayUtil.toArray(permitAllUrl.getUrls(), String.class)).permitAll().anyRequest()
|
.requestMatchers(ArrayUtil.toArray(permitAllUrl.getUrls(), String.class))
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest()
|
||||||
.authenticated())
|
.authenticated())
|
||||||
.oauth2ResourceServer(
|
.oauth2ResourceServer(
|
||||||
oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector))
|
oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector))
|
||||||
.authenticationEntryPoint(resourceAuthExceptionEntryPoint)
|
.authenticationEntryPoint(resourceAuthExceptionEntryPoint)
|
||||||
.bearerTokenResolver(pigBearerTokenExtractor))
|
.bearerTokenResolver(pigBearerTokenExtractor))
|
||||||
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer
|
.headers()
|
||||||
.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
.frameOptions()
|
||||||
.csrf(AbstractHttpConfigurer::disable);
|
.disable()
|
||||||
|
.and()
|
||||||
|
.csrf()
|
||||||
|
.disable();
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ public class PigRedisOAuth2AuthorizationConsentService implements OAuth2Authoriz
|
|||||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||||
|
|
||||||
redisTemplate.opsForValue().set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT,
|
redisTemplate.opsForValue()
|
||||||
TimeUnit.MINUTES);
|
.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, TimeUnit.MINUTES);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ public class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationSe
|
|||||||
if (isState(authorization)) {
|
if (isState(authorization)) {
|
||||||
String token = authorization.getAttribute("state");
|
String token = authorization.getAttribute("state");
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT,
|
redisTemplate.opsForValue()
|
||||||
TimeUnit.MINUTES);
|
.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCode(authorization)) {
|
if (isCode(authorization)) {
|
||||||
@ -50,24 +50,27 @@ public class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationSe
|
|||||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
||||||
authorizationCodeToken.getExpiresAt());
|
authorizationCodeToken.getExpiresAt());
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()),
|
redisTemplate.opsForValue()
|
||||||
authorization, between, TimeUnit.MINUTES);
|
.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,
|
||||||
|
between, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRefreshToken(authorization)) {
|
if (isRefreshToken(authorization)) {
|
||||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()),
|
redisTemplate.opsForValue()
|
||||||
authorization, between, TimeUnit.SECONDS);
|
.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAccessToken(authorization)) {
|
if (isAccessToken(authorization)) {
|
||||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
||||||
redisTemplate.setValueSerializer(RedisSerializer.java());
|
redisTemplate.setValueSerializer(RedisSerializer.java());
|
||||||
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
|
redisTemplate.opsForValue()
|
||||||
authorization, between, TimeUnit.SECONDS);
|
.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,6 @@ public class PigRemoteRegisteredClientRepository implements RegisteredClientRepo
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void save(RegisteredClient registeredClient) {
|
public void save(RegisteredClient registeredClient) {
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +86,8 @@ public class PigRemoteRegisteredClientRepository implements RegisteredClientRepo
|
|||||||
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
|
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
|
||||||
public RegisteredClient findByClientId(String clientId) {
|
public RegisteredClient findByClientId(String clientId) {
|
||||||
|
|
||||||
SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId)).getData()
|
SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))
|
||||||
|
.getData()
|
||||||
.orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException(
|
.orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException(
|
||||||
new OAuth2Error("客户端查询异常,请检查数据库链接"), null));
|
new OAuth2Error("客户端查询异常,请检查数据库链接"), null));
|
||||||
|
|
||||||
@ -101,23 +101,28 @@ public class PigRemoteRegisteredClientRepository implements RegisteredClientRepo
|
|||||||
.ifPresent(grants -> StringUtils.commaDelimitedListToSet(grants)
|
.ifPresent(grants -> StringUtils.commaDelimitedListToSet(grants)
|
||||||
.forEach(s -> builder.authorizationGrantType(new AuthorizationGrantType(s))));
|
.forEach(s -> builder.authorizationGrantType(new AuthorizationGrantType(s))));
|
||||||
// 回调地址
|
// 回调地址
|
||||||
Optional.ofNullable(clientDetails.getWebServerRedirectUri()).ifPresent(redirectUri -> Arrays
|
Optional.ofNullable(clientDetails.getWebServerRedirectUri())
|
||||||
.stream(redirectUri.split(StrUtil.COMMA)).filter(StrUtil::isNotBlank).forEach(builder::redirectUri));
|
.ifPresent(redirectUri -> Arrays.stream(redirectUri.split(StrUtil.COMMA))
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.forEach(builder::redirectUri));
|
||||||
|
|
||||||
// scope
|
// scope
|
||||||
Optional.ofNullable(clientDetails.getScope()).ifPresent(
|
Optional.ofNullable(clientDetails.getScope())
|
||||||
scope -> Arrays.stream(scope.split(StrUtil.COMMA)).filter(StrUtil::isNotBlank).forEach(builder::scope));
|
.ifPresent(scope -> Arrays.stream(scope.split(StrUtil.COMMA))
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.forEach(builder::scope));
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
.tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
.tokenSettings(TokenSettings.builder()
|
||||||
.accessTokenTimeToLive(Duration.ofSeconds(Optional
|
.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
|
||||||
.ofNullable(clientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds)))
|
.accessTokenTimeToLive(Duration.ofSeconds(
|
||||||
.refreshTokenTimeToLive(
|
Optional.ofNullable(clientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds)))
|
||||||
Duration.ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity())
|
.refreshTokenTimeToLive(Duration.ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity())
|
||||||
.orElse(refreshTokenValiditySeconds)))
|
.orElse(refreshTokenValiditySeconds)))
|
||||||
.build())
|
.build())
|
||||||
.clientSettings(ClientSettings.builder()
|
.clientSettings(ClientSettings.builder()
|
||||||
.requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoapprove())).build())
|
.requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoapprove()))
|
||||||
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,8 @@ public class OAuth2EndpointUtils {
|
|||||||
OAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken();
|
OAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken();
|
||||||
|
|
||||||
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||||
.tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes());
|
.tokenType(accessToken.getTokenType())
|
||||||
|
.scopes(accessToken.getScopes());
|
||||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,8 @@ public class SecurityUtils {
|
|||||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||||
|
|
||||||
List<Long> roleIds = new ArrayList<>();
|
List<Long> roleIds = new ArrayList<>();
|
||||||
authorities.stream().filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE))
|
authorities.stream()
|
||||||
|
.filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE))
|
||||||
.forEach(granted -> {
|
.forEach(granted -> {
|
||||||
String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE);
|
String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE);
|
||||||
roleIds.add(Long.parseLong(id));
|
roleIds.add(Long.parseLong(id));
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package com.pig4cloud.pig.common.swagger.annotation;
|
package com.pig4cloud.pig.common.swagger.annotation;
|
||||||
|
|
||||||
import com.pig4cloud.pig.common.swagger.config.SwaggerAutoConfiguration;
|
import com.pig4cloud.pig.common.swagger.config.OpenAPIDefinitionImportSelector;
|
||||||
import com.pig4cloud.pig.common.swagger.support.SwaggerProperties;
|
import com.pig4cloud.pig.common.swagger.support.SwaggerProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
@ -34,7 +34,21 @@ import java.lang.annotation.*;
|
|||||||
@Documented
|
@Documented
|
||||||
@Inherited
|
@Inherited
|
||||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||||
@Import({ SwaggerAutoConfiguration.class })
|
@Import(OpenAPIDefinitionImportSelector.class)
|
||||||
public @interface EnablePigDoc {
|
public @interface EnablePigDoc {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关路由前缀
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是微服务架构
|
||||||
|
*
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
boolean isMicro() default true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,12 @@ import io.swagger.v3.oas.models.security.Scopes;
|
|||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.servers.Server;
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
import lombok.Setter;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.cloud.client.ServiceInstance;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -49,27 +51,14 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
|
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
|
||||||
@ConditionalOnMissingClass("org.springframework.cloud.gateway.config.GatewayAutoConfiguration")
|
public class OpenAPIDefinition extends OpenAPI implements InitializingBean, ApplicationContextAware {
|
||||||
public class SwaggerAutoConfiguration {
|
|
||||||
|
|
||||||
private final SwaggerProperties swaggerProperties;
|
@Setter
|
||||||
|
private String path;
|
||||||
|
|
||||||
private final ServiceInstance serviceInstance;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
@Bean
|
private SecurityScheme securityScheme(SwaggerProperties swaggerProperties) {
|
||||||
public OpenAPI springOpenAPI() {
|
|
||||||
OpenAPI openAPI = new OpenAPI().info(new Info().title(swaggerProperties.getTitle()));
|
|
||||||
// oauth2.0 password
|
|
||||||
openAPI.schemaRequirement(HttpHeaders.AUTHORIZATION, this.securityScheme());
|
|
||||||
// servers
|
|
||||||
List<Server> serverList = new ArrayList<>();
|
|
||||||
String path = swaggerProperties.getServices().get(serviceInstance.getServiceId());
|
|
||||||
serverList.add(new Server().url(swaggerProperties.getGateway() + "/" + path));
|
|
||||||
openAPI.servers(serverList);
|
|
||||||
return openAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecurityScheme securityScheme() {
|
|
||||||
OAuthFlow clientCredential = new OAuthFlow();
|
OAuthFlow clientCredential = new OAuthFlow();
|
||||||
clientCredential.setTokenUrl(swaggerProperties.getTokenUrl());
|
clientCredential.setTokenUrl(swaggerProperties.getTokenUrl());
|
||||||
clientCredential.setScopes(new Scopes().addString(swaggerProperties.getScope(), swaggerProperties.getScope()));
|
clientCredential.setScopes(new Scopes().addString(swaggerProperties.getScope(), swaggerProperties.getScope()));
|
||||||
@ -81,4 +70,21 @@ public class SwaggerAutoConfiguration {
|
|||||||
return securityScheme;
|
return securityScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
SwaggerProperties swaggerProperties = applicationContext.getBean(SwaggerProperties.class);
|
||||||
|
this.info(new Info().title(swaggerProperties.getTitle()));
|
||||||
|
// oauth2.0 password
|
||||||
|
this.schemaRequirement(HttpHeaders.AUTHORIZATION, this.securityScheme(swaggerProperties));
|
||||||
|
// servers
|
||||||
|
List<Server> serverList = new ArrayList<>();
|
||||||
|
serverList.add(new Server().url(swaggerProperties.getGateway() + "/" + path));
|
||||||
|
this.servers(serverList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.pig4cloud.pig.common.swagger.config;
|
||||||
|
|
||||||
|
import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||||
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* openapi 配置类
|
||||||
|
*
|
||||||
|
* @author lengleng
|
||||||
|
* @date 2023/1/1
|
||||||
|
*/
|
||||||
|
public class OpenAPIDefinitionImportSelector implements ImportBeanDefinitionRegistrar {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||||
|
|
||||||
|
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(EnablePigDoc.class.getName(), true);
|
||||||
|
Object value = annotationAttributes.get("value");
|
||||||
|
if (Objects.isNull(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanDefinitionBuilder openAPIMetadataRegister = BeanDefinitionBuilder.genericBeanDefinition(OpenAPIMetadataRegister.class);
|
||||||
|
openAPIMetadataRegister.addPropertyValue("path", value);
|
||||||
|
|
||||||
|
registry.registerBeanDefinition("openAPIMetadataRegister", openAPIMetadataRegister.getBeanDefinition());
|
||||||
|
|
||||||
|
|
||||||
|
BeanDefinitionBuilder openAPIDefinition = BeanDefinitionBuilder.genericBeanDefinition(OpenAPIDefinition.class);
|
||||||
|
openAPIDefinition.addPropertyValue("path", value);
|
||||||
|
registry.registerBeanDefinition("openAPIDefinition", openAPIDefinition.getBeanDefinition());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.pig4cloud.pig.common.swagger.config;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lengleng
|
||||||
|
* @date 2023/1/4
|
||||||
|
*/
|
||||||
|
public class OpenAPIMetadataRegister implements InitializingBean, ApplicationContextAware {
|
||||||
|
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
ServiceInstance serviceInstance = applicationContext.getBean(ServiceInstance.class);
|
||||||
|
serviceInstance.getMetadata().put("spring-doc", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -75,8 +75,10 @@ public class PigXssAutoConfiguration implements WebMvcConfigurer {
|
|||||||
}
|
}
|
||||||
com.pig4cloud.pig.common.xss.core.XssCleanInterceptor interceptor = new com.pig4cloud.pig.common.xss.core.XssCleanInterceptor(
|
com.pig4cloud.pig.common.xss.core.XssCleanInterceptor interceptor = new com.pig4cloud.pig.common.xss.core.XssCleanInterceptor(
|
||||||
xssProperties);
|
xssProperties);
|
||||||
registry.addInterceptor(interceptor).addPathPatterns(patterns)
|
registry.addInterceptor(interceptor)
|
||||||
.excludePathPatterns(xssProperties.getPathExcludePatterns()).order(Ordered.LOWEST_PRECEDENCE);
|
.addPathPatterns(patterns)
|
||||||
|
.excludePathPatterns(xssProperties.getPathExcludePatterns())
|
||||||
|
.order(Ordered.LOWEST_PRECEDENCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-gateway
|
RUN mkdir -p /pig-gateway
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ public class RateLimiterConfiguration {
|
|||||||
public KeyResolver remoteAddrKeyResolver() {
|
public KeyResolver remoteAddrKeyResolver() {
|
||||||
return exchange -> Mono
|
return exchange -> Mono
|
||||||
.just(Objects.requireNonNull(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()))
|
.just(Objects.requireNonNull(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()))
|
||||||
.getAddress().getHostAddress());
|
.getAddress()
|
||||||
|
.getHostAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package com.pig4cloud.pig.gateway.config;
|
package com.pig4cloud.pig.gateway.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
|
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
|
||||||
import org.springdoc.core.properties.SwaggerUiConfigProperties;
|
import org.springdoc.core.properties.SwaggerUiConfigProperties;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.cloud.client.ServiceInstance;
|
||||||
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -21,14 +24,26 @@ public class SpringDocConfiguration implements InitializingBean {
|
|||||||
|
|
||||||
private final SwaggerUiConfigProperties swaggerUiConfigProperties;
|
private final SwaggerUiConfigProperties swaggerUiConfigProperties;
|
||||||
|
|
||||||
|
private final DiscoveryClient discoveryClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrlSet = new HashSet<>();
|
Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrlSet = new HashSet<>();
|
||||||
|
|
||||||
|
for (String serviceId : discoveryClient.getServices()) {
|
||||||
|
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
|
||||||
|
String doc = instance.getMetadata().get("spring-doc");
|
||||||
|
if (StrUtil.isNotBlank(doc)) {
|
||||||
AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
|
AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
|
||||||
swaggerUrl.setName("admin");
|
swaggerUrl.setName(serviceId);
|
||||||
swaggerUrl.setUrl("/admin/v3/api-docs");
|
swaggerUrl.setUrl(String.format("/%s/v3/api-docs", doc));
|
||||||
swaggerUrlSet.add(swaggerUrl);
|
swaggerUrlSet.add(swaggerUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
swaggerUiConfigProperties.setUrls(swaggerUrlSet);
|
swaggerUiConfigProperties.setUrls(swaggerUrlSet);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,8 @@ public class PigRequestGlobalFilter implements GlobalFilter, Ordered {
|
|||||||
// 2. 重写StripPrefix
|
// 2. 重写StripPrefix
|
||||||
addOriginalRequestUrl(exchange, request.getURI());
|
addOriginalRequestUrl(exchange, request.getURI());
|
||||||
String rawPath = request.getURI().getRawPath();
|
String rawPath = request.getURI().getRawPath();
|
||||||
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")).skip(1L)
|
String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/"))
|
||||||
|
.skip(1L)
|
||||||
.collect(Collectors.joining("/"));
|
.collect(Collectors.joining("/"));
|
||||||
|
|
||||||
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
|
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
|
||||||
|
@ -59,14 +59,15 @@ public class ImageCodeHandler implements HandlerFunction<ServerResponse> {
|
|||||||
// 保存验证码信息
|
// 保存验证码信息
|
||||||
Optional<String> randomStr = serverRequest.queryParam("randomStr");
|
Optional<String> randomStr = serverRequest.queryParam("randomStr");
|
||||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
randomStr.ifPresent(s -> redisTemplate.opsForValue().set(CacheConstants.DEFAULT_CODE_KEY + s, result,
|
randomStr.ifPresent(s -> redisTemplate.opsForValue()
|
||||||
SecurityConstants.CODE_TIME, TimeUnit.SECONDS));
|
.set(CacheConstants.DEFAULT_CODE_KEY + s, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS));
|
||||||
|
|
||||||
// 转换流信息写出
|
// 转换流信息写出
|
||||||
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
|
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
|
||||||
captcha.out(os);
|
captcha.out(os);
|
||||||
|
|
||||||
return ServerResponse.status(HttpStatus.OK).contentType(MediaType.IMAGE_JPEG)
|
return ServerResponse.status(HttpStatus.OK)
|
||||||
|
.contentType(MediaType.IMAGE_JPEG)
|
||||||
.body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
|
.body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,9 @@ spring:
|
|||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: ${NACOS_HOST:pig-register}:${NACOS_PORT:8848}
|
server-addr: ${NACOS_HOST:pig-register}:${NACOS_PORT:8848}
|
||||||
|
watch:
|
||||||
|
enabled: true
|
||||||
|
watch-delay: 1000
|
||||||
config:
|
config:
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
config:
|
config:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-register
|
RUN mkdir -p /pig-register
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<nacos.version>2.2.0.PIGOEM</nacos.version>
|
<nacos.version>2.2.0.PIGOEM</nacos.version>
|
||||||
<spring-boot-dependencies.version>2.7.7</spring-boot-dependencies.version>
|
<spring-boot-dependencies.version>2.7.10</spring-boot-dependencies.version>
|
||||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
<spring-cloud.version>2021.0.7</spring-cloud.version>
|
||||||
<spring-boot-admin.version>2.7.8</spring-boot-admin.version>
|
<spring-boot-admin.version>2.7.8</spring-boot-admin.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@ -131,6 +131,7 @@
|
|||||||
<exclude>**/*.woff</exclude>
|
<exclude>**/*.woff</exclude>
|
||||||
<exclude>**/*.woff2</exclude>
|
<exclude>**/*.woff2</exclude>
|
||||||
<exclude>**/*.ttf</exclude>
|
<exclude>**/*.ttf</exclude>
|
||||||
|
<exclude>**/*.eot</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</resource>
|
</resource>
|
||||||
<resource>
|
<resource>
|
||||||
@ -140,6 +141,7 @@
|
|||||||
<include>**/*.woff</include>
|
<include>**/*.woff</include>
|
||||||
<include>**/*.woff2</include>
|
<include>**/*.woff2</include>
|
||||||
<include>**/*.ttf</include>
|
<include>**/*.ttf</include>
|
||||||
|
<include>**/*.eot</include>
|
||||||
</includes>
|
</includes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-upms-biz
|
RUN mkdir -p /pig-upms-biz
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
|||||||
* @author lengleng
|
* @author lengleng
|
||||||
* @date 2018年06月21日 用户统一管理系统
|
* @date 2018年06月21日 用户统一管理系统
|
||||||
*/
|
*/
|
||||||
@EnablePigDoc
|
@EnablePigDoc("admin")
|
||||||
@EnablePigResourceServer
|
@EnablePigResourceServer
|
||||||
@EnablePigFeignClients
|
@EnablePigFeignClients
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
|
@ -56,8 +56,11 @@ public class MenuController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public R<List<Tree<Long>>> getUserMenu(Long parentId) {
|
public R<List<Tree<Long>>> getUserMenu(Long parentId) {
|
||||||
// 获取符合条件的菜单
|
// 获取符合条件的菜单
|
||||||
Set<SysMenu> menuSet = SecurityUtils.getRoles().stream().map(sysMenuService::findMenuByRoleId)
|
Set<SysMenu> menuSet = SecurityUtils.getRoles()
|
||||||
.flatMap(Collection::stream).collect(Collectors.toSet());
|
.stream()
|
||||||
|
.map(sysMenuService::findMenuByRoleId)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
return R.ok(sysMenuService.filterMenu(menuSet, parentId));
|
return R.ok(sysMenuService.filterMenu(menuSet, parentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +82,8 @@ public class MenuController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/tree/{roleId}")
|
@GetMapping("/tree/{roleId}")
|
||||||
public R<List<Long>> getRoleTree(@PathVariable Long roleId) {
|
public R<List<Long>> getRoleTree(@PathVariable Long roleId) {
|
||||||
return R.ok(
|
return R
|
||||||
sysMenuService.findMenuByRoleId(roleId).stream().map(SysMenu::getMenuId).collect(Collectors.toList()));
|
.ok(sysMenuService.findMenuByRoleId(roleId).stream().map(SysMenu::getMenuId).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,8 +76,8 @@ public class AppServiceImpl implements AppService {
|
|||||||
|
|
||||||
String code = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
|
String code = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE));
|
||||||
log.info("手机号生成验证码成功:{},{}", sms.getPhone(), code);
|
log.info("手机号生成验证码成功:{},{}", sms.getPhone(), code);
|
||||||
redisTemplate.opsForValue().set(CacheConstants.DEFAULT_CODE_KEY + sms.getPhone(), code,
|
redisTemplate.opsForValue()
|
||||||
SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
|
.set(CacheConstants.DEFAULT_CODE_KEY + sms.getPhone(), code, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
|
||||||
|
|
||||||
// 调用短信通道发送
|
// 调用短信通道发送
|
||||||
this.smsClient.sendCode(code, sms.getPhone());
|
this.smsClient.sendCode(code, sms.getPhone());
|
||||||
|
@ -55,10 +55,12 @@ public class SysDeptRelationServiceImpl extends ServiceImpl<SysDeptRelationMappe
|
|||||||
// 增加部门关系表
|
// 增加部门关系表
|
||||||
List<SysDeptRelation> relationList = sysDeptRelationMapper.selectList(
|
List<SysDeptRelation> relationList = sysDeptRelationMapper.selectList(
|
||||||
Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getDescendant, sysDept.getParentId()))
|
Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getDescendant, sysDept.getParentId()))
|
||||||
.stream().map(relation -> {
|
.stream()
|
||||||
|
.map(relation -> {
|
||||||
relation.setDescendant(sysDept.getDeptId());
|
relation.setDescendant(sysDept.getDeptId());
|
||||||
return relation;
|
return relation;
|
||||||
}).collect(Collectors.toList());
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
if (CollUtil.isNotEmpty(relationList)) {
|
if (CollUtil.isNotEmpty(relationList)) {
|
||||||
this.saveBatch(relationList);
|
this.saveBatch(relationList);
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,10 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
|
|||||||
public Boolean removeDeptById(Long id) {
|
public Boolean removeDeptById(Long id) {
|
||||||
// 级联删除部门
|
// 级联删除部门
|
||||||
List<Long> idList = sysDeptRelationService
|
List<Long> idList = sysDeptRelationService
|
||||||
.list(Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getAncestor, id)).stream()
|
.list(Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getAncestor, id))
|
||||||
.map(SysDeptRelation::getDescendant).collect(Collectors.toList());
|
.stream()
|
||||||
|
.map(SysDeptRelation::getDescendant)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (CollUtil.isNotEmpty(idList)) {
|
if (CollUtil.isNotEmpty(idList)) {
|
||||||
this.removeByIds(idList);
|
this.removeByIds(idList);
|
||||||
@ -108,7 +110,8 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
|
|||||||
@Override
|
@Override
|
||||||
public List<Long> listChildDeptId(Long deptId) {
|
public List<Long> listChildDeptId(Long deptId) {
|
||||||
List<SysDeptRelation> deptRelations = sysDeptRelationService.list(Wrappers.<SysDeptRelation>lambdaQuery()
|
List<SysDeptRelation> deptRelations = sysDeptRelationService.list(Wrappers.<SysDeptRelation>lambdaQuery()
|
||||||
.eq(SysDeptRelation::getAncestor, deptId).ne(SysDeptRelation::getDescendant, deptId));
|
.eq(SysDeptRelation::getAncestor, deptId)
|
||||||
|
.ne(SysDeptRelation::getDescendant, deptId));
|
||||||
if (CollUtil.isNotEmpty(deptRelations)) {
|
if (CollUtil.isNotEmpty(deptRelations)) {
|
||||||
return deptRelations.stream().map(SysDeptRelation::getDescendant).collect(Collectors.toList());
|
return deptRelations.stream().map(SysDeptRelation::getDescendant).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@ -132,8 +135,10 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
|
|||||||
public List<Tree<Long>> listCurrentUserDeptTrees() {
|
public List<Tree<Long>> listCurrentUserDeptTrees() {
|
||||||
Long deptId = SecurityUtils.getUser().getDeptId();
|
Long deptId = SecurityUtils.getUser().getDeptId();
|
||||||
List<Long> descendantIdList = sysDeptRelationService
|
List<Long> descendantIdList = sysDeptRelationService
|
||||||
.list(Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getAncestor, deptId)).stream()
|
.list(Wrappers.<SysDeptRelation>query().lambda().eq(SysDeptRelation::getAncestor, deptId))
|
||||||
.map(SysDeptRelation::getDescendant).collect(Collectors.toList());
|
.stream()
|
||||||
|
.map(SysDeptRelation::getDescendant)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
List<SysDept> deptList = baseMapper.selectBatchIds(descendantIdList);
|
List<SysDept> deptList = baseMapper.selectBatchIds(descendantIdList);
|
||||||
Optional<SysDept> dept = deptList.stream().filter(item -> item.getDeptId().intValue() == deptId).findFirst();
|
Optional<SysDept> dept = deptList.stream().filter(item -> item.getDeptId().intValue() == deptId).findFirst();
|
||||||
@ -147,8 +152,10 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private List<Tree<Long>> getDeptTree(List<SysDept> depts, Long parentId) {
|
private List<Tree<Long>> getDeptTree(List<SysDept> depts, Long parentId) {
|
||||||
List<TreeNode<Long>> collect = depts.stream().filter(dept -> dept.getDeptId().intValue() != dept.getParentId())
|
List<TreeNode<Long>> collect = depts.stream()
|
||||||
.sorted(Comparator.comparingInt(SysDept::getSortOrder)).map(dept -> {
|
.filter(dept -> dept.getDeptId().intValue() != dept.getParentId())
|
||||||
|
.sorted(Comparator.comparingInt(SysDept::getSortOrder))
|
||||||
|
.map(dept -> {
|
||||||
TreeNode<Long> treeNode = new TreeNode();
|
TreeNode<Long> treeNode = new TreeNode();
|
||||||
treeNode.setId(dept.getDeptId());
|
treeNode.setId(dept.getDeptId());
|
||||||
treeNode.setParentId(dept.getParentId());
|
treeNode.setParentId(dept.getParentId());
|
||||||
@ -159,7 +166,8 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
|
|||||||
extra.put("createTime", dept.getCreateTime());
|
extra.put("createTime", dept.getCreateTime());
|
||||||
treeNode.setExtra(extra);
|
treeNode.setExtra(extra);
|
||||||
return treeNode;
|
return treeNode;
|
||||||
}).collect(Collectors.toList());
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return TreeUtil.build(collect, parentId);
|
return TreeUtil.build(collect, parentId);
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,8 @@ public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> impleme
|
|||||||
.like(StrUtil.isNotBlank(sysLog.getRemoteAddr()), SysLog::getRemoteAddr, sysLog.getRemoteAddr());
|
.like(StrUtil.isNotBlank(sysLog.getRemoteAddr()), SysLog::getRemoteAddr, sysLog.getRemoteAddr());
|
||||||
|
|
||||||
if (ArrayUtil.isNotEmpty(sysLog.getCreateTime())) {
|
if (ArrayUtil.isNotEmpty(sysLog.getCreateTime())) {
|
||||||
wrapper.ge(SysLog::getCreateTime, sysLog.getCreateTime()[0]).le(SysLog::getCreateTime,
|
wrapper.ge(SysLog::getCreateTime, sysLog.getCreateTime()[0])
|
||||||
sysLog.getCreateTime()[1]);
|
.le(SysLog::getCreateTime, sysLog.getCreateTime()[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
|
@ -103,8 +103,10 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
|||||||
public List<Tree<Long>> treeMenu(boolean lazy, Long parentId) {
|
public List<Tree<Long>> treeMenu(boolean lazy, Long parentId) {
|
||||||
if (!lazy) {
|
if (!lazy) {
|
||||||
List<TreeNode<Long>> collect = baseMapper
|
List<TreeNode<Long>> collect = baseMapper
|
||||||
.selectList(Wrappers.<SysMenu>lambdaQuery().orderByAsc(SysMenu::getSortOrder)).stream()
|
.selectList(Wrappers.<SysMenu>lambdaQuery().orderByAsc(SysMenu::getSortOrder))
|
||||||
.map(getNodeFunction()).collect(Collectors.toList());
|
.stream()
|
||||||
|
.map(getNodeFunction())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return TreeUtil.build(collect, CommonConstants.MENU_TREE_ROOT_ID);
|
return TreeUtil.build(collect, CommonConstants.MENU_TREE_ROOT_ID);
|
||||||
}
|
}
|
||||||
@ -112,9 +114,11 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
|||||||
Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;
|
Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;
|
||||||
|
|
||||||
List<TreeNode<Long>> collect = baseMapper
|
List<TreeNode<Long>> collect = baseMapper
|
||||||
.selectList(Wrappers.<SysMenu>lambdaQuery().eq(SysMenu::getParentId, parent)
|
.selectList(
|
||||||
.orderByAsc(SysMenu::getSortOrder))
|
Wrappers.<SysMenu>lambdaQuery().eq(SysMenu::getParentId, parent).orderByAsc(SysMenu::getSortOrder))
|
||||||
.stream().map(getNodeFunction()).collect(Collectors.toList());
|
.stream()
|
||||||
|
.map(getNodeFunction())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return TreeUtil.build(collect, parent);
|
return TreeUtil.build(collect, parent);
|
||||||
}
|
}
|
||||||
@ -129,7 +133,9 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
|||||||
public List<Tree<Long>> filterMenu(Set<SysMenu> all, Long parentId) {
|
public List<Tree<Long>> filterMenu(Set<SysMenu> all, Long parentId) {
|
||||||
List<TreeNode<Long>> collect = all.stream()
|
List<TreeNode<Long>> collect = all.stream()
|
||||||
.filter(menu -> MenuTypeEnum.LEFT_MENU.getType().equals(menu.getType()))
|
.filter(menu -> MenuTypeEnum.LEFT_MENU.getType().equals(menu.getType()))
|
||||||
.filter(menu -> StrUtil.isNotBlank(menu.getPath())).map(getNodeFunction()).collect(Collectors.toList());
|
.filter(menu -> StrUtil.isNotBlank(menu.getPath()))
|
||||||
|
.map(getNodeFunction())
|
||||||
|
.collect(Collectors.toList());
|
||||||
Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;
|
Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId;
|
||||||
return TreeUtil.build(collect, parent);
|
return TreeUtil.build(collect, parent);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,8 @@ public class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPost> impl
|
|||||||
for (PostExcelVO excel : excelVOList) {
|
for (PostExcelVO excel : excelVOList) {
|
||||||
Set<String> errorMsg = new HashSet<>();
|
Set<String> errorMsg = new HashSet<>();
|
||||||
// 检验岗位名称或者岗位编码是否存在
|
// 检验岗位名称或者岗位编码是否存在
|
||||||
boolean existPost = postList.stream().anyMatch(post -> excel.getPostName().equals(post.getPostName())
|
boolean existPost = postList.stream()
|
||||||
|
.anyMatch(post -> excel.getPostName().equals(post.getPostName())
|
||||||
|| excel.getPostCode().equals(post.getPostCode()));
|
|| excel.getPostCode().equals(post.getPostCode()));
|
||||||
|
|
||||||
if (existPost) {
|
if (existPost) {
|
||||||
|
@ -87,7 +87,8 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
for (RoleExcelVO excel : excelVOList) {
|
for (RoleExcelVO excel : excelVOList) {
|
||||||
Set<String> errorMsg = new HashSet<>();
|
Set<String> errorMsg = new HashSet<>();
|
||||||
// 检验角色名称或者角色编码是否存在
|
// 检验角色名称或者角色编码是否存在
|
||||||
boolean existRole = roleList.stream().anyMatch(sysRole -> excel.getRoleName().equals(sysRole.getRoleName())
|
boolean existRole = roleList.stream()
|
||||||
|
.anyMatch(sysRole -> excel.getRoleName().equals(sysRole.getRoleName())
|
||||||
|| excel.getRoleCode().equals(sysRole.getRoleCode()));
|
|| excel.getRoleCode().equals(sysRole.getRoleCode()));
|
||||||
|
|
||||||
if (existRole) {
|
if (existRole) {
|
||||||
|
@ -130,9 +130,13 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
List<SysPost> postList = sysPostMapper.listPostsByUserId(sysUser.getUserId());
|
List<SysPost> postList = sysPostMapper.listPostsByUserId(sysUser.getUserId());
|
||||||
userInfo.setPostList(postList);
|
userInfo.setPostList(postList);
|
||||||
// 设置权限列表(menu.permission)
|
// 设置权限列表(menu.permission)
|
||||||
Set<String> permissions = roleIds.stream().map(sysMenuService::findMenuByRoleId).flatMap(Collection::stream)
|
Set<String> permissions = roleIds.stream()
|
||||||
.filter(m -> MenuTypeEnum.BUTTON.getType().equals(m.getType())).map(SysMenu::getPermission)
|
.map(sysMenuService::findMenuByRoleId)
|
||||||
.filter(StrUtil::isNotBlank).collect(Collectors.toSet());
|
.flatMap(Collection::stream)
|
||||||
|
.filter(m -> MenuTypeEnum.BUTTON.getType().equals(m.getType()))
|
||||||
|
.map(SysMenu::getPermission)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
|
userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class));
|
||||||
|
|
||||||
return userInfo;
|
return userInfo;
|
||||||
@ -260,10 +264,14 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
List<UserExcelVO> userExcelVOList = voList.stream().map(userVO -> {
|
List<UserExcelVO> userExcelVOList = voList.stream().map(userVO -> {
|
||||||
UserExcelVO excelVO = new UserExcelVO();
|
UserExcelVO excelVO = new UserExcelVO();
|
||||||
BeanUtils.copyProperties(userVO, excelVO);
|
BeanUtils.copyProperties(userVO, excelVO);
|
||||||
String roleNameList = userVO.getRoleList().stream().map(SysRole::getRoleName)
|
String roleNameList = userVO.getRoleList()
|
||||||
|
.stream()
|
||||||
|
.map(SysRole::getRoleName)
|
||||||
.collect(Collectors.joining(StrUtil.COMMA));
|
.collect(Collectors.joining(StrUtil.COMMA));
|
||||||
excelVO.setRoleNameList(roleNameList);
|
excelVO.setRoleNameList(roleNameList);
|
||||||
String postNameList = userVO.getPostList().stream().map(SysPost::getPostName)
|
String postNameList = userVO.getPostList()
|
||||||
|
.stream()
|
||||||
|
.map(SysPost::getPostName)
|
||||||
.collect(Collectors.joining(StrUtil.COMMA));
|
.collect(Collectors.joining(StrUtil.COMMA));
|
||||||
excelVO.setPostNameList(postNameList);
|
excelVO.setPostNameList(postNameList);
|
||||||
return excelVO;
|
return excelVO;
|
||||||
@ -301,7 +309,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
|||||||
|
|
||||||
// 判断输入的部门名称列表是否合法
|
// 判断输入的部门名称列表是否合法
|
||||||
Optional<SysDept> deptOptional = deptList.stream()
|
Optional<SysDept> deptOptional = deptList.stream()
|
||||||
.filter(dept -> excel.getDeptName().equals(dept.getName())).findFirst();
|
.filter(dept -> excel.getDeptName().equals(dept.getName()))
|
||||||
|
.findFirst();
|
||||||
if (!deptOptional.isPresent()) {
|
if (!deptOptional.isPresent()) {
|
||||||
errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptName()));
|
errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptName()));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-codegen
|
RUN mkdir -p /pig-codegen
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ package com.pig4cloud.pig.codegen;
|
|||||||
import com.pig4cloud.pig.common.datasource.annotation.EnableDynamicDataSource;
|
import com.pig4cloud.pig.common.datasource.annotation.EnableDynamicDataSource;
|
||||||
import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;
|
import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;
|
||||||
import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;
|
import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer;
|
||||||
import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
@ -28,7 +27,6 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
|||||||
* @author lengleng
|
* @author lengleng
|
||||||
* @date 2020/03/11 代码生成模块
|
* @date 2020/03/11 代码生成模块
|
||||||
*/
|
*/
|
||||||
@EnablePigDoc
|
|
||||||
@EnableDynamicDataSource
|
@EnableDynamicDataSource
|
||||||
@EnablePigFeignClients
|
@EnablePigFeignClients
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
|
@ -121,8 +121,8 @@ public class GenDatasourceConfServiceImpl extends ServiceImpl<GenDatasourceConfM
|
|||||||
dataSourceProperty.setPassword(conf.getPassword());
|
dataSourceProperty.setPassword(conf.getPassword());
|
||||||
dataSourceProperty.setLazy(true);
|
dataSourceProperty.setLazy(true);
|
||||||
DataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
|
DataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
|
||||||
SpringContextHolder.getBean(DynamicRoutingDataSource.class).addDataSource(dataSourceProperty.getPoolName(),
|
SpringContextHolder.getBean(DynamicRoutingDataSource.class)
|
||||||
dataSource);
|
.addDataSource(dataSourceProperty.getPoolName(), dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +63,8 @@ public class GenFormConfServiceImpl extends ServiceImpl<GenFormConfMapper, GenFo
|
|||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public String getForm(String dsName, String tableName) {
|
public String getForm(String dsName, String tableName) {
|
||||||
GenFormConf form = getOne(Wrappers.<GenFormConf>lambdaQuery().eq(GenFormConf::getTableName, tableName)
|
GenFormConf form = getOne(Wrappers.<GenFormConf>lambdaQuery()
|
||||||
|
.eq(GenFormConf::getTableName, tableName)
|
||||||
.orderByDesc(GenFormConf::getCreateTime), false);
|
.orderByDesc(GenFormConf::getCreateTime), false);
|
||||||
|
|
||||||
if (form != null) {
|
if (form != null) {
|
||||||
|
@ -77,7 +77,8 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||||||
public Map<String, String> previewCode(GenConfig genConfig) {
|
public Map<String, String> previewCode(GenConfig genConfig) {
|
||||||
// 根据tableName 查询最新的表单配置
|
// 根据tableName 查询最新的表单配置
|
||||||
List<GenFormConf> formConfList = genFormConfMapper.selectList(Wrappers.<GenFormConf>lambdaQuery()
|
List<GenFormConf> formConfList = genFormConfMapper.selectList(Wrappers.<GenFormConf>lambdaQuery()
|
||||||
.eq(GenFormConf::getTableName, genConfig.getTableName()).orderByDesc(GenFormConf::getCreateTime));
|
.eq(GenFormConf::getTableName, genConfig.getTableName())
|
||||||
|
.orderByDesc(GenFormConf::getCreateTime));
|
||||||
|
|
||||||
String tableNames = genConfig.getTableName();
|
String tableNames = genConfig.getTableName();
|
||||||
String dsName = genConfig.getDsName();
|
String dsName = genConfig.getDsName();
|
||||||
@ -111,7 +112,8 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||||||
public byte[] generatorCode(GenConfig genConfig) {
|
public byte[] generatorCode(GenConfig genConfig) {
|
||||||
// 根据tableName 查询最新的表单配置
|
// 根据tableName 查询最新的表单配置
|
||||||
List<GenFormConf> formConfList = genFormConfMapper.selectList(Wrappers.<GenFormConf>lambdaQuery()
|
List<GenFormConf> formConfList = genFormConfMapper.selectList(Wrappers.<GenFormConf>lambdaQuery()
|
||||||
.eq(GenFormConf::getTableName, genConfig.getTableName()).orderByDesc(GenFormConf::getCreateTime));
|
.eq(GenFormConf::getTableName, genConfig.getTableName())
|
||||||
|
.orderByDesc(GenFormConf::getCreateTime));
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
||||||
|
@ -36,8 +36,11 @@ public enum StyleTypeEnum {
|
|||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
public static String getDecs(String style) {
|
public static String getDecs(String style) {
|
||||||
return Arrays.stream(StyleTypeEnum.values()).filter(styleTypeEnum -> styleTypeEnum.getStyle().equals(style))
|
return Arrays.stream(StyleTypeEnum.values())
|
||||||
.findFirst().orElse(ELEMENT).getDescription();
|
.filter(styleTypeEnum -> styleTypeEnum.getStyle().equals(style))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(ELEMENT)
|
||||||
|
.getDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM moxm/java:1.8-full
|
FROM alibabadragonwell/dragonwell:17-anolis
|
||||||
|
|
||||||
RUN mkdir -p /pig-monitor
|
RUN mkdir -p /pig-monitor
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ import de.codecentric.boot.admin.server.config.AdminServerProperties;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
|
||||||
@ -51,21 +49,28 @@ public class WebSecurityConfigurer {
|
|||||||
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
|
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
|
||||||
successHandler.setTargetUrlParameter("redirectTo");
|
successHandler.setTargetUrlParameter("redirectTo");
|
||||||
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
||||||
http.headers(httpSecurityHeadersConfigurer -> {
|
http.headers()
|
||||||
httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable);
|
.frameOptions()
|
||||||
}).authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {
|
.disable()
|
||||||
authorizationManagerRequestMatcherRegistry
|
.and()
|
||||||
|
.authorizeHttpRequests()
|
||||||
.requestMatchers(adminContextPath + "/assets/**", adminContextPath + "/login",
|
.requestMatchers(adminContextPath + "/assets/**", adminContextPath + "/login",
|
||||||
adminContextPath + "/instances/**", adminContextPath + "/actuator/**")
|
adminContextPath + "/instances/**", adminContextPath + "/actuator/**")
|
||||||
.permitAll().anyRequest().authenticated();
|
.permitAll()
|
||||||
|
.anyRequest()
|
||||||
}).formLogin(httpSecurityFormLoginConfigurer -> {
|
.authenticated()
|
||||||
httpSecurityFormLoginConfigurer.loginPage(adminContextPath + "/login");
|
.and()
|
||||||
httpSecurityFormLoginConfigurer.successHandler(successHandler);
|
.formLogin()
|
||||||
}).logout(httpSecurityLogoutConfigurer -> {
|
.loginPage(adminContextPath + "/login")
|
||||||
httpSecurityLogoutConfigurer.logoutUrl(adminContextPath + "/logout");
|
.successHandler(successHandler)
|
||||||
}).httpBasic(httpSecurityHttpBasicConfigurer -> {
|
.and()
|
||||||
}).csrf(AbstractHttpConfigurer::disable);
|
.logout()
|
||||||
|
.logoutUrl(adminContextPath + "/logout")
|
||||||
|
.and()
|
||||||
|
.httpBasic()
|
||||||
|
.and()
|
||||||
|
.csrf()
|
||||||
|
.disable();
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +20,11 @@ public class NacosServiceInstanceConverter extends DefaultServiceInstanceConvert
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, String> getMetadata(ServiceInstance instance) {
|
protected Map<String, String> getMetadata(ServiceInstance instance) {
|
||||||
return (instance.getMetadata() != null)
|
return (instance.getMetadata() != null) ? instance.getMetadata()
|
||||||
? instance.getMetadata().entrySet().stream().filter((e) -> e.getKey() != null && e.getValue() != null)
|
.entrySet()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
.stream()
|
||||||
: emptyMap();
|
.filter((e) -> e.getKey() != null && e.getValue() != null)
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
FROM moxm/java:1.8-full
|
|
||||||
|
|
||||||
RUN mkdir -p /pig-sentinel-dashboard
|
|
||||||
|
|
||||||
WORKDIR /pig-sentinel-dashboard
|
|
||||||
|
|
||||||
ARG JAR_FILE=target/pig-sentinel-dashboard.jar
|
|
||||||
|
|
||||||
COPY ${JAR_FILE} app.jar
|
|
||||||
|
|
||||||
EXPOSE 5003
|
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom"
|
|
||||||
|
|
||||||
CMD sleep 60; java $JAVA_OPTS -jar app.jar
|
|
@ -1,157 +0,0 @@
|
|||||||
<?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>3.6.4</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>pig-sentinel-dashboard</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<spring-boot-dependencies.version>2.7.7</spring-boot-dependencies.version>
|
|
||||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
|
||||||
<spring-boot-admin.version>2.7.8</spring-boot-admin.version>
|
|
||||||
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
|
|
||||||
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!--注册中心客户端-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<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>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>
|
|
||||||
|
|
||||||
<!--依赖版本声明-->
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- SpringBoot 依赖配置 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
|
||||||
<version>${spring-boot-dependencies.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- SpringCloud 微服务 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-dependencies</artifactId>
|
|
||||||
<version>${spring-cloud.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- SpringBoot Admin -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>de.codecentric</groupId>
|
|
||||||
<artifactId>spring-boot-admin-starter-server</artifactId>
|
|
||||||
<version>${spring-boot-admin.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- spring cloud alibaba 依赖 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
|
||||||
<version>${spring-cloud-alibaba.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>pig-sentinel-dashboard</finalName>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</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>
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sentinel dashboard application.
|
|
||||||
*
|
|
||||||
* @author Carpenter Lee
|
|
||||||
*/
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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";
|
|
||||||
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.web.servlet.HandlerInterceptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The web interceptor for privilege-based authorization. 不需要改
|
|
||||||
*
|
|
||||||
* @author lkxiaolou
|
|
||||||
* @author wxq
|
|
||||||
* @since 1.7.1
|
|
||||||
*/
|
|
||||||
public interface AuthorizationInterceptor extends HandlerInterceptor {
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.web.method.HandlerMethod;
|
|
||||||
|
|
||||||
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. 不需要
|
|
||||||
* <p>
|
|
||||||
* move from old {@link AuthorizationInterceptor}.
|
|
||||||
*
|
|
||||||
* @author lkxiaolou
|
|
||||||
* @author wxq
|
|
||||||
* @since 1.7.1
|
|
||||||
*/
|
|
||||||
public class DefaultAuthorizationInterceptor implements AuthorizationInterceptor {
|
|
||||||
|
|
||||||
private final AuthService<HttpServletRequest> authService;
|
|
||||||
|
|
||||||
public DefaultAuthorizationInterceptor(AuthService<HttpServletRequest> authService) {
|
|
||||||
this.authService = 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Value;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
|
|
||||||
import javax.servlet.*;
|
|
||||||
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>
|
|
||||||
* <p>
|
|
||||||
* The excluded urls and urlSuffixes could be configured in {@code application.properties}
|
|
||||||
* file.
|
|
||||||
*
|
|
||||||
* @author cdfive 不需要
|
|
||||||
* @since 1.6.0
|
|
||||||
*/
|
|
||||||
public class DefaultLoginAuthenticationFilter implements LoginAuthenticationFilter {
|
|
||||||
|
|
||||||
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
|
|
||||||
|
|
||||||
private static final String URL_SUFFIX_DOT = ".";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 忽略鉴权的url
|
|
||||||
*/
|
|
||||||
@Value("#{'${auth.filter.exclude-urls}'.split(',')}")
|
|
||||||
private List<String> authFilterExcludeUrls;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据后缀不需要鉴权的url
|
|
||||||
*/
|
|
||||||
@Value("#{'${auth.filter.exclude-url-suffixes}'.split(',')}")
|
|
||||||
private List<String> authFilterExcludeUrlSuffixes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authentication using AuthService interface.
|
|
||||||
*/
|
|
||||||
private final AuthService<HttpServletRequest> authService;
|
|
||||||
|
|
||||||
public DefaultLoginAuthenticationFilter(AuthService<HttpServletRequest> authService) {
|
|
||||||
this.authService = 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
|
|
||||||
boolean authFilterExcludeMatch = authFilterExcludeUrls.stream()
|
|
||||||
.anyMatch(authFilterExcludeUrl -> PATH_MATCHER.match(authFilterExcludeUrl, servletPath));
|
|
||||||
if (authFilterExcludeMatch) {
|
|
||||||
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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fake AuthService implementation, which will pass all user auth checking.
|
|
||||||
*
|
|
||||||
* @author Carpenter Lee
|
|
||||||
* @since 1.5.0
|
|
||||||
*/
|
|
||||||
public class FakeAuthServiceImpl implements AuthService<HttpServletRequest> {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
|
|
||||||
public FakeAuthServiceImpl() {
|
|
||||||
this.logger.warn("there is no auth, use {} by implementation {}", AuthService.class, this.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Filter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <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>
|
|
||||||
* <p>
|
|
||||||
* The excluded urls and urlSuffixes could be configured in {@code application.properties}
|
|
||||||
* file.
|
|
||||||
*
|
|
||||||
* @author cdfive 不需要
|
|
||||||
* @author wxq
|
|
||||||
* @since 1.6.0
|
|
||||||
*/
|
|
||||||
public interface LoginAuthenticationFilter extends Filter {
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 javax.servlet.http.HttpSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author cdfive 不需要
|
|
||||||
* @since 1.6.0
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,884 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
|
||||||
import com.alibaba.csp.sentinel.command.CommandConstants;
|
|
||||||
import com.alibaba.csp.sentinel.command.vo.NodeVo;
|
|
||||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.*;
|
|
||||||
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.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.domain.cluster.state.ClusterServerStateVO;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterStateSimpleEntity;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.VersionUtils;
|
|
||||||
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 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.client.utils.URLEncodedUtils;
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 HTTP_HEADER_CONTENT_TYPE = "Content-Type";
|
|
||||||
|
|
||||||
private static final String HTTP_HEADER_CONTENT_TYPE_URLENCODED = ContentType.create(URLEncodedUtils.CONTENT_TYPE)
|
|
||||||
.toString();
|
|
||||||
|
|
||||||
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_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);
|
|
||||||
|
|
||||||
private static final SentinelVersion version171 = new SentinelVersion(1, 7, 1);
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isSupportPost(String app, String ip, int port) {
|
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether target instance (identified by tuple of app-ip:port) supports the
|
|
||||||
* form of "xxxxx; xx=xx" in "Content-Type" header.
|
|
||||||
* @param app target app name
|
|
||||||
* @param ip target node's address
|
|
||||||
* @param port target node's port
|
|
||||||
*/
|
|
||||||
protected boolean isSupportEnhancedContentType(String app, String ip, int port) {
|
|
||||||
return StringUtil.isNotEmpty(app)
|
|
||||||
&& Optional.ofNullable(appManagement.getDetailApp(app)).flatMap(e -> e.getMachine(ip, port))
|
|
||||||
.flatMap(m -> VersionUtils.parseVersion(m.getVersion()).map(v -> v.greaterOrEqual(version171)))
|
|
||||||
.orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build an `HttpUriRequest` in POST way.
|
|
||||||
* @param url
|
|
||||||
* @param params
|
|
||||||
* @param supportEnhancedContentType see
|
|
||||||
* {@link #isSupportEnhancedContentType(String, String, int)}
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected static HttpUriRequest postRequest(String url, Map<String, String> params,
|
|
||||||
boolean supportEnhancedContentType) {
|
|
||||||
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));
|
|
||||||
if (!supportEnhancedContentType) {
|
|
||||||
httpPost.setHeader(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_CONTENT_TYPE_URLENCODED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(HTTP_HEADER_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();
|
|
||||||
}
|
|
||||||
if (!useHttpPost || !isSupportPost(app, ip, port)) {
|
|
||||||
// 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, isSupportEnhancedContentType(app, ip, port)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.fromParamFlowRule(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.dashboard.auth.*;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableConfigurationProperties(AuthProperties.class)
|
|
||||||
public class AuthConfiguration {
|
|
||||||
|
|
||||||
private final AuthProperties authProperties;
|
|
||||||
|
|
||||||
public AuthConfiguration(AuthProperties authProperties) {
|
|
||||||
this.authProperties = authProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public AuthService<HttpServletRequest> httpServletRequestAuthService() {
|
|
||||||
if (this.authProperties.isEnabled()) {
|
|
||||||
return new SimpleWebAuthServiceImpl();
|
|
||||||
}
|
|
||||||
return new FakeAuthServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public LoginAuthenticationFilter loginAuthenticationFilter(
|
|
||||||
AuthService<HttpServletRequest> httpServletRequestAuthService) {
|
|
||||||
return new DefaultLoginAuthenticationFilter(httpServletRequestAuthService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public AuthorizationInterceptor authorizationInterceptor(
|
|
||||||
AuthService<HttpServletRequest> httpServletRequestAuthService) {
|
|
||||||
return new DefaultAuthorizationInterceptor(httpServletRequestAuthService);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
@ConfigurationProperties(prefix = "auth")
|
|
||||||
public class AuthProperties {
|
|
||||||
|
|
||||||
private boolean enabled = true;
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 org.apache.commons.lang.StringUtils;
|
|
||||||
import org.apache.commons.lang.math.NumberUtils;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.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.*;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,186 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.SentinelApiClient;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.SentinelApiClient;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity;
|
|
||||||
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.slots.block.RuleConstant;
|
|
||||||
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller regarding APIs of degrade rules. Refactored since 1.8.0.
|
|
||||||
*
|
|
||||||
* @author Carpenter Lee
|
|
||||||
* @author Eric Zhao
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/degrade")
|
|
||||||
public class DegradeController {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(DegradeController.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RuleRepository<DegradeRuleEntity, Long> repository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SentinelApiClient sentinelApiClient;
|
|
||||||
|
|
||||||
@GetMapping("/rules.json")
|
|
||||||
@AuthAction(PrivilegeType.READ_RULE)
|
|
||||||
public Result<List<DegradeRuleEntity>> apiQueryMachineRules(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/rule")
|
|
||||||
@AuthAction(PrivilegeType.WRITE_RULE)
|
|
||||||
public Result<DegradeRuleEntity> apiAddRule(@RequestBody DegradeRuleEntity entity) {
|
|
||||||
Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);
|
|
||||||
if (checkResult != null) {
|
|
||||||
return checkResult;
|
|
||||||
}
|
|
||||||
Date date = new Date();
|
|
||||||
entity.setGmtCreate(date);
|
|
||||||
entity.setGmtModified(date);
|
|
||||||
try {
|
|
||||||
entity = repository.save(entity);
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t);
|
|
||||||
return Result.ofThrowable(-1, t);
|
|
||||||
}
|
|
||||||
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
|
|
||||||
logger.warn("Publish degrade rules failed, app={}", entity.getApp());
|
|
||||||
}
|
|
||||||
return Result.ofSuccess(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/rule/{id}")
|
|
||||||
@AuthAction(PrivilegeType.WRITE_RULE)
|
|
||||||
public Result<DegradeRuleEntity> apiUpdateRule(@PathVariable("id") Long id, @RequestBody DegradeRuleEntity entity) {
|
|
||||||
if (id == null || id <= 0) {
|
|
||||||
return Result.ofFail(-1, "id can't be null or negative");
|
|
||||||
}
|
|
||||||
DegradeRuleEntity oldEntity = repository.findById(id);
|
|
||||||
if (oldEntity == null) {
|
|
||||||
return Result.ofFail(-1, "Degrade rule does not exist, id=" + id);
|
|
||||||
}
|
|
||||||
entity.setApp(oldEntity.getApp());
|
|
||||||
entity.setIp(oldEntity.getIp());
|
|
||||||
entity.setPort(oldEntity.getPort());
|
|
||||||
entity.setId(oldEntity.getId());
|
|
||||||
Result<DegradeRuleEntity> checkResult = checkEntityInternal(entity);
|
|
||||||
if (checkResult != null) {
|
|
||||||
return checkResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.setGmtCreate(oldEntity.getGmtCreate());
|
|
||||||
entity.setGmtModified(new Date());
|
|
||||||
try {
|
|
||||||
entity = repository.save(entity);
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t);
|
|
||||||
return Result.ofThrowable(-1, t);
|
|
||||||
}
|
|
||||||
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
|
|
||||||
logger.warn("Publish degrade rules failed, app={}", entity.getApp());
|
|
||||||
}
|
|
||||||
return Result.ofSuccess(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/rule/{id}")
|
|
||||||
@AuthAction(PrivilegeType.DELETE_RULE)
|
|
||||||
public Result<Long> delete(@PathVariable("id") 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("Failed to delete degrade rule, id={}", id, throwable);
|
|
||||||
return Result.ofThrowable(-1, throwable);
|
|
||||||
}
|
|
||||||
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
|
|
||||||
logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp());
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <R> Result<R> checkEntityInternal(DegradeRuleEntity entity) {
|
|
||||||
if (StringUtil.isBlank(entity.getApp())) {
|
|
||||||
return Result.ofFail(-1, "app can't be blank");
|
|
||||||
}
|
|
||||||
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, "invalid port: " + entity.getPort());
|
|
||||||
}
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
Double threshold = entity.getCount();
|
|
||||||
if (threshold == null || threshold < 0) {
|
|
||||||
return Result.ofFail(-1, "invalid threshold: " + threshold);
|
|
||||||
}
|
|
||||||
Integer recoveryTimeoutSec = entity.getTimeWindow();
|
|
||||||
if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) {
|
|
||||||
return Result.ofFail(-1, "recoveryTimeout should be positive");
|
|
||||||
}
|
|
||||||
Integer strategy = entity.getGrade();
|
|
||||||
if (strategy == null) {
|
|
||||||
return Result.ofFail(-1, "circuit breaker strategy cannot be null");
|
|
||||||
}
|
|
||||||
if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()
|
|
||||||
|| strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) {
|
|
||||||
return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy);
|
|
||||||
}
|
|
||||||
if (entity.getMinRequestAmount() == null || entity.getMinRequestAmount() <= 0) {
|
|
||||||
return Result.ofFail(-1, "Invalid minRequestAmount");
|
|
||||||
}
|
|
||||||
if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) {
|
|
||||||
return Result.ofFail(-1, "Invalid statInterval");
|
|
||||||
}
|
|
||||||
if (strategy == RuleConstant.DEGRADE_GRADE_RT) {
|
|
||||||
Double slowRatio = entity.getSlowRatioThreshold();
|
|
||||||
if (slowRatio == null) {
|
|
||||||
return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy");
|
|
||||||
}
|
|
||||||
else if (slowRatio < 0 || slowRatio > 1) {
|
|
||||||
return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) {
|
|
||||||
if (threshold > 1) {
|
|
||||||
return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.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;
|
|
||||||
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 java.util.Random;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,267 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.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 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.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.dashboard.discovery.MachineInfo;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
|
||||||
import org.apache.http.conn.util.InetAddressUtils;
|
|
||||||
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 (StringUtil.isBlank(app) || app.length() > 256) {
|
|
||||||
return Result.ofFail(-1, "invalid appName");
|
|
||||||
}
|
|
||||||
if (StringUtil.isBlank(ip) || ip.length() > 128) {
|
|
||||||
return Result.ofFail(-1, "invalid ip: " + ip);
|
|
||||||
}
|
|
||||||
if (!InetAddressUtils.isIPv4Address(ip) && !InetAddressUtils.isIPv6Address(ip)) {
|
|
||||||
return Result.ofFail(-1, "invalid ip: " + ip);
|
|
||||||
}
|
|
||||||
if (port == null || port < -1) {
|
|
||||||
return Result.ofFail(-1, "invalid port");
|
|
||||||
}
|
|
||||||
if (hostname != null && hostname.length() > 256) {
|
|
||||||
return Result.ofFail(-1, "hostname too long");
|
|
||||||
}
|
|
||||||
if (port == -1) {
|
|
||||||
logger.warn("Receive heartbeat from " + ip + " but port not set yet");
|
|
||||||
return Result.ofFail(-1, "your port not set yet");
|
|
||||||
}
|
|
||||||
String sentinelVersion = StringUtil.isBlank(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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.datasource.entity.MetricEntity;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.MetricVo;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
|
|
||||||
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.http.MediaType;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,270 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.command.vo.NodeVo;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.ResourceTreeNode;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.vo.ResourceVo;
|
|
||||||
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.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,257 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.SentinelApiClient;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
|
|
||||||
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.util.StringUtil;
|
|
||||||
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;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppFullAssignRequest;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterAppSingleServerAssignRequest;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.service.ClusterAssignService;
|
|
||||||
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.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.cluster.ClusterStateManager;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.client.CommandNotFoundException;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
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.util.StringUtil;
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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";
|
|
||||||
|
|
||||||
}
|
|
@ -1,267 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,445 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.adapter.gateway.common.SentinelGatewayConstants.*;
|
|
||||||
import static com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity.*;
|
|
||||||
import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,220 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.Result;
|
|
||||||
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.util.StringUtil;
|
|
||||||
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.*;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.dashboard.discovery.AppInfo;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
+ '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user