mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
Merge branch 'develop'
# Conflicts: # changelog.md # sop-admin/sop-admin-server/pom.xml # sop-common/pom.xml # sop-common/sop-gateway-common/pom.xml # sop-common/sop-registry-api/pom.xml # sop-common/sop-service-common/pom.xml # sop-example/sop-auth/pom.xml # sop-example/sop-book/sop-book-web/pom.xml # sop-example/sop-easyopen/pom.xml # sop-example/sop-springmvc/pom.xml # sop-example/sop-story/sop-story-web/pom.xml # sop-gateway/pom.xml # sop-website/website-server/pom.xml
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# changelog
|
||||
|
||||
## 1.14.0
|
||||
|
||||
- 支持预发布、灰度发布环境
|
||||
|
||||
## 1.13.7
|
||||
|
||||
- 修复修复context-path识别问题
|
||||
|
@@ -1,32 +1,32 @@
|
||||
* [首页](/?t=1564370846275)
|
||||
* [首页](/?t=1564558762831)
|
||||
* 开发文档
|
||||
* [快速体验](files/10010_快速体验.md?t=1564370846281)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1564370846298)
|
||||
* [新增接口](files/10020_新增接口.md?t=1564370846298)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1564370846299)
|
||||
* [错误处理](files/10040_错误处理.md?t=1564370846299)
|
||||
* [编写文档](files/10041_编写文档.md?t=1564370846299)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1564370846299)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1564370846299)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1564370846299)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1564370846299)
|
||||
* [自定义路由](files/10086_自定义路由.md?t=1564370846299)
|
||||
* [路由授权](files/10090_路由授权.md?t=1564370846300)
|
||||
* [接口限流](files/10092_接口限流.md?t=1564370846300)
|
||||
* [监控日志](files/10093_监控日志.md?t=1564370846300)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1564370846300)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1564370846300)
|
||||
* [应用授权](files/10097_应用授权.md?t=1564370846300)
|
||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1564370846300)
|
||||
* [传统web开发](files/10100_传统web开发.md?t=1564370846300)
|
||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1564370846300)
|
||||
* [文件上传](files/10104_文件上传.md?t=1564370846301)
|
||||
* [nacos注册中心](files/10106_nacos注册中心.md?t=1564370846301)
|
||||
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1564370846301)
|
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1564370846301)
|
||||
* [快速体验](files/10010_快速体验.md?t=1564558762834)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1564558762850)
|
||||
* [新增接口](files/10020_新增接口.md?t=1564558762850)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1564558762850)
|
||||
* [错误处理](files/10040_错误处理.md?t=1564558762850)
|
||||
* [编写文档](files/10041_编写文档.md?t=1564558762850)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1564558762850)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1564558762850)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1564558762850)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1564558762850)
|
||||
* [自定义路由](files/10086_自定义路由.md?t=1564558762851)
|
||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1564558762851)
|
||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1564558762851)
|
||||
* [路由授权](files/10090_路由授权.md?t=1564558762851)
|
||||
* [接口限流](files/10092_接口限流.md?t=1564558762851)
|
||||
* [监控日志](files/10093_监控日志.md?t=1564558762851)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1564558762851)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1564558762851)
|
||||
* [应用授权](files/10097_应用授权.md?t=1564558762851)
|
||||
* [传统web开发](files/10100_传统web开发.md?t=1564558762851)
|
||||
* [文件上传](files/10104_文件上传.md?t=1564558762852)
|
||||
* [nacos注册中心](files/10106_nacos注册中心.md?t=1564558762852)
|
||||
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1564558762852)
|
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1564558762852)
|
||||
* 原理分析
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1564370846301)
|
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1564370846301)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1564370846301)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1564370846301)
|
||||
* [常见问题](files/90100_常见问题.md?t=1564370846301)
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1564558762852)
|
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1564558762852)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1564558762852)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1564558762852)
|
||||
* [常见问题](files/90100_常见问题.md?t=1564558762852)
|
||||
|
125
doc/docs/files/10087_自定义返回结果.md
Normal file
125
doc/docs/files/10087_自定义返回结果.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 自定义返回结果
|
||||
|
||||
网关默认对业务结果进行合并,然后返回统一的格式。
|
||||
|
||||
针对`alipay.story.find`接口,微服务端返回结果如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "白雪公主",
|
||||
"id": 1,
|
||||
"gmtCreate": 1554193987378
|
||||
}
|
||||
```
|
||||
|
||||
网关合并后,最终结果如下
|
||||
|
||||
```json
|
||||
{
|
||||
"alipay_story_find_response": {
|
||||
"msg": "Success",
|
||||
"code": "10000",
|
||||
"name": "白雪公主",
|
||||
"id": 1,
|
||||
"gmtCreate": 1554193987378
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
其中`alipay_story_find_response`是它的数据节点。规则是:
|
||||
|
||||
> 将接口名中的点`.`转换成下划线`_`,后面加上`_response`
|
||||
|
||||
代码实现如下:
|
||||
|
||||
```java
|
||||
String method = "alipay.story.find";
|
||||
return method.replace('.', '_') + "_response";
|
||||
```
|
||||
|
||||
详见`DefaultDataNameBuilder.java`
|
||||
|
||||
如果要更改数据节点,比如`result`,可使用`CustomDataNameBuilder.java`。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ZuulConfig extends AlipayZuulConfiguration {
|
||||
|
||||
static {
|
||||
...
|
||||
ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder());
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
设置后,网关统一的返回结果如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
...
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
此外,构造方法可指定自定义字段名称:`new CustomDataNameBuilder("data");`。
|
||||
设置后,数据节点将变成`data`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
...
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
**注**:网关设置了CustomDataNameBuilder后,SDK也要做相应的更改:`SdkConfig.dataNameBuilder = new CustomDataNameBuilder();`
|
||||
|
||||
## 自定义结果处理
|
||||
|
||||
如果想要对微服务结果做更深一步处理,步骤如下:
|
||||
|
||||
1. 新增一个类,继承`ZuulResultExecutor.java`,并重写`public String merge(T exchange, JSONObject responseData)`方法
|
||||
|
||||
方法merge参数说明如下:
|
||||
|
||||
exchange:RequestContext对象
|
||||
responseData:微服务端返回的结果
|
||||
|
||||
方法返回最终结果
|
||||
|
||||
2. 配置自定义类
|
||||
|
||||
```java
|
||||
public class SopGatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApiConfig.getInstance().setZuulResultExecutor(new MyzuulResultExecutor());
|
||||
SpringApplication.run(SopGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 不合并结果
|
||||
|
||||
如果不希望对结果进行合并,可设置`ApiConfig.getInstance().setMergeResult(false);`
|
||||
|
||||
```java
|
||||
public class SopGatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApiConfig.getInstance().setMergeResult(false);
|
||||
SpringApplication.run(SopGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
这样,网关最终返回结果即为微服务端的返回结果。
|
@@ -1,69 +0,0 @@
|
||||
# 更改数据节点名称
|
||||
|
||||
针对`alipay.story.find`接口,它的返回结果如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"alipay_story_find_response": {
|
||||
"msg": "Success",
|
||||
"code": "10000",
|
||||
"name": "白雪公主",
|
||||
"id": 1,
|
||||
"gmtCreate": 1554193987378
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
其中`alipay_story_find_response`是它的数据节点。规则是:
|
||||
|
||||
> 将接口名中的点`.`转换成下划线`_`,后面加上`_response`
|
||||
|
||||
代码实现如下:
|
||||
|
||||
```java
|
||||
String method = "alipay.story.find";
|
||||
return method.replace('.', '_') + "_response";
|
||||
```
|
||||
|
||||
详见`DefaultDataNameBuilder.java`
|
||||
|
||||
如果要更改数据节点,比如`result`,可使用`CustomDataNameBuilder.java`。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ZuulConfig extends AlipayZuulConfiguration {
|
||||
|
||||
static {
|
||||
...
|
||||
ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder());
|
||||
...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
设置后,网关统一的返回结果如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
...
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
此外,构造方法可指定自定义字段名称:`new CustomDataNameBuilder("data");`。
|
||||
设置后,数据节点将变成`data`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
...
|
||||
},
|
||||
"sign": "xxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
**注**:网关设置了CustomDataNameBuilder后,SDK也要做相应的更改:`SdkConfig.dataNameBuilder = new CustomDataNameBuilder();`
|
41
doc/docs/files/10110_预发布灰度发布.md
Normal file
41
doc/docs/files/10110_预发布灰度发布.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 预发布灰度发布
|
||||
|
||||
从1.14.0开始支持预发布、灰度发布,可登陆`SOP-Admin`,然后选择`服务列表`进行操作。
|
||||
|
||||
## 使用预发布
|
||||
|
||||
假设网关工程在阿里云负载均衡有两台服务器,域名分别为:
|
||||
|
||||
|域名|说明|
|
||||
|:---- |:---- |
|
||||
|open1.domain.com |网关服务器1 |
|
||||
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
|
||||
|
||||
线上网关入口为`http://open.domain.com/api`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器
|
||||
|
||||
SOP开启预发布步骤如下:
|
||||
|
||||
修改网关工程配置文件,指定预发布域名
|
||||
|
||||
```properties
|
||||
# 预发布网关域名
|
||||
pre.domain=openpre.domain.com
|
||||
```
|
||||
重启网关
|
||||
|
||||
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。
|
||||
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
|
||||
|
||||
## 使用灰度发布
|
||||
|
||||
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
|
||||
|
||||
登录SOP-Admin,前往服务列表。
|
||||
|
||||
- 先设置灰度参数,指定灰度用户和灰度接口
|
||||
- 服务器实例开启灰度
|
||||
|
||||
参考类:
|
||||
|
||||
- PreVersionDecisionFilter.java
|
||||
- EnvironmentServerChooser.java
|
25
sop-1.14.0.sql
Normal file
25
sop-1.14.0.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
use sop;
|
||||
|
||||
CREATE TABLE `config_gray` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`service_id` varchar(64) NOT NULL DEFAULT '',
|
||||
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
|
||||
`name_version_content` text COMMENT '需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开',
|
||||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_serviceid` (`service_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='服务灰度配置';
|
||||
|
||||
|
||||
CREATE TABLE `config_gray_instance` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'instance_id',
|
||||
`service_id` varchar(64) NOT NULL DEFAULT '' COMMENT 'service_id',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0:禁用,1:启用',
|
||||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_instanceid` (`instance_id`) USING BTREE,
|
||||
KEY `idx_serviceid` (`service_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='开启灰度服务器实例';
|
@@ -26,6 +26,7 @@ import com.gitee.sop.adminserver.api.isv.result.RoleVO;
|
||||
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import com.gitee.sop.adminserver.common.BizException;
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import com.gitee.sop.adminserver.common.IdGen;
|
||||
import com.gitee.sop.adminserver.common.RSATool;
|
||||
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
|
||||
@@ -229,7 +230,7 @@ public class IsvApi {
|
||||
if (isvDetail == null) {
|
||||
return;
|
||||
}
|
||||
ChannelMsg channelMsg = new ChannelMsg("update", isvDetail);
|
||||
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ISV_INFO_UPDATE, isvDetail);
|
||||
String path = ZookeeperContext.getIsvInfoChannelPath();
|
||||
String data = JSON.toJSONString(channelMsg);
|
||||
try {
|
||||
|
@@ -16,6 +16,7 @@ import com.gitee.sop.adminserver.api.service.result.ConfigIpBlacklistVO;
|
||||
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import com.gitee.sop.adminserver.common.BizException;
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import com.gitee.sop.adminserver.entity.ConfigIpBlacklist;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigIpBlacklistMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -51,7 +52,7 @@ public class IPBlacklistApi {
|
||||
CopyUtil.copyPropertiesIgnoreNull(form, rec);
|
||||
configIpBlacklistMapper.saveIgnoreNull(rec);
|
||||
try {
|
||||
this.sendIpBlacklistMsg(rec, BlacklistMsgType.ADD);
|
||||
this.sendIpBlacklistMsg(rec, ChannelOperation.BLACKLIST_ADD);
|
||||
} catch (Exception e) {
|
||||
log.error("推送IP黑名单失败, rec:{}",rec, e);
|
||||
throw new BizException("推送IP黑名单失败");
|
||||
@@ -75,19 +76,18 @@ public class IPBlacklistApi {
|
||||
}
|
||||
configIpBlacklistMapper.deleteById(id);
|
||||
try {
|
||||
this.sendIpBlacklistMsg(rec, BlacklistMsgType.DELETE);
|
||||
this.sendIpBlacklistMsg(rec, ChannelOperation.BLACKLIST_DELETE);
|
||||
} catch (Exception e) {
|
||||
log.error("推送IP黑名单失败, rec:{}",rec, e);
|
||||
throw new BizException("推送IP黑名单失败");
|
||||
}
|
||||
}
|
||||
|
||||
public void sendIpBlacklistMsg(ConfigIpBlacklist configIpBlacklist, BlacklistMsgType blacklistMsgType) throws Exception {
|
||||
String configData = JSON.toJSONString(configIpBlacklist);
|
||||
ChannelMsg channelMsg = new ChannelMsg(blacklistMsgType.name().toLowerCase(), configData);
|
||||
public void sendIpBlacklistMsg(ConfigIpBlacklist configIpBlacklist, ChannelOperation channelOperation) throws Exception {
|
||||
ChannelMsg channelMsg = new ChannelMsg(channelOperation, configIpBlacklist);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getIpBlacklistChannelPath();
|
||||
log.info("消息推送--IP黑名单设置({}), path:{}, data:{}",blacklistMsgType.name(), path, jsonData);
|
||||
log.info("消息推送--IP黑名单设置({}), path:{}, data:{}",channelOperation.getOperation(), path, jsonData);
|
||||
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||
}
|
||||
|
||||
|
@@ -6,15 +6,29 @@ import com.gitee.easyopen.annotation.ApiService;
|
||||
import com.gitee.easyopen.doc.annotation.ApiDoc;
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
|
||||
import com.gitee.sop.adminserver.api.service.param.ServiceAddParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.ServiceGrayConfigParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.ServiceIdParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.ServiceInstanceParam;
|
||||
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
|
||||
import com.gitee.sop.adminserver.api.service.result.RouteServiceInfo;
|
||||
import com.gitee.sop.adminserver.api.service.result.ServiceInfoVo;
|
||||
import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
|
||||
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.MetadataEnum;
|
||||
import com.gitee.sop.adminserver.bean.ServiceGrayDefinition;
|
||||
import com.gitee.sop.adminserver.bean.ServiceRouteInfo;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import com.gitee.sop.adminserver.common.BizException;
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import com.gitee.sop.adminserver.common.StatusEnum;
|
||||
import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
|
||||
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
|
||||
import com.gitee.sop.adminserver.entity.ConfigGray;
|
||||
import com.gitee.sop.adminserver.entity.ConfigGrayInstance;
|
||||
import com.gitee.sop.adminserver.entity.ConfigGrayUserkey;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigGrayInstanceMapper;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigGrayMapper;
|
||||
import com.gitee.sop.adminserver.mapper.ConfigGrayUserkeyMapper;
|
||||
import com.gitee.sop.registryapi.bean.ServiceInfo;
|
||||
import com.gitee.sop.registryapi.bean.ServiceInstance;
|
||||
import com.gitee.sop.registryapi.service.RegistryService;
|
||||
@@ -41,9 +55,19 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class ServiceApi {
|
||||
|
||||
|
||||
@Autowired
|
||||
private RegistryService registryService;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayUserkeyMapper configGrayUserkeyMapper;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayMapper configGrayMapper;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayInstanceMapper configGrayInstanceMapper;
|
||||
|
||||
@Api(name = "zookeeper.service.list")
|
||||
@ApiDocMethod(description = "zk中的服务列表", elementClass = RouteServiceInfo.class)
|
||||
List<RouteServiceInfo> listServiceInfo(ServiceSearchParam param) {
|
||||
@@ -144,6 +168,9 @@ public class ServiceApi {
|
||||
int id = idGen.getAndIncrement();
|
||||
instanceVO.setId(id);
|
||||
instanceVO.setParentId(pid);
|
||||
if (instanceVO.getMetadata() == null) {
|
||||
instanceVO.setMetadata(Collections.emptyMap());
|
||||
}
|
||||
serviceInfoVoList.add(instanceVO);
|
||||
}
|
||||
});
|
||||
@@ -152,25 +179,138 @@ public class ServiceApi {
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.offline")
|
||||
@ApiDocMethod(description = "服务下线")
|
||||
void serviceOffline(ServiceInstance param) {
|
||||
@ApiDocMethod(description = "服务禁用")
|
||||
void serviceOffline(ServiceInstanceParam param) {
|
||||
try {
|
||||
registryService.offlineInstance(param);
|
||||
registryService.offlineInstance(param.buildServiceInstance());
|
||||
} catch (Exception e) {
|
||||
log.error("下线失败,param:{}", param, e);
|
||||
throw new BizException("下线失败,请查看日志");
|
||||
log.error("服务禁用失败,param:{}", param, e);
|
||||
throw new BizException("服务禁用失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.online")
|
||||
@ApiDocMethod(description = "服务上线")
|
||||
void serviceOnline(ServiceInstance param) throws IOException {
|
||||
@ApiDocMethod(description = "服务启用")
|
||||
void serviceOnline(ServiceInstanceParam param) throws IOException {
|
||||
try {
|
||||
registryService.onlineInstance(param);
|
||||
registryService.onlineInstance(param.buildServiceInstance());
|
||||
} catch (Exception e) {
|
||||
log.error("服务启用失败,param:{}", param, e);
|
||||
throw new BizException("服务启用失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.env.pre.open")
|
||||
@ApiDocMethod(description = "预发布")
|
||||
void serviceEnvPre(ServiceInstanceParam param) throws IOException {
|
||||
try {
|
||||
MetadataEnum envPre = MetadataEnum.ENV_PRE;
|
||||
registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
|
||||
} catch (Exception e) {
|
||||
log.error("预发布失败,param:{}", param, e);
|
||||
throw new BizException("预发布失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "service.gray.config.get")
|
||||
@ApiDocMethod(description = "灰度配置--获取")
|
||||
ConfigGray serviceEnvGrayConfigGet(ServiceIdParam param) throws IOException {
|
||||
return this.getConfigGray(param.getServiceId());
|
||||
}
|
||||
|
||||
@Api(name = "service.gray.config.save")
|
||||
@ApiDocMethod(description = "灰度配置--保存")
|
||||
void serviceEnvGrayConfigSave(ServiceGrayConfigParam param) throws IOException {
|
||||
String serviceId = param.getServiceId().toLowerCase();
|
||||
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
||||
if (configGray == null) {
|
||||
configGray = new ConfigGray();
|
||||
configGray.setServiceId(serviceId);
|
||||
configGray.setNameVersionContent(param.getNameVersionContent());
|
||||
configGray.setUserKeyContent(param.getUserKeyContent());
|
||||
configGrayMapper.save(configGray);
|
||||
} else {
|
||||
configGray.setNameVersionContent(param.getNameVersionContent());
|
||||
configGray.setUserKeyContent(param.getUserKeyContent());
|
||||
configGrayMapper.update(configGray);
|
||||
}
|
||||
this.sendServiceGrayMsg(serviceId, ChannelOperation.GRAY_USER_KEY_SET);
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.env.gray.open")
|
||||
@ApiDocMethod(description = "开启灰度发布")
|
||||
void serviceEnvGray(ServiceInstanceParam param) throws IOException {
|
||||
try {
|
||||
String serviceId = param.getServiceId().toLowerCase();
|
||||
ConfigGray configGray = this.getConfigGray(serviceId);
|
||||
if (configGray == null) {
|
||||
throw new BizException("请先设置灰度参数");
|
||||
}
|
||||
MetadataEnum envPre = MetadataEnum.ENV_GRAY;
|
||||
registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
|
||||
|
||||
String instanceId = param.getInstanceId();
|
||||
|
||||
ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
|
||||
if (configGrayInstance == null) {
|
||||
configGrayInstance = new ConfigGrayInstance();
|
||||
configGrayInstance.setServiceId(serviceId);
|
||||
configGrayInstance.setInstanceId(instanceId);
|
||||
configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
|
||||
configGrayInstanceMapper.save(configGrayInstance);
|
||||
} else {
|
||||
configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
|
||||
configGrayInstance.setServiceId(serviceId);
|
||||
configGrayInstanceMapper.update(configGrayInstance);
|
||||
}
|
||||
this.sendServiceGrayMsg(instanceId, serviceId, ChannelOperation.GRAY_USER_KEY_OPEN);
|
||||
} catch (Exception e) {
|
||||
log.error("灰度发布失败,param:{}", param, e);
|
||||
throw new BizException("灰度发布失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.env.online")
|
||||
@ApiDocMethod(description = "上线")
|
||||
void serviceEnvOnline(ServiceInstance param) throws IOException {
|
||||
try {
|
||||
MetadataEnum envPre = MetadataEnum.ENV_ONLINE;
|
||||
registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
|
||||
|
||||
ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", param.getInstanceId());
|
||||
if (configGrayInstance != null) {
|
||||
configGrayInstance.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
|
||||
configGrayInstanceMapper.update(configGrayInstance);
|
||||
this.sendServiceGrayMsg(param.getInstanceId(), param.getServiceId().toLowerCase(), ChannelOperation.GRAY_USER_KEY_CLOSE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("上线失败,param:{}", param, e);
|
||||
throw new BizException("上线失败,请查看日志");
|
||||
}
|
||||
}
|
||||
|
||||
@Api(name = "service.instance.gray.userkey.get")
|
||||
ConfigGrayUserkey getGrayUserkey(ServiceSearchParam param) {
|
||||
return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
|
||||
}
|
||||
|
||||
private void sendServiceGrayMsg(String serviceId, ChannelOperation channelOperation) {
|
||||
this.sendServiceGrayMsg(null, serviceId, channelOperation);
|
||||
}
|
||||
|
||||
private void sendServiceGrayMsg(String instanceId, String serviceId, ChannelOperation channelOperation) {
|
||||
ServiceGrayDefinition serviceGrayDefinition = new ServiceGrayDefinition();
|
||||
serviceGrayDefinition.setInstanceId(instanceId);
|
||||
serviceGrayDefinition.setServiceId(serviceId);
|
||||
ChannelMsg channelMsg = new ChannelMsg(channelOperation, serviceGrayDefinition);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getServiceGrayChannelPath();
|
||||
log.info("消息推送--灰度发布({}), path:{}, data:{}",channelOperation.getOperation(), path, jsonData);
|
||||
ZookeeperContext.createOrUpdateData(path, jsonData);
|
||||
}
|
||||
|
||||
private ConfigGray getConfigGray(String serviceId) {
|
||||
return configGrayMapper.getByColumn("service_id", serviceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ServiceGrayConfigParam {
|
||||
@ApiDocField(description = "服务名serviceId")
|
||||
@NotBlank(message = "serviceId不能为空")
|
||||
private String serviceId;
|
||||
|
||||
@ApiDocField(description = "灰度发布用户,多个用英文逗号隔开")
|
||||
@NotBlank(message = "灰度发布用户不能为空")
|
||||
private String userKeyContent;
|
||||
|
||||
@ApiDocField(description = "灰度发布接口名版本号如:order.get1.0=1.2,多个用英文逗号隔开")
|
||||
@NotBlank(message = "灰度发布接口名版本号不能为空")
|
||||
private String nameVersionContent;
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ServiceIdParam {
|
||||
@ApiDocField(description = "serviceId")
|
||||
@NotBlank(message = "serviceId不能为空")
|
||||
private String serviceId;
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class ServiceInstanceGrayParam extends ServiceInstanceParam {
|
||||
|
||||
@ApiDocField(description = "灰度发布用户,多个用英文逗号隔开")
|
||||
@NotBlank(message = "灰度发布用户不能为空")
|
||||
private String userKeyContent;
|
||||
|
||||
@ApiDocField(description = "灰度发布接口名版本号如:order.get1.0=1.2,多个用英文逗号隔开")
|
||||
@NotBlank(message = "灰度发布接口名版本号不能为空")
|
||||
private String nameVersionContent;
|
||||
|
||||
@ApiDocField(description = "是否仅更新灰度用户")
|
||||
private Boolean onlyUpdateGrayUserkey;
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
package com.gitee.sop.adminserver.api.service.param;
|
||||
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import com.gitee.easyopen.util.CopyUtil;
|
||||
import com.gitee.sop.registryapi.bean.ServiceInstance;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
@@ -17,4 +19,29 @@ public class ServiceInstanceParam {
|
||||
@ApiDocField(description = "instanceId")
|
||||
@NotBlank(message = "instanceId不能为空")
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* ip
|
||||
*/
|
||||
@ApiDocField(description = "ip")
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* port
|
||||
*/
|
||||
@ApiDocField(description = "port")
|
||||
private int port;
|
||||
|
||||
/**
|
||||
* 服务状态,UP:已上线,OUT_OF_SERVICE:已下线
|
||||
*/
|
||||
@ApiDocField(description = "status")
|
||||
private String status;
|
||||
|
||||
|
||||
public ServiceInstance buildServiceInstance() {
|
||||
ServiceInstance serviceInstance = new ServiceInstance();
|
||||
CopyUtil.copyPropertiesIgnoreNull(this, serviceInstance);
|
||||
return serviceInstance;
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@ import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@@ -15,4 +13,7 @@ public class ServiceSearchParam {
|
||||
|
||||
@ApiDocField(description = "服务名serviceId")
|
||||
private String serviceId;
|
||||
|
||||
@ApiDocField(description = "instanceId")
|
||||
private String instanceId;
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@ package com.gitee.sop.adminserver.api.service.result;
|
||||
import com.gitee.easyopen.doc.annotation.ApiDocField;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@@ -35,6 +37,9 @@ public class ServiceInstanceVO {
|
||||
@ApiDocField(description = "parentId")
|
||||
private Integer parentId;
|
||||
|
||||
@ApiDocField(description = "metadata")
|
||||
private Map<String, String> metadata;
|
||||
|
||||
public String getIpPort() {
|
||||
return ip != null && port > 0 ? ip + ":" + port : "";
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.gitee.sop.adminserver.bean;
|
||||
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -8,8 +9,8 @@ import lombok.Data;
|
||||
@Data
|
||||
public class ChannelMsg {
|
||||
|
||||
public ChannelMsg(String operation, Object data) {
|
||||
this.operation = operation;
|
||||
public ChannelMsg(ChannelOperation channelOperation, Object data) {
|
||||
this.operation = channelOperation.getOperation();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package com.gitee.sop.adminserver.bean;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public enum MetadataEnum {
|
||||
/**
|
||||
* 预发布环境
|
||||
*/
|
||||
ENV_PRE("env", "pre"),
|
||||
|
||||
/**
|
||||
* 上线环境
|
||||
*/
|
||||
ENV_ONLINE("env", ""),
|
||||
|
||||
/**
|
||||
* 灰度环境
|
||||
*/
|
||||
ENV_GRAY("env", "gray"),
|
||||
;
|
||||
private String key,value;
|
||||
|
||||
MetadataEnum(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.gitee.sop.adminserver.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ServiceGrayDefinition {
|
||||
private String serviceId;
|
||||
private String instanceId;
|
||||
private String data;
|
||||
}
|
@@ -89,6 +89,10 @@ public class ZookeeperContext {
|
||||
return serviceIdPath + "/" + routeId;
|
||||
}
|
||||
|
||||
public static String getServiceGrayChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/gray";
|
||||
}
|
||||
|
||||
public static String getIsvInfoChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
|
||||
}
|
||||
|
@@ -0,0 +1,64 @@
|
||||
package com.gitee.sop.adminserver.common;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public enum ChannelOperation {
|
||||
/**
|
||||
* 限流推送路由配置-修改
|
||||
*/
|
||||
LIMIT_CONFIG_UPDATE("update"),
|
||||
|
||||
/**
|
||||
* 路由信息更新
|
||||
*/
|
||||
ROUTE_CONFIG_UPDATE("update"),
|
||||
|
||||
/**
|
||||
* isv信息修改
|
||||
*/
|
||||
ISV_INFO_UPDATE("update"),
|
||||
|
||||
/**
|
||||
* 黑名单消息类型:添加
|
||||
*/
|
||||
BLACKLIST_ADD("add"),
|
||||
/**
|
||||
* 黑名单消息类型:删除
|
||||
*/
|
||||
BLACKLIST_DELETE("delete"),
|
||||
|
||||
/**
|
||||
* 路由权限配置更新
|
||||
*/
|
||||
ROUTE_PERMISSION_UPDATE("update"),
|
||||
/**
|
||||
* 路由权限加载
|
||||
*/
|
||||
ROUTE_PERMISSION_RELOAD("reload"),
|
||||
|
||||
/**
|
||||
* 灰度发布设置
|
||||
*/
|
||||
GRAY_USER_KEY_SET("set"),
|
||||
/**
|
||||
* 灰度发布-开启
|
||||
*/
|
||||
GRAY_USER_KEY_OPEN("open"),
|
||||
/**
|
||||
* 灰度发布-关闭
|
||||
*/
|
||||
GRAY_USER_KEY_CLOSE("close"),
|
||||
|
||||
;
|
||||
|
||||
private String operation;
|
||||
|
||||
ChannelOperation(String operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public String getOperation() {
|
||||
return operation;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.gitee.sop.adminserver.common;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 通用状态枚举
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Getter
|
||||
public enum StatusEnum {
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
STATUS_ENABLE((byte)1),
|
||||
/**
|
||||
* 禁用
|
||||
*/
|
||||
STATUS_DISABLE((byte)0),
|
||||
;
|
||||
private byte status;
|
||||
|
||||
StatusEnum(byte status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package com.gitee.sop.adminserver.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray
|
||||
* 备注:服务灰度配置
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_gray")
|
||||
@Data
|
||||
public class ConfigGray {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
||||
private String userKeyContent;
|
||||
|
||||
/** 需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段:name_version_content */
|
||||
private String nameVersionContent;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.gitee.sop.adminserver.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray_instance
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_gray_instance")
|
||||
@Data
|
||||
public class ConfigGrayInstance {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** instance_id, 数据库字段:instance_id */
|
||||
private String instanceId;
|
||||
|
||||
/** service_id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 0:禁用,1:启用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.adminserver.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
|
||||
import com.gitee.sop.adminserver.entity.ConfigGrayInstance;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigGrayInstanceMapper extends CrudMapper<ConfigGrayInstance, Long> {
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.adminserver.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
|
||||
import com.gitee.sop.adminserver.entity.ConfigGray;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigGrayMapper extends CrudMapper<ConfigGray, Long> {
|
||||
}
|
@@ -5,6 +5,7 @@ import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.adminserver.bean.RouteConfigDto;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -21,8 +22,7 @@ public class RouteConfigService {
|
||||
* @throws Exception
|
||||
*/
|
||||
public void sendRouteConfigMsg(RouteConfigDto routeConfigDto) {
|
||||
String configData = JSON.toJSONString(routeConfigDto);
|
||||
ChannelMsg channelMsg = new ChannelMsg("update", configData);
|
||||
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_CONFIG_UPDATE, routeConfigDto);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getRouteConfigChannelPath();
|
||||
log.info("消息推送--路由配置(update), path:{}, data:{}", path, jsonData);
|
||||
@@ -35,8 +35,7 @@ public class RouteConfigService {
|
||||
* @throws Exception
|
||||
*/
|
||||
public void sendLimitConfigMsg(ConfigLimitDto routeConfigDto) throws Exception {
|
||||
String configData = JSON.toJSONString(routeConfigDto);
|
||||
ChannelMsg channelMsg = new ChannelMsg("update", configData);
|
||||
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.LIMIT_CONFIG_UPDATE, routeConfigDto);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getLimitConfigChannelPath();
|
||||
log.info("消息推送--限流配置(update), path:{}, data:{}", path, jsonData);
|
||||
|
@@ -7,6 +7,7 @@ import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||
import com.gitee.sop.adminserver.bean.IsvRoutePermission;
|
||||
import com.gitee.sop.adminserver.bean.SopAdminConstants;
|
||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||
import com.gitee.sop.adminserver.entity.PermIsvRole;
|
||||
import com.gitee.sop.adminserver.entity.PermRolePermission;
|
||||
import com.gitee.sop.adminserver.mapper.IsvInfoMapper;
|
||||
@@ -68,7 +69,7 @@ public class RoutePermissionService {
|
||||
isvRoutePermission.setAppKey(appKey);
|
||||
isvRoutePermission.setRouteIdList(routeIdList);
|
||||
isvRoutePermission.setRouteIdListMd5(roleCodeListMd5);
|
||||
ChannelMsg channelMsg = new ChannelMsg("update", isvRoutePermission);
|
||||
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_PERMISSION_UPDATE, isvRoutePermission);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
|
||||
log.info("消息推送--路由权限(update), path:{}, data:{}", path, jsonData);
|
||||
@@ -105,7 +106,7 @@ public class RoutePermissionService {
|
||||
});
|
||||
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
|
||||
isvRoutePermission.setListenPath(listenPath);
|
||||
ChannelMsg channelMsg = new ChannelMsg("reload", isvRoutePermission);
|
||||
ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ROUTE_PERMISSION_RELOAD, isvRoutePermission);
|
||||
String jsonData = JSON.toJSONString(channelMsg);
|
||||
String path = ZookeeperContext.getIsvRoutePermissionChannelPath();
|
||||
log.info("消息推送--路由权限(reload), path:{}, data:{}", path, jsonData);
|
||||
|
@@ -45,8 +45,8 @@ registry.name=eureka
|
||||
logging.level.com.gitee=debug
|
||||
|
||||
# 不用改
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||
mybatis.ignore-update-columns[0]=gmt_create
|
||||
mybatis.ignore-update-columns[1]=gmt_modified
|
||||
|
||||
# 不用改,如果要改,请全局替换修改
|
||||
zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],l=0,d=[];l<a.length;l++)c=a[l],u[c]&&d.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(d.length)d.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-238a81e9":"5955f13d","chunk-25908fca":"ca176fa6","chunk-29e7142c":"994a3ac0","chunk-2d2085ef":"7c741493","chunk-2d208c3a":"93f165b2","chunk-2d221c34":"8f017357","chunk-34c76be7":"98e1e7e5","chunk-37401378":"4e39ec9b","chunk-6f78c9fe":"f1ed64fa","chunk-73b2dcec":"14f248eb","chunk-9b31c83a":"2758df30"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-238a81e9":1,"chunk-25908fca":1,"chunk-29e7142c":1,"chunk-34c76be7":1,"chunk-37401378":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-238a81e9":"e8e2beee","chunk-25908fca":"89ab33e8","chunk-29e7142c":"d10599db","chunk-2d2085ef":"31d6cfe0","chunk-2d208c3a":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-34c76be7":"f531fb07","chunk-37401378":"a43114f3","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"99cf6327","chunk-9b31c83a":"3b12267b"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],l=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(l===r||l===u))return n()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){i=d[a],l=i.getAttribute("data-href");if(l===r||l===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,l=document.createElement("script");l.charset="utf-8",l.timeout=120,f.nc&&l.setAttribute("nonce",f.nc),l.src=a(e),i=function(n){l.onerror=l.onload=null,clearTimeout(d);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var d=setTimeout(function(){i({type:"timeout",target:l})},12e4);l.onerror=l.onload=i,document.head.appendChild(l)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],l=i.push.bind(i);i.push=n,i=i.slice();for(var d=0;d<i.length;d++)n(i[d]);var h=l;t()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.4a507d5e.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],l=0,h=[];l<a.length;l++)c=a[l],u[c]&&h.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);d&&d(n);while(h.length)h.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-238a81e9":"5955f13d","chunk-25908fca":"ca176fa6","chunk-2d2085ef":"7c741493","chunk-2d221c34":"c8ef105a","chunk-34c76be7":"98e1e7e5","chunk-37401378":"4e39ec9b","chunk-6f78c9fe":"f1ed64fa","chunk-73b2dcec":"14f248eb","chunk-9b31c83a":"2758df30","chunk-9f479afe":"1204bc29","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-238a81e9":1,"chunk-25908fca":1,"chunk-34c76be7":1,"chunk-37401378":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-ea2e58a4":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-238a81e9":"e8e2beee","chunk-25908fca":"89ab33e8","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-34c76be7":"f531fb07","chunk-37401378":"a43114f3","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"99cf6327","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],l=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(l===r||l===u))return n()}var h=document.getElementsByTagName("style");for(a=0;a<h.length;a++){i=h[a],l=i.getAttribute("data-href");if(l===r||l===u)return n()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=n,d.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],d.parentNode.removeChild(d),t(o)},d.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(d)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,l=document.createElement("script");l.charset="utf-8",l.timeout=120,f.nc&&l.setAttribute("nonce",f.nc),l.src=a(e),i=function(n){l.onerror=l.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var h=setTimeout(function(){i({type:"timeout",target:l})},12e4);l.onerror=l.onload=i,document.head.appendChild(l)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],l=i.push.bind(i);i.push=n,i=i.slice();for(var h=0;h<i.length;h++)n(i[h]);var d=l;t()})([]);</script><script src=static/js/chunk-elementUI.8ebdfbab.js></script><script src=static/js/chunk-libs.9cf9cc40.js></script><script src=static/js/app.8145abe4.js></script></body></html>
|
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d208c3a"],{a5d4:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"app-container"},[n("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[n("el-form-item",{attrs:{label:"serviceId"}},[n("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),n("el-form-item",[n("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),n("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[n("el-table-column",{attrs:{prop:"serviceId",label:"服务名称",width:"200"},scopedSlots:t._u([{key:"default",fn:function(e){return[n("span",{domProps:{innerHTML:t._s(t.renderServiceName(e.row))}})]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"ipPort",label:"IP端口",width:"250"}}),t._v(" "),n("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?n("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?n("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?n("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?n("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),n("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),n("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?n("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(n){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(n("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var n=e.parentId;0===n||t.forEach(function(t){if(t.id===n){var a=t.children;a||(a=[]),a.push(e),t.children=a}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.serviceId+"】吗?",function(e){this.post("service.instance.offline",t,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.serviceId+"】吗?",function(e){this.post("service.instance.online",t,function(){this.tip("上线成功"),e()})})},renderServiceName:function(t){var e="";if(t.children&&t.children.length>0){var n=t.children.filter(function(t){return"UP"===t.status}).length;e=" (".concat(n,"/").concat(t.children.length,")")}return t.serviceId+e}}}),o=i,s=n("2877"),l=Object(s["a"])(o,a,r,!1,null,null,null);e["default"]=l.exports}}]);
|
@@ -1 +1 @@
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d221c34"],{cc75:function(a,t,e){"use strict";e.r(t);var i=function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",{staticClass:"app-container"},[e("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:a.searchFormData,size:"mini"}},[e("el-form-item",{attrs:{label:"IP"}},[e("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"输入IP"},model:{value:a.searchFormData.ip,callback:function(t){a.$set(a.searchFormData,"ip",t)},expression:"searchFormData.ip"}})],1),a._v(" "),e("el-form-item",[e("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:a.loadTable}},[a._v("查询")])],1)],1),a._v(" "),e("el-button",{staticStyle:{"margin-bottom":"10px"},attrs:{type:"primary",size:"mini",icon:"el-icon-plus"},on:{click:a.onAdd}},[a._v("新增IP")]),a._v(" "),e("el-table",{attrs:{data:a.pageInfo.rows,border:"","highlight-current-row":""}},[e("el-table-column",{attrs:{prop:"ip",label:"IP",width:"200"}}),a._v(" "),e("el-table-column",{attrs:{prop:"remark",label:"备注",width:"300"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtCreate",label:"添加时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtModified",label:"修改时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{label:"操作",width:"150"},scopedSlots:a._u([{key:"default",fn:function(t){return[e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableUpdate(t.row)}}},[a._v("修改")]),a._v(" "),e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableDelete(t.row)}}},[a._v("删除")])]}}])})],1),a._v(" "),e("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":a.searchFormData.pageIndex,"page-size":a.searchFormData.pageSize,"page-sizes":[5,10,20,40],total:a.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":a.onSizeChange,"current-change":a.onPageIndexChange}}),a._v(" "),e("el-dialog",{attrs:{title:a.dialogTitle,visible:a.dialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){a.dialogVisible=t},close:function(t){return a.resetForm("dialogForm")}}},[e("el-form",{ref:"dialogForm",attrs:{rules:a.dialogFormRules,model:a.dialogFormData,"label-width":"120px",size:"mini"}},[e("el-form-item",{attrs:{prop:"ip",label:"IP"}},[e("el-input",{directives:[{name:"show",rawName:"v-show",value:0===a.dialogFormData.id,expression:"dialogFormData.id === 0"}],model:{value:a.dialogFormData.ip,callback:function(t){a.$set(a.dialogFormData,"ip",t)},expression:"dialogFormData.ip"}}),a._v(" "),e("span",{directives:[{name:"show",rawName:"v-show",value:a.dialogFormData.id>0,expression:"dialogFormData.id > 0"}]},[a._v(a._s(a.dialogFormData.ip))])],1),a._v(" "),e("el-form-item",{attrs:{prop:"remark",label:"备注"}},[e("el-input",{model:{value:a.dialogFormData.remark,callback:function(t){a.$set(a.dialogFormData,"remark",t)},expression:"dialogFormData.remark"}})],1)],1),a._v(" "),e("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[e("el-button",{on:{click:function(t){a.dialogVisible=!1}}},[a._v("取 消")]),a._v(" "),e("el-button",{attrs:{type:"primary"},on:{click:a.onDialogSave}},[a._v("保 存")])],1)],1)],1)},o=[],l={data:function(){var a=function(a,t,e){if(""===t)e(new Error("请输入IP"));else{var i=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;i.test(t)||e(new Error("IP格式不正确")),e()}};return{searchFormData:{ip:"",pageIndex:1,pageSize:10},pageInfo:{rows:[],total:0},dialogVisible:!1,dialogTitle:"",dialogFormData:{id:0,ip:"",remark:""},dialogFormRules:{ip:[{validator:a,trigger:"blur"},{min:1,max:64,message:"长度在 1 到 64 个字符",trigger:"blur"}],remark:[{max:100,message:"不能超过 100 个字符",trigger:"blur"}]}}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("ip.blacklist.page",this.searchFormData,function(a){this.pageInfo=a.data})},onTableUpdate:function(a){var t=this;this.dialogTitle="修改IP",this.dialogVisible=!0,this.$nextTick(function(){Object.assign(t.dialogFormData,a)})},onTableDelete:function(a){this.confirm("确认要移除IP【".concat(a.ip,"】吗?"),function(t){var e={id:a.id};this.post("ip.blacklist.del",e,function(){t(),this.tip("删除成功"),this.loadTable()})})},onDialogSave:function(){var a=this;this.$refs.dialogForm.validate(function(t){if(t){var e=a.dialogFormData.id?"ip.blacklist.update":"ip.blacklist.add";a.post(e,a.dialogFormData,function(){this.dialogVisible=!1,this.loadTable()})}})},onSizeChange:function(a){this.searchFormData.pageSize=a,this.loadTable()},onAdd:function(){this.dialogTitle="新增IP",this.dialogVisible=!0,this.dialogFormData.id=0},onPageIndexChange:function(a){this.searchFormData.pageIndex=a,this.loadTable()}}},r=l,n=e("2877"),s=Object(n["a"])(r,i,o,!1,null,null,null);t["default"]=s.exports}}]);
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d221c34"],{cc75:function(a,t,e){"use strict";e.r(t);var i=function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",{staticClass:"app-container"},[e("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:a.searchFormData,size:"mini"}},[e("el-form-item",{attrs:{label:"IP"}},[e("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"输入IP"},model:{value:a.searchFormData.ip,callback:function(t){a.$set(a.searchFormData,"ip",t)},expression:"searchFormData.ip"}})],1),a._v(" "),e("el-form-item",[e("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:a.loadTable}},[a._v("查询")])],1)],1),a._v(" "),e("el-button",{staticStyle:{"margin-bottom":"10px"},attrs:{type:"primary",size:"mini",icon:"el-icon-plus"},on:{click:a.onAdd}},[a._v("新增IP")]),a._v(" "),e("el-table",{attrs:{data:a.pageInfo.rows,border:"","highlight-current-row":""}},[e("el-table-column",{attrs:{prop:"ip",label:"IP",width:"200"}}),a._v(" "),e("el-table-column",{attrs:{prop:"remark",label:"备注",width:"300"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtCreate",label:"添加时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtModified",label:"修改时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{label:"操作",width:"150"},scopedSlots:a._u([{key:"default",fn:function(t){return[e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableUpdate(t.row)}}},[a._v("修改")]),a._v(" "),e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableDelete(t.row)}}},[a._v("删除")])]}}])})],1),a._v(" "),e("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":a.searchFormData.pageIndex,"page-size":a.searchFormData.pageSize,"page-sizes":[5,10,20,40],total:a.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":a.onSizeChange,"current-change":a.onPageIndexChange}}),a._v(" "),e("el-dialog",{attrs:{title:a.dialogTitle,visible:a.dialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){a.dialogVisible=t},close:function(t){return a.resetForm("dialogForm")}}},[e("el-form",{ref:"dialogForm",attrs:{rules:a.dialogFormRules,model:a.dialogFormData,"label-width":"120px",size:"mini"}},[e("el-form-item",{attrs:{prop:"ip",label:"IP"}},[e("el-input",{directives:[{name:"show",rawName:"v-show",value:0===a.dialogFormData.id,expression:"dialogFormData.id === 0"}],model:{value:a.dialogFormData.ip,callback:function(t){a.$set(a.dialogFormData,"ip",t)},expression:"dialogFormData.ip"}}),a._v(" "),e("span",{directives:[{name:"show",rawName:"v-show",value:a.dialogFormData.id>0,expression:"dialogFormData.id > 0"}]},[a._v(a._s(a.dialogFormData.ip))])],1),a._v(" "),e("el-form-item",{attrs:{prop:"remark",label:"备注"}},[e("el-input",{model:{value:a.dialogFormData.remark,callback:function(t){a.$set(a.dialogFormData,"remark",t)},expression:"dialogFormData.remark"}})],1)],1),a._v(" "),e("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[e("el-button",{on:{click:function(t){a.dialogVisible=!1}}},[a._v("取 消")]),a._v(" "),e("el-button",{attrs:{type:"primary"},on:{click:a.onDialogSave}},[a._v("保 存")])],1)],1)],1)},o=[],l={data:function(){var a=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,t=function(t,e,i){""===e?i(new Error("请输入IP")):(a.test(e)||i(new Error("IP格式不正确")),i())};return{searchFormData:{ip:"",pageIndex:1,pageSize:10},pageInfo:{rows:[],total:0},dialogVisible:!1,dialogTitle:"",dialogFormData:{id:0,ip:"",remark:""},dialogFormRules:{ip:[{validator:t,trigger:"blur"},{min:1,max:64,message:"长度在 1 到 64 个字符",trigger:"blur"}],remark:[{max:100,message:"不能超过 100 个字符",trigger:"blur"}]}}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("ip.blacklist.page",this.searchFormData,function(a){this.pageInfo=a.data})},onTableUpdate:function(a){var t=this;this.dialogTitle="修改IP",this.dialogVisible=!0,this.$nextTick(function(){Object.assign(t.dialogFormData,a)})},onTableDelete:function(a){this.confirm("确认要移除IP【".concat(a.ip,"】吗?"),function(t){var e={id:a.id};this.post("ip.blacklist.del",e,function(){t(),this.tip("删除成功"),this.loadTable()})})},onDialogSave:function(){var a=this;this.$refs.dialogForm.validate(function(t){if(t){var e=a.dialogFormData.id?"ip.blacklist.update":"ip.blacklist.add";a.post(e,a.dialogFormData,function(){this.dialogVisible=!1,this.loadTable()})}})},onSizeChange:function(a){this.searchFormData.pageSize=a,this.loadTable()},onAdd:function(){this.dialogTitle="新增IP",this.dialogVisible=!0,this.dialogFormData.id=0},onPageIndexChange:function(a){this.searchFormData.pageIndex=a,this.loadTable()}}},r=l,n=e("2877"),s=Object(n["a"])(r,i,o,!1,null,null,null);t["default"]=s.exports}}]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -88,11 +88,11 @@
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const regexIP = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
const ipValidator = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请输入IP'))
|
||||
} else {
|
||||
const regexIP = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
if (!regexIP.test(value)) {
|
||||
callback(new Error('IP格式不正确'))
|
||||
}
|
||||
|
@@ -28,16 +28,36 @@
|
||||
label="IP端口"
|
||||
width="250"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="metadata"
|
||||
label="当前环境"
|
||||
width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'pre'" type="warning">预发布</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'gray'" type="info">灰度</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="success">线上</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="metadata"
|
||||
label="metadata"
|
||||
width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.parentId > 0">{{ JSON.stringify(scope.row.metadata) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="服务状态"
|
||||
width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="success">已上线</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="success">正常</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'STARTING'" type="info">正在启动</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UNKNOWN'">未知</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">已下线</el-tag>
|
||||
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">已禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -47,24 +67,152 @@
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="100"
|
||||
width="250"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="text" size="mini" @click="onOffline(scope.row)">下线</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'OUT_OF_SERVICE'" type="text" size="mini" @click="onOnline(scope.row)">上线</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'pre'" type="text" size="mini" @click="onEnvPreClose(scope.row)">结束预发布</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'gray'" type="text" size="mini" @click="onEnvGrayClose(scope.row)">结束灰度</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="text" size="mini" @click="onEnvPreOpen(scope.row)">开启预发布</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="text" size="mini" @click="onEnvGrayOpen(scope.row)">开启灰度</el-button>
|
||||
<el-button v-if="scope.row.parentId === 0" type="text" size="mini" @click="onGrayConfigUpdate(scope.row)">设置灰度参数</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="text" size="mini" @click="onDisable(scope.row)">禁用</el-button>
|
||||
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'OUT_OF_SERVICE'" type="text" size="mini" @click="onEnable(scope.row)">启用</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- dialog -->
|
||||
<el-dialog
|
||||
title="灰度设置"
|
||||
:visible.sync="grayDialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
@close="resetForm('grayForm')"
|
||||
>
|
||||
<el-form
|
||||
ref="grayForm"
|
||||
:model="grayForm"
|
||||
:rules="grayFormRules"
|
||||
size="mini"
|
||||
>
|
||||
<el-form-item label="serviceId">
|
||||
{{ grayForm.serviceId }}
|
||||
</el-form-item>
|
||||
<el-tabs v-model="tabsActiveName" type="card">
|
||||
<el-tab-pane label="灰度用户" name="first">
|
||||
<el-alert
|
||||
title="可以是appId,或userId,多个用英文逗号隔开"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px;"
|
||||
/>
|
||||
<el-form-item prop="userKeyContent">
|
||||
<el-input
|
||||
v-model="grayForm.userKeyContent"
|
||||
placeholder="可以是appId,或userId,多个用英文逗号隔开"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="接口配置" name="second">
|
||||
<el-form-item>
|
||||
<el-button type="text" @click="addNameVersion">新增灰度接口</el-button>
|
||||
</el-form-item>
|
||||
<table cellpadding="0" cellspacing="0">
|
||||
<tr
|
||||
v-for="(grayRouteConfig, index) in grayForm.grayRouteConfigList"
|
||||
:key="grayRouteConfig.key"
|
||||
>
|
||||
<td>
|
||||
<el-form-item
|
||||
:key="grayRouteConfig.key"
|
||||
:prop="'grayRouteConfigList.' + index + '.oldRouteId'"
|
||||
:rules="{required: true, message: '不能为空', trigger: ['blur', 'change']}"
|
||||
>
|
||||
老接口:
|
||||
<el-select
|
||||
v-model="grayRouteConfig.oldRouteId"
|
||||
style="margin-right: 10px;"
|
||||
@change="onChangeOldRoute(grayRouteConfig)"
|
||||
>
|
||||
<el-option
|
||||
v-for="route in routeList"
|
||||
:key="route.id"
|
||||
:label="route.name + '(' + route.version + ')'"
|
||||
:value="route.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</td>
|
||||
<td>
|
||||
<el-form-item
|
||||
:key="grayRouteConfig.key + 1"
|
||||
:prop="'grayRouteConfigList.' + index + '.newVersion'"
|
||||
:rules="{required: true, message: '不能为空', trigger: ['blur', 'change']}"
|
||||
>
|
||||
灰度接口:
|
||||
<el-select
|
||||
v-model="grayRouteConfig.newVersion"
|
||||
no-data-text="无数据"
|
||||
>
|
||||
<el-option
|
||||
v-for="routeNew in getGraySelectData(grayRouteConfig.oldRouteId)"
|
||||
:key="routeNew.id"
|
||||
:label="routeNew.name + '(' + routeNew.version + ')'"
|
||||
:value="routeNew.version"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</td>
|
||||
<td style="vertical-align: baseline;">
|
||||
<el-button v-show="grayForm.grayRouteConfigList.length > 1" type="text" @click.prevent="removeNameVersion(grayRouteConfig)">删除</el-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="grayDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onGrayConfigSave">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const regex = /^\w+(,\w+)*$/
|
||||
const userKeyContentValidator = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('不能为空'))
|
||||
} else {
|
||||
if (!regex.test(value)) {
|
||||
callback(new Error('格式不正确'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
searchFormData: {
|
||||
serviceId: ''
|
||||
},
|
||||
grayDialogVisible: false,
|
||||
grayForm: {
|
||||
serviceId: '',
|
||||
userKeyContent: '',
|
||||
onlyUpdateGrayUserkey: false,
|
||||
grayRouteConfigList: []
|
||||
},
|
||||
tabsActiveName: 'first',
|
||||
routeList: [],
|
||||
selectNameVersion: [],
|
||||
grayFormRules: {
|
||||
userKeyContent: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
{ validator: userKeyContentValidator, trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
tableData: []
|
||||
}
|
||||
},
|
||||
@@ -77,6 +225,18 @@ export default {
|
||||
this.tableData = this.buildTreeData(resp.data)
|
||||
})
|
||||
},
|
||||
loadRouteList: function(serviceId) {
|
||||
if (this.routeList.length === 0) {
|
||||
this.post('route.list/1.2', { serviceId: serviceId.toLowerCase() }, function(resp) {
|
||||
this.routeList = resp.data
|
||||
})
|
||||
}
|
||||
},
|
||||
getGraySelectData: function(oldRouteId) {
|
||||
return this.routeList.filter(routeNew => {
|
||||
return oldRouteId !== routeNew.id && oldRouteId.indexOf(routeNew.name) > -1
|
||||
})
|
||||
},
|
||||
buildTreeData: function(data) {
|
||||
data.forEach(ele => {
|
||||
const parentId = ele.parentId
|
||||
@@ -103,22 +263,126 @@ export default {
|
||||
onSearchTable: function() {
|
||||
this.loadTable()
|
||||
},
|
||||
onOffline: function(row) {
|
||||
this.confirm('确定要下线【' + row.serviceId + '】吗?', function(done) {
|
||||
onDisable: function(row) {
|
||||
this.confirm('确定要禁用【' + row.serviceId + '】吗?', function(done) {
|
||||
this.post('service.instance.offline', row, function() {
|
||||
this.tip('下线成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
onOnline: function(row) {
|
||||
this.confirm('确定要上线【' + row.serviceId + '】吗?', function(done) {
|
||||
onEnable: function(row) {
|
||||
this.confirm('确定要启用【' + row.serviceId + '】吗?', function(done) {
|
||||
this.post('service.instance.online', row, function() {
|
||||
this.tip('上线成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
doEnvOnline: function(row, callback) {
|
||||
this.post('service.instance.env.online', row, function() {
|
||||
callback && callback.call(this)
|
||||
})
|
||||
},
|
||||
onEnvPreOpen: function(row) {
|
||||
this.confirm(`确定要开启 ${row.instanceId} 预发布吗?`, function(done) {
|
||||
this.post('service.instance.env.pre.open', row, function() {
|
||||
this.tip('预发布成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
onEnvPreClose: function(row) {
|
||||
this.confirm(`确定要结束 ${row.instanceId} 预发布吗?`, function(done) {
|
||||
this.doEnvOnline(row, function() {
|
||||
this.tip('操作成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
onEnvGrayOpen: function(row) {
|
||||
this.confirm(`确定要开启 ${row.instanceId} 灰度吗?`, function(done) {
|
||||
this.post('service.instance.env.gray.open', row, function() {
|
||||
this.tip('开启成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
onEnvGrayClose: function(row) {
|
||||
this.confirm(`确定要结束 ${row.instanceId} 灰度吗?`, function(done) {
|
||||
this.doEnvOnline(row, function() {
|
||||
this.tip('操作成功')
|
||||
done()
|
||||
})
|
||||
})
|
||||
},
|
||||
onGrayConfigUpdate: function(row) {
|
||||
const serviceId = row.serviceId
|
||||
this.loadRouteList(serviceId)
|
||||
this.post('service.gray.config.get', { serviceId: serviceId }, function(resp) {
|
||||
this.grayDialogVisible = true
|
||||
const data = resp.data
|
||||
Object.assign(this.grayForm, {
|
||||
serviceId: serviceId,
|
||||
userKeyContent: data.userKeyContent || '',
|
||||
grayRouteConfigList: this.createGrayRouteConfigList(data.nameVersionContent)
|
||||
})
|
||||
})
|
||||
},
|
||||
onGrayConfigSave: function() {
|
||||
this.$refs.grayForm.validate((valid) => {
|
||||
if (valid) {
|
||||
const nameVersionContents = []
|
||||
const grayRouteConfigList = this.grayForm.grayRouteConfigList
|
||||
for (let i = 0; i < grayRouteConfigList.length; i++) {
|
||||
const config = grayRouteConfigList[i]
|
||||
nameVersionContents.push(config.oldRouteId + '=' + config.newVersion)
|
||||
}
|
||||
this.grayForm.nameVersionContent = nameVersionContents.join(',')
|
||||
this.post('service.gray.config.save', this.grayForm, function() {
|
||||
this.grayDialogVisible = false
|
||||
this.tip('保存成功')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
createGrayRouteConfigList: function(nameVersionContent) {
|
||||
if (!nameVersionContent) {
|
||||
return [{
|
||||
oldRouteId: '',
|
||||
newVersion: '',
|
||||
key: Date.now()
|
||||
}]
|
||||
}
|
||||
const list = []
|
||||
const arr = nameVersionContent.split(',')
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const el = arr[i]
|
||||
const elArr = el.split('=')
|
||||
list.push({
|
||||
oldRouteId: elArr[0],
|
||||
newVersion: elArr[1],
|
||||
key: Date.now()
|
||||
})
|
||||
}
|
||||
return list
|
||||
},
|
||||
onChangeOldRoute: function(config) {
|
||||
config.newVersion = ''
|
||||
},
|
||||
addNameVersion: function() {
|
||||
this.grayForm.grayRouteConfigList.push({
|
||||
oldRouteId: '',
|
||||
newVersion: '',
|
||||
key: Date.now()
|
||||
})
|
||||
},
|
||||
removeNameVersion: function(item) {
|
||||
const index = this.grayForm.grayRouteConfigList.indexOf(item)
|
||||
if (index !== -1) {
|
||||
this.grayForm.grayRouteConfigList.splice(index, 1)
|
||||
}
|
||||
},
|
||||
renderServiceName: function(row) {
|
||||
let instanceCount = ''
|
||||
if (row.children && row.children.length > 0) {
|
||||
|
@@ -10,11 +10,13 @@ import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
|
||||
@@ -97,11 +99,6 @@ public class ApiConfig {
|
||||
*/
|
||||
private SessionManager sessionManager = new ApiSessionManager();
|
||||
|
||||
/**
|
||||
* zuul网关全局异常处理
|
||||
*/
|
||||
private ZuulErrorController zuulErrorController = new ZuulErrorController();
|
||||
|
||||
/**
|
||||
* isv路由权限
|
||||
*/
|
||||
@@ -127,6 +124,11 @@ public class ApiConfig {
|
||||
*/
|
||||
private LimitManager limitManager = new DefaultLimitManager();
|
||||
|
||||
/**
|
||||
* 用户key管理
|
||||
*/
|
||||
private EnvGrayManager userKeyManager = new DefaultEnvGrayManager();
|
||||
|
||||
/**
|
||||
* 构建数据节点名称
|
||||
*/
|
||||
@@ -142,6 +144,11 @@ public class ApiConfig {
|
||||
*/
|
||||
private ServiceErrorManager serviceErrorManager = new DefaultServiceErrorManager();
|
||||
|
||||
/**
|
||||
* zuul网关全局异常处理
|
||||
*/
|
||||
private ZuulErrorController zuulErrorController = new ZuulErrorController();
|
||||
|
||||
// -------- fields ---------
|
||||
|
||||
/**
|
||||
|
@@ -12,4 +12,8 @@ import java.util.List;
|
||||
public class BaseServiceRouteInfo<T extends BaseRouteDefinition> {
|
||||
private String serviceId;
|
||||
private List<T> routeDefinitionList = Collections.emptyList();
|
||||
|
||||
public String fetchServiceIdLowerCase() {
|
||||
return this.serviceId.toLowerCase();
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ServiceGrayDefinition {
|
||||
private String serviceId;
|
||||
private String instanceId;
|
||||
private String data;
|
||||
}
|
@@ -54,4 +54,5 @@ public class SopConstants {
|
||||
public static final String UNKNOWN_SERVICE= "_sop_unknown_service_";
|
||||
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
|
||||
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,27 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class SpringContext {
|
||||
|
||||
private static ApplicationContext ctx;
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return ctx.getBean(clazz);
|
||||
}
|
||||
|
||||
public static Object getBean(String beanName) {
|
||||
return ctx.getBean(beanName);
|
||||
}
|
||||
|
||||
public static void setApplicationContext(ApplicationContext ctx) {
|
||||
SpringContext.ctx = ctx;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return ctx;
|
||||
}
|
||||
}
|
@@ -1,12 +1,12 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||
import com.gitee.sop.gatewaycommon.gateway.GatewayContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -19,14 +19,18 @@ import reactor.core.publisher.Mono;
|
||||
@Slf4j
|
||||
public class ValidateFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ParamBuilder<ServerWebExchange> paramBuilder;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
// 解析参数
|
||||
ApiParam param = apiConfig.getGatewayParamBuilder().build(exchange);
|
||||
ApiParam param = paramBuilder.build(exchange);
|
||||
GatewayContext.setApiParam(exchange, param);
|
||||
// 验证操作,这里有负责验证签名参数
|
||||
Validator validator = apiConfig.getValidator();
|
||||
try {
|
||||
validator.validate(param);
|
||||
} catch (ApiException e) {
|
||||
|
@@ -69,7 +69,7 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
|
||||
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
|
||||
}
|
||||
|
||||
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
|
||||
|
@@ -84,7 +84,7 @@ public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory
|
||||
return Mono.just(test);
|
||||
} catch (ClassCastException e) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object",
|
||||
LOGGER.debug("Predicate test failed because class in predicate does not canVisit the cached body object",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,15 @@ package com.gitee.sop.gatewaycommon.manager;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -35,30 +40,59 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
Validator validator() {
|
||||
return ApiConfig.getInstance().getValidator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
IsvManager isvManager() {
|
||||
return ApiConfig.getInstance().getIsvManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
IsvRoutePermissionManager isvRoutePermissionManager() {
|
||||
return ApiConfig.getInstance().getIsvRoutePermissionManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RouteConfigManager routeConfigManager() {
|
||||
return ApiConfig.getInstance().getRouteConfigManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
LimitConfigManager limitConfigManager() {
|
||||
return ApiConfig.getInstance().getLimitConfigManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
LimitManager limitManager() {
|
||||
return ApiConfig.getInstance().getLimitManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
IPBlacklistManager ipBlacklistManager() {
|
||||
return ApiConfig.getInstance().getIpBlacklistManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
EnvGrayManager userKeyManager() {
|
||||
return ApiConfig.getInstance().getUserKeyManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SessionManager sessionManager() {
|
||||
return ApiConfig.getInstance().getSessionManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
*
|
||||
@@ -89,6 +123,7 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
|
||||
@PostConstruct
|
||||
public final void after() {
|
||||
SpringContext.setApplicationContext(applicationContext);
|
||||
if (RouteRepositoryContext.getRouteRepository() == null) {
|
||||
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
||||
}
|
||||
|
@@ -0,0 +1,73 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class DefaultEnvGrayManager implements EnvGrayManager {
|
||||
|
||||
/**
|
||||
* key:serviceId,服务对应的灰度配置
|
||||
*/
|
||||
private Map<String, ServiceGrayConfig> serviceGrayConfigMap = Maps.newConcurrentMap();
|
||||
|
||||
/**
|
||||
* key:instanceId value:serviceId
|
||||
*/
|
||||
private Map<String, String> instanceIdServiceIdMap = Maps.newConcurrentMap();
|
||||
|
||||
@Override
|
||||
public void saveServiceGrayConfig(ServiceGrayConfig serviceGrayConfig) {
|
||||
serviceGrayConfigMap.putIfAbsent(serviceGrayConfig.getServiceId(), serviceGrayConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(String instanceId, Object userKey) {
|
||||
if (instanceId == null || userKey == null) {
|
||||
return false;
|
||||
}
|
||||
String serviceId = instanceIdServiceIdMap.get(instanceId);
|
||||
ServiceGrayConfig grayConfig = this.getGrayConfig(serviceId);
|
||||
return grayConfig != null && grayConfig.containsKey(userKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion(String serviceId, String nameVersion) {
|
||||
if (serviceId == null || nameVersion == null) {
|
||||
return null;
|
||||
}
|
||||
boolean opened = instanceIdServiceIdMap.values().contains(serviceId);
|
||||
// 没有开启灰度
|
||||
if (!opened) {
|
||||
return null;
|
||||
}
|
||||
ServiceGrayConfig grayConfig = this.getGrayConfig(serviceId);
|
||||
return grayConfig != null ? grayConfig.getVersion(nameVersion) : null;
|
||||
}
|
||||
|
||||
private ServiceGrayConfig getGrayConfig(String serviceId) {
|
||||
if (serviceId == null) {
|
||||
return null;
|
||||
}
|
||||
return serviceGrayConfigMap.get(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openGray(String instanceId, String serviceId) {
|
||||
instanceIdServiceIdMap.putIfAbsent(instanceId, serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeGray(String instanceId) {
|
||||
instanceIdServiceIdMap.remove(instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface EnvGrayManager extends BeanInitializer {
|
||||
|
||||
/**
|
||||
* 保存灰度配置
|
||||
* @param serviceGrayConfig 灰度配置
|
||||
*/
|
||||
void saveServiceGrayConfig(ServiceGrayConfig serviceGrayConfig);
|
||||
|
||||
/**
|
||||
* 实例是否允许
|
||||
* @param instanceId 实例id
|
||||
* @param userKey 用户key,如appKey
|
||||
* @return true:允许访问
|
||||
*/
|
||||
boolean containsKey(String instanceId, Object userKey);
|
||||
|
||||
/**
|
||||
* 获取灰度发布新版本号
|
||||
* @param serviceId serviceId
|
||||
* @param nameVersion 路由id
|
||||
* @return 返回新版本号
|
||||
*/
|
||||
String getVersion(String serviceId, String nameVersion);
|
||||
|
||||
/**
|
||||
* 开启灰度
|
||||
* @param instanceId instanceId
|
||||
* @param serviceId serviceId
|
||||
*/
|
||||
void openGray(String instanceId, String serviceId);
|
||||
|
||||
/**
|
||||
* 关闭灰度
|
||||
* @param instanceId instanceId
|
||||
*/
|
||||
void closeGray(String instanceId);
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ManagerContext {
|
||||
|
||||
private static ApplicationContext ctx;
|
||||
|
||||
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
ctx = applicationContext;
|
||||
}
|
||||
|
||||
public static <T> T getManager(Class<T> clazz) {
|
||||
return ctx.getBean(clazz);
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return ctx;
|
||||
}
|
||||
}
|
@@ -69,6 +69,10 @@ public class ZookeeperContext {
|
||||
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
|
||||
}
|
||||
|
||||
public static String getServiceGrayChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/gray";
|
||||
}
|
||||
|
||||
public static String getIsvRoutePermissionChannelPath() {
|
||||
return SOP_MSG_CHANNEL_PATH + "/isv-route-permission";
|
||||
}
|
||||
|
@@ -9,9 +9,7 @@ public enum ErrorEnum {
|
||||
SUCCESS(Codes.CODE_SUCCESS, ""),
|
||||
|
||||
/** 服务暂不可用 */
|
||||
ISP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "isp.unknow-error"),
|
||||
/** 服务暂不可用 */
|
||||
AOP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "aop.unknow-error"),
|
||||
ISP_UNKNOWN_ERROR(Codes.CODE_UNKNOW, "isp.unknown-error"),
|
||||
/** 服务不可用,路由被禁用 */
|
||||
ISP_API_DISABLED(Codes.CODE_UNKNOW, "isp.service-not-available"),
|
||||
|
||||
|
@@ -76,7 +76,7 @@ public class ErrorFactory {
|
||||
// open.error_20000=Service is temporarily unavailable
|
||||
String msg = getErrorMessage(modulePrefix + code, locale);
|
||||
String subCode = errorMeta.getSubCode();
|
||||
// open.error_20000_isp.unknow-error=Service is temporarily unavailable
|
||||
// open.error_20000_isp.unknown-error=Service is temporarily unavailable
|
||||
String subMsg = getErrorMessage(modulePrefix + code + UNDERLINE + subCode, locale, params);
|
||||
if (StringUtils.isEmpty(msg)) {
|
||||
msg = SYS_ERR;
|
||||
@@ -85,7 +85,7 @@ public class ErrorFactory {
|
||||
subMsg = SYS_ERR;
|
||||
}
|
||||
// solution暂未实现,如果要实现,可以这样配置:
|
||||
// open.error_20000_isp.unknow-error_solution=Service is temporarily unavailable
|
||||
// open.error_20000_isp.unknown-error_solution=Service is temporarily unavailable
|
||||
// <code>String solution = getErrorMessage(modulePrefix + code + UNDERLINE + subCode + "_solution", locale, params);</code>
|
||||
error = new ErrorImpl(code, msg, subCode, subMsg, null);
|
||||
errorCache.put(key, error);
|
||||
|
@@ -22,7 +22,7 @@ public abstract class BaseParamBuilder<T> implements ParamBuilder<T> {
|
||||
* @param ctx 请求request
|
||||
* @return 返回请求参数
|
||||
*/
|
||||
public abstract Map<String, String> buildRequestParams(T ctx);
|
||||
public abstract Map<String, ?> buildRequestParams(T ctx);
|
||||
|
||||
/**
|
||||
* 返回客户端ip
|
||||
@@ -42,10 +42,8 @@ public abstract class BaseParamBuilder<T> implements ParamBuilder<T> {
|
||||
@Override
|
||||
public ApiParam build(T ctx) {
|
||||
ApiParam apiParam = this.newApiParam(ctx);
|
||||
Map<String, String> requestParams = this.buildRequestParams(ctx);
|
||||
for (Map.Entry<String, ?> entry : requestParams.entrySet()) {
|
||||
apiParam.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
Map<String, ?> requestParams = this.buildRequestParams(ctx);
|
||||
apiParam.putAll(requestParams);
|
||||
this.initOtherProperty(apiParam);
|
||||
apiParam.setIp(this.getIP(ctx));
|
||||
this.setVersionInHeader(ctx, ParamNames.HEADER_VERSION_NAME, apiParam.fetchVersion());
|
||||
@@ -60,7 +58,7 @@ public abstract class BaseParamBuilder<T> implements ParamBuilder<T> {
|
||||
RouteRepository<? extends TargetRoute> routeRepository = RouteRepositoryContext.getRouteRepository();
|
||||
if (routeRepository == null) {
|
||||
log.error("RouteRepositoryContext.setRouteRepository()方法未使用");
|
||||
throw ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getException();
|
||||
throw ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getException();
|
||||
}
|
||||
|
||||
String nameVersion = Optional.ofNullable(apiParam.fetchNameVersion()).orElse(String.valueOf(System.currentTimeMillis()));
|
||||
|
@@ -35,7 +35,7 @@ import java.util.Optional;
|
||||
@Slf4j
|
||||
public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R> {
|
||||
private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta();
|
||||
private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOW_ERROR.getErrorMeta();
|
||||
private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta();
|
||||
private static final ErrorMeta ISP_BIZ_ERROR = ErrorEnum.BIZ_ERROR.getErrorMeta();
|
||||
private static final ErrorMeta ISV_MISSING_METHOD_META = ErrorEnum.ISV_MISSING_METHOD.getErrorMeta();
|
||||
|
||||
|
@@ -6,7 +6,7 @@ package com.gitee.sop.gatewaycommon.result;
|
||||
* "result": {
|
||||
* "code": "20000",
|
||||
* "msg": "Service Currently Unavailable",
|
||||
* "sub_code": "isp.unknow-error",
|
||||
* "sub_code": "isp.unknown-error",
|
||||
* "sub_msg": "系统繁忙"
|
||||
* },
|
||||
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
|
||||
|
@@ -8,7 +8,7 @@ package com.gitee.sop.gatewaycommon.result;
|
||||
* "alipay_trade_order_settle_response": {
|
||||
* "code": "20000",
|
||||
* "msg": "Service Currently Unavailable",
|
||||
* "sub_code": "isp.unknow-error",
|
||||
* "sub_code": "isp.unknown-error",
|
||||
* "sub_msg": "系统繁忙"
|
||||
* },
|
||||
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
|
||||
|
@@ -22,7 +22,7 @@ package com.gitee.sop.gatewaycommon.result;
|
||||
* "alipay_trade_fastpay_refund_query_response": {
|
||||
* "code": "20000",
|
||||
* "msg": "Service Currently Unavailable",
|
||||
* "sub_code": "isp.unknow-error",
|
||||
* "sub_code": "isp.unknown-error",
|
||||
* "sub_msg": "系统繁忙"
|
||||
* },
|
||||
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
|
||||
|
@@ -36,7 +36,7 @@ public class ApiSessionManager implements SessionManager {
|
||||
return cache.get(sessionId);
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
throw ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getException();
|
||||
throw ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
|
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
@@ -124,15 +123,10 @@ public class RequestUtil {
|
||||
* @param request 请求类型为application/json的Request
|
||||
* @return 返回Map
|
||||
*/
|
||||
public static Map<String, String> convertJsonRequestToMap(HttpServletRequest request) {
|
||||
public static Map<String, Object> convertJsonRequestToMap(HttpServletRequest request) {
|
||||
try {
|
||||
String text = getText(request);
|
||||
JSONObject parseObject = JSON.parseObject(text);
|
||||
Map<String, String> params = new HashMap<>(parseObject.size());
|
||||
for (Map.Entry<String, Object> entry : parseObject.entrySet()) {
|
||||
params.put(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
return params;
|
||||
return JSON.parseObject(text);
|
||||
} catch (IOException e) {
|
||||
log.error("解析json请求失败", e);
|
||||
return Collections.emptyMap();
|
||||
|
@@ -15,11 +15,11 @@ import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.param.UploadContext;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -35,15 +35,26 @@ import java.util.List;
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
public class ApiValidator implements Validator {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiValidator.class);
|
||||
|
||||
private static final int MILLISECOND_OF_ONE_SECOND = 1000;
|
||||
public static final int STATUS_FORBIDDEN = 2;
|
||||
|
||||
private static final int STATUS_FORBIDDEN = 2;
|
||||
|
||||
private static List<String> FORMAT_LIST = Arrays.asList("json", "xml");
|
||||
|
||||
@Autowired
|
||||
private IsvManager isvManager;
|
||||
|
||||
@Autowired
|
||||
private IsvRoutePermissionManager isvRoutePermissionManager;
|
||||
|
||||
@Autowired
|
||||
private IPBlacklistManager ipBlacklistManager;
|
||||
|
||||
@Autowired
|
||||
private RouteConfigManager routeConfigManager;
|
||||
|
||||
@Override
|
||||
public void validate(ApiParam param) {
|
||||
@@ -52,8 +63,8 @@ public class ApiValidator implements Validator {
|
||||
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +82,6 @@ public class ApiValidator implements Validator {
|
||||
* @param param 接口参数
|
||||
*/
|
||||
protected void checkIP(ApiParam param) {
|
||||
IPBlacklistManager ipBlacklistManager = ApiConfig.getInstance().getIpBlacklistManager();
|
||||
String ip = param.fetchIp();
|
||||
if (ipBlacklistManager.contains(ip)) {
|
||||
throw ErrorEnum.ISV_IP_FORBIDDEN.getErrorMeta().getException();
|
||||
@@ -87,7 +97,6 @@ public class ApiValidator implements Validator {
|
||||
// 检查路由是否存在
|
||||
RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
|
||||
// 检查路由是否启用
|
||||
RouteConfigManager routeConfigManager = ApiConfig.getInstance().getRouteConfigManager();
|
||||
RouteConfig routeConfig = routeConfigManager.get(routeId);
|
||||
if (!routeConfig.enable()) {
|
||||
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
|
||||
@@ -115,7 +124,7 @@ public class ApiValidator implements Validator {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("验证上传文件MD5错误", e);
|
||||
log.error("验证上传文件MD5错误", e);
|
||||
throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
|
||||
}
|
||||
}
|
||||
@@ -147,8 +156,6 @@ public class ApiValidator implements Validator {
|
||||
if (StringUtils.isEmpty(param.fetchAppKey())) {
|
||||
throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
|
||||
}
|
||||
IsvManager isvManager = ApiContext.getApiConfig().getIsvManager();
|
||||
Assert.notNull(isvManager, "isvManager未初始化");
|
||||
Isv isv = isvManager.getIsv(param.fetchAppKey());
|
||||
// 没有用户
|
||||
if (isv == null) {
|
||||
@@ -167,7 +174,6 @@ public class ApiValidator implements Validator {
|
||||
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion(), ParamNames.SIGN_NAME);
|
||||
}
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
IsvManager isvManager = apiConfig.getIsvManager();
|
||||
// 根据appId获取秘钥
|
||||
Isv isvInfo = isvManager.getIsv(param.fetchAppKey());
|
||||
String secret = isvInfo.getSecretInfo();
|
||||
@@ -205,7 +211,6 @@ public class ApiValidator implements Validator {
|
||||
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
|
||||
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
|
||||
if (needCheckPermission) {
|
||||
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
|
||||
if (!hasPermission) {
|
||||
|
@@ -69,20 +69,6 @@ public class ZuulContext extends ApiContext {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回自定义的session,被SessionManager管理
|
||||
*
|
||||
* @return 如果sessionId为null,则返回null
|
||||
*/
|
||||
public static HttpSession getManagedSession() {
|
||||
String sessionId = getSessionId();
|
||||
if (sessionId != null) {
|
||||
return getSessionManager().getSession(sessionId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登陆的token
|
||||
*
|
||||
|
@@ -1,19 +1,24 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.configuration;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreVersionDecisionFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulZookeeperRouteManager;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
@@ -33,6 +38,12 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
@Autowired
|
||||
protected ServerProperties server;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ParamBuilder<RequestContext> paramBuilder() {
|
||||
return ApiConfig.getInstance().getZuulParamBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由存储
|
||||
* @return
|
||||
@@ -101,6 +112,14 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
return new PreLimitFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定版本号
|
||||
*/
|
||||
@Bean
|
||||
PreVersionDecisionFilter preVersionDecisionFilter() {
|
||||
return new PreVersionDecisionFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理扩展
|
||||
*/
|
||||
@@ -117,10 +136,12 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
return new PostResultFilter();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 统一错误处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ZuulErrorController baseZuulController() {
|
||||
return ApiContext.getApiConfig().getZuulErrorController();
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
@@ -26,6 +27,12 @@ import java.util.List;
|
||||
*/
|
||||
public class PreLimitFilter extends BaseZuulFilter {
|
||||
|
||||
@Autowired
|
||||
private LimitManager limitManager;
|
||||
|
||||
@Autowired
|
||||
private LimitConfigManager limitConfigManager;
|
||||
|
||||
@Override
|
||||
protected FilterType getFilterType() {
|
||||
return FilterType.PRE;
|
||||
@@ -53,7 +60,6 @@ public class PreLimitFilter extends BaseZuulFilter {
|
||||
return null;
|
||||
}
|
||||
byte limitType = configLimitDto.getLimitType().byteValue();
|
||||
LimitManager limitManager = ApiConfig.getInstance().getLimitManager();
|
||||
// 如果是漏桶策略
|
||||
if (limitType == LimitType.LEAKY_BUCKET.getType()) {
|
||||
boolean acquire = limitManager.acquire(configLimitDto);
|
||||
@@ -67,8 +73,6 @@ public class PreLimitFilter extends BaseZuulFilter {
|
||||
}
|
||||
|
||||
protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, HttpServletRequest request) {
|
||||
LimitConfigManager limitConfigManager = apiConfig.getLimitConfigManager();
|
||||
|
||||
String routeId = apiParam.fetchNameVersion();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
String ip = RequestUtil.getIP(request);
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||
@@ -11,6 +10,7 @@ import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 路由权限校验,有些接口需要配置权限才能访问。
|
||||
@@ -19,6 +19,10 @@ import org.apache.commons.lang3.BooleanUtils;
|
||||
*/
|
||||
@Deprecated
|
||||
public class PreRoutePermissionFilter extends BaseZuulFilter {
|
||||
|
||||
@Autowired
|
||||
private IsvRoutePermissionManager isvRoutePermissionManager;
|
||||
|
||||
@Override
|
||||
protected FilterType getFilterType() {
|
||||
return FilterType.PRE;
|
||||
@@ -38,7 +42,6 @@ public class PreRoutePermissionFilter extends BaseZuulFilter {
|
||||
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
|
||||
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
|
||||
if (needCheckPermission) {
|
||||
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
|
||||
if (!hasPermission) {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* 前置校验
|
||||
@@ -15,6 +15,13 @@ import com.netflix.zuul.exception.ZuulException;
|
||||
* @author tanghc
|
||||
*/
|
||||
public class PreValidateFilter extends BaseZuulFilter {
|
||||
|
||||
@Autowired
|
||||
private ParamBuilder<RequestContext> paramBuilder;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Override
|
||||
protected FilterType getFilterType() {
|
||||
return FilterType.PRE;
|
||||
@@ -27,12 +34,10 @@ public class PreValidateFilter extends BaseZuulFilter {
|
||||
|
||||
@Override
|
||||
protected Object doRun(RequestContext requestContext) throws ZuulException {
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
// 解析参数
|
||||
ApiParam param = apiConfig.getZuulParamBuilder().build(requestContext);
|
||||
ApiParam param = paramBuilder.build(requestContext);
|
||||
ZuulContext.setApiParam(param);
|
||||
// 验证操作,这里有负责验证签名参数
|
||||
Validator validator = apiConfig.getValidator();
|
||||
try {
|
||||
validator.validate(param);
|
||||
} catch (ApiException e) {
|
||||
|
@@ -0,0 +1,47 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class PreVersionDecisionFilter extends BaseZuulFilter {
|
||||
|
||||
@Autowired
|
||||
private EnvGrayManager envGrayManager;
|
||||
|
||||
@Override
|
||||
protected FilterType getFilterType() {
|
||||
return FilterType.PRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getFilterOrder() {
|
||||
return PRE_LIMIT_FILTER_ORDER + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doRun(RequestContext requestContext) throws ZuulException {
|
||||
ApiParam apiParam = ZuulContext.getApiParam();
|
||||
String nameVersion = apiParam.fetchNameVersion();
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
||||
if (targetRoute == null) {
|
||||
return null;
|
||||
}
|
||||
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||
String version = envGrayManager.getVersion(serviceId, nameVersion);
|
||||
if (version != null) {
|
||||
requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 服务实例选择器
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseServerChooser extends ZoneAvoidanceRule {
|
||||
|
||||
|
||||
/**
|
||||
* 是否是预发布服务器
|
||||
* @param server 服务器
|
||||
* @return true:是
|
||||
*/
|
||||
protected abstract boolean isPreServer(Server server);
|
||||
|
||||
/**
|
||||
* 是否是灰度发布服务器
|
||||
* @param server 服务器
|
||||
* @return true:是
|
||||
*/
|
||||
protected abstract boolean isGrayServer(Server server);
|
||||
|
||||
/**
|
||||
* 能否访问预发布
|
||||
* @param server 预发布服务器
|
||||
* @param request request
|
||||
* @return true:能
|
||||
*/
|
||||
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
|
||||
|
||||
@Override
|
||||
public Server choose(Object key) {
|
||||
ILoadBalancer lb = getLoadBalancer();
|
||||
// 获取服务实例列表
|
||||
List<Server> allServers = lb.getAllServers();
|
||||
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
|
||||
|
||||
List<Server> preServers = allServers.stream()
|
||||
.filter(this::isPreServer)
|
||||
.filter(server -> canVisitPre(server, request))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!preServers.isEmpty()) {
|
||||
return this.doChoose(preServers, key);
|
||||
}
|
||||
|
||||
List<Server> grayServers = allServers.stream()
|
||||
.filter(this::isGrayServer)
|
||||
// 这句暂时不需要,放到了PreVersionDecisionFilter中判断
|
||||
//.filter(server -> canVisitGray(server, request))
|
||||
.collect(Collectors.toList());
|
||||
if (!grayServers.isEmpty()) {
|
||||
return doChoose(grayServers, key);
|
||||
}
|
||||
|
||||
return super.choose(key);
|
||||
}
|
||||
|
||||
protected Server doChoose(List<Server> servers, Object key) {
|
||||
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
|
||||
if (server.isPresent()) {
|
||||
return server.get();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ServiceGrayConfig {
|
||||
|
||||
private String serviceId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Set<String> userKeys;
|
||||
|
||||
/** 存放接口隐射关系,key:nameversion,value:newVersion */
|
||||
private Map<String, String> grayNameVersion;
|
||||
|
||||
public boolean containsKey(Object userKey) {
|
||||
return userKeys.contains(String.valueOf(userKey));
|
||||
}
|
||||
|
||||
public String getVersion(String name) {
|
||||
return grayNameVersion.get(name);
|
||||
}
|
||||
|
||||
}
|
@@ -7,6 +7,7 @@ import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -20,14 +21,12 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class ZuulParamBuilder extends BaseParamBuilder<RequestContext> {
|
||||
|
||||
private static final String CONTENT_TYPE_JSON = MediaType.APPLICATION_JSON_VALUE;
|
||||
private static final String CONTENT_TYPE_TEXT = MediaType.TEXT_PLAIN_VALUE;
|
||||
private static final String GET = "get";
|
||||
|
||||
@Override
|
||||
public Map<String, String> buildRequestParams(RequestContext ctx) {
|
||||
public Map<String, ?> buildRequestParams(RequestContext ctx) {
|
||||
HttpServletRequest request = ctx.getRequest();
|
||||
Map<String, String> params;
|
||||
Map<String, ?> params;
|
||||
if (GET.equalsIgnoreCase(request.getMethod())) {
|
||||
params = RequestUtil.convertRequestParamsToMap(request);
|
||||
} else {
|
||||
@@ -37,7 +36,7 @@ public class ZuulParamBuilder extends BaseParamBuilder<RequestContext> {
|
||||
}
|
||||
contentType = contentType.toLowerCase();
|
||||
// json或者纯文本形式
|
||||
if (contentType.contains(CONTENT_TYPE_JSON) || contentType.contains(CONTENT_TYPE_TEXT)) {
|
||||
if (StringUtils.containsAny(contentType, MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE)) {
|
||||
params = RequestUtil.convertJsonRequestToMap(request);
|
||||
} else if (ServletFileUpload.isMultipartContent(request)) {
|
||||
params = RequestUtil.convertMultipartRequestToMap(request);
|
||||
|
@@ -76,7 +76,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
|
||||
}
|
||||
}
|
||||
if (error == null) {
|
||||
error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
|
||||
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ public class ZuulZookeeperRouteManager extends BaseRouteManager<ZuulServiceRoute
|
||||
|
||||
@Override
|
||||
protected ZuulTargetRoute buildRouteDefinition(ZuulServiceRouteInfo serviceRouteInfo, ZuulRouteDefinition routeDefinition) {
|
||||
Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), null, RETRYABLE, null);
|
||||
Route route = new Route(routeDefinition.getId(), routeDefinition.getPath(), RouteUtil.getZuulLocation(routeDefinition.getUri()), "", RETRYABLE, null);
|
||||
return new ZuulTargetRoute(serviceRouteInfo, routeDefinition, route);
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,45 @@ import org.springframework.util.CollectionUtils;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {
|
||||
* "instance": {
|
||||
* "instanceId": "demo-order2:11101",
|
||||
* "app": "demo-order2",
|
||||
* "appGroutName": null,
|
||||
* "ipAddr": "127.0.0.1",
|
||||
* "sid": "na",
|
||||
* "homePageUrl": null,
|
||||
* "statusPageUrl": null,
|
||||
* "healthCheckUrl": null,
|
||||
* "secureHealthCheckUrl": null,
|
||||
* "vipAddress": "demo-order2",
|
||||
* "secureVipAddress": "demo-order2",
|
||||
* "countryId": 1,
|
||||
* "dataCenterInfo": {
|
||||
* "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
|
||||
* "name": "MyOwn"
|
||||
* },
|
||||
* "hostName": "127.0.0.1",
|
||||
* "status": "UP",
|
||||
* "leaseInfo": null,
|
||||
* "isCoordinatingDiscoveryServer": false,
|
||||
* "lastUpdatedTimestamp": 1529391461000,
|
||||
* "lastDirtyTimestamp": 1529391461000,
|
||||
* "actionType": null,
|
||||
* "asgName": null,
|
||||
* "overridden_status": "UNKNOWN",
|
||||
* "port": {
|
||||
* "$": 11102,
|
||||
* "@enabled": "false"
|
||||
* },
|
||||
* "securePort": {
|
||||
* "$": 7002,
|
||||
* "@enabled": "false"
|
||||
* },
|
||||
* "metadata": {
|
||||
* "@class": "java.util.Collections$EmptyMap"
|
||||
* }* }
|
||||
* }
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
@@ -19,6 +58,8 @@ public class EurekaInstance {
|
||||
private String healthCheckUrl;
|
||||
private String lastUpdatedTimestamp;
|
||||
|
||||
private Map<String, String> metadata;
|
||||
|
||||
public String fetchPort() {
|
||||
if (CollectionUtils.isEmpty(port)) {
|
||||
return "";
|
||||
|
@@ -16,6 +16,10 @@ public enum EurekaUri {
|
||||
* 查询所有实例 Query for all instances
|
||||
*/
|
||||
QUERY_APPS("GET", "/apps"),
|
||||
/**
|
||||
* 查询一个实例
|
||||
*/
|
||||
QUERY_INSTANCES("GET", "/instances/%s"),
|
||||
/**
|
||||
* 下线 Take instance out of service
|
||||
*/
|
||||
@@ -24,6 +28,12 @@ public enum EurekaUri {
|
||||
* 上线 Move instance back into service (remove override)
|
||||
*/
|
||||
ONLINE_SERVICE("DELETE", "/apps/%s/%s/status?value=UP"),
|
||||
/**
|
||||
* 设置metadata信息
|
||||
*
|
||||
* /apps/{appID}/{instanceID}/metadata?key=value
|
||||
*/
|
||||
SET_METADATA("PUT", "/apps/%s/%s/metadata?%s=%s")
|
||||
;
|
||||
public static final String URL_PREFIX = "/";
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package com.gitee.sop.websiteserver.bean;
|
||||
package com.gitee.sop.registryapi.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
@@ -110,7 +110,7 @@ public class HttpTool {
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public String request(String url, Map<String, String> form, Map<String, String> header, HTTPMethod method) throws IOException {
|
||||
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
|
||||
Request.Builder requestBuilder = buildRequestBuilder(url, form, method);
|
||||
// 添加header
|
||||
addHeader(requestBuilder, header);
|
||||
@@ -153,7 +153,7 @@ public class HttpTool {
|
||||
}
|
||||
}
|
||||
|
||||
public static Request.Builder buildRequestBuilder(String url, Map<String, String> form, HTTPMethod method) {
|
||||
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, HTTPMethod method) {
|
||||
switch (method) {
|
||||
case GET:
|
||||
return new Request.Builder()
|
||||
@@ -178,18 +178,18 @@ public class HttpTool {
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpUrl buildHttpUrl(String url, Map<String, String> form) {
|
||||
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) {
|
||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||
for (Map.Entry<String, String> entry : form.entrySet()) {
|
||||
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
|
||||
for (Map.Entry<String, ?> entry : form.entrySet()) {
|
||||
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
return urlBuilder.build();
|
||||
}
|
||||
|
||||
public static FormBody buildFormBody(Map<String, String> form) {
|
||||
public static FormBody buildFormBody(Map<String, ?> form) {
|
||||
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
|
||||
for (Map.Entry<String, String> entry : form.entrySet()) {
|
||||
paramBuilder.add(entry.getKey(), entry.getValue());
|
||||
for (Map.Entry<String, ?> entry : form.entrySet()) {
|
||||
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
return paramBuilder.build();
|
||||
}
|
@@ -2,6 +2,9 @@ package com.gitee.sop.registryapi.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@@ -37,4 +40,9 @@ public class ServiceInstance {
|
||||
*/
|
||||
private String updateTime;
|
||||
|
||||
/**
|
||||
* user extended attributes
|
||||
*/
|
||||
private Map<String, String> metadata = new HashMap<String, String>();
|
||||
|
||||
}
|
@@ -38,4 +38,13 @@ public interface RegistryService {
|
||||
void offlineInstance(ServiceInstance serviceInstance) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 设置实例元数据
|
||||
*
|
||||
* @param serviceInstance 实例
|
||||
* @param key key
|
||||
* @param value 值
|
||||
* @throws Exception 设置实例元数据失败抛出异常
|
||||
*/
|
||||
void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception;
|
||||
}
|
||||
|
@@ -60,6 +60,7 @@ public class RegistryServiceEureka implements RegistryService {
|
||||
serviceInstance.setStatus(eurekaInstance.getStatus());
|
||||
Date updateTime = new Date(Long.valueOf(eurekaInstance.getLastUpdatedTimestamp()));
|
||||
serviceInstance.setUpdateTime(DateFormatUtils.format(updateTime, TIMESTAMP_PATTERN));
|
||||
serviceInstance.setMetadata(eurekaInstance.getMetadata());
|
||||
serviceInfo.getInstances().add(serviceInstance);
|
||||
}
|
||||
}
|
||||
@@ -80,6 +81,11 @@ public class RegistryServiceEureka implements RegistryService {
|
||||
this.requestEurekaServer(EurekaUri.OFFLINE_SERVICE, serviceInstance.getServiceId(), serviceInstance.getInstanceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception {
|
||||
this.requestEurekaServer(EurekaUri.SET_METADATA, serviceInstance.getServiceId(), serviceInstance.getInstanceId(), key, value);
|
||||
}
|
||||
|
||||
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
|
||||
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
|
||||
Response response = client.newCall(request).execute();
|
||||
|
@@ -1,17 +1,16 @@
|
||||
package com.gitee.sop.registryapi.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingFactory;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacos.api.naming.pojo.ListView;
|
||||
import com.gitee.sop.registryapi.bean.HttpTool;
|
||||
import com.gitee.sop.registryapi.bean.ServiceInfo;
|
||||
import com.gitee.sop.registryapi.bean.ServiceInstance;
|
||||
import com.gitee.sop.registryapi.service.RegistryService;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -24,12 +23,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* nacos接口实现
|
||||
* nacos接口实现, https://nacos.io/zh-cn/docs/open-api.html
|
||||
* @author tanghc
|
||||
*/
|
||||
public class RegistryServiceNacos implements RegistryService {
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
static HttpTool httpTool = new HttpTool();
|
||||
|
||||
@Value("${registry.nacos-server-addr:}")
|
||||
private String nacosAddr;
|
||||
@@ -63,6 +62,7 @@ public class RegistryServiceNacos implements RegistryService {
|
||||
boolean isOnline = instance.getWeight() > 0;
|
||||
String status = isOnline ? "UP" : "OUT_OF_SERVICE";
|
||||
serviceInstance.setStatus(status);
|
||||
serviceInstance.setMetadata(instance.getMetadata());
|
||||
serviceInfo.getInstances().add(serviceInstance);
|
||||
}
|
||||
}
|
||||
@@ -73,34 +73,50 @@ public class RegistryServiceNacos implements RegistryService {
|
||||
|
||||
@Override
|
||||
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
|
||||
Map<String, String> params = new HashMap<>(8);
|
||||
// 查询实例
|
||||
Instance instance = this.getInstance(serviceInstance);
|
||||
// 上线,把权重设置成1
|
||||
params.put("weight", "1");
|
||||
this.updateInstance(serviceInstance, params);
|
||||
instance.setWeight(1);
|
||||
this.updateInstance(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
|
||||
Map<String, String> params = new HashMap<>(8);
|
||||
// 查询实例
|
||||
Instance instance = this.getInstance(serviceInstance);
|
||||
// 下线,把权重设置成0
|
||||
params.put("weight", "0");
|
||||
this.updateInstance(serviceInstance, params);
|
||||
instance.setWeight(0);
|
||||
this.updateInstance(instance);
|
||||
}
|
||||
|
||||
private Response updateInstance(ServiceInstance serviceInstance, Map<String, String> params) throws IOException {
|
||||
FormBody.Builder builder = new FormBody.Builder();
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
builder.add(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
}
|
||||
builder.add("serviceName", serviceInstance.getServiceId())
|
||||
.add("ip", serviceInstance.getIp())
|
||||
.add("port", String.valueOf(serviceInstance.getPort()));
|
||||
FormBody formBody = builder.build();
|
||||
final Request request = new Request.Builder()
|
||||
.url("http://" + nacosAddr + "/nacos/v1/ns/instance")
|
||||
.put(formBody)
|
||||
.build();
|
||||
@Override
|
||||
public void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception {
|
||||
// 查询实例
|
||||
Instance instance = this.getInstance(serviceInstance);
|
||||
// 设置metadata
|
||||
Map<String, String> metadata = instance.getMetadata();
|
||||
metadata.put(key, value);
|
||||
this.updateInstance(instance);
|
||||
}
|
||||
|
||||
return client.newCall(request).execute();
|
||||
protected void updateInstance(Instance instance) throws IOException {
|
||||
String json = JSON.toJSONString(instance);
|
||||
JSONObject jsonObject = JSON.parseObject(json);
|
||||
httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", jsonObject, null, HttpTool.HTTPMethod.PUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询实例
|
||||
* @param serviceInstance 实例信息
|
||||
* @return 返回nacos实例
|
||||
* @throws IOException 查询异常
|
||||
*/
|
||||
protected Instance getInstance(ServiceInstance serviceInstance) throws IOException {
|
||||
Map<String, String> params = new HashMap<>(8);
|
||||
params.put("serviceName", serviceInstance.getServiceId());
|
||||
params.put("ip", serviceInstance.getIp());
|
||||
params.put("port", String.valueOf(serviceInstance.getPort()));
|
||||
String instanceJson = httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", params, null, HttpTool.HTTPMethod.GET);
|
||||
return JSON.parseObject(instanceJson, Instance.class);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ package com.gitee.sop.servercommon.message;
|
||||
*/
|
||||
public enum ServiceErrorEnum {
|
||||
/** 系统繁忙 */
|
||||
ISP_UNKNOW_ERROR("isp.unknow-error"),
|
||||
ISP_UNKNOW_ERROR("isp.unknown-error"),
|
||||
/** 参数错误 */
|
||||
ISV_PARAM_ERROR("isv.invalid-parameter"),
|
||||
/** 通用错误 */
|
||||
|
@@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -19,8 +20,6 @@ import java.util.Set;
|
||||
@Slf4j
|
||||
public class OpenUtil {
|
||||
|
||||
private static final String UTF8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* 获取request中的参数
|
||||
*
|
||||
@@ -36,7 +35,7 @@ public class OpenUtil {
|
||||
JSONObject jsonObject;
|
||||
if (StringUtils.containsAny(contentType, MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE)) {
|
||||
try {
|
||||
String requestJson = IOUtils.toString(request.getInputStream(), UTF8);
|
||||
String requestJson = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
|
||||
jsonObject = JSON.parseObject(requestJson);
|
||||
} catch (Exception e) {
|
||||
jsonObject = new JSONObject();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# 错误配置
|
||||
|
||||
isp.error_isp.unknow-error=Service is temporarily unavailable
|
||||
isp.error_isp.unknown-error=Service is temporarily unavailable
|
||||
isp.error_isv.invalid-parameter=Invalid parameter
|
@@ -1,5 +1,5 @@
|
||||
# 错误配置
|
||||
|
||||
isp.error_isp.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
|
||||
isp.error_isp.unknown-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
|
||||
# 参数无效
|
||||
isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548
|
||||
|
@@ -130,7 +130,7 @@ public class AlipayController {
|
||||
public Story getStory() {
|
||||
Story story = new Story();
|
||||
story.setId(1);
|
||||
story.setName("海底小纵队(alipay.story.get),2222");
|
||||
story.setName("海底小纵队(alipay.story.get1.0)");
|
||||
return story;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,10 @@ public class AlipayController {
|
||||
*/
|
||||
@ApiMapping(value = "alipay.story.get", version = "1.2")
|
||||
public Story getStory11(Story story) {
|
||||
return story;
|
||||
Story story2 = new Story();
|
||||
story2.setId(1);
|
||||
story2.setName("海底小纵队(alipay.story.get1.2)");
|
||||
return story2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package com.gitee.sop.gateway.config;
|
||||
|
||||
import com.gitee.sop.gateway.manager.ManagerInitializer;
|
||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||
|
||||
|
||||
@@ -20,9 +19,6 @@ import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfigurat
|
||||
//@Configuration
|
||||
public class GatewayConfig extends AlipayGatewayConfiguration {
|
||||
|
||||
static {
|
||||
new ManagerInitializer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,8 +6,10 @@ package com.gitee.sop.gateway.config;
|
||||
* 注意:下面两个只能使用一个
|
||||
*/
|
||||
|
||||
import com.gitee.sop.gateway.manager.ManagerInitializer;
|
||||
import com.gitee.sop.gateway.loadbalancer.SopPropertiesFactory;
|
||||
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
@@ -17,8 +19,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class ZuulConfig extends AlipayZuulConfiguration {
|
||||
|
||||
static {
|
||||
new ManagerInitializer();
|
||||
@Bean
|
||||
PropertiesFactory propertiesFactory() {
|
||||
return new SopPropertiesFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray
|
||||
* 备注:服务灰度配置
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_gray")
|
||||
@Data
|
||||
public class ConfigGray {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
||||
private String userKeyContent;
|
||||
|
||||
/** 需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段:name_version_content */
|
||||
private String nameVersionContent;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray_instance
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Table(name = "config_gray_instance")
|
||||
@Data
|
||||
public class ConfigGrayInstance {
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** instance_id, 数据库字段:instance_id */
|
||||
private String instanceId;
|
||||
|
||||
/** service_id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 0:禁用,1:启用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package com.gitee.sop.gateway.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gateway.manager.DbEnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.gitee.sop.gatewaycommon.param.Param;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
public class EnvironmentServerChooser extends BaseServerChooser {
|
||||
|
||||
private static final String MEDATA_KEY_ENV = "env";
|
||||
private static final String ENV_PRE_VALUE = "pre";
|
||||
private static final String ENV_GRAY_VALUE = "gray";
|
||||
|
||||
/**
|
||||
* 预发布机器域名
|
||||
*/
|
||||
private static final String PRE_DOMAIN = "localhost";
|
||||
|
||||
@Override
|
||||
protected boolean isPreServer(Server server) {
|
||||
String env = getEnvValue(server);
|
||||
return ENV_PRE_VALUE.equals(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGrayServer(Server server) {
|
||||
String env = getEnvValue(server);
|
||||
return ENV_GRAY_VALUE.equals(env);
|
||||
}
|
||||
|
||||
private String getEnvValue(Server server) {
|
||||
// eureka存储的metadata
|
||||
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
|
||||
return metadata.get(MEDATA_KEY_ENV);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过判断hostname来确定是否是预发布请求,可修改此方法实现自己想要的
|
||||
*
|
||||
* @param request request
|
||||
* @return 返回true:可以进入到预发环境
|
||||
*/
|
||||
@Override
|
||||
protected boolean canVisitPre(Server server, HttpServletRequest request) {
|
||||
String serverName = request.getServerName();
|
||||
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN);
|
||||
return domain.equals(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是灰度用户,可修改此方法实现自己想要的
|
||||
*
|
||||
* @param param 接口参数
|
||||
* @param userKeyManager userKey管理
|
||||
* @param server 服务器实例
|
||||
* @param request request
|
||||
* @return true:是
|
||||
*/
|
||||
protected boolean isGrayUser(Param param, DbEnvGrayManager userKeyManager, Server server, HttpServletRequest request) {
|
||||
String instanceId = server.getMetaInfo().getInstanceId();
|
||||
// 这里的灰度用户为appKey,包含此appKey则为灰度用户,允许访问
|
||||
String appKey = param.fetchAppKey();
|
||||
return userKeyManager.containsKey(instanceId, appKey);
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.gitee.sop.gateway.loadbalancer;
|
||||
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* 自定义PropertiesFactory,用来动态添加LoadBalance规则
|
||||
* @author tanghc
|
||||
*/
|
||||
public class SopPropertiesFactory extends PropertiesFactory {
|
||||
|
||||
/**
|
||||
* 可在配置文件中设置<code>zuul.custom-rule-classname=com.xx.ClassName</code>指定负载均衡规则类
|
||||
* 默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
|
||||
*/
|
||||
private static final String PROPERTIES_KEY = "zuul.custom-rule-classname";
|
||||
|
||||
private static final String CUSTOM_RULE_CLASSNAME = EnvironmentServerChooser.class.getName();
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* 配置文件配置:<serviceId>.ribbon.NFLoadBalancerRuleClassName=com.gitee.sop.gateway.loadbalancer.EnvironmentServerChooser
|
||||
* @param clazz
|
||||
* @param name serviceId
|
||||
* @return 返回class全限定名
|
||||
*/
|
||||
@Override
|
||||
public String getClassName(Class clazz, String name) {
|
||||
if (clazz == IRule.class) {
|
||||
return this.environment.getProperty(PROPERTIES_KEY, CUSTOM_RULE_CLASSNAME);
|
||||
} else {
|
||||
return super.getClassName(clazz, name);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.entity.ConfigGray;
|
||||
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
|
||||
import com.gitee.sop.gateway.mapper.ConfigGrayInstanceMapper;
|
||||
import com.gitee.sop.gateway.mapper.ConfigGrayMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.ServiceGrayDefinition;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 存放用户key,这里放在本机内容,如果灰度发布保存的用户id数量偏多,可放在redis中
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbEnvGrayManager extends DefaultEnvGrayManager {
|
||||
|
||||
private static final int STATUS_ENABLE = 1;
|
||||
|
||||
private static final Function<String[], String> FUNCTION_KEY = arr -> arr[0];
|
||||
private static final Function<String[], String> FUNCTION_VALUE = arr -> arr[1];
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayMapper configGrayMapper;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayInstanceMapper configGrayInstanceMapper;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
|
||||
List<ConfigGray> list = configGrayMapper.list(new Query());
|
||||
for (ConfigGray configGray : list) {
|
||||
this.setServiceGrayConfig(configGray);
|
||||
}
|
||||
|
||||
Query query = new Query();
|
||||
query.eq("status", STATUS_ENABLE);
|
||||
List<ConfigGrayInstance> grayInstanceList = configGrayInstanceMapper.list(query);
|
||||
for (ConfigGrayInstance configGrayInstance : grayInstanceList) {
|
||||
this.openGray(configGrayInstance.getInstanceId(), configGrayInstance.getServiceId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户key
|
||||
*
|
||||
* @param configGray 灰度配置
|
||||
*/
|
||||
public void setServiceGrayConfig(ConfigGray configGray) {
|
||||
if (configGray == null) {
|
||||
return;
|
||||
}
|
||||
String userKeyData = configGray.getUserKeyContent();
|
||||
String nameVersionContent = configGray.getNameVersionContent();
|
||||
String[] userKeys = StringUtils.split(userKeyData, ',');
|
||||
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
|
||||
log.info("灰度配置,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
|
||||
|
||||
Set<String> userKeySet = Stream.of(userKeys)
|
||||
.collect(Collectors.toCollection(Sets::newConcurrentHashSet));
|
||||
|
||||
Map<String, String> grayNameVersionMap = Stream.of(nameVersionList)
|
||||
.map(nameVersion -> StringUtils.split(nameVersion, '='))
|
||||
.collect(Collectors.toConcurrentMap(FUNCTION_KEY, FUNCTION_VALUE));
|
||||
|
||||
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
|
||||
serviceGrayConfig.setServiceId(configGray.getServiceId());
|
||||
serviceGrayConfig.setUserKeys(userKeySet);
|
||||
serviceGrayConfig.setGrayNameVersion(grayNameVersionMap);
|
||||
this.saveServiceGrayConfig(serviceGrayConfig);
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
protected void after() throws Exception {
|
||||
ZookeeperContext.setEnvironment(environment);
|
||||
String isvChannelPath = ZookeeperContext.getServiceGrayChannelPath();
|
||||
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
|
||||
String nodeData = new String(nodeCache.getCurrentData().getData());
|
||||
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
||||
String data = channelMsg.getData();
|
||||
ServiceGrayDefinition userKeyDefinition = JSON.parseObject(data, ServiceGrayDefinition.class);
|
||||
String serviceId = userKeyDefinition.getServiceId();
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "set":
|
||||
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
||||
this.setServiceGrayConfig(configGray);
|
||||
break;
|
||||
case "open":
|
||||
openGray(userKeyDefinition.getInstanceId(), serviceId);
|
||||
break;
|
||||
case "close":
|
||||
closeGray(userKeyDefinition.getInstanceId());
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -9,6 +9,7 @@ import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
@@ -19,6 +20,7 @@ import java.util.List;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
|
||||
|
||||
@Autowired
|
||||
@@ -54,7 +56,6 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
|
||||
remove(ip);
|
||||
break;
|
||||
default:
|
||||
log.error("IP黑名单,错误的消息指令,nodeData:{}", nodeData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package com.gitee.sop.gateway.manager;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.sop.gateway.entity.IsvDetailDTO;
|
||||
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
|
||||
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||
@@ -11,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
@@ -19,6 +21,7 @@ import java.util.List;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIsvManager extends CacheIsvManager {
|
||||
|
||||
@Autowired
|
||||
@@ -41,6 +44,7 @@ public class DbIsvManager extends CacheIsvManager {
|
||||
|
||||
@PostConstruct
|
||||
protected void after() throws Exception {
|
||||
ApiConfig.getInstance().setIsvManager(this);
|
||||
ZookeeperContext.setEnvironment(environment);
|
||||
String isvChannelPath = ZookeeperContext.getIsvInfoChannelPath();
|
||||
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
|
||||
@@ -57,7 +61,6 @@ public class DbIsvManager extends CacheIsvManager {
|
||||
remove(isvDefinition.getAppKey());
|
||||
break;
|
||||
default:
|
||||
log.error("ISV信息,错误的消息指令,nodeData:{}", nodeData);
|
||||
|
||||
}
|
||||
});
|
||||
|
@@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
@@ -35,6 +36,7 @@ import static java.util.stream.Collectors.toList;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManager {
|
||||
|
||||
@Autowired
|
||||
@@ -150,6 +152,8 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
|
||||
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
||||
remove(isvRoutePermission.getAppKey());
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@@ -19,6 +20,7 @@ import javax.annotation.PostConstruct;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbLimitConfigManager extends DefaultLimitConfigManager {
|
||||
|
||||
@Autowired
|
||||
@@ -61,7 +63,6 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
|
||||
update(configLimitDto);
|
||||
break;
|
||||
default:
|
||||
log.error("限流配置信息,错误的消息指令,nodeData:{}", nodeData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Collection;
|
||||
@@ -22,6 +23,7 @@ import java.util.Collection;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbRouteConfigManager extends DefaultRouteConfigManager {
|
||||
|
||||
@Autowired
|
||||
@@ -85,7 +87,6 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
|
||||
update(routeConfig);
|
||||
break;
|
||||
default:
|
||||
log.error("路由配置信息,错误的消息指令,nodeData:{}", nodeData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -14,5 +14,6 @@ public class ManagerInitializer {
|
||||
apiConfig.setRouteConfigManager(new DbRouteConfigManager());
|
||||
apiConfig.setLimitConfigManager(new DbLimitConfigManager());
|
||||
apiConfig.setIpBlacklistManager(new DbIPBlacklistManager());
|
||||
apiConfig.setUserKeyManager(new DbEnvGrayManager());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigGrayInstanceMapper extends CrudMapper<ConfigGrayInstance, Long> {
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigGray;
|
||||
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ConfigGrayMapper extends CrudMapper<ConfigGray, Long> {
|
||||
}
|
@@ -12,6 +12,9 @@ eureka.url=http://localhost:1111/eureka/
|
||||
zookeeper.url=localhost:2181
|
||||
# zipkin服务监控地址,没有开启不用改
|
||||
zipkin.url=http://127.0.0.1:9411/
|
||||
|
||||
# 预发布网关域名
|
||||
pre.domain=localhost
|
||||
# ------- 需要改的配置end -------
|
||||
|
||||
# 入口地址,不用改,默认是/zuul
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user