支持预发布、灰度发布

1.x
tanghc 5 years ago
parent 91ffd3fa55
commit 17602e6efe
  1. 85
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  2. 25
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/ServiceGrayConfigParam.java
  3. 16
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/param/ServiceIdParam.java
  4. 42
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigGray.java
  5. 41
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/entity/ConfigGrayInstance.java
  6. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigGrayInstanceMapper.java
  7. 12
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/mapper/ConfigGrayMapper.java
  8. 106
      sop-admin/sop-admin-vue/src/views/service/serviceList.vue
  9. 15
      sop-gateway/src/main/java/com/gitee/sop/gateway/loadbalancer/EnvironmentServerChooser.java

@ -6,7 +6,8 @@ import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc; import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocMethod; import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.sop.adminserver.api.service.param.ServiceAddParam; import com.gitee.sop.adminserver.api.service.param.ServiceAddParam;
import com.gitee.sop.adminserver.api.service.param.ServiceInstanceGrayParam; import com.gitee.sop.adminserver.api.service.param.ServiceGrayConfigParam;
import com.gitee.sop.adminserver.api.service.param.ServiceIdParam;
import com.gitee.sop.adminserver.api.service.param.ServiceInstanceParam; import com.gitee.sop.adminserver.api.service.param.ServiceInstanceParam;
import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam; import com.gitee.sop.adminserver.api.service.param.ServiceSearchParam;
import com.gitee.sop.adminserver.api.service.result.RouteServiceInfo; import com.gitee.sop.adminserver.api.service.result.RouteServiceInfo;
@ -22,7 +23,11 @@ import com.gitee.sop.adminserver.common.ChannelOperation;
import com.gitee.sop.adminserver.common.StatusEnum; import com.gitee.sop.adminserver.common.StatusEnum;
import com.gitee.sop.adminserver.common.ZookeeperPathExistException; import com.gitee.sop.adminserver.common.ZookeeperPathExistException;
import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException; import com.gitee.sop.adminserver.common.ZookeeperPathNotExistException;
import com.gitee.sop.adminserver.entity.ConfigGray;
import com.gitee.sop.adminserver.entity.ConfigGrayInstance;
import com.gitee.sop.adminserver.entity.ConfigGrayUserkey; import com.gitee.sop.adminserver.entity.ConfigGrayUserkey;
import com.gitee.sop.adminserver.mapper.ConfigGrayInstanceMapper;
import com.gitee.sop.adminserver.mapper.ConfigGrayMapper;
import com.gitee.sop.adminserver.mapper.ConfigGrayUserkeyMapper; import com.gitee.sop.adminserver.mapper.ConfigGrayUserkeyMapper;
import com.gitee.sop.registryapi.bean.ServiceInfo; import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance; import com.gitee.sop.registryapi.bean.ServiceInstance;
@ -57,6 +62,12 @@ public class ServiceApi {
@Autowired @Autowired
private ConfigGrayUserkeyMapper configGrayUserkeyMapper; private ConfigGrayUserkeyMapper configGrayUserkeyMapper;
@Autowired
private ConfigGrayMapper configGrayMapper;
@Autowired
private ConfigGrayInstanceMapper configGrayInstanceMapper;
@Api(name = "zookeeper.service.list") @Api(name = "zookeeper.service.list")
@ApiDocMethod(description = "zk中的服务列表", elementClass = RouteServiceInfo.class) @ApiDocMethod(description = "zk中的服务列表", elementClass = RouteServiceInfo.class)
List<RouteServiceInfo> listServiceInfo(ServiceSearchParam param) { List<RouteServiceInfo> listServiceInfo(ServiceSearchParam param) {
@ -189,7 +200,7 @@ public class ServiceApi {
} }
} }
@Api(name = "service.instance.env.pre") @Api(name = "service.instance.env.pre.open")
@ApiDocMethod(description = "预发布") @ApiDocMethod(description = "预发布")
void serviceEnvPre(ServiceInstanceParam param) throws IOException { void serviceEnvPre(ServiceInstanceParam param) throws IOException {
try { try {
@ -201,35 +212,51 @@ public class ServiceApi {
} }
} }
@Api(name = "service.instance.env.gray") @Api(name = "service.gray.config.get")
@ApiDocMethod(description = "灰度发布") @ApiDocMethod(description = "灰度配置--获取")
void serviceEnvGray(ServiceInstanceGrayParam param) throws IOException { ConfigGray serviceEnvGrayConfigGet(ServiceIdParam param) throws IOException {
String serviceId = param.getServiceId();
return configGrayMapper.getByColumn("service_id", serviceId);
}
@Api(name = "service.gray.config.save")
@ApiDocMethod(description = "灰度配置--保存")
void serviceEnvGrayConfigSave(ServiceGrayConfigParam param) throws IOException {
String serviceId = param.getServiceId();
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
if (configGray == null) {
configGray = new ConfigGray();
configGray.setServiceId(serviceId);
configGray.setNameVersionContent(param.getNameVersionContent());
configGray.setUserKeyContent(param.getUserKeyContent());
configGrayMapper.save(configGray);
} else {
configGray.setNameVersionContent(param.getNameVersionContent());
configGray.setUserKeyContent(param.getUserKeyContent());
configGrayMapper.update(configGray);
}
}
@Api(name = "service.instance.env.gray.open")
@ApiDocMethod(description = "开启灰度发布")
void serviceEnvGray(ServiceInstanceParam param) throws IOException {
try { try {
Boolean onlyUpdateGrayUserkey = param.getOnlyUpdateGrayUserkey(); MetadataEnum envPre = MetadataEnum.ENV_GRAY;
if (onlyUpdateGrayUserkey == null || !onlyUpdateGrayUserkey) { registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
MetadataEnum envPre = MetadataEnum.ENV_GRAY;
registryService.setMetadata(param.buildServiceInstance(), envPre.getKey(), envPre.getValue());
}
String serviceId = param.getServiceId(); String serviceId = param.getServiceId();
String instanceId = param.getInstanceId(); String instanceId = param.getInstanceId();
String userKeyContent = param.getUserKeyContent();
String nameVersionContent = param.getNameVersionContent();
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", instanceId); ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
if (configGrayUserkey == null) { if (configGrayInstance == null) {
configGrayUserkey = new ConfigGrayUserkey(); configGrayInstance = new ConfigGrayInstance();
configGrayUserkey.setServiceId(serviceId); configGrayInstance.setServiceId(serviceId);
configGrayUserkey.setInstanceId(instanceId); configGrayInstance.setInstanceId(instanceId);
configGrayUserkey.setUserKeyContent(userKeyContent); configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkey.setNameVersionContent(nameVersionContent); configGrayInstanceMapper.save(configGrayInstance);
configGrayUserkey.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkeyMapper.save(configGrayUserkey);
} else { } else {
configGrayUserkey.setUserKeyContent(userKeyContent); configGrayInstance.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkey.setNameVersionContent(nameVersionContent); configGrayInstanceMapper.update(configGrayInstance);
configGrayUserkey.setStatus(StatusEnum.STATUS_ENABLE.getStatus());
configGrayUserkeyMapper.update(configGrayUserkey);
} }
this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET); this.sendUserKeyMsg(instanceId, ChannelOperation.GRAY_USER_KEY_SET);
} catch (Exception e) { } catch (Exception e) {
@ -245,10 +272,10 @@ public class ServiceApi {
MetadataEnum envPre = MetadataEnum.ENV_ONLINE; MetadataEnum envPre = MetadataEnum.ENV_ONLINE;
registryService.setMetadata(param, envPre.getKey(), envPre.getValue()); registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
ConfigGrayUserkey configGrayUserkey = configGrayUserkeyMapper.getByColumn("instance_id", param.getInstanceId()); ConfigGrayInstance configGrayInstance = configGrayInstanceMapper.getByColumn("instance_id", param.getInstanceId());
if (configGrayUserkey != null && configGrayUserkey.getStatus() == StatusEnum.STATUS_ENABLE.getStatus()) { if (configGrayInstance != null) {
configGrayUserkey.setStatus(StatusEnum.STATUS_DISABLE.getStatus()); configGrayInstance.setStatus(StatusEnum.STATUS_DISABLE.getStatus());
configGrayUserkeyMapper.update(configGrayUserkey); configGrayInstanceMapper.update(configGrayInstance);
} }
this.sendUserKeyMsg(param.getInstanceId(), ChannelOperation.GRAY_USER_KEY_CLEAR); this.sendUserKeyMsg(param.getInstanceId(), ChannelOperation.GRAY_USER_KEY_CLEAR);
} catch (Exception e) { } catch (Exception e) {

@ -0,0 +1,25 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Data
public class ServiceGrayConfigParam {
@ApiDocField(description = "服务名serviceId")
@NotBlank(message = "serviceId不能为空")
private String serviceId;
@ApiDocField(description = "灰度发布用户,多个用英文逗号隔开")
@NotBlank(message = "灰度发布用户不能为空")
private String userKeyContent;
@ApiDocField(description = "灰度发布接口名版本号如:order.get1.0=1.2,多个用英文逗号隔开")
@NotBlank(message = "灰度发布接口名版本号不能为空")
private String nameVersionContent;
}

@ -0,0 +1,16 @@
package com.gitee.sop.adminserver.api.service.param;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author tanghc
*/
@Data
public class ServiceIdParam {
@ApiDocField(description = "serviceId")
@NotBlank(message = "serviceId不能为空")
private String serviceId;
}

@ -0,0 +1,42 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名config_gray
* 备注服务灰度配置
*
* @author tanghc
*/
@Table(name = "config_gray")
@Data
public class ConfigGray {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** 数据库字段:service_id */
private String serviceId;
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
private String userKeyContent;
/** 需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段:name_version_content */
private String nameVersionContent;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,41 @@
package com.gitee.sop.adminserver.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名config_gray_instance
*
* @author tanghc
*/
@Table(name = "config_gray_instance")
@Data
public class ConfigGrayInstance {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** instance_id, 数据库字段:instance_id */
private String instanceId;
/** service_id, 数据库字段:service_id */
private String serviceId;
/** 0:禁用,1:启用, 数据库字段:status */
private Byte status;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.ConfigGrayInstance;
/**
* @author tanghc
*/
public interface ConfigGrayInstanceMapper extends CrudMapper<ConfigGrayInstance, Long> {
}

@ -0,0 +1,12 @@
package com.gitee.sop.adminserver.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.adminserver.entity.ConfigGray;
/**
* @author tanghc
*/
public interface ConfigGrayMapper extends CrudMapper<ConfigGray, Long> {
}

@ -67,13 +67,14 @@
/> />
<el-table-column <el-table-column
label="操作" label="操作"
width="200" width="250"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-button v-if="scope.row.parentId > 0 && scope.row.metadata.env" type="text" size="mini" @click="onEnvOnline(scope.row)">上线</el-button> <el-button v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'pre'" type="text" size="mini" @click="onEnvPreClose(scope.row)">结束预发布</el-button>
<el-button v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="text" size="mini" @click="onEnvPre(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="onEnvGray(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 === 'gray'" type="text" size="mini" @click="onUpdateUserkey(scope.row)">灰度设置</el-button> <el-button v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="text" size="mini" @click="onEnvGrayOpen(scope.row)">开启灰度</el-button>
<el-button v-if="scope.row.parentId === 0" type="text" size="mini" @click="onGrayConfigUpdate(scope.row)">灰度设置</el-button>
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="text" size="mini" @click="onDisable(scope.row)">禁用</el-button> <el-button v-if="scope.row.parentId > 0 && scope.row.status === '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>
@ -92,8 +93,8 @@
:rules="grayFormRules" :rules="grayFormRules"
size="mini" size="mini"
> >
<el-form-item label="服务器实例"> <el-form-item label="serviceId">
{{ grayForm.serviceId + ' (' + grayForm.ipPort + ')' }} {{ grayForm.serviceId }}
</el-form-item> </el-form-item>
<el-tabs v-model="tabsActiveName" type="card"> <el-tabs v-model="tabsActiveName" type="card">
<el-tab-pane label="灰度用户" name="first"> <el-tab-pane label="灰度用户" name="first">
@ -172,7 +173,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="grayDialogVisible = false"> </el-button> <el-button @click="grayDialogVisible = false"> </el-button>
<el-button type="primary" @click="onAddUserKey"> </el-button> <el-button type="primary" @click="onGrayConfigSave"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@ -199,8 +200,6 @@ export default {
grayDialogVisible: false, grayDialogVisible: false,
grayForm: { grayForm: {
serviceId: '', serviceId: '',
instanceId: '',
ipPort: '',
userKeyContent: '', userKeyContent: '',
onlyUpdateGrayUserkey: false, onlyUpdateGrayUserkey: false,
grayRouteConfigList: [] grayRouteConfigList: []
@ -280,45 +279,79 @@ export default {
}) })
}) })
}, },
onEnvOnline: function(row) { doEnvOnline: function(row, callback) {
this.confirm('确定要上线【' + row.serviceId + '】吗?', function(done) { this.post('service.instance.env.online', row, function() {
this.post('service.instance.env.online', row, function() { callback && callback()
this.tip('上线成功') })
},
onEnvPreOpen: function(row) {
this.confirm(`确定要开启 ${row.instanceId} 预发布吗?`, function(done) {
this.post('service.instance.env.pre.open', {
serviceId: row.serviceId,
instanceId: row.instanceId
}, function() {
this.tip('预发布成功')
done() done()
}) })
}) })
}, },
onEnvPre: function(row) { onEnvPreClose: function(row) {
this.confirm('确定要预发布【' + row.serviceId + '】吗?', function(done) { this.confirm(`确定要结束 ${row.instanceId} 预发布吗?`, function(done) {
this.post('service.instance.env.pre', row, function() { this.doEnvOnline(row, function() {
this.tip('预发布成功') this.tip('操作成功')
done() done()
}) })
}) })
}, },
onEnvGray: function(row) { onEnvGrayOpen: function(row) {
this.grayForm.onlyUpdateGrayUserkey = false this.confirm(`确定要开启 ${row.instanceId} 灰度吗?`, function(done) {
this.openGrayDialog(row) this.post('service.instance.env.gray.open', {
serviceId: row.serviceId,
instanceId: row.instanceId
}, function() {
this.tip('开启成功')
done()
})
})
}, },
onUpdateUserkey: function(row) { onEnvGrayClose: function(row) {
this.grayForm.onlyUpdateGrayUserkey = true this.confirm(`确定要结束 ${row.instanceId} 灰度吗?`, function(done) {
this.openGrayDialog(row) this.doEnvOnline(row, function() {
this.tip('操作成功')
done()
})
})
}, },
openGrayDialog: function(row) { onGrayConfigUpdate: function(row) {
const serviceId = row.serviceId const serviceId = row.serviceId
this.loadRouteList(serviceId) this.loadRouteList(serviceId)
this.post('service.instance.gray.userkey.get', { instanceId: row.instanceId }, function(resp) { this.post('service.gray.config.get', { serviceId: serviceId }, function(resp) {
this.grayDialogVisible = true this.grayDialogVisible = true
const data = resp.data const data = resp.data
Object.assign(this.grayForm, { Object.assign(this.grayForm, {
serviceId: serviceId, serviceId: serviceId,
instanceId: row.instanceId,
ipPort: row.ipPort,
userKeyContent: data.userKeyContent || '', userKeyContent: data.userKeyContent || '',
grayRouteConfigList: this.createGrayRouteConfigList(data.nameVersionContent) grayRouteConfigList: this.createGrayRouteConfigList(data.nameVersionContent)
}) })
}) })
}, },
onGrayConfigSave: function() {
this.$refs.grayForm.validate((valid) => {
if (valid) {
const nameVersionContents = []
const grayRouteConfigList = this.grayForm.grayRouteConfigList
for (let i = 0; i < grayRouteConfigList.length; i++) {
const config = grayRouteConfigList[i]
nameVersionContents.push(config.oldRouteId + '=' + config.newVersion)
}
this.grayForm.nameVersionContent = nameVersionContents.join(',')
this.post('service.gray.config.save', this.grayForm, function() {
this.grayDialogVisible = false
this.tip('保存成功')
})
}
})
},
createGrayRouteConfigList: function(nameVersionContent) { createGrayRouteConfigList: function(nameVersionContent) {
if (!nameVersionContent) { if (!nameVersionContent) {
return [{ return [{
@ -340,23 +373,6 @@ export default {
} }
return list return list
}, },
onAddUserKey: function() {
this.$refs.grayForm.validate((valid) => {
if (valid) {
const nameVersionContents = []
const grayRouteConfigList = this.grayForm.grayRouteConfigList
for (let i = 0; i < grayRouteConfigList.length; i++) {
const config = grayRouteConfigList[i]
nameVersionContents.push(config.oldRouteId + '=' + config.newVersion)
}
this.grayForm.nameVersionContent = nameVersionContents.join(',')
this.post('service.instance.env.gray', this.grayForm, function() {
this.grayDialogVisible = false
this.tip('灰度发布发成功')
})
}
})
},
onChangeOldRoute: function(config) { onChangeOldRoute: function(config) {
config.newVersion = '' config.newVersion = ''
}, },

@ -60,7 +60,7 @@ public class EnvironmentServerChooser extends BaseServerChooser {
} }
/** /**
* 能否进入灰度环境可修改此方法实现自己想要的 * 能否进入灰度环境
* *
* @param request request * @param request request
* @return 返回true可以进入到预发环境 * @return 返回true可以进入到预发环境
@ -68,23 +68,12 @@ public class EnvironmentServerChooser extends BaseServerChooser {
protected boolean canVisitGray(Server server, HttpServletRequest request) { protected boolean canVisitGray(Server server, HttpServletRequest request) {
ApiParam apiParam = ZuulContext.getApiParam(); ApiParam apiParam = ZuulContext.getApiParam();
DbEnvGrayManager userKeyManager = SpringContext.getBean(DbEnvGrayManager.class); DbEnvGrayManager userKeyManager = SpringContext.getBean(DbEnvGrayManager.class);
boolean canVisit = false;
/*if (this.isGrayUser(apiParam, userKeyManager, server, request)) {
// 指定灰度版本号
String instanceId = server.getMetaInfo().getInstanceId();
String newVersion = userKeyManager.getVersion(instanceId, apiParam.fetchNameVersion());
if (newVersion != null) {
// 在header中设置新的版本号,然后微服务端先获取这个新版本号
RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, newVersion);
canVisit = true;
}
}*/
return this.isGrayUser(apiParam, userKeyManager, server, request); return this.isGrayUser(apiParam, userKeyManager, server, request);
} }
/** /**
* 是否是灰度用户 * 是否是灰度用户可修改此方法实现自己想要的
* *
* @param param 接口参数 * @param param 接口参数
* @param userKeyManager userKey管理 * @param userKeyManager userKey管理

Loading…
Cancel
Save