parent
6125625b87
commit
202267b686
@ -0,0 +1,31 @@ |
|||||||
|
# 预发布灰度发布 |
||||||
|
|
||||||
|
从1.14.0开始支持预发布、灰度发布,可登陆`SOP-Admin`,然后选择`服务列表`进行操作。 |
||||||
|
|
||||||
|
## 使用预发布 |
||||||
|
|
||||||
|
假设网关工程在阿里云负载均衡有两台服务器,域名分别为: |
||||||
|
|
||||||
|
|域名|说明| |
||||||
|
|:---- |:---- | |
||||||
|
|open1.domain.com |网关服务器1 | |
||||||
|
|openpre.domain.com | 网关服务器2,作为预发布请求入口| |
||||||
|
|
||||||
|
线上域名为`open.domain.com`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器 |
||||||
|
|
||||||
|
在网关工程打开`com.gitee.sop.gateway.loadbalancer.EnvironmentServerChooser`类,修改`PRE_DOMAIN`变量 |
||||||
|
|
||||||
|
```java |
||||||
|
/** |
||||||
|
* 预发布机器域名 |
||||||
|
*/ |
||||||
|
private static final String PRE_DOMAIN = "openpre.domain.com"; |
||||||
|
``` |
||||||
|
|
||||||
|
网关工程打包发布到阿里云 |
||||||
|
|
||||||
|
登录SOP-Admin,在服务列表中点击预发布,然后接口的请求地址变成:`http://openpre.domain.com/api` |
||||||
|
|
||||||
|
## 使用灰度发布 |
||||||
|
|
||||||
|
灰度发布可允许指定的用户进行访问,其它用户则走正常流程。 |
@ -0,0 +1,12 @@ |
|||||||
|
use sop; |
||||||
|
|
||||||
|
CREATE TABLE `config_gray_userkey` ( |
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, |
||||||
|
`instance_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'serviceId', |
||||||
|
`user_key_content` text COMMENT '用户key,多个用引文逗号隔开', |
||||||
|
`name_version_content` text COMMENT '需要灰度的接口,goods.get=1.2,order.list=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'; |
@ -1,12 +1,16 @@ |
|||||||
package com.gitee.sop.adminserver; |
package com.gitee.sop.adminserver; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.FastmybatisConfig; |
||||||
import org.springframework.boot.SpringApplication; |
import org.springframework.boot.SpringApplication; |
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
@SpringBootApplication |
@SpringBootApplication |
||||||
public class SopAdminServerApplication { |
public class SopAdminServerApplication { |
||||||
|
|
||||||
public static void main(String[] args) { |
public static void main(String[] args) { |
||||||
|
FastmybatisConfig.defaultIgnoreUpdateColumns = Arrays.asList("gmt_create", "gmt_modified"); |
||||||
SpringApplication.run(SopAdminServerApplication.class, args); |
SpringApplication.run(SopAdminServerApplication.class, args); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,26 @@ |
|||||||
|
package com.gitee.sop.adminserver.api.service.param; |
||||||
|
|
||||||
|
import com.gitee.easyopen.doc.annotation.ApiDocField; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.EqualsAndHashCode; |
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@EqualsAndHashCode(callSuper = true) |
||||||
|
@Data |
||||||
|
public class ServiceInstanceGrayParam extends ServiceInstanceParam { |
||||||
|
|
||||||
|
@ApiDocField(description = "灰度发布用户,多个用英文逗号隔开") |
||||||
|
@NotBlank(message = "灰度发布用户不能为空") |
||||||
|
private String userKeyContent; |
||||||
|
|
||||||
|
@ApiDocField(description = "灰度发布接口名版本号如:order.get1.0=1.2,多个用英文逗号隔开") |
||||||
|
@NotBlank(message = "灰度发布接口名版本号不能为空") |
||||||
|
private String nameVersionContent; |
||||||
|
|
||||||
|
@ApiDocField(description = "是否仅更新灰度用户") |
||||||
|
private Boolean onlyUpdateGrayUserkey; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.adminserver.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class UserKeyDefinition { |
||||||
|
private String serviceId; |
||||||
|
private String data; |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
package com.gitee.sop.adminserver.common; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public enum ChannelOperation { |
||||||
|
/** |
||||||
|
* 限流推送路由配置-修改 |
||||||
|
*/ |
||||||
|
LIMIT_CONFIG_UPDATE("update"), |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由信息更新 |
||||||
|
*/ |
||||||
|
ROUTE_CONFIG_UPDATE("update"), |
||||||
|
|
||||||
|
/** |
||||||
|
* isv信息修改 |
||||||
|
*/ |
||||||
|
ISV_INFO_UPDATE("update"), |
||||||
|
|
||||||
|
/** |
||||||
|
* 黑名单消息类型:添加 |
||||||
|
*/ |
||||||
|
BLACKLIST_ADD("add"), |
||||||
|
/** |
||||||
|
* 黑名单消息类型:删除 |
||||||
|
*/ |
||||||
|
BLACKLIST_DELETE("delete"), |
||||||
|
|
||||||
|
/** |
||||||
|
* 路由权限配置更新 |
||||||
|
*/ |
||||||
|
ROUTE_PERMISSION_UPDATE("update"), |
||||||
|
/** |
||||||
|
* 路由权限加载 |
||||||
|
*/ |
||||||
|
ROUTE_PERMISSION_RELOAD("reload"), |
||||||
|
|
||||||
|
/** |
||||||
|
* 灰度发布用户key设置 |
||||||
|
*/ |
||||||
|
GRAY_USER_KEY_SET("set"), |
||||||
|
/** |
||||||
|
* 灰度发布用户key清除 |
||||||
|
*/ |
||||||
|
GRAY_USER_KEY_CLEAR("clear"), |
||||||
|
|
||||||
|
; |
||||||
|
|
||||||
|
private String operation; |
||||||
|
|
||||||
|
ChannelOperation(String operation) { |
||||||
|
this.operation = operation; |
||||||
|
} |
||||||
|
|
||||||
|
public String getOperation() { |
||||||
|
return operation; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package com.gitee.sop.adminserver.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:config_gray_userkey |
||||||
|
* 备注:灰度发布用户key |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "config_gray_userkey") |
||||||
|
@Data |
||||||
|
public class ConfigGrayUserkey { |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
/** 数据库字段:id */ |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** instanceId, 数据库字段: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; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.adminserver.mapper; |
||||||
|
|
||||||
|
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||||
|
|
||||||
|
import com.gitee.sop.adminserver.entity.ConfigGrayUserkey; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface ConfigGrayUserkeyMapper extends CrudMapper<ConfigGrayUserkey, Long> { |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import org.springframework.beans.BeansException; |
||||||
|
import org.springframework.context.ApplicationContext; |
||||||
|
import org.springframework.context.ApplicationContextAware; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class SpringContext implements ApplicationContextAware { |
||||||
|
|
||||||
|
private static ApplicationContext ctx; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
||||||
|
ctx = applicationContext; |
||||||
|
} |
||||||
|
|
||||||
|
public static <T> T getBean(Class<T> clazz) { |
||||||
|
return ctx.getBean(clazz); |
||||||
|
} |
||||||
|
|
||||||
|
public static Object getBean(String beanName) { |
||||||
|
return ctx.getBean(beanName); |
||||||
|
} |
||||||
|
|
||||||
|
public static ApplicationContext getApplicationContext() { |
||||||
|
return ctx; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class UserKeyDefinition { |
||||||
|
private String serviceId; |
||||||
|
private String data; |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package com.gitee.sop.gateway.entity; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import javax.persistence.Column; |
||||||
|
import javax.persistence.GeneratedValue; |
||||||
|
import javax.persistence.GenerationType; |
||||||
|
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 表名:config_gray_userkey |
||||||
|
* 备注:灰度发布用户key |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Table(name = "config_gray_userkey") |
||||||
|
@Data |
||||||
|
public class ConfigGrayUserkey { |
||||||
|
@Id |
||||||
|
@Column(name = "id") |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
/** 数据库字段:id */ |
||||||
|
private Long id; |
||||||
|
|
||||||
|
/** instanceId, 数据库字段: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; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_create */ |
||||||
|
private Date gmtCreate; |
||||||
|
|
||||||
|
/** 数据库字段:gmt_modified */ |
||||||
|
private Date gmtModified; |
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
package com.gitee.sop.gateway.loadbalancer; |
||||||
|
|
||||||
|
import com.gitee.sop.gateway.manager.UserKeyManager; |
||||||
|
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.ParamNames; |
||||||
|
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 com.netflix.zuul.context.RequestContext; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class EnvironmentServerChooser extends BaseServerChooser { |
||||||
|
|
||||||
|
private static final String MEDATA_KEY_ENV = "env"; |
||||||
|
private static final String ENV_PRE_VALUE = "pre"; |
||||||
|
private static final String ENV_GRAY_VALUE = "gray"; |
||||||
|
/** |
||||||
|
* 预发布机器域名 |
||||||
|
*/ |
||||||
|
private static final String PRE_DOMAIN = "localhost"; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean match(Server server) { |
||||||
|
// eureka存储的metadata
|
||||||
|
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); |
||||||
|
String env = metadata.get(MEDATA_KEY_ENV); |
||||||
|
return StringUtils.isNotBlank(env); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 这里判断客户端能否访问,可以根据ip地址,域名,header内容来决定是否可以访问预发布环境 |
||||||
|
* |
||||||
|
* @param server 服务器实例 |
||||||
|
* @param request request |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected boolean canVisit(Server server, HttpServletRequest request) { |
||||||
|
// eureka存储的metadata
|
||||||
|
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); |
||||||
|
String env = metadata.get(MEDATA_KEY_ENV); |
||||||
|
boolean canVisit; |
||||||
|
switch (env) { |
||||||
|
case ENV_PRE_VALUE: |
||||||
|
canVisit = canVisitPre(server, request); |
||||||
|
break; |
||||||
|
case ENV_GRAY_VALUE: |
||||||
|
canVisit = canVisitGray(server, request); |
||||||
|
break; |
||||||
|
default: |
||||||
|
canVisit = false; |
||||||
|
} |
||||||
|
return canVisit; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 通过判断hostname来确定是否是预发布请求,可修改此方法实现自己想要的 |
||||||
|
* |
||||||
|
* @param request request |
||||||
|
* @return 返回true:可以进入到预发环境 |
||||||
|
*/ |
||||||
|
protected boolean canVisitPre(Server server, HttpServletRequest request) { |
||||||
|
String serverName = request.getServerName(); |
||||||
|
return PRE_DOMAIN.equals(serverName); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 能否进入灰度环境,可修改此方法实现自己想要的 |
||||||
|
* |
||||||
|
* @param request request |
||||||
|
* @return 返回true:可以进入到预发环境 |
||||||
|
*/ |
||||||
|
protected boolean canVisitGray(Server server, HttpServletRequest request) { |
||||||
|
ApiParam apiParam = ZuulContext.getApiParam(); |
||||||
|
UserKeyManager userKeyManager = SpringContext.getBean(UserKeyManager.class); |
||||||
|
boolean canVisit = false; |
||||||
|
if (this.isGrayUser(apiParam, userKeyManager, server, request)) { |
||||||
|
// 指定灰度版本号
|
||||||
|
String instanceId = server.getId(); |
||||||
|
String newVersion = userKeyManager.getVersion(instanceId, apiParam.fetchNameVersion()); |
||||||
|
if (newVersion != null) { |
||||||
|
RequestContext.getCurrentContext().getZuulRequestHeaders().put(ParamNames.HEADER_VERSION_NAME, newVersion); |
||||||
|
canVisit = true; |
||||||
|
} |
||||||
|
} |
||||||
|
return canVisit; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 是否是灰度用户 |
||||||
|
* |
||||||
|
* @param param 接口参数 |
||||||
|
* @param userKeyManager userKey管理 |
||||||
|
* @param server 服务器实例 |
||||||
|
* @param request request |
||||||
|
* @return true:是 |
||||||
|
*/ |
||||||
|
protected boolean isGrayUser(Param param, UserKeyManager userKeyManager, Server server, HttpServletRequest request) { |
||||||
|
String instanceId = server.getId(); |
||||||
|
// 这里的灰度用户为appKey,包含此appKey则为灰度用户,允许访问
|
||||||
|
String appKey = param.fetchAppKey(); |
||||||
|
return userKeyManager.containsKey(instanceId, appKey); |
||||||
|
} |
||||||
|
} |
@ -1,55 +0,0 @@ |
|||||||
package com.gitee.sop.gateway.loadbalancer; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser; |
|
||||||
import com.netflix.loadbalancer.Server; |
|
||||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; |
|
||||||
import org.apache.commons.lang3.StringUtils; |
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Objects; |
|
||||||
|
|
||||||
/** |
|
||||||
* 预发布环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public class PreEnvironmentServerChooser extends BaseServerChooser { |
|
||||||
|
|
||||||
private static final String MEDATA_KEY_ENV = "env"; |
|
||||||
private static final String ENV_PRE_VALUE = "pre"; |
|
||||||
|
|
||||||
private static final String HOST_NAME = "localhost"; |
|
||||||
|
|
||||||
@Override |
|
||||||
protected boolean match(Server server) { |
|
||||||
// eureka存储的metadata
|
|
||||||
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); |
|
||||||
String env = metadata.get(MEDATA_KEY_ENV); |
|
||||||
return StringUtils.isNotBlank(env); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 这里判断客户端能否访问,可以根据ip地址,域名,header内容来决定是否可以访问预发布环境 |
|
||||||
* @param server 服务器实例 |
|
||||||
* @param request request |
|
||||||
* @return |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
protected boolean canVisit(Server server, HttpServletRequest request) { |
|
||||||
// eureka存储的metadata
|
|
||||||
Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); |
|
||||||
String env = metadata.get(MEDATA_KEY_ENV); |
|
||||||
return Objects.equals(ENV_PRE_VALUE, env) && canClientVisit(request); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 通过判断hostname来确定是否是预发布请求,如果需要通过其它条件判断,修改此方法 |
|
||||||
* @param request request |
|
||||||
* @return 返回true:可以进入到预发环境 |
|
||||||
*/ |
|
||||||
protected boolean canClientVisit(HttpServletRequest request) { |
|
||||||
String serverName = request.getServerName(); |
|
||||||
return HOST_NAME.equals(serverName); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,33 @@ |
|||||||
|
package com.gitee.sop.gateway.loadbalancer; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
public class ServiceGrayConfig { |
||||||
|
/** |
||||||
|
* 用户id |
||||||
|
*/ |
||||||
|
private Set<String> userKeys; |
||||||
|
|
||||||
|
/** 存放接口隐射关系,key:nameversion,value:newVersion */ |
||||||
|
private Map<String, String> grayNameVersion; |
||||||
|
|
||||||
|
public boolean containsKey(Object userKey) { |
||||||
|
return userKeys.contains(String.valueOf(userKey)); |
||||||
|
} |
||||||
|
|
||||||
|
public String getVersion(String name) { |
||||||
|
return grayNameVersion.get(name); |
||||||
|
} |
||||||
|
|
||||||
|
public void clear() { |
||||||
|
this.userKeys.clear(); |
||||||
|
this.grayNameVersion.clear(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
package com.gitee.sop.gateway.manager; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.gitee.sop.gateway.entity.ConfigGrayUserkey; |
||||||
|
import com.gitee.sop.gateway.loadbalancer.ServiceGrayConfig; |
||||||
|
import com.gitee.sop.gateway.mapper.ConfigGrayUserkeyMapper; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ChannelMsg; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.UserKeyDefinition; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.ZookeeperContext; |
||||||
|
import com.google.common.collect.Maps; |
||||||
|
import com.google.common.collect.Sets; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.core.env.Environment; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
/** |
||||||
|
* 存放用户key,这里放在本机内容,如果灰度发布保存的用户id数量偏多,可放在redis中 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
@Slf4j |
||||||
|
public class UserKeyManager { |
||||||
|
|
||||||
|
/** |
||||||
|
* KEY:instanceId |
||||||
|
*/ |
||||||
|
private Map<String, ServiceGrayConfig> serviceUserKeyMap = Maps.newConcurrentMap(); |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private Environment environment; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ConfigGrayUserkeyMapper configGrayUserkeyMapper; |
||||||
|
|
||||||
|
public boolean containsKey(String serviceId, Object userKey) { |
||||||
|
if (serviceId == null || userKey == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return this.getServiceGrayConfig(serviceId).containsKey(userKey); |
||||||
|
} |
||||||
|
|
||||||
|
public String getVersion(String serviceId, String nameVersion) { |
||||||
|
if (serviceId == null || nameVersion == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return this.getServiceGrayConfig(serviceId).getVersion(nameVersion); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置用户key |
||||||
|
* |
||||||
|
* @param configGrayUserkey 灰度配置 |
||||||
|
*/ |
||||||
|
public void setServiceGrayConfig(String serviceId, ConfigGrayUserkey configGrayUserkey) { |
||||||
|
if (configGrayUserkey == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.clear(serviceId); |
||||||
|
String userKeyData = configGrayUserkey.getUserKeyContent(); |
||||||
|
String nameVersionContent = configGrayUserkey.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(serviceId); |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清空用户key |
||||||
|
*/ |
||||||
|
public void clear(String serviceId) { |
||||||
|
getServiceGrayConfig(serviceId).clear(); |
||||||
|
} |
||||||
|
|
||||||
|
public ServiceGrayConfig getServiceGrayConfig(String serviceId) { |
||||||
|
ServiceGrayConfig serviceGrayConfig = serviceUserKeyMap.get(serviceId); |
||||||
|
if (serviceGrayConfig == null) { |
||||||
|
serviceGrayConfig = new ServiceGrayConfig(); |
||||||
|
serviceGrayConfig.setUserKeys(Sets.newConcurrentHashSet()); |
||||||
|
serviceGrayConfig.setGrayNameVersion(Maps.newConcurrentMap()); |
||||||
|
serviceUserKeyMap.put(serviceId, serviceGrayConfig); |
||||||
|
} |
||||||
|
return serviceGrayConfig; |
||||||
|
} |
||||||
|
|
||||||
|
@PostConstruct |
||||||
|
protected void after() throws Exception { |
||||||
|
ZookeeperContext.setEnvironment(environment); |
||||||
|
String isvChannelPath = ZookeeperContext.getUserKeyChannelPath(); |
||||||
|
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 serviceId = userKeyDefinition.getServiceId(); |
||||||
|
switch (channelMsg.getOperation()) { |
||||||
|
case "set": |
||||||
|
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("service_id", serviceId); |
||||||
|
this.setServiceGrayConfig(serviceId, configGrayUserkey); |
||||||
|
break; |
||||||
|
case "clear": |
||||||
|
clear(serviceId); |
||||||
|
break; |
||||||
|
default: |
||||||
|
log.error("userKey消息,错误的消息指令,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.ConfigGrayUserkey; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface ConfigGrayUserkeyMapper extends CrudMapper<ConfigGrayUserkey, Long> { |
||||||
|
} |
Loading…
Reference in new issue