支持预发布

1.x
tanghc 5 years ago
parent 043b760e03
commit 6125625b87
  1. 48
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/ServiceApi.java
  2. 5
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/service/result/ServiceInstanceVO.java
  3. 37
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/MetadataEnum.java
  4. 73
      sop-admin/sop-admin-vue/src/views/service/serviceList.vue
  5. 1
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  6. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/result/GatewayResultExecutor.java
  7. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/ReadBodyRoutePredicateFactory.java
  8. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorEnum.java
  9. 4
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorFactory.java
  10. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/BaseParamBuilder.java
  11. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java
  12. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/CustomDataNameBuilder.java
  13. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/DefaultDataNameBuilder.java
  14. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutor.java
  15. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiSessionManager.java
  16. 72
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/loadbalancer/BaseServerChooser.java
  17. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/result/ZuulResultExecutor.java
  18. 41
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaInstance.java
  19. 10
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/EurekaUri.java
  20. 21
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/HttpTool.java
  21. 8
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/bean/ServiceInstance.java
  22. 9
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/RegistryService.java
  23. 6
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/impl/RegistryServiceEureka.java
  24. 67
      sop-common/sop-registry-api/src/main/java/com/gitee/sop/registryapi/service/impl/RegistryServiceNacos.java
  25. 2
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorEnum.java
  26. 2
      sop-common/sop-service-common/src/main/resources/i18n/isp/error_en.properties
  27. 2
      sop-common/sop-service-common/src/main/resources/i18n/isp/error_zh_CN.properties
  28. 8
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
  29. 55
      sop-gateway/src/main/java/com/gitee/sop/gateway/loadbalancer/PreEnvironmentServerChooser.java
  30. 39
      sop-gateway/src/main/java/com/gitee/sop/gateway/loadbalancer/SopPropertiesFactory.java
  31. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/common/CustomDataNameBuilder.java
  32. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/common/DefaultDataNameBuilder.java
  33. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java
  34. 16
      sop-test/src/main/java/com/gitee/sop/test/HttpTool.java
  35. 2
      sop-website/website-front/pages/doc/code.html
  36. 2
      sop-website/website-front/pages/doc/docEvent.js
  37. 4
      sop-website/website-server/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java

@ -10,6 +10,7 @@ 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.ServiceInfoVo;
import com.gitee.sop.adminserver.api.service.result.ServiceInstanceVO;
import com.gitee.sop.adminserver.bean.MetadataEnum;
import com.gitee.sop.adminserver.bean.ServiceRouteInfo;
import com.gitee.sop.adminserver.bean.ZookeeperContext;
import com.gitee.sop.adminserver.common.BizException;
@ -144,6 +145,9 @@ public class ServiceApi {
int id = idGen.getAndIncrement();
instanceVO.setId(id);
instanceVO.setParentId(pid);
if (instanceVO.getMetadata() == null) {
instanceVO.setMetadata(Collections.emptyMap());
}
serviceInfoVoList.add(instanceVO);
}
});
@ -152,21 +156,57 @@ public class ServiceApi {
}
@Api(name = "service.instance.offline")
@ApiDocMethod(description = "服务下线")
@ApiDocMethod(description = "服务禁用")
void serviceOffline(ServiceInstance param) {
try {
registryService.offlineInstance(param);
} catch (Exception e) {
log.error("下线失败,param:{}", param, e);
throw new BizException("下线失败,请查看日志");
log.error("服务禁用失败,param:{}", param, e);
throw new BizException("服务禁用失败,请查看日志");
}
}
@Api(name = "service.instance.online")
@ApiDocMethod(description = "服务上线")
@ApiDocMethod(description = "服务启用")
void serviceOnline(ServiceInstance param) throws IOException {
try {
registryService.onlineInstance(param);
} catch (Exception e) {
log.error("服务启用失败,param:{}", param, e);
throw new BizException("服务启用失败,请查看日志");
}
}
@Api(name = "service.instance.env.pre")
@ApiDocMethod(description = "预发布")
void serviceEnvPre(ServiceInstance param) throws IOException {
try {
MetadataEnum envPre = MetadataEnum.ENV_PRE;
registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
} catch (Exception e) {
log.error("预发布失败,param:{}", param, e);
throw new BizException("预发布失败,请查看日志");
}
}
@Api(name = "service.instance.env.gray")
@ApiDocMethod(description = "灰度发布")
void serviceEnvGray(ServiceInstance param) throws IOException {
try {
MetadataEnum envPre = MetadataEnum.ENV_GRAY;
registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
} catch (Exception e) {
log.error("灰度发布失败,param:{}", param, e);
throw new BizException("灰度发布失败,请查看日志");
}
}
@Api(name = "service.instance.env.online")
@ApiDocMethod(description = "上线")
void serviceEnvOnline(ServiceInstance param) throws IOException {
try {
MetadataEnum envPre = MetadataEnum.ENV_ONLINE;
registryService.setMetadata(param, envPre.getKey(), envPre.getValue());
} catch (Exception e) {
log.error("上线失败,param:{}", param, e);
throw new BizException("上线失败,请查看日志");

@ -3,6 +3,8 @@ package com.gitee.sop.adminserver.api.service.result;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import java.util.Map;
/**
* @author tanghc
*/
@ -35,6 +37,9 @@ public class ServiceInstanceVO {
@ApiDocField(description = "parentId")
private Integer parentId;
@ApiDocField(description = "metadata")
private Map<String, String> metadata;
public String getIpPort() {
return ip != null && port > 0 ? ip + ":" + port : "";
}

@ -0,0 +1,37 @@
package com.gitee.sop.adminserver.bean;
/**
* @author tanghc
*/
public enum MetadataEnum {
/**
* 预发布环境
*/
ENV_PRE("env", "pre"),
/**
* 上线环境
*/
ENV_ONLINE("env", ""),
/**
* 灰度环境
*/
ENV_GRAY("env", "gray"),
;
private String key,value;
MetadataEnum(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

@ -28,16 +28,36 @@
label="IP端口"
width="250"
/>
<el-table-column
prop="metadata"
label="当前环境"
width="100"
>
<template slot-scope="scope">
<el-tag v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'pre'" type="warning">预发布</el-tag>
<el-tag v-if="scope.row.parentId > 0 && scope.row.metadata.env === 'gray'" type="info">灰度</el-tag>
<el-tag v-if="scope.row.parentId > 0 && !scope.row.metadata.env" type="success">线上</el-tag>
</template>
</el-table-column>
<el-table-column
prop="metadata"
label="metadata"
width="250"
>
<template slot-scope="scope">
<span v-if="scope.row.parentId > 0">{{ JSON.stringify(scope.row.metadata) }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
label="服务状态"
width="100"
>
<template slot-scope="scope">
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="success">已上线</el-tag>
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="success">启用</el-tag>
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'STARTING'" type="info">正在启动</el-tag>
<el-tag v-if="scope.row.parentId > 0 && scope.row.status === 'UNKNOWN'">未知</el-tag>
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">已下线</el-tag>
<el-tag v-if="scope.row.parentId > 0 && (scope.row.status === 'OUT_OF_SERVICE' || scope.row.status === 'DOWN')" type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column
@ -47,11 +67,14 @@
/>
<el-table-column
label="操作"
width="100"
width="200"
>
<template slot-scope="scope">
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'UP'" type="text" size="mini" @click="onOffline(scope.row)">下线</el-button>
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'OUT_OF_SERVICE'" type="text" size="mini" @click="onOnline(scope.row)">上线</el-button>
<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" type="text" size="mini" @click="onEnvPre(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.status === 'UP'" type="text" size="mini" @click="onDisable(scope.row)">禁用</el-button>
<el-button v-if="scope.row.parentId > 0 && scope.row.status === 'OUT_OF_SERVICE'" type="text" size="mini" @click="onEnable(scope.row)">启用</el-button>
</template>
</el-table-column>
</el-table>
@ -103,22 +126,46 @@ export default {
onSearchTable: function() {
this.loadTable()
},
onOffline: function(row) {
this.confirm('确定要下线【' + row.serviceId + '】吗?', function(done) {
onDisable: function(row) {
this.confirm('确定要禁用【' + row.serviceId + '】吗?', function(done) {
this.post('service.instance.offline', row, function() {
this.tip('下线成功')
done()
})
})
},
onOnline: function(row) {
this.confirm('确定要上线【' + row.serviceId + '】吗?', function(done) {
onEnable: function(row) {
this.confirm('确定要启用【' + row.serviceId + '】吗?', function(done) {
this.post('service.instance.online', row, function() {
this.tip('上线成功')
done()
})
})
},
onEnvOnline: function(row) {
this.confirm('确定要上线【' + row.serviceId + '】吗?', function(done) {
this.post('service.instance.env.online', row, function() {
this.tip('上线成功')
done()
})
})
},
onEnvPre: function(row) {
this.confirm('确定要预发布【' + row.serviceId + '】吗?', function(done) {
this.post('service.instance.env.pre', row, function() {
this.tip('预发布成功')
done()
})
})
},
onEnvGray: function(row) {
this.confirm('确定要灰度发布【' + row.serviceId + '】吗?', function(done) {
this.post('service.instance.env.gray', row, function() {
this.tip('灰度发布发成功')
done()
})
})
},
renderServiceName: function(row) {
let instanceCount = ''
if (row.children && row.children.length > 0) {
@ -128,6 +175,14 @@ export default {
instanceCount = ` (${onlineCount}/${row.children.length})`
}
return row.serviceId + instanceCount
},
renderMetadata: function(row) {
const metadata = row.metadata
const html = []
for (const key in metadata) {
html.push(key + '=' + metadata[key])
}
return html.join('<br />')
}
}
}

@ -54,4 +54,5 @@ public class SopConstants {
public static final String UNKNOWN_SERVICE= "_sop_unknown_service_";
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
}

@ -69,7 +69,7 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
}
if (error == null) {
error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
}
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);

@ -84,7 +84,7 @@ public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory
return Mono.just(test);
} catch (ClassCastException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object",
LOGGER.debug("Predicate test failed because class in predicate does not canVisit the cached body object",
e);
}
}

@ -9,9 +9,7 @@ public enum ErrorEnum {
SUCCESS(Codes.CODE_SUCCESS, ""),
/** 服务暂不可用 */
ISP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "isp.unknow-error"),
/** 服务暂不可用 */
AOP_UNKNOW_ERROR(Codes.CODE_UNKNOW, "aop.unknow-error"),
ISP_UNKNOWN_ERROR(Codes.CODE_UNKNOW, "isp.unknown-error"),
/** 服务不可用,路由被禁用 */
ISP_API_DISABLED(Codes.CODE_UNKNOW, "isp.service-not-available"),

@ -76,7 +76,7 @@ public class ErrorFactory {
// open.error_20000=Service is temporarily unavailable
String msg = getErrorMessage(modulePrefix + code, locale);
String subCode = errorMeta.getSubCode();
// open.error_20000_isp.unknow-error=Service is temporarily unavailable
// open.error_20000_isp.unknown-error=Service is temporarily unavailable
String subMsg = getErrorMessage(modulePrefix + code + UNDERLINE + subCode, locale, params);
if (StringUtils.isEmpty(msg)) {
msg = SYS_ERR;
@ -85,7 +85,7 @@ public class ErrorFactory {
subMsg = SYS_ERR;
}
// solution暂未实现,如果要实现,可以这样配置:
// open.error_20000_isp.unknow-error_solution=Service is temporarily unavailable
// open.error_20000_isp.unknown-error_solution=Service is temporarily unavailable
// <code>String solution = getErrorMessage(modulePrefix + code + UNDERLINE + subCode + "_solution", locale, params);</code>
error = new ErrorImpl(code, msg, subCode, subMsg, null);
errorCache.put(key, error);

@ -51,7 +51,7 @@ public abstract class BaseParamBuilder<T> implements ParamBuilder<T> {
RouteRepository<? extends TargetRoute> routeRepository = RouteRepositoryContext.getRouteRepository();
if (routeRepository == null) {
log.error("RouteRepositoryContext.setRouteRepository()方法未使用");
throw ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getException();
throw ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getException();
}
String nameVersion = Optional.ofNullable(apiParam.fetchNameVersion()).orElse(String.valueOf(System.currentTimeMillis()));

@ -35,7 +35,7 @@ import java.util.Optional;
@Slf4j
public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R> {
private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta();
private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOW_ERROR.getErrorMeta();
private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta();
private static final ErrorMeta ISP_BIZ_ERROR = ErrorEnum.BIZ_ERROR.getErrorMeta();
public static final String GATEWAY_CODE_NAME = "code";

@ -6,7 +6,7 @@ package com.gitee.sop.gatewaycommon.result;
* "result": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -8,7 +8,7 @@ package com.gitee.sop.gatewaycommon.result;
* "alipay_trade_order_settle_response": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -22,7 +22,7 @@ package com.gitee.sop.gatewaycommon.result;
* "alipay_trade_fastpay_refund_query_response": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -36,7 +36,7 @@ public class ApiSessionManager implements SessionManager {
return cache.get(sessionId);
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getException();
throw ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getException();
}
}

@ -0,0 +1,72 @@
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
import com.google.common.base.Optional;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* 服务实例选择器
*
* @author tanghc
*/
@Slf4j
public abstract class BaseServerChooser extends ZoneAvoidanceRule {
/**
* 是否匹配对应的服务器可在此判断是否是预发布灰度环境
*
* @param server 指定服务器
* @return 返回true
*/
protected abstract boolean match(Server server);
/**
* 客户端能否够访问服务器
*
* @param server 服务器实例
* @param request request
* @return 返回true能访问
*/
protected abstract boolean canVisit(Server server, HttpServletRequest request);
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
// 获取服务实例列表
List<Server> allServers = new ArrayList<>(lb.getAllServers());
int index = -1;
for (int i = 0; i < allServers.size(); i++) {
Server server = allServers.get(i);
if (match(server)) {
index = i;
if (canVisit(server, RequestContext.getCurrentContext().getRequest())) {
return server;
}
}
}
// 调用默认的算法
// 如果选出了特殊环境服务器,需要移除命中的服务器
if (index > -1) {
allServers.remove(index);
}
if (CollectionUtils.isEmpty(allServers)) {
log.error("无可用服务实例,key:", key);
return null;
}
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(allServers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}

@ -76,7 +76,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
}
}
if (error == null) {
error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
}
return error;
}

@ -6,6 +6,45 @@ import org.springframework.util.CollectionUtils;
import java.util.Map;
/**
* {
* "instance": {
* "instanceId": "demo-order2:11101",
* "app": "demo-order2",
* "appGroutName": null,
* "ipAddr": "127.0.0.1",
* "sid": "na",
* "homePageUrl": null,
* "statusPageUrl": null,
* "healthCheckUrl": null,
* "secureHealthCheckUrl": null,
* "vipAddress": "demo-order2",
* "secureVipAddress": "demo-order2",
* "countryId": 1,
* "dataCenterInfo": {
* "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
* "name": "MyOwn"
* },
* "hostName": "127.0.0.1",
* "status": "UP",
* "leaseInfo": null,
* "isCoordinatingDiscoveryServer": false,
* "lastUpdatedTimestamp": 1529391461000,
* "lastDirtyTimestamp": 1529391461000,
* "actionType": null,
* "asgName": null,
* "overridden_status": "UNKNOWN",
* "port": {
* "$": 11102,
* "@enabled": "false"
* },
* "securePort": {
* "$": 7002,
* "@enabled": "false"
* },
* "metadata": {
* "@class": "java.util.Collections$EmptyMap"
* }* }
* }
* @author tanghc
*/
@Data
@ -19,6 +58,8 @@ public class EurekaInstance {
private String healthCheckUrl;
private String lastUpdatedTimestamp;
private Map<String, String> metadata;
public String fetchPort() {
if (CollectionUtils.isEmpty(port)) {
return "";

@ -16,6 +16,10 @@ public enum EurekaUri {
* 查询所有实例 Query for all instances
*/
QUERY_APPS("GET", "/apps"),
/**
* 查询一个实例
*/
QUERY_INSTANCES("GET", "/instances/%s"),
/**
* 下线 Take instance out of service
*/
@ -24,6 +28,12 @@ public enum EurekaUri {
* 上线 Move instance back into service (remove override)
*/
ONLINE_SERVICE("DELETE", "/apps/%s/%s/status?value=UP"),
/**
* 设置metadata信息
*
* /apps/{appID}/{instanceID}/metadata?key=value
*/
SET_METADATA("PUT", "/apps/%s/%s/metadata?%s=%s")
;
public static final String URL_PREFIX = "/";

@ -1,4 +1,4 @@
package com.gitee.sop.websiteserver.bean;
package com.gitee.sop.registryapi.bean;
import lombok.Data;
import lombok.Getter;
@ -110,7 +110,7 @@ public class HttpTool {
* @return
* @throws IOException
*/
public String request(String url, Map<String, String> form, Map<String, String> header, HTTPMethod method) throws IOException {
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method.value());
// 添加header
addHeader(requestBuilder, header);
@ -153,7 +153,7 @@ public class HttpTool {
}
}
public static Request.Builder buildRequestBuilder(String url, Map<String, String> form, String method) {
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, String method) {
switch (method) {
case "get":
return new Request.Builder()
@ -178,18 +178,18 @@ public class HttpTool {
}
}
public static HttpUrl buildHttpUrl(String url, Map<String, String> form) {
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (Map.Entry<String, String> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
for (Map.Entry<String, ?> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
return urlBuilder.build();
}
public static FormBody buildFormBody(Map<String, String> form) {
public static FormBody buildFormBody(Map<String, ?> form) {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), entry.getValue());
for (Map.Entry<String, ?> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
return paramBuilder.build();
}
@ -262,6 +262,9 @@ public class HttpTool {
HEAD,
DELETE;
private HTTPMethod() {
}
public String value() {
return this.name();
}

@ -2,6 +2,9 @@ package com.gitee.sop.registryapi.bean;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @author tanghc
*/
@ -37,4 +40,9 @@ public class ServiceInstance {
*/
private String updateTime;
/**
* user extended attributes
*/
private Map<String, String> metadata = new HashMap<String, String>();
}

@ -38,4 +38,13 @@ public interface RegistryService {
void offlineInstance(ServiceInstance serviceInstance) throws Exception;
/**
* 设置实例元数据
*
* @param serviceInstance 实例
* @param key key
* @param value
* @throws Exception 设置实例元数据失败抛出异常
*/
void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception;
}

@ -60,6 +60,7 @@ public class RegistryServiceEureka implements RegistryService {
serviceInstance.setStatus(eurekaInstance.getStatus());
Date updateTime = new Date(Long.valueOf(eurekaInstance.getLastUpdatedTimestamp()));
serviceInstance.setUpdateTime(DateFormatUtils.format(updateTime, TIMESTAMP_PATTERN));
serviceInstance.setMetadata(eurekaInstance.getMetadata());
serviceInfo.getInstances().add(serviceInstance);
}
}
@ -80,6 +81,11 @@ public class RegistryServiceEureka implements RegistryService {
this.requestEurekaServer(EurekaUri.OFFLINE_SERVICE, serviceInstance.getServiceId(), serviceInstance.getInstanceId());
}
@Override
public void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception {
this.requestEurekaServer(EurekaUri.SET_METADATA, serviceInstance.getServiceId(), serviceInstance.getInstanceId(), key, value);
}
private String requestEurekaServer(EurekaUri eurekaUri, String... args) throws IOException {
Request request = eurekaUri.getRequest(this.eurekaUrl, args);
Response response = client.newCall(request).execute();

@ -1,17 +1,17 @@
package com.gitee.sop.registryapi.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.gitee.sop.registryapi.bean.HttpTool;
import com.gitee.sop.registryapi.bean.ServiceInfo;
import com.gitee.sop.registryapi.bean.ServiceInstance;
import com.gitee.sop.registryapi.service.RegistryService;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
@ -24,13 +24,15 @@ import java.util.List;
import java.util.Map;
/**
* nacos接口实现
* nacos接口实现, https://nacos.io/zh-cn/docs/open-api.html
* @author tanghc
*/
public class RegistryServiceNacos implements RegistryService {
OkHttpClient client = new OkHttpClient();
static HttpTool httpTool = new HttpTool();
@Value("${registry.nacos-server-addr:}")
private String nacosAddr;
@ -63,6 +65,7 @@ public class RegistryServiceNacos implements RegistryService {
boolean isOnline = instance.getWeight() > 0;
String status = isOnline ? "UP" : "OUT_OF_SERVICE";
serviceInstance.setStatus(status);
serviceInstance.setMetadata(instance.getMetadata());
serviceInfo.getInstances().add(serviceInstance);
}
}
@ -73,34 +76,50 @@ public class RegistryServiceNacos implements RegistryService {
@Override
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
Map<String, String> params = new HashMap<>(8);
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 上线,把权重设置成1
params.put("weight", "1");
this.updateInstance(serviceInstance, params);
instance.setWeight(1);
this.updateInstance(instance);
}
@Override
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
Map<String, String> params = new HashMap<>(8);
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 下线,把权重设置成0
params.put("weight", "0");
this.updateInstance(serviceInstance, params);
instance.setWeight(0);
this.updateInstance(instance);
}
private Response updateInstance(ServiceInstance serviceInstance, Map<String, String> params) throws IOException {
FormBody.Builder builder = new FormBody.Builder();
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
builder.add("serviceName", serviceInstance.getServiceId())
.add("ip", serviceInstance.getIp())
.add("port", String.valueOf(serviceInstance.getPort()));
FormBody formBody = builder.build();
final Request request = new Request.Builder()
.url("http://" + nacosAddr + "/nacos/v1/ns/instance")
.put(formBody)
.build();
@Override
public void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception {
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 设置metadata
Map<String, String> metadata = instance.getMetadata();
metadata.put(key, value);
this.updateInstance(instance);
}
protected void updateInstance(Instance instance) throws IOException {
String json = JSON.toJSONString(instance);
JSONObject jsonObject = JSON.parseObject(json);
httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", jsonObject, null, HttpTool.HTTPMethod.PUT);
}
return client.newCall(request).execute();
/**
* 查询实例
* @param serviceInstance 实例信息
* @return 返回nacos实例
* @throws IOException 查询异常
*/
protected Instance getInstance(ServiceInstance serviceInstance) throws IOException {
Map<String, String> params = new HashMap<>(8);
params.put("serviceName", serviceInstance.getServiceId());
params.put("ip", serviceInstance.getIp());
params.put("port", String.valueOf(serviceInstance.getPort()));
String instanceJson = httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", params, null, HttpTool.HTTPMethod.GET);
return JSON.parseObject(instanceJson, Instance.class);
}
}

@ -5,7 +5,7 @@ package com.gitee.sop.servercommon.message;
*/
public enum ServiceErrorEnum {
/** 系统繁忙 */
ISP_UNKNOW_ERROR("isp.unknow-error"),
ISP_UNKNOW_ERROR("isp.unknown-error"),
/** 参数错误 */
ISV_PARAM_ERROR("isv.invalid-parameter"),
/** 通用错误 */

@ -1,4 +1,4 @@
# 错误配置
isp.error_isp.unknow-error=Service is temporarily unavailable
isp.error_isp.unknown-error=Service is temporarily unavailable
isp.error_isv.invalid-parameter=Invalid parameter

@ -1,5 +1,5 @@
# 错误配置
isp.error_isp.unknow-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
isp.error_isp.unknown-error=\u670d\u52a1\u6682\u4e0d\u53ef\u7528
# 参数无效
isp.error_isv.invalid-parameter=\u53c2\u6570\u65e0\u6548

@ -6,8 +6,11 @@ package com.gitee.sop.gateway.config;
* 注意下面两个只能使用一个
*/
import com.gitee.sop.gateway.loadbalancer.SopPropertiesFactory;
import com.gitee.sop.gateway.manager.ManagerInitializer;
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
@ -21,6 +24,11 @@ public class ZuulConfig extends AlipayZuulConfiguration {
new ManagerInitializer();
}
@Bean
PropertiesFactory propertiesFactory() {
return new SopPropertiesFactory();
}
}
/**

@ -0,0 +1,55 @@
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,39 @@
package com.gitee.sop.gateway.loadbalancer;
import com.netflix.loadbalancer.IRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
import org.springframework.core.env.Environment;
/**
* 自定义PropertiesFactory用来动态添加LoadBalance规则
* @author tanghc
*/
public class SopPropertiesFactory extends PropertiesFactory {
/**
* 可在配置文件中设置<code>zuul.custom-rule-classname=com.xx.ClassName</code>指定负载均衡规则类
* 默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
*/
private static final String PROPERTIES_KEY = "zuul.custom-rule-classname";
private static final String CUSTOM_RULE_CLASSNAME = PreEnvironmentServerChooser.class.getName();
@Autowired
private Environment environment;
/**
* 配置文件配置<serviceId>.ribbon.NFLoadBalancerRuleClassName=com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
* @param clazz
* @param name serviceId
* @return 返回class全限定名
*/
@Override
public String getClassName(Class clazz, String name) {
if (clazz == IRule.class) {
return this.environment.getProperty(PROPERTIES_KEY, CUSTOM_RULE_CLASSNAME);
} else {
return super.getClassName(clazz, name);
}
}
}

@ -6,7 +6,7 @@ package com.gitee.sop.sdk.common;
* "result": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -8,7 +8,7 @@ package com.gitee.sop.sdk.common;
* "alipay_trade_order_settle_response": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -11,7 +11,7 @@ import lombok.Setter;
* "alipay_trade_close_response": {
* "code": "20000",
* "msg": "Service Currently Unavailable",
* "sub_code": "isp.unknow-error",
* "sub_code": "isp.unknown-error",
* "sub_msg": "系统繁忙"
* },
* "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"

@ -110,7 +110,7 @@ public class HttpTool {
* @return
* @throws IOException
*/
public String request(String url, Map<String, String> form, Map<String, String> header, HTTPMethod method) throws IOException {
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method.value());
// 添加header
addHeader(requestBuilder, header);
@ -153,7 +153,7 @@ public class HttpTool {
}
}
public static Request.Builder buildRequestBuilder(String url, Map<String, String> form, String method) {
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, String method) {
switch (method) {
case "get":
return new Request.Builder()
@ -178,18 +178,18 @@ public class HttpTool {
}
}
public static HttpUrl buildHttpUrl(String url, Map<String, String> form) {
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (Map.Entry<String, String> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
for (Map.Entry<String, ?> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
return urlBuilder.build();
}
public static FormBody buildFormBody(Map<String, String> form) {
public static FormBody buildFormBody(Map<String, ?> form) {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), entry.getValue());
for (Map.Entry<String, ?> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
return paramBuilder.build();
}

@ -141,7 +141,7 @@
<div>服务不可用</div>
</td>
<td rowspan="1" colspan="1">
<div>isp.unknow-error</div>
<div>isp.unknown-error</div>
</td>
<td rowspan="1" colspan="1">
<div>服务暂不可用(业务系统不可用)</div>

@ -93,7 +93,7 @@ function createResponseCode(docItem) {
' "'+method+'_response": {\n' +
' "code": "20000",\n' +
' "msg": "Service is temporarily unavailable",\n' +
' "sub_code": "isp.unknow-error",\n' +
' "sub_code": "isp.unknown-error",\n' +
' "sub_msg": "服务暂不可用"\n' +
' }' +
'}';

@ -1,7 +1,7 @@
package com.gitee.sop.websiteserver.controller;
import com.gitee.sop.websiteserver.bean.HttpTool;
import com.gitee.sop.websiteserver.bean.HttpTool.*;
import com.gitee.sop.registryapi.bean.HttpTool;
import com.gitee.sop.registryapi.bean.HttpTool.*;
import com.gitee.sop.websiteserver.sign.AlipayApiException;
import com.gitee.sop.websiteserver.sign.AlipaySignature;
import com.gitee.sop.websiteserver.util.UploadUtil;

Loading…
Cancel
Save