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}.