# 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.xml1.x
commit
d188faf621
@ -1,32 +1,32 @@ |
|||||||
* [首页](/?t=1564370846275) |
* [首页](/?t=1564558762831) |
||||||
* 开发文档 |
* 开发文档 |
||||||
* [快速体验](files/10010_快速体验.md?t=1564370846281) |
* [快速体验](files/10010_快速体验.md?t=1564558762834) |
||||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1564370846298) |
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1564558762850) |
||||||
* [新增接口](files/10020_新增接口.md?t=1564370846298) |
* [新增接口](files/10020_新增接口.md?t=1564558762850) |
||||||
* [业务参数校验](files/10030_业务参数校验.md?t=1564370846299) |
* [业务参数校验](files/10030_业务参数校验.md?t=1564558762850) |
||||||
* [错误处理](files/10040_错误处理.md?t=1564370846299) |
* [错误处理](files/10040_错误处理.md?t=1564558762850) |
||||||
* [编写文档](files/10041_编写文档.md?t=1564370846299) |
* [编写文档](files/10041_编写文档.md?t=1564558762850) |
||||||
* [接口交互详解](files/10050_接口交互详解.md?t=1564370846299) |
* [接口交互详解](files/10050_接口交互详解.md?t=1564558762850) |
||||||
* [easyopen支持](files/10070_easyopen支持.md?t=1564370846299) |
* [easyopen支持](files/10070_easyopen支持.md?t=1564558762850) |
||||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1564370846299) |
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1564558762850) |
||||||
* [ISV管理](files/10085_ISV管理.md?t=1564370846299) |
* [ISV管理](files/10085_ISV管理.md?t=1564558762850) |
||||||
* [自定义路由](files/10086_自定义路由.md?t=1564370846299) |
* [自定义路由](files/10086_自定义路由.md?t=1564558762851) |
||||||
* [路由授权](files/10090_路由授权.md?t=1564370846300) |
* [自定义返回结果](files/10087_自定义返回结果.md?t=1564558762851) |
||||||
* [接口限流](files/10092_接口限流.md?t=1564370846300) |
* [自定义过滤器](files/10088_自定义过滤器.md?t=1564558762851) |
||||||
* [监控日志](files/10093_监控日志.md?t=1564370846300) |
* [路由授权](files/10090_路由授权.md?t=1564558762851) |
||||||
* [SDK开发](files/10095_SDK开发.md?t=1564370846300) |
* [接口限流](files/10092_接口限流.md?t=1564558762851) |
||||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1564370846300) |
* [监控日志](files/10093_监控日志.md?t=1564558762851) |
||||||
* [应用授权](files/10097_应用授权.md?t=1564370846300) |
* [SDK开发](files/10095_SDK开发.md?t=1564558762851) |
||||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1564370846300) |
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1564558762851) |
||||||
* [传统web开发](files/10100_传统web开发.md?t=1564370846300) |
* [应用授权](files/10097_应用授权.md?t=1564558762851) |
||||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1564370846300) |
* [传统web开发](files/10100_传统web开发.md?t=1564558762851) |
||||||
* [文件上传](files/10104_文件上传.md?t=1564370846301) |
* [文件上传](files/10104_文件上传.md?t=1564558762852) |
||||||
* [nacos注册中心](files/10106_nacos注册中心.md?t=1564370846301) |
* [nacos注册中心](files/10106_nacos注册中心.md?t=1564558762852) |
||||||
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1564370846301) |
* [扩展其它注册中心](files/10107_扩展其它注册中心.md?t=1564558762852) |
||||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1564370846301) |
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1564558762852) |
||||||
* 原理分析 |
* 原理分析 |
||||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1564370846301) |
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1564558762852) |
||||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1564370846301) |
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1564558762852) |
||||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1564370846301) |
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1564558762852) |
||||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1564370846301) |
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1564558762852) |
||||||
* [常见问题](files/90100_常见问题.md?t=1564370846301) |
* [常见问题](files/90100_常见问题.md?t=1564558762852) |
||||||
|
@ -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();` |
|
@ -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 |
@ -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='开启灰度服务器实例'; |
@ -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; |
||||||
|
} |
@ -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; |
||||||
|
} |
@ -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> { |
||||||
|
} |
@ -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
@ -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; |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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 |
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 |
isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548 |
||||||
|
@ -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: |
||||||
|
|
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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> { |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue