支持预发布、灰度发布

1.x
tanghc 5 years ago
parent 17602e6efe
commit f03be910c4
  1. 32
      doc/docs/files/10110_预发布灰度发布.md
  2. 24
      sop-1.14.0.sql
  3. 38
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  4. 3
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceGrayDefinition.java
  5. 4
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ZookeeperContext.java
  6. 10
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/common/ChannelOperation.java
  7. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigGrayUserkeyMapper.java
  8. 2
      sop-admin/sop-admin-server/src/main/resources/public/index.html
  9. 0
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-ea2e58a4.d10599db.css
  10. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.8145abe4.js
  11. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d208c3a.93f165b2.js
  12. 2
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d221c34.c8ef105a.js
  13. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.1204bc29.js
  14. 4
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-ea2e58a4.f3f85b0e.js
  15. 16
      sop-admin/sop-admin-vue/src/views/service/serviceList.vue
  16. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/BaseServiceRouteInfo.java
  17. 3
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceGrayDefinition.java
  18. 58
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultEnvGrayManager.java
  19. 35
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/EnvGrayManager.java
  20. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ZookeeperContext.java
  21. 14
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreVersionDecisionFilter.java
  22. 8
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java
  23. 6
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/ServiceGrayConfig.java
  24. 18
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigGray.java
  25. 21
      sop-gateway/src/main/java/com/gitee/sop/gateway/entity/ConfigGrayInstance.java
  26. 19
      sop-gateway/src/main/java/com/gitee/sop/gateway/loadbalancer/EnvironmentServerChooser.java
  27. 93
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbEnvGrayManager.java
  28. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIPBlacklistManager.java
  29. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvManager.java
  30. 2
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbIsvRoutePermissionManager.java
  31. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbLimitConfigManager.java
  32. 1
      sop-gateway/src/main/java/com/gitee/sop/gateway/manager/DbRouteConfigManager.java
  33. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/ConfigGrayInstanceMapper.java
  34. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/ConfigGrayMapper.java
  35. 11
      sop-gateway/src/main/java/com/gitee/sop/gateway/mapper/ConfigGrayUserkeyMapper.java
  36. 3
      sop-gateway/src/main/resources/application-dev.properties
  37. 27
      sop.sql

@ -11,21 +11,31 @@
|open1.domain.com |网关服务器1 |
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
线上域名为`open.domain.com`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器
线上网关入口为`http://open.domain.com/api`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器
在网关工程打开`com.gitee.sop.gateway.loadbalancer.EnvironmentServerChooser`类,修改`PRE_DOMAIN`变量
SOP开启预发布步骤如下:
```java
/**
* 预发布机器域名
*/
private static final String PRE_DOMAIN = "openpre.domain.com";
```
修改网关工程配置文件,指定预发布域名
网关工程打包发布到阿里云
```properties
# 预发布网关域名
pre.domain=openpre.domain.com
```
重启网关
登录SOP-Admin,在服务列表中点击预发布,然后接口的请求地址变成:`http://openpre.domain.com/api`
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
## 使用灰度发布
灰度发布可允许指定的用户进行访问,其它用户则走正常流程。
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
登录SOP-Admin,前往服务列表。
- 先设置灰度参数,指定灰度用户和灰度接口
- 服务器实例开启灰度
参考类:
- PreVersionDecisionFilter.java
- EnvironmentServerChooser.java

@ -1,13 +1,25 @@
use sop;
CREATE TABLE `config_gray_userkey` (
CREATE TABLE `config_gray` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId',
`service_id` varchar(64) NOT NULL DEFAULT '',
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
`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_instanceid` (`instance_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='灰度发布用户key';
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='开启灰度服务器实例';

@ -15,8 +15,8 @@ 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.UserKeyDefinition;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
import com.gitee.sop.adminserver.common.ChannelOperation;
@ -215,14 +215,13 @@ public class ServiceApi {
@Api(name = "service.gray.config.get")
@ApiDocMethod(description = "灰度配置--获取")
ConfigGray serviceEnvGrayConfigGet(ServiceIdParam param) throws IOException {
String serviceId = param.getServiceId();
return configGrayMapper.getByColumn("service_id", serviceId);
return this.getConfigGray(param.getServiceId());
}
@Api(name = "service.gray.config.save")
@ApiDocMethod(description = "灰度配置--保存")
void serviceEnvGrayConfigSave(ServiceGrayConfigParam param) throws IOException {
String serviceId = param.getServiceId();
String serviceId = param.getServiceId().toLowerCase();
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
if (configGray == null) {
configGray = new ConfigGray();
@ -235,16 +234,21 @@ public class ServiceApi {
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 serviceId = param.getServiceId();
String instanceId = param.getInstanceId();
ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
@ -256,9 +260,10 @@ public class ServiceApi {
configGrayInstanceMapper.save(configGrayInstance);
} else {
configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayInstance.setServiceId(serviceId);
configGrayInstanceMapper.update(configGrayInstance);
}
this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET);
this.sendServiceGrayMsg(instanceId, serviceId, ChannelOperation.GRAY_USER_KEY_OPEN);
} catch (Exception e) {
log.error("灰度发布失败,param:{}", param, e);
throw new BizException("灰度发布失败,请查看日志");
@ -276,8 +281,8 @@ public class ServiceApi {
if (configGrayInstance != null) {
configGrayInstance.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
configGrayInstanceMapper.update(configGrayInstance);
this.sendServiceGrayMsg(param.getInstanceId(), param.getServiceId().toLowerCase(), ChannelOperation.GRAY_USER_KEY_CLOSE);
}
this.sendUserKeyMsg(param.getInstanceId(), ChannelOperation.GRAY_USER_KEY_CLEAR);
} catch (Exception e) {
log.error("上线失败,param:{}", param, e);
throw new BizException("上线失败,请查看日志");
@ -289,14 +294,23 @@ public class ServiceApi {
return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
}
private void sendUserKeyMsg(String instanceId, ChannelOperation channelOperation) {
UserKeyDefinition userKeyDefinition = new UserKeyDefinition();
userKeyDefinition.setInstanceId(instanceId);
ChannelMsg channelMsg = new ChannelMsg(channelOperation, userKeyDefinition);
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.getUserKeyChannelPath();
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);
}
}

@ -6,7 +6,8 @@ import lombok.Data;
* @author tanghc
*/
@Data
public class UserKeyDefinition {
public class ServiceGrayDefinition {
private String serviceId;
private String instanceId;
private String data;
}

@ -89,8 +89,8 @@ public class ZookeeperContext {
return serviceIdPath + "/" + routeId;
}
public static String getUserKeyChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/userkey";
public static String getServiceGrayChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/gray";
}
public static String getIsvInfoChannelPath() {

@ -38,13 +38,17 @@ public enum ChannelOperation {
ROUTE_PERMISSION_RELOAD("reload"),
/**
* 灰度发布用户key设置
* 灰度发布设置
*/
GRAY_USER_KEY_SET("set"),
/**
* 灰度发布用户key清除
* 灰度发布-开启
*/
GRAY_USER_KEY_CLEAR("clear"),
GRAY_USER_KEY_OPEN("open"),
/**
* 灰度发布-关闭
*/
GRAY_USER_KEY_CLOSE("close"),
;

@ -1,12 +0,0 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.ConfigGrayUserkey;
/**
* @author tanghc
*/
public interface ConfigGrayUserkeyMapper extends CrudMapper<ConfigGrayUserkey, 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>

@ -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}}]);

@ -54,7 +54,7 @@
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>
@ -74,7 +74,7 @@
<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" 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>
@ -281,15 +281,12 @@ export default {
},
doEnvOnline: function(row, callback) {
this.post('service.instance.env.online', row, function() {
callback && callback()
callback && callback.call(this)
})
},
onEnvPreOpen: function(row) {
this.confirm(`确定要开启 ${row.instanceId} 预发布吗?`, function(done) {
this.post('service.instance.env.pre.open', {
serviceId: row.serviceId,
instanceId: row.instanceId
}, function() {
this.post('service.instance.env.pre.open', row, function() {
this.tip('预发布成功')
done()
})
@ -305,10 +302,7 @@ export default {
},
onEnvGrayOpen: function(row) {
this.confirm(`确定要开启 ${row.instanceId} 灰度吗?`, function(done) {
this.post('service.instance.env.gray.open', {
serviceId: row.serviceId,
instanceId: row.instanceId
}, function() {
this.post('service.instance.env.gray.open', row, function() {
this.tip('开启成功')
done()
})

@ -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();
}
}

@ -6,7 +6,8 @@ import lombok.Data;
* @author tanghc
*/
@Data
public class UserKeyDefinition {
public class ServiceGrayDefinition {
private String serviceId;
private String instanceId;
private String data;
}

@ -2,11 +2,8 @@ package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author tanghc
@ -14,17 +11,18 @@ import java.util.stream.Collectors;
public class DefaultEnvGrayManager implements EnvGrayManager {
/**
* KEY:instanceId
* keyserviceId服务对应的灰度配置
*/
private Map<String, ServiceGrayConfig> serviceUserKeyMap = Maps.newConcurrentMap();
private Map<String, ServiceGrayConfig> serviceGrayConfigMap = Maps.newConcurrentMap();
/**
* key:instanceId value:serviceId
*/
private Map<String, String> instanceIdServiceIdMap = Maps.newConcurrentMap();
@Override
public List<String> listGrayInstanceId(String serviceId) {
return serviceUserKeyMap
.values()
.stream()
.map(ServiceGrayConfig::getInstanceId)
.collect(Collectors.toList());
public void saveServiceGrayConfig(ServiceGrayConfig serviceGrayConfig) {
serviceGrayConfigMap.putIfAbsent(serviceGrayConfig.getServiceId(), serviceGrayConfig);
}
@Override
@ -32,26 +30,40 @@ public class DefaultEnvGrayManager implements EnvGrayManager {
if (instanceId == null || userKey == null) {
return false;
}
return this.getServiceGrayConfig(instanceId).containsKey(userKey);
String serviceId = instanceIdServiceIdMap.get(instanceId);
ServiceGrayConfig grayConfig = this.getGrayConfig(serviceId);
return grayConfig != null && grayConfig.containsKey(userKey);
}
@Override
public String getVersion(String instanceId, String nameVersion) {
if (instanceId == null || nameVersion == null) {
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 this.getServiceGrayConfig(instanceId).getVersion(nameVersion);
return serviceGrayConfigMap.get(serviceId);
}
@Override
public void openGray(String instanceId, String serviceId) {
instanceIdServiceIdMap.putIfAbsent(instanceId, serviceId);
}
@Override
public ServiceGrayConfig getServiceGrayConfig(String instanceId) {
return serviceUserKeyMap.computeIfAbsent(instanceId, key -> {
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
serviceGrayConfig.setInstanceId(instanceId);
serviceGrayConfig.setUserKeys(Sets.newConcurrentHashSet());
serviceGrayConfig.setGrayNameVersion(Maps.newConcurrentMap());
return serviceGrayConfig;
});
public void closeGray(String instanceId) {
instanceIdServiceIdMap.remove(instanceId);
}
@Override

@ -3,18 +3,43 @@ package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
import java.util.List;
/**
* @author tanghc
*/
public interface EnvGrayManager extends BeanInitializer {
List<String> listGrayInstanceId(String serviceId);
/**
* 保存灰度配置
* @param serviceGrayConfig 灰度配置
*/
void saveServiceGrayConfig(ServiceGrayConfig serviceGrayConfig);
/**
* 实例是否允许
* @param instanceId 实例id
* @param userKey 用户key如appKey
* @return true允许访问
*/
boolean containsKey(String instanceId, Object userKey);
String getVersion(String instanceId, String nameVersion);
/**
* 获取灰度发布新版本号
* @param serviceId serviceId
* @param nameVersion 路由id
* @return 返回新版本号
*/
String getVersion(String serviceId, String nameVersion);
ServiceGrayConfig getServiceGrayConfig(String instanceId);
/**
* 开启灰度
* @param instanceId instanceId
* @param serviceId serviceId
*/
void openGray(String instanceId, String serviceId);
/**
* 关闭灰度
* @param instanceId instanceId
*/
void closeGray(String instanceId);
}

@ -69,8 +69,8 @@ public class ZookeeperContext {
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
}
public static String getUserKeyChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/userkey";
public static String getServiceGrayChannelPath() {
return SOP_MSG_CHANNEL_PATH + "/gray";
}
public static String getIsvRoutePermissionChannelPath() {

@ -10,8 +10,6 @@ import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @author tanghc
*/
@ -38,17 +36,11 @@ public class PreVersionDecisionFilter extends BaseZuulFilter {
if (targetRoute == null) {
return null;
}
String serviceId = targetRoute.getServiceRouteInfo().getServiceId();
List<String> instanceIdList = envGrayManager.listGrayInstanceId(serviceId);
String appKey = apiParam.fetchAppKey();
for (String instanceId : instanceIdList) {
if (envGrayManager.containsKey(instanceId, appKey)) {
String version = envGrayManager.getVersion(instanceId, nameVersion);
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
// 如果服务在灰度阶段,返回一个灰度版本号
String version = envGrayManager.getVersion(serviceId, nameVersion);
if (version != null) {
requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version);
break;
}
}
}
return null;
}

@ -42,14 +42,6 @@ public abstract class BaseServerChooser extends ZoneAvoidanceRule {
*/
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
/**
* 能否访问灰度服务器
* @param server 灰度服务器
* @param request request
* @return true
*/
protected abstract boolean canVisitGray(Server server, HttpServletRequest request);
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();

@ -13,8 +13,6 @@ public class ServiceGrayConfig {
private String serviceId;
private String instanceId;
/**
* 用户id
*/
@ -31,8 +29,4 @@ public class ServiceGrayConfig {
return grayNameVersion.get(name);
}
public void clear() {
this.userKeys.clear();
this.grayNameVersion.clear();
}
}

@ -11,35 +11,29 @@ import java.util.Date;
/**
* 表名config_gray_userkey
* 备注灰度发布用户key
* 表名config_gray
* 备注服务灰度配置
*
* @author tanghc
*/
@Table(name = "config_gray_userkey")
@Table(name = "config_gray")
@Data
public class ConfigGrayUserkey {
public class ConfigGray {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** serviceId, 数据库字段:service_id */
/** 数据库字段:service_id */
private String serviceId;
/** instanceId, 数据库字段:instance_id */
private String instanceId;
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
private String userKeyContent;
/** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */
/** 需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段:name_version_content */
private String nameVersionContent;
/** 0:禁用,1:启用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */
private Date gmtCreate;

@ -1,4 +1,4 @@
package com.gitee.sop.adminserver.entity;
package com.gitee.sop.gateway.entity;
import lombok.Data;
@ -11,31 +11,24 @@ import java.util.Date;
/**
* 表名config_gray_userkey
* 备注灰度发布用户key
* 表名config_gray_instance
*
* @author tanghc
*/
@Table(name = "config_gray_userkey")
@Table(name = "config_gray_instance")
@Data
public class ConfigGrayUserkey {
public class ConfigGrayInstance {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** serviceId, 数据库字段:service_id */
private String serviceId;
/** instanceId, 数据库字段:instance_id */
/** instance_id, 数据库字段:instance_id */
private String instanceId;
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
private String userKeyContent;
/** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */
private String nameVersionContent;
/** service_id, 数据库字段:service_id */
private String serviceId;
/** 0:禁用,1:启用, 数据库字段:status */
private Byte status;

@ -2,12 +2,11 @@ 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.ApiParam;
import com.gitee.sop.gatewaycommon.param.Param;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
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;
@ -56,22 +55,10 @@ public class EnvironmentServerChooser extends BaseServerChooser {
@Override
protected boolean canVisitPre(Server server, HttpServletRequest request) {
String serverName = request.getServerName();
return PRE_DOMAIN.equals(serverName);
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN);
return domain.equals(serverName);
}
/**
* 能否进入灰度环境
*
* @param request request
* @return 返回true可以进入到预发环境
*/
protected boolean canVisitGray(Server server, HttpServletRequest request) {
ApiParam apiParam = ZuulContext.getApiParam();
DbEnvGrayManager userKeyManager = SpringContext.getBean(DbEnvGrayManager.class);
return this.isGrayUser(apiParam, userKeyManager, server, request);
}
/**
* 是否是灰度用户可修改此方法实现自己想要的
*

@ -2,13 +2,16 @@ package com.gitee.sop.gateway.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.gateway.entity.ConfigGrayUserkey;
import com.gitee.sop.gateway.mapper.ConfigGrayUserkeyMapper;
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.UserKeyDefinition;
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;
@ -19,6 +22,8 @@ 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;
@ -33,82 +38,86 @@ 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 ConfigGrayUserkeyMapper configGrayUserkeyMapper;
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<ConfigGrayUserkey> list = configGrayUserkeyMapper.list(query);
for (ConfigGrayUserkey configGrayUserkey : list) {
this.setServiceGrayConfig(configGrayUserkey);
List<ConfigGrayInstance> grayInstanceList = configGrayInstanceMapper.list(query);
for (ConfigGrayInstance configGrayInstance : grayInstanceList) {
this.openGray(configGrayInstance.getInstanceId(), configGrayInstance.getServiceId());
}
}
/**
* 设置用户key
*
* @param configGrayUserkey 灰度配置
* @param configGray 灰度配置
*/
public void setServiceGrayConfig(ConfigGrayUserkey configGrayUserkey) {
if (configGrayUserkey == null) {
public void setServiceGrayConfig(ConfigGray configGray) {
if (configGray == null) {
return;
}
String instanceId = configGrayUserkey.getInstanceId();
this.clear(instanceId);
String userKeyData = configGrayUserkey.getUserKeyContent();
String nameVersionContent = configGrayUserkey.getNameVersionContent();
String userKeyData = configGray.getUserKeyContent();
String nameVersionContent = configGray.getNameVersionContent();
String[] userKeys = StringUtils.split(userKeyData, ',');
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
log.info("添加userKey,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
List<String> list = Stream.of(userKeys).collect(Collectors.toList());
ServiceGrayConfig serviceGrayConfig = getServiceGrayConfig(instanceId);
serviceGrayConfig.setServiceId(configGrayUserkey.getServiceId());
serviceGrayConfig.getUserKeys().addAll(list);
Map<String, String> grayNameVersion = serviceGrayConfig.getGrayNameVersion();
for (String nameVersion : nameVersionList) {
String[] nameVersionInfo = StringUtils.split(nameVersion, '=');
String name = nameVersionInfo[0];
String version = nameVersionInfo[1];
grayNameVersion.put(name, version);
}
log.info("灰度配置,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
}
Set<String> userKeySet = Stream.of(userKeys)
.collect(Collectors.toCollection(Sets::newConcurrentHashSet));
/**
* 清空用户key
*/
public void clear(String instanceId) {
getServiceGrayConfig(instanceId).clear();
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.getUserKeyChannelPath();
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();
UserKeyDefinition userKeyDefinition = JSON.parseObject(data, UserKeyDefinition.class);
String instanceId = userKeyDefinition.getInstanceId();
ServiceGrayDefinition userKeyDefinition = JSON.parseObject(data, ServiceGrayDefinition.class);
String serviceId = userKeyDefinition.getServiceId();
switch (channelMsg.getOperation()) {
case "set":
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", instanceId);
this.setServiceGrayConfig(configGrayUserkey);
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
this.setServiceGrayConfig(configGray);
break;
case "open":
openGray(userKeyDefinition.getInstanceId(), serviceId);
break;
case "clear":
clear(instanceId);
case "close":
closeGray(userKeyDefinition.getInstanceId());
break;
default:
log.error("userKey消息,错误的消息指令,nodeData:{}", nodeData);
}
});

@ -56,7 +56,6 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
remove(ip);
break;
default:
log.error("IP黑名单,错误的消息指令,nodeData:{}", nodeData);
}
});
}

@ -61,7 +61,6 @@ public class DbIsvManager extends CacheIsvManager {
remove(isvDefinition.getAppKey());
break;
default:
log.error("ISV信息,错误的消息指令,nodeData:{}", nodeData);
}
});

@ -152,6 +152,8 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
remove(isvRoutePermission.getAppKey());
break;
default:
}
});
}

@ -63,7 +63,6 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
update(configLimitDto);
break;
default:
log.error("限流配置信息,错误的消息指令,nodeData:{}", nodeData);
}
});
}

@ -87,7 +87,6 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
update(routeConfig);
break;
default:
log.error("路由配置信息,错误的消息指令,nodeData:{}", nodeData);
}
});
}

@ -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> {
}

@ -1,11 +0,0 @@
package com.gitee.sop.gateway.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.gateway.entity.ConfigGrayUserkey;
/**
* @author tanghc
*/
public interface ConfigGrayUserkeyMapper extends CrudMapper<ConfigGrayUserkey, 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

@ -15,7 +15,8 @@ DROP TABLE IF EXISTS `admin_user_info`;
DROP TABLE IF EXISTS `config_common`;
DROP TABLE IF EXISTS `isv_keys`;
DROP TABLE IF EXISTS `config_ip_blacklist`;
DROP TABLE IF EXISTS `config_gray_userkey`;
DROP TABLE IF EXISTS `config_gray`;
DROP TABLE IF EXISTS `config_gray_instance`;
CREATE TABLE `admin_user_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@ -176,17 +177,29 @@ CREATE TABLE `config_ip_blacklist` (
UNIQUE KEY `uk_ip` (`ip`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP黑名单';
CREATE TABLE `config_gray_userkey` (
CREATE TABLE `config_gray` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId',
`service_id` varchar(64) NOT NULL DEFAULT '',
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
`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
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='灰度发布用户key';
UNIQUE KEY `uk_instanceid` (`instance_id`) USING BTREE,
KEY `idx_serviceid` (`service_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='开启灰度服务器实例';
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;

Loading…
Cancel
Save