mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
支持预发布、灰度发布
This commit is contained in:
@@ -11,21 +11,31 @@
|
|||||||
|open1.domain.com |网关服务器1 |
|
|open1.domain.com |网关服务器1 |
|
||||||
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
|
|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
|
修改网关工程配置文件,指定预发布域名
|
||||||
/**
|
|
||||||
* 预发布机器域名
|
```properties
|
||||||
*/
|
# 预发布网关域名
|
||||||
private static final String PRE_DOMAIN = "openpre.domain.com";
|
pre.domain=openpre.domain.com
|
||||||
```
|
```
|
||||||
|
重启网关
|
||||||
|
|
||||||
网关工程打包发布到阿里云
|
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。
|
||||||
|
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
|
||||||
登录SOP-Admin,在服务列表中点击预发布,然后接口的请求地址变成:`http://openpre.domain.com/api`
|
|
||||||
|
|
||||||
## 使用灰度发布
|
## 使用灰度发布
|
||||||
|
|
||||||
灰度发布可允许指定的用户进行访问,其它用户则走正常流程。
|
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
|
||||||
|
|
||||||
|
登录SOP-Admin,前往服务列表。
|
||||||
|
|
||||||
|
- 先设置灰度参数,指定灰度用户和灰度接口
|
||||||
|
- 服务器实例开启灰度
|
||||||
|
|
||||||
|
参考类:
|
||||||
|
|
||||||
|
- PreVersionDecisionFilter.java
|
||||||
|
- EnvironmentServerChooser.java
|
@@ -1,13 +1,25 @@
|
|||||||
use sop;
|
use sop;
|
||||||
|
|
||||||
CREATE TABLE `config_gray_userkey` (
|
CREATE TABLE `config_gray` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`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,多个用引文逗号隔开',
|
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
|
||||||
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
|
`name_version_content` text COMMENT '需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开',
|
||||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
|
|
||||||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_instanceid` (`instance_id`) USING BTREE
|
UNIQUE KEY `uk_serviceid` (`service_id`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='灰度发布用户key';
|
) 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.api.service.result.ServiceInstanceVO;
|
||||||
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
import com.gitee.sop.adminserver.bean.ChannelMsg;
|
||||||
import com.gitee.sop.adminserver.bean.MetadataEnum;
|
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.ServiceRouteInfo;
|
||||||
import com.gitee.sop.adminserver.bean.UserKeyDefinition;
|
|
||||||
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
import com.gitee.sop.adminserver.bean.ZookeeperContext;
|
||||||
import com.gitee.sop.adminserver.common.BizException;
|
import com.gitee.sop.adminserver.common.BizException;
|
||||||
import com.gitee.sop.adminserver.common.ChannelOperation;
|
import com.gitee.sop.adminserver.common.ChannelOperation;
|
||||||
@@ -215,14 +215,13 @@ public class ServiceApi {
|
|||||||
@Api(name = "service.gray.config.get")
|
@Api(name = "service.gray.config.get")
|
||||||
@ApiDocMethod(description = "灰度配置--获取")
|
@ApiDocMethod(description = "灰度配置--获取")
|
||||||
ConfigGray serviceEnvGrayConfigGet(ServiceIdParam param) throws IOException {
|
ConfigGray serviceEnvGrayConfigGet(ServiceIdParam param) throws IOException {
|
||||||
String serviceId = param.getServiceId();
|
return this.getConfigGray(param.getServiceId());
|
||||||
return configGrayMapper.getByColumn("service_id", serviceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Api(name = "service.gray.config.save")
|
@Api(name = "service.gray.config.save")
|
||||||
@ApiDocMethod(description = "灰度配置--保存")
|
@ApiDocMethod(description = "灰度配置--保存")
|
||||||
void serviceEnvGrayConfigSave(ServiceGrayConfigParam param) throws IOException {
|
void serviceEnvGrayConfigSave(ServiceGrayConfigParam param) throws IOException {
|
||||||
String serviceId = param.getServiceId();
|
String serviceId = param.getServiceId().toLowerCase();
|
||||||
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
||||||
if (configGray == null) {
|
if (configGray == null) {
|
||||||
configGray = new ConfigGray();
|
configGray = new ConfigGray();
|
||||||
@@ -235,16 +234,21 @@ public class ServiceApi {
|
|||||||
configGray.setUserKeyContent(param.getUserKeyContent());
|
configGray.setUserKeyContent(param.getUserKeyContent());
|
||||||
configGrayMapper.update(configGray);
|
configGrayMapper.update(configGray);
|
||||||
}
|
}
|
||||||
|
this.sendServiceGrayMsg(serviceId, ChannelOperation.GRAY_USER_KEY_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Api(name = "service.instance.env.gray.open")
|
@Api(name = "service.instance.env.gray.open")
|
||||||
@ApiDocMethod(description = "开启灰度发布")
|
@ApiDocMethod(description = "开启灰度发布")
|
||||||
void serviceEnvGray(ServiceInstanceParam param) throws IOException {
|
void serviceEnvGray(ServiceInstanceParam param) throws IOException {
|
||||||
try {
|
try {
|
||||||
|
String serviceId = param.getServiceId().toLowerCase();
|
||||||
|
ConfigGray configGray = this.getConfigGray(serviceId);
|
||||||
|
if (configGray == null) {
|
||||||
|
throw new BizException("请先设置灰度参数");
|
||||||
|
}
|
||||||
MetadataEnum envPre = MetadataEnum.ENV_GRAY;
|
MetadataEnum envPre = MetadataEnum.ENV_GRAY;
|
||||||
registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
|
registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
|
||||||
|
|
||||||
String serviceId = param.getServiceId();
|
|
||||||
String instanceId = param.getInstanceId();
|
String instanceId = param.getInstanceId();
|
||||||
|
|
||||||
ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
|
ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
|
||||||
@@ -256,9 +260,10 @@ public class ServiceApi {
|
|||||||
configGrayInstanceMapper.save(configGrayInstance);
|
configGrayInstanceMapper.save(configGrayInstance);
|
||||||
} else {
|
} else {
|
||||||
configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
|
configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
|
||||||
|
configGrayInstance.setServiceId(serviceId);
|
||||||
configGrayInstanceMapper.update(configGrayInstance);
|
configGrayInstanceMapper.update(configGrayInstance);
|
||||||
}
|
}
|
||||||
this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET);
|
this.sendServiceGrayMsg(instanceId, serviceId, ChannelOperation.GRAY_USER_KEY_OPEN);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("灰度发布失败,param:{}", param, e);
|
log.error("灰度发布失败,param:{}", param, e);
|
||||||
throw new BizException("灰度发布失败,请查看日志");
|
throw new BizException("灰度发布失败,请查看日志");
|
||||||
@@ -276,8 +281,8 @@ public class ServiceApi {
|
|||||||
if (configGrayInstance != null) {
|
if (configGrayInstance != null) {
|
||||||
configGrayInstance.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
|
configGrayInstance.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
|
||||||
configGrayInstanceMapper.update(configGrayInstance);
|
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) {
|
} catch (Exception e) {
|
||||||
log.error("上线失败,param:{}", param, e);
|
log.error("上线失败,param:{}", param, e);
|
||||||
throw new BizException("上线失败,请查看日志");
|
throw new BizException("上线失败,请查看日志");
|
||||||
@@ -289,14 +294,23 @@ public class ServiceApi {
|
|||||||
return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
|
return configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendUserKeyMsg(String instanceId, ChannelOperation channelOperation) {
|
private void sendServiceGrayMsg(String serviceId, ChannelOperation channelOperation) {
|
||||||
UserKeyDefinition userKeyDefinition = new UserKeyDefinition();
|
this.sendServiceGrayMsg(null, serviceId, channelOperation);
|
||||||
userKeyDefinition.setInstanceId(instanceId);
|
}
|
||||||
ChannelMsg channelMsg = new ChannelMsg(channelOperation, userKeyDefinition);
|
|
||||||
|
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 jsonData = JSON.toJSONString(channelMsg);
|
||||||
String path = ZookeeperContext.getUserKeyChannelPath();
|
String path = ZookeeperContext.getServiceGrayChannelPath();
|
||||||
log.info("消息推送--灰度发布({}), path:{}, data:{}",channelOperation.getOperation(), path, jsonData);
|
log.info("消息推送--灰度发布({}), path:{}, data:{}",channelOperation.getOperation(), path, jsonData);
|
||||||
ZookeeperContext.createOrUpdateData(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
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class UserKeyDefinition {
|
public class ServiceGrayDefinition {
|
||||||
|
private String serviceId;
|
||||||
private String instanceId;
|
private String instanceId;
|
||||||
private String data;
|
private String data;
|
||||||
}
|
}
|
@@ -89,8 +89,8 @@ public class ZookeeperContext {
|
|||||||
return serviceIdPath + "/" + routeId;
|
return serviceIdPath + "/" + routeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUserKeyChannelPath() {
|
public static String getServiceGrayChannelPath() {
|
||||||
return SOP_MSG_CHANNEL_PATH + "/userkey";
|
return SOP_MSG_CHANNEL_PATH + "/gray";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getIsvInfoChannelPath() {
|
public static String getIsvInfoChannelPath() {
|
||||||
|
@@ -38,13 +38,17 @@ public enum ChannelOperation {
|
|||||||
ROUTE_PERMISSION_RELOAD("reload"),
|
ROUTE_PERMISSION_RELOAD("reload"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 灰度发布用户key设置
|
* 灰度发布设置
|
||||||
*/
|
*/
|
||||||
GRAY_USER_KEY_SET("set"),
|
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>
|
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
@@ -54,7 +54,7 @@
|
|||||||
width="100"
|
width="100"
|
||||||
>
|
>
|
||||||
<template slot-scope="scope">
|
<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 === '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 === 'UNKNOWN'">未知</el-tag>
|
||||||
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">已禁用</el-tag>
|
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">已禁用</el-tag>
|
||||||
@@ -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 === '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="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 && !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 === '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>
|
<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>
|
</template>
|
||||||
@@ -281,15 +281,12 @@ export default {
|
|||||||
},
|
},
|
||||||
doEnvOnline: function(row, callback) {
|
doEnvOnline: function(row, callback) {
|
||||||
this.post('service.instance.env.online', row, function() {
|
this.post('service.instance.env.online', row, function() {
|
||||||
callback && callback()
|
callback && callback.call(this)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onEnvPreOpen: function(row) {
|
onEnvPreOpen: function(row) {
|
||||||
this.confirm(`确定要开启 ${row.instanceId} 预发布吗?`, function(done) {
|
this.confirm(`确定要开启 ${row.instanceId} 预发布吗?`, function(done) {
|
||||||
this.post('service.instance.env.pre.open', {
|
this.post('service.instance.env.pre.open', row, function() {
|
||||||
serviceId: row.serviceId,
|
|
||||||
instanceId: row.instanceId
|
|
||||||
}, function() {
|
|
||||||
this.tip('预发布成功')
|
this.tip('预发布成功')
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
@@ -305,10 +302,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onEnvGrayOpen: function(row) {
|
onEnvGrayOpen: function(row) {
|
||||||
this.confirm(`确定要开启 ${row.instanceId} 灰度吗?`, function(done) {
|
this.confirm(`确定要开启 ${row.instanceId} 灰度吗?`, function(done) {
|
||||||
this.post('service.instance.env.gray.open', {
|
this.post('service.instance.env.gray.open', row, function() {
|
||||||
serviceId: row.serviceId,
|
|
||||||
instanceId: row.instanceId
|
|
||||||
}, function() {
|
|
||||||
this.tip('开启成功')
|
this.tip('开启成功')
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
@@ -12,4 +12,8 @@ import java.util.List;
|
|||||||
public class BaseServiceRouteInfo<T extends BaseRouteDefinition> {
|
public class BaseServiceRouteInfo<T extends BaseRouteDefinition> {
|
||||||
private String serviceId;
|
private String serviceId;
|
||||||
private List<T> routeDefinitionList = Collections.emptyList();
|
private List<T> routeDefinitionList = Collections.emptyList();
|
||||||
|
|
||||||
|
public String fetchServiceIdLowerCase() {
|
||||||
|
return this.serviceId.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
@@ -6,7 +6,8 @@ import lombok.Data;
|
|||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class UserKeyDefinition {
|
public class ServiceGrayDefinition {
|
||||||
|
private String serviceId;
|
||||||
private String instanceId;
|
private String instanceId;
|
||||||
private String data;
|
private String data;
|
||||||
}
|
}
|
@@ -2,11 +2,8 @@ package com.gitee.sop.gatewaycommon.manager;
|
|||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
@@ -14,17 +11,18 @@ import java.util.stream.Collectors;
|
|||||||
public class DefaultEnvGrayManager implements EnvGrayManager {
|
public class DefaultEnvGrayManager implements EnvGrayManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KEY:instanceId
|
* key:serviceId,服务对应的灰度配置
|
||||||
*/
|
*/
|
||||||
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
|
@Override
|
||||||
public List<String> listGrayInstanceId(String serviceId) {
|
public void saveServiceGrayConfig(ServiceGrayConfig serviceGrayConfig) {
|
||||||
return serviceUserKeyMap
|
serviceGrayConfigMap.putIfAbsent(serviceGrayConfig.getServiceId(), serviceGrayConfig);
|
||||||
.values()
|
|
||||||
.stream()
|
|
||||||
.map(ServiceGrayConfig::getInstanceId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -32,26 +30,40 @@ public class DefaultEnvGrayManager implements EnvGrayManager {
|
|||||||
if (instanceId == null || userKey == null) {
|
if (instanceId == null || userKey == null) {
|
||||||
return false;
|
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
|
@Override
|
||||||
public String getVersion(String instanceId, String nameVersion) {
|
public String getVersion(String serviceId, String nameVersion) {
|
||||||
if (instanceId == null || nameVersion == null) {
|
if (serviceId == null || nameVersion == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.getServiceGrayConfig(instanceId).getVersion(nameVersion);
|
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
|
@Override
|
||||||
public ServiceGrayConfig getServiceGrayConfig(String instanceId) {
|
public void openGray(String instanceId, String serviceId) {
|
||||||
return serviceUserKeyMap.computeIfAbsent(instanceId, key -> {
|
instanceIdServiceIdMap.putIfAbsent(instanceId, serviceId);
|
||||||
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
|
}
|
||||||
serviceGrayConfig.setInstanceId(instanceId);
|
|
||||||
serviceGrayConfig.setUserKeys(Sets.newConcurrentHashSet());
|
@Override
|
||||||
serviceGrayConfig.setGrayNameVersion(Maps.newConcurrentMap());
|
public void closeGray(String instanceId) {
|
||||||
return serviceGrayConfig;
|
instanceIdServiceIdMap.remove(instanceId);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -3,18 +3,43 @@ package com.gitee.sop.gatewaycommon.manager;
|
|||||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
public interface EnvGrayManager extends BeanInitializer {
|
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);
|
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";
|
return SOP_MSG_CHANNEL_PATH + "/isvinfo";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUserKeyChannelPath() {
|
public static String getServiceGrayChannelPath() {
|
||||||
return SOP_MSG_CHANNEL_PATH + "/userkey";
|
return SOP_MSG_CHANNEL_PATH + "/gray";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getIsvRoutePermissionChannelPath() {
|
public static String getIsvRoutePermissionChannelPath() {
|
||||||
|
@@ -10,8 +10,6 @@ import com.netflix.zuul.context.RequestContext;
|
|||||||
import com.netflix.zuul.exception.ZuulException;
|
import com.netflix.zuul.exception.ZuulException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@@ -38,17 +36,11 @@ public class PreVersionDecisionFilter extends BaseZuulFilter {
|
|||||||
if (targetRoute == null) {
|
if (targetRoute == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String serviceId = targetRoute.getServiceRouteInfo().getServiceId();
|
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
||||||
List<String> instanceIdList = envGrayManager.listGrayInstanceId(serviceId);
|
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||||
String appKey = apiParam.fetchAppKey();
|
String version = envGrayManager.getVersion(serviceId, nameVersion);
|
||||||
for (String instanceId : instanceIdList) {
|
|
||||||
if (envGrayManager.containsKey(instanceId, appKey)) {
|
|
||||||
String version = envGrayManager.getVersion(instanceId, nameVersion);
|
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version);
|
requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -42,14 +42,6 @@ public abstract class BaseServerChooser extends ZoneAvoidanceRule {
|
|||||||
*/
|
*/
|
||||||
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
|
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
|
||||||
|
|
||||||
/**
|
|
||||||
* 能否访问灰度服务器
|
|
||||||
* @param server 灰度服务器
|
|
||||||
* @param request request
|
|
||||||
* @return true:能
|
|
||||||
*/
|
|
||||||
protected abstract boolean canVisitGray(Server server, HttpServletRequest request);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Server choose(Object key) {
|
public Server choose(Object key) {
|
||||||
ILoadBalancer lb = getLoadBalancer();
|
ILoadBalancer lb = getLoadBalancer();
|
||||||
|
@@ -13,8 +13,6 @@ public class ServiceGrayConfig {
|
|||||||
|
|
||||||
private String serviceId;
|
private String serviceId;
|
||||||
|
|
||||||
private String instanceId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户id
|
* 用户id
|
||||||
*/
|
*/
|
||||||
@@ -31,8 +29,4 @@ public class ServiceGrayConfig {
|
|||||||
return grayNameVersion.get(name);
|
return grayNameVersion.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
this.userKeys.clear();
|
|
||||||
this.grayNameVersion.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -11,35 +11,29 @@ import java.util.Date;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表名:config_gray_userkey
|
* 表名:config_gray
|
||||||
* 备注:灰度发布用户key
|
* 备注:服务灰度配置
|
||||||
*
|
*
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Table(name = "config_gray_userkey")
|
@Table(name = "config_gray")
|
||||||
@Data
|
@Data
|
||||||
public class ConfigGrayUserkey {
|
public class ConfigGray {
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "id")
|
@Column(name = "id")
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
/** 数据库字段:id */
|
/** 数据库字段:id */
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** serviceId, 数据库字段:service_id */
|
/** 数据库字段:service_id */
|
||||||
private String serviceId;
|
private String serviceId;
|
||||||
|
|
||||||
/** instanceId, 数据库字段:instance_id */
|
|
||||||
private String instanceId;
|
|
||||||
|
|
||||||
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
||||||
private String userKeyContent;
|
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;
|
private String nameVersionContent;
|
||||||
|
|
||||||
/** 0:禁用,1:启用, 数据库字段:status */
|
|
||||||
private Byte status;
|
|
||||||
|
|
||||||
/** 数据库字段:gmt_create */
|
/** 数据库字段:gmt_create */
|
||||||
private Date gmtCreate;
|
private Date gmtCreate;
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package com.gitee.sop.adminserver.entity;
|
package com.gitee.sop.gateway.entity;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -11,31 +11,24 @@ import java.util.Date;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表名:config_gray_userkey
|
* 表名:config_gray_instance
|
||||||
* 备注:灰度发布用户key
|
|
||||||
*
|
*
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Table(name = "config_gray_userkey")
|
@Table(name = "config_gray_instance")
|
||||||
@Data
|
@Data
|
||||||
public class ConfigGrayUserkey {
|
public class ConfigGrayInstance {
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "id")
|
@Column(name = "id")
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
/** 数据库字段:id */
|
/** 数据库字段:id */
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** serviceId, 数据库字段:service_id */
|
/** instance_id, 数据库字段:instance_id */
|
||||||
private String serviceId;
|
|
||||||
|
|
||||||
/** instanceId, 数据库字段:instance_id */
|
|
||||||
private String instanceId;
|
private String instanceId;
|
||||||
|
|
||||||
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
/** service_id, 数据库字段:service_id */
|
||||||
private String userKeyContent;
|
private String serviceId;
|
||||||
|
|
||||||
/** 需要灰度的接口,goods.get=1.2,order.list=1.2, 数据库字段:name_version_content */
|
|
||||||
private String nameVersionContent;
|
|
||||||
|
|
||||||
/** 0:禁用,1:启用, 数据库字段:status */
|
/** 0:禁用,1:启用, 数据库字段:status */
|
||||||
private Byte 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.gateway.manager.DbEnvGrayManager;
|
||||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
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.param.Param;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
|
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
|
||||||
import com.netflix.loadbalancer.Server;
|
import com.netflix.loadbalancer.Server;
|
||||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -56,22 +55,10 @@ public class EnvironmentServerChooser extends BaseServerChooser {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canVisitPre(Server server, HttpServletRequest request) {
|
protected boolean canVisitPre(Server server, HttpServletRequest request) {
|
||||||
String serverName = request.getServerName();
|
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.alibaba.fastjson.JSON;
|
||||||
import com.gitee.fastmybatis.core.query.Query;
|
import com.gitee.fastmybatis.core.query.Query;
|
||||||
import com.gitee.sop.gateway.entity.ConfigGrayUserkey;
|
import com.gitee.sop.gateway.entity.ConfigGray;
|
||||||
import com.gitee.sop.gateway.mapper.ConfigGrayUserkeyMapper;
|
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.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.DefaultEnvGrayManager;
|
||||||
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.ServiceGrayConfig;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -19,6 +22,8 @@ import javax.annotation.PostConstruct;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -33,82 +38,86 @@ public class DbEnvGrayManager extends DefaultEnvGrayManager {
|
|||||||
|
|
||||||
private static final int STATUS_ENABLE = 1;
|
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
|
@Autowired
|
||||||
private Environment environment;
|
private Environment environment;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ConfigGrayUserkeyMapper configGrayUserkeyMapper;
|
private ConfigGrayMapper configGrayMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigGrayInstanceMapper configGrayInstanceMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
public void load() {
|
||||||
|
|
||||||
|
List<ConfigGray> list = configGrayMapper.list(new Query());
|
||||||
|
for (ConfigGray configGray : list) {
|
||||||
|
this.setServiceGrayConfig(configGray);
|
||||||
|
}
|
||||||
|
|
||||||
Query query = new Query();
|
Query query = new Query();
|
||||||
query.eq("status", STATUS_ENABLE);
|
query.eq("status", STATUS_ENABLE);
|
||||||
List<ConfigGrayUserkey> list = configGrayUserkeyMapper.list(query);
|
List<ConfigGrayInstance> grayInstanceList = configGrayInstanceMapper.list(query);
|
||||||
for (ConfigGrayUserkey configGrayUserkey : list) {
|
for (ConfigGrayInstance configGrayInstance : grayInstanceList) {
|
||||||
this.setServiceGrayConfig(configGrayUserkey);
|
this.openGray(configGrayInstance.getInstanceId(), configGrayInstance.getServiceId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置用户key
|
* 设置用户key
|
||||||
*
|
*
|
||||||
* @param configGrayUserkey 灰度配置
|
* @param configGray 灰度配置
|
||||||
*/
|
*/
|
||||||
public void setServiceGrayConfig(ConfigGrayUserkey configGrayUserkey) {
|
public void setServiceGrayConfig(ConfigGray configGray) {
|
||||||
if (configGrayUserkey == null) {
|
if (configGray == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String instanceId = configGrayUserkey.getInstanceId();
|
String userKeyData = configGray.getUserKeyContent();
|
||||||
this.clear(instanceId);
|
String nameVersionContent = configGray.getNameVersionContent();
|
||||||
String userKeyData = configGrayUserkey.getUserKeyContent();
|
|
||||||
String nameVersionContent = configGrayUserkey.getNameVersionContent();
|
|
||||||
String[] userKeys = StringUtils.split(userKeyData, ',');
|
String[] userKeys = StringUtils.split(userKeyData, ',');
|
||||||
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
|
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
|
||||||
log.info("添加userKey,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
|
log.info("灰度配置,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
|
||||||
|
|
||||||
List<String> list = Stream.of(userKeys).collect(Collectors.toList());
|
Set<String> userKeySet = Stream.of(userKeys)
|
||||||
ServiceGrayConfig serviceGrayConfig = getServiceGrayConfig(instanceId);
|
.collect(Collectors.toCollection(Sets::newConcurrentHashSet));
|
||||||
serviceGrayConfig.setServiceId(configGrayUserkey.getServiceId());
|
|
||||||
serviceGrayConfig.getUserKeys().addAll(list);
|
|
||||||
|
|
||||||
Map<String, String> grayNameVersion = serviceGrayConfig.getGrayNameVersion();
|
Map<String, String> grayNameVersionMap = Stream.of(nameVersionList)
|
||||||
for (String nameVersion : nameVersionList) {
|
.map(nameVersion -> StringUtils.split(nameVersion, '='))
|
||||||
String[] nameVersionInfo = StringUtils.split(nameVersion, '=');
|
.collect(Collectors.toConcurrentMap(FUNCTION_KEY, FUNCTION_VALUE));
|
||||||
String name = nameVersionInfo[0];
|
|
||||||
String version = nameVersionInfo[1];
|
|
||||||
grayNameVersion.put(name, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
|
||||||
|
serviceGrayConfig.setServiceId(configGray.getServiceId());
|
||||||
/**
|
serviceGrayConfig.setUserKeys(userKeySet);
|
||||||
* 清空用户key
|
serviceGrayConfig.setGrayNameVersion(grayNameVersionMap);
|
||||||
*/
|
this.saveServiceGrayConfig(serviceGrayConfig);
|
||||||
public void clear(String instanceId) {
|
|
||||||
getServiceGrayConfig(instanceId).clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
protected void after() throws Exception {
|
protected void after() throws Exception {
|
||||||
ZookeeperContext.setEnvironment(environment);
|
ZookeeperContext.setEnvironment(environment);
|
||||||
String isvChannelPath = ZookeeperContext.getUserKeyChannelPath();
|
String isvChannelPath = ZookeeperContext.getServiceGrayChannelPath();
|
||||||
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
|
ZookeeperContext.listenPath(isvChannelPath, nodeCache -> {
|
||||||
String nodeData = new String(nodeCache.getCurrentData().getData());
|
String nodeData = new String(nodeCache.getCurrentData().getData());
|
||||||
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
ChannelMsg channelMsg = JSON.parseObject(nodeData, ChannelMsg.class);
|
||||||
String data = channelMsg.getData();
|
String data = channelMsg.getData();
|
||||||
UserKeyDefinition userKeyDefinition = JSON.parseObject(data, UserKeyDefinition.class);
|
ServiceGrayDefinition userKeyDefinition = JSON.parseObject(data, ServiceGrayDefinition.class);
|
||||||
String instanceId = userKeyDefinition.getInstanceId();
|
String serviceId = userKeyDefinition.getServiceId();
|
||||||
switch (channelMsg.getOperation()) {
|
switch (channelMsg.getOperation()) {
|
||||||
case "set":
|
case "set":
|
||||||
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", instanceId);
|
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
||||||
this.setServiceGrayConfig(configGrayUserkey);
|
this.setServiceGrayConfig(configGray);
|
||||||
break;
|
break;
|
||||||
case "clear":
|
case "open":
|
||||||
clear(instanceId);
|
openGray(userKeyDefinition.getInstanceId(), serviceId);
|
||||||
|
break;
|
||||||
|
case "close":
|
||||||
|
closeGray(userKeyDefinition.getInstanceId());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("userKey消息,错误的消息指令,nodeData:{}", nodeData);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -56,7 +56,6 @@ public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
|
|||||||
remove(ip);
|
remove(ip);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("IP黑名单,错误的消息指令,nodeData:{}", nodeData);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,6 @@ public class DbIsvManager extends CacheIsvManager {
|
|||||||
remove(isvDefinition.getAppKey());
|
remove(isvDefinition.getAppKey());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("ISV信息,错误的消息指令,nodeData:{}", nodeData);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -152,6 +152,8 @@ public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManage
|
|||||||
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
||||||
remove(isvRoutePermission.getAppKey());
|
remove(isvRoutePermission.getAppKey());
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,6 @@ public class DbLimitConfigManager extends DefaultLimitConfigManager {
|
|||||||
update(configLimitDto);
|
update(configLimitDto);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("限流配置信息,错误的消息指令,nodeData:{}", nodeData);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -87,7 +87,6 @@ public class DbRouteConfigManager extends DefaultRouteConfigManager {
|
|||||||
update(routeConfig);
|
update(routeConfig);
|
||||||
break;
|
break;
|
||||||
default:
|
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
|
zookeeper.url=localhost:2181
|
||||||
# zipkin服务监控地址,没有开启不用改
|
# zipkin服务监控地址,没有开启不用改
|
||||||
zipkin.url=http://127.0.0.1:9411/
|
zipkin.url=http://127.0.0.1:9411/
|
||||||
|
|
||||||
|
# 预发布网关域名
|
||||||
|
pre.domain=localhost
|
||||||
# ------- 需要改的配置end -------
|
# ------- 需要改的配置end -------
|
||||||
|
|
||||||
# 入口地址,不用改,默认是/zuul
|
# 入口地址,不用改,默认是/zuul
|
||||||
|
27
sop.sql
27
sop.sql
@@ -15,7 +15,8 @@ DROP TABLE IF EXISTS `admin_user_info`;
|
|||||||
DROP TABLE IF EXISTS `config_common`;
|
DROP TABLE IF EXISTS `config_common`;
|
||||||
DROP TABLE IF EXISTS `isv_keys`;
|
DROP TABLE IF EXISTS `isv_keys`;
|
||||||
DROP TABLE IF EXISTS `config_ip_blacklist`;
|
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` (
|
CREATE TABLE `admin_user_info` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
@@ -176,17 +177,29 @@ CREATE TABLE `config_ip_blacklist` (
|
|||||||
UNIQUE KEY `uk_ip` (`ip`) USING BTREE
|
UNIQUE KEY `uk_ip` (`ip`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP黑名单';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='IP黑名单';
|
||||||
|
|
||||||
CREATE TABLE `config_gray_userkey` (
|
CREATE TABLE `config_gray` (
|
||||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
`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,多个用引文逗号隔开',
|
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开',
|
||||||
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=1.2',
|
`name_version_content` text COMMENT '需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开',
|
||||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '0:禁用,1:启用',
|
|
||||||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_instanceid` (`instance_id`) USING BTREE
|
UNIQUE KEY `uk_serviceid` (`service_id`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='灰度发布用户key';
|
) 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='开启灰度服务器实例';
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;
|
SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user