diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43135661f..5d2a38260 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,3 +22,4 @@
- [feat: support config ratelimit addresses and remote task interval.](https://github.com/Tencent/spring-cloud-tencent/pull/1679)
- [docs:optimize tsf example.](https://github.com/Tencent/spring-cloud-tencent/pull/1710)
- [feat:support TSF certificate manager.](https://github.com/Tencent/spring-cloud-tencent/pull/1715)
+- [feat:support tsf unit.](https://github.com/Tencent/spring-cloud-tencent/pull/1681)
\ No newline at end of file
diff --git a/spring-cloud-starter-tencent-all/pom.xml b/spring-cloud-starter-tencent-all/pom.xml
index 3758dd093..e917a0a6f 100644
--- a/spring-cloud-starter-tencent-all/pom.xml
+++ b/spring-cloud-starter-tencent-all/pom.xml
@@ -60,6 +60,11 @@
spring-cloud-starter-tencent-gateway-plugin
+
+ com.tencent.cloud
+ spring-cloud-tencent-unit-plugin
+
+
com.tencent.cloud
spring-cloud-starter-tencent-polaris-auth
diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java
index 48644bb70..07e5d53cf 100644
--- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java
+++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java
@@ -53,7 +53,7 @@ public class PolarisReactiveDiscoveryClient implements ReactiveDiscoveryClient {
@Override
public Flux getInstances(String serviceId) {
-
+ // TODO: shedfree 服务发现单元化
return Mono.justOrEmpty(serviceId).flatMapMany(loadInstancesFromPolaris())
.subscribeOn(Schedulers.boundedElastic());
}
diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java
index 771dacb1b..220fba945 100644
--- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java
+++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java
@@ -66,6 +66,10 @@ public final class MetadataConstant {
* TSF Metadata.
*/
public static final String TSF_METADATA = "TSF-Metadata";
+ /**
+ * TSF Unit.
+ */
+ public static final String TSF_UNIT = "TSF-Unit";
/**
* Custom metadata.
*/
diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java
index 79c2eb027..341340246 100644
--- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java
+++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/OrderConstant.java
@@ -42,6 +42,10 @@ public class OrderConstant {
* Order of encode router label interceptor.
*/
public static final int ROUTER_LABEL_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE;
+ /**
+ * Order of unit interceptor.
+ */
+ public static final int UNIT_INTERCEPTOR_ORDER = 10;
}
/**
@@ -57,6 +61,10 @@ public class OrderConstant {
* Order of encode router label interceptor.
*/
public static final int ROUTER_LABEL_INTERCEPTOR_ORDER = 0;
+ /**
+ * Order of unit interceptor.
+ */
+ public static final int UNIT_INTERCEPTOR_ORDER = 10;
}
/**
diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/EnvironmentUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/EnvironmentUtils.java
new file mode 100644
index 000000000..97ebc220a
--- /dev/null
+++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/EnvironmentUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.common.util;
+
+import com.tencent.polaris.api.utils.ClassUtils;
+
+public final class EnvironmentUtils {
+
+ private final static boolean IS_GATEWAY = ClassUtils.isClassPresent("org.springframework.cloud.gateway.filter.GlobalFilter");
+
+ private EnvironmentUtils() {
+ }
+
+ public static boolean isGateway() {
+ return IS_GATEWAY;
+ }
+}
diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml
index 6045fe525..ef75f7131 100644
--- a/spring-cloud-tencent-dependencies/pom.xml
+++ b/spring-cloud-tencent-dependencies/pom.xml
@@ -178,6 +178,12 @@
${revision}
+
+ com.tencent.cloud
+ spring-cloud-tencent-unit-plugin
+ ${revision}
+
+
com.tencent.cloud
spring-cloud-starter-tencent-threadlocal-plugin
diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml
index 149ece160..82d740823 100644
--- a/spring-cloud-tencent-plugin-starters/pom.xml
+++ b/spring-cloud-tencent-plugin-starters/pom.xml
@@ -18,6 +18,7 @@
spring-cloud-starter-tencent-gateway-plugin
spring-cloud-starter-tencent-discovery-adapter-plugin
spring-cloud-tencent-lossless-plugin
+ spring-cloud-tencent-unit-plugin
spring-cloud-starter-tencent-threadlocal-plugin
spring-cloud-starter-tencent-trace-plugin
spring-cloud-starter-tencent-fault-tolerance
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml
index 64bbb5991..4690f32c7 100644
--- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/pom.xml
@@ -19,6 +19,11 @@
spring-cloud-starter-tencent-polaris-config
+
+ com.tencent.cloud
+ spring-cloud-tencent-unit-plugin
+
+
com.tencent.cloud
spring-cloud-starter-tencent-polaris-discovery
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java
index b69ba8d7e..90eb8a282 100644
--- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayFilter.java
@@ -19,6 +19,7 @@ package com.tencent.cloud.plugin.gateway.context;
import java.lang.reflect.Constructor;
import java.net.URI;
+import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -27,12 +28,15 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import com.tencent.cloud.common.constant.ContextConstant;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
+import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.MetadataContextUtils;
+import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.tsf.gateway.core.TsfGatewayRequest;
@@ -47,6 +51,9 @@ import com.tencent.tsf.gateway.core.plugin.GatewayPluginFactory;
import com.tencent.tsf.gateway.core.plugin.IGatewayPlugin;
import com.tencent.tsf.gateway.core.util.CookieUtil;
import com.tencent.tsf.gateway.core.util.IdGenerator;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitTagPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@@ -94,6 +101,7 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
if (pathRewriteException != null) {
throw pathRewriteException;
}
+
// plugins will add metadata
if (exchange.getAttributes().containsKey(MetadataConstant.HeaderName.METADATA_CONTEXT)) {
MetadataContextHolder.set((MetadataContext) exchange.getAttributes().get(
@@ -114,11 +122,17 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
ServerWebExchange apiRebuildExchange;
- if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
- apiRebuildExchange = msFilter(exchange, chain, groupContext, path);
+ if (TencentUnitManager.isEnable()) {
+ unitPreprocess(exchange);
+ apiRebuildExchange = unitFilter(exchange, chain, groupContext, path);
}
else {
- apiRebuildExchange = externalFilter(exchange, chain, path);
+ if (ApiType.MS.equals(groupContext.getPredicate().getApiType())) {
+ apiRebuildExchange = msFilter(exchange, chain, groupContext, path);
+ }
+ else {
+ apiRebuildExchange = externalFilter(exchange, chain, path);
+ }
}
ServerWebExchange pluginRebuildExchange = doPlugins(apiRebuildExchange,
@@ -283,6 +297,83 @@ public class ContextGatewayFilter implements GatewayFilter, Ordered {
return exchange.mutate().request(newRequest).build();
}
+ private void unitPreprocess(ServerWebExchange exchange) {
+ // 提前到这里清理
+ TencentUnitContext.removeAll();
+
+ ServerHttpRequest servletRequest = exchange.getRequest();
+ HttpHeaders headers = servletRequest.getHeaders();
+ String unitAttr = Optional.ofNullable(headers.getFirst(HeaderName.UNIT)).orElse("");
+
+ String cid = headers.getFirst(TencentUnitManager.getRouterIdentifierHeader());
+
+ for (String grayKey: TencentUnitManager.getGrayUnitHeaderKey()) {
+ TencentUnitContext.putGrayUserTag(UnitTagPosition.HEADER.name(), grayKey, headers.getFirst(grayKey));
+ }
+
+ Map unitMap = JacksonUtils.deserialize2Map(URLDecoder.decode(unitAttr, StandardCharsets.UTF_8));
+ String path = exchange.getRequest().getURI().getPath();
+ if (CollectionUtils.isEmpty(unitMap)) {
+ SpringCloudUnitUtils.processFromCid(cid, path);
+ }
+ else {
+ SpringCloudUnitUtils.processFromUnitHeader(unitMap, path);
+ }
+ }
+
+ private ServerWebExchange unitFilter(ServerWebExchange exchange, GatewayFilterChain chain, GroupContext groupContext, PathContainer path) {
+ ServerHttpRequest request = exchange.getRequest();
+ String[] apis = rebuildUnitApi(request, path.value());
+ logger.debug("[unitFilter] path:{}, apis: {}", path, apis);
+ // check api
+ GroupContext.ContextRoute contextRoute = manager.getGroupUnitPathRoute(config.getGroup(), apis[0]);
+ if (contextRoute == null) {
+ String msg = String.format("[unitFilter] Can't find context route for group: %s, path: %s, origin path: %s", config.getGroup(), apis[0], path.value());
+ logger.warn(msg);
+ throw NotFoundException.create(true, msg);
+ }
+ updateRouteMetadata(exchange, contextRoute);
+ setTraceAttributes(contextRoute, GatewayConstant.UNIT_TYPE, GatewayConstant.UNIT_TRANSFER_TYPE);
+ exchange.getAttributes().put(GatewayConstant.CONTEXT_ROUTE, contextRoute);
+
+ MetadataContext metadataContext = exchange.getAttribute(
+ MetadataConstant.HeaderName.METADATA_CONTEXT);
+ if (metadataContext != null) {
+ // get unit ns
+ String routeNamespace = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_ID);
+ metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
+ MetadataConstant.POLARIS_TARGET_NAMESPACE, routeNamespace);
+ }
+ URI requestUri = URI.create("lb://" + contextRoute.getService() + apis[1]);
+ exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri);
+ // to correct path
+ ServerHttpRequest newRequest = request.mutate().path(apis[1]).build();
+ return exchange.mutate().request(newRequest).build();
+ }
+
+ /**
+ * e.g. "/context/system/svc/api/test" → [ "GET|/svc/api/test", "/api/test"]
+ */
+ String[] rebuildUnitApi(ServerHttpRequest request, String path) {
+ String[] pathSegments = path.split("/");
+ StringBuilder matchPath = new StringBuilder();
+ matchPath.append(request.getMethod().name()).append("|");
+ if (pathSegments.length > 3) {
+ matchPath.append("/").append(pathSegments[3]);
+ }
+ StringBuilder realPath = new StringBuilder();
+ int index = 4;
+ for (int i = index; i < pathSegments.length; i++) {
+ matchPath.append("/").append(pathSegments[i]);
+ realPath.append("/").append(pathSegments[i]);
+ }
+ if (path.endsWith("/")) {
+ matchPath.append("/");
+ realPath.append("/");
+ }
+ return new String[] {matchPath.toString(), realPath.toString()};
+ }
+
/**
* e.g. "/context/api/test" → [ "GET|/api/test", "/api/test"]
*/
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java
index 38ecd8af6..1736af61c 100644
--- a/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-starter-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/context/ContextGatewayPropertiesManager.java
@@ -58,6 +58,14 @@ public class ContextGatewayPropertiesManager {
* context -> {wildcard path key -> route}.
*/
private volatile ConcurrentHashMap> groupWildcardPathRouteMap = new ConcurrentHashMap<>();
+ /**
+ * context -> {unit path key -> route}.
+ */
+ private volatile ConcurrentHashMap> groupUnitPathRouteMap = new ConcurrentHashMap<>();
+ /**
+ * context -> {unit wildcard path key -> route}.
+ */
+ private volatile ConcurrentHashMap> groupUnitWildcardPathRouteMap = new ConcurrentHashMap<>();
/**
* group -> plugin info.
*/
@@ -145,31 +153,47 @@ public class ContextGatewayPropertiesManager {
ConcurrentHashMap> newGroupPathRouteMap = new ConcurrentHashMap<>();
ConcurrentHashMap> newGroupWildcardPathRouteMap = new ConcurrentHashMap<>();
+ ConcurrentHashMap> newGroupUnitPathRouteMap = new ConcurrentHashMap<>();
+ ConcurrentHashMap> newGroupUnitWildcardPathRouteMap = new ConcurrentHashMap<>();
+
if (groups != null) {
for (Map.Entry entry : groups.entrySet()) {
GroupContext groupContext = entry.getValue();
Map newGroupPathRoute = new HashMap<>();
Map newGroupWildcardPathRoute = new HashMap<>();
+ Map newGroupUnitPathRoute = new HashMap<>();
+ Map newGroupUnitWildcardPathRoute = new HashMap<>();
+
for (GroupContext.ContextRoute route : groupContext.getRoutes()) {
String path = route.getPath();
// convert path parameter to group wildcard path
if (path.contains("{") && path.contains("}") || path.contains("*") || path.contains("?")) {
newGroupWildcardPathRoute.put(buildPathKey(groupContext, route), route);
+ newGroupUnitWildcardPathRoute.put(buildUnitPathKey(groupContext, route), route);
}
else {
newGroupPathRoute.put(buildPathKey(groupContext, route), route);
+ newGroupUnitPathRoute.put(buildUnitPathKey(groupContext, route), route);
if (StringUtils.isNotEmpty(route.getPathMapping())) {
newGroupPathRoute.put(buildPathMappingKey(groupContext, route), route);
+ newGroupUnitPathRoute.put(buildUnitPathKey(groupContext, route), route);
}
}
}
newGroupWildcardPathRouteMap.put(entry.getKey(), newGroupWildcardPathRoute);
newGroupPathRouteMap.put(entry.getKey(), newGroupPathRoute);
+
+ newGroupUnitWildcardPathRouteMap.put(entry.getKey(), newGroupUnitWildcardPathRoute);
+ newGroupUnitPathRouteMap.put(entry.getKey(), newGroupUnitPathRoute);
}
}
this.groupPathRouteMap = newGroupPathRouteMap;
this.groupWildcardPathRouteMap = newGroupWildcardPathRouteMap;
+
+ this.groupUnitPathRouteMap = newGroupUnitPathRouteMap;
+ this.groupUnitWildcardPathRouteMap = newGroupUnitWildcardPathRouteMap;
+
this.groups = groups;
}
@@ -195,6 +219,24 @@ public class ContextGatewayPropertiesManager {
return null;
}
+ public GroupContext.ContextRoute getGroupUnitPathRoute(String group, String path) {
+ Map groupPathRouteMap = this.groupUnitPathRouteMap.get(group);
+ if (groupPathRouteMap != null && groupPathRouteMap.containsKey(path)) {
+ return groupPathRouteMap.get(path);
+ }
+
+ Map groupWildcardPathRouteMap = this.groupUnitWildcardPathRouteMap.get(group);
+ if (groupWildcardPathRouteMap != null) {
+ for (Map.Entry entry : groupWildcardPathRouteMap.entrySet()) {
+ boolean matched = antPathMatcher.match(entry.getKey(), path);
+ if (matched) {
+ return entry.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
public void eagerLoad(PolarisDiscoveryClient polarisDiscoveryClient,
PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient) {
for (Map contextRouteMap : groupPathRouteMap.values()) {
@@ -241,6 +283,10 @@ public class ContextGatewayPropertiesManager {
}
}
+ private String buildUnitPathKey(GroupContext groupContext, GroupContext.ContextRoute route) {
+ return String.format("%s|/%s%s", route.getMethod(), route.getService(), route.getPath());
+ }
+
private String buildPathMappingKey(GroupContext groupContext, GroupContext.ContextRoute route) {
return String.format("%s|%s", route.getMethod(), route.getPathMapping());
}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/pom.xml
new file mode 100644
index 000000000..cde3a90bd
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+ spring-cloud-tencent-plugin-starters
+ com.tencent.cloud
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+
+ spring-cloud-tencent-unit-plugin
+ Spring Cloud Tencent Unit Plugin
+
+
+
+
+ com.tencent.cloud
+ spring-cloud-starter-tencent-polaris-discovery
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ true
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ true
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ true
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+ true
+
+
+
+
+
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/GatewayUnitAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/GatewayUnitAutoConfiguration.java
new file mode 100644
index 000000000..2e07142cb
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/GatewayUnitAutoConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.config;
+
+import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled;
+import com.tencent.tsf.unit.core.GatewayUnitArchCallback;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.TsfZoneFilterUnitCallback;
+import com.tencent.tsf.unit.core.remote.TsfUnitConsulManager;
+import jakarta.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class GatewayUnitAutoConfiguration {
+
+ /**
+ * 网关时执行.
+ */
+ @Configuration
+ @ConditionalOnOnlyTsfConsulEnabled
+ @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter")
+ static class GatewayUnitEnable {
+
+ @Value("${spring.application.name:}")
+ private String applicationName;
+
+
+ @PostConstruct
+ public void init() {
+ TencentUnitManager.addArchCallback(new GatewayUnitArchCallback(applicationName));
+ TencentUnitManager.addRuleCallback(new TsfZoneFilterUnitCallback());
+ TsfUnitConsulManager.init();
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfiguration.java
new file mode 100644
index 000000000..275b8bb8e
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfiguration.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.tencent.cloud.common.tsf.ConditionalOnOnlyTsfConsulEnabled;
+import com.tencent.cloud.plugin.unit.instrument.feign.UnitFeignRequestInterceptor;
+import com.tencent.cloud.plugin.unit.instrument.resttemplate.UnitRestTemplateInterceptor;
+import com.tencent.cloud.plugin.unit.plugin.UnitClientFinallyEnhancedPlugin;
+import com.tencent.cloud.plugin.unit.plugin.UnitFeignEnhancedPlugin;
+import com.tencent.cloud.plugin.unit.plugin.UnitRestTemplateEnhancedPlugin;
+import com.tencent.cloud.plugin.unit.plugin.UnitServletPreEnhancedPlugin;
+import com.tencent.tsf.unit.aspect.TsfUnitRouteAspect;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.TsfZoneFilterUnitCallback;
+import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
+
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.web.client.RestTemplate;
+
+
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnOnlyTsfConsulEnabled
+public class UnitAutoConfiguration {
+
+ @Bean
+ public UnitBeanPostProcessor unitPolarisDiscoveryClientBeanPostProcessor() {
+ return new UnitBeanPostProcessor();
+ }
+
+ @Bean
+ public UnitClientFinallyEnhancedPlugin unitClientFinallyEnhancedPlugin() {
+ return new UnitClientFinallyEnhancedPlugin();
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+ static class UnitServletFilterConfig {
+
+ @Bean
+ public UnitServletPreEnhancedPlugin unitServletPreEnhancedPlugin() {
+ return new UnitServletPreEnhancedPlugin();
+ }
+ }
+
+ /**
+ * 如果不是网关才启动,避免重复调用.
+ */
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnMissingClass("org.springframework.cloud.gateway.filter.GlobalFilter")
+ static class MicroserviceUnitEnable {
+ static {
+ TencentUnitManager.addRuleCallback(new TsfZoneFilterUnitCallback());
+ TencentUnitUtils.enable();
+ }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
+ static class TsfUnitRestTemplateConfig {
+
+ @Autowired(required = false)
+ private List restTemplates = Collections.emptyList();
+
+
+ @Bean
+ public UnitRestTemplateInterceptor tsfUnitRestTemplateInterceptor() {
+ return new UnitRestTemplateInterceptor();
+ }
+
+ @Bean
+ public SmartInitializingSingleton addTsfUnitRestTemplateInterceptorForRestTemplate(UnitRestTemplateInterceptor interceptor) {
+ return () -> restTemplates.forEach(restTemplate -> {
+ List list = new ArrayList<>(restTemplate.getInterceptors());
+ list.add(interceptor);
+ restTemplate.setInterceptors(list);
+ });
+ }
+
+ @Bean
+ public UnitRestTemplateEnhancedPlugin unitRestTemplateEnhancedPlugin() {
+ return new UnitRestTemplateEnhancedPlugin();
+ }
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(name = "feign.Feign")
+ static class TsfUnitFeignConfig {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public UnitFeignRequestInterceptor tsfUnitFeignRequestInterceptor() {
+ return new UnitFeignRequestInterceptor();
+ }
+
+ @Bean
+ public TsfUnitRouteAspect tsfUnitRouteAspect() {
+ return new TsfUnitRouteAspect();
+ }
+
+ @Bean
+ public UnitFeignEnhancedPlugin unitFeignEnhancedPlugin() {
+ return new UnitFeignEnhancedPlugin();
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java
new file mode 100644
index 000000000..cc2aa1040
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/config/UnitBeanPostProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.config;
+
+import com.tencent.cloud.plugin.unit.discovery.UnitFeignEagerLoadSmartLifecycle;
+import com.tencent.cloud.plugin.unit.discovery.UnitPolarisDiscoveryClient;
+import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
+import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+
+public class UnitBeanPostProcessor implements BeanPostProcessor {
+
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof PolarisDiscoveryClient discoveryClient) {
+ return new UnitPolarisDiscoveryClient(discoveryClient);
+ }
+
+ if (bean instanceof FeignEagerLoadSmartLifecycle) {
+ return new UnitFeignEagerLoadSmartLifecycle();
+ }
+
+ return bean;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java
new file mode 100644
index 000000000..fafb52572
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitFeignEagerLoadSmartLifecycle.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.discovery;
+
+import com.tencent.cloud.polaris.eager.instrument.feign.FeignEagerLoadSmartLifecycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UnitFeignEagerLoadSmartLifecycle extends FeignEagerLoadSmartLifecycle {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UnitFeignEagerLoadSmartLifecycle.class);
+
+
+ public UnitFeignEagerLoadSmartLifecycle() {
+ super(null, null, null);
+ }
+
+ @Override
+ public void start() {
+ LOG.info("ignore feign eager load in unit mode");
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitPolarisDiscoveryClient.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitPolarisDiscoveryClient.java
new file mode 100644
index 000000000..e6b5c8993
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/discovery/UnitPolarisDiscoveryClient.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.discovery;
+
+import java.util.List;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.metadata.MetadataContext;
+import com.tencent.cloud.common.metadata.MetadataContextHolder;
+import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.cloud.client.ServiceInstance;
+
+public class UnitPolarisDiscoveryClient extends PolarisDiscoveryClient {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(UnitPolarisDiscoveryClient.class);
+
+ private final PolarisDiscoveryClient delegate;
+
+ public UnitPolarisDiscoveryClient(PolarisDiscoveryClient delegate) {
+ super(null);
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String description() {
+ return delegate.description();
+ }
+
+ @Override
+ public List getInstances(String service) {
+ if (TencentUnitManager.isEnable()) {
+ String[] parts = service.split("/");
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[getInstance] service:{}, unit context:{}", service, TencentUnitContext.getCompositeContextMap());
+ }
+
+ if (parts.length != 2) {
+ String namespace = TencentUnitContext.getStringRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID);
+
+ MetadataContext metadataContext = MetadataContextHolder.get();
+ metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
+ MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace);
+ return delegate.getInstances(service);
+ }
+ else {
+ MetadataContext metadataContext = MetadataContextHolder.get();
+ metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
+ MetadataConstant.POLARIS_TARGET_NAMESPACE, parts[0]);
+
+ return delegate.getInstances(parts[1]);
+ }
+ }
+ else {
+ return delegate.getInstances(service);
+ }
+ }
+
+ @Override
+ public List getServices() {
+ return delegate.getServices();
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/feign/UnitFeignRequestInterceptor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/feign/UnitFeignRequestInterceptor.java
new file mode 100644
index 000000000..4fed6d1ca
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/feign/UnitFeignRequestInterceptor.java
@@ -0,0 +1,61 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.instrument.feign;
+
+import java.net.URI;
+
+import com.tencent.cloud.common.constant.OrderConstant;
+import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitArch;
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+
+import org.springframework.core.Ordered;
+
+/**
+ * Interceptor used for setting Feign RequestTemplate metadata provider.
+ *
+ * @author lepdou, Hoatian Zhang
+ */
+public class UnitFeignRequestInterceptor implements RequestInterceptor, Ordered {
+
+ @Override
+ public int getOrder() {
+ return OrderConstant.Client.Feign.UNIT_INTERCEPTOR_ORDER;
+ }
+
+ @Override
+ public void apply(RequestTemplate requestTemplate) {
+ // 开启单元化
+ if (TencentUnitManager.isEnable()) {
+ String httpServiceName = requestTemplate.feignTarget().url();
+ URI uri = URI.create(httpServiceName);
+ // 截取http://provider-demo 为 provider-demo,
+ String serviceName = uri.getHost();
+
+ SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
+
+ if (TencentUnitContext.containRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY)) {
+ UnitArch.Gateway gateway = (UnitArch.Gateway) TencentUnitContext.getObjectRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY);
+ requestTemplate.target(uri.getScheme() + "//" + gateway.getServiceName());
+ }
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UnitRestTemplateInterceptor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UnitRestTemplateInterceptor.java
new file mode 100644
index 000000000..263a2e272
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UnitRestTemplateInterceptor.java
@@ -0,0 +1,74 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.instrument.resttemplate;
+
+import java.io.IOException;
+import java.net.URI;
+
+import com.tencent.cloud.common.constant.OrderConstant;
+import com.tencent.cloud.plugin.unit.utils.SpringCloudUnitUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitArch;
+
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Interceptor used for unit in RestTemplate.
+ *
+ * @author Shedfree Wu
+ */
+public class UnitRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered {
+
+ @Override
+ public int getOrder() {
+ return OrderConstant.Client.RestTemplate.UNIT_INTERCEPTOR_ORDER;
+ }
+
+ /**
+ * @see org.springframework.http.client.ClientHttpRequestInterceptor#intercept(org.springframework.http.HttpRequest, byte[], org.springframework.http.client.ClientHttpRequestExecution)
+ */
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+ return execution.execute(getHttpRequestWrapper(request), body);
+ }
+
+ public HttpRequest getHttpRequestWrapper(HttpRequest httpRequest) {
+ // 未开启单元化, 直接返回
+ if (!TencentUnitManager.isEnable()) {
+ return httpRequest;
+ }
+
+ String serviceName = httpRequest.getURI().getHost();
+ SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
+ // 不需要转发到网关, 直接返回
+ if (!TencentUnitContext.containRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY)) {
+ return httpRequest;
+ }
+
+ UnitArch.Gateway gateway = (UnitArch.Gateway) TencentUnitContext.getObjectRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY);
+ URI unitRouteUri = UriComponentsBuilder.fromUri(httpRequest.getURI()).host(gateway.getServiceName()).build()
+ .toUri();
+ return new UriModifyHttpRequestWrapper(httpRequest, unitRouteUri);
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UriModifyHttpRequestWrapper.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UriModifyHttpRequestWrapper.java
new file mode 100644
index 000000000..06ae77262
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/instrument/resttemplate/UriModifyHttpRequestWrapper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+package com.tencent.cloud.plugin.unit.instrument.resttemplate;
+
+import java.net.URI;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.support.HttpRequestWrapper;
+
+public class UriModifyHttpRequestWrapper extends HttpRequestWrapper {
+
+ private final URI uri;
+
+ public UriModifyHttpRequestWrapper(HttpRequest request, URI uri) {
+ super(request);
+ this.uri = uri;
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitClientFinallyEnhancedPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitClientFinallyEnhancedPlugin.java
new file mode 100644
index 000000000..a51c4fffb
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitClientFinallyEnhancedPlugin.java
@@ -0,0 +1,46 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.plugin;
+
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+
+import org.springframework.core.Ordered;
+
+public class UnitClientFinallyEnhancedPlugin implements EnhancedPlugin {
+
+ public UnitClientFinallyEnhancedPlugin() {
+ }
+
+ @Override
+ public EnhancedPluginType getType() {
+ return EnhancedPluginType.Client.FINALLY;
+ }
+
+ @Override
+ public void run(EnhancedPluginContext context) throws Throwable {
+ TencentUnitContext.removeAll();
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitFeignEnhancedPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitFeignEnhancedPlugin.java
new file mode 100644
index 000000000..88cb660d7
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitFeignEnhancedPlugin.java
@@ -0,0 +1,108 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.plugin;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.cloud.common.util.ReflectionUtils;
+import com.tencent.cloud.common.util.UrlUtils;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
+import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import feign.Request;
+import shade.polaris.com.google.common.collect.ImmutableMap;
+
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Pre EnhancedPlugin for feign to encode unit metadata.
+ *
+ * @author Shedfree Wu
+ */
+public class UnitFeignEnhancedPlugin implements EnhancedPlugin {
+ @Override
+ public EnhancedPluginType getType() {
+ return EnhancedPluginType.Client.BEFORE_CALLING;
+ }
+
+ @Override
+ public void run(EnhancedPluginContext context) throws Throwable {
+ if (!(context.getOriginRequest() instanceof Request request)) {
+ return;
+ }
+
+ TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
+ if (!com.tencent.polaris.api.utils.CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
+ buildMetadataHeader(request, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
+ }
+ }
+
+ /**
+ * Set metadata into the request header for {@link Request} .
+ * @param request instance of {@link Request}
+ * @param metadata metadata map .
+ * @param headerName target metadata http header name .
+ */
+ private void buildMetadataHeader(Request request, Map metadata, String headerName) {
+ if (!CollectionUtils.isEmpty(metadata)) {
+ buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
+ }
+ }
+
+
+ /**
+ * Set headerMap into the request header for {@link Request} .
+ * @param request instance of {@link Request}
+ * @param headerMap header map .
+ */
+ private void buildHeaderMap(Request request, Map headerMap) {
+ if (!CollectionUtils.isEmpty(headerMap)) {
+ Map> headers = getModifiableHeaders(request);
+ headerMap.forEach((key, value) -> headers.put(key, Collections.singletonList(UrlUtils.encode(value))));
+ }
+ }
+
+ /**
+ * The value obtained directly from the headers method is an unmodifiable map.
+ * If the Feign client uses the URL, the original headers are unmodifiable.
+ * @param request feign request
+ * @return modifiable headers
+ */
+ private Map> getModifiableHeaders(Request request) {
+ Map> headers;
+ headers = (Map>) ReflectionUtils.getFieldValue(request, "headers");
+
+ if (!(headers instanceof LinkedHashMap)) {
+ headers = new LinkedHashMap<>(headers);
+ ReflectionUtils.setFieldValue(request, "headers", headers);
+ }
+ return headers;
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitRestTemplateEnhancedPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitRestTemplateEnhancedPlugin.java
new file mode 100644
index 000000000..13d9414a9
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitRestTemplateEnhancedPlugin.java
@@ -0,0 +1,81 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.plugin;
+
+import java.util.Map;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.cloud.common.util.UrlUtils;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
+import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
+import com.tencent.polaris.api.utils.CollectionUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import shade.polaris.com.google.common.collect.ImmutableMap;
+
+import org.springframework.http.HttpRequest;
+
+/**
+ * Pre EnhancedPlugin for rest template to encode unit metadata.
+ *
+ * @author Shedfree Wu
+ */
+public class UnitRestTemplateEnhancedPlugin implements EnhancedPlugin {
+ @Override
+ public EnhancedPluginType getType() {
+ return EnhancedPluginType.Client.BEFORE_CALLING;
+ }
+
+ @Override
+ public void run(EnhancedPluginContext context) throws Throwable {
+ if (!(context.getOriginRequest() instanceof HttpRequest httpRequest)) {
+ return;
+ }
+
+ TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
+ if (!CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
+ buildMetadataHeader(httpRequest, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
+ }
+ }
+
+ private void buildHeaderMap(HttpRequest request, Map headerMap) {
+ if (!CollectionUtils.isEmpty(headerMap)) {
+ headerMap.forEach((key, value) -> request.getHeaders().set(key, UrlUtils.encode(value)));
+ }
+ }
+
+ /**
+ * Set metadata into the request header for {@link HttpRequest} .
+ *
+ * @param request instance of {@link HttpRequest}
+ * @param metadata metadata map .
+ * @param headerName target metadata http header name .
+ */
+ private void buildMetadataHeader(HttpRequest request, Map metadata, String headerName) {
+ if (!CollectionUtils.isEmpty(metadata)) {
+ buildHeaderMap(request, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitScgEnhancedPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitScgEnhancedPlugin.java
new file mode 100644
index 000000000..57ec49433
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitScgEnhancedPlugin.java
@@ -0,0 +1,86 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.plugin;
+
+import java.util.Map;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.cloud.common.util.UrlUtils;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
+import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import shade.polaris.com.google.common.collect.ImmutableMap;
+
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Pre EnhancedPlugin for scg to encode unit metadata.
+ *
+ * @author Shedfree Wu
+ */
+public class UnitScgEnhancedPlugin implements EnhancedPlugin {
+ @Override
+ public EnhancedPluginType getType() {
+ return EnhancedPluginType.Client.PRE;
+ }
+
+ @Override
+ public void run(EnhancedPluginContext context) throws Throwable {
+ if (!(context.getOriginRequest() instanceof ServerWebExchange exchange)) {
+ return;
+ }
+
+ // get request builder
+ ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
+
+ TencentUnitContext.UnitCompositeContextMap unitCompositeContextMap = TencentUnitContext.getCompositeContextMap();
+ if (!com.tencent.polaris.api.utils.CollectionUtils.isEmpty(unitCompositeContextMap.getSystemContext())) {
+ buildMetadataHeader(builder, unitCompositeContextMap.getSystemContext(), MetadataConstant.HeaderName.TSF_UNIT);
+ }
+
+ context.setOriginRequest(exchange.mutate().request(builder.build()).build());
+ }
+
+ private void buildHeaderMap(ServerHttpRequest.Builder builder, Map headerMap) {
+ if (!CollectionUtils.isEmpty(headerMap)) {
+ headerMap.forEach((key, value) -> builder.header(key, UrlUtils.encode(value)));
+ }
+ }
+
+ /**
+ * Set metadata into the request header for {@link ServerHttpRequest.Builder} .
+ * @param builder instance of {@link ServerHttpRequest.Builder}
+ * @param metadata metadata map .
+ * @param headerName target metadata http header name .
+ */
+ private void buildMetadataHeader(ServerHttpRequest.Builder builder, Map metadata, String headerName) {
+ if (!CollectionUtils.isEmpty(metadata)) {
+ buildHeaderMap(builder, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginOrderConstant.ClientPluginOrder.CONSUMER_UNIT_METADATA_PLUGIN_ORDER;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitServletPreEnhancedPlugin.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitServletPreEnhancedPlugin.java
new file mode 100644
index 000000000..00c523235
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/plugin/UnitServletPreEnhancedPlugin.java
@@ -0,0 +1,96 @@
+/*
+ * Tencent is pleased to support the open source community by making spring-cloud-tencent available.
+ *
+ * Copyright (C) 2021 Tencent. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package com.tencent.cloud.plugin.unit.plugin;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Optional;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPlugin;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginContext;
+import com.tencent.cloud.rpc.enhancement.plugin.EnhancedPluginType;
+import com.tencent.cloud.rpc.enhancement.plugin.PluginOrderConstant;
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitTagPosition;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UnitServletPreEnhancedPlugin implements EnhancedPlugin {
+ private static final Logger logger = LoggerFactory.getLogger(UnitServletPreEnhancedPlugin.class);
+
+ @Override
+ public EnhancedPluginType getType() {
+ return EnhancedPluginType.Server.PRE;
+ }
+
+ @Override
+ public void run(EnhancedPluginContext context) throws Throwable {
+ if (!(context.getOriginRequest() instanceof HttpServletRequest httpServletRequest)) {
+ return;
+ }
+
+ if (!TencentUnitManager.isEnable()) {
+ return;
+ }
+
+ String unitContextEncoded = httpServletRequest.getHeader(MetadataConstant.HeaderName.TSF_UNIT);
+ if (StringUtils.isNotEmpty(unitContextEncoded)) {
+ Map unitMap = JacksonUtils.deserialize2Map(URLDecoder.decode(unitContextEncoded, StandardCharsets.UTF_8));
+
+ TencentUnitContext.putSourceTags(unitMap);
+ if (TencentUnitManager.isContextPassingThroughEnabled()) {
+ // 不能传递所有的 key, 只需要传递客户要素和业务系统,灰度信息
+ Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER)).
+ ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, value));
+ Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM)).
+ ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, value));
+ Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO)).
+ ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, value));
+ }
+ else {
+ // 没有 passing through 标志,灰度信息也要透传
+ Optional.ofNullable(unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO)).
+ ifPresent(value -> TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, value));
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("[getSerializeTagsFromRequestMeta] unit context:{}",
+ TencentUnitContext.getCompositeContextMap());
+ }
+
+ for (String grayKey : TencentUnitManager.getGrayUnitHeaderKey()) {
+ String value = httpServletRequest.getHeader(grayKey);
+ // 非空 value 才设置,便于匹配灰度规则时能否直接跳过
+ if (StringUtils.isNotEmpty(value)) {
+ TencentUnitContext.putGrayUserTag(UnitTagPosition.HEADER.name(), grayKey, value);
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginOrderConstant.ServerPluginOrder.TRACE_SERVER_PRE_PLUGIN_ORDER;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/utils/SpringCloudUnitUtils.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/utils/SpringCloudUnitUtils.java
new file mode 100644
index 000000000..b042f21de
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/cloud/plugin/unit/utils/SpringCloudUnitUtils.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.cloud.plugin.unit.utils;
+
+import java.util.List;
+import java.util.Map;
+
+import com.tencent.cloud.common.constant.MetadataConstant;
+import com.tencent.cloud.common.metadata.MetadataContext;
+import com.tencent.cloud.common.metadata.MetadataContextHolder;
+import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
+import com.tencent.cloud.common.util.EnvironmentUtils;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClient;
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.UnitTagEngine;
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+import com.tencent.tsf.unit.core.model.RoutingUnit;
+import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
+import com.tencent.tsf.unit.core.model.UnitNamespace;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRoute;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRouteUnit;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
+import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
+import org.apache.commons.collections.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.cloud.client.ServiceInstance;
+
+public final class SpringCloudUnitUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SpringCloudUnitUtils.class);
+
+ private static PolarisDiscoveryClient discoveryClient;
+
+ private SpringCloudUnitUtils() {
+ }
+
+ /**
+ * 单元化才会进入这里, feign/rest template 请求前的预处理, 判断是否需要计算客户号, 是否转发网关, 判断结果记录在 TencentUnitContext.
+ */
+ public static void preRequestRecordUnitContext(String serviceName) {
+ if (discoveryClient == null) {
+ discoveryClient = ApplicationContextAwareUtils.getBean(PolarisDiscoveryClient.class);
+ }
+
+ TencentUnitContext.putSystemTagsFromUser();
+
+ String system = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
+ // 无论 gdu 还是 sdu, 都需要 system 信息,如果没有 system(返回 false),则直接返回
+ if (!checkSystem(system, serviceName)) {
+ return;
+ }
+
+ // 先进行灰度判断
+ RoutingUnit routingUnit = preRequestRecordGrayUnitContext();
+ String customerIdentifier = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER);
+
+ // 如果 dest unit id 存在,则直接设置 unit context 并返回
+ if (preRequestRecordIfDestUnitExist(system, customerIdentifier, serviceName)) {
+ return;
+ }
+
+ // 发生 gdu 容灾切换,gdu 不在同一个 cloud 的,直接设置 unit context 并返回
+ if (preRequestRecordIfGduNotInLocalCloud(serviceName)) {
+ return;
+ }
+
+ boolean toSdu = true;
+ String gduUnitId = getGduUnitId();
+
+ UnitNamespace gduNs = TencentUnitManager.getUnitNamespaceId(gduUnitId, system);
+ boolean gduNsNotExist = (gduNs == null || StringUtils.isEmpty(gduNs.getId()));
+
+ // preRequestRecordIfDestUnitExist 可能需要 gdu 的信息,所以需要这里进行解析 gdu 和设置
+ if (gduNsNotExist) {
+ if (StringUtils.isEmpty(customerIdentifier)) {
+ String msg = String.format("[preRequestRecordUnitContext] gdu system not exist:%s, and customerIdentifier is empty, serviceName:%s", system, serviceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ // 没有 gdu 时,肯定是 sdu forward
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.TRUE.toString());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.FALSE.toString());
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] gduNsNotExist. only forward to sdu, system:{}, service name:{}, unit context:{}",
+ system, serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ }
+ else {
+ // 有 gdu 对应 ns
+ // 没有客户要素,限定只转发到 gdu
+ if (StringUtils.isEmpty(customerIdentifier)) {
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.FALSE.toString());
+ // 前面校验了,这里直接 set context
+ setUnitContext(gduUnitId, gduNs, serviceName);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] cid or business system is empty. only forward to gdu, service name:{}, unit context:{}",
+ serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ return;
+ }
+
+ String gduServiceId = getGduServiceId(serviceName, gduNs.getId());
+ List gduServices = discoveryClient.getInstances(gduServiceId);
+ // gdu 有实例,直接转入 gdu,不需要计算客户号
+ if (CollectionUtils.isNotEmpty(gduServices)) {
+ toSdu = false;
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_INSTANCE_EXIST, Boolean.TRUE.toString());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_SDU_FORWARD_ONLY, Boolean.FALSE.toString());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
+ setUnitContext(gduUnitId, gduNs, serviceName);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] gdu first and exist instance. forward to gdu, service name:{}, unit context:{}",
+ serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ }
+ }
+
+ if (toSdu) {
+ String graySduUnitId = TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_SDU_UNIT_ID);
+ if (StringUtils.isNotEmpty(graySduUnitId)) {
+ // gray sdu, 直接获取 unit id, 不需要解析客户要素
+ checkUnitIdAndSetContext(graySduUnitId, system, serviceName);
+ }
+ else {
+ // 普通 sdu。灰度时可能计算过客户号,这里就不重复计算
+ if (routingUnit == null) {
+ routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
+ }
+ if (checkUnitIdAndSetContext(routingUnit, system, customerIdentifier, serviceName)) {
+ // 增加上下文,记录目标单元号和目标的shardingKey,传递到下游
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CUSTOMER_NUMBER, routingUnit.getCustomerNumber());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SHARDING_KEY, routingUnit.getShardingKey());
+ }
+ else {
+ // sdu 没有对应 ns,前面 gdu 没有对应实例,预期后面会提示没有实例的错误
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
+ }
+ }
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] do calculate customer num. service name:{}, unit context:{}",
+ serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ }
+ }
+
+ public static boolean checkSystem(String system, String serviceName) {
+ // 有业务系统时,才需要判断
+ if (StringUtils.isEmpty(system)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] system is empty, serviceName:{}", serviceName);
+ }
+ return false;
+ }
+
+ if (!TencentUnitManager.getBusinessSystemSet().contains(system)) {
+ String msg = String.format("[preRequestRecordUnitContext] system is empty or not exist:%s, serviceName:%s", system, serviceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ return true;
+ }
+
+ public static boolean preRequestRecordIfGduNotInLocalCloud(String serviceName) {
+ String gduUnitId = getGduUnitId();
+ boolean gduInLocalCloud = TencentUnitUtils.checkLocalCloudSpace(gduUnitId);
+
+ if (gduInLocalCloud) {
+ return false;
+ }
+
+ setCrossGduContext(gduUnitId, serviceName);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] gdu in other cloud, gdu unit id:{}, service name:{}, unit context:{}",
+ gduUnitId, serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ return true;
+ }
+
+ public static boolean preRequestRecordIfDestUnitExist(String system, String customerIdentifier, String serviceName) {
+ String destUnitId = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
+ if (StringUtils.isNotEmpty(destUnitId)) {
+ String failoverUnitId = TencentUnitManager.getFailoverUnitIdMap().get(destUnitId);
+ if (StringUtils.isNotEmpty(failoverUnitId)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] use failover unit id:{}, origin unit id:{}, service name:{}",
+ failoverUnitId, destUnitId, serviceName);
+ }
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, failoverUnitId);
+ destUnitId = failoverUnitId;
+ }
+
+ if (checkUnitIdAndSetContext(destUnitId, system, serviceName)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordUnitContext] unit id exist:{}. service name:{}, unit context:{}",
+ destUnitId, serviceName, TencentUnitContext.getCompositeContextMap());
+ }
+ return true;
+ }
+ else {
+ String msg = String.format("[preRequestRecordUnitContext] destUnitId ns not exist:%s, customerIdentifier:%s,"
+ + " system:%s, serviceName:%s", destUnitId, customerIdentifier, system, serviceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 灰度 unit context 预处理.
+ * @return 如果需要计算客户号,则返回 RoutingUnit,避免重复计算
+ */
+ public static RoutingUnit preRequestRecordGrayUnitContext() {
+ String sourceGrayUnitInfo = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO);
+
+ if (StringUtils.isEmpty(sourceGrayUnitInfo) && CollectionUtils.isEmpty(TencentUnitManager.getGrayUnitRoutes())) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordGrayUnitContext] empty header gray info and empty gray unit routes,"
+ + "unitContextMap:{}", TencentUnitContext.getCompositeContextMap());
+ }
+ return null;
+ }
+
+ if (StringUtils.isNotEmpty(sourceGrayUnitInfo)) {
+ // 已经是灰度单元的,直接转
+ List grayMatchRouteUnitList = TencentUnitContext.parseGrayMatchRouteUnitList();
+ TencentUnitContext.setGrayUnitContext(grayMatchRouteUnitList);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordGrayUnitContext] using header gray, unitContextMap:{}",
+ TencentUnitContext.getCompositeContextMap());
+ }
+ return null;
+ }
+
+ // 判断是否存在潜在的 gray context,如果不存在则一定不是灰度
+ if (TencentUnitContext.grayContextIsEmpty()) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordGrayUnitContext] empty gray header, unitContextMap:{}",
+ TencentUnitContext.getCompositeContextMap());
+ }
+ return null;
+ }
+ // 存在灰度规则的相关 header,判断是否匹配
+ GrayMatchRoute grayMatchRoute = null;
+ RoutingUnit routingUnit = null;
+ String customerIdentifier = TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER);
+
+ for (GrayUnitRoute grayUnitRoute : TencentUnitManager.getGrayUnitRoutes()) {
+ if (UnitTagEngine.checkGrayUnitRouteHit(grayUnitRoute)) {
+ boolean match = false;
+ if (Boolean.TRUE.equals(grayUnitRoute.getMatch().getGrayListEnabled())) {
+ // 还需要命中需要灰度名单
+ if (StringUtils.isNotEmpty(customerIdentifier)) {
+ if (routingUnit == null) {
+ // 客户号只获取一次
+ routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
+ }
+ if (routingUnit != null && TencentUnitManager.getUnitGrayIds()
+ .contains(routingUnit.getCustomerNumber())) {
+ match = true;
+ }
+ else {
+ LOGGER.debug("[preRequestRecordGrayUnitContext] match routes, not in gray list");
+ }
+ }
+ }
+ else {
+ match = true;
+ }
+ if (match) {
+ grayMatchRoute = grayUnitRoute.getRoute();
+ break;
+ }
+ }
+ }
+ // 匹配
+ if (grayMatchRoute != null) {
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO,
+ JacksonUtils.serialize2Json(grayMatchRoute.getUnits()));
+ TencentUnitContext.setGrayUnitContext(grayMatchRoute.getUnits());
+ }
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[preRequestRecordGrayUnitContext] grayMatchRoute:{}, unitContextMap:{}",
+ grayMatchRoute, TencentUnitContext.getCompositeContextMap());
+ }
+ return routingUnit;
+ }
+
+ /**
+ * 如果没有 gdu 对应 ns,返回 null;如果目标 ns 为当前 ns,返回 serviceName;其他 ns 则返回 nsId/serviceName.
+ */
+ public static String getGduServiceId(String serviceName, String gduNsId) {
+ if (StringUtils.isEmpty(gduNsId)) {
+ return null;
+ }
+ if (TencentUnitManager.checkLocalNamespace(gduNsId)) {
+ return serviceName;
+ }
+ else {
+ return gduNsId + "/" + serviceName;
+ }
+ }
+
+ public static String getGduUnitId() {
+ String gduUnitId;
+ if (StringUtils.isNotEmpty(TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_GDU_UNIT_ID))) {
+ gduUnitId = TencentUnitContext.getGraySystemTag(TencentUnitContext.GRAY_CLOUD_SPACE_GDU_UNIT_ID);
+ }
+ else {
+ gduUnitId = TencentUnitManager.getGduUnitId();
+ }
+ return gduUnitId;
+ }
+
+ /**
+ * 【网关使用该方法】从consumer过来的feign或restTemplate调用/或其他 cloud gw 转过来的,从header中取已经计算好的ns以及token做校验.
+ */
+ public static void processFromUnitHeader(Map unitMap, String path) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[getProxyInfoFromUnitHeader] parse unit from header. path:{}, origin unit map:{}", path, unitMap);
+ }
+
+ String targetCloudId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD);
+ if (TencentUnitManager.isLocalCloudSpace(targetCloudId)) {
+ updateLocalCloudUnitContext(unitMap);
+ }
+ else {
+ updateCrossCloudUnitContext(unitMap);
+ }
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[getProxyInfoFromUnitHeader] parse unit from header. path:{}, unit context:{}",
+ path, TencentUnitContext.getCompositeContextMap());
+ }
+ }
+
+ /**
+ * 下一跳是其他 cloud 的 gw.
+ */
+ private static void updateCrossCloudUnitContext(Map unitMap) {
+ String destUnitId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
+ String destServiceName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE);
+ String destSystem = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
+
+ if (!TencentUnitManager.checkUnitId(destUnitId)) {
+ // 非当前 cloud, 只需要检查是否存在
+ String msg = String.format("[getProxyInfoFromUnitHeader] check unit id error, destUnitId:%s, destServiceName:%s",
+ destUnitId, destServiceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+
+ unitMap.put(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap().get(destUnitId));
+
+ Gateway gateway = getGateway(destUnitId, destSystem);
+ TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
+ TencentUnitContext.putSystemTags(unitMap);
+ }
+
+ /**
+ * 下一跳是本 cloud 的服务.
+ */
+ private static void updateLocalCloudUnitContext(Map unitMap) {
+ String destUnitId = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
+ String destServiceName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE);
+
+ if (StringUtils.isEmpty(destUnitId)) {
+ TencentUnitContext.putSystemTags(unitMap);
+ // 是否跨 cloud gdu 优先访问,此时没有目标单元
+ String grayUnitInfo = unitMap.get(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO);
+ // 设置灰度上游
+ if (StringUtils.isNotEmpty(grayUnitInfo)) {
+ TencentUnitContext.putSourceTag(TencentUnitContext.CLOUD_SPACE_GRAY_UNIT_INFO, grayUnitInfo);
+ }
+ SpringCloudUnitUtils.preRequestRecordUnitContext(destServiceName);
+ }
+ else {
+ String destSystemName = unitMap.get(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM);
+
+ // 在当前 cloud, check ns
+ UnitNamespace namespace = TencentUnitManager.getUnitNamespaceId(destUnitId, destSystemName);
+ if (namespace == null) {
+ String msg = String.format("[getProxyInfoFromUnitHeader] check ns error, destUnitId:%s, destServiceName:%s",
+ destUnitId, destServiceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ TencentUnitContext.putSystemTags(unitMap);
+ TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID, namespace.getId());
+ }
+ }
+
+ /**
+ * 【网关使用该方法】通过http直接调用网关,从header获取 cid 并解析.
+ */
+ public static void processFromCid(String cid, String path) {
+
+ //url:/groupContext/system/ms/api
+ String serviceName;
+ String systemName;
+ String[] pathSegments = path.split("/");
+ if (pathSegments.length >= 5) {
+ serviceName = pathSegments[3];
+ systemName = pathSegments[2];
+ if (StringUtils.isEmpty(serviceName) || StringUtils.isEmpty(systemName)) {
+ String msg = String.format("[checkUnitFromHttp] serviceName:%s or systemName:%s is empty", serviceName, systemName);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ }
+ else {
+ String msg = "[checkUnitFromHttp] path pattern is wrong, path:" + path;
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, cid);
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, systemName);
+
+ SpringCloudUnitUtils.preRequestRecordUnitContext(serviceName);
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[getProxyInfoFromCid] parse unit from cid. path:{}, unit context:{}",
+ path, TencentUnitContext.getCompositeContextMap());
+ }
+ }
+
+ private static boolean checkUnitIdAndSetContext(RoutingUnit routingUnit, String system, String customerIdentifier, String serviceName) {
+ if (routingUnit == null || StringUtils.isEmpty(routingUnit.getUnitId())) {
+ String msg = String.format("[preRequestRecordUnitContext] routingUnit unit id not exist, customerIdentifier:%s, system:%s, serviceName:%s", customerIdentifier, system, serviceName);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+
+ return checkUnitIdAndSetContext(routingUnit.getUnitId(), system, serviceName);
+ }
+
+ private static boolean checkUnitIdAndSetContext(String unitId, String system, String serviceName) {
+ if (TencentUnitUtils.checkLocalCloudSpace(unitId)) {
+ // 本地,检查 ns 是否存在
+ UnitNamespace namespace = TencentUnitManager.getUnitNamespaceId(unitId, system);
+ if (namespace != null) {
+ setUnitContext(unitId, namespace, serviceName);
+ // 设置治理 ns
+ MetadataContext metadataContext = MetadataContextHolder.get();
+ metadataContext.putFragmentContext(MetadataContext.FRAGMENT_APPLICATION_NONE,
+ MetadataConstant.POLARIS_TARGET_NAMESPACE, namespace.getId());
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ else if (TencentUnitManager.checkUnitId(unitId)) {
+ // 异地,检查 unitId 是否存在
+ setUnitContext(unitId, serviceName);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ private static void setUnitContext(String unitId, String serviceName) {
+ setUnitContext(unitId, null, serviceName);
+ }
+
+ private static void setUnitContext(String unitId, UnitNamespace namespace, String serviceName) {
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, unitId);
+ if (namespace != null) {
+ TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID, namespace.getId());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_ID, namespace.getId());
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_NAMESPACE_NAME, namespace.getName());
+ }
+
+ // 不同 cloud, 需要转到对应 gateway
+ if (!TencentUnitUtils.checkLocalCloudSpace(unitId)) {
+ Gateway gateway = getGateway(unitId);
+
+ TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
+
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap()
+ .get(unitId));
+ }
+ }
+
+ /**
+ * 设置跨 cloud gdu 的上下文, 然后再 gdu first 访问,暂不确定目标单元.
+ */
+ private static void setCrossGduContext(String unitId, String serviceName) {
+ Gateway gateway = getGateway(unitId);
+ TencentUnitContext.putRouteTag(TencentUnitContext.CLOUD_SPACE_ROUTE_GATEWAY, gateway);
+
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SERVICE, serviceName);
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_CLOUD, TencentUnitManager.getUnitCloudMap()
+ .get(unitId));
+ }
+
+ private static Gateway getGateway(String unitId) {
+ return getGateway(unitId, TencentUnitContext.getSystemTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM));
+ }
+
+ private static Gateway getGateway(String unitId, String system) {
+ String targetGatewayCloudId = TencentUnitManager.getUnitCloudMap().get(unitId);
+ if (!EnvironmentUtils.isGateway()) {
+ targetGatewayCloudId = TencentUnitManager.getLocalCloudSpaceId();
+ }
+ Gateway result = TencentUnitManager.getBusinessSystemGateway(targetGatewayCloudId, system);
+ if (result == null) {
+ String msg = String.format("[getGateway] gateway not exist, targetGatewayCloudId:%s, unitId:%s, system:%s", targetGatewayCloudId, unitId, system);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, msg);
+ }
+ return result;
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java
similarity index 85%
rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java
rename to spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java
index 1172fdd91..0da583fdc 100644
--- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/EnableTsfUnit.java
@@ -25,15 +25,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/**
- * Empty annotation. Compatible with old versions TSF SDK.
- *
- * @author Haotian Zhang
- */
-@Deprecated(since = "2.0.0.0")
+import com.tencent.cloud.plugin.unit.config.UnitAutoConfiguration;
+
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
+@ImportAutoConfiguration({UnitAutoConfiguration.class})
public @interface EnableTsfUnit {
}
diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java
similarity index 90%
rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java
rename to spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java
index c1464a26f..2dd0eb857 100644
--- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCall.java
@@ -24,12 +24,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/**
- * Empty annotation. Compatible with old versions TSF SDK.
- *
- * @author Haotian Zhang
- */
-@Deprecated(since = "2.0.0.0")
+
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java
similarity index 89%
rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java
rename to spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java
index ad23911af..a18318bf7 100644
--- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitCustomerIdentifier.java
@@ -24,12 +24,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-/**
- * Empty annotation. Compatible with old versions TSF SDK.
- *
- * @author Haotian Zhang
- */
-@Deprecated(since = "2.0.0.0")
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitLocalCall.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitLocalCall.java
similarity index 100%
rename from spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitLocalCall.java
rename to spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/annotation/TsfUnitLocalCall.java
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/aspect/TsfUnitRouteAspect.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/aspect/TsfUnitRouteAspect.java
new file mode 100644
index 000000000..35f95b06b
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/aspect/TsfUnitRouteAspect.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.aspect;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import com.tencent.tsf.unit.annotation.TsfUnitCall;
+import com.tencent.tsf.unit.annotation.TsfUnitCustomerIdentifier;
+import com.tencent.tsf.unit.core.utils.TencentUnitUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 拦截单元化相关的注解.
+ */
+@Aspect
+public class TsfUnitRouteAspect {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TsfUnitRouteAspect.class);
+
+ public TsfUnitRouteAspect() {
+ LOG.info("init TsfUnitRouteAspect");
+ }
+
+ // 拦截@FeignClient+@TsfUnitCall的对象
+ @Pointcut("@within(com.tencent.tsf.unit.annotation.TsfUnitCall) &&" +
+ "@within(org.springframework.cloud.openfeign.FeignClient)")
+ public void unitRoutePointcut() {
+ }
+
+ @Around("unitRoutePointcut()")
+ public Object unitRouteProcess(ProceedingJoinPoint joinPoint) throws Throwable {
+ try {
+ // 获取拦截点(pointcut)的函数签名
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ // 获取拦截点的类名
+ String pointClassName = methodSignature.getDeclaringTypeName();
+ // 获取拦截点所属类名的Class
+ Class> pointCutClass = Class.forName(pointClassName);
+ // 获取拦截点所在类Class上的注解,获取本地调用注解@TsfUnitCall
+ TsfUnitCall tsfUnitCallClass = pointCutClass.getAnnotation(TsfUnitCall.class);
+
+ // 获取单元化注解上的systemName
+ String systemName = tsfUnitCallClass.systemName();
+ boolean global = tsfUnitCallClass.global();
+ // 获取在拦截点对象上执行的方法
+ Method pointCutMethod = methodSignature.getMethod();
+ // 获取方法上所有参数的注解,找到@TsfUnitUserTag的修饰的参数,作为用户标签(客户号)取出来
+ String cid = getCustomerIdentifier(pointCutMethod.getParameterAnnotations(), joinPoint.getArgs());
+
+ // 写入Context
+ TencentUnitUtils.putCustomerTags(systemName, cid, global);
+
+ return joinPoint.proceed();
+ }
+ catch (Throwable e) {
+ LOG.error("[TsfUnitRoute] aspect catch exception: " + e.getMessage());
+ throw e;
+ }
+ }
+
+ // 获取参数里面里有userTag相关注解的参数
+ private String getCustomerIdentifier(Annotation[][] annotations, Object[] args) {
+ // 找到TsfUnitUserTag注解的位置
+ int index = -1;
+ for (int i = 0; i < annotations.length; i++) {
+ for (Annotation annotation : annotations[i]) {
+ if (annotation.annotationType() == TsfUnitCustomerIdentifier.class) {
+ index = i;
+ break;
+ }
+ }
+ if (index >= 0) {
+ break;
+ }
+ }
+
+ if (index >= 0 && index < args.length) {
+ Object object = args[index];
+ if (object.getClass().equals(String.class)) {
+ return (String) object;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/Env.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/Env.java
new file mode 100644
index 000000000..5ba69ed79
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/Env.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import com.tencent.polaris.api.utils.IPAddressUtils;
+
+public final class Env {
+
+ private Env() {
+ }
+
+ private final static String consulToken;
+
+ private final static String consulHost;
+
+ private final static Integer consulPort;
+
+ private final static String namespaceId;
+
+ static {
+ // 只支持从环境变量取
+ consulHost = IPAddressUtils.getIpCompatible(getSystemProperty("tsf_consul_ip", "localhost"));
+ consulPort = Integer.parseInt(getSystemProperty("tsf_consul_port", "8500"));
+ consulToken = getSystemProperty("tsf_token", "");
+ namespaceId = getSystemProperty("tsf_namespace_id", "");
+ }
+
+ private static String getSystemProperty(String name, String defaultValue) {
+ String val = null;
+ if (System.getenv(name) != null) {
+ val = System.getenv(name);
+ }
+ if (System.getProperty(name) != null) {
+ val = System.getProperty(name);
+ }
+ return (val == null) ? defaultValue : val;
+ }
+
+ public static String getConsulToken() {
+ return consulToken;
+ }
+
+ public static String getConsulHost() {
+ return consulHost;
+ }
+
+ public static Integer getConsulPort() {
+ return consulPort;
+ }
+
+ public static String getNamespaceId() {
+ return namespaceId;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/GatewayUnitArchCallback.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/GatewayUnitArchCallback.java
new file mode 100644
index 000000000..b51c7b7a8
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/GatewayUnitArchCallback.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.Map;
+import java.util.Objects;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GatewayUnitArchCallback implements IUnitChangeCallback {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(GatewayUnitArchCallback.class);
+ private final String applicationName;
+
+ public GatewayUnitArchCallback(String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ @Override
+ public void callback() {
+ boolean enableUnit = false;
+ for (Map.Entry entry : TencentUnitManager.getLocalBusinessSystemGwMap().entrySet()) {
+ Gateway gateway = entry.getValue();
+ if (StringUtils.equals(applicationName, gateway.getServiceName())
+ && StringUtils.equals(Env.getNamespaceId(), gateway.getNamespaceId())) {
+ enableUnit = true;
+ break;
+ }
+ }
+ if (!enableUnit) {
+ for (Map.Entry entry: TencentUnitManager.getLocalOnlyGwMap().entrySet()) {
+ Gateway gateway = entry.getValue();
+ if (StringUtils.equals(applicationName, gateway.getServiceName())
+ && StringUtils.equals(Env.getNamespaceId(), gateway.getNamespaceId())) {
+ enableUnit = true;
+ break;
+ }
+ }
+ }
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[callback] enableUnit:{}, applicationName:{}, ns:{}, localBusinessSystemGwMap:{}, localOnlyGwMap:{}",
+ enableUnit, applicationName, Env.getNamespaceId(), TencentUnitManager.getLocalBusinessSystemGwMap(), TencentUnitManager.getLocalOnlyGwMap());
+ }
+ if (TencentUnitManager.isEnable() != enableUnit) {
+ LOGGER.info("[callback] unit enable change, new status:{}", enableUnit);
+ }
+ TencentUnitManager.setEnable(enableUnit);
+ }
+
+ @Override
+ public String getName() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof GatewayUnitArchCallback that)) {
+ return false;
+ }
+ return StringUtils.equals(applicationName, that.applicationName) && StringUtils.equals(getName(), that.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getName(), applicationName);
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/IUnitChangeCallback.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/IUnitChangeCallback.java
new file mode 100644
index 000000000..87bcd7f13
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/IUnitChangeCallback.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+public interface IUnitChangeCallback {
+
+ void callback();
+
+ String getName();
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/MappingServiceLoader.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/MappingServiceLoader.java
new file mode 100644
index 000000000..cfe13d562
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/MappingServiceLoader.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import com.tencent.tsf.unit.core.mapping.api.IMappingService;
+import com.tencent.tsf.unit.core.mapping.impl.CustomerMappingService;
+
+/**
+ * 找到MappingService的实现.
+ */
+public final class MappingServiceLoader {
+
+ private MappingServiceLoader() {
+ }
+
+ private static IMappingService service;
+
+ static {
+ ServiceLoader mappingServices = ServiceLoader.load(IMappingService.class);
+ if (mappingServices != null) {
+ Iterator itr = mappingServices.iterator();
+ while (itr.hasNext()) {
+ service = itr.next();
+ }
+ }
+
+ // 默认实现
+ if (service == null) {
+ service = new CustomerMappingService();
+ }
+ }
+
+ public static IMappingService getService() {
+ return service;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitContext.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitContext.java
new file mode 100644
index 000000000..54ae9ccbc
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitContext.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.tencent.cloud.common.util.JacksonUtils;
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayMatchRouteUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TencentUnitContext {
+
+ /**
+ * 内部的 key 都以 CloudSpace 开头.
+ */
+ public static final String CLOUD_SPACE_TARGET_UNIT_ID = "CloudSpaceTargetUnitId";
+ /**
+ * 是否限定转发到 gdu 服务,true/false.
+ */
+ public static final String CLOUD_SPACE_GDU_FORWARD_ONLY = "CloudSpaceGduFrowardOnly";
+ /**
+ * 是否限定转发到 sdu 服务,true/false,内部变量,可能会调整.
+ */
+ public static final String CLOUD_SPACE_SDU_FORWARD_ONLY = "CloudSpaceSduFrowardOnly";
+ /**
+ * CLOUD_SPACE_GDU_INSTANCE_EXIST.
+ */
+ public static final String CLOUD_SPACE_GDU_INSTANCE_EXIST = "CloudSpaceGduInstanceExist";
+ /**
+ * 目标 cloud space,用于判断是否转发到下一个网关,网关时判断.
+ */
+ public static final String CLOUD_SPACE_TARGET_CLOUD = "CloudSpaceTargetCloud";
+ /**
+ * 目标 ns id,如果是 gdu forward only,这里的目标 ns 即 gdu ns.
+ */
+ public static final String CLOUD_SPACE_TARGET_NAMESPACE_ID = "CloudSpaceTargetNamespaceId";
+ /**
+ * CLOUD_SPACE_TARGET_NAMESPACE_NAME.
+ */
+ public static final String CLOUD_SPACE_TARGET_NAMESPACE_NAME = "CloudSpaceTargetNamespaceName";
+ /**
+ * 客户要素传递.
+ */
+ public static final String CLOUD_SPACE_CUSTOMER_IDENTIFIER = "CloudSpaceCustomerIdentifier";
+ /**
+ * 业务系统传递.
+ */
+ public static final String CLOUD_SPACE_TARGET_SYSTEM = "CloudSpaceTargetSystem";
+ /**
+ * gw转发时用, 目标 ms.
+ */
+ public static final String CLOUD_SPACE_TARGET_SERVICE = "CloudSpaceTargetService";
+ /**
+ * 灰度单元信息,进入了灰度单元,将 GrayMatchRouteUnit list 的 json 序列化设置进去.
+ */
+ public static final String CLOUD_SPACE_GRAY_UNIT_INFO = "CloudSpaceGrayUnitInfo";
+ /**
+ * GrayCloudSpaceGduUnitId.
+ */
+ public static final String GRAY_CLOUD_SPACE_GDU_UNIT_ID = "GrayCloudSpaceGduUnitId";
+ /**
+ * GrayCloudSpaceSduUnitId.
+ */
+ public static final String GRAY_CLOUD_SPACE_SDU_UNIT_ID = "GrayCloudSpaceSduUnitId";
+ /**
+ * 根据目标客户要素计算出的目标客户号.
+ */
+ public static final String CLOUD_SPACE_TARGET_CUSTOMER_NUMBER = "CloudSpaceTargetCustomerNumber";
+ /**
+ * 根据目标客户号计算出的ShardingKey,后续通过这个ShardingKey可以算出路由到目标单元号.
+ */
+ public static final String CLOUD_SPACE_TARGET_SHARDING_KEY = "CloudSpaceTargetShardingKey";
+ /**
+ * 路由上下文标签,路由网关,如果 value 不为空,则需要转发到网关.
+ */
+ public static final String CLOUD_SPACE_ROUTE_GATEWAY = "CloudSpaceRouteGateway";
+ /**
+ * CloudSpaceRouteTargetNamespaceId.
+ */
+ public static final String CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID = "CloudSpaceRouteTargetNamespaceId";
+ /**
+ * 需要从 user context 转移到 system context 的 key.
+ */
+ public static final Set CLOUD_SPACE_SYSTEM_FROM_USER_KEYS = new HashSet<>(
+ Arrays.asList(CLOUD_SPACE_TARGET_SYSTEM, CLOUD_SPACE_CUSTOMER_IDENTIFIER, CLOUD_SPACE_TARGET_UNIT_ID));
+ /**
+ * SOURCE_PREFIX.
+ */
+ public static final String SOURCE_PREFIX = "source.";
+ /**
+ * GRAY_PREFIX.
+ */
+ public static final String GRAY_PREFIX = "gray.";
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitContext.class);
+ // 用户标签,客户可以直接设置的
+ private final static ThreadLocal> USER_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+ // 系统标签,用于中间的计算,部分需要传递
+ private final static ThreadLocal> SYSTEM_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+ // 路由标签, key 为 CloudSpaceRoute 前缀的,提供给 TsfConsulReactiveCommonDiscoveryClient 做服务发现使用,无需传递
+ private final static ThreadLocal> ROUTE_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+ // 上游标签,不需要放到 header 传递
+ private final static ThreadLocal> SOURCE_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+ // 用户灰度标签,用于匹配灰度规则,不需要放到 header 传递
+ private final static ThreadLocal> GRAY_USER_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+ // 灰度内部标签,不需要放到 header 传递
+ private final static ThreadLocal> GRAY_SYSTEM_CONTEXTS = ThreadLocal.withInitial(HashMap::new);
+
+ private TencentUnitContext() {
+ }
+
+ @Deprecated
+ public static void putTag(String key, String value) {
+ putUserTag(key, value);
+ }
+
+ public static void putUserTag(String key, String value) {
+ USER_CONTEXTS.get().put(key, value);
+ }
+
+ public static void putSystemTag(String key, String value) {
+ SYSTEM_CONTEXTS.get().put(key, value);
+ }
+
+ public static void putRouteTag(String key, Object value) {
+ ROUTE_CONTEXTS.get().put(key, value);
+ }
+
+ public static void putSourceTag(String key, String value) {
+ SOURCE_CONTEXTS.get().put(SOURCE_PREFIX + key, value);
+ }
+
+ public static void putGrayUserTags(String position, Map labels) {
+ for (Map.Entry label : labels.entrySet()) {
+ putGrayUserTag(position, label.getKey(), label.getValue());
+ }
+ }
+
+ // 需要携带 position,做灰度路由匹配用的
+ public static void putGrayUserTag(String position, String key, String value) {
+ GRAY_USER_CONTEXTS.get().put(getGrayPositionPrefix(position) + key, value);
+ }
+
+ // 传递灰度单元信息用的
+ public static void putGraySystemTag(String key, String value) {
+ GRAY_SYSTEM_CONTEXTS.get().put(getGrayPrefix() + key, value);
+ }
+
+ public static String getGraySystemTag(String key) {
+ return GRAY_SYSTEM_CONTEXTS.get().get(getGrayPrefix() + key);
+ }
+
+ public static boolean grayContextIsEmpty() {
+ return GRAY_USER_CONTEXTS.get().isEmpty();
+ }
+
+ public static String getGrayPositionPrefix(String position) {
+ return GRAY_PREFIX + position.toLowerCase(Locale.ROOT) + ".";
+ }
+
+ public static String getGrayPrefix() {
+ return GRAY_PREFIX;
+ }
+
+ public static void putSystemTagsFromUser() {
+ for (String key : CLOUD_SPACE_SYSTEM_FROM_USER_KEYS) {
+ if (USER_CONTEXTS.get().containsKey(key)) {
+ SYSTEM_CONTEXTS.get().put(key, USER_CONTEXTS.get().get(key));
+ }
+ }
+ }
+
+ public static void putSystemTags(Map tags) {
+ for (Map.Entry tag : tags.entrySet()) {
+ SYSTEM_CONTEXTS.get().put(tag.getKey(), tag.getValue());
+ }
+ }
+
+ public static void putSourceTags(Map tags) {
+ for (Map.Entry tag : tags.entrySet()) {
+ putSourceTag(tag.getKey(), tag.getValue());
+ }
+ }
+
+ @Deprecated
+ public static String getTag(String key) {
+ return getUserTag(key);
+ }
+
+ public static String getUserTag(String key) {
+ return USER_CONTEXTS.get().get(key);
+ }
+
+ public static String getSystemTag(String key) {
+ return SYSTEM_CONTEXTS.get().get(key);
+ }
+
+ public static Object getObjectRouteTag(String key) {
+ return ROUTE_CONTEXTS.get().get(key);
+ }
+
+ public static String getStringRouteTag(String key) {
+ return (String) ROUTE_CONTEXTS.get().get(key);
+ }
+
+ public static boolean containRouteTag(String key) {
+ return ROUTE_CONTEXTS.get().containsKey(key);
+ }
+
+ public static void removeAll() {
+ USER_CONTEXTS.get().clear();
+ SYSTEM_CONTEXTS.get().clear();
+ ROUTE_CONTEXTS.get().clear();
+ SOURCE_CONTEXTS.get().clear();
+ GRAY_USER_CONTEXTS.get().clear();
+ GRAY_SYSTEM_CONTEXTS.get().clear();
+ }
+
+ public static void clearGrayUserContext() {
+ GRAY_USER_CONTEXTS.get().clear();
+ }
+
+ /**
+ * 清理客户直接设置或直接影响的 key.
+ */
+ public static void clearUserTags() {
+ USER_CONTEXTS.get().remove(CLOUD_SPACE_TARGET_UNIT_ID);
+ USER_CONTEXTS.get().remove(CLOUD_SPACE_CUSTOMER_IDENTIFIER);
+ USER_CONTEXTS.get().remove(CLOUD_SPACE_TARGET_SYSTEM);
+ }
+
+ public static void setUnitCompositeContextMap(UnitCompositeContextMap unitCompositeContextMap) {
+ removeAll();
+
+ USER_CONTEXTS.get().putAll(unitCompositeContextMap.getUserContext());
+ SYSTEM_CONTEXTS.get().putAll(unitCompositeContextMap.getSystemContext());
+ SOURCE_CONTEXTS.get().putAll(unitCompositeContextMap.getSourceContext());
+ GRAY_USER_CONTEXTS.get().putAll(unitCompositeContextMap.getGrayUserContext());
+ GRAY_SYSTEM_CONTEXTS.get().putAll(unitCompositeContextMap.getGraySystemContext());
+ }
+
+
+ public static UnitCompositeContextMap getCompositeContextMap() {
+ return new UnitCompositeContextMap(USER_CONTEXTS.get(), SYSTEM_CONTEXTS.get(), ROUTE_CONTEXTS.get(),
+ SOURCE_CONTEXTS.get(), GRAY_USER_CONTEXTS.get(), GRAY_SYSTEM_CONTEXTS.get());
+ }
+
+ public static String getSourceTag(String key) {
+ return SOURCE_CONTEXTS.get().get(SOURCE_PREFIX + key);
+ }
+
+ public static String getGrayTag(String position, String key) {
+ return GRAY_USER_CONTEXTS.get().get(getGrayPositionPrefix(position) + key);
+ }
+
+ public static List parseGrayMatchRouteUnitList() {
+ String json = getSourceTag(CLOUD_SPACE_GRAY_UNIT_INFO);
+ List result = null;
+
+ if (StringUtils.isNotEmpty(json)) {
+ result = JacksonUtils.deserialize(json, new TypeReference>() { });
+ }
+ return result;
+ }
+
+ public static void setGrayUnitContext(List grayMatchRouteUnitList) {
+ // 目前应该只有一个 gdu 和 sdu
+ if (grayMatchRouteUnitList != null && grayMatchRouteUnitList.size() == 2) {
+ // 需要根据 unit id + system 才能获取 ns 信息,这里设置 unit id
+ putGraySystemTag(GRAY_CLOUD_SPACE_GDU_UNIT_ID, grayMatchRouteUnitList.get(0).getId());
+ putGraySystemTag(GRAY_CLOUD_SPACE_SDU_UNIT_ID, grayMatchRouteUnitList.get(1).getId());
+
+ // 以 gray 信息为准,优先级高,清空可能存在的 target unit id
+ putSystemTag(CLOUD_SPACE_TARGET_UNIT_ID, null);
+ }
+ else {
+ LOGGER.warn("[setGrayUnitContext] gray route format error:{}", grayMatchRouteUnitList);
+ }
+
+ }
+
+ public static class UnitCompositeContextMap {
+ private Map userContext;
+
+ private Map systemContext;
+
+ private Map routeContext;
+
+ private Map sourceContext;
+
+ private Map grayUserContext;
+
+ private Map graySystemContext;
+
+ public UnitCompositeContextMap() {
+ this.userContext = Collections.emptyMap();
+ this.systemContext = Collections.emptyMap();
+ this.routeContext = Collections.emptyMap();
+ this.sourceContext = Collections.emptyMap();
+ this.grayUserContext = Collections.emptyMap();
+ this.graySystemContext = Collections.emptyMap();
+ }
+
+ public UnitCompositeContextMap(Map userContext,
+ Map systemContext, Map routeContext, Map sourceContext,
+ Map grayUserContext, Map graySystemContext) {
+ this.userContext = userContext;
+ this.systemContext = systemContext;
+ this.routeContext = routeContext;
+ this.sourceContext = sourceContext;
+ this.grayUserContext = grayUserContext;
+ this.graySystemContext = graySystemContext;
+ }
+
+ public Map getUserContext() {
+ return userContext;
+ }
+
+ public void setUserContext(Map userContext) {
+ this.userContext = userContext;
+ }
+
+ public Map getSystemContext() {
+ return systemContext;
+ }
+
+ public void setSystemContext(Map systemContext) {
+ this.systemContext = systemContext;
+ }
+
+ public Map getRouteContext() {
+ return routeContext;
+ }
+
+ public void setRouteContext(Map routeContext) {
+ this.routeContext = routeContext;
+ }
+
+ public Map getSourceContext() {
+ return sourceContext;
+ }
+
+ public void setSourceContext(Map sourceContext) {
+ this.sourceContext = sourceContext;
+ }
+
+ public Map getGrayUserContext() {
+ return grayUserContext;
+ }
+
+ public void setGrayUserContext(Map grayUserContext) {
+ this.grayUserContext = grayUserContext;
+ }
+
+ public Map getGraySystemContext() {
+ return graySystemContext;
+ }
+
+ public void setGraySystemContext(Map graySystemContext) {
+ this.graySystemContext = graySystemContext;
+ }
+
+ public String getSystemTag(String key) {
+ return systemContext.get(key);
+ }
+
+ public String getRouteStringTag(String key) {
+ if (routeContext.containsKey(key)) {
+ return String.valueOf(routeContext.get(key));
+ }
+ else {
+ return null;
+ }
+ }
+
+ public boolean containRouteTag(String key) {
+ return routeContext.containsKey(key);
+ }
+
+ public Object getRouteTag(String key) {
+ return routeContext.get(key);
+ }
+
+ public String getSourceTag(String key) {
+ return sourceContext.get(SOURCE_PREFIX + key);
+ }
+
+ public String getGraySystemTag(String key) {
+ return graySystemContext.get(GRAY_PREFIX + key);
+ }
+
+ public String getGrayTag(String position, String key) {
+ return grayUserContext.get(getGrayPositionPrefix(position) + key);
+ }
+
+ public boolean isForwardToGateway() {
+ return routeContext.containsKey(CLOUD_SPACE_ROUTE_GATEWAY);
+ }
+
+ public boolean containTargetNamespaceId() {
+ return routeContext.containsKey(CLOUD_SPACE_ROUTE_TARGET_NAMESPACE_ID);
+ }
+
+ @Override
+ public String toString() {
+ return "UnitCompositeContextMap{" +
+ "userContext=" + userContext +
+ ", systemContext=" + systemContext +
+ ", routeContext=" + routeContext +
+ ", sourceContext=" + sourceContext +
+ ", grayUserContext=" + grayUserContext +
+ ", graySystemContext=" + graySystemContext +
+ '}';
+ }
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitManager.java
new file mode 100644
index 000000000..014082f27
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TencentUnitManager.java
@@ -0,0 +1,925 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.algorithm.HashCodeAlgorithm;
+import com.tencent.tsf.unit.core.algorithm.IUnitTransformAlgorithm;
+import com.tencent.tsf.unit.core.algorithm.ModAlgorithm;
+import com.tencent.tsf.unit.core.algorithm.SubstrAlgorithm;
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+import com.tencent.tsf.unit.core.mapping.api.MappingEntity;
+import com.tencent.tsf.unit.core.model.RoutingUnit;
+import com.tencent.tsf.unit.core.model.UnitArch;
+import com.tencent.tsf.unit.core.model.UnitArch.CloudSpace;
+import com.tencent.tsf.unit.core.model.UnitArch.DeploymentUnit;
+import com.tencent.tsf.unit.core.model.UnitArch.Gateway;
+import com.tencent.tsf.unit.core.model.UnitArch.Gdu;
+import com.tencent.tsf.unit.core.model.UnitArch.Sdu;
+import com.tencent.tsf.unit.core.model.UnitArch.TencentUnitArch;
+import com.tencent.tsf.unit.core.model.UnitArch.UnitCloudArch;
+import com.tencent.tsf.unit.core.model.UnitGray;
+import com.tencent.tsf.unit.core.model.UnitGray.TencentUnitGray;
+import com.tencent.tsf.unit.core.model.UnitGray.UnitGrayList;
+import com.tencent.tsf.unit.core.model.UnitNamespace;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRouteRule;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.MappingService;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.MatchRoute;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.MatchRouteFailover;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.RouterIdentifier;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.TagTransform;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.TencentUnitRouteRule;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.TransformAction;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRoute;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRouteRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TencentUnitManager {
+
+ /**
+ * DEFAULT_KEY_SEPARATOR.
+ */
+ public static final String DEFAULT_KEY_SEPARATOR = ",";
+ /**
+ * DEFAULT_KEY_VALUE_SEPARATOR.
+ */
+ public static final String DEFAULT_KEY_VALUE_SEPARATOR = "=";
+ /**
+ * DEFAULT_SHARDING_IDENTIFIER_KEY.
+ */
+ public static final String DEFAULT_SHARDING_IDENTIFIER_KEY = "CustomerNumber";
+ /**
+ * DEFAULT_ROUTER_IDENTIFIER_HEADER.
+ */
+ public static final String DEFAULT_ROUTER_IDENTIFIER_HEADER = "Router-Identifier";
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitManager.class);
+ private static final List archCallbacks = new ArrayList<>();
+ private static final ReadWriteLock archCallbackRwLock = new ReentrantReadWriteLock();
+ private static final Lock archCallbackRLock = archCallbackRwLock.readLock();
+ private static final Lock archCallbackWLock = archCallbackRwLock.writeLock();
+ private static final List ruleCallbacks = new ArrayList<>();
+ private static final ReadWriteLock ruleCallbackRwLock = new ReentrantReadWriteLock();
+ private static final Lock ruleCallbackRLock = ruleCallbackRwLock.readLock();
+ private static final Lock ruleCallbackWLock = ruleCallbackRwLock.writeLock();
+ private volatile static UnitArch unitArch;
+ private volatile static UnitRouteInfo unitRouteInfo;
+ // 灰名单列表
+ private volatile static Set unitGrayIds = new HashSet<>();
+ private volatile static List grayUnitRoutes;
+ private volatile static Set grayUnitHeaderKey = new HashSet<>();
+ private volatile static String localCloudSpaceId = "";
+ private volatile static String localUnitId = null;
+ private volatile static String localGduUnitId = null;
+ private volatile static String localFailoverGduUnitId = null;
+ // all cloud space
+ private volatile static List allCloudSpaces = new ArrayList<>();
+ // gdu unit id -> Gdu
+ private volatile static Map allGduMap = new ConcurrentHashMap<>();
+ // gray unit id -> du(gdu/sdu)
+ private volatile static Map grayUnitDuMap = new ConcurrentHashMap<>();
+ // gdu unit id -> { system -> ns }
+ private volatile static Map> gduNsMap = new ConcurrentHashMap<>();
+ // gray gdu unit id -> { system -> ns }
+ private volatile static Map> grayGduNsMap = new HashMap<>();
+ // (cloud id + ns id) -> unit id
+ private volatile static Map nsUnitMap = new ConcurrentHashMap<>();
+ // unit id -> cloud id
+ private volatile static Map unitCloudMap = new ConcurrentHashMap<>();
+ // (sdu unit id + business system name) -> gdu unit id
+ private volatile static Map businessSystemGduMap = new ConcurrentHashMap<>();
+ // (gdu/sdu unit id + business system name) -> ns
+ private volatile static Map businessSystemNsMap = new ConcurrentHashMap<>();
+ // cloud id -> { business system name -> gw}
+ private volatile static Map> businessSystemGwMap = new ConcurrentHashMap<>();
+ // cloud id -> { gw id -> local only gw}
+ private volatile static Map> localOnlyGwMap = new ConcurrentHashMap<>();
+ // (gdu/sdu unit id) -> failover gdu/sdu unit id
+ private volatile static Map failoverUnitIdMap = new ConcurrentHashMap<>();
+ private volatile static Map activeSduIdMap = new ConcurrentHashMap<>();
+ private volatile static Set disableZoneSet = new HashSet<>();
+ private volatile static Set businessSystemSet = new HashSet<>();
+ // 一期只有一个
+ private volatile static TagTransform tagTransform = null;
+ private volatile static List unitRoutes = Collections.emptyList();
+ private volatile static CloudSpace localCloudSpace;
+ private volatile static boolean enable = false;
+ // 是否透传 unit context,默认 false
+ private volatile static boolean contextPassingThroughEnabled = false;
+ private volatile static RouterIdentifier routerIdentifier = new RouterIdentifier(
+ DEFAULT_KEY_SEPARATOR, DEFAULT_KEY_VALUE_SEPARATOR, DEFAULT_SHARDING_IDENTIFIER_KEY, DEFAULT_ROUTER_IDENTIFIER_HEADER);
+ /**
+ * 未来扩展自定义转换算法.
+ */
+ private volatile static IUnitTransformAlgorithm customAlgorithm;
+
+ private TencentUnitManager() {
+ }
+
+ public static UnitArch getUnitArch() {
+ return unitArch;
+ }
+
+ public static void setUnitArch(UnitArch unitArch) {
+
+ if (!checkUnitArch(unitArch)) {
+ throw new TencentUnitException(ErrorCode.LOAD_ERROR, "parse unit arch exception");
+ }
+
+ TencentUnitManager.unitArch = unitArch;
+
+ Optional.ofNullable(unitArch).map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
+ map(UnitCloudArch::getLocalCloudId).ifPresent(id -> localCloudSpaceId = id);
+
+ Optional> optionalCloudSpaces = Optional.ofNullable(unitArch).
+ map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
+ map(UnitCloudArch::getCloudSpaces);
+
+ optionalCloudSpaces.ifPresent(cloudSpaces -> {
+ Map newNsUnitMap = new ConcurrentHashMap<>();
+ Map newUnitCloudMap = new ConcurrentHashMap<>();
+ Map newBusinessSystemGduMap = new ConcurrentHashMap<>();
+ Map newBusinessSystemNsMap = new ConcurrentHashMap<>();
+ Map> newBusinessSystemGwMap = new ConcurrentHashMap<>();
+ Map> newLocalOnlyGwMap = new ConcurrentHashMap<>();
+ Map> newGrayGduNsMap = new ConcurrentHashMap<>();
+ Map> newGduNsMap = new ConcurrentHashMap<>();
+ Map newGrayUnitDuMap = new ConcurrentHashMap<>();
+ Map newAllGduMap = new ConcurrentHashMap<>();
+ for (CloudSpace cloudSpace : cloudSpaces) {
+ String gduUnitId = "";
+ String grayGduUnitId = "";
+ // 目前一套 cloud space 下应该最多只有一个 gdu
+ if (cloudSpace.getGdus() != null) {
+ for (Gdu gdu : cloudSpace.getGdus()) {
+ gduUnitId = gdu.getId();
+ newAllGduMap.put(gdu.getId(), gdu);
+ Map tmpSystemNsMap = new ConcurrentHashMap<>();
+ if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
+ for (UnitNamespace namespace : gdu.getNamespaces()) {
+ newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
+ namespace.getId()), gdu.getId());
+ tmpSystemNsMap.put(namespace.getBusinessSystemName(), namespace);
+ newBusinessSystemNsMap.put(getUnitBusinessSystem(
+ gdu.getId(), namespace.getBusinessSystemName()), namespace);
+ }
+ }
+ newUnitCloudMap.put(gdu.getId(), cloudSpace.getCloudId());
+ if (cloudSpace.getCloudId().equals(localCloudSpaceId)) {
+ localGduUnitId = gduUnitId;
+ }
+ newGduNsMap.put(gdu.getId(), tmpSystemNsMap);
+ }
+ }
+ if (cloudSpace.getSdus() != null) {
+ for (Sdu sdu : cloudSpace.getSdus()) {
+ if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
+ for (UnitNamespace namespace : sdu.getNamespaces()) {
+ newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
+ namespace.getId()), sdu.getId());
+ // 如果 cloud space 下没有 gdu,对应 gduUnitId 为默认值空字符串
+ newBusinessSystemGduMap.put(getUnitBusinessSystem(
+ sdu.getId(), namespace.getBusinessSystemName()), gduUnitId);
+ newBusinessSystemNsMap.put(getUnitBusinessSystem(
+ sdu.getId(), namespace.getBusinessSystemName()), namespace);
+ }
+ }
+ newUnitCloudMap.put(sdu.getId(), cloudSpace.getCloudId());
+ }
+ }
+
+ if (cloudSpace.getGrayGdus() != null) {
+ for (Gdu grayGdu : cloudSpace.getGrayGdus()) {
+ // 未来如果有多个 gray gdu,则下面处理 gray sdu 时需要额外处理
+ grayGduUnitId = grayGdu.getId();
+ newGrayUnitDuMap.put(grayGduUnitId, grayGdu);
+ Map tmpSystemNsMap = new ConcurrentHashMap<>();
+ if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
+ for (UnitNamespace namespace : grayGdu.getNamespaces()) {
+ newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
+ namespace.getId()), grayGdu.getId());
+ tmpSystemNsMap.put(namespace.getBusinessSystemName(), namespace);
+ newBusinessSystemNsMap.put(getUnitBusinessSystem(
+ grayGdu.getId(), namespace.getBusinessSystemName()), namespace);
+ }
+ }
+ newUnitCloudMap.put(grayGdu.getId(), cloudSpace.getCloudId());
+ newGrayGduNsMap.put(grayGdu.getId(), tmpSystemNsMap);
+ }
+ }
+
+ if (cloudSpace.getGraySdus() != null) {
+ for (Sdu graySdu : cloudSpace.getGraySdus()) {
+ newGrayUnitDuMap.put(graySdu.getId(), graySdu);
+ if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
+ for (UnitNamespace namespace : graySdu.getNamespaces()) {
+ newNsUnitMap.put(getCloudSpaceNamespaceId(cloudSpace.getCloudId(),
+ namespace.getId()), graySdu.getId());
+ // 如果 cloud space 下没有 gdu,对应 gduUnitId 为默认值空字符串
+ newBusinessSystemGduMap.put(getUnitBusinessSystem(
+ graySdu.getId(), namespace.getBusinessSystemName()), grayGduUnitId);
+ newBusinessSystemNsMap.put(getUnitBusinessSystem(
+ graySdu.getId(), namespace.getBusinessSystemName()), namespace);
+ }
+ }
+ newUnitCloudMap.put(graySdu.getId(), cloudSpace.getCloudId());
+ }
+ }
+
+ // 优先读新配置
+ if (cloudSpace.getScopeGateways() != null) {
+ for (Gateway gateway : cloudSpace.getScopeGateways()) {
+ UnitArch.RouteScope routeScope = Optional.ofNullable(gateway.getRouteScope()).orElse(UnitArch.RouteScope.LOCAL_REMOTE);
+ switch (routeScope) {
+ case LOCAL:
+ newLocalOnlyGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
+ newLocalOnlyGwMap.get(cloudSpace.getCloudId()).put(gateway.getId(), gateway);
+ break;
+ default:
+ newBusinessSystemGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
+ newBusinessSystemGwMap.get(cloudSpace.getCloudId()).put(gateway.getBusinessSystemName(), gateway);
+ break;
+ }
+
+ }
+ }
+ else if (cloudSpace.getGateways() != null) {
+ for (Gateway gateway : cloudSpace.getGateways()) {
+ newBusinessSystemGwMap.putIfAbsent(cloudSpace.getCloudId(), new ConcurrentHashMap<>());
+ newBusinessSystemGwMap.get(cloudSpace.getCloudId())
+ .put(gateway.getBusinessSystemName(), gateway);
+ }
+ }
+
+ if (StringUtils.equals(localCloudSpaceId, cloudSpace.getCloudId())) {
+ localCloudSpace = cloudSpace;
+ }
+
+ }
+ allCloudSpaces = cloudSpaces;
+ nsUnitMap = newNsUnitMap;
+ unitCloudMap = newUnitCloudMap;
+ businessSystemGduMap = newBusinessSystemGduMap;
+ businessSystemNsMap = newBusinessSystemNsMap;
+ businessSystemGwMap = newBusinessSystemGwMap;
+ localOnlyGwMap = newLocalOnlyGwMap;
+ localUnitId = nsUnitMap.get(getCloudSpaceNamespaceId(localCloudSpaceId, Env.getNamespaceId()));
+ grayGduNsMap = newGrayGduNsMap;
+ gduNsMap = newGduNsMap;
+ grayUnitDuMap = newGrayUnitDuMap;
+ allGduMap = newAllGduMap;
+ });
+
+ Optional.ofNullable(unitArch).
+ map(UnitArch::getTencent).map(TencentUnitArch::getUnitCloudArchitecture).
+ map(UnitCloudArch::getBusinessSystems).ifPresent(businessSystemList -> {
+ Set newBusinessNameSet = new HashSet<>();
+ for (UnitArch.BusinessSystem businessSystem : businessSystemList) {
+ newBusinessNameSet.add(businessSystem.getName());
+ }
+ businessSystemSet = newBusinessNameSet;
+ });
+
+ loadArchCallback();
+
+ }
+
+ private static void loadArchCallback() {
+ archCallbackRLock.lock();
+ for (IUnitChangeCallback callback : archCallbacks) {
+ try {
+ callback.callback();
+ }
+ catch (Exception e) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[setUnitArch] arch callback:{}, error msg:{}",
+ callback.getName(), e.getMessage(), e);
+ }
+ else {
+ LOGGER.warn("[setUnitArch] arch callback:{}, error msg:{}",
+ callback.getName(), e.getMessage());
+ }
+ }
+ }
+ archCallbackRLock.unlock();
+
+ }
+
+ public static UnitRouteInfo getUnitRouteRule() {
+ return unitRouteInfo;
+ }
+
+ public static void setUnitRouteRule(UnitRouteInfo unitRouteInfo) {
+ if (!checkUnitRouteRule(unitRouteInfo)) {
+ throw new TencentUnitException(ErrorCode.LOAD_ERROR, "parse unit route exception");
+ }
+ TencentUnitManager.unitRouteInfo = unitRouteInfo;
+
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).
+ map(TencentUnitRouteRule::getUnitRouteRule).
+ map(UnitRouteRule::getPassingThroughEnabled).ifPresent(throughEnabled -> {
+ contextPassingThroughEnabled = throughEnabled;
+ });
+
+ // tagTransforms 目前最多只有一个,list.get(0) 取第一个
+ Optional optionalTransformAction = Optional.ofNullable(unitRouteInfo).
+ map(UnitRouteInfo::getTencent).map(TencentUnitRouteRule::getUnitRouteRule).
+ map(UnitRouteRule::getTagTransforms).filter(list -> list.size() > 0).
+ map(list -> list.get(0)).map(TagTransform::getTransform);
+
+ optionalTransformAction.ifPresent(transformAction -> {
+ if (transformAction.getAlgorithm() != null) {
+ TransformAlgorithmEnum algorithmEnum =
+ TransformAlgorithmEnum.getTransformAlgorith(transformAction.getAlgorithm().getName());
+ IUnitTransformAlgorithm unitTransformAlgorithm = null;
+ switch (algorithmEnum) {
+ case HASHCODE:
+ unitTransformAlgorithm = new HashCodeAlgorithm(transformAction.getAlgorithm()
+ .getOptions());
+ break;
+ case MOD:
+ unitTransformAlgorithm = new ModAlgorithm(transformAction.getAlgorithm()
+ .getOptions());
+ break;
+ case SUBSTR:
+ unitTransformAlgorithm = new SubstrAlgorithm(transformAction.getAlgorithm()
+ .getOptions());
+ break;
+ }
+ // TODO: 2023/11/3 支持自定义算法
+ transformAction.setUnitTransformAlgorithm(unitTransformAlgorithm);
+
+ tagTransform = unitRouteInfo.getTencent().getUnitRouteRule().getTagTransforms().get(0);
+ unitRoutes = unitRouteInfo.getTencent().getUnitRouteRule().getUnitRoutes();
+ }
+ });
+
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
+ .map(TencentUnitRouteRule::getGrayUnitRouteRule).map(
+ GrayUnitRouteRule::getUnitRoutes).ifPresent(grayUnitRouteList -> {
+ grayUnitRoutes = grayUnitRouteList;
+ Set newGrayUnitHeaderKey = new HashSet<>();
+ for (GrayUnitRoute route : grayUnitRoutes) {
+ for (UnitTag unitTag : route.getMatch().getConditions()) {
+ switch (unitTag.getTagPosition()) {
+ case HEADER:
+ newGrayUnitHeaderKey.add(unitTag.getTagField());
+ break;
+ }
+ }
+ }
+ grayUnitHeaderKey = newGrayUnitHeaderKey;
+ }
+ );
+
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).
+ map(TencentUnitRouteRule::getRouterIdentifier).ifPresent(identifier -> {
+ if (StringUtils.isEmpty(identifier.getKeySeparator())) {
+ identifier.setKeySeparator(DEFAULT_KEY_SEPARATOR);
+ }
+ if (StringUtils.isEmpty(identifier.getKeyValueSeparator())) {
+ identifier.setKeyValueSeparator(DEFAULT_KEY_VALUE_SEPARATOR);
+ }
+ if (StringUtils.isEmpty(identifier.getShardingIdentifierKey())) {
+ identifier.setShardingIdentifierKey(DEFAULT_SHARDING_IDENTIFIER_KEY);
+ }
+
+ if (StringUtils.isEmpty(identifier.getRouterIdentifierHeader())) {
+ identifier.setRouterIdentifierHeader(DEFAULT_ROUTER_IDENTIFIER_HEADER);
+ }
+
+ Map mappingServiceMap = new HashMap<>();
+ if (identifier.getShardingIdentifierMappingServices() != null) {
+ for (MappingService mappingService : identifier.getShardingIdentifierMappingServices()) {
+ mappingServiceMap.put(mappingService.getCloudId(), mappingService);
+ }
+ }
+ identifier.setMappingServiceMap(mappingServiceMap);
+
+ routerIdentifier = identifier;
+ });
+
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent).ifPresent(tencentUnitRouteRule -> {
+ Map newFailoverUnitIdMap = new ConcurrentHashMap<>();
+ Map newActiveSduIdMap = new ConcurrentHashMap<>();
+ Set newDisableZoneSet = new HashSet<>();
+ String newLocalFailoverGduUnitId = null;
+ if (tencentUnitRouteRule.getGduRouteRule() != null) {
+ for (UnitRoute gduRouteRule : tencentUnitRouteRule.getGduRouteRule()) {
+ // 暂时都是只有一个 CloudId, operator 只有 and
+ Optional matchTag = gduRouteRule.getMatch().getConditions().stream().
+ filter(cond -> UnitTagEngine.matchTag(cond, localCloudSpaceId)).findFirst();
+ if (matchTag.isPresent()) {
+ String scope = Optional.ofNullable(gduRouteRule).map(UnitRoute::getRoute)
+ .map(MatchRoute::getFailover).map(MatchRouteFailover::getScope).orElse("");
+ switch (scope) {
+ case "region":
+ if (Boolean.TRUE.equals(gduRouteRule.getRoute().getFailover().getEnabled())) {
+ LOGGER.info("use gdu failover mode, match name:{}, origin gdu route unit:{}, failover unit:{}",
+ gduRouteRule.getMatch().getName(), gduRouteRule.getRoute()
+ .getUnitId(), gduRouteRule.getRoute().getFailover().getRoute()
+ .getUnitId());
+ newLocalFailoverGduUnitId = gduRouteRule.getRoute().getFailover().getRoute()
+ .getUnitId();
+ newFailoverUnitIdMap.put(gduRouteRule.getRoute()
+ .getUnitId(), newLocalFailoverGduUnitId);
+ }
+ break;
+ case "zone":
+ for (UnitTag unitTag : gduRouteRule.getRoute().getFailover().getRoute().getLocationFilter()
+ .getZoneFilter().getConditions()) {
+ newDisableZoneSet.add(unitTag.getTagValue());
+ }
+ LOGGER.info("use zone failover mode, match name:{}, newDisableZoneSet:{}", gduRouteRule.getMatch()
+ .getName(), newDisableZoneSet);
+ break;
+ }
+ break;
+ }
+ }
+ }
+ if (tencentUnitRouteRule.getUnitRouteRule() != null) {
+ for (UnitRoute unitRoute : tencentUnitRouteRule.getUnitRouteRule().getUnitRoutes()) {
+ MatchRouteFailover failover = unitRoute.getRoute().getFailover();
+ if (failover != null && Boolean.TRUE.equals(failover.getEnabled())) {
+ newFailoverUnitIdMap.put(unitRoute.getRoute().getUnitId(), failover.getRoute().getUnitId());
+ newActiveSduIdMap.put(failover.getRoute().getUnitId(), true);
+ }
+ else {
+ newActiveSduIdMap.put(unitRoute.getRoute().getUnitId(), true);
+ }
+ }
+ }
+ failoverUnitIdMap = newFailoverUnitIdMap;
+ disableZoneSet = newDisableZoneSet;
+ localFailoverGduUnitId = newLocalFailoverGduUnitId;
+ activeSduIdMap = newActiveSduIdMap;
+ });
+
+ loadRuleCallback();
+
+ }
+
+ private static void loadRuleCallback() {
+ ruleCallbackRLock.lock();
+ try {
+ for (IUnitChangeCallback callback : ruleCallbacks) {
+ callback.callback();
+ }
+ }
+ finally {
+ ruleCallbackRLock.unlock();
+ }
+ }
+
+ public static Map getFailoverUnitIdMap() {
+ return failoverUnitIdMap;
+ }
+
+ public static void setUnitGray(UnitGray unitGray) {
+ Optional.ofNullable(unitGray).map(UnitGray::getTencent).map(TencentUnitGray::getUnitGrayList)
+ .map(UnitGrayList::getIds).ifPresent(ids -> {
+ unitGrayIds = new HashSet<>(ids);
+ });
+ }
+
+ public static Set getUnitGrayIds() {
+ return unitGrayIds;
+ }
+
+ public static IUnitTransformAlgorithm getCustomAlgorithm() {
+ return customAlgorithm;
+ }
+
+ public static void setCustomAlgorithm(IUnitTransformAlgorithm customAlgorithm) {
+ TencentUnitManager.customAlgorithm = customAlgorithm;
+ }
+
+ public static String getCloudSpaceNamespaceId(String cloudSpaceId, String namespaceId) {
+ return String.format("cn:%s@%s", cloudSpaceId, namespaceId);
+ }
+
+ public static String getUnitBusinessSystem(String unitId, String businessSystemName) {
+ return String.format("ub:%s@%s", unitId, businessSystemName);
+ }
+
+ public static String getCloudBusinessSystem(String cloudSpaceId, String businessSystemName) {
+ return String.format("cb:%s@%s", cloudSpaceId, businessSystemName);
+ }
+
+ public static String getLocalCloudSpaceId() {
+ return localCloudSpaceId;
+ }
+
+ public static boolean isLocalCloudSpace(String targetCloudId) {
+ return StringUtils.equals(targetCloudId, getLocalCloudSpaceId());
+ }
+
+ public static String getLocalUnitId() {
+ return localUnitId;
+ }
+
+ public static String getGduUnitId() {
+ if (StringUtils.isNotEmpty(localFailoverGduUnitId)) {
+ return localFailoverGduUnitId;
+ }
+ else {
+ return localGduUnitId;
+ }
+ }
+
+ public static TagTransform getTagTransform() {
+ return tagTransform;
+ }
+
+ public static Map getNsUnitMap() {
+ return nsUnitMap;
+ }
+
+ public static Map getUnitCloudMap() {
+ return unitCloudMap;
+ }
+
+ public static Map getBusinessSystemGduMap() {
+ return businessSystemGduMap;
+ }
+
+ public static List getUnitRoutes() {
+ return unitRoutes;
+ }
+
+ public static CloudSpace getLocalCloudSpace() {
+ return localCloudSpace;
+ }
+
+ /**
+ * 判断 unit id 是否存在,可查询其他 cloud 的.
+ */
+ public static boolean checkUnitId(String unitId) {
+ return getUnitCloudMap().containsKey(unitId);
+ }
+
+ /**
+ * 通过 customerIdentifier(客户要素)获取匹配的单元化路由(从 unitRoutes 中匹配).
+ * 若获取不到RoutingUnit,返回null
+ */
+ public static RoutingUnit getRoutingUnit(String customerIdentifier) {
+ // 根据客户要素(routerIdentifier)获取客户号
+ String customerNumber = TencentUnitManager.getCustomerNumber(customerIdentifier);
+ // 根据客户号,进行hash等计算,得到转换值(分片号)
+ String targetTagValue = getTransformTargetTagValue(customerNumber);
+ if (targetTagValue == null) {
+ LOGGER.warn("[unit] customerIdentifier:{}, customerNumber: {}, transfer to target tag is null",
+ customerIdentifier, customerNumber);
+ return null;
+ }
+
+ // 根据分片信息计算出匹配的单元信息
+ MatchRoute matchRoute = getMatchRoute(customerIdentifier, customerNumber, targetTagValue);
+ if (matchRoute != null) {
+ // 返回路由单元信息,包含了中间计算的数据,便于上层调用方回溯和使用(RoutingUnitContext会使用)
+ return new RoutingUnit(customerNumber, targetTagValue, matchRoute);
+ }
+
+ LOGGER.warn("[unit] customerIdentifier: {}, customerNumber: {}, not match any rule",
+ customerIdentifier, customerNumber);
+ return null;
+ }
+
+ /**
+ * 根据taValue(sharingKey)计算出对应的匹配的单元信息(MatchRoute).
+ *
+ * @param customerIdentifier 客户要素
+ * @param customerNumber 客户号
+ * @param tagValue 分片号
+ * @return 出现匹配不上,返回null
+ */
+ public static MatchRoute getMatchRoute(String customerIdentifier, String customerNumber, String tagValue) {
+ // 从路由规则里获取tagName
+ String tagName = Optional.ofNullable(getTagTransform()).
+ map(tagTransform -> tagTransform.getDestination()).
+ map(destination -> destination.getTagName()).orElse(null);
+
+ // 路由匹配
+ Map targetTagMap = new HashMap<>();
+ targetTagMap.put(tagName, tagValue);
+ for (UnitRoute route : getUnitRoutes()) {
+ if (UnitTagEngine.checkUnitRouteHit(route, targetTagMap)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[checkUnitRouteListHit] customer({}, {}) tag({}) match route unit id:{}",
+ customerIdentifier, customerNumber, targetTagMap, route.getRoute().getActualUnitId());
+ }
+ return route.getRoute();
+ }
+ }
+
+ // 没有匹配到规则的 tag,返回null,由上层决定是否抛出异常
+ return null;
+ }
+
+ public static String getTransformTargetTagValue(String cid) {
+ return Optional.ofNullable(getTagTransform()).
+ map(tagTransform -> tagTransform.getTransform()).
+ map(transform -> transform.getUnitTransformAlgorithm()).
+ map(algorithm -> algorithm.transform(cid)).orElse(null);
+ }
+
+ /**
+ * 根据客户要素,获取客户号.
+ * 客户要素格式:k1=v1,k2=v2,k3=v3,客户号的k=CustomerNumber
+ *
+ * @param cid 客户要素
+ * @return String 客户号
+ */
+ public static String getCustomerNumber(String cid) {
+ // 解释k1=v1,k2=v2...
+ String[] kvList = StringUtils.split(cid, routerIdentifier.getKeySeparator());
+
+ // 找一下是否存在CustomerNumber
+ Map kvMap = new HashMap<>(kvList.length);
+ for (String value : kvList) {
+
+ String[] kv = StringUtils.split(value, routerIdentifier.getKeyValueSeparator(), 2);
+ if (kv.length != 2) {
+ String msg = String.format("customerIdentifier(%s) format error, key separator:(%s), key value separator:(%s)",
+ cid, routerIdentifier.getKeySeparator(), routerIdentifier.getKeyValueSeparator());
+ throw new TencentUnitException(ErrorCode.CUSTOMER_IDENTIFIER_TRANSFORM_ERROR, msg);
+ }
+
+ kvMap.put(kv[0], kv[1]);
+ }
+ if (kvMap.containsKey(routerIdentifier.getShardingIdentifierKey())) {
+ return kvMap.get(routerIdentifier.getShardingIdentifierKey());
+ }
+
+ if (MappingServiceLoader.getService() == null) {
+ String msg = String.format("[unit] cid:%s, not found key(%s) and not found mapping service", cid, routerIdentifier.getShardingIdentifierKey());
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.MAPPING_SERVICE_EMPTY_ERROR, msg);
+ }
+
+ MappingEntity message = MappingServiceLoader.getService().processMapping(cid);
+ if (message == null || StringUtils.isEmpty(message.getCustomerNumber())) {
+ // 如果使用的是 CustomerMappingService,这里只会是 empty customer number
+ String msg = String.format("invoke mapping service get empty customer, cid:%s", cid);
+ LOGGER.error(msg);
+ throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_CUSTOMER_NUMBER_EMPTY_ERROR, msg);
+ }
+ return message.getCustomerNumber();
+ }
+
+ /**
+ * 只能匹配当前 cloud 的.
+ */
+ public static UnitNamespace getUnitNamespaceId(String unitId, String systemName) {
+ // businessSystemNsMap 包含 gdu 和 sdu 的
+ return businessSystemNsMap.get(getUnitBusinessSystem(unitId, systemName));
+ }
+
+ public static boolean isEnable() {
+ return enable;
+ }
+
+ public static void setEnable(boolean enable) {
+ TencentUnitManager.enable = enable;
+ }
+
+ public static boolean checkLocalNamespace(String nsId) {
+ return StringUtils.equals(Env.getNamespaceId(), nsId);
+ }
+
+ public static Gateway getBusinessSystemGateway(String cloudId, String system) {
+ return businessSystemGwMap.getOrDefault(cloudId, Collections.emptyMap()).get(system);
+ }
+
+ public static Gateway getLocalBusinessSystemGateway(String system) {
+ return businessSystemGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap()).get(system);
+ }
+
+ public static Map getLocalBusinessSystemGwMap() {
+ return businessSystemGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap());
+ }
+
+ public static Map getLocalOnlyGwMap() {
+ return localOnlyGwMap.getOrDefault(getLocalCloudSpaceId(), Collections.emptyMap());
+ }
+
+ public static void addArchCallback(IUnitChangeCallback callback) {
+ archCallbackWLock.lock();
+ try {
+ if (!archCallbacks.contains(callback)) {
+ archCallbacks.add(callback);
+ }
+ }
+ finally {
+ archCallbackWLock.unlock();
+ }
+
+ loadArchCallback();
+ loadRuleCallback();
+ }
+
+ public static void addRuleCallback(IUnitChangeCallback callback) {
+ ruleCallbackWLock.lock();
+ try {
+ if (!ruleCallbacks.contains(callback)) {
+ ruleCallbacks.add(callback);
+ }
+ }
+ finally {
+ ruleCallbackWLock.unlock();
+ }
+
+ loadArchCallback();
+ loadRuleCallback();
+ }
+
+ public static RouterIdentifier getRouterIdentifier() {
+ return routerIdentifier;
+ }
+
+ public static Map> getGrayGduNsMap() {
+ return grayGduNsMap;
+ }
+
+ public static Map> getGduNsMap() {
+ return gduNsMap;
+ }
+
+ public static Set getBusinessSystemSet() {
+ return businessSystemSet;
+ }
+
+ public static String getRouterIdentifierHeader() {
+ if (routerIdentifier != null) {
+ return routerIdentifier.getRouterIdentifierHeader();
+ }
+ else {
+ return DEFAULT_ROUTER_IDENTIFIER_HEADER;
+ }
+ }
+
+ public static String getLocalCloudMappingApi() {
+ String result = Optional.ofNullable(routerIdentifier).map(RouterIdentifier::getMappingServiceMap)
+ .map(m -> m.get(localCloudSpaceId)).map(MappingService::getApiPath).orElse(null);
+ // sdk 兼容 mapping url 从 arch 获取
+ if (StringUtils.isEmpty(result)) {
+ result = Optional.ofNullable(TencentUnitManager.getLocalCloudSpace()).
+ map(CloudSpace::getMappingServiceUrl).orElse(null);
+ }
+ return result;
+ }
+
+ public static List getGrayUnitRoutes() {
+ return grayUnitRoutes;
+ }
+
+ public static boolean inGrayUnit() {
+ return grayUnitDuMap.containsKey(getLocalUnitId());
+ }
+
+ public static String getGraySduUnitId() {
+ List graySdus = Optional.ofNullable(localCloudSpace)
+ .map(CloudSpace::getGraySdus).orElse(Collections.emptyList());
+ for (Sdu graySdu : graySdus) {
+ // 不为空时返回第一个
+ return graySdu.getId();
+ }
+ return null;
+ }
+
+
+ public static Set getGrayUnitHeaderKey() {
+ return grayUnitHeaderKey;
+ }
+
+ public static boolean isContextPassingThroughEnabled() {
+ return contextPassingThroughEnabled;
+ }
+
+ private static boolean checkUnitArch(UnitArch unitArch) {
+ try {
+ String localCloudId = unitArch.getTencent().getUnitCloudArchitecture().getLocalCloudId();
+ if (StringUtils.isEmpty(localCloudId)) {
+ LOGGER.warn("parse unit arch error, empty localCloudId");
+ return false;
+ }
+ Set cloudIds = new HashSet<>();
+ for (CloudSpace cloudSpace : unitArch.getTencent().getUnitCloudArchitecture().getCloudSpaces()) {
+ if (StringUtils.isEmpty(cloudSpace.getCloudId())) {
+ LOGGER.warn("parse unit arch error, empty cloudId");
+ return false;
+ }
+ cloudIds.add(cloudSpace.getCloudId());
+ }
+ if (!cloudIds.contains(localCloudId)) {
+ LOGGER.warn("parse unit arch error, localCloudId:{} not in cloudIds:{}", localCloudId, cloudIds);
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception e) {
+ LOGGER.warn("parse unit arch exception:{}", e.getMessage());
+ return false;
+ }
+ }
+
+ private static boolean checkUnitRouteRule(UnitRouteInfo unitRouteInfo) {
+ try {
+ AtomicBoolean pass = new AtomicBoolean(true);
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
+ .map(TencentUnitRouteRule::getGrayUnitRouteRule).map(
+ GrayUnitRouteRule::getUnitRoutes).ifPresent(grayUnitRouteList -> {
+
+ for (GrayUnitRoute route : grayUnitRouteList) {
+ for (UnitTag unitTag : route.getMatch().getConditions()) {
+ switch (unitTag.getTagPosition()) {
+ // 暂时只支持 header
+ case HEADER:
+ break;
+ default:
+ LOGGER.warn("parse unit tag position error, not support:{} ", unitTag.getTagPosition());
+ pass.set(false);
+ }
+ }
+
+ if (route.getRoute().getUnits().size() != 2) {
+ LOGGER.warn("gray match route units count error");
+ pass.set(false);
+ }
+ }
+ }
+ );
+
+ // 开启 failover,打印日志
+ Optional.ofNullable(unitRouteInfo).map(UnitRouteInfo::getTencent)
+ .map(TencentUnitRouteRule::getUnitRouteRule)
+ .map(UnitRouteRule::getUnitRoutes).ifPresent(unitRoutesList -> {
+ for (UnitRoute unitRoute : unitRoutesList) {
+ MatchRouteFailover failover = unitRoute.getRoute().getFailover();
+ if (failover != null && Boolean.TRUE.equals(failover.getEnabled())) {
+ LOGGER.info("use failover mode, match name:{}, origin route unit:{}, failover unit:{}",
+ unitRoute.getMatch().getName(), unitRoute.getRoute().getUnitId(), failover.getRoute()
+ .getUnitId());
+ }
+ }
+ });
+
+ return pass.get();
+ }
+ catch (Exception e) {
+ LOGGER.warn("parse unit arch exception:{}", e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * 返回本 cloud space 里业务系统所在的全局单元(GDU)的单元号.
+ */
+ public static String getGlobalUnitId(String systemName) {
+ return TencentUnitManager.getBusinessSystemGduMap().get(
+ TencentUnitManager.getUnitBusinessSystem(
+ TencentUnitManager.getLocalUnitId(), systemName));
+ }
+
+ public static Set getDisableZoneSet() {
+ return disableZoneSet;
+ }
+
+ public static Map getAllGduMap() {
+ return allGduMap;
+ }
+
+ public static List getAllCloudSpaces() {
+ return allCloudSpaces;
+ }
+
+ public static Map getActiveSduIdMap() {
+ return activeSduIdMap;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TransformAlgorithmEnum.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TransformAlgorithmEnum.java
new file mode 100644
index 000000000..c306bec36
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TransformAlgorithmEnum.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+
+import com.tencent.polaris.api.utils.StringUtils;
+
+public enum TransformAlgorithmEnum {
+
+ /**
+ * hash后取模.
+ */
+ HASHCODE,
+ /**
+ * 直接取模.
+ */
+ MOD,
+ /**
+ * 字符串截取.
+ */
+ SUBSTR,
+ /**
+ * 扩展用.
+ */
+ OTHER;
+
+ public static TransformAlgorithmEnum getTransformAlgorith(String type) {
+ if (StringUtils.isEmpty(type)) {
+ return OTHER;
+ }
+ for (TransformAlgorithmEnum transformAlgorithmEnum : TransformAlgorithmEnum.values()) {
+ if (transformAlgorithmEnum.name().equalsIgnoreCase(type)) {
+ return transformAlgorithmEnum;
+ }
+ }
+ // 其他的当做 other
+ return OTHER;
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TsfZoneFilterUnitCallback.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TsfZoneFilterUnitCallback.java
new file mode 100644
index 000000000..73b5140de
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/TsfZoneFilterUnitCallback.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.Objects;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.zonefilter.TsfZoneFilterManager;
+
+public class TsfZoneFilterUnitCallback implements IUnitChangeCallback {
+
+ @Override
+ public void callback() {
+ TsfZoneFilterManager.setDisabledZoneSet(TencentUnitManager.getDisableZoneSet());
+ }
+
+ @Override
+ public String getName() {
+ return this.getClass().getSimpleName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TsfZoneFilterUnitCallback that)) {
+ return false;
+ }
+ return StringUtils.equals(getName(), that.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getName());
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTag.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTag.java
new file mode 100644
index 000000000..a5bcb421d
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTag.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+
+import java.util.Objects;
+
+import com.tencent.tsf.unit.core.model.UnitTagPosition;
+
+public class UnitTag {
+
+ private UnitTagPosition tagPosition;
+ /**
+ * 标签名.
+ */
+ private String tagField;
+
+ /**
+ * 标签运算符.
+ * 定义在 UnitTagConstant.OPERATOR
+ */
+ private String tagOperator;
+
+ /**
+ * 标签的被运算对象值.
+ */
+ private String tagValue;
+
+ public UnitTag() {
+ }
+
+ public UnitTag(String tagField, String tagValue) {
+ this.tagField = tagField;
+ this.tagValue = tagValue;
+ }
+
+ @Override
+ public String toString() {
+ return "UnitTag{" +
+ "position=" + tagPosition +
+ ", tagField='" + tagField + '\'' +
+ ", tagOperator='" + tagOperator + '\'' +
+ ", tagValue='" + tagValue + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof UnitTag tag)) {
+ return false;
+ }
+ return Objects.equals(tagPosition, tag.tagPosition) &&
+ tagField.equals(tag.tagField) &&
+ tagOperator.equals(tag.tagOperator) &&
+ tagValue.equals(tag.tagValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(tagField, tagOperator, tagValue);
+ }
+
+ public UnitTagPosition getTagPosition() {
+ return tagPosition;
+ }
+
+ public void setTagPosition(UnitTagPosition tagPosition) {
+ this.tagPosition = tagPosition;
+ }
+
+ public String getTagField() {
+ return tagField;
+ }
+
+ public void setTagField(String tagField) {
+ this.tagField = tagField;
+ }
+
+ public String getTagOperator() {
+ return tagOperator;
+ }
+
+ public void setTagOperator(String tagOperator) {
+ this.tagOperator = tagOperator;
+ }
+
+ public String getTagValue() {
+ return tagValue;
+ }
+
+ public void setTagValue(String tagValue) {
+ this.tagValue = tagValue;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagConstant.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagConstant.java
new file mode 100644
index 000000000..a7dda55b2
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagConstant.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+public class UnitTagConstant {
+
+ /**
+ * 规则之间运算表达式的逻辑关系.
+ */
+ public static class TagRuleRelation {
+ /**
+ * 与.
+ */
+ public static final String AND = "AND";
+ /**
+ * 或.
+ */
+ public static final String OR = "OR";
+ }
+
+ /**
+ * 操作符.
+ */
+ public static class OPERATOR {
+
+ /**
+ * 包含.
+ */
+ public static final String IN = "IN";
+ /**
+ * 不包含.
+ */
+ public static final String NOT_IN = "NOT_IN";
+ /**
+ * 等于.
+ */
+ public static final String EQUAL = "EQUAL";
+ /**
+ * 不等于.
+ */
+ public static final String NOT_EQUAL = "NOT_EQUAL";
+ /**
+ * 正则.
+ */
+ public static final String REGEX = "REGEX";
+ /**
+ * 范围.
+ */
+ public static final String RANGE = "RANGE";
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagEngine.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagEngine.java
new file mode 100644
index 000000000..4ad588613
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/UnitTagEngine.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.UnitTagConstant.TagRuleRelation;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.GrayUnitRoute;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo.UnitRoute;
+
+public final class UnitTagEngine {
+
+ private UnitTagEngine() {
+ }
+
+ private static final Map PATTERN_CACHE_MAP = new ConcurrentHashMap<>();
+
+ public static boolean matchTag(UnitTag tag, String targetTagValue) {
+ String tagField = tag.getTagField();
+ String tagOperator = tag.getTagOperator();
+ String tagValue = tag.getTagValue();
+
+
+ if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.EQUAL)) {
+ // 匹配关系 等于
+ return StringUtils.equals(tagValue, targetTagValue);
+ }
+ else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.NOT_EQUAL)) {
+ // 匹配关系 不等于
+ return !StringUtils.equals(tagValue, targetTagValue);
+ }
+ else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.IN)) {
+ // 匹配关系 包含
+ Set routeTagValueSet = new HashSet<>(Arrays.asList(tagValue.split("\\s*,\\s*")));
+ return routeTagValueSet.contains(targetTagValue);
+ }
+ else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.NOT_IN)) {
+ // 匹配关系 不包含
+ Set routeTagValueSet = new HashSet<>(Arrays.asList(tagValue.split("\\s*,\\s*")));
+ return !routeTagValueSet.contains(targetTagValue);
+ }
+ else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.REGEX)) {
+ // 如果没有 targetTagValue,认为不匹配
+ if (targetTagValue == null) {
+ return false;
+ }
+ // 匹配关系 正则
+ Pattern pattern = PATTERN_CACHE_MAP.get(tagValue);
+ if (pattern == null) {
+ pattern = Pattern.compile(tagValue);
+ PATTERN_CACHE_MAP.putIfAbsent(targetTagValue, pattern);
+ }
+
+ return pattern.matcher(targetTagValue).matches();
+ }
+ else if (StringUtils.equals(tagOperator, UnitTagConstant.OPERATOR.RANGE)) {
+ // 按,分割tagValue
+ String[] values = tagValue.split("\\s*,\\s*");
+ if (values.length != 2) {
+ return false;
+ }
+
+ try {
+ // 转换为int进行range匹配
+ int start = Integer.parseInt(values[0]);
+ int end = Integer.parseInt(values[1]);
+ int target = Integer.parseInt(targetTagValue);
+ if (target >= start && target <= end) {
+ return true;
+ }
+ }
+ catch (NumberFormatException ex) {
+ return false;
+ }
+
+ return false;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * 检查传入的 route 是否命中.
+ */
+ public static boolean checkUnitRouteHit(UnitRoute route, Map targetTagMap) {
+ // condition 之间是 or 关系
+ if (StringUtils.equalsIgnoreCase(TagRuleRelation.OR, route.getMatch().getOperator())) {
+ for (UnitTag condition : route.getMatch().getConditions()) {
+ // 有一组匹配直接返回成功
+ if (UnitTagEngine.matchTag(condition, targetTagMap.get(condition.getTagField()))) {
+ return true;
+ }
+ }
+ }
+ else {
+ // and 关系
+ for (UnitTag condition : route.getMatch().getConditions()) {
+ // 有一组不匹配则直接返回 null
+ if (!UnitTagEngine.matchTag(condition, targetTagMap.get(condition.getTagField()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean checkGrayUnitRouteHit(GrayUnitRoute route) {
+ // condition 之间是 or 关系
+ if (StringUtils.equalsIgnoreCase(TagRuleRelation.OR, route.getMatch().getOperator())) {
+ for (UnitTag condition : route.getMatch().getConditions()) {
+ // 有一组匹配直接返回成功
+ if (UnitTagEngine.matchTag(condition,
+ TencentUnitContext.getGrayTag(condition.getTagPosition().name(), condition.getTagField()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ else {
+ // and 关系
+ for (UnitTag condition : route.getMatch().getConditions()) {
+ // 有一组不匹配则直接返回 null
+ if (!UnitTagEngine.matchTag(condition,
+ TencentUnitContext.getGrayTag(condition.getTagPosition().name(), condition.getTagField()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/HashCodeAlgorithm.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/HashCodeAlgorithm.java
new file mode 100644
index 000000000..e2e9ef9ef
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/HashCodeAlgorithm.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.algorithm;
+
+import java.util.Map;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
+
+public class HashCodeAlgorithm implements IUnitTransformAlgorithm {
+
+ private final Map options;
+
+ private final int mod;
+
+ public HashCodeAlgorithm(Map options) {
+ this.options = options;
+ mod = Integer.parseInt(String.valueOf(options.get("mod")));
+ }
+
+ @Override
+ public String getName() {
+ return TransformAlgorithmEnum.HASHCODE.name();
+ }
+
+ @Override
+ public String transform(String cid) {
+ if (StringUtils.isEmpty(cid)) {
+ return null;
+ }
+
+ return String.valueOf(cid.hashCode() % mod);
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/IUnitTransformAlgorithm.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/IUnitTransformAlgorithm.java
new file mode 100644
index 000000000..379d66015
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/IUnitTransformAlgorithm.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.algorithm;
+
+public interface IUnitTransformAlgorithm {
+
+ String getName();
+
+ String transform(String cid);
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/ModAlgorithm.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/ModAlgorithm.java
new file mode 100644
index 000000000..7ed0d13be
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/ModAlgorithm.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.algorithm;
+
+import java.util.Map;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+
+public class ModAlgorithm implements IUnitTransformAlgorithm {
+
+ private final Map options;
+
+ private final int mod;
+
+ public ModAlgorithm(Map options) {
+ this.options = options;
+ mod = Integer.parseInt(String.valueOf(options.get("mod")));
+ }
+
+ @Override
+ public String getName() {
+ return TransformAlgorithmEnum.MOD.name();
+ }
+
+ @Override
+ public String transform(String cid) {
+ if (StringUtils.isEmpty(cid)) {
+ return null;
+ }
+ try {
+ long id = Long.parseLong(cid);
+ return String.valueOf(id % mod);
+ }
+ catch (Exception e) {
+ throw new TencentUnitException(ErrorCode.CUSTOMER_NUMBER_TRANSFORM_ERROR, "format invalid, customNumber:" + cid, e);
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/SubstrAlgorithm.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/SubstrAlgorithm.java
new file mode 100644
index 000000000..fd434f5f3
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/algorithm/SubstrAlgorithm.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.algorithm;
+
+import java.util.Map;
+
+import com.tencent.tsf.unit.core.TransformAlgorithmEnum;
+
+public class SubstrAlgorithm implements IUnitTransformAlgorithm {
+
+ private final Map options;
+
+ public SubstrAlgorithm(Map options) {
+ this.options = options;
+ }
+
+ @Override
+ public String getName() {
+ return TransformAlgorithmEnum.SUBSTR.name();
+ }
+
+ @Override
+ public String transform(String cid) {
+ return cid;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/ErrorCode.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/ErrorCode.java
new file mode 100644
index 000000000..dce461dc9
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/ErrorCode.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.exception;
+
+public enum ErrorCode {
+ /**
+ * 内部加载配置错误.
+ */
+ LOAD_ERROR(10000),
+ /**
+ * 客户直接或间接输入的通用参数错误.
+ */
+ COMMON_PARAMETER_ERROR(10001),
+ /**
+ * 客户号映射服务为空.
+ */
+ MAPPING_SERVICE_EMPTY_ERROR(10002),
+ /**
+ * 客户号映射服务的 url 为空.
+ */
+ MAPPING_URL_EMPTY_ERROR(10003),
+ /**
+ * 客户号映射服务的响应码错误.
+ */
+ MAPPING_RESPONSE_CODE_ERROR(10004),
+ /**
+ * 客户号映射服务的响应体空错误.
+ */
+ MAPPING_RESPONSE_EMPTY_BODY_ERROR(10005),
+ /**
+ * 客户号服务响应体解析后无 customer number.
+ */
+ MAPPING_RESPONSE_CUSTOMER_NUMBER_EMPTY_ERROR(10006),
+ /**
+ * 客户号服务请求出现 IO 错误.
+ */
+ MAPPING_REQUEST_IO_ERROR(10007),
+ /**
+ * 客户号转换错误.
+ */
+ CUSTOMER_NUMBER_TRANSFORM_ERROR(10008),
+ /**
+ * 客户要素解析错误.
+ */
+ CUSTOMER_IDENTIFIER_TRANSFORM_ERROR(10009);
+
+ private final int code;
+
+ ErrorCode(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/TencentUnitException.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/TencentUnitException.java
new file mode 100644
index 000000000..d021d85f7
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/exception/TencentUnitException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.exception;
+
+public class TencentUnitException extends RuntimeException {
+
+ private final ErrorCode code;
+
+ public TencentUnitException(ErrorCode code) {
+ this.code = code;
+ }
+
+ public TencentUnitException(ErrorCode code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public TencentUnitException(ErrorCode code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ }
+
+ public String getMessage() {
+ StringBuilder builder = new StringBuilder(String.format("ERR-%d(%s): ", code.getCode(), code.name()));
+ builder.append(super.getMessage());
+ Throwable cause = getCause();
+ if (null != cause) {
+ builder.append(", cause: ").append(cause.getMessage());
+ }
+ return builder.toString();
+ }
+
+ public ErrorCode getCode() {
+ return code;
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/IMappingService.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/IMappingService.java
new file mode 100644
index 000000000..0f9fd4ea4
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/IMappingService.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.mapping.api;
+
+/**
+ * 数据映射服务,根据用户输入,执行转换后输出.
+ */
+public interface IMappingService {
+ // 数据映射服务接口定义
+ MappingEntity processMapping(String params);
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/MappingEntity.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/MappingEntity.java
new file mode 100644
index 000000000..b0e2f9f57
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/api/MappingEntity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.mapping.api;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MappingEntity {
+
+ // 客户号
+ private String customerNumber;
+
+ public MappingEntity() {
+
+ }
+
+ public MappingEntity(String customerNumber) {
+ this.customerNumber = customerNumber;
+ }
+
+ public String getCustomerNumber() {
+ return customerNumber;
+ }
+
+ public void setCustomerNumber(String customerNumber) {
+ this.customerNumber = customerNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "MappingEntity{" +
+ "customerNumber='" + customerNumber + '\'' +
+ '}';
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/impl/CustomerMappingService.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/impl/CustomerMappingService.java
new file mode 100644
index 000000000..0e7ba6f09
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/mapping/impl/CustomerMappingService.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.mapping.impl;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+import com.tencent.tsf.unit.core.mapping.api.IMappingService;
+import com.tencent.tsf.unit.core.mapping.api.MappingEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import shade.polaris.okhttp3.HttpUrl;
+import shade.polaris.okhttp3.OkHttpClient;
+import shade.polaris.okhttp3.Request;
+import shade.polaris.okhttp3.Response;
+
+public class CustomerMappingService implements IMappingService {
+
+ private static final String DEFAULT_ROUTER_IDENTIFIER_QUERY_KEY = "RouterIdentifier";
+
+ private static final Logger LOG = LoggerFactory.getLogger(CustomerMappingService.class);
+
+ // 连接超时=2s,请求超时=2s
+ private final OkHttpClient client = new OkHttpClient.Builder().
+ connectTimeout(2, TimeUnit.SECONDS).readTimeout(2, TimeUnit.SECONDS).build();
+
+ @Override
+ public MappingEntity processMapping(String params) {
+ String mappingUrl = getMappingUrl();
+ if (StringUtils.isEmpty(mappingUrl)) {
+ String msg = "[MappingService] mapping url is null";
+ LOG.warn(msg);
+ throw new TencentUnitException(ErrorCode.MAPPING_URL_EMPTY_ERROR, msg);
+ }
+
+ // params解析
+ String url = urlBuild(mappingUrl, params);
+ Request request = new Request.Builder().url(url).build();
+ try {
+ Response response = client.newCall(request).execute();
+ if (!response.isSuccessful()) {
+ String msg = String.format("request fail. url:%s, code:%s, err:%s",
+ url, response.code(), response.message());
+ LOG.error("[CustomerMappingService] " + msg);
+ if (response.body() != null) {
+ response.body().close();
+ }
+ throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_CODE_ERROR, msg);
+ }
+
+ if (response.body() == null) {
+ String msg = String.format("response body is null. url:%s", url);
+ LOG.error("[CustomerMappingService] " + msg);
+ throw new TencentUnitException(ErrorCode.MAPPING_RESPONSE_EMPTY_BODY_ERROR, msg);
+ }
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ MappingEntity entity = objectMapper.readValue(response.body().string(), MappingEntity.class);
+ response.body().close();
+ return entity;
+ }
+ catch (IOException e) {
+ String msg = String.format("occur IOException. url:%s, err:%s", url, e);
+ if (LOG.isDebugEnabled()) {
+ LOG.error("[CustomerMappingService] " + msg, e);
+ }
+ else {
+ LOG.error("[CustomerMappingService] " + msg);
+ }
+ throw new TencentUnitException(ErrorCode.MAPPING_REQUEST_IO_ERROR, msg, e);
+ }
+ }
+
+ private String urlBuild(String mappingUrl, String params) {
+ if (params == null) {
+ return mappingUrl;
+ }
+
+ HttpUrl.Builder urlBuilder = HttpUrl.get(mappingUrl).newBuilder();
+ if (StringUtils.isNotEmpty(params)) {
+ urlBuilder.addQueryParameter(DEFAULT_ROUTER_IDENTIFIER_QUERY_KEY, params);
+ }
+ return urlBuilder.build().toString();
+ }
+
+ /**
+ * 每次都从配置中获取.
+ */
+ public String getMappingUrl() {
+ return TencentUnitManager.getLocalCloudMappingApi();
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnit.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnit.java
new file mode 100644
index 000000000..99f64ea03
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnit.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+/**
+ * RoutingUnit 根据客户要素计算得到的目标单元相关信息,包括计算中间态的数据.
+ */
+public class RoutingUnit {
+
+ // 客户号
+ private String customerNumber;
+
+ // 分片KEY,分片号信息
+ private String shardingKey;
+
+ // 匹配的目标单元
+ private UnitRouteInfo.MatchRoute matchRoute;
+
+ public RoutingUnit() {
+ }
+
+ public RoutingUnit(String customerNumber, String shardingKey, UnitRouteInfo.MatchRoute matchRoute) {
+ this.customerNumber = customerNumber;
+ this.shardingKey = shardingKey;
+ this.matchRoute = matchRoute;
+ }
+
+ public String getCustomerNumber() {
+ return customerNumber;
+ }
+
+ public void setCustomerNumber(String customerNumber) {
+ this.customerNumber = customerNumber;
+ }
+
+ public String getShardingKey() {
+ return shardingKey;
+ }
+
+ public void setShardingKey(String shardingKey) {
+ this.shardingKey = shardingKey;
+ }
+
+ public UnitRouteInfo.MatchRoute getMatchRoute() {
+ return matchRoute;
+ }
+
+ public void setMatchRoute(UnitRouteInfo.MatchRoute matchRoute) {
+ this.matchRoute = matchRoute;
+ }
+
+ public String getUnitId() {
+ if (matchRoute == null) {
+ return null;
+ }
+
+ return matchRoute.getActualUnitId();
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnitContext.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnitContext.java
new file mode 100644
index 000000000..f58126a24
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/RoutingUnitContext.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+/**
+ * 路由单元信息上下文,场景:服务A -> 服务B,在服务B中获取路由单元上下文.
+ */
+public class RoutingUnitContext extends RoutingUnit {
+
+ private String unitId;
+
+ public RoutingUnitContext(String customerNumber, String shardingKey, String unitId) {
+ setCustomerNumber(customerNumber);
+ setShardingKey(shardingKey);
+ this.unitId = unitId;
+ }
+
+ @Override
+ public String getUnitId() {
+ return unitId;
+ }
+
+ public void setUnitId(String unitId) {
+ this.unitId = unitId;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/TargetUnitInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/TargetUnitInfo.java
new file mode 100644
index 000000000..ea30769d8
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/TargetUnitInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+
+/**
+ * 由业务系统+客户号计算出的目标单元信息,utils包使用的class.
+ * 包括ns信息等
+ */
+public class TargetUnitInfo extends UnitInfo {
+ // 逻辑分片ID,多个逻辑分片ID,映射到一个单元号里
+ private String shardingKey;
+
+ // namespace
+ private String namespaceId;
+ private String namespaceName;
+
+ public TargetUnitInfo(UnitInfo unitInfo) {
+ if (unitInfo == null) {
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "unit info is invalid(null)");
+ }
+
+ this.setUnitId(unitInfo.getUnitId());
+ this.setZoneId(unitInfo.getZoneId());
+ this.setZoneName(unitInfo.getZoneName());
+ this.setRegionId(unitInfo.getRegionId());
+ this.setRegionName(unitInfo.getRegionName());
+ this.setUnitType(unitInfo.getUnitType());
+ this.setAllNamespaceList(unitInfo.getAllNamespaceList());
+ this.setAllShardingKeyList(unitInfo.getAllShardingKeyList());
+ }
+
+ public String getShardingKey() {
+ return shardingKey;
+ }
+
+ public void setShardingKey(String shardingKey) {
+ this.shardingKey = shardingKey;
+ }
+
+ public String getNamespaceId() {
+ return namespaceId;
+ }
+
+ public void setNamespaceId(String namespaceId) {
+ this.namespaceId = namespaceId;
+ }
+
+ public String getNamespaceName() {
+ return namespaceName;
+ }
+
+ public void setNamespaceName(String namespaceName) {
+ this.namespaceName = namespaceName;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitArch.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitArch.java
new file mode 100644
index 000000000..e1c21bef5
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitArch.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+import java.util.List;
+
+/**
+ * 详细定义参考 iwiki /p/4009047246.
+ */
+public class UnitArch {
+
+ private TencentUnitArch tencent;
+
+ public TencentUnitArch getTencent() {
+ return tencent;
+ }
+
+ public void setTencent(TencentUnitArch tencent) {
+ this.tencent = tencent;
+ }
+
+ public static class TencentUnitArch {
+ private UnitCloudArch unitCloudArchitecture;
+
+ public UnitCloudArch getUnitCloudArchitecture() {
+ return unitCloudArchitecture;
+ }
+
+ public void setUnitCloudArchitecture(UnitCloudArch unitCloudArchitecture) {
+ this.unitCloudArchitecture = unitCloudArchitecture;
+ }
+ }
+
+ public static class UnitCloudArch {
+
+ private String localCloudId;
+
+ private List cloudSpaces;
+
+ private List businessSystems;
+
+ public String getLocalCloudId() {
+ return localCloudId;
+ }
+
+ public void setLocalCloudId(String localCloudId) {
+ this.localCloudId = localCloudId;
+ }
+
+ public List getCloudSpaces() {
+ return cloudSpaces;
+ }
+
+ public void setCloudSpaces(List cloudSpaces) {
+ this.cloudSpaces = cloudSpaces;
+ }
+
+ public List getBusinessSystems() {
+ return businessSystems;
+ }
+
+ public void setBusinessSystems(
+ List businessSystems) {
+ this.businessSystems = businessSystems;
+ }
+ }
+
+ public static class CloudSpace {
+ private String cloudId;
+
+ private String cloudName;
+
+ private String regionId;
+
+ private String RegionName;
+
+ private List sdus;
+
+ private List gdus;
+
+ private List graySdus;
+
+ private List grayGdus;
+
+ private List gateways;
+
+ private List scopeGateways;
+
+ private String mappingServiceUrl;
+
+ public String getCloudId() {
+ return cloudId;
+ }
+
+ public void setCloudId(String cloudId) {
+ this.cloudId = cloudId;
+ }
+
+ public String getCloudName() {
+ return cloudName;
+ }
+
+ public void setCloudName(String cloudName) {
+ this.cloudName = cloudName;
+ }
+
+ public String getRegionId() {
+ return regionId;
+ }
+
+ public void setRegionId(String regionId) {
+ this.regionId = regionId;
+ }
+
+ public String getRegionName() {
+ return RegionName;
+ }
+
+ public void setRegionName(String regionName) {
+ RegionName = regionName;
+ }
+
+ public List getSdus() {
+ return sdus;
+ }
+
+ public void setSdus(List sdus) {
+ this.sdus = sdus;
+ }
+
+ public List getGdus() {
+ return gdus;
+ }
+
+ public void setGdus(List gdus) {
+ this.gdus = gdus;
+ }
+
+ public List getGraySdus() {
+ return graySdus;
+ }
+
+ public void setGraySdus(List graySdus) {
+ this.graySdus = graySdus;
+ }
+
+ public List getGrayGdus() {
+ return grayGdus;
+ }
+
+ public void setGrayGdus(List grayGdus) {
+ this.grayGdus = grayGdus;
+ }
+
+ public List getGateways() {
+ return gateways;
+ }
+
+ public void setGateways(List gateways) {
+ this.gateways = gateways;
+ }
+
+ public List getScopeGateways() {
+ return scopeGateways;
+ }
+
+ public void setScopeGateways(List scopeGateways) {
+ this.scopeGateways = scopeGateways;
+ }
+
+ public String getMappingServiceUrl() {
+ return mappingServiceUrl;
+ }
+
+ public void setMappingServiceUrl(String mappingServiceUrl) {
+ this.mappingServiceUrl = mappingServiceUrl;
+ }
+ }
+
+ public static class Sdu extends DeploymentUnit {
+ private GduRule gduRule;
+
+ public GduRule getGduRule() {
+ return gduRule;
+ }
+
+ public void setGduRule(GduRule gduRule) {
+ this.gduRule = gduRule;
+ }
+ }
+
+ public static class Gdu extends DeploymentUnit {
+
+ }
+
+ public static class DeploymentUnit {
+ private String id;
+
+ private String name;
+
+ private String zoneId;
+
+ private String zoneName;
+
+ private List namespaces;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(String zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public List getNamespaces() {
+ return namespaces;
+ }
+
+ public void setNamespaces(List namespaces) {
+ this.namespaces = namespaces;
+ }
+ }
+
+ public static class Gateway {
+ private String id;
+
+ private Address address;
+
+ private String businessSystemName;
+
+ private RouteScope routeScope;
+
+ private String serviceName;
+
+ private String namespaceId;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public String getBusinessSystemName() {
+ return businessSystemName;
+ }
+
+ public void setBusinessSystemName(String businessSystemName) {
+ this.businessSystemName = businessSystemName;
+ }
+
+ public RouteScope getRouteScope() {
+ return routeScope;
+ }
+
+ public void setRouteScope(RouteScope routeScope) {
+ this.routeScope = routeScope;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getNamespaceId() {
+ return namespaceId;
+ }
+
+ public void setNamespaceId(String namespaceId) {
+ this.namespaceId = namespaceId;
+ }
+
+ @Override
+ public String toString() {
+ return "Gateway{" +
+ "id='" + id + '\'' +
+ ", address=" + address +
+ ", businessSystemName='" + businessSystemName + '\'' +
+ ", serviceName='" + serviceName + '\'' +
+ ", namespaceId='" + namespaceId + '\'' +
+ '}';
+ }
+ }
+
+ public static class Address {
+ private String host;
+
+ private String port;
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getPort() {
+ return port;
+ }
+
+ public void setPort(String port) {
+ this.port = port;
+ }
+
+ @Override
+ public String toString() {
+ return "Address{" +
+ "host='" + host + '\'' +
+ ", port='" + port + '\'' +
+ '}';
+ }
+ }
+
+ public static class GduRule {
+ private GduRuleRoute route;
+
+ public GduRuleRoute getRoute() {
+ return route;
+ }
+
+ public void setRoute(GduRuleRoute route) {
+ this.route = route;
+ }
+ }
+
+ public static class BusinessSystem {
+ private String id;
+
+ private String name;
+
+ private String type;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+ }
+
+ public static class GduRuleRoute {
+ private String cloudSpaceId;
+
+ private String unitId;
+
+ public String getCloudSpaceId() {
+ return cloudSpaceId;
+ }
+
+ public void setCloudSpaceId(String cloudSpaceId) {
+ this.cloudSpaceId = cloudSpaceId;
+ }
+
+ public String getUnitId() {
+ return unitId;
+ }
+
+ public void setUnitId(String unitId) {
+ this.unitId = unitId;
+ }
+ }
+
+ public enum RouteScope {
+ /**
+ * LOCAL.
+ */
+ LOCAL,
+ /**
+ * REMOTE.
+ */
+ REMOTE,
+ /**
+ * LOCAL_REMOTE.
+ */
+ LOCAL_REMOTE
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitGray.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitGray.java
new file mode 100644
index 000000000..00db292a5
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitGray.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+import java.util.List;
+
+public class UnitGray {
+
+ private TencentUnitGray tencent;
+
+ public TencentUnitGray getTencent() {
+ return tencent;
+ }
+
+ public void setTencent(TencentUnitGray tencent) {
+ this.tencent = tencent;
+ }
+
+ public static class TencentUnitGray {
+ private UnitGrayList unitGrayList;
+
+ public UnitGrayList getUnitGrayList() {
+ return unitGrayList;
+ }
+
+ public void setUnitGrayList(UnitGrayList unitGrayList) {
+ this.unitGrayList = unitGrayList;
+ }
+ }
+
+ public static class UnitGrayList {
+ private String version;
+
+ private List ids;
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public List getIds() {
+ return ids;
+ }
+
+ public void setIds(List ids) {
+ this.ids = ids;
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitInfo.java
new file mode 100644
index 000000000..fbe6ab7ef
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitInfo.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+import java.util.List;
+
+/**
+ * 单元信息,包括SDU或GDU,提供给utils使用的class.
+ */
+public class UnitInfo {
+ // 单元号
+ private String unitId;
+
+ // 单元类型 SDU or GDU
+ private String unitType;
+
+ // region
+ private String regionId;
+ private String regionName;
+
+ // zone
+ private String zoneId;
+ private String zoneName;
+
+ // 当前单元对应的命名空间列表
+ private List allNamespaceList;
+
+ // 当前单元对应的所有shardingId集合
+ private List allShardingKeyList;
+
+ public String getUnitId() {
+ return unitId;
+ }
+
+ public void setUnitId(String unitId) {
+ this.unitId = unitId;
+ }
+
+ public String getUnitType() {
+ return unitType;
+ }
+
+ public void setUnitType(String unitType) {
+ this.unitType = unitType;
+ }
+
+ public String getZoneId() {
+ return zoneId;
+ }
+
+ public void setZoneId(String zoneId) {
+ this.zoneId = zoneId;
+ }
+
+ public String getRegionId() {
+ return regionId;
+ }
+
+ public void setRegionId(String regionId) {
+ this.regionId = regionId;
+ }
+
+ public String getRegionName() {
+ return regionName;
+ }
+
+ public void setRegionName(String regionName) {
+ this.regionName = regionName;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public List getAllNamespaceList() {
+ return allNamespaceList;
+ }
+
+ public void setAllNamespaceList(List allNamespaceList) {
+ this.allNamespaceList = allNamespaceList;
+ }
+
+ public List getAllShardingKeyList() {
+ return allShardingKeyList;
+ }
+
+ public void setAllShardingKeyList(List allShardingKeyList) {
+ this.allShardingKeyList = allShardingKeyList;
+ }
+
+ @Override
+ public String toString() {
+ return "UnitInfo{" +
+ "unitId='" + unitId + '\'' +
+ ", unitType='" + unitType + '\'' +
+ ", regionId='" + regionId + '\'' +
+ ", regionName='" + regionName + '\'' +
+ ", zoneId='" + zoneId + '\'' +
+ ", zoneName='" + zoneName + '\'' +
+ ", allNamespaceList=" + allNamespaceList +
+ ", allShardingKeyList=" + allShardingKeyList +
+ '}';
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitNamespace.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitNamespace.java
new file mode 100644
index 000000000..cb52a15ef
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitNamespace.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+public class UnitNamespace {
+
+ private String id;
+
+ private String name;
+
+ private String businessSystemName;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getBusinessSystemName() {
+ return businessSystemName;
+ }
+
+ public void setBusinessSystemName(String businessSystemName) {
+ this.businessSystemName = businessSystemName;
+ }
+
+ @Override
+ public String toString() {
+ return "UnitNamespace{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ ", businessSystemName='" + businessSystemName + '\'' +
+ '}';
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitRouteInfo.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitRouteInfo.java
new file mode 100644
index 000000000..f5a25b98e
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitRouteInfo.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.tencent.tsf.unit.core.UnitTag;
+import com.tencent.tsf.unit.core.algorithm.IUnitTransformAlgorithm;
+
+public class UnitRouteInfo {
+
+ private TencentUnitRouteRule tencent;
+
+ public TencentUnitRouteRule getTencent() {
+ return tencent;
+ }
+
+ public void setTencent(TencentUnitRouteRule tencent) {
+ this.tencent = tencent;
+ }
+
+ public static class TencentUnitRouteRule {
+ private UnitRouteRule unitRouteRule;
+
+ private List gduRouteRule;
+
+ private GrayUnitRouteRule grayUnitRouteRule;
+
+ private RouterIdentifier routerIdentifier;
+
+ public UnitRouteRule getUnitRouteRule() {
+ return unitRouteRule;
+ }
+
+ public void setUnitRouteRule(UnitRouteRule unitRouteRule) {
+ this.unitRouteRule = unitRouteRule;
+ }
+
+ public List getGduRouteRule() {
+ return gduRouteRule;
+ }
+
+ public void setGduRouteRule(List gduRouteRule) {
+ this.gduRouteRule = gduRouteRule;
+ }
+
+ public GrayUnitRouteRule getGrayUnitRouteRule() {
+ return grayUnitRouteRule;
+ }
+
+ public void setGrayUnitRouteRule(
+ GrayUnitRouteRule grayUnitRouteRule) {
+ this.grayUnitRouteRule = grayUnitRouteRule;
+ }
+
+ public RouterIdentifier getRouterIdentifier() {
+ return routerIdentifier;
+ }
+
+ public void setRouterIdentifier(
+ RouterIdentifier routerIdentifier) {
+ this.routerIdentifier = routerIdentifier;
+ }
+ }
+
+ public static class GrayUnitRouteRule {
+
+ private List unitRoutes;
+
+ public List getUnitRoutes() {
+ return unitRoutes;
+ }
+
+ public void setUnitRoutes(
+ List unitRoutes) {
+ this.unitRoutes = unitRoutes;
+ }
+ }
+
+ public static class UnitRouteRule {
+ private String id;
+
+ private String name;
+
+ private String desc;
+
+ private Boolean passingThroughEnabled;
+
+ private List tagTransforms;
+
+ private List unitRoutes;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public Boolean getPassingThroughEnabled() {
+ return passingThroughEnabled;
+ }
+
+ public void setPassingThroughEnabled(Boolean passingThroughEnabled) {
+ this.passingThroughEnabled = passingThroughEnabled;
+ }
+
+ public List getTagTransforms() {
+ return tagTransforms;
+ }
+
+ public void setTagTransforms(
+ List tagTransforms) {
+ this.tagTransforms = tagTransforms;
+ }
+
+ public List getUnitRoutes() {
+ return unitRoutes;
+ }
+
+ public void setUnitRoutes(List unitRoutes) {
+ this.unitRoutes = unitRoutes;
+ }
+ }
+
+ public static class TagTransform {
+ private TagTransformLocation source;
+
+ private TransformAction transform;
+
+ private TagTransformLocation destination;
+
+ public TagTransformLocation getSource() {
+ return source;
+ }
+
+ public void setSource(TagTransformLocation source) {
+ this.source = source;
+ }
+
+ public TransformAction getTransform() {
+ return transform;
+ }
+
+ public void setTransform(TransformAction transform) {
+ this.transform = transform;
+ }
+
+ public TagTransformLocation getDestination() {
+ return destination;
+ }
+
+ public void setDestination(TagTransformLocation destination) {
+ this.destination = destination;
+ }
+ }
+
+ public static class TagTransformLocation {
+ private String tagName;
+
+ public String getTagName() {
+ return tagName;
+ }
+
+ public void setTagName(String tagName) {
+ this.tagName = tagName;
+ }
+ }
+
+ public static class TransformAction {
+ private TransformAlgorithm algorithm;
+ // 解析 algorithm 后放入的实际操作类
+ @JsonIgnore
+ private IUnitTransformAlgorithm unitTransformAlgorithm;
+
+ public TransformAlgorithm getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(TransformAlgorithm algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public IUnitTransformAlgorithm getUnitTransformAlgorithm() {
+ return unitTransformAlgorithm;
+ }
+
+ public void setUnitTransformAlgorithm(
+ IUnitTransformAlgorithm unitTransformAlgorithm) {
+ this.unitTransformAlgorithm = unitTransformAlgorithm;
+ }
+ }
+
+ public static class TransformAlgorithm {
+ private String name;
+ // algorithm 的操作算子,由 name 决定
+ private Map options;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getOptions() {
+ return options;
+ }
+
+ public void setOptions(Map options) {
+ this.options = options;
+ }
+ }
+
+ public static class GrayUnitRoute {
+ private GrayUnitRouteMatch match;
+
+ private GrayMatchRoute route;
+
+ public GrayUnitRouteMatch getMatch() {
+ return match;
+ }
+
+ public void setMatch(GrayUnitRouteMatch match) {
+ this.match = match;
+ }
+
+ public GrayMatchRoute getRoute() {
+ return route;
+ }
+
+ public void setRoute(GrayMatchRoute route) {
+ this.route = route;
+ }
+ }
+
+ public static class UnitRoute {
+ private UnitRouteMatch match;
+
+ private MatchRoute route;
+
+ public UnitRouteMatch getMatch() {
+ return match;
+ }
+
+ public void setMatch(UnitRouteMatch match) {
+ this.match = match;
+ }
+
+ public MatchRoute getRoute() {
+ return route;
+ }
+
+ public void setRoute(MatchRoute route) {
+ this.route = route;
+ }
+ }
+
+ public static class GrayUnitRouteMatch extends UnitRouteMatch {
+ private Boolean grayListEnabled;
+
+ public Boolean getGrayListEnabled() {
+ return grayListEnabled;
+ }
+
+ public void setGrayListEnabled(Boolean grayListEnabled) {
+ this.grayListEnabled = grayListEnabled;
+ }
+ }
+
+ public static class UnitRouteMatch {
+ private String name;
+
+ private String operator;
+
+ private List conditions;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public void setOperator(String operator) {
+ this.operator = operator;
+ }
+
+ public List getConditions() {
+ return conditions;
+ }
+
+ public void setConditions(List conditions) {
+ this.conditions = conditions;
+ }
+ }
+
+ public static class GrayMatchRoute {
+
+ private List units;
+
+ public List getUnits() {
+ return units;
+ }
+
+ public void setUnits(
+ List units) {
+ this.units = units;
+ }
+ }
+
+ public static class GrayMatchRouteUnit {
+ private String id;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "GrayMatchRouteUnit{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+ }
+
+ public static class MatchRoute {
+ private String unitId;
+
+ private MatchRouteFailover failover;
+
+ public String getUnitId() {
+ return unitId;
+ }
+
+ public void setUnitId(String unitId) {
+ this.unitId = unitId;
+ }
+
+ public MatchRouteFailover getFailover() {
+ return failover;
+ }
+
+ public void setFailover(MatchRouteFailover failover) {
+ this.failover = failover;
+ }
+
+ public String getActualUnitId() {
+ if (failover != null && failover.getEnabled()) {
+ return failover.getRoute().getUnitId();
+ }
+ else {
+ return unitId;
+ }
+ }
+ }
+
+ public static class MatchRouteFailover {
+ private Boolean enabled;
+
+ private FailoverRoute route;
+
+ private String scope;
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public FailoverRoute getRoute() {
+ return route;
+ }
+
+ public void setRoute(FailoverRoute route) {
+ this.route = route;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public void setScope(String scope) {
+ this.scope = scope;
+ }
+ }
+
+ public static class FailoverRoute {
+ private String unitId;
+
+ private FailoverRouteLocationFilter locationFilter;
+
+ public String getUnitId() {
+ return unitId;
+ }
+
+ public void setUnitId(String unitId) {
+ this.unitId = unitId;
+ }
+
+ public FailoverRouteLocationFilter getLocationFilter() {
+ return locationFilter;
+ }
+
+ public void setLocationFilter(FailoverRouteLocationFilter locationFilter) {
+ this.locationFilter = locationFilter;
+ }
+ }
+
+ public static class FailoverRouteLocationFilter {
+ private UnitRouteMatch zoneFilter;
+
+ public UnitRouteMatch getZoneFilter() {
+ return zoneFilter;
+ }
+
+ public void setZoneFilter(UnitRouteMatch zoneFilter) {
+ this.zoneFilter = zoneFilter;
+ }
+ }
+
+ public static class RouterIdentifier {
+ private String keySeparator;
+
+ private String keyValueSeparator;
+
+ private String shardingIdentifierKey;
+
+ private String routerIdentifierHeader;
+
+ private List shardingIdentifierMappingServices;
+
+ @JsonIgnore
+ private Map mappingServiceMap;
+
+ public RouterIdentifier() {
+ }
+
+ public RouterIdentifier(String keySeparator, String keyValueSeparator,
+ String shardingIdentifierKey,
+ String routerIdentifierHeader) {
+ this.keySeparator = keySeparator;
+ this.keyValueSeparator = keyValueSeparator;
+ this.shardingIdentifierKey = shardingIdentifierKey;
+ this.routerIdentifierHeader = routerIdentifierHeader;
+ }
+
+ public String getKeySeparator() {
+ return keySeparator;
+ }
+
+ public void setKeySeparator(String keySeparator) {
+ this.keySeparator = keySeparator;
+ }
+
+ public String getKeyValueSeparator() {
+ return keyValueSeparator;
+ }
+
+ public void setKeyValueSeparator(String keyValueSeparator) {
+ this.keyValueSeparator = keyValueSeparator;
+ }
+
+ public String getShardingIdentifierKey() {
+ return shardingIdentifierKey;
+ }
+
+ public void setShardingIdentifierKey(String shardingIdentifierKey) {
+ this.shardingIdentifierKey = shardingIdentifierKey;
+ }
+
+ public String getRouterIdentifierHeader() {
+ return routerIdentifierHeader;
+ }
+
+ public void setRouterIdentifierHeader(String routerIdentifierHeader) {
+ this.routerIdentifierHeader = routerIdentifierHeader;
+ }
+
+ public List getShardingIdentifierMappingServices() {
+ return shardingIdentifierMappingServices;
+ }
+
+ public void setShardingIdentifierMappingServices(
+ List shardingIdentifierMappingServices) {
+ this.shardingIdentifierMappingServices = shardingIdentifierMappingServices;
+ }
+
+ public Map getMappingServiceMap() {
+ return mappingServiceMap;
+ }
+
+ public void setMappingServiceMap(
+ Map mappingServiceMap) {
+ this.mappingServiceMap = mappingServiceMap;
+ }
+ }
+
+ public static class MappingService {
+ private String cloudId;
+
+ private String apiPath;
+
+ public String getCloudId() {
+ return cloudId;
+ }
+
+ public void setCloudId(String cloudId) {
+ this.cloudId = cloudId;
+ }
+
+ public String getApiPath() {
+ return apiPath;
+ }
+
+ public void setApiPath(String apiPath) {
+ this.apiPath = apiPath;
+ }
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitTagPosition.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitTagPosition.java
new file mode 100644
index 000000000..3ace51f0e
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitTagPosition.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+public enum UnitTagPosition {
+
+ /**
+ * HTTP HEADER.
+ */
+ HEADER,
+ /**
+ * HTTP QUERY.
+ */
+ QUERY,
+ /**
+ * HTTP COOKIE.
+ */
+ COOKIE,
+ /**
+ * HTTP PATH.
+ */
+ PATH,
+ /**
+ * TSF TAG.
+ */
+ TSF_TAG;
+
+ @JsonCreator
+ public static UnitTagPosition fromString(String key) {
+ for (UnitTagPosition tagPosition : UnitTagPosition.values()) {
+ if (tagPosition.name().equalsIgnoreCase(key)) {
+ return tagPosition;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitType.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitType.java
new file mode 100644
index 000000000..44c1e6408
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/model/UnitType.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.model;
+
+public enum UnitType {
+ /**
+ * SDU.
+ */
+ SDU,
+ /**
+ * GDU.
+ */
+ GDU
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitArchKVLoader.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitArchKVLoader.java
new file mode 100644
index 000000000..3827e1230
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitArchKVLoader.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.remote;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.QueryParams;
+import com.ecwid.consul.v1.Response;
+import com.ecwid.consul.v1.kv.model.GetValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.tencent.cloud.common.util.GzipUtil;
+import com.tencent.tsf.unit.core.Env;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitArch;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TencentUnitArchKVLoader {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitArchKVLoader.class);
+ /**
+ * consul 长轮询等待时间.
+ */
+ private final static Integer watchTime = 55;
+ /**
+ * 数据当前游标.
+ */
+ private static Long index = -1L;
+ private static String rawContent;
+
+ private TencentUnitArchKVLoader() {
+ }
+
+ private static String getUnitArchKey() {
+ return "unit/cloudArchitecture/data";
+ }
+
+ public static void syncUnitArch() {
+ String newContent = null;
+ try {
+ ConsulClient client = TsfUnitConsulManager.getConsulClient();
+
+ if (client == null) {
+ LOGGER.warn("[syncUnitArch] tsf unit consul client is null");
+ return;
+ }
+
+ Response response = client.getKVValue(getUnitArchKey(),
+ Env.getConsulToken(), new QueryParams(watchTime, index));
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[syncUnitArch] resp:{}", response);
+ }
+ // data not change
+ if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
+ return;
+ }
+ index = response.getConsulIndex();
+ // 推空保护,启动后理论上不存在单元化配置被删除的场景
+ if (response.getValue() != null) {
+ // 控制台 gzip 压缩加 base64 转换,这里进行解压
+ newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
+ loadUnitArch(newContent);
+ }
+
+ }
+ catch (Throwable t) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[syncUnitArch] newContent:{} error:", newContent, t);
+ }
+ else {
+ LOGGER.warn("[syncUnitArch] error: {}", t.getMessage());
+ }
+ }
+ }
+
+ public static void loadUnitArch(String content) throws JsonProcessingException {
+ LOGGER.info("[unit] unit arch config old raw content:\n{}", rawContent);
+
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ TencentUnitManager.setUnitArch(mapper.readValue(content, UnitArch.class));
+
+ rawContent = content;
+ LOGGER.info("[unit] unit arch config new raw content:\n{}", rawContent);
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitGrayKVLoader.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitGrayKVLoader.java
new file mode 100644
index 000000000..a5a9c5ef5
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitGrayKVLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.remote;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.QueryParams;
+import com.ecwid.consul.v1.Response;
+import com.ecwid.consul.v1.kv.model.GetValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.tencent.cloud.common.util.GzipUtil;
+import com.tencent.tsf.unit.core.Env;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitGray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TencentUnitGrayKVLoader {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitGrayKVLoader.class);
+ /**
+ * consul 长轮询等待时间.
+ */
+ private final static Integer watchTime = 55;
+ /**
+ * 数据当前游标.
+ */
+ private static Long index = -1L;
+ private static String rawContent;
+
+ private TencentUnitGrayKVLoader() {
+ }
+
+ private static String getUnitGrayKey() {
+ return "unit/grayList/data";
+ }
+
+ public static void syncUnitGray() {
+ String newContent = null;
+ try {
+ ConsulClient client = TsfUnitConsulManager.getConsulClient();
+
+ if (client == null) {
+ LOGGER.warn("[syncUnitGray] tsf unit consul client is null");
+ return;
+ }
+ Response response = client.getKVValue(getUnitGrayKey(),
+ Env.getConsulToken(), new QueryParams(watchTime, index));
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[syncUnitGray] resp:{}", response);
+ }
+ // data not change
+ if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
+ return;
+ }
+ index = response.getConsulIndex();
+ // 推空保护,启动后理论上不存在单元化配置被删除的场景
+ if (response.getValue() != null) {
+ // 控制台 gzip 压缩加 base64 转换,这里进行解压
+ newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
+ loadUnitGray(newContent);
+ }
+ }
+ catch (Throwable t) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("[syncUnitGray] newContent:{} error:", newContent, t);
+ }
+ else {
+ LOGGER.warn("[syncUnitGray] error: {}", t.getMessage());
+ }
+ }
+ }
+
+ public static void loadUnitGray(String content) throws JsonProcessingException {
+ LOGGER.info("[unit] unit gray old raw content:\n{}", rawContent);
+
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ TencentUnitManager.setUnitGray(mapper.readValue(content, UnitGray.class));
+
+ rawContent = content;
+ LOGGER.info("[unit] unit gray new raw content:\n{}", rawContent);
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitRouteRuleKVLoader.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitRouteRuleKVLoader.java
new file mode 100644
index 000000000..1c3e54eec
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TencentUnitRouteRuleKVLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.remote;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.QueryParams;
+import com.ecwid.consul.v1.Response;
+import com.ecwid.consul.v1.kv.model.GetValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.tencent.cloud.common.util.GzipUtil;
+import com.tencent.tsf.unit.core.Env;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class TencentUnitRouteRuleKVLoader {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitRouteRuleKVLoader.class);
+ /**
+ * consul 长轮询等待时间.
+ */
+ private final static Integer watchTime = 55;
+ /**
+ * 数据当前游标.
+ */
+ private static Long index = -1L;
+ private static String rawContent;
+
+ private TencentUnitRouteRuleKVLoader() {
+ }
+
+ private static String getUnitRouteRuleKey() {
+ return "unit/routeRule/data";
+ }
+
+ public static void syncUnitRouteRule() {
+ String newContent = null;
+ try {
+ ConsulClient client = TsfUnitConsulManager.getConsulClient();
+
+ if (client == null) {
+ LOGGER.warn("[syncUnitRouteRule] tsf unit consul client is null");
+ return;
+ }
+ Response response = client.getKVValue(getUnitRouteRuleKey(),
+ Env.getConsulToken(), new QueryParams(watchTime, index));
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[syncUnitRouteRule] resp:{}", response);
+ }
+ // data not change
+ if (response.getConsulIndex() == null || index.equals(response.getConsulIndex())) {
+ return;
+ }
+ index = response.getConsulIndex();
+ // 推空保护,启动后理论上不存在单元化配置被删除的场景
+ if (response.getValue() != null) {
+ // 控制台 gzip 压缩加 base64 转换,这里进行解压
+ newContent = GzipUtil.base64DecodeDecompress(response.getValue().getDecodedValue());
+ loadUnitRouteRule(newContent);
+ }
+ }
+ catch (Throwable t) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("[syncUnitRouteRule] newContent:{} error:", newContent, t);
+ }
+ else {
+ LOGGER.warn("[syncUnitRouteRule] error: {}", t.getMessage());
+ }
+ }
+ }
+
+ public static void loadUnitRouteRule(String content) throws JsonProcessingException {
+ LOGGER.info("[unit] unit route rule old raw content:\n{}", rawContent);
+
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ TencentUnitManager.setUnitRouteRule(mapper.readValue(content, UnitRouteInfo.class));
+
+ rawContent = content;
+ LOGGER.info("[unit] unit route rule new raw content:\n{}", rawContent);
+ }
+
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TsfUnitConsulManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TsfUnitConsulManager.java
new file mode 100644
index 000000000..ce2d7c3bc
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/remote/TsfUnitConsulManager.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.remote;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.ecwid.consul.v1.ConsulClient;
+import com.tencent.polaris.api.utils.IPAddressUtils;
+import com.tencent.tsf.unit.core.Env;
+
+public final class TsfUnitConsulManager {
+
+ protected static final AtomicInteger POOL_SEQ = new AtomicInteger(1);
+
+ // 只需要 3 个长轮训
+ private final static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3, new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("tsf-unit-consul-" + POOL_SEQ.getAndIncrement());
+ t.setDaemon(true); // 设置为守护线程
+ return t;
+ }
+ });
+ private static ConsulClient consulClient = null;
+
+ private static volatile boolean init = false;
+
+ private TsfUnitConsulManager() {
+ }
+
+ public static synchronized void init() {
+ if (!init) {
+ init = true;
+ consulClient = new ConsulClient(IPAddressUtils.getIpCompatible(Env.getConsulHost()), Env.getConsulPort());
+
+ // 初始化先同步拉取一次
+ TencentUnitArchKVLoader.syncUnitArch();
+ TencentUnitRouteRuleKVLoader.syncUnitRouteRule();
+ TencentUnitGrayKVLoader.syncUnitGray();
+ // 启动长轮训定时任务
+ executorService.scheduleAtFixedRate(TencentUnitArchKVLoader::syncUnitArch, 55, 1, TimeUnit.SECONDS);
+ executorService.scheduleAtFixedRate(TencentUnitRouteRuleKVLoader::syncUnitRouteRule, 55, 1, TimeUnit.SECONDS);
+ executorService.scheduleAtFixedRate(TencentUnitGrayKVLoader::syncUnitGray, 55, 1, TimeUnit.SECONDS);
+ }
+ }
+
+ public static ConsulClient getConsulClient() {
+ return consulClient;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/utils/TencentUnitUtils.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/utils/TencentUnitUtils.java
new file mode 100644
index 000000000..5f656bd75
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/utils/TencentUnitUtils.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import com.tencent.polaris.api.utils.StringUtils;
+import com.tencent.tsf.unit.core.TencentUnitContext;
+import com.tencent.tsf.unit.core.TencentUnitContext.UnitCompositeContextMap;
+import com.tencent.tsf.unit.core.TencentUnitManager;
+import com.tencent.tsf.unit.core.UnitTag;
+import com.tencent.tsf.unit.core.UnitTagConstant;
+import com.tencent.tsf.unit.core.exception.ErrorCode;
+import com.tencent.tsf.unit.core.exception.TencentUnitException;
+import com.tencent.tsf.unit.core.model.RoutingUnit;
+import com.tencent.tsf.unit.core.model.RoutingUnitContext;
+import com.tencent.tsf.unit.core.model.TargetUnitInfo;
+import com.tencent.tsf.unit.core.model.UnitArch;
+import com.tencent.tsf.unit.core.model.UnitInfo;
+import com.tencent.tsf.unit.core.model.UnitNamespace;
+import com.tencent.tsf.unit.core.model.UnitRouteInfo;
+import com.tencent.tsf.unit.core.model.UnitTagPosition;
+import com.tencent.tsf.unit.core.model.UnitType;
+import com.tencent.tsf.unit.core.remote.TsfUnitConsulManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 该类提供给客户使用,客户不直接使用 TencentUnitManager,后续的迭代只需要这里保持兼容性即可.
+ */
+public final class TencentUnitUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TencentUnitUtils.class);
+
+ private TencentUnitUtils() {
+ }
+
+ /**
+ * 根据传入的客户要素(customerIdentifier),判断是否和本地的所在单元是同一个单元.
+ */
+ public static boolean checkLocalUnit(String customerIdentifier) {
+ RoutingUnit routingUnit = TencentUnitManager.getRoutingUnit(customerIdentifier);
+ if (routingUnit != null) {
+ return StringUtils.equals(routingUnit.getUnitId(), TencentUnitManager.getLocalUnitId());
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * 传入 unitId(单元号),判断是否和该单元号处在同一个 cloud space 里.
+ */
+ public static boolean checkLocalCloudSpace(String unitId) {
+ String targetCloudId = TencentUnitManager.getUnitCloudMap().get(unitId);
+ return StringUtils.equals(targetCloudId, TencentUnitManager.getLocalCloudSpaceId());
+ }
+
+
+ /**
+ * 获取本地对应的SDU信息,若在GDU上执行,返回 null,需适配灰度.
+ *
+ * @return UnitInfo 单元信息
+ */
+ public static UnitInfo getLocalStandardUnitInfo() {
+ // 判断是否在 gdu 上
+ if (TencentUnitManager.getGduNsMap().containsKey(TencentUnitManager.getLocalUnitId())
+ || TencentUnitManager.getGrayGduNsMap().containsKey(TencentUnitManager.getLocalUnitId())) {
+ return null;
+ }
+
+ return getStandardUnitInfo(TencentUnitManager.getLocalUnitId());
+ }
+
+ /**
+ * 获取本Region下对应的GDU,需要适配灰度.
+ *
+ * @return 单元信息
+ */
+ public static UnitInfo getLocalGlobalUnitInfo() {
+ UnitInfo info = new UnitInfo();
+
+ UnitArch.CloudSpace cloudSpace = TencentUnitManager.getLocalCloudSpace();
+ if (cloudSpace != null) {
+ info.setRegionId(cloudSpace.getRegionId());
+ info.setRegionName(cloudSpace.getRegionName());
+ List gduList;
+ if (TencentUnitManager.inGrayUnit()) {
+ gduList = cloudSpace.getGrayGdus();
+ }
+ else {
+ gduList = cloudSpace.getGdus();
+ }
+ // 默认就是只有一个GDU
+ for (UnitArch.Gdu gdu : gduList) {
+ String failoverUnitId = TencentUnitManager.getFailoverUnitIdMap().get(gdu.getId());
+ // 如果有容灾单元,则使用容灾单元
+ if (StringUtils.isNotEmpty(failoverUnitId)
+ && TencentUnitManager.getAllGduMap().containsKey(failoverUnitId)) {
+ gdu = TencentUnitManager.getAllGduMap().get(failoverUnitId);
+ }
+ info.setUnitId(gdu.getId());
+ info.setUnitType(UnitType.GDU.toString());
+ info.setZoneId(gdu.getZoneId());
+ info.setZoneName(gdu.getZoneName());
+ info.setAllNamespaceList(gdu.getNamespaces());
+ info.setAllShardingKeyList(Collections.emptyList());
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 根据目标业务系统,目标客户要素,获取目标SDU信息,需适配灰度,需适配容灾.
+ *
+ * @param systemName 目标业务系统
+ * @param cid 目标客户要素
+ * @return 目标单元信息
+ */
+ public static TargetUnitInfo getTargetStandardUnitInfo(String systemName, String cid) {
+ UnitInfo unitInfo = null;
+ TargetUnitInfo targetUnitInfo = null;
+ String customerNumber = null;
+ // 在灰度单元内
+ if (TencentUnitManager.inGrayUnit()) {
+ unitInfo = getStandardUnitInfo(TencentUnitManager.getGraySduUnitId());
+ targetUnitInfo = new TargetUnitInfo(unitInfo);
+ // 灰度不需要计算映射值
+ }
+ else {
+ RoutingUnit routingUnit = TencentUnitManager.getRoutingUnit(cid);
+ if (routingUnit == null) {
+ LOGGER.error("[unit] cid: {} not get any match unit route", cid);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("system: %s, cid: %s not found match unit route", systemName, cid));
+ }
+ customerNumber = routingUnit.getCustomerNumber();
+ unitInfo = getStandardUnitInfo(routingUnit.getUnitId());
+ targetUnitInfo = new TargetUnitInfo(unitInfo);
+ // 设置计算转换后的映射值
+ targetUnitInfo.setShardingKey(routingUnit.getShardingKey());
+ }
+
+ // 找到目标Namespace信息
+ for (UnitNamespace namespace : targetUnitInfo.getAllNamespaceList()) {
+ if (namespace.getBusinessSystemName().equals(systemName)) {
+ targetUnitInfo.setNamespaceId(namespace.getId());
+ targetUnitInfo.setNamespaceName(namespace.getName());
+ return targetUnitInfo;
+ }
+ }
+
+ LOGGER.error("[unit] cid: {}, customerNumber: {}, system: {}, not found target namespace",
+ cid, customerNumber, systemName);
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("system: %s, cid: %s not found target unit namespace", systemName, cid));
+ }
+
+
+ /**
+ * 返回所有地域所有的SDU,.
+ * from 2025-01-17 需适配灰度
+ * 需要适配容灾(不返回故障单元,故障单元的分配挪到对应的容灾单元)
+ * 使用场景: 在消息队列发送消息时用到的,需要广播消息到所有活跃中的SDU
+ */
+ public static List getAllStandardUnitInfoList() {
+
+ // 当前是否处于灰度单元内
+ if (TencentUnitManager.inGrayUnit()) {
+ return getGraySduUnitInfos();
+ }
+ else {
+ return getSduUnitInfos();
+ }
+ }
+
+
+ /**
+ * 获取路由上下文传递过来的单元化信息.
+ */
+ public static RoutingUnitContext getRoutingUnitContext() {
+ String customerNumber = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_CUSTOMER_NUMBER);
+ String shardingKey = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_SHARDING_KEY);
+ String unitId = TencentUnitContext.getSourceTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID);
+
+ return new RoutingUnitContext(customerNumber, shardingKey, unitId);
+ }
+
+ /**
+ * 设置业务的灰度标识。position为灰度标识的位置,只支持传递HEADER,tags为标识组合.
+ */
+ public static void putGrayTags(String position, Map tags) {
+ UnitTagPosition unitTagPosition = UnitTagPosition.fromString(position);
+ if (unitTagPosition == null) {
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "gray tag position format error, pos:" + position);
+ }
+ TencentUnitContext.clearGrayUserContext();
+ switch (unitTagPosition) {
+ case HEADER:
+ break;
+ default:
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "gray tag position only support header, pos:" + position);
+ }
+ TencentUnitContext.putGrayUserTags(position, tags);
+ }
+
+
+ public static void putCustomerTags(String system, String cid) {
+ TencentUnitContext.clearUserTags();
+
+ TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, system);
+ TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_CUSTOMER_IDENTIFIER, cid);
+ }
+
+ /**
+ * context写入unit相关数据.
+ *
+ * @param system 业务系统
+ * @param cid 客户要素
+ * @param global 是否只转给GDU
+ */
+ public static void putCustomerTags(String system, String cid, boolean global) {
+ putCustomerTags(system, cid);
+ if (global) {
+ TencentUnitContext.putSystemTag(TencentUnitContext.CLOUD_SPACE_GDU_FORWARD_ONLY, Boolean.TRUE.toString());
+ }
+ }
+
+ /**
+ * 往context里写入目标业务系统+目标单元号.
+ */
+ public static void putTargetUnitTags(String system, String unitId) {
+ TencentUnitContext.clearUserTags();
+
+ TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_SYSTEM, system);
+ TencentUnitContext.putUserTag(TencentUnitContext.CLOUD_SPACE_TARGET_UNIT_ID, unitId);
+ }
+
+ /**
+ * tsf spring cloud 普通应用通过注解开启,网关默认都开启.
+ */
+ public static void enable() {
+ TencentUnitManager.setEnable(true);
+ TsfUnitConsulManager.init();
+ }
+
+ public static UnitCompositeContextMap getCompositeContextMap() {
+ return TencentUnitContext.getCompositeContextMap();
+ }
+
+ public static void setUnitCompositeContextMap(UnitCompositeContextMap unitCompositeContextMap) {
+ TencentUnitContext.setUnitCompositeContextMap(unitCompositeContextMap);
+ }
+
+ public static List getALLSharding() {
+ List unitRoutes = TencentUnitManager.getUnitRoutes();
+ if (unitRoutes.size() == 0) {
+ return Collections.emptyList();
+ }
+ List shardingIdList = new ArrayList<>();
+ for (UnitRouteInfo.UnitRoute unitRoute : unitRoutes) {
+ appendShardingIdList(unitRoute, shardingIdList);
+ }
+ shardingIdList.sort(Comparator.naturalOrder());
+
+ return shardingIdList;
+ }
+
+ private static void appendShardingIdList(UnitRouteInfo.UnitRoute unitRoute, List shardingIdList) {
+ // 单元化路径规则下仅支持识别操作符:range,equal,in
+ List unitTagList = unitRoute.getMatch().getConditions();
+ for (UnitTag unitTag : unitTagList) {
+ if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.RANGE)) {
+ String[] values = unitTag.getTagValue().split("\\s*,\\s*");
+ if (values.length != 2) {
+ LOGGER.warn("[unit] unit rule tag value: {} is invalid", unitTag.getTagValue());
+ continue;
+ }
+
+ // 转换为int进行range匹配
+ int start = Integer.parseInt(values[0]);
+ int end = Integer.parseInt(values[1]);
+ for (int i = start; i <= end; i++) {
+ shardingIdList.add(i);
+ }
+ }
+ else if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.EQUAL)) {
+ int target = Integer.parseInt(unitTag.getTagValue());
+ shardingIdList.add(target);
+ }
+ else if (unitTag.getTagOperator().equals(UnitTagConstant.OPERATOR.IN)) {
+ String[] values = unitTag.getTagValue().split("\\s*,\\s*");
+ for (String entry : values) {
+ int target = Integer.parseInt(entry);
+ shardingIdList.add(target);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 通过解析单元化架构配置,返回对应单元号的SDU.
+ * private 方法,不对外暴露
+ *
+ * @param unitId 单元号
+ * @return UnitInfo 单元信息
+ */
+ private static UnitInfo getStandardUnitInfo(String unitId) {
+ if (StringUtils.isEmpty(unitId)) {
+ LOGGER.error("[unit] not found sdu local id");
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, "unit id is empty");
+ }
+
+ UnitInfo info = new UnitInfo();
+ info.setUnitId(unitId);
+
+ // 填充单元信息,包括suds,namespaces等
+ fillStandardUnitInfo(info);
+
+ // 增加SharingList信息
+ appendStandardUnitShardingList(info);
+
+ return info;
+ }
+
+ private static void fillStandardUnitInfo(UnitInfo info) {
+ UnitArch.CloudSpace cloudSpace = TencentUnitManager.getLocalCloudSpace();
+ info.setRegionId(cloudSpace.getRegionId());
+ info.setRegionName(cloudSpace.getRegionName());
+ List sduList;
+ if (TencentUnitManager.inGrayUnit()) {
+ sduList = cloudSpace.getGraySdus();
+ }
+ else {
+ sduList = cloudSpace.getSdus();
+ }
+ for (UnitArch.Sdu sdu : sduList) {
+ if (!sdu.getId().equals(info.getUnitId())) {
+ continue;
+ }
+
+ info.setUnitType(UnitType.SDU.toString());
+ info.setZoneId(sdu.getZoneId());
+ info.setZoneName(sdu.getZoneName());
+ info.setAllNamespaceList(sdu.getNamespaces());
+ return;
+ }
+
+ LOGGER.error("[unit] unitId: {}, cloudSpace: {}, not found unit in sdu list",
+ info.getUnitId(), cloudSpace.getCloudId());
+ throw new TencentUnitException(ErrorCode.COMMON_PARAMETER_ERROR, String.format("local unitId: %s, cloudSpace: %s, not found in the sdu list",
+ info.getUnitId(), cloudSpace.getCloudId()));
+ }
+
+ /**
+ * 给定单元,返回路由规则里对应的shardingKeyList.
+ */
+ private static void appendStandardUnitShardingList(UnitInfo info) {
+ List unitRoutes = TencentUnitManager.getUnitRoutes();
+ if (unitRoutes.size() == 0) {
+ LOGGER.warn("[unit] unit routes size is empty, so shardingList is empty");
+ return;
+ }
+
+ List shardingIdList = new ArrayList<>();
+ for (UnitRouteInfo.UnitRoute unitRoute : unitRoutes) {
+ if (!unitRoute.getRoute().getActualUnitId().equals(info.getUnitId())) {
+ continue;
+ }
+
+ appendShardingIdList(unitRoute, shardingIdList);
+ }
+ // 容灾场景下会合并多个单元的 shardingKeyList, 这里需要排序
+ shardingIdList.sort(Comparator.naturalOrder());
+ info.setAllShardingKeyList(shardingIdList);
+ }
+
+
+ private static List getSduUnitInfos() {
+ List infos = new ArrayList<>();
+ for (UnitArch.CloudSpace cloudSpace : TencentUnitManager.getAllCloudSpaces()) {
+ if (cloudSpace.getSdus() == null) {
+ continue;
+ }
+ for (UnitArch.Sdu sdu : cloudSpace.getSdus()) {
+ // 过滤非活跃单元(故障单元、未配置路由单元)
+ if (!TencentUnitManager.getActiveSduIdMap().containsKey(sdu.getId())) {
+ continue;
+ }
+ infos.add(getUnitInfo(sdu, cloudSpace));
+ }
+ }
+ return infos;
+ }
+
+ private static List getGraySduUnitInfos() {
+ List infos = new ArrayList<>();
+ for (UnitArch.CloudSpace cloudSpace : TencentUnitManager.getAllCloudSpaces()) {
+ if (cloudSpace.getGraySdus() == null) {
+ continue;
+ }
+ for (UnitArch.Sdu sdu : cloudSpace.getGraySdus()) {
+ infos.add(getUnitInfo(sdu, cloudSpace));
+ }
+ }
+ return infos;
+ }
+
+ private static UnitInfo getUnitInfo(UnitArch.Sdu sdu, UnitArch.CloudSpace cloudSpace) {
+ UnitInfo info = new UnitInfo();
+ // 单元基础信息
+ info.setUnitId(sdu.getId());
+ info.setUnitType(UnitType.SDU.toString());
+ info.setZoneId(sdu.getZoneId());
+ info.setZoneName(sdu.getZoneName());
+ info.setAllNamespaceList(sdu.getNamespaces());
+ // 增加Region
+ info.setRegionId(cloudSpace.getRegionId());
+ info.setRegionName(cloudSpace.getRegionName());
+ // 增加SharingList信息
+ appendStandardUnitShardingList(info);
+ return info;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/zonefilter/TsfZoneFilterManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/zonefilter/TsfZoneFilterManager.java
new file mode 100644
index 000000000..5aa21257a
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/java/com/tencent/tsf/unit/core/zonefilter/TsfZoneFilterManager.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 www.tencent.com.
+ * All Rights Reserved.
+ * This program is the confidential and proprietary information of
+ * www.tencent.com ("Confidential Information"). You shall not disclose such
+ * Confidential Information and shall use it only in accordance with
+ * the terms of the license agreement you entered into with www.tencent.com.
+ */
+
+package com.tencent.tsf.unit.core.zonefilter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 当前通过从单元化配置解析,后续支持 TSF 管控端下发.
+ */
+public final class TsfZoneFilterManager {
+
+ private TsfZoneFilterManager() {
+ }
+
+ private volatile static Set disabledZoneSet = new HashSet<>();
+
+ public static Set getDisabledZoneSet() {
+ return disabledZoneSet;
+ }
+
+ public static void setDisabledZoneSet(Set disabledZoneSet) {
+ TsfZoneFilterManager.disabledZoneSet = disabledZoneSet;
+ }
+}
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..d1147e2f1
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.tencent.cloud.plugin.unit.config.GatewayUnitAutoConfiguration
\ No newline at end of file
diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/test/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfigurationTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/test/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfigurationTest.java
new file mode 100644
index 000000000..e6c28b950
--- /dev/null
+++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-unit-plugin/src/test/java/com/tencent/cloud/plugin/unit/config/UnitAutoConfigurationTest.java
@@ -0,0 +1,25 @@
+package com.tencent.cloud.plugin.unit.config;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class UnitAutoConfigurationTest {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(UnitAutoConfiguration.class))
+ .withPropertyValues(
+ "tsf_consul_ip=localhost"
+ );
+
+ @Test
+ void shouldCreateBeansWhenConditionsMet() {
+ contextRunner.run(context -> {
+ assertThat(context).hasSingleBean(UnitBeanPostProcessor.class);
+ });
+ }
+}
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/filter/EnhancedServletFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/filter/EnhancedServletFilter.java
index 349de3894..4c31a2a3c 100644
--- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/filter/EnhancedServletFilter.java
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/instrument/filter/EnhancedServletFilter.java
@@ -73,6 +73,7 @@ public class EnhancedServletFilter extends OncePerRequestFilter {
.url(URI.create(request.getRequestURL().toString()))
.build();
enhancedPluginContext.setRequest(enhancedRequestContext);
+ enhancedPluginContext.setOriginRequest(request);
enhancedPluginContext.setLocalServiceInstance(pluginRunner.getLocalServiceInstance());
diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java
index fae0be2b0..7e6fd0f67 100644
--- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java
+++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/plugin/PluginOrderConstant.java
@@ -72,6 +72,11 @@ public class PluginOrderConstant {
*/
public static final int CONSUMER_TRANSFER_METADATA_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
+ /**
+ * order for ${@link com.tencent.cloud.plugin.unit.plugin.UnitRestTemplateEnhancedPlugin}.
+ */
+ public static final int CONSUMER_UNIT_METADATA_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 11;
+
/**
* order for
* {@link com.tencent.cloud.plugin.trace.TraceMetadataEnhancedPlugin}.