From 9b68f0575210d369b61fb886426deeb370d5aa15 Mon Sep 17 00:00:00 2001 From: SkyeBeFreeman <928016560@qq.com> Date: Thu, 29 Jul 2021 15:09:00 +0800 Subject: [PATCH] feat:first commit --- .gitignore | 56 ++++ .mvn/wrapper/MavenWrapperDownloader.java | 117 +++++++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 2 + CONTRIBUTING.md | 26 ++ Code-of-Conduct.md | 6 + LICENSE | 105 ++++++ README-zh.md | 73 +++++ README.md | 74 +++++ mvnw | 310 ++++++++++++++++++ mvnw.cmd | 182 ++++++++++ pom.xml | 167 ++++++++++ spring-cloud-tencent-converage/pom.xml | 78 +++++ spring-cloud-tencent-dependencies/pom.xml | 167 ++++++++++ spring-cloud-tencent-docs/pom.xml | 17 + .../src/main/doc-zh/dependency-management.md | 17 + .../doc-zh/spring-cloud-tencent-metadata.md | 59 ++++ ...ng-cloud-tencent-polaris-circuitbreaker.md | 43 +++ .../spring-cloud-tencent-polaris-discovery.md | 88 +++++ .../spring-cloud-tencent-polaris-ratelimit.md | 48 +++ .../src/main/doc/dependency-management.md | 18 + .../main/doc/spring-cloud-tencent-metadata.md | 59 ++++ ...ng-cloud-tencent-polaris-circuitbreaker.md | 43 +++ .../spring-cloud-tencent-polaris-discovery.md | 85 +++++ .../spring-cloud-tencent-polaris-ratelimit.md | 47 +++ .../README-zh.md | 78 +++++ .../polaris-circuitbreaker-example/README.md | 79 +++++ .../polaris-circuitbreaker-example-a/pom.xml | 118 +++++++ .../circuitbreaker/example/ProviderB.java | 39 +++ .../example/ProviderBFallback.java | 35 ++ .../circuitbreaker/example/ServiceA.java | 46 +++ .../example/ServiceAController.java | 73 +++++ .../src/main/resources/bootstrap.yml | 40 +++ .../src/main/resources/conf/polaris.yml | 287 ++++++++++++++++ .../polaris-circuitbreaker-example-b/pom.xml | 116 +++++++ .../circuitbreaker/example/ProviderA.java | 39 +++ .../example/ProviderAFallback.java | 34 ++ .../circuitbreaker/example/ServiceB.java | 46 +++ .../example/ServiceBController.java | 75 +++++ .../src/main/resources/bootstrap.yml | 40 +++ .../polaris-circuitbreaker-example/pom.xml | 64 ++++ .../polaris-discovery-example/README-zh.md | 81 +++++ .../polaris-discovery-example/README.md | 81 +++++ .../discovery-callee-service/pom.xml | 51 +++ .../callee/DiscoveryCalleeController.java | 54 +++ .../callee/DiscoveryCalleeService.java | 32 ++ .../src/main/resources/bootstrap.yml | 9 + .../src/main/resources/log4j.properties | 15 + .../src/main/resources/logback-spring.xml | 30 ++ .../discovery-caller-service/pom.xml | 70 ++++ .../caller/DiscoveryCalleeService.java | 39 +++ .../DiscoveryCalleeServiceCallback.java | 32 ++ .../caller/DiscoveryCallerController.java | 62 ++++ .../caller/DiscoveryCallerService.java | 46 +++ .../src/main/resources/bootstrap.yml | 9 + .../src/main/resources/log4j.properties | 15 + .../src/main/resources/logback-spring.xml | 31 ++ .../polaris-discovery-example/pom.xml | 65 ++++ .../polaris-ratelimit-example/README-zh.md | 61 ++++ .../polaris-ratelimit-example/README.md | 68 ++++ .../polaris-ratelimit-example/pom.xml | 20 ++ .../ratelimit-callee-service/pom.xml | 34 ++ .../service/callee/BusinessController.java | 76 +++++ .../callee/RateLimitCalleeService.java | 43 +++ .../src/main/resources/bootstrap.yml | 11 + .../src/main/resources/log4j.properties | 15 + .../src/main/resources/rule.json | 19 ++ spring-cloud-tencent-examples/pom.xml | 24 ++ spring-cloud-tencent-starters/pom.xml | 53 +++ .../pom.xml | 96 ++++++ .../PolarisFeignClientAutoConfiguration.java | 75 +++++ .../feign/PolarisFeignBeanPostProcessor.java | 91 +++++ ...olarisFeignBlockingLoadBalancerClient.java | 36 ++ .../feign/PolarisFeignClient.java | 94 ++++++ .../feign/PolarisLoadBalancerFeignClient.java | 37 +++ .../spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 2 + .../feign/PolarisFeignClientTest.java | 62 ++++ .../feign/TestPolarisFeignApp.java | 63 ++++ .../src/test/resources/application.yml | 15 + .../pom.xml | 135 ++++++++ .../cloud/polaris/PolarisProperties.java | 233 +++++++++++++ .../cloud/polaris/client/PolarisClient.java | 52 +++ .../ConditionalOnPolarisDiscoveryEnabled.java | 36 ++ .../PolarisDiscoveryAutoConfiguration.java | 71 ++++ .../discovery/PolarisDiscoveryClient.java | 61 ++++ .../PolarisDiscoveryClientConfiguration.java | 46 +++ .../discovery/PolarisDiscoveryHandler.java | 96 ++++++ .../discovery/PolarisServiceDiscovery.java | 69 ++++ .../PolarisReactiveDiscoveryClient.java | 84 +++++ ...sReactiveDiscoveryClientConfiguration.java | 50 +++ .../PolarisAutoServiceRegistration.java | 95 ++++++ .../polaris/registry/PolarisRegistration.java | 89 +++++ .../registry/PolarisServiceRegistry.java | 192 +++++++++++ ...larisServiceRegistryAutoConfiguration.java | 70 ++++ ...larisDiscoveryRibbonAutoConfiguration.java | 37 +++ .../PolarisRibbonServerListConfiguration.java | 42 +++ .../polaris/ribbon/PolarisServerList.java | 74 +++++ ...itional-spring-configuration-metadata.json | 46 +++ .../main/resources/META-INF/spring.factories | 5 + .../cloud/polaris/PolarisPropertiesTest.java | 50 +++ ...PolarisDiscoveryAutoConfigurationTest.java | 80 +++++ ...larisDiscoveryClientConfigurationTest.java | 75 +++++ .../discovery/PolarisDiscoveryClientTest.java | 68 ++++ .../PolarisServiceDiscoveryTest.java | 108 ++++++ ...ctiveDiscoveryClientConfigurationTest.java | 73 +++++ .../PolarisReactiveDiscoveryClientTest.java | 73 +++++ ...sServiceRegistryAutoConfigurationTest.java | 77 +++++ .../registry/PolarisServiceRegistryTest.java | 107 ++++++ ...RibbonServerListAutoConfigurationTest.java | 83 +++++ .../polaris/ribbon/PolarisServerListTest.java | 133 ++++++++ .../pom.xml | 130 ++++++++ .../ratelimit/RateLimitConfiguration.java | 75 +++++ .../ratelimit/callee/QuotaCheckFilter.java | 90 +++++ .../cloud/polaris/ratelimit/utils/Consts.java | 26 ++ ...itional-spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 2 + .../controller/CalleeControllerTests.java | 137 ++++++++ .../ratelimit/controller/TestController.java | 35 ++ .../pom.xml | 67 ++++ .../router/PolarisRoutingLoadBalancer.java | 96 ++++++ .../PolarisRibbonAutoConfiguration.java | 56 ++++ .../PolarisRibbonClientConfiguration.java | 56 ++++ .../config/PolarisRibbonProperties.java | 64 ++++ .../router/rule/PolarisLoadBalanceRule.java | 53 +++ .../rule/PolarisWeightedRandomRule.java | 68 ++++ ...itional-spring-configuration-metadata.json | 16 + .../main/resources/META-INF/spring.factories | 2 + .../PolarisRibbonAutoConfigurationTest.java | 57 ++++ .../spring-cloud-tencent-commons/pom.xml | 67 ++++ .../util/ApplicationContextAwareUtils.java | 69 ++++ .../cloud/common/util/ReflectionUtils.java | 43 +++ .../cloud/polaris/pojo/PolarisServer.java | 109 ++++++ .../polaris/pojo/PolarisServiceInstance.java | 90 +++++ .../main/resources/META-INF/spring.factories | 3 + .../spring-cloud-tencent-feign/pom.xml | 49 +++ .../tencent/cloud/feign/PluggableFeign.java | 163 +++++++++ .../PluggableFeignAutoConfiguration.java | 46 +++ .../cloud/feign/PluggableFeignContext.java | 124 +++++++ .../feign/PluggableFeignContractHolder.java | 53 +++ .../PluggableFeignInvocationHandler.java | 172 ++++++++++ .../cloud/feign/PluggableFeignPlugin.java | 49 +++ .../cloud/feign/PluggableFeignPluginType.java | 42 +++ ...itional-spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 2 + .../spring-cloud-tencent-metadata/pom.xml | 89 +++++ .../config/MetadataConfiguration.java | 187 +++++++++++ .../config/MetadataLocalProperties.java | 67 ++++ .../metadata/constant/MetadataConstant.java | 111 +++++++ .../metadata/context/MetadataContext.java | 77 +++++ .../context/MetadataContextHolder.java | 133 ++++++++ .../core/filter/MetadataReactiveFilter.java | 101 ++++++ .../core/filter/MetadataServletFilter.java | 92 ++++++ .../Metadata2HeaderFeignInterceptor.java | 80 +++++ .../MetadataRestTemplateInterceptor.java | 79 +++++ .../feign/MetadataFirstFeignPlugin.java | 79 +++++ .../cloud/metadata/util/JacksonUtils.java | 74 +++++ .../cloud/metadata/util/MetadataUtils.java | 60 ++++ ...itional-spring-configuration-metadata.json | 14 + .../main/resources/META-INF/spring.factories | 3 + .../config/MetadataConfigurationTest.java | 126 +++++++ .../config/MetadataLocalPropertiesTest.java | 59 ++++ .../context/MetadataContextHolderTest.java | 86 +++++ .../filter/MetadataReactiveFilterTest.java | 86 +++++ .../filter/MetadataServletFilterTest.java | 82 +++++ .../Metadata2HeaderFeignInterceptorTest.java | 106 ++++++ .../MetadataRestTemplateInterceptorTest.java | 101 ++++++ .../src/test/resources/application-test.yml | 11 + .../pom.xml | 131 ++++++++ .../context/PolarisConfigModifier.java | 33 ++ .../context/PolarisContextConfiguration.java | 68 ++++ .../context/PolarisContextProperties.java | 83 +++++ .../spring-configuration-metadata.json | 18 + .../main/resources/META-INF/spring.factories | 1 + .../context/PolarisContextApplication.java | 25 ++ .../PolarisContextConfigurationTest.java | 45 +++ .../context/PolarisContextGetHostTest.java | 43 +++ .../src/test/resources/application-test.yml | 4 + 178 files changed, 11818 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 CONTRIBUTING.md create mode 100644 Code-of-Conduct.md create mode 100644 LICENSE create mode 100644 README-zh.md create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 spring-cloud-tencent-converage/pom.xml create mode 100644 spring-cloud-tencent-dependencies/pom.xml create mode 100644 spring-cloud-tencent-docs/pom.xml create mode 100644 spring-cloud-tencent-docs/src/main/doc-zh/dependency-management.md create mode 100644 spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-metadata.md create mode 100644 spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-circuitbreaker.md create mode 100644 spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-discovery.md create mode 100644 spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-ratelimit.md create mode 100644 spring-cloud-tencent-docs/src/main/doc/dependency-management.md create mode 100644 spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-metadata.md create mode 100644 spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-circuitbreaker.md create mode 100644 spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-discovery.md create mode 100644 spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-ratelimit.md create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/conf/polaris.yml create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderA.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderAFallback.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/README.md create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeController.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeService.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/log4j.properties create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/logback-spring.xml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeService.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeServiceCallback.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerController.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerService.java create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/log4j.properties create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/logback-spring.xml create mode 100644 spring-cloud-tencent-examples/polaris-discovery-example/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/README.md create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/log4j.properties create mode 100644 spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/rule.json create mode 100644 spring-cloud-tencent-examples/pom.xml create mode 100644 spring-cloud-tencent-starters/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBeanPostProcessor.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBlockingLoadBalancerClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisLoadBalancerFeignClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/TestPolarisFeignApp.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application.yml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/client/PolarisClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/ConditionalOnPolarisDiscoveryEnabled.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscovery.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisAutoServiceRegistration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisRegistration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisDiscoveryRibbonAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisServerList.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/PolarisPropertiesTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/callee/QuotaCheckFilter.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/Consts.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/CalleeControllerTests.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/TestController.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRoutingLoadBalancer.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonClientConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonProperties.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisLoadBalanceRule.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisWeightedRandomRule.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServer.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServiceInstance.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeign.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignAutoConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContext.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContractHolder.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignInvocationHandler.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPlugin.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPluginType.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataLocalProperties.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/constant/MetadataConstant.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContext.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContextHolder.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilter.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilter.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/feign/Metadata2HeaderFeignInterceptor.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/resttemplate/MetadataRestTemplateInterceptor.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/plugin/feign/MetadataFirstFeignPlugin.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/JacksonUtils.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/MetadataUtils.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataLocalPropertiesTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/context/MetadataContextHolderTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilterTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilterTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/feign/Metadata2HeaderFeignInterceptorTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/resttemplate/MetadataRestTemplateInterceptorTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/resources/application-test.yml create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/pom.xml create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisConfigModifier.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextApplication.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextConfigurationTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..704fcd5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Eclipse project files +.project +.classpath +.settings + +# IntelliJ IDEA project files and directories +*.iml +*.ipr +*.iws +.idea/ + +# Build targets +/target +*/target +target +/applog +*/applog +applog + +# Mac-specific directory that no other operating system needs. +.DS_Store + +# JVM crash logs +hs_err_pid*.log + +dependency-reduced-pom.xml + +*/.unison.* + +# exclude docker-sync stuff +.docker-sync +*/.docker-sync + +# exclude vscode files +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.factorypath + +# misc +*classes +*.class +.svn +logs/ +lib/ +applog/ + +# Maven ignore +.flattened-pom.xml + +# Polaris +*/backup +/backup +backup diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..b901097f --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..642d572c --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8cfc2cc0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing +--- +If you have good comments or suggestions, welcome to create [Issues](https://github.com/Tencent/spring-cloud-tencent/issues) or [Pull Requests](https://github.com/Tencent/spring-cloud-tencent/pulls),contribute to the Spring Cloud Tencent open source community. Spring Cloud Tencent continues to recruit contributors, even if it is answering questions in the issue, or doing some simple bugfixes, it will be of great help to Spring Cloud Tencent. + +[Tencent Open Source Incentive Program](https://opensource.tencent.com/contribution) Encourage developers to participate and contribute, and look forward to your joining. + +## Issue +#### For contributors + +Please ensure that the following conditions are met before submitting an issue: + +* Must be a bug or new feature +* Have searched in the issue, and did not find a similar issue or solution +* When creating a new issue, please provide a detailed description, screenshot or short video to help us locate the problem + +## Pull Request +We welcome everyone to contribute code to make our product more powerful. The code team will monitor all pull requests, and we will do the corresponding code inspection and testing. After the test passes, we will accept the PR, but will not immediately merge into the master branch. + +Please confirm before completing a PR: + +1. Fork your own branch from the master branch. +2. Please modify the corresponding documents and comments after modifying the code. +3. Please add License and Copyright declarations in the newly created file. +4. Ensure a consistent code style. +5. Do adequate testing. +6. Then, you can submit your code to the dev branch. \ No newline at end of file diff --git a/Code-of-Conduct.md b/Code-of-Conduct.md new file mode 100644 index 00000000..9212b41b --- /dev/null +++ b/Code-of-Conduct.md @@ -0,0 +1,6 @@ +# Spring Cloud Tencent Community Code of Conduct + +Spring Cloud Tencent follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). + + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the Spring Cloud Tencent Code of Conduct Committee via email: SpringCloudTencent_Community@qq.com \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..03ef73e6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,105 @@ +Tencent is pleased to support the open source community by making spring-cloud-tencent available. + +Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + +A spring-cloud-tencent is licensed under the BSD 3-Clause License. A copy of the BSD 3-Clause License is included in this file. + + +Terms of BSD 3-Clause License +--------------------------------------------------- +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Other dependencies and licenses: + + +Open Source Software Licensed under the Apache License Version 2.0: +-------------------------------------------------------------------- +1. spring-boot +Copyright (c) spring-boot authors and contributors. + +2. spring-framework +Copyright (c) spring-framework authors and contributors. + +3. spring-cloud-netflix +Copyright (c) spring-cloud-netflix authors and contributors. + +4. spring-cloud config +Copyright (c) spring-cloud-config authors and contributors. + +5. guava +Copyright (c) guava authors and contributors. + +6. reactor +Copyright (c) reactor authors and contributors. + +7. powermock +Copyright 2007-2017 PowerMock Contributors + + +Terms of the Apache v2.0 License: +-------------------------------------------------------------------- +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/README-zh.md b/README-zh.md new file mode 100644 index 00000000..bd80849f --- /dev/null +++ b/README-zh.md @@ -0,0 +1,73 @@ +# Spring Cloud Tencent + +Spring Cloud Tencent包含了分布式应用微服务开发过程中所需的组件,基于 Spring Cloud 框架的开发者可以使用这些组件快速进行分布式应用的开发 + +在Spring Cloud Tencent的基础上,您只需要添加少量配置,就可以将 Spring Cloud 应用接入腾讯云微服务解决方案,通过腾讯云中间件来迅速搭建分布式应用系统。 + +## 主要功能 + +* **服务注册与发现**:基于 Spring Cloud Common的标准进行微服务的注册与发现 +* **服务路由与负载均衡**:基于 Ribbon 的接口标准,提供场景更丰富的动态路由以及负载均衡的能力 +* **故障节点熔断**:提供故障节点的熔断剔除以及主/被动探测恢复的能力,保证分布式服务的可靠性 +* **服务限流**:支持工作于微服务被调接入层的限流功能,保证后台微服务稳定性,可通过控制台动态配置规则,及查看流量监控数据 + +## 组件 + +**[Polaris](https://github.com/polarismesh)**:北极星云原生的服务治理平台,解决远程调用的服务注册发现、动态路由、负载均衡和容错问题。 + +## 如何构建 + +* master 分支对应的是 Spring Cloud Hoxton,最低支持 JDK 1.8。 + +Spring Cloud Tencent 使用 Maven 来构建,最快的使用方式是将本项目 clone 到本地,然后执行以下命令: +```bash + ./mvnw install +``` +执行完毕后,项目将被安装到本地 Maven 仓库。 + +## 如何使用 + +### 如何引入依赖 + +在 dependencyManagement 中添加如下配置,然后在 dependencies 中添加自己所需使用的依赖即可使用。 + +```` + + + + com.tencent.cloud + spring-cloud-tencent-dependencies + + ${version} + pom + import + + + +```` + +### 示例 + +Spring Cloud Tencent 项目包含了一个子模块spring-cloud-tencent-examples。此模块中提供了体验接入用的 example ,您可以阅读对应的 example 工程下的 readme 文档,根据里面的步骤来体验。 + +Example 列表: + +- [Polaris Discovery Example](spring-cloud-tencent-examples/polaris-discovery-example/README.md) + +- [Polaris CircuitBreaker Example](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md) + +- [Polaris RateLimit Example](spring-cloud-tencent-examples/polaris-ratelimit-example/README.md) + +## 版本号规范 + +采取与Spring Cloud大版本号相关的版本策略。 + +项目的版本号格式为 ```大版本号.小版本号.补丁版本号.对应Spring Cloud版本号.发布类型``` 的形式。 +大版本号、小版本号、补丁版本号的类型为数字,从 0 开始取值。项目处于孵化器阶段时,大版本号固定使用 0 。 +对应Spring Cloud版本号为Spring Cloud提供的英文版本号,例如Hoxton、Greenwich等。 +发布类型包括正式发布(RELEASE)、最终测试版(RC)、测试版(BETA)。 + +示例:0.1.0.Hoxton.BETA + +## License +The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) diff --git a/README.md b/README.md index c88abb37..121fdde6 100644 --- a/README.md +++ b/README.md @@ -1 +1,75 @@ # Spring Cloud Tencent + +Spring Cloud Tencent contains components distributed micro-service applications need during developing phase, developers that built their key architectures based on Spring Cloud can use these components + +Based on Spring Cloud Tencent, you only need a small configuration to launch Spring Cloud and micro-service's joint solutions. + +## Key Features + +* **Service Registration and Discovery**:Based on Spring Cloud's discovery and registration standard. +* **Service Routing and LoadBalancer**:Based on ribbon's API port, provide dynamic routing and load balancing use cases. +* **CircuitBreaker Node**:Support circuitbreak's auto-reset ability, ensure the reliability of distributed server +* **Rate Limiter**:Support rate limit between microservice and access layer, ensure the stability of backend, one can configure policies and traffic data from the control panel + +## component + +**[Polaris](https://github.com/polarismesh)**:Polaris Spring Cloud operation centre, provide solutions to registration, dynamic routing, load balancing and circuitbreaker. + +## How to build + +* master's branch matches Spring Cloud Hoxton, support lowest at JDK 1.8. + +Spring Cloud Tencent uses Maven to construct, the fastest way is to cone project to local files, then execute the following orders: + +```bash +./mvnw install +``` + +When all the steps are finished, the project will be installed in local Maven repository. + +## How to Use + +### How to Introduce Dependency + +Add the following configurations n dependencyManagement, then add the dependencies you need. + +```` + + + + com.tencent.cloud + spring-cloud-tencent-dependencies + + ${version} + pom + import + + + +```` + +### Example + +Spring Cloud Tencent project contains a sub-module spring-cloud-tencent-examples. This module provides examples for users to experience, you can read the README.md in each example, and follow the instructions there. + +Example List: + +- [Polaris Discovery Example](spring-cloud-tencent-examples/polaris-discovery-example/README.md) + +- [Polaris CircuitBreaker Example](spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md) + +- [Polaris RateLimit Example](spring-cloud-tencent-examples/polaris-ratelimit-example/README.md) + +### Version Standard + +Adopt a version policy related to Spring Cloud's major version number + +Project version includes major version. minor version. patch version. Correspond with the version of Spring Cloud release. +major version. minor version. patch version are in numbers, start from 0. This project is in the incubating phase, major version number is set to 0. +Spring Cloud's version number is the same as the English version number, like Hoxton, Greenwich. +Release type will include RELEASE, RC, BETA. + +For example: 0.1.0.Hoxton.BETA + +## License +The spring-cloud-tencent is licensed under the BSD 3-Clause License. Copyright and license information can be found in the file [LICENSE](LICENSE) diff --git a/mvnw b/mvnw new file mode 100644 index 00000000..41c0f0c2 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 00000000..86115719 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..cbe0fc7c --- /dev/null +++ b/pom.xml @@ -0,0 +1,167 @@ + + + + org.springframework.cloud + spring-cloud-build + 2.3.4.RELEASE + + + 4.0.0 + + com.tencent.cloud + spring-cloud-tencent + pom + ${revision} + Spring Cloud Tencent + Spring Cloud Tencent + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + spring-cloud-tencent-dependencies + spring-cloud-tencent-starters + spring-cloud-tencent-examples + spring-cloud-tencent-docs + spring-cloud-tencent-converage + + + + + SkyeBeFreeman + Haotian Zhang + 928016560@qq.com + Tencent + https://github.com/SkyeBeFreeman/ + + + + + + 0.1.0.Hoxton.BETA + + + Hoxton.SR9 + + + 0.8.0-SNAPSHOT + + + 3.7.0 + 2.21.0 + 3.2.0 + 1.1.0 + 0.8.3 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + + + com.tencent.cloud + spring-cloud-tencent-dependencies + ${revision} + pom + import + + + + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + true + + 1.8 + 1.8 + true + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + true + + 1 + false + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-converage/pom.xml b/spring-cloud-tencent-converage/pom.xml new file mode 100644 index 00000000..6649d2c1 --- /dev/null +++ b/spring-cloud-tencent-converage/pom.xml @@ -0,0 +1,78 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-converage + Spring Cloud Tencent Converage + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-circuitbreaker + + + + com.tencent.cloud + spring-cloud-tencent-metadata + + + + com.tencent.cloud + spring-cloud-tencent-feign + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + + + org.jacoco + jacoco-maven-plugin + + + report-aggregate + test + + report-aggregate + + + ${basedir}/../target/site/jacoco + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml new file mode 100644 index 00000000..88cee186 --- /dev/null +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -0,0 +1,167 @@ + + + + org.springframework.cloud + spring-cloud-dependencies-parent + 2.3.1.RELEASE + + + 4.0.0 + + com.tencent.cloud + spring-cloud-tencent-dependencies + 0.1.0.Hoxton.BETA + pom + Spring Cloud Tencent Dependencies + Spring Cloud Tencent Dependencies + + + 0.0.1-SNAPSHOT + 0.8.0-SNAPSHOT + 10.0.0-M6 + 2.0.0 + + + 3.2.0 + 3.1.1 + 1.1.0 + + + + + + polaris-dependencies + com.tencent.nameservice + ${polaris.version} + pom + import + + + org.apache.tomcat.embed + tomcat-embed-websocket + ${tomcat.version} + + + + com.tencent.cloud + spring-cloud-tencent-commons + ${revision} + + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + ${revision} + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-circuitbreaker + ${revision} + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-metadata + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-feign + ${revision} + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + ${revision} + + + + + org.powermock + powermock-module-junit4 + ${powermock.version} + + + + + org.powermock + powermock-api-mockito2 + ${powermock.version} + + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + package + + jar + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-docs/pom.xml b/spring-cloud-tencent-docs/pom.xml new file mode 100644 index 00000000..5fab38be --- /dev/null +++ b/spring-cloud-tencent-docs/pom.xml @@ -0,0 +1,17 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-docs + Spring Cloud Tencent Documentation + pom + + \ No newline at end of file diff --git a/spring-cloud-tencent-docs/src/main/doc-zh/dependency-management.md b/spring-cloud-tencent-docs/src/main/doc-zh/dependency-management.md new file mode 100644 index 00000000..c2c9c44d --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc-zh/dependency-management.md @@ -0,0 +1,17 @@ +# 依赖管理 + +如果您想使用Spring Cloud Tencent微服务开发套件,您可以直接依赖以下bom,即在pom.xml的中添加如下代码。后续使用bom下的依赖无需带版本号即可引入。 + +```xml + + + + com.tencent.cloud + spring-cloud-tencent-dependencies + 0.1.0.Hoxton.BETA + pom + import + + + +``` \ No newline at end of file diff --git a/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-metadata.md b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-metadata.md new file mode 100644 index 00000000..21352897 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-metadata.md @@ -0,0 +1,59 @@ +# Spring Cloud Tencent Metadata + +## 使用方式 + +### 可传递的自定义metadata + +> 可以从主调方传递给被调方的metadata + +- 获取只读的所有可传递的自定义metadata的映射表 +``` +Map customMetadataMap = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); +``` + +- 根据key获取可传递的自定义metadata +``` +String value = MetadataContextHolder.get().getTransitiveCustomMetadata(KEY); +``` + +- 以key-value形式保存可传递的自定义metadata +``` +MetadataContextHolder.get().putTransitiveCustomMetadata(KEY, VALUE); +``` + +- 从一个映射表中读取并保存到可传递的自定义metadata映射表中 +``` +MetadataContextHolder.get().putAllTransitiveCustomMetadata(ANOTHER_MAP); +``` + +### 系统metadata + +> 系统metadata不可被传递。 + +- 获取只读的所有系统metadata的映射表 +``` +Map systemMetadataMap = MetadataContextHolder.get().getAllSystemMetadata(); +``` + +- 根据key获取系统metadata +``` +String value = MetadataContextHolder.get().getSystemMetadata(KEY); +``` + +- 以key-value形式保存系统metadata +``` +MetadataContextHolder.get().putSystemMetadata(KEY, VALUE); +``` + +- 从一个映射表中读取并保存到系统metadata映射表中 +``` +MetadataContextHolder.get().putAllSystemMetadata(ANOTHER_MAP); +``` + +- 系统metadata映射表的key如下所示: +- LOCAL_NAMESPACE +- LOCAL_SERVICE +- LOCAL_PATH +- PEER_NAMESPACE +- PEER_SERVICE +- PEER_PATH diff --git a/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-circuitbreaker.md b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-circuitbreaker.md new file mode 100644 index 00000000..f5f3e1cc --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-circuitbreaker.md @@ -0,0 +1,43 @@ +# Polaris CircuitBreaker + +## 模块简介 + +```spring-cloud-starter-tencent-polaris-circuitbreaker```是用于Spring +Cloud项目对接服务治理平台[Polaris](https://github.com/polarismesh)的故障熔断模块。 +您可以通过引入依赖即可获得对微服务架构的服务限流能力。建议与```spring-cloud-starter-tencent-polaris-discovery```配合使用。 + +## 功能介绍 + +### 故障节点熔断 + +故障实例熔断能实现主调方迅速自动屏蔽错误率高或故障的服务实例,并启动定时任务对熔断实例进行探活。在达到恢复条件后对其进行半开恢复。在半开恢复后,释放少量请求去进行真实业务探测。并根据真实业务探测结果去判断是否完全恢复正常。 + +### 熔断策略 +- 故障比例熔断:当服务实例在上一个时间窗(默认1分钟)内,通过的请求量达到或超过最小请求阈值(默认10个),且错误率达到或超过故障比率阈值(默认50%),实例会进入隔离状态。故障比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。 +- 连续故障熔断:当实例在上一个时间窗(默认1分钟)内,连续失败的请求数达到或者超过连续故障阈值(默认10个),实例会进入隔离状态。 +- 熔断隔离时间:默认隔离30s,支持可配置。 + +相关配置请参考[Polaris故障熔断相关文档](https://github.com/polarismesh) + +## 快速入门 + +本章节将介绍如何最简单地在Spring Cloud项目中使用Polaris +CircuitBreaker的功能。启动微服务之前,需要启动Polaris,具体启动方式参考[Polaris](https://github.com/polarismesh)。 + +1. 您可以在项目中加入```spring-cloud-starter-tencent-polaris-circuitbreaker```依赖即可使用故障熔断的特性。如Maven项目中,在pom中添加如下配置: + +```XML + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-circuitbreaker + +``` + +2. 更加详细的使用方法参考 [Polaris CircuitBreaker Example](../../../../spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md)。 + +## 配置列表 + +| 配置项Key | 默认值 |是否必填| 配置项说明 | +| ----------------------------------------------- | -----------------------| --------- | ---------------------------- | +| spring.cloud.polaris.circuitbreaker.enabled | true |否| 是否开启故障熔断 | + diff --git a/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-discovery.md b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-discovery.md new file mode 100644 index 00000000..5750a82a --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-discovery.md @@ -0,0 +1,88 @@ +# Polaris Discovery + +## 模块简介 + +```spring-cloud-starter-tencent-polaris-discovery```是用于Spring +Cloud项目对接服务治理平台[Polaris](https://github.com/polarismesh)的服务发现模块。您可以通过引入依赖即可完成微服务注册到服务治理平台Polaris,获得对整个微服务架构的服务治理能力。 + +## 功能介绍 + +### 服务注册与发现 + +基于Spring Cloud的标准接口实现服务注册与发现。 + +### 服务路由 + +基于Ribbon的标准接口实现的支持多种场景的动态服务路由,是北极星提供规则路由的能力,通过规则来动态控制消息的分配转发。通过该功能,您者可以轻松实现多环境路由、分SET路由、灰度发布、集群容灾降级、金丝雀测试等功能。 + +同时,用户也可以利用独立于服务发现模块的自定义元数据功能来形成路由规则进行规则路由,进一步提升服务路由的灵活性。 + +### 负载均衡 + +负载均衡支持从满足本次转发要求的服务实例集中, 通过一定的均衡策略,选取一个实例返回给主调方,供主调方进行服务请求发送。负载均衡规则包括权重随机策略、权重响应时间策略和一致性哈希算法。 + +## 快速入门 + +本章节将介绍如何最简单地在Spring Cloud项目中使用Polaris +Discovery的功能。启动微服务之前,需要启动Polaris,具体启动方式参考[Polaris](https://github.com/polarismesh)。 + +1. 您可以在项目中加入```spring-cloud-starter-tencent-polaris-discovery```依赖即可使用Polaris的服务注册与发现功能。如Maven项目中,在pom中添加如下配置: + +```XML + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + +``` + +2. 在配置文件中主要添加如下配置,即可完成服务注册与发现(在Spring Cloud Edgware之后,无需使用```@EnableDiscoveryClient```即可进行服务注册与发现): + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + address: ${protocol}://${ip}:${port} +``` + +更加详细的使用方法参考 [Polaris Discovery Example](../../../../spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md)。 + +## 拓展使用 + +### 服务路由 + +- 您可以在Polaris控制台页面上配置路由规则,即可使用服务路由的功能。 +- 您也可以在配置文件(application.yml)中添加自定义元数据,然后再Polaris控制台页面上配置路由规则,也可使用服务路由的功能。样例配置如下所示,在应用运行时将读为Map的数据格式。 + +``` +spring: + cloud: + tencent: + content: + a: 1 + b: 2 +``` + +### 负载均衡 + +以权重随机策略为例,您可以在Polaris控制台页面上或者配置文件(application.yml)中添加权重值,即可使用负载均衡的功能。 + +## 配置列表 + +| 配置项Key | 默认值 |是否必填| 配置项说明 | +| ----------------------------------------------- | -----------------------| --------- | ---------------------------- | +| spring.cloud.polaris.server-addr | 无 |是| Polaris后端地址 | +| spring.cloud.polaris.discovery.service | ${spring.application.name} |否| 服务名称 | +| spring.cloud.polaris.discovery.enabled | true |否| 是否开启服务注册与发现 | +| spring.cloud.polaris.discovery.instance-enabled | true |否| 当前微服务实例是否可以被访问 | +| spring.cloud.polaris.discovery.token | 无 |否| 鉴权Token | +| spring.cloud.polaris.discovery.version | null |否| 微服务版本 | +| spring.cloud.polaris.protocol | null |否| 微服务协议类型 | +| spring.cloud.polaris.weight | 100 |否| 微服务权重 | +| spring.cloud.loadbalancer.polaris.enabled | true |否| 是否开启负载均衡 | +| spring.cloud.loadbalancer.polaris.strategy | weighted_random |否| 负载均衡策略 | +| spring.cloud.tencent.metadata.content | 无 |否| 自定义元数据,为Map结构 | +| spring.cloud.tencent.metadata.transitive | 无 |否| 需要传递的自定义元数据的key列表,为List结构 | + diff --git a/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-ratelimit.md b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-ratelimit.md new file mode 100644 index 00000000..b0208caa --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc-zh/spring-cloud-tencent-polaris-ratelimit.md @@ -0,0 +1,48 @@ +# Polaris RateLimit + +## 模块简介 + +```spring-cloud-starter-tencent-polaris-ratelimit```是用于Spring +Cloud项目对接服务治理平台[Polaris](https://github.com/polarismesh)的服务限流模块。 +您可以通过引入依赖即可获得对微服务架构的服务限流能力。建议与```spring-cloud-starter-tencent-polaris-discovery```配合使用。 + +## 功能介绍 + +### 服务级限流 + +支持为所有的HTTP服务提供限流功能。 + +默认引入spring-cloud-starter-tencent-polaris-ratelimit依赖即可对所有的HTTP服务执行限流检查。 + +### 接口级限流 + +支持为所有的HTTP调用根据path级别的提供限流功能。 + +默认引入spring-cloud-starter-tencent-polaris-ratelimit依赖即可对所有的HTTP path调用执行限流检查。 + +## 快速入门 + +本章节将介绍如何最简单地在Spring Cloud项目中使用Polaris +RateLimit的功能。启动微服务之前,需要启动Polaris,具体启动方式参考[Polaris](https://github.com/polarismesh)。 + +1. 您可以在项目中加入```spring-cloud-starter-tencent-polaris-ratelimit```依赖即可使用服务限流的特性。如Maven项目中,在pom中添加如下配置: + +```XML + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + +``` + +2. 添加限流规则配置 + +北极星提供了三种添加限流配置的方式,包括控制台操作、HTTP接口上传和本地文件配置,具体请参考[北极星服务限流使用文档](https://github.com/polarismesh) + +更加详细的使用方法参考 [Polaris RateLimit Example](../../../../spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md)。 + +## 配置列表 + +| 配置项Key | 默认值 |是否必填| 配置项说明 | +| ----------------------------------------------- | -----------------------| --------- | ---------------------------- | +| spring.cloud.polaris.ratelimit.enabled | true |否| 是否开启服务限流 | + diff --git a/spring-cloud-tencent-docs/src/main/doc/dependency-management.md b/spring-cloud-tencent-docs/src/main/doc/dependency-management.md new file mode 100644 index 00000000..384825e4 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc/dependency-management.md @@ -0,0 +1,18 @@ +# dependencyManagement + +if you want to use Spring Cloud Tencent micro-service software development kit, you can depend on the bom as below, add code as below at `````` in pom.xml. Going forward, continue using dependencies in bom no longer needs version number. + +```xml + + + + com.tencent.cloud + spring-cloud-tencent-dependencies + 0.1.0.Hoxton.BETA + pom + import + + + +``` + diff --git a/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-metadata.md b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-metadata.md new file mode 100644 index 00000000..75587a95 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-metadata.md @@ -0,0 +1,59 @@ +# Spring Cloud Tencent Metadata + +## Usage + +### Transitive Custom Metadata + +> Transitive custom metadata can be transferred from caller to callee. + +- Get all transitive custom metadata with read-only. +``` +Map customMetadataMap = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); +``` + +- Get transitive custom metadata with key. +``` +String value = MetadataContextHolder.get().getTransitiveCustomMetadata(KEY); +``` + +- Put transitive custom metadata with key-value. +``` +MetadataContextHolder.get().putTransitiveCustomMetadata(KEY, VALUE); +``` + +- Put transitive custom metadata with another map. +``` +MetadataContextHolder.get().putAllTransitiveCustomMetadata(ANOTHER_MAP); +``` + +### System Metadata + +> System metadata cannot be transferred. + +- Get all system metadata with read-only. +``` +Map systemMetadataMap = MetadataContextHolder.get().getAllSystemMetadata(); +``` + +- Get system metadata with key. +``` +String value = MetadataContextHolder.get().getSystemMetadata(KEY); +``` + +- Put system metadata with key-value. +``` +MetadataContextHolder.get().putSystemMetadata(KEY, VALUE); +``` + +- Put system metadata with another map. +``` +MetadataContextHolder.get().putAllSystemMetadata(ANOTHER_MAP); +``` + +- Map key list: +- LOCAL_NAMESPACE +- LOCAL_SERVICE +- LOCAL_PATH +- PEER_NAMESPACE +- PEER_SERVICE +- PEER_PATH diff --git a/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-circuitbreaker.md b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-circuitbreaker.md new file mode 100644 index 00000000..fe7afb65 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-circuitbreaker.md @@ -0,0 +1,43 @@ +# Polaris CircuitBreaker + +## Module Intro + +```spring-cloud-starter-tencent-polaris-circuitbreaker```is applied to Spring +Cloud project joint with [Polaris](https://github.com/polarismesh)'s CircuitBreaker module you can get cloud service engine's rate limit ability by introducing dependency. Recommended using with ```spring-cloud-starter-tencent-polaris-discovery```. + +## Key Features + +### Failed Node CircuitBreaker + +Failed instance circuitbreak can achieve caller service's immediate auto-block high failure rate command instance, and set timed task to conduct live probing. When the recover status is achieved, one can start half recovery. After half recovery, release a few request to test probing. Identify the recovery status from the probing result. + +### CircuitBreaker Strategy + +- Failed ratio circuitbreak: when command instance at a service window (default one minute) request rate has reached or passed the minimum request threshold (default 10), and failure rate reached or passed failure ratio threshold (default 50%), instance will enter insolation state. Failure rate threshold range is [0.0, 1.0] , represent 0% - 100%. +- continuous failure circuitbreak: when command instance at a service window (default), continuous failure request reached or exceeded failure threshold (default 10), instance will inter insolation state. +- circuitbreak insolation time: default 30 seconds, support configuration + +For configuration, please refer to [Polaris CircuitBreaker](https://github.com/polarismesh) + +## User Guide + +This chapter will explain how to use Polaris in Spring Cloud project in the simplest way. +CircuitBreaker's feature. Before starting MicroService, one needs to activate Polaris, activation details please refer to [Polaris](https://github.com/polarismesh). + +1. you can add ```spring-cloud-starter-tencent-polaris-circuitbreaker``` 's dependencies in your project to use CircuitBreaker features. For example, in Maven's project, add listed configurations in pom: + +```XML + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-circuitbreaker + +``` + +2. For further instructions, please refer to [Polaris CircuitBreaker Example](../../../../spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md). + +## Configuration List + +| Configuration Key | default | Must Fill | Configuration Instruction | +| ------------------------------------------- | ------- | --------- | ---------------------------- | +| spring.cloud.polaris.circuitbreaker.enabled | true | false | Whether turn on CircuitBreak | + diff --git a/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-discovery.md b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-discovery.md new file mode 100644 index 00000000..4f60a646 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-discovery.md @@ -0,0 +1,85 @@ +# Polaris Discovery + +## Module Intro + +```spring-cloud-starter-tencent-polaris-discovery``` is used in Spring Cloud project joint with [Polaris](https://github.com/polarismesh)'s Polaris Discovery component. You can complete Microservice registration at Polaris through dependencies, get visibility and control to the entire Cloud Service Engine. + +## Features Instruction + +### Service Registration and Discovery + +Spring Cloud API to achieve service registration and discovery. + +### Service Route + +Foundation built on Ribbon's API allows multiple application's dynamic service route, provided by Polaris's policy route feature. One can assign and control tasks through this feature. Through this feature, one can easily adapt different applications, SET routing, greyscale release, disaster recovery degrade, and canary test etc. + +Meanwhile, users can use independent discovery component's custom data feature to program routing, further improve its agility and performance. + +### CLoud Load Balance + +CLB supports qualified packet forwarding in the service instance. Through set balancing, send a selected instance to the caller service, to support caller's service request. CLB rule includes random weight policy, weight response time and coordinated Hash. + +## User Guide + +This chapter will explain how to use Polaris Discovery's features in the Spring Cloud project. Before starting MicroService, one needs to activate Polaris, activation details please refer to [Polaris](https://github.com/polarismesh). + +1. you can add ```spring-cloud-starter-tencent-polaris-discovery```'s 's dependencies in your project to use Polaris's service registration and discovery feature. For example, in Maven's project, add listed configurations in pom: + +```XML + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + +``` + +2. Add listed configurations in the document, one can complete service registration and discovery (after Spring Cloud Edgware, ```@EnableDiscoveryClient``` is no longer needed to run service registration and discovery): + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + server-addr: ${ip}:${port} +``` + +For further instructions, please refer to [Polaris Discovery Example](../../../../spring-cloud-tencent-examples/polaris-discovery-example/README.md). + +## Extended Application + +### Service Route + +- you can configure routing policy at Polaris control panel, and use the features. +- you can add custom metadata at configure documentations (application.yml), Then configure routing policy at Polaris control panel, one can also use the features. Example listed below, this will be read as Metadata Map. + +``` +spring: + cloud: + tencent: + content: + a: 1 + b: 2 +``` + +### Cloud Load Balancing + +Taking examples like random weight policy, you can add weight at Polaris control panel or configure documentations (application.yml) to access CLB features. + +## Configuration LIst + +| ConfigurationKey | default | Yes/No required | Configuration Instruction | +| ----------------------------------------------- | -------------------------- | --------------- | ---------------------------------------------------- | +| spring.cloud.polaris.server-addr | false | yes | Polaris backend address | +| spring.cloud.polaris.discovery.service | ${spring.application.name} | null | service name | +| spring.cloud.polaris.discovery.enabled | true | false | whether to active service registration and discovery | +| spring.cloud.polaris.discovery.instance-enabled | true | false | can current Microservice be visited | +| spring.cloud.polaris.discovery.token | false | false | Authentication Token | +| spring.cloud.polaris.discovery.version | null | false | Microservice Version | +| spring.cloud.polaris.protocol | null | false | Microservice agreement type | +| spring.cloud.polaris.weight | 100 | false | Microservice weight | +| spring.cloud.loadbalancer.polaris.enabled | true | false | whether to open CLB | +| spring.cloud.loadbalancer.polaris.strategy | weighted_random | false | CLB policy | +| spring.cloud.tencent.metadata.content | null | false | custom metadata Map | +| spring.cloud.tencent.metadata.transitive | null | false | need custom metadata key list | + diff --git a/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-ratelimit.md b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-ratelimit.md new file mode 100644 index 00000000..a4e540e7 --- /dev/null +++ b/spring-cloud-tencent-docs/src/main/doc/spring-cloud-tencent-polaris-ratelimit.md @@ -0,0 +1,47 @@ +# Polaris RateLimit + +## Module Introduction + +```spring-cloud-starter-tencent-polaris-ratelimit``` is used in Spring +Cloud project joint with [Polaris](https://github.com/polarismesh)'s rate limit component. +You can access Microservice's rate limit through dependencies. Recommended using with ```spring-cloud-starter-tencent-polaris-discovery``` + +## Features Introduction + +### Service Rate Limit + +Provide rate limit feature to all HTTP server. + +Default introduce spring-cloud-starter-tencent-polaris-ratelimit dependencies can apply rate limit check to all HTTP server. + +### API Rate Limit + +Provide rate limit feature to all HTTP server depending on the path level + +Default introduce spring-cloud-starter-tencent-polaris-ratelimit dependencies can apply rate limit check to all HTTP server path. + +## User Guide + +This chapter will explain how to use Polaris RateLimit in Spring Cloud project with the easiest way. Before starting MicroService, one needs to activate Polaris, activation details please refer to [Polaris](https://github.com/polarismesh). + +1. you can add ```spring-cloud-starter-tencent-polaris-ratelimit```‘s dependencies in your project to use the rate limit feature. For example, in Maven's project, add listed: + +```XML + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + +``` + +2. Add rate limit configuration + +Polaris provides three different configuration methods, control panel operation, HTTP API upload and local files configuration, further information please refer to [Polaris service rate limit operation guide](https://github.com/polarismesh) + +For more details, please refer to [Polaris RateLimit Example](../../../../spring-cloud-tencent-examples/polaris-ratelimit-example/README.md) + +## Configuration list + +| Configuration Key | default | yes/no required | Configuration Instruction | +| -------------------------------------- | ------- | --------------- | ----------------------------- | +| spring.cloud.polaris.ratelimit.enabled | true | false | whether to turn on rate limit | + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md new file mode 100644 index 00000000..0aab2ad0 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README-zh.md @@ -0,0 +1,78 @@ +# Spring Cloud Polaris CircuitBreaker Example + +## 样例简介 + +本样例将介绍如何在Spring Cloud项目中使用```spring-cloud-starter-tencent-polaris-circuitbreaker```以使用其各项功能。 + +该样例分为两个微服务,即polaris-circuitbreaker-example-a和polaris-circuitbreaker-example-b。其中,polaris-circuitbreaker-example-a对polaris-circuitbreaker-example-b发生调用。 + +## 使用说明 + +### 修改配置 + +在两个微服务的```src/main/resources```下的```bootstrap.yml```文件中添加如下配置。其中,${ip}和${port}为Polaris后端服务的IP地址与端口号。 + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + server-addr: ${ip}:${port} +``` + +### 启动样例 + +#### 启动Polaris后端服务 + +参考[Polaris](https://github.com/polarismesh)。 + +#### 启动应用 + +注意,由于需要验证熔断功能,因此需要部署两个及以上的被调服务(样例中部署两个即可)。 +- IDEA启动 + +分别启动```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a```下的```ServiceA```和```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b```下的```ServiceB```。 + +注意,ServiceB需要启动两个。同机器上可以修改端口号来实现。 + +两个ServiceB的com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info的逻辑需不同,即一个正常返回一个抛出异常。 + +- Maven打包启动 + +在```spring-cloud-tencent-examples/polaris-discovery-example```下执行 + +注意,ServiceB需要启动两个。同机器上可以修改端口号来实现。 + +两个ServiceB的com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info的逻辑需不同,即一个正常返回一个抛出异常。 + +```sh +mvn clean package +``` + +然后在```polaris-circuitbreaker-example-a```和```polaris-circuitbreaker-example-b```下找到生成的jar包,运行 + +``` +java -jar ${app.jar} +``` + +启动应用,其中${app.jar}替换为对应的jar包名。 + +### 验证 + +#### Feign调用 + +执行以下命令发起Feign调用,其逻辑为```ServiceB```抛出一个异常 + +```shell +curl -L -X GET 'localhost:48080/example/service/a/getBServiceInfo' +``` + +预期返回情况: + +在出现 +``` +trigger the refuse for service b +``` +时,表示请求到有异常的ServiceB,需要熔断这个实例。后面的所有请求都会得到正常返回。 + diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md new file mode 100644 index 00000000..67221f3f --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/README.md @@ -0,0 +1,79 @@ +# Spring Cloud Polaris CircuitBreaker Example + +## Example Introduction + +The examples will explain how to use```spring-cloud-starter-tencent-polaris-circuitbreaker``` in Spring Cloud project and other features + +This example is divided to two microservice, ```polaris-circuitbreaker-example-a``` and ```polaris-circuitbreaker-example-b```. In these two microservices, ```polaris-circuitbreaker-example-a``` invokes ```polaris-circuitbreaker-example-b```. + +## Instruction + +### Configuration + +```src/main/resources``` and ```bootstrap.yml``` of two micro-services add the following instructions. ${ip} and ${port} are Polaris backend IP address and port number. + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + server-addr: ${ip}:${port} +``` + +###Launching Example + +###Launching Polaris Backend Service + +Reference to [Polaris](https://github.com/polarismesh) + +####Launching Application + +Note, because verification is needed for circuit-break feature, therefore, one needs to deploy more than two invoked services (two deployment in this example) + + +Launching```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a```'s ServiceA and ```spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b```'s ServiceB + +note, Service B needs to launch two. One can adjust the port on the same machine. + +Two Services B's ```com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info``` logics are different. One returns normally, one is abnormal. + +- Maven Package Launching + +Execute under ``spring-cloud-tencent-examples/polaris-discovery-example``` + +note, Service B needs to launch two. One can adjust the port on the same machine. + +Two Services B's com.tencent.cloud.polaris.circuitbreaker.example.ServiceBController.info logics are different. One returns normally, one is abnormal. + +```sh +mvn clean package +``` + +Then under ``polaris-circuitbreaker-example-a``` and ``polaris-circuitbreaker-example-b``` find the package that generated jar, and run it + +``` +java -jar ${app.jar} +``` + +Launch application, change ${app.jar} to jar's package name + +##Verify + +####Feign Invoke + +Execute the following orders to invoke Feign, the logic is ```ServiceB``` has an abnormal signal + +```shell +curl -L -X GET 'localhost:48080/example/service/a/getBServiceInfo' +``` + +Expected return condition: + +when appear + +``` +trigger the refuse for service b +``` + +it means the request signals abnormal ServiceB, and will ciruitbreak this instance, the later requests will return normally. \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml new file mode 100644 index 00000000..5bf16820 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/pom.xml @@ -0,0 +1,118 @@ + + + + polaris-circuitbreaker-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-circuitbreaker-example-a + + + 1.8 + + + + + ch.qos.logback + logback-core + 1.2.3 + compile + + + org.springframework.boot + spring-boot-starter-web + + + + spring-cloud-starter-tencent-polaris-discovery + com.tencent.cloud + + + + spring-cloud-starter-tencent-polaris-circuitbreaker + com.tencent.cloud + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.github.openfeign + feign-okhttp + + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + org.springframework.retry + spring-retry + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.0 + + + attach-sources + + jar + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java new file mode 100644 index 00000000..bab2c2a7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderB.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * service b provider. + * + * @author Haotian Zhang + */ +@FeignClient(name = "polaris-circuitbreaker-example-b", fallback = ProviderBFallback.class) +public interface ProviderB { + + /** + * 获取B的服务的信息 + * + * @return + */ + @GetMapping("/example/service/b/info") + String info(); + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java new file mode 100644 index 00000000..f972e567 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderBFallback.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.stereotype.Component; + +/** + * provider b fallback + * + * @author Haotian Zhang + */ +@Component +public class ProviderBFallback implements ProviderB { + + @Override + public String info() { + return "trigger the refuse for service b"; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java new file mode 100644 index 00000000..d3b15803 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceA.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * circuitbraker example. + * + * @author Haotian Zhang + */ +@SpringBootApplication +@EnableDiscoveryClient +@EnableFeignClients +public class ServiceA { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(ServiceA.class); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java new file mode 100644 index 00000000..aadbfaac --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceAController.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +/** + * Service A Controller + * + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/example/service/a") +public class ServiceAController { + + private final ProviderB polarisServiceB; + + private final RestTemplate restTemplate; + + public ServiceAController(ProviderB polarisServiceB, RestTemplate restTemplate) { + this.polarisServiceB = polarisServiceB; + this.restTemplate = restTemplate; + } + + /** + * 获取当前服务的信息 + * + * @return 返回服务信息 + * @throws Exception + */ + @GetMapping("/info") + public String info() throws Exception { + return "hello world ! I'am a service"; + } + + /** + * 获取B服务的信息 + * + * @return 返回B服务的信息 + * @throws Exception + */ + @GetMapping("/getBServiceInfo") + public String getBServiceInfo() throws Exception { + return polarisServiceB.info(); + } + + @RequestMapping(value = "/testRest", method = RequestMethod.GET) + public String testRest() { + ResponseEntity entity = restTemplate.getForEntity("http://polaris-circuitbreaker-example-b/example/service/b/info", String.class); + return entity.getBody(); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..32f2eb16 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/bootstrap.yml @@ -0,0 +1,40 @@ +server: + session-timeout: 1800 + port: 48080 +spring: + application: + name: polaris-circuitbreaker-example-a + cloud: + polaris: + address: grpc://127.0.0.1:8091 +global: + api: + timeout: 5s + +feign: + hystrix: + enabled: true #在Feign中开启Hystrix + compression: + request: + enabled: false #是否对请求进行GZIP压缩 + mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型 + min-request-size: 2048 #超过该大小的请求会被压缩 + response: + enabled: false #是否对响应进行GZIP压缩 + +ribbon: + polaris: + enabled: true + # 同一实例最大重试次数,不包括首次调用 + MaxAutoRetries: 1 + # 重试其他实例的最大重试次数,不包括首次所选的server + MaxAutoRetriesNextServer: 2 + # 是否所有操作都进行重试 + OkToRetryOnAllOperations: false + ConnectionTimeout: 1000 + ReadTimeout: 1000 + eager-load: + enabled: on + +serivceB: + url: http://localhost:48081 \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/conf/polaris.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/conf/polaris.yml new file mode 100644 index 00000000..39d33bd6 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-a/src/main/resources/conf/polaris.yml @@ -0,0 +1,287 @@ +#描述:全局配置项 +global: + #描述系统相关配置 + system: + #描述:SDK运行模式 + #类型:enum + #范围:0(直连模式,SDK直接对接server); 1(代理模式,SDK只对接agent, 通过agent进行server的对接) + #默认值:0 + mode: 0 + #服务发现集群 + discoverCluster: + namespace: Polaris + service: polaris.discover + #可选:服务刷新间隔 + refreshInterval: 10m + #健康检查集群 + healthCheckCluster: + namespace: Polaris + service: polaris.healthcheck + #可选:服务刷新间隔 + refreshInterval: 10m + #监控上报集群 + monitorCluster: + namespace: Polaris + service: polaris.monitor + #可选:服务刷新间隔 + refreshInterval: 10m + api: + #描述:api超时时间 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:1s + timeout: 5s + #描述:上报间隔 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:10m + reportInterval: 10m + #描述:API因为网络原因调用失败后的重试次数 + #类型:int + #范围:[0:...] + #默认值:5 + maxRetryTimes: 5 + #描述:重试间隔 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1s:...] + #默认值:1s + retryInterval: 1s + #描述:对接polaris server的相关配置 + serverConnector: + #描述:访问server的连接协议,SDK会根据协议名称会加载对应的插件 + #类型:string + #范围:已注册的连接器插件名 + #默认值:grpc + protocol: grpc + #描述:发起连接后的连接超时时间 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:200ms + connectTimeout: 500ms + #描述:远程请求超时时间 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:1s + messageTimeout: 5s + #描述:连接空闲时间,长连接模式下,当连接空闲超过一定时间后,SDK会主动释放连接 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:1s + connectionIdleTimeout: 1s + #描述:首次请求的任务队列长度,当用户发起首次服务访问请求时,SDK会对任务进行队列调度并连接server,当积压的任务数超过队列长度后,SDK会直接拒绝首次请求的发起。 + #类型:int + #范围:[0:...] + #默认值:1000 + requestQueueSize: 1000 + #描述:server节点的切换周期,为了使得server的压力能够均衡,SDK会定期针对最新的节点列表进行重新计算自己当前应该连接的节点,假如和当前不一致,则进行切换 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1m:...] + #默认值:10m + serverSwitchInterval: 10m + plugin: + grpc: + #描述:GRPC客户端单次最大链路接收报文 + #类型:int + #范围:(0:524288000] + maxCallRecvMsgSize: 52428800 + #统计上报设置 + statReporter: + #描述:是否将统计信息上报至monitor + #类型:bool + #默认值:true + enable: true + #描述:启用的统计上报插件类型 + #类型:list + #范围:已经注册的统计上报插件的名字 + #默认值:stat2Monitor(将信息上报至monitor服务) + chain: + - stat2Monitor + - serviceCache + #描述:统计上报插件配置 + plugin: + stat2Monitor: + #描述:每次上报多长一段时间的统计信息 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1m:...] + metricsReportWindow: 1m + #描述:将一段时间内的统计信息分为多少个基本单元收集 + #类型:int + #范围:[1:...] + #默认值:12 + metricsNumBuckets: 12 + serviceCache: + #描述:上报缓存信息的周期 + reportInterval: 3m +#描述:主调端配置 +consumer: + #描述:本地缓存相关配置 + localCache: + #描述:缓存类型 + #类型:string + #范围:已注册的本地缓存插件名 + #默认值:inmemory(基于本机内存的缓存策略) + type: inmemory + #描述:服务过期淘汰时间 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1m:...] + #默认值:24h + serviceExpireTime: 24h + #描述:服务定期刷新周期 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1s:...] + #默认值:2s + serviceRefreshInterval: 2s + #描述:服务缓存持久化目录,SDK在实例数据更新后,按照服务维度将数据持久化到磁盘 + #类型:string + #格式:本机磁盘目录路径,支持$HOME变量 + #默认值:$HOME/polaris/backup + persistDir: ./polaris/backup + #描述:缓存写盘失败的最大重试次数 + #类型:int + #范围:[1:...] + #默认值:5 + persistMaxWriteRetry: 5 + #描述:缓存从磁盘读取失败的最大重试次数 + #类型:int + #范围:[1:...] + #默认值:1 + persistMaxReadRetry: 1 + #描述:缓存读写磁盘的重试间隔 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1ms:...] + #默认值:1s + persistRetryInterval: 1s + #描述:服务路由相关配置 + serviceRouter: + # 服务路由链 + chain: + # 基于主调和被调服务规则的路由策略(默认的路由策略) + - ruleBasedRouter + # 就近路由策略 + - nearbyBasedRouter + #描述:服务路由插件的配置 + plugin: + nearbyBasedRouter: + #描述:就近路由的最小匹配级别 + #类型:string + #范围:region(大区)、zone(区域)、campus(园区) + #默认值:zone + matchLevel: zone + ruleBasedRouter: { } + recoverRouter: + #至少应该返回多少比率的实例,如果不填,默认0%,即全死全活 + percentOfMinInstances: 0 + #是否开启全死全活,默认开启 + enableRecoverAll: true + #描述:负载均衡相关配置 + loadbalancer: + #描述:负载均衡类型 + #范围:已注册的负载均衡插件名 + #默认值:权重随机负载均衡 + type: weightedRandom + plugin: + #描述:虚拟节点的数量 + #类型:int + #默认值:500 + ringHash: + vnodeCount: 500 + + #描述:节点熔断相关配置 + circuitBreaker: + #描述:是否启用节点熔断功能 + #类型:bool + #默认值:true + enable: true + #描述:实例定时熔断检测周期 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[100ms:...] + #默认值:30s + checkPeriod: 100ms + #描述:熔断器半开后最大允许的请求数 + #类型:int + #范围:[3:...] + #默认值:10 + requestCountAfterHalfOpen: 10 + #描述:熔断器打开后,多久后转换为半开状态 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1s:...] + #默认值:30s + sleepWindow: 60s + #描述:熔断器半开到关闭所必须的最少成功请求数 + #类型:int + #范围:[1:requestCountAfterHalfOpen] + #默认值:8 + successCountAfterHalfOpen: 8 + #描述:熔断器半开到关闭的统计周期 + #类型:string + #范围:[10s:...] + #默认值:60s + recoverWindow: 60s + #描述:熔断器半开到关闭的统计滑桶数 + #类型:int + #范围:[1:...] + #默认值:10 + recoverNumBuckets: 10 + #描述:熔断策略,SDK会根据策略名称加载对应的熔断器插件 + #类型:list + #范围:已注册的熔断器插件名 + #默认值:基于周期连续错误数熔断(errorCount)、以及基于周期错误率的熔断策略(errorRate) + chain: + - errorCount + - errorRate + #描述:熔断插件配置 + plugin: + #描述:基于周期连续错误数熔断策略配置 + errorCount: + #描述:触发连续错误熔断的阈值 + #类型:int + #范围:[1:...] + #默认值:10 + continuousErrorThreshold: 1 + #描述:连续错误数的最小统计单元数量 + #类型:int + #范围:[1:...] + #默认值:10 + metricNumBuckets: 1 + #描述:连续失败的统计周期 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[10ms:...] + #默认值:1m + metricStatTimeWindow: 100ms + #描述:基于周期错误率的熔断策略配置 + errorRate: + #描述:触发错误率熔断的阈值 + #类型:double + #范围:(0:1] + #默认值:0.5 + errorRateThreshold: 0.01 + #描述:错误率熔断的最小统计单元数量 + #类型:int + #范围:[1:...] + #默认值:5 + metricNumBuckets: 5 + #描述:错误率熔断的统计周期 + #类型:string + #格式:^\d+(ms|s|m|h)$ + #范围:[1s:...] + #默认值:1m + metricStatTimeWindow: 1s + #描述:触发错误率熔断的最低请求阈值 + #类型:int + #范围:(0:...] + #默认值:10 + requestVolumeThreshold: 1 diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml new file mode 100644 index 00000000..2c56070e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/pom.xml @@ -0,0 +1,116 @@ + + + + polaris-circuitbreaker-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-circuitbreaker-example-b + + + 1.8 + + + + + ch.qos.logback + logback-core + 1.2.3 + compile + + + org.springframework.boot + spring-boot-starter-webflux + + + + spring-cloud-starter-tencent-polaris-discovery + com.tencent.cloud + + + spring-cloud-starter-tencent-polaris-circuitbreaker + com.tencent.cloud + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + io.github.openfeign + feign-okhttp + + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + org.springframework.retry + spring-retry + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.0 + + + attach-sources + + jar + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderA.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderA.java new file mode 100644 index 00000000..df1fca9b --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderA.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * service a provider. + * + * @author Haotian Zhang + */ +@FeignClient(name = "polaris-circuitbreaker-example-a", fallback = ProviderAFallback.class) +public interface ProviderA { + + /** + * 获取B的服务的信息 + * + * @return + */ + @GetMapping("/example/service/a/info") + String info(); + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderAFallback.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderAFallback.java new file mode 100644 index 00000000..cfbe437e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ProviderAFallback.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.stereotype.Component; + +/** + * provider a fallback + * + * @author Haotian Zhang + */ +@Component +public class ProviderAFallback implements ProviderA { + + @Override + public String info() { + return "trigger the refuse for service a"; + } +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java new file mode 100644 index 00000000..8da0f9de --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceB.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * circuitbraker example. + * + * @author Haotian Zhang + */ +@SpringBootApplication +@EnableDiscoveryClient +@EnableFeignClients +public class ServiceB { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(ServiceB.class); + } + +} \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java new file mode 100644 index 00000000..df3c6af9 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/java/com/tencent/cloud/polaris/circuitbreaker/example/ServiceBController.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.example; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +/** + * Service B Controller + * + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/example/service/b") +public class ServiceBController { + + private final ProviderA polarisServiceA; + + private final RestTemplate restTemplate; + + public ServiceBController(ProviderA polarisServiceA, RestTemplate restTemplate) { + this.polarisServiceA = polarisServiceA; + this.restTemplate = restTemplate; + } + + /** + * 获取当前服务的信息 + * + * @return 返回服务信息 + * @throws Exception + */ + @GetMapping("/info") + public String info() throws Exception { +// return "hello world ! I'am a service"; + throw new RuntimeException("failed for call my service"); + } + + /** + * 获取B服务的信息 + * + * @return 返回B服务的信息 + * @throws Exception + */ + @GetMapping("/getAServiceInfo") + public String getAServiceInfo() throws Exception { + return polarisServiceA.info(); + } + + @RequestMapping(value = "/testRest", method = RequestMethod.GET) + public String testRest() { + ResponseEntity entity = restTemplate.getForEntity("http://polaris-circuitbreaker-example-a/example/service/b/info", String.class); + return entity.getBody(); + } + + +} diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..eedf3e1e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/polaris-circuitbreaker-example-b/src/main/resources/bootstrap.yml @@ -0,0 +1,40 @@ +server: + session-timeout: 1800 + port: 48081 +spring: + application: + name: polaris-circuitbreaker-example-b + cloud: + polaris: + address: grpc://127.0.0.1:8091 +global: + api: + timeout: 5s + +feign: + hystrix: + enabled: true #在Feign中开启Hystrix + compression: + request: + enabled: false #是否对请求进行GZIP压缩 + mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型 + min-request-size: 2048 #超过该大小的请求会被压缩 + response: + enabled: false #是否对响应进行GZIP压缩 + +ribbon: + polaris: + enabled: true + # 同一实例最大重试次数,不包括首次调用 + MaxAutoRetries: 1 + # 重试其他实例的最大重试次数,不包括首次所选的server + MaxAutoRetriesNextServer: 2 + # 是否所有操作都进行重试 + OkToRetryOnAllOperations: false + ConnectionTimeout: 1000 + ReadTimeout: 1000 + eager-load: + enabled: on + +serivceB: + url: http://localhost:48081 \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml new file mode 100644 index 00000000..d337185e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-circuitbreaker-example/pom.xml @@ -0,0 +1,64 @@ + + + + spring-cloud-tencent-examples + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-circuitbreaker-example + pom + + + polaris-circuitbreaker-example-a + polaris-circuitbreaker-example-b + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + org.springframework.retry + spring-retry + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md b/spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md new file mode 100644 index 00000000..0c9af0f0 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/README-zh.md @@ -0,0 +1,81 @@ +# Polaris Discovery example + +## 样例简介 + +本样例将介绍如何在Spring Cloud项目中使用```spring-cloud-starter-tencent-polaris-discovery```以使用其各项功能。 + +该样例分为两个微服务,即discovery-caller-service和discovery-callee-service。其中,discovery-caller-service对discovery-callee-service发生调用。 + +## 使用说明 + +### 修改配置 + +在两个微服务的```src/main/resources```下的```bootstrap.yml```文件中添加如下配置。其中,${ip}和${port}为Polaris后端服务的IP地址与端口号。 + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + server-addr: ${ip}:${port} +``` + +### 启动样例 + +#### 启动Polaris后端服务 + +参考[Polaris](https://github.com/polarismesh)。 + +#### 启动应用 + +- IDEA启动 + +分别启动```spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service```下的```DiscoveryCallerService```和```spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service```下的```DiscoveryCalleeService```。 + +- Maven打包启动 + +在```spring-cloud-tencent-examples/polaris-discovery-example```下执行 + +```sh +mvn clean package +``` + +然后在```discovery-caller-service```和```discovery-callee-service```下找到生成的jar包,运行 + +``` +java -jar ${app.jar} +``` + +启动应用,其中${app.jar}替换为对应的jar包名。 + +### 验证 + +#### Feign调用 + +执行以下命令发起Feign调用,其逻辑为```DiscoveryCalleeService```返回value1+value2的和 + +```shell +curl -L -X GET 'http://localhost:48080/discovery/service/caller/feign?value1=1&value2=2' +``` + +预期返回值 + +``` +3 +``` + +#### RestTemplate调用 + +执行以下命令发起RestTemplate调用,其逻辑为```DiscoveryCalleeService```返回一段字符串 + +```shell +curl -L -X GET 'http://localhost:48080/discovery/service/caller/rest' +``` + +预期返回值 + +``` +Discovery Service Callee +``` + diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/README.md b/spring-cloud-tencent-examples/polaris-discovery-example/README.md new file mode 100644 index 00000000..f72e870d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/README.md @@ -0,0 +1,81 @@ +# Polaris Discovery example + +## Example Introduction + +The examples will explain how to use ```spring-cloud-starter-tencent-polaris-discovery`` in Spring Cloud project for its features. + +This example is divided to two microservice, discovery-caller-service and discovery-callee-service. In these two microservices, discovery-caller-service invokes discovery-callee-service. + +## Instruction + +### Configuration + +```src/main/resources``` and ```bootstrap.yml``` of two micro-services add the following instructions. ${ip} and ${port} are Polaris backend IP address and port number. + +```yaml +spring: + application: + name: ${application.name} + cloud: + polaris: + server-addr: ${ip}:${port} +``` + +### Launching Example + +#### Launching Polaris Backend Service + +Reference to [Polaris](https://github.com/polarismesh) + +#### Launching Application + +- IDEA Launching + +Launching ```spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service```'s ``DiscoveryCallerService``` and ``spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service```'s ```DiscoveryCalleeService`` + +- Maven Package Launching + +Execute under ```spring-cloud-tencent-examples/polaris-discovery-example``` + +```sh +mvn clean package +``` + +Then at ```discovery-caller-service``` and ```discovery-callee-service``` find the package that generates jar, and run it + +``` +java -jar ${app.jar} +``` + +Launch application, change ${app.jar} to jar's package name + +### Verify + +#### Feign Invoke + +Execute the following orders to invoke Feign, ```DiscoveryCalleeService``` goes bank to the sum of value1+value2 + +```shell +curl -L -X GET 'http://localhost:48080/discovery/service/caller/feign?value1=1&value2=2' +``` + +Expected return rate + +``` +3 +``` + +#### RestTemplate Invoke + +Execute the following orders to invoke RestTemplate, ```DiscoveryCalleeService``` goes back to string characters + +```shell +curl -L -X GET 'http://localhost:48080/discovery/service/caller/rest' +``` + +Expected return rate + +``` +Discovery Service Callee +``` + diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml new file mode 100644 index 00000000..5ba4d554 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/pom.xml @@ -0,0 +1,51 @@ + + + + polaris-discovery-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + discovery-callee-service + Polaris Discovery Callee Service + + + + spring-cloud-starter-tencent-polaris-discovery + com.tencent.cloud + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.0 + + + attach-sources + + jar + + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeController.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeController.java new file mode 100644 index 00000000..0ca911b7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeController.java @@ -0,0 +1,54 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.callee; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/discovery/service/callee") +public class DiscoveryCalleeController { + + /** + * 获取当前服务的信息 + * + * @return 返回服务信息 + */ + @GetMapping("/info") + public String info() { + return "Discovery Service Callee"; + } + + /** + * 获取相加完的结果 + * + * @param value1 值1 + * @param value2 值2 + * @return 总值 + */ + @GetMapping("/sum") + public int sum(@RequestParam int value1, @RequestParam int value2) { + return value1 + value2; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeService.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeService.java new file mode 100644 index 00000000..bfe92d37 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/java/com/tencent/cloud/polaris/discovery/service/callee/DiscoveryCalleeService.java @@ -0,0 +1,32 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.callee; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Haotian Zhang + */ +@SpringBootApplication +public class DiscoveryCalleeService { + + public static void main(String[] args) { + SpringApplication.run(DiscoveryCalleeService.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..25cec4c3 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml @@ -0,0 +1,9 @@ +server: + session-timeout: 1800 + port: 48081 +spring: + application: + name: DiscoveryCalleeService + cloud: + polaris: + address: grpc://127.0.0.1:8091 \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/log4j.properties b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/log4j.properties new file mode 100644 index 00000000..9e520cb7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/log4j.properties @@ -0,0 +1,15 @@ +log4j.rootLogger=DEBUG,console,FILE + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.threshold=INFO +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n + +log4j.appender.FILE=org.apache.log4j.RollingFileAppender +log4j.appender.FILE.Append=true + +log4j.appender.FILE.File=applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log +log4j.appender.FILE.Threshold=INFO +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n +log4j.appender.FILE.MaxFileSize=10MB \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/logback-spring.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..300805d1 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/logback-spring.xml @@ -0,0 +1,30 @@ + + + logback + + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + true + + + applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log + + + + + %d{yyyy-MM-dd HH:mm:ss} -%msg%n + + + + + + + + + diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml new file mode 100644 index 00000000..e341830d --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/pom.xml @@ -0,0 +1,70 @@ + + + + polaris-discovery-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + discovery-caller-service + Polaris Discovery Caller Service + + + 1.8 + + + + + spring-cloud-starter-tencent-polaris-discovery + com.tencent.cloud + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + io.github.openfeign + feign-okhttp + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.0 + + + attach-sources + + jar + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeService.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeService.java new file mode 100644 index 00000000..448ae9d2 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeService.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.caller; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * @author Haotian Zhang + */ +@FeignClient("DiscoveryCalleeService") +public interface DiscoveryCalleeService { + + /** + * 求和计算 + * + * @param value1 值1 + * @param value2 值2 + * @return 总值 + */ + @GetMapping("/discovery/service/callee/sum") + int sum(@RequestParam("value1") final int value1, @RequestParam("value2") final int value2); +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeServiceCallback.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeServiceCallback.java new file mode 100644 index 00000000..c8e52a66 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCalleeServiceCallback.java @@ -0,0 +1,32 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.caller; + +import org.springframework.stereotype.Component; + +/** + * @author Haotian Zhang + */ +@Component +public class DiscoveryCalleeServiceCallback implements DiscoveryCalleeService { + + @Override + public int sum(int value1, int value2) { + return 0; + } +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerController.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerController.java new file mode 100644 index 00000000..af338861 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerController.java @@ -0,0 +1,62 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.caller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +/** + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/discovery/service/caller") +public class DiscoveryCallerController { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private DiscoveryCalleeService discoveryCalleeService; + + /** + * 获取相加完的结果 + * + * @param value1 值1 + * @param value2 值2 + * @return 总值 + */ + @GetMapping("/feign") + public int feign(@RequestParam int value1, @RequestParam int value2) { + return discoveryCalleeService.sum(value1, value2); + } + + /** + * 获取被调服务信息 + * + * @return 信息 + */ + @GetMapping("/rest") + public String rest() { + return restTemplate.getForObject("http://DiscoveryCalleeService/discovery/service/callee/info", String.class); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerService.java b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerService.java new file mode 100644 index 00000000..16e9539f --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/java/com/tencent/cloud/polaris/discovery/service/caller/DiscoveryCallerService.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.service.caller; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * @author Haotian Zhang + */ +@SpringBootApplication +@EnableDiscoveryClient +@EnableFeignClients +public class DiscoveryCallerService { + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(DiscoveryCallerService.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..231b56bd --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/bootstrap.yml @@ -0,0 +1,9 @@ +server: + session-timeout: 1800 + port: 48080 +spring: + application: + name: DiscoveryCallerService + cloud: + polaris: + address: grpc://127.0.0.1:8091 \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/log4j.properties b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/log4j.properties new file mode 100644 index 00000000..9e520cb7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/log4j.properties @@ -0,0 +1,15 @@ +log4j.rootLogger=DEBUG,console,FILE + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.threshold=INFO +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n + +log4j.appender.FILE=org.apache.log4j.RollingFileAppender +log4j.appender.FILE.Append=true + +log4j.appender.FILE.File=applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log +log4j.appender.FILE.Threshold=INFO +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n +log4j.appender.FILE.MaxFileSize=10MB \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/logback-spring.xml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..57f025c4 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-caller-service/src/main/resources/logback-spring.xml @@ -0,0 +1,31 @@ + + + logback + + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + true + + + applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log + + + + + %d{yyyy-MM-dd HH:mm:ss} -%msg%n + + + + + + + + + + diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml new file mode 100644 index 00000000..f4f0add8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml @@ -0,0 +1,65 @@ + + + + spring-cloud-tencent-examples + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-discovery-example + pom + Spring Cloud Starter Tencent Polaris Discovery Example + + + discovery-callee-service + discovery-caller-service + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + + org.springframework.retry + spring-retry + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md b/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md new file mode 100644 index 00000000..49af0b80 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/README-zh.md @@ -0,0 +1,61 @@ +# Polaris RateLimit Example + +## 项目说明 + +本项目演示如何使用 Polaris ratelimit starter 完成 Spring Cloud 应用的限流管理。 + +[Polaris](https://github.com/polarismesh):北极星是腾讯开源的云原生的服务治理平台及组件,提供多维度的服务限流功能,防护应用系统的可靠性。 + +## 示例 + +### 如何接入 + +在启动示例进行演示之前,我们先了解一下如何接入 Polaris 限流组件。 + +> **注意:本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。** + +1. 首先,修改 `pom.xml` 文件,引入 Polaris ratelimit starter。 + ```xml + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + ``` + +2. 启动应用 + + 北极星提供的example都支持在IDE中直接运行,或者编译打包后通过命令行方式进行运行。 + - 在本地启动Polaris服务端。 + - 在北极星服务端,可以通过控制台,在命名空间Production下,添加服务RateLimitCalleeService。 + - 启动服务被调方: + 1. IDE直接启动:找到主类 `RateLimitCalleeService`,执行 main 方法启动应用。 + 2. 打包编译后启动:首先执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar ratelimit-callee-sevice-${verion}.jar`启动应用。 + - 启动后,可以在北极星控制台上看到注册上来的服务实例信息。 + +3. 调用服务 + + 通过浏览器访问http://127.0.0.1:48081/business/invoke,可以看到以下输出信息: + ```` + hello world for ratelimit service 1 + hello world for ratelimit service 2 + hello world for ratelimit service 3 + ... + ```` + +4. 配置限流规则并验证 + 北极星提供了三个方式进行限流规则的配置(控制台、HTTP接口以及本地文件)。 + + 本示例使用的方式为通过HTTP接口进行配置。通过以下命令来配置: + ```` + curl -X POST -H "Content-Type:application/json" 127.0.0.1:8090/naming/v1/ratelimits -d @rule.json + ```` + +5. 验证限流效果 + 继续访问http://127.0.0.1:48081/business/invoke,可以看到,10次调用后,就开始被限流: + ```` + hello world for ratelimit service 1 + hello world for ratelimit service 2 + ... + hello world for ratelimit service 10 + request has been limited, service is RateLimitCalleeService, path is /business/invoke, 11 + ```` \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md b/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md new file mode 100644 index 00000000..bebb9224 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/README.md @@ -0,0 +1,68 @@ +# Polaris RateLimit Example + +## Project Explanation + +This project shows how to use ratelimit feature of Polaris to complete Spring Cloud application's rate limit + +[Polaris](https://github.com/polarismesh): Polaris is Tencent open source cloud-native's operation centre and component, provide multi dimensional rate limit service and reliability of application firewall + +## Example + +### How to access + +Before showcasing the project, let's get to know how to access Polaris rate limit component + +> ** note: this chapter is to help you understand different ways to access, the codes in the example has been executed, you don't need to re-edit.** + +1, first, change document `pom.xml`, introduce Polaris ratelimit starter + + ```xml + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + ``` + +2. Launch Application + +Examples provided by Polaris all support to run at IDE, or compile and run with orders + +- Launch Polaris locally + +- at Polaris end, through control panel, under namespace Product, add RateLimitCalleeService + +- Launch callee server: + + 1. Launch IDE directly: First find `RateLimitCalleeService`, execute main then launch application + 2. compile package then launch: first execute `mvn clean package` compile the package, then execute `java -jar ratelimit-callee-sevice-${verion}.jar` execute the application + + - After launching, one can watch server instance from Polaris control panel + + 3. Invoke Service + + After visiting http://127.0.0.1:48081/business/invoke, one can see the following information: + + ```` + hello world for ratelimit service 1 + hello world for ratelimit service 2 + hello world for ratelimit service 3 + ... + ```` + +4. Configuration rate limit and verification + Polaris provide three wats to conduct rate limit configuration (control panel, HTTP port and local files) + +this example is HTTP configuration. One can figure with the following steps: + + ```` + curl -X POST -H "Content-Type:application/json" 127.0.0.1:8090/naming/v1/ratelimits -d @rule.json + ```` + +5. Verify rate limit result + continue visit http://127.0.0.1:48081/business/invoke, one can see, after 10 invokes, rate limit will start: + + ```` + hello world for ratelimit service 1 + hello world for ratelimit service 2 + + ```` \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml new file mode 100644 index 00000000..c2317809 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/pom.xml @@ -0,0 +1,20 @@ + + + + spring-cloud-tencent-examples + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-ratelimit-example + pom + Spring Cloud Starter Tencent Polaris RateLimit Example + + + ratelimit-callee-service + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml new file mode 100644 index 00000000..b37077e3 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml @@ -0,0 +1,34 @@ + + + + polaris-ratelimit-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + ratelimit-callee-service + + + + org.springframework.boot + spring-boot-starter-web + + + + spring-cloud-starter-tencent-polaris-discovery + com.tencent.cloud + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + + org.springframework.boot + spring-boot-actuator-autoconfigure + + + \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java new file mode 100644 index 00000000..76790744 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/BusinessController.java @@ -0,0 +1,76 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.ratelimit.example.service.callee; + +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException.TooManyRequests; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * @author Haotian Zhang + */ +@RestController +@RequestMapping("/business") +public class BusinessController { + + @Autowired + private RestTemplate restTemplate; + + private final AtomicInteger index = new AtomicInteger(0); + + @Value("${spring.application.name}") + private String appName; + + + /** + * 获取当前服务的信息 + * + * @return 返回服务信息 + */ + @GetMapping("/info") + public String info() { + return "hello world for ratelimit service " + index.incrementAndGet(); + } + + @GetMapping("/invoke") + public String invokeInfo() throws Exception { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 30; i++) { + try { + ResponseEntity entity = restTemplate + .getForEntity("http://" + appName + "/business/info", String.class); + builder.append(entity.getBody()).append("
"); + } catch (RestClientException e) { + if (e instanceof TooManyRequests) { + builder.append(((TooManyRequests) e).getResponseBodyAsString()).append(index.incrementAndGet()) + .append("
"); + } else { + throw e; + } + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java new file mode 100644 index 00000000..342d1280 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/java/com/tencent/cloud/ratelimit/example/service/callee/RateLimitCalleeService.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.ratelimit.example.service.callee; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * @author Haotian Zhang + */ +@SpringBootApplication +@EnableAutoConfiguration +public class RateLimitCalleeService { + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + public static void main(String[] args) { + SpringApplication.run(RateLimitCalleeService.class, args); + } +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..9974b810 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml @@ -0,0 +1,11 @@ +server: + session-timeout: 1800 + port: 48081 +spring: + application: + name: RateLimitCalleeService + cloud: + polaris: + address: grpc://127.0.0.1:8091 + discovery: + namespace: Test \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/log4j.properties b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/log4j.properties new file mode 100644 index 00000000..9e520cb7 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/log4j.properties @@ -0,0 +1,15 @@ +log4j.rootLogger=DEBUG,console,FILE + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.threshold=INFO +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n + +log4j.appender.FILE=org.apache.log4j.RollingFileAppender +log4j.appender.FILE.Append=true + +log4j.appender.FILE.File=applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log +log4j.appender.FILE.Threshold=INFO +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%5p] - %c -%F(%L) -%m%n +log4j.appender.FILE.MaxFileSize=10MB \ No newline at end of file diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/rule.json b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/rule.json new file mode 100644 index 00000000..9eabb50a --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/rule.json @@ -0,0 +1,19 @@ +{ + "service": "RateLimitCalleeService", + "namespace": "Production", + "priority": 0, + "resource": "QPS", + "type": "LOCAL", + "labels": { + "method": { + "value": "/business/invoke" + } + }, + "amounts": [ + { + "maxAmount": 10, + "validDuration": "1s" + } + ], + "action": "REJECT" +} \ No newline at end of file diff --git a/spring-cloud-tencent-examples/pom.xml b/spring-cloud-tencent-examples/pom.xml new file mode 100644 index 00000000..2bf49d9f --- /dev/null +++ b/spring-cloud-tencent-examples/pom.xml @@ -0,0 +1,24 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-examples + pom + Spring Cloud Tencent Examples + Examples of Spring Cloud Tencent + + + polaris-discovery-example + polaris-ratelimit-example + polaris-circuitbreaker-example + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/pom.xml b/spring-cloud-tencent-starters/pom.xml new file mode 100644 index 00000000..9ae3ce3d --- /dev/null +++ b/spring-cloud-tencent-starters/pom.xml @@ -0,0 +1,53 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-starters + pom + Spring Cloud Tencent Starters + Spring Cloud Tencent Starters + + + spring-cloud-tencent-polaris-context + spring-cloud-tencent-commons + spring-cloud-tencent-metadata + spring-cloud-tencent-feign + spring-cloud-starter-tencent-polaris-discovery + spring-cloud-starter-tencent-polaris-ratelimit + spring-cloud-starter-tencent-polaris-circuitbreaker + spring-cloud-starter-tencent-polaris-router + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + jacoco-initialize + + prepare-agent + + + + jacoco-site + test + + report + + + + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml new file mode 100644 index 00000000..6d03a419 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/pom.xml @@ -0,0 +1,96 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + spring-cloud-starter-tencent-polaris-circuitbreaker + Spring Cloud Starter Tencent Polaris Circuitbreaker + + + + + com.tencent.cloud + spring-cloud-tencent-metadata + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + + com.tencent.nameservice + polaris-discovery-factory + + + + com.tencent.nameservice + polaris-circuitbreaker-factory + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + io.github.openfeign + feign-core + true + + + + org.springframework.cloud + spring-cloud-openfeign-core + true + + + + org.springframework.cloud + spring-cloud-loadbalancer + true + + + + org.springframework.cloud + spring-cloud-commons + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.boot + spring-boot-starter-test + test + true + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + test + true + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java new file mode 100644 index 00000000..f5b67702 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker; + +import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; + +/** + * Configuration for Polaris {@link feign.Feign} which can automatically bring in the call results for reporting + * + * @author Haotian Zhang + */ +@ConditionalOnProperty( + value = "spring.cloud.polaris.circuitbreaker.enabled", + havingValue = "true", + matchIfMissing = true) +@Configuration(proxyBeanMethods = false) +@AutoConfigureAfter(PolarisContextConfiguration.class) +@AutoConfigureBefore(FeignAutoConfiguration.class) +public class PolarisFeignClientAutoConfiguration { + + @Bean + public ConsumerAPI consumerAPI(SDKContext context) { + return DiscoveryAPIFactory.createConsumerAPIByContext(context); + } + + @Bean + @Order(value = HIGHEST_PRECEDENCE) + public PolarisFeignBeanPostProcessor polarisFeignBeanPostProcessor(ConsumerAPI consumerAPI) { + return new PolarisFeignBeanPostProcessor(consumerAPI); + } + + @Bean + public CircuitBreakerConfigModifier circuitBreakerConfigModifier() { + return new CircuitBreakerConfigModifier(); + } + + public static class CircuitBreakerConfigModifier implements PolarisConfigModifier { + + @Override + public void modify(ConfigurationImpl configuration) { + //开启熔断配置 + configuration.getConsumer().getCircuitBreaker().setEnable(true); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBeanPostProcessor.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBeanPostProcessor.java new file mode 100644 index 00000000..339f3a58 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBeanPostProcessor.java @@ -0,0 +1,91 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import com.tencent.polaris.api.core.ConsumerAPI; +import feign.Client; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; +import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; +import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; + +/** + * Wrap Spring Bean and decorating proxy for Feign Client. + * + * @author Haotian Zhang + */ +public class PolarisFeignBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { + + private BeanFactory factory; + + private final ConsumerAPI consumerAPI; + + public PolarisFeignBeanPostProcessor(ConsumerAPI consumerAPI) { + this.consumerAPI = consumerAPI; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return wrapper(bean); + } + + private Object wrapper(Object bean) { + if (isNeedWrap(bean)) { + if (bean instanceof LoadBalancerFeignClient) { + LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); + return new PolarisLoadBalancerFeignClient(createPolarisFeignClient(client.getDelegate()), factory(), + clientFactory()); + } + if (bean instanceof FeignBlockingLoadBalancerClient) { + FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) bean; + return new PolarisFeignBlockingLoadBalancerClient(createPolarisFeignClient(client.getDelegate()), + factory.getBean(BlockingLoadBalancerClient.class)); + } + return createPolarisFeignClient((Client) bean); + } + return bean; + } + + private boolean isNeedWrap(Object bean) { + return bean instanceof Client && !(bean instanceof PolarisFeignClient) + && !(bean instanceof PolarisFeignBlockingLoadBalancerClient) + && !(bean instanceof PolarisLoadBalancerFeignClient); + } + + private PolarisFeignClient createPolarisFeignClient(Client delegate) { + return new PolarisFeignClient(delegate, consumerAPI); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.factory = beanFactory; + } + + CachingSpringLoadBalancerFactory factory() { + return this.factory.getBean(CachingSpringLoadBalancerFactory.class); + } + + SpringClientFactory clientFactory() { + return this.factory.getBean(SpringClientFactory.class); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBlockingLoadBalancerClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBlockingLoadBalancerClient.java new file mode 100644 index 00000000..076e269e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignBlockingLoadBalancerClient.java @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import feign.Client; +import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; +import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient; + +/** + * Wrap for {@link FeignBlockingLoadBalancerClient} + * + * @author Haotian Zhang + */ +public class PolarisFeignBlockingLoadBalancerClient extends FeignBlockingLoadBalancerClient { + + public PolarisFeignBlockingLoadBalancerClient(Client delegate, + BlockingLoadBalancerClient loadBalancerClient) { + super(delegate, loadBalancerClient); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClient.java new file mode 100644 index 00000000..85c61a93 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClient.java @@ -0,0 +1,94 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import static feign.Util.checkNotNull; + +import com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey; +import com.tencent.cloud.metadata.context.MetadataContext; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import feign.Client; +import feign.Request; +import feign.Request.Options; +import feign.Response; +import java.io.IOException; +import java.net.URI; +import org.apache.commons.lang.StringUtils; + +/** + * Wrap for {@link Client} + * + * @author Haotian Zhang + */ +public class PolarisFeignClient implements Client { + + private final Client delegate; + + private final ConsumerAPI consumerAPI; + + public PolarisFeignClient(Client target, ConsumerAPI consumerAPI) { + this.delegate = checkNotNull(target, "target"); + this.consumerAPI = checkNotNull(consumerAPI, "CircuitBreakAPI"); + } + + @Override + public Response execute(Request request, Options options) throws IOException { + final ServiceCallResult resultRequest = createServiceCallResult(request); + try { + Response response = delegate.execute(request, options); + //HTTP code greater than 400 is an exception + if (response.status() >= 400) { + resultRequest.setRetStatus(RetStatus.RetFail); + } + return response; + } catch (IOException origin) { + resultRequest.setRetStatus(RetStatus.RetFail); + throw origin; + } finally { + consumerAPI.updateServiceCallResult(resultRequest); + } + } + + private ServiceCallResult createServiceCallResult(final Request request) { + ServiceCallResult resultRequest = new ServiceCallResult(); + + MetadataContext metadataContext = MetadataContextHolder.get(); + String namespace = metadataContext.getSystemMetadata(SystemMetadataKey.PEER_NAMESPACE); + resultRequest.setNamespace(namespace); + String serviceName = metadataContext.getSystemMetadata(SystemMetadataKey.PEER_SERVICE); + resultRequest.setService(serviceName); + String method = metadataContext.getSystemMetadata(SystemMetadataKey.PEER_PATH); + resultRequest.setMethod(method); + resultRequest.setRetStatus(RetStatus.RetSuccess); + String sourceNamespace = metadataContext.getSystemMetadata(SystemMetadataKey.LOCAL_NAMESPACE); + String sourceService = metadataContext.getSystemMetadata(SystemMetadataKey.LOCAL_SERVICE); + if (StringUtils.isNotBlank(sourceNamespace) && StringUtils.isNotBlank(sourceService)) { + resultRequest.setCallerService(new ServiceKey(sourceNamespace, sourceService)); + } + + URI uri = URI.create(request.url()); + resultRequest.setHost(uri.getHost()); + resultRequest.setPort(uri.getPort()); + + return resultRequest; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisLoadBalancerFeignClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisLoadBalancerFeignClient.java new file mode 100644 index 00000000..420cf27e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisLoadBalancerFeignClient.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import feign.Client; +import org.springframework.cloud.netflix.ribbon.SpringClientFactory; +import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; +import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; + +/** + * Wrap for {@link LoadBalancerFeignClient} + * + * @author Haotian Zhang + */ +public class PolarisLoadBalancerFeignClient extends LoadBalancerFeignClient { + + public PolarisLoadBalancerFeignClient(Client delegate, + CachingSpringLoadBalancerFactory lbClientFactory, + SpringClientFactory clientFactory) { + super(delegate, lbClientFactory, clientFactory); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 00000000..3ab08a57 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "spring.cloud.polaris.circuitbreaker.enabled", + "type": "java.lang.Boolean", + "sourceType": "com.tencent.cloud.polaris.circuitbreaker.PolarisFeignProperties" + } + ], + "hints": [] +} \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..9ef0f8c3 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java new file mode 100644 index 00000000..a6a15d19 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java @@ -0,0 +1,62 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration; +import feign.Client; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author liaochuntao + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestPolarisFeignApp.class) +@ContextConfiguration(classes = {PolarisFeignClientAutoConfiguration.class}) +public class PolarisFeignClientTest { + + @Autowired + private ApplicationContext springCtx; + + @Test + public void testPolarisFeignBeanPostProcessor() { + final PolarisFeignBeanPostProcessor postProcessor = springCtx.getBean(PolarisFeignBeanPostProcessor.class); + Assertions.assertNotNull(postProcessor, "PolarisFeignBeanPostProcessor"); + } + + @Test + public void testFeignClient() { + final Client client = springCtx.getBean(Client.class); + if (client instanceof PolarisFeignClient) { + return; + } + if (client instanceof PolarisLoadBalancerFeignClient) { + return; + } + if (client instanceof PolarisFeignBlockingLoadBalancerClient) { + return; + } + throw new IllegalStateException("Polaris burying failed"); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/TestPolarisFeignApp.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/TestPolarisFeignApp.java new file mode 100644 index 00000000..1b451746 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/TestPolarisFeignApp.java @@ -0,0 +1,63 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.circuitbreaker.feign; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author liaochuntao + */ +@SpringBootApplication +@EnableFeignClients +public class TestPolarisFeignApp { + + @Autowired + TestPolarisService service; + + public static void main(String[] args) { + SpringApplication.run(TestPolarisFeignApp.class); + } + + @FeignClient(name = "feign-service-polaris", fallback = TestPolarisServiceFallback.class) + public interface TestPolarisService { + + /** + * 获取B的服务的信息 + * + * @return + */ + @GetMapping("/example/service/b/info") + String info(); + + } + + @Component + public static class TestPolarisServiceFallback implements TestPolarisService { + + @Override + public String info() { + return "trigger the refuse"; + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application.yml b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application.yml new file mode 100644 index 00000000..beaf23b7 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/resources/application.yml @@ -0,0 +1,15 @@ +spring: + cloud: + polaris: + address: grpc://127.0.0.1:8091 + +feign: + polaris: + enable: true + compression: + request: + enabled: false #是否对请求进行GZIP压缩 + mime-types: text/xml,application/xml,application/json #指定压缩的请求数据类型 + min-request-size: 2048 #超过该大小的请求会被压缩 + response: + enabled: false #是否对响应进行GZIP压缩 \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/pom.xml b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/pom.xml new file mode 100644 index 00000000..b24a9b00 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/pom.xml @@ -0,0 +1,135 @@ + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-polaris-discovery + Spring Cloud Starter Tencent Polaris Discovery + + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + com.tencent.cloud + spring-cloud-tencent-metadata + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + + + + + com.tencent.nameservice + polaris-discovery-factory + + + + com.tencent.nameservice + polaris-test-common + test + + + + com.tencent.nameservice + polaris-test-mock-discovery + test + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-webflux + true + + + + org.springframework + spring-context-support + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + true + + + + org.springframework.cloud + spring-cloud-config-client + true + + + + org.springframework.cloud + spring-cloud-config-server + true + + + + org.springframework.boot + spring-boot-starter-web + test + true + + + + org.springframework.boot + spring-boot-starter-test + test + true + + + + io.projectreactor + reactor-test + test + true + + + + org.powermock + powermock-module-junit4 + test + true + + + + org.powermock + powermock-api-mockito2 + test + true + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java new file mode 100644 index 00000000..3d6f2c42 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java @@ -0,0 +1,233 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris; + +import javax.annotation.PostConstruct; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.env.Environment; + +/** + * Properties for Polaris. + * + * @author Haotian Zhang + */ +@ConfigurationProperties("spring.cloud.polaris.discovery") +public class PolarisProperties { + + /** + * the polaris authentication token. + */ + private String token; + + /** + * namespace, separation registry of different environments. + */ + @Value("${spring.cloud.polaris.discovery.namespace:#{'Production'}}") + private String namespace; + + /** + * service name to registry. + */ + @Value("${spring.cloud.polaris.discovery.service:${spring.application.name:}}") + private String service; + + /** + * 权重 + */ + @Value("${spring.cloud.polaris.discovery.weight:#{100}}") + private float weight; + + /** + * 版本号 + */ + private String version; + + /** + * 协议名称 + */ + @Value("${spring.cloud.polaris.discovery.protocol:http}") + private String protocol; + + /** + * 使用spring cloud监听端口 + */ + @Value("${server.port:}") + private int port; + + /** + * 是否开启负载均衡 + */ + @Value("${spring.cloud.polaris.discovery.loadbalancer.enabled:#{true}}") + private Boolean loadbalancerEnabled; + + + /** + * loadbalnce strategy + */ + @Value("${spring.cloud.polaris.discovery.loadbalancer.policy:#{'weightedRandom'}}") + private String policy; + + + /** + * loadbalnce strategy + */ + @Value("${spring.cloud.polaris.discovery.register.enabled:#{true}}") + private Boolean registerEnabled; + + /** + * loadbalnce strategy + */ + @Value("${spring.cloud.polaris.discovery.heartbeat.enabled:#{false}}") + private Boolean heartbeatEnabled = true; + + @Autowired + private Environment environment; + + /** + * init properties + * + * @throws Exception + */ + @PostConstruct + public void init() throws Exception { + if (StringUtils.isEmpty(this.getNamespace())) { + this.setNamespace(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.namespace:}")); + } + if (StringUtils.isEmpty(this.getService())) { + this.setService(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.service:}")); + } + if (StringUtils.isEmpty(this.getToken())) { + this.setToken(environment.resolvePlaceholders("${spring.cloud.polaris.discovery.token:}")); + } + } + + public boolean isHeartbeatEnabled() { + if (null == heartbeatEnabled) { + return false; + } + return heartbeatEnabled; + } + + public void setHeartbeatEnabled(Boolean heartbeatEnabled) { + this.heartbeatEnabled = heartbeatEnabled; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + this.weight = weight; + } + + public String getService() { + return service; + } + + public void setService(String service) { + this.service = service; + } + + + public boolean isRegisterEnabled() { + return registerEnabled; + } + + public void setRegisterEnabled(boolean registerEnabled) { + this.registerEnabled = registerEnabled; + } + + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getPolicy() { + return policy; + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + public Boolean getLoadbalancerEnabled() { + return loadbalancerEnabled; + } + + public void setLoadbalancerEnabled(Boolean loadbalancerEnabled) { + this.loadbalancerEnabled = loadbalancerEnabled; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + @SuppressWarnings("checkstyle:all") + public String toString() { + return "PolarisProperties{" + + "token='" + token + '\'' + + ", namespace='" + namespace + '\'' + + ", service='" + service + '\'' + + ", weight=" + weight + + ", version='" + version + '\'' + + ", protocol='" + protocol + '\'' + + ", port=" + port + + ", loadbalancerEnabled=" + loadbalancerEnabled + + ", policy='" + policy + '\'' + + ", registerEnabled=" + registerEnabled + + ", heartbeatEnabled=" + heartbeatEnabled + + ", environment=" + environment + + '}'; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/client/PolarisClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/client/PolarisClient.java new file mode 100644 index 00000000..763fafeb --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/client/PolarisClient.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.client; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for Polaris Client for service configuration. + * + * @author Haotian Zhang + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface PolarisClient { + + /** + * service + * + * @return service + */ + String service() default ""; + + /** + * namespace + * + * @return namespace + */ + String namespace() default ""; + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/ConditionalOnPolarisDiscoveryEnabled.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/ConditionalOnPolarisDiscoveryEnabled.java new file mode 100644 index 00000000..84b612f8 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/ConditionalOnPolarisDiscoveryEnabled.java @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; + +/** + * @author Haotian Zhang + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ConditionalOnDiscoveryEnabled +@ConditionalOnProperty(value = "spring.cloud.polaris.discovery.enabled", matchIfMissing = true) +public @interface ConditionalOnPolarisDiscoveryEnabled { + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfiguration.java new file mode 100644 index 00000000..a46908b3 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfiguration.java @@ -0,0 +1,71 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.cloud.polaris.discovery.reactive.PolarisReactiveDiscoveryClientConfiguration; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.core.ProviderAPI; +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * Discovery Auto Configuration for Polaris. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnPolarisDiscoveryEnabled +@Import({PolarisDiscoveryClientConfiguration.class, PolarisReactiveDiscoveryClientConfiguration.class}) +public class PolarisDiscoveryAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public PolarisProperties polarisDiscoveryProperties() { + return new PolarisProperties(); + } + + @Bean(name = "polarisProvider") + @ConditionalOnMissingBean + public ProviderAPI polarisProvider(SDKContext polarisContext) throws PolarisException { + return DiscoveryAPIFactory.createProviderAPIByContext(polarisContext); + } + + @Bean(name = "polarisConsumer") + @ConditionalOnMissingBean + public ConsumerAPI polarisConsumer(SDKContext polarisContext) throws PolarisException { + return DiscoveryAPIFactory.createConsumerAPIByContext(polarisContext); + } + + @Bean + @ConditionalOnMissingBean + public PolarisDiscoveryHandler polarisDiscoveryHandler() { + return new PolarisDiscoveryHandler(); + } + + @Bean + @ConditionalOnMissingBean + public PolarisServiceDiscovery polarisServiceDiscovery(PolarisDiscoveryHandler polarisDiscoveryHandler) { + return new PolarisServiceDiscovery(polarisDiscoveryHandler); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClient.java new file mode 100644 index 00000000..aea0c115 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClient.java @@ -0,0 +1,61 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; + +/** + * Discovery Client for Polaris. + * + * @author Haotian Zhang + */ +public class PolarisDiscoveryClient implements DiscoveryClient { + + private static final Logger log = LoggerFactory.getLogger(PolarisDiscoveryClient.class); + + /** + * Polaris Discovery Client Description. + */ + public final String description = "Spring Cloud Polaris Discovery Client"; + + private final PolarisServiceDiscovery polarisServiceDiscovery; + + public PolarisDiscoveryClient(PolarisServiceDiscovery polarisServiceDiscovery) { + this.polarisServiceDiscovery = polarisServiceDiscovery; + } + + @Override + public String description() { + return description; + } + + @Override + public List getInstances(String service) { + return polarisServiceDiscovery.getInstances(service); + } + + @Override + public List getServices() { + return polarisServiceDiscovery.getServices(); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfiguration.java new file mode 100644 index 00000000..01ad501a --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfiguration.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.cloud.client.CommonsClientAutoConfiguration; +import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Discovery Client Configuration for Polaris. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBlockingDiscoveryEnabled +@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class, + CommonsClientAutoConfiguration.class}) +@AutoConfigureAfter(PolarisDiscoveryAutoConfiguration.class) +public class PolarisDiscoveryClientConfiguration { + + @Bean + public DiscoveryClient polarisDiscoveryClient(PolarisServiceDiscovery polarisServiceDiscovery) { + return new PolarisDiscoveryClient(polarisServiceDiscovery); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java new file mode 100644 index 00000000..878b3fd5 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryHandler.java @@ -0,0 +1,96 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.core.ProviderAPI; +import com.tencent.polaris.api.pojo.ServiceInfo; +import com.tencent.polaris.api.rpc.GetAllInstancesRequest; +import com.tencent.polaris.api.rpc.GetInstancesRequest; +import com.tencent.polaris.api.rpc.InstancesResponse; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Discovery Handler for Polaris. + * + * @author Haotian Zhang + */ +@Component +public class PolarisDiscoveryHandler { + + @Autowired + private PolarisProperties polarisProperties; + + @Autowired + private ProviderAPI providerAPI; + + @Autowired + private ConsumerAPI polarisConsumer; + + /** + * 获取服务路由后的实例列表 + * + * @param service 服务名 + * @return 服务实例列表 + */ + public InstancesResponse getFilteredInstances(String service) { + String namespace = polarisProperties.getNamespace(); + GetInstancesRequest getInstancesRequest = new GetInstancesRequest(); + getInstancesRequest.setNamespace(namespace); + getInstancesRequest.setService(service); + String method = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.PEER_PATH); + getInstancesRequest.setMethod(method); + String localNamespace = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_NAMESPACE); + String localService = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_SERVICE); + Map allTransitiveCustomMetadata = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); + if (StringUtils.isNotBlank(localNamespace) || StringUtils.isNotBlank(localService) + || null != allTransitiveCustomMetadata) { + ServiceInfo sourceService = new ServiceInfo(); + sourceService.setNamespace(localNamespace); + sourceService.setService(localService); + sourceService.setMetadata(allTransitiveCustomMetadata); + getInstancesRequest.setServiceInfo(sourceService); + } + return polarisConsumer.getInstances(getInstancesRequest); + } + + /** + * Return all instances for the given service. + * + * @param service serviceName + * @return 服务实例列表 + */ + public InstancesResponse getInstances(String service) { + String namespace = polarisProperties.getNamespace(); + GetAllInstancesRequest request = new GetAllInstancesRequest(); + request.setNamespace(namespace); + request.setService(service); + return polarisConsumer.getAllInstance(request); + } + + public ProviderAPI getProviderAPI() { + return providerAPI; + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscovery.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscovery.java new file mode 100644 index 00000000..ebb93df1 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscovery.java @@ -0,0 +1,69 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import com.tencent.cloud.polaris.pojo.PolarisServiceInstance; +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.pojo.ServiceInstances; +import com.tencent.polaris.api.rpc.InstancesResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.cloud.client.ServiceInstance; + +/** + * @author Haotian Zhang + */ +public class PolarisServiceDiscovery { + + private final PolarisDiscoveryHandler polarisDiscoveryHandler; + + public PolarisServiceDiscovery(PolarisDiscoveryHandler polarisDiscoveryHandler) { + this.polarisDiscoveryHandler = polarisDiscoveryHandler; + } + + /** + * Return all instances for the given service. + * + * @param serviceId id of service + * @return list of instances + * @throws PolarisException polarisException + */ + public List getInstances(String serviceId) throws PolarisException { + List instances = new ArrayList<>(); + InstancesResponse filteredInstances = polarisDiscoveryHandler.getFilteredInstances(serviceId); + ServiceInstances serviceInstances = filteredInstances.toServiceInstances(); + for (Instance instance : serviceInstances.getInstances()) { + instances.add(new PolarisServiceInstance(instance)); + } + return instances; + } + + /** + * TODO Return the names of all services. + * + * @return list of service names + * @throws PolarisException polarisException + */ + public List getServices() throws PolarisException { + return Collections.emptyList(); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java new file mode 100644 index 00000000..48746dfe --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClient.java @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.reactive; + +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery; +import java.util.function.Function; +import org.reactivestreams.Publisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +/** + * Reactive Discovery Client for Polaris. + * + * @author Haotian Zhang + */ +public class PolarisReactiveDiscoveryClient implements ReactiveDiscoveryClient { + + private static final Logger log = LoggerFactory + .getLogger(PolarisReactiveDiscoveryClient.class); + + private PolarisServiceDiscovery polarisServiceDiscovery; + + public PolarisReactiveDiscoveryClient( + PolarisServiceDiscovery polarisServiceDiscovery) { + this.polarisServiceDiscovery = polarisServiceDiscovery; + } + + @Override + public String description() { + return "Spring Cloud Polaris Reactive Discovery Client"; + } + + @Override + public Flux getInstances(String serviceId) { + + return Mono.justOrEmpty(serviceId).flatMapMany(loadInstancesFromPolaris()) + .subscribeOn(Schedulers.boundedElastic()); + } + + private Function> loadInstancesFromPolaris() { + return serviceId -> { + try { + return Flux.fromIterable(polarisServiceDiscovery.getInstances(serviceId)); + } catch (PolarisException e) { + log.error("get service instance[{}] from polaris error!", serviceId, e); + return Flux.empty(); + } + }; + } + + @Override + public Flux getServices() { + return Flux.defer(() -> { + try { + return Flux.fromIterable(polarisServiceDiscovery.getServices()); + } catch (Exception e) { + log.error("get services from polaris server fail,", e); + return Flux.empty(); + } + }).subscribeOn(Schedulers.boundedElastic()); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfiguration.java new file mode 100644 index 00000000..183e1578 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfiguration.java @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.reactive; + +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled; +import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration; +import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Reactive Discovery Client Configuration for Polaris. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnReactiveDiscoveryEnabled +@AutoConfigureAfter({PolarisDiscoveryAutoConfiguration.class, + ReactiveCompositeDiscoveryClientAutoConfiguration.class}) +@AutoConfigureBefore({ReactiveCommonsClientAutoConfiguration.class}) +public class PolarisReactiveDiscoveryClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public PolarisReactiveDiscoveryClient polarisReactiveDiscoveryClient( + PolarisServiceDiscovery polarisServiceDiscovery) { + return new PolarisReactiveDiscoveryClient(polarisServiceDiscovery); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisAutoServiceRegistration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisAutoServiceRegistration.java new file mode 100644 index 00000000..d32b20d9 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisAutoServiceRegistration.java @@ -0,0 +1,95 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.util.StringUtils; + +/** + * @author Haotian Zhang + */ +public class PolarisAutoServiceRegistration extends AbstractAutoServiceRegistration { + + private static final Logger log = LoggerFactory.getLogger(PolarisAutoServiceRegistration.class); + + private final PolarisRegistration registration; + + public PolarisAutoServiceRegistration(ServiceRegistry serviceRegistry, + AutoServiceRegistrationProperties autoServiceRegistrationProperties, + PolarisRegistration registration) { + super(serviceRegistry, autoServiceRegistrationProperties); + this.registration = registration; + } + + @Override + protected PolarisRegistration getRegistration() { + if (this.registration.getPort() <= 0) { + this.registration.setPort(this.getPort().get()); + } + return this.registration; + } + + @Override + protected PolarisRegistration getManagementRegistration() { + return null; + } + + @Override + protected void register() { + if (!this.registration.getPolarisProperties().isRegisterEnabled()) { + log.debug("Registration disabled."); + return; + } + if (this.registration.getPort() <= 0) { + this.registration.setPort(getPort().get()); + } + super.register(); + } + + @Override + protected void registerManagement() { + if (!this.registration.getPolarisProperties().isRegisterEnabled()) { + return; + } + super.registerManagement(); + + } + + @Override + protected Object getConfiguration() { + return this.registration.getPolarisProperties(); + } + + @Override + protected boolean isEnabled() { + return this.registration.getPolarisProperties().isRegisterEnabled(); + } + + @Override + @SuppressWarnings("deprecation") + protected String getAppName() { + String appName = registration.getPolarisProperties().getService(); + return StringUtils.isEmpty(appName) ? super.getAppName() : appName; + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisRegistration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisRegistration.java new file mode 100644 index 00000000..3dcc05d0 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisRegistration.java @@ -0,0 +1,89 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.polaris.client.api.SDKContext; +import java.net.URI; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.serviceregistry.Registration; + +/** + * @author Haotian Zhang + */ +public class PolarisRegistration implements Registration, ServiceInstance { + + private final PolarisProperties polarisProperties; + + private final SDKContext polarisContext; + + public PolarisRegistration(PolarisProperties polarisProperties, SDKContext context) { + this.polarisProperties = polarisProperties; + this.polarisContext = context; + } + + @Override + public String getServiceId() { + return polarisProperties.getService(); + } + + @Override + public String getHost() { + return polarisContext.getConfig().getGlobal().getAPI().getBindIP(); + } + + @Override + public int getPort() { + return polarisProperties.getPort(); + } + + public void setPort(int port) { + this.polarisProperties.setPort(port); + } + + @Override + public boolean isSecure() { + return StringUtils.equalsIgnoreCase(polarisProperties.getProtocol(), "https"); + } + + @Override + public URI getUri() { + return DefaultServiceInstance.getUri(this); + } + + @Override + public Map getMetadata() { + return MetadataContextHolder.get().getAllSystemMetadata(); + } + + public PolarisProperties getPolarisProperties() { + return polarisProperties; + } + + @Override + public String toString() { + return "PolarisRegistration{" + + "polarisProperties=" + polarisProperties + + ", polarisContext=" + polarisContext + + '}'; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java new file mode 100644 index 00000000..5dc4814f --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistry.java @@ -0,0 +1,192 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import static org.springframework.util.ReflectionUtils.rethrowRuntimeException; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.polaris.api.core.ProviderAPI; +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.rpc.InstanceDeregisterRequest; +import com.tencent.polaris.api.rpc.InstanceHeartbeatRequest; +import com.tencent.polaris.api.rpc.InstanceRegisterRequest; +import com.tencent.polaris.api.rpc.InstancesResponse; +import com.tencent.polaris.client.util.NamedThreadFactory; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.cloud.client.serviceregistry.Registration; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.util.StringUtils; + +/** + * @author Haotian Zhang + */ +public class PolarisServiceRegistry implements ServiceRegistry { + + private static final Logger log = LoggerFactory.getLogger(PolarisServiceRegistry.class); + + private static final int ttl = 5; + + private final PolarisProperties polarisProperties; + + private final PolarisDiscoveryHandler polarisDiscoveryHandler; + + private final MetadataLocalProperties metadataLocalProperties; + + private final ScheduledExecutorService heartbeatExecutor; + + public PolarisServiceRegistry(PolarisProperties polarisProperties, PolarisDiscoveryHandler polarisDiscoveryHandler, + MetadataLocalProperties metadataLocalProperties) { + this.polarisProperties = polarisProperties; + this.polarisDiscoveryHandler = polarisDiscoveryHandler; + this.metadataLocalProperties = metadataLocalProperties; + if (polarisProperties.isHeartbeatEnabled()) { + ScheduledThreadPoolExecutor heartbeatExecutor = new ScheduledThreadPoolExecutor(0, + new NamedThreadFactory("spring-cloud-heartbeat")); + heartbeatExecutor.setMaximumPoolSize(1); + this.heartbeatExecutor = heartbeatExecutor; + } else { + this.heartbeatExecutor = null; + } + } + + @Override + public void register(Registration registration) { + + if (StringUtils.isEmpty(registration.getServiceId())) { + log.warn("No service to register for polaris client..."); + return; + } + // 注册实例 + InstanceRegisterRequest instanceRegisterRequest = new InstanceRegisterRequest(); + instanceRegisterRequest.setNamespace(polarisProperties.getNamespace()); + instanceRegisterRequest.setService(registration.getServiceId()); + instanceRegisterRequest.setHost(registration.getHost()); + instanceRegisterRequest.setPort(registration.getPort()); + instanceRegisterRequest.setToken(polarisProperties.getToken()); + if (null != heartbeatExecutor) { + instanceRegisterRequest.setTtl(ttl); + } + instanceRegisterRequest.setMetadata(metadataLocalProperties.getContent()); + instanceRegisterRequest.setProtocol(polarisProperties.getProtocol()); + instanceRegisterRequest.setVersion(polarisProperties.getVersion()); + try { + ProviderAPI providerClient = polarisDiscoveryHandler.getProviderAPI(); + providerClient.register(instanceRegisterRequest); + log.info("polaris registry, {} {} {}:{} {} register finished", + polarisProperties.getNamespace(), + registration.getServiceId(), registration.getHost(), + registration.getPort(), metadataLocalProperties.getContent()); + + if (null != heartbeatExecutor) { + InstanceHeartbeatRequest heartbeatRequest = new InstanceHeartbeatRequest(); + BeanUtils.copyProperties(instanceRegisterRequest, heartbeatRequest); + //注册成功后开始启动心跳线程 + heartbeat(heartbeatRequest); + } + } catch (Exception e) { + log.error("polaris registry, {} register failed...{},", registration.getServiceId(), registration, e); + rethrowRuntimeException(e); + } + } + + @Override + public void deregister(Registration registration) { + + log.info("De-registering from Polaris Server now..."); + + if (StringUtils.isEmpty(registration.getServiceId())) { + log.warn("No dom to de-register for polaris client..."); + return; + } + + InstanceDeregisterRequest deRegisterRequest = new InstanceDeregisterRequest(); + deRegisterRequest.setToken(polarisProperties.getToken()); + deRegisterRequest.setNamespace(polarisProperties.getNamespace()); + deRegisterRequest.setService(registration.getServiceId()); + deRegisterRequest.setHost(registration.getHost()); + deRegisterRequest.setPort(registration.getPort()); + + try { + ProviderAPI providerClient = polarisDiscoveryHandler.getProviderAPI(); + providerClient.deRegister(deRegisterRequest); + } catch (Exception e) { + log.error("ERR_POLARIS_DEREGISTER, de-register failed...{},", registration, e); + } finally { + if (null != heartbeatExecutor) { + heartbeatExecutor.shutdown(); + } + } + log.info("De-registration finished."); + } + + @Override + public void close() { + + } + + @Override + public void setStatus(Registration registration, String status) { + + } + + @Override + public Object getStatus(Registration registration) { + String serviceName = registration.getServiceId(); + InstancesResponse instancesResponse = polarisDiscoveryHandler.getInstances(serviceName); + Instance[] instances = instancesResponse.getInstances(); + if (null == instances || instances.length == 0) { + return null; + } + for (Instance instance : instances) { + if (instance.getHost().equalsIgnoreCase(registration.getHost()) + && instance.getPort() == polarisProperties.getPort()) { + return instance.isHealthy() ? "UP" : "DOWN"; + } + } + return null; + } + + /** + * 开启心跳线程 + * + * @param heartbeatRequest 心跳请求 + */ + public void heartbeat(InstanceHeartbeatRequest heartbeatRequest) { + heartbeatExecutor.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + try { + polarisDiscoveryHandler.getProviderAPI().heartbeat(heartbeatRequest); + } catch (PolarisException e) { + log.error("polaris heartbeat[{}]", e.getCode(), e); + } catch (Exception e) { + log.error("polaris heartbeat runtime error", e); + } + } + }, 0, ttl, TimeUnit.SECONDS); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfiguration.java new file mode 100644 index 00000000..157e60be --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfiguration.java @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.cloud.polaris.discovery.ConditionalOnPolarisDiscoveryEnabled; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties +@ConditionalOnPolarisDiscoveryEnabled +@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) +@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class, + AutoServiceRegistrationAutoConfiguration.class, + PolarisDiscoveryAutoConfiguration.class}) +public class PolarisServiceRegistryAutoConfiguration { + + @Bean + public PolarisServiceRegistry polarisServiceRegistry(PolarisProperties polarisProperties, + PolarisDiscoveryHandler polarisDiscoveryHandler, + MetadataLocalProperties metadataLocalProperties) { + return new PolarisServiceRegistry(polarisProperties, polarisDiscoveryHandler, metadataLocalProperties); + } + + @Bean + @ConditionalOnBean(AutoServiceRegistrationProperties.class) + public PolarisRegistration polarisRegistration(PolarisProperties polarisProperties, + SDKContext context) { + return new PolarisRegistration(polarisProperties, context); + } + + @Bean + @ConditionalOnBean(AutoServiceRegistrationProperties.class) + public PolarisAutoServiceRegistration polarisAutoServiceRegistration(PolarisServiceRegistry registry, + AutoServiceRegistrationProperties autoServiceRegistrationProperties, PolarisRegistration registration) { + return new PolarisAutoServiceRegistration(registry, + autoServiceRegistrationProperties, registration); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisDiscoveryRibbonAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisDiscoveryRibbonAutoConfiguration.java new file mode 100644 index 00000000..ff123f9a --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisDiscoveryRibbonAutoConfiguration.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ribbon; + +import com.tencent.cloud.polaris.discovery.ConditionalOnPolarisDiscoveryEnabled; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.context.annotation.Configuration; + +/** + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties +@ConditionalOnPolarisDiscoveryEnabled +@AutoConfigureAfter(RibbonAutoConfiguration.class) +@RibbonClients(defaultConfiguration = PolarisRibbonServerListConfiguration.class) +public class PolarisDiscoveryRibbonAutoConfiguration { + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfiguration.java new file mode 100644 index 00000000..f612b0e1 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfiguration.java @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ribbon; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Haotian Zhang + */ +@Configuration +public class PolarisRibbonServerListConfiguration { + + @Bean + @ConditionalOnMissingBean + public ServerList ribbonServerList(PolarisDiscoveryHandler polarisDiscoveryHandler, + IClientConfig iClientConfig) { + PolarisServerList serverList = new PolarisServerList(polarisDiscoveryHandler); + serverList.initWithNiwsConfig(iClientConfig); + return serverList; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisServerList.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisServerList.java new file mode 100644 index 00000000..b52fd6b7 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/ribbon/PolarisServerList.java @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ribbon; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractServerList; +import com.netflix.loadbalancer.Server; +import com.tencent.cloud.polaris.pojo.PolarisServer; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.pojo.ServiceInstances; +import com.tencent.polaris.api.rpc.InstancesResponse; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Haotian Zhang + */ +public class PolarisServerList extends AbstractServerList { + + private String serviceId; + + private PolarisDiscoveryHandler polarisDiscoveryHandler; + + public PolarisServerList(PolarisDiscoveryHandler polarisDiscoveryHandler) { + this.polarisDiscoveryHandler = polarisDiscoveryHandler; + } + + @Override + public List getInitialListOfServers() { + return getServers(); + } + + @Override + public List getUpdatedListOfServers() { + return getServers(); + } + + private List getServers() { + InstancesResponse filteredInstances = polarisDiscoveryHandler.getInstances(serviceId); + ServiceInstances serviceInstances = filteredInstances.toServiceInstances(); + List polarisServers = new ArrayList<>(); + for (Instance instance : serviceInstances.getInstances()) { + polarisServers.add(new PolarisServer(serviceInstances, instance)); + } + return polarisServers; + } + + public String getServiceId() { + return serviceId; + } + + @Override + public void initWithNiwsConfig(IClientConfig iClientConfig) { + this.serviceId = iClientConfig.getClientName(); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..bf979c14 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,46 @@ +{ + "properties": [ + { + "name": "spring.cloud.polaris.discovery.service", + "type": "java.lang.String", + "defaultValue": "${spring.application.name}", + "description": "the service name to register, default value is ${spring.application.name}." + }, + { + "name": "spring.cloud.polaris.discovery.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "enable polaris discovery or not." + }, + { + "name": "spring.cloud.polaris.discovery.instance-enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "If instance is enabled to accept request. The default value is true." + }, + { + "name": "spring.cloud.polaris.discovery.token", + "type": "java.lang.String", + "defaultValue": "${spring.cloud.polaris.token}", + "description": "polaris discovery service's username to authenticate." + }, + { + "name": "spring.cloud.polaris.discovery.version", + "type": "java.lang.String", + "defaultValue": "${spring.cloud.polaris.version}", + "description": "polaris discovery service's username to authenticate." + }, + { + "name": "spring.cloud.polaris.protocol", + "type": "java.lang.String", + "defaultValue": "${spring.cloud.polaris.protocol}", + "description": "the protocol of polaris instance ." + }, + { + "name": "spring.cloud.polaris.weight", + "type": "java.lang.String", + "defaultValue": 100, + "description": "the weight of polaris instance , use to loadbalance." + } + ] +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..75b02a91 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration,\ + com.tencent.cloud.polaris.ribbon.PolarisDiscoveryRibbonAutoConfiguration,\ + com.tencent.cloud.polaris.registry.PolarisServiceRegistryAutoConfiguration + diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/PolarisPropertiesTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/PolarisPropertiesTest.java new file mode 100644 index 00000000..0d8b8772 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/PolarisPropertiesTest.java @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris; + + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +import org.junit.Test; + +public class PolarisPropertiesTest { + + @Test + public void testInitAndGetSet() { + PolarisProperties temp = new PolarisProperties(); + try { + temp.setNamespace(NAMESPACE_TEST); + temp.getNamespace(); + + temp.setService(SERVICE_PROVIDER); + temp.getService(); + + temp.setToken("xxxxxx"); + temp.getToken(); + + temp.init(); + assertThat(temp).isNotNull(); + } catch (Exception e) { + fail(); + e.printStackTrace(); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java new file mode 100644 index 00000000..c95058b0 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java @@ -0,0 +1,80 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.tencent.cloud.polaris.PolarisProperties; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.core.ProviderAPI; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisDiscoveryAutoConfigurationTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisDiscoveryAutoConfiguration.class, + PolarisDiscoveryClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testDefaultInitialization() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(ProviderAPI.class); + assertThat(context).hasSingleBean(ConsumerAPI.class); + assertThat(context).hasSingleBean(PolarisProperties.class); + assertThat(context).hasSingleBean(PolarisServiceDiscovery.class); + }); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisDiscoveryAutoConfiguration { + + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java new file mode 100644 index 00000000..05c26209 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisDiscoveryClientConfigurationTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisDiscoveryClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testDefaultInitialization() { + this.contextRunner.run(context -> assertThat(context).hasSingleBean(PolarisDiscoveryClient.class)); + } + + @Test + public void testDiscoveryBlockingDisabled() { + this.contextRunner.withPropertyValues("spring.cloud.discovery.blocking.enabled=false") + .run(context -> assertThat(context).doesNotHaveBean(PolarisDiscoveryClient.class)); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisDiscoveryClientConfiguration { + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientTest.java new file mode 100644 index 00000000..ceefa8f9 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientTest.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import com.tencent.cloud.polaris.pojo.PolarisServiceInstance; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.cloud.client.ServiceInstance; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore("javax.management.*") +public class PolarisDiscoveryClientTest { + + @Mock + private PolarisServiceDiscovery polarisServiceDiscovery; + + @InjectMocks + private PolarisDiscoveryClient client; + + @Test + public void testGetInstances() { + + when(polarisServiceDiscovery.getInstances(anyString())).thenReturn(singletonList(mock(PolarisServiceInstance.class))); + + List serviceInstances = client.getInstances(SERVICE_PROVIDER); + + assertThat(serviceInstances).isNotEmpty(); + } + + @Test + public void testGetServices() { + + when(polarisServiceDiscovery.getServices()).thenReturn(singletonList(SERVICE_PROVIDER)); + + List services = client.getServices(); + + assertThat(services).contains(SERVICE_PROVIDER).size().isEqualTo(1); + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java new file mode 100644 index 00000000..77d99d44 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java @@ -0,0 +1,108 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.polaris.api.exception.PolarisException; + +import java.util.List; + +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import com.tencent.polaris.test.mock.discovery.NamingService; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisServiceDiscoveryTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisServiceDiscoveryTest.PolarisPropertiesConfiguration.class, + PolarisDiscoveryClientConfiguration.class, + PolarisDiscoveryAutoConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") + .withPropertyValues("spring.cloud.polaris.discovery.namespace=" + NAMESPACE_TEST) + .withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + + // add service with 3 instances + NamingService.InstanceParameter instanceParameter = new NamingService.InstanceParameter(); + instanceParameter.setHealthy(true); + instanceParameter.setIsolated(false); + instanceParameter.setWeight(100); + ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER); + namingServer.getNamingService().batchAddInstances(serviceKey, PORT, 3, instanceParameter); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testGetInstances() { + this.contextRunner.run(context -> { + PolarisServiceDiscovery polarisServiceDiscovery = context.getBean(PolarisServiceDiscovery.class); + List serviceInstances = polarisServiceDiscovery.getInstances(SERVICE_PROVIDER); + assertThat(serviceInstances.isEmpty()).isFalse(); + assertThat(serviceInstances).hasSize(3); + assertThat(serviceInstances.get(0).getPort()).isEqualTo(PORT); + assertThat(serviceInstances.get(1).getPort()).isEqualTo(PORT + 1); + assertThat(serviceInstances.get(2).getPort()).isEqualTo(PORT + 2); + }); + } + + @Test + public void testGetServices() throws PolarisException { + this.contextRunner.run(context -> { + PolarisServiceDiscovery polarisServiceDiscovery = context.getBean(PolarisServiceDiscovery.class); + List services = polarisServiceDiscovery.getServices(); + + assertThat(services.size()).isEqualTo(0); + }); + + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisPropertiesConfiguration { + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java new file mode 100644 index 00000000..330cde35 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.reactive; + +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisReactiveDiscoveryClientConfigurationTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisReactiveDiscoveryClientConfiguration.class, + PolarisDiscoveryClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testDefaultInitialization() { + this.contextRunner.run(context -> assertThat(context) + .hasSingleBean(PolarisReactiveDiscoveryClient.class)); + } + + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisReactiveDiscoveryClientConfiguration { + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientTest.java new file mode 100644 index 00000000..aaca939f --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientTest.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.discovery.reactive; + +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static java.util.Collections.singletonList; +import static org.mockito.Mockito.when; + +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.cloud.polaris.discovery.PolarisServiceDiscovery; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.cloud.client.ServiceInstance; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore("javax.management.*") +public class PolarisReactiveDiscoveryClientTest { + + @Mock + private PolarisServiceDiscovery serviceDiscovery; + + @Mock + private ServiceInstance serviceInstance; + + @InjectMocks + private PolarisReactiveDiscoveryClient client; + + @Test + public void testGetInstances() throws PolarisException { + + when(serviceDiscovery.getInstances(SERVICE_PROVIDER)).thenReturn(singletonList(serviceInstance)); + + Flux instances = this.client.getInstances(SERVICE_PROVIDER); + + StepVerifier.create(instances).expectNextCount(1).expectComplete().verify(); + } + + @Test + public void testGetServices() throws PolarisException { + + when(serviceDiscovery.getServices()).thenReturn(Arrays.asList(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2)); + + Flux services = this.client.getServices(); + + StepVerifier.create(services).expectNext(SERVICE_PROVIDER + 1, SERVICE_PROVIDER + 2) + .expectComplete().verify(); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfigurationTest.java new file mode 100644 index 00000000..877903e6 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryAutoConfigurationTest.java @@ -0,0 +1,77 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; + +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +public class PolarisServiceRegistryAutoConfigurationTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisServiceRegistryAutoConfiguration.class, + PolarisDiscoveryClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testDefaultInitialization() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(PolarisDiscoveryAutoConfiguration.class); + assertThat(context).hasSingleBean(AutoServiceRegistrationAutoConfiguration.class); + }); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisServiceRegistryAutoConfiguration { + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryTest.java new file mode 100644 index 00000000..1265ca6e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/registry/PolarisServiceRegistryTest.java @@ -0,0 +1,107 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.registry; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisServiceRegistryTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisPropertiesConfiguration.class, + PolarisDiscoveryClientConfiguration.class, + PolarisDiscoveryAutoConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") + .withPropertyValues("spring.cloud.polaris.discovery.namespace="+ NAMESPACE_TEST) + .withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + + // add service + namingServer.getNamingService().addService(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER)); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Test + public void testRegister() { + this.contextRunner.run(context -> { + PolarisServiceRegistry registry = context.getBean(PolarisServiceRegistry.class); + PolarisRegistration registration = Mockito.mock(PolarisRegistration.class); + when(registration.getHost()).thenReturn("127.0.0.1"); + when(registration.getPort()).thenReturn(PORT); + when(registration.getServiceId()).thenReturn(SERVICE_PROVIDER); + + try { + registry.register(registration); + } catch (Exception e) { + fail(); + } + + try { + assertThat(registry.getStatus(registration)).isEqualTo("DOWN"); + } catch (Exception e) { + fail(); + } + + try { + registry.deregister(registration); + } catch (Exception e) { + fail(); + } + }); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisPropertiesConfiguration { + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListAutoConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListAutoConfigurationTest.java new file mode 100644 index 00000000..93b5c7c4 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListAutoConfigurationTest.java @@ -0,0 +1,83 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ribbon; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.client.config.IClientConfig; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +public class PolarisRibbonServerListAutoConfigurationTest { + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisRibbonClientTest.class, + PolarisDiscoveryClientConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") + .withPropertyValues("spring.cloud.polaris.discovery.namespace=" + NAMESPACE_TEST) + .withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx"); + + @Test + public void testProperties() { + this.contextRunner.run(context -> { + PolarisDiscoveryHandler discoveryHandler = context.getBean(PolarisDiscoveryHandler.class); + PolarisServerList serverList = new PolarisServerList(discoveryHandler); + IClientConfig iClientConfig = context.getBean(IClientConfig.class); + serverList.initWithNiwsConfig(iClientConfig); + + assertThat(serverList.getServiceId()).isEqualTo(SERVICE_PROVIDER); + }); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisRibbonClientTest { + + @Bean + IClientConfig iClientConfig() { + DefaultClientConfigImpl config = new DefaultClientConfigImpl(); + config.setClientName(SERVICE_PROVIDER); + return config; + } + + @Bean + @LoadBalanced + RestTemplate restTemplate() { + return new RestTemplate(); + } + + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java new file mode 100644 index 00000000..f42fcb68 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ribbon; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.Server; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryAutoConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; +import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; +import java.util.List; + +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import com.tencent.polaris.test.mock.discovery.NamingService; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.Configuration; + +public class PolarisServerListTest { + + private static NamingServer namingServer; + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisContextConfiguration.class, + PolarisServerListTest.PolarisPropertiesConfiguration.class, + PolarisDiscoveryClientConfiguration.class, + PolarisDiscoveryAutoConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") + .withPropertyValues("spring.cloud.polaris.discovery.namespace=" + NAMESPACE_TEST) + .withPropertyValues("spring.cloud.polaris.discovery.token=xxxxxx"); + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + + // add service + namingServer.getNamingService().addService(new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER)); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + /** + * Test {@link PolarisServerList#getInitialListOfServers()} with empty server list. + */ + @Test + @SuppressWarnings("unchecked") + public void test1(){ + this.contextRunner.run(context -> { + // mock + IClientConfig iClientConfig = mock(IClientConfig.class); + when(iClientConfig.getClientName()).thenReturn(SERVICE_PROVIDER); + PolarisDiscoveryHandler polarisDiscoveryHandler = context.getBean(PolarisDiscoveryHandler.class); + PolarisServerList serverList = new PolarisServerList(polarisDiscoveryHandler); + serverList.initWithNiwsConfig(iClientConfig); + + List servers = serverList.getInitialListOfServers(); + assertThat(servers).isEmpty(); + }); + } + + /** + * Test {@link PolarisServerList#getUpdatedListOfServers()} with server list of size 3. + */ + @Test + @SuppressWarnings("unchecked") + public void test2() throws Exception { + this.contextRunner.run(context -> { + // mock + IClientConfig iClientConfig = mock(IClientConfig.class); + when(iClientConfig.getClientName()).thenReturn(SERVICE_PROVIDER); + PolarisDiscoveryHandler polarisDiscoveryHandler = context.getBean(PolarisDiscoveryHandler.class); + PolarisServerList serverList = new PolarisServerList(polarisDiscoveryHandler); + serverList.initWithNiwsConfig(iClientConfig); + + + // add service with 3 instances + NamingService.InstanceParameter instanceParameter = new NamingService.InstanceParameter(); + instanceParameter.setHealthy(true); + instanceParameter.setIsolated(false); + instanceParameter.setWeight(100); + ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER); + namingServer.getNamingService().batchAddInstances(serviceKey, PORT, 3, instanceParameter); + + List servers = serverList.getUpdatedListOfServers(); + assertThat(servers).hasSize(3); + assertThat(servers.get(0).getPort()).isEqualTo(PORT); + assertThat(servers.get(1).getPort()).isEqualTo(PORT + 1); + assertThat(servers.get(2).getPort()).isEqualTo(PORT + 2); + }); + } + + @Configuration + @EnableAutoConfiguration + @EnableDiscoveryClient + static class PolarisPropertiesConfiguration { + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml new file mode 100644 index 00000000..544a4360 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/pom.xml @@ -0,0 +1,130 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-polaris-ratelimit + Spring Cloud Starter Tencent Polaris Ratelimit + + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + com.tencent.cloud + spring-cloud-tencent-metadata + + + + + + com.tencent.nameservice + polaris-factory + + + + com.tencent.nameservice + polaris-test-common + test + true + + + + com.tencent.nameservice + polaris-test-mock-discovery + test + true + + + + + org.springframework.boot + spring-boot + true + + + + org.springframework.cloud + spring-cloud-commons + true + + + + org.springframework + spring-webmvc + true + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.apache.tomcat.embed + tomcat-embed-core + true + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + true + + + + org.springframework.cloud + spring-cloud-context + true + + + + io.github.openfeign + feign-hystrix + true + + + + org.springframework.boot + spring-boot-starter-test + test + true + + + + org.powermock + powermock-module-junit4 + test + true + + + + org.powermock + powermock-api-mockito2 + test + true + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitConfiguration.java new file mode 100644 index 00000000..4ad99a82 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitConfiguration.java @@ -0,0 +1,75 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ratelimit; + +import static javax.servlet.DispatcherType.ASYNC; +import static javax.servlet.DispatcherType.ERROR; +import static javax.servlet.DispatcherType.FORWARD; +import static javax.servlet.DispatcherType.INCLUDE; +import static javax.servlet.DispatcherType.REQUEST; + +import com.tencent.cloud.polaris.ratelimit.callee.QuotaCheckFilter; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.ratelimit.api.core.LimitAPI; +import com.tencent.polaris.ratelimit.factory.LimitAPIFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Haotian Zhang + */ +@Configuration +public class RateLimitConfiguration { + + @Bean + @ConditionalOnMissingBean + public LimitAPI limitAPI(SDKContext polarisContext) { + return LimitAPIFactory.createLimitAPIByContext(polarisContext); + } + + /** + * 被调方限流 + */ + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + @ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true) + static class QuotaCheckFilterConfig { + + @Bean + @ConditionalOnMissingBean + public QuotaCheckFilter quotaCheckFilter(LimitAPI limitAPI) { + return new QuotaCheckFilter(limitAPI); + } + + @Bean + public FilterRegistrationBean quotaFilterRegistrationBean( + QuotaCheckFilter quotaCheckFilter) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(quotaCheckFilter); + registrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); + registrationBean.setName("quotaFilterRegistrationBean"); + registrationBean.setOrder(QuotaCheckFilter.ORDER); + return registrationBean; + } + } + + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/callee/QuotaCheckFilter.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/callee/QuotaCheckFilter.java new file mode 100644 index 00000000..e4b9d755 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/callee/QuotaCheckFilter.java @@ -0,0 +1,90 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ratelimit.callee; + +import static org.springframework.http.HttpStatus.TOO_MANY_REQUESTS; + +import com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.polaris.ratelimit.utils.Consts; +import com.tencent.polaris.ratelimit.api.core.LimitAPI; +import com.tencent.polaris.ratelimit.api.rpc.QuotaRequest; +import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; +import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * @author Haotian Zhang + */ +@Order(QuotaCheckFilter.ORDER) +public class QuotaCheckFilter extends OncePerRequestFilter { + + private static final Logger LOG = LoggerFactory.getLogger(QuotaCheckFilter.class); + + public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10; + + private final LimitAPI limitAPI; + + public QuotaCheckFilter(LimitAPI limitAPI) { + this.limitAPI = limitAPI; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + QuotaRequest quotaRequest = new QuotaRequest(); + String localNamespace = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_NAMESPACE); + String localService = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_SERVICE); + quotaRequest.setNamespace(localNamespace); + quotaRequest.setService(localService); + quotaRequest.setCount(1); + + String method = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_PATH); + if (StringUtils.isNotBlank(method)) { + Map labels = new HashMap<>(); + labels.put("method", method); + quotaRequest.setLabels(labels); + } + try { + QuotaResponse quotaResponse = limitAPI.getQuota(quotaRequest); + if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { + response.setStatus(TOO_MANY_REQUESTS.value()); + response.getWriter().write(Consts.QUOTA_LIMITED_INFO + quotaResponse.getInfo()); + } else { + filterChain.doFilter(request, response); + } + } catch (Throwable t) { + //限流API调用出现异常,不应该影响业务流程的调用 + LOG.error("fail to invoke getQuota, service is " + localService, t); + filterChain.doFilter(request, response); + } + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/Consts.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/Consts.java new file mode 100644 index 00000000..34260e7b --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/Consts.java @@ -0,0 +1,26 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ratelimit.utils; + +/** + * @author Haotian Zhang + */ +public interface Consts { + + String QUOTA_LIMITED_INFO = "request blocked by polaris, reason is "; +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..b71a17cc --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "spring.cloud.polaris.ratelimit.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "Enable polaris rate limit or not." + } + ] +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..59b514cb --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.polaris.ratelimit.RateLimitConfiguration \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/CalleeControllerTests.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/CalleeControllerTests.java new file mode 100644 index 00000000..2a4f4e80 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/CalleeControllerTests.java @@ -0,0 +1,137 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ratelimit.controller; + +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.ratelimit.api.core.LimitAPI; +import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; +import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; +import com.tencent.polaris.test.mock.discovery.NamingServer; +import com.tencent.polaris.test.mock.discovery.NamingService; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.HttpClientErrorException.TooManyRequests; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST; +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = {CalleeControllerTests.Config.class, + TestController.class}, properties = {"spring.application.name=java_provider_test", + "spring.cloud.polaris.discovery.namespace=Test", "spring.cloud.polaris.address=grpc://127.0.0.1:10081"}) +public class CalleeControllerTests { + + private static NamingServer namingServer; + + @LocalServerPort + private int port; + + @Autowired + private RestTemplate restTemplate; + + @MockBean + private LimitAPI limitAPI; + + @BeforeClass + public static void beforeClass() throws Exception { + namingServer = NamingServer.startNamingServer(10081); + + // add service with 3 instances + NamingService.InstanceParameter instanceParameter = new NamingService.InstanceParameter(); + instanceParameter.setHealthy(true); + instanceParameter.setIsolated(false); + instanceParameter.setWeight(100); + ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, SERVICE_PROVIDER); + namingServer.getNamingService().batchAddInstances(serviceKey, PORT, 3, instanceParameter); + } + + @AfterClass + public static void afterClass() throws Exception { + if (null != namingServer) { + namingServer.terminate(); + } + } + + @Before + public void setUp() throws Exception { + QuotaResponse quotaResponse = mock(QuotaResponse.class); + when(quotaResponse.getCode()).thenReturn(QuotaResultCode.QuotaResultOk); + when(limitAPI.getQuota(any())).thenReturn(quotaResponse); + } + + @Test + public void test1() { + String url = "http://localhost:" + port + "/test/info"; + + boolean hasPassed = false; + boolean hasLimited = false; + for (int i = 0; i < 30; i++) { + try { + if (i > 9) { + QuotaResponse quotaResponse = mock(QuotaResponse.class); + when(quotaResponse.getCode()).thenReturn(QuotaResultCode.QuotaResultLimited); + when(quotaResponse.getInfo()).thenReturn("Testing rate limit after 10 times success."); + when(limitAPI.getQuota(any())).thenReturn(quotaResponse); + } + String result = restTemplate.getForObject(url, String.class); + System.out.println(result + " [" + i + "]"); + hasPassed = true; + } catch (RestClientException e) { + if (e instanceof TooManyRequests) { + System.out.println(((TooManyRequests) e).getResponseBodyAsString()); + hasLimited = true; + } else { + e.printStackTrace(); + Assert.fail(e.getMessage()); + } + } + } + Assert.assertTrue(hasPassed); + Assert.assertTrue(hasLimited); + } + + + @Configuration + @EnableAutoConfiguration + public static class Config { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/TestController.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/TestController.java new file mode 100644 index 00000000..ff3d479f --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/controller/TestController.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.ratelimit.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@RequestMapping("/test") +public class TestController { + + @GetMapping("/info") + public String info() throws Exception { + return "hello service info"; + } + + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/pom.xml b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/pom.xml new file mode 100644 index 00000000..bdac01d1 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/pom.xml @@ -0,0 +1,67 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-polaris-router + Spring Cloud Starter Tencent Polaris Router + + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + com.tencent.cloud + spring-cloud-tencent-metadata + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + + com.tencent.nameservice + polaris-router-factory + + + + com.tencent.nameservice + polaris-test-common + test + + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + true + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + org.springframework.boot + spring-boot-starter-test + test + true + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRoutingLoadBalancer.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRoutingLoadBalancer.java new file mode 100644 index 00000000..82c1c55e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRoutingLoadBalancer.java @@ -0,0 +1,96 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.DynamicServerListLoadBalancer; +import com.netflix.loadbalancer.IPing; +import com.netflix.loadbalancer.IRule; +import com.netflix.loadbalancer.PollingServerListUpdater; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.polaris.pojo.PolarisServer; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.pojo.ServiceInfo; +import com.tencent.polaris.api.pojo.ServiceInstances; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; +import org.apache.commons.lang.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Haotian Zhang + */ +public class PolarisRoutingLoadBalancer extends DynamicServerListLoadBalancer { + + private final RouterAPI routerAPI; + + public PolarisRoutingLoadBalancer(IClientConfig config, IRule rule, IPing ping, + ServerList serverList, RouterAPI routerAPI) { + super(config, rule, ping, serverList, null, new PollingServerListUpdater()); + this.routerAPI = routerAPI; + } + + @Override + public List getReachableServers() { + List allServers = super.getAllServers(); + if (CollectionUtils.isEmpty(allServers)) { + return allServers; + } + ServiceInstances serviceInstances = null; + if (allServers.get(0) instanceof PolarisServer) { + serviceInstances = ((PolarisServer) allServers.get(0)).getServiceInstances(); + } else { + // TODO serviceInstances = DefaultServiceInstancesImpl.createByServerList(allServers); + throw new IllegalStateException("PolarisRoutingLoadBalancer only support PolarisServer instances"); + } + ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest(); + processRoutersRequest.setDstInstances(serviceInstances); + String srcNamespace = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_NAMESPACE); + String srcService = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.LOCAL_SERVICE); + Map transitiveCustomMetadata = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); + String method = MetadataContextHolder.get().getSystemMetadata(SystemMetadataKey.PEER_PATH); + processRoutersRequest.setMethod(method); + if (StringUtils.isNotBlank(srcNamespace) && StringUtils.isNotBlank(srcService)) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setNamespace(srcNamespace); + serviceInfo.setService(srcService); + serviceInfo.setMetadata(transitiveCustomMetadata); + processRoutersRequest.setSourceService(serviceInfo); + } + ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest); + ServiceInstances filteredInstances = processRoutersResponse.getServiceInstances(); + List instances = new ArrayList<>(); + for (Instance instance : filteredInstances.getInstances()) { + instances.add(new PolarisServer(serviceInstances, instance)); + } + return instances; + } + + @Override + public List getAllServers() { + return getReachableServers(); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfiguration.java new file mode 100644 index 00000000..8d33972a --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.config; + +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.api.RouterAPIFactory; +import com.tencent.polaris.router.api.core.RouterAPI; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClients; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto-configuration Ribbon for Polaris. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties +@ConditionalOnProperty(value = "spring.cloud.polaris.loadbalancer.enabled", matchIfMissing = true) +@AutoConfigureAfter(RibbonAutoConfiguration.class) +@RibbonClients(defaultConfiguration = PolarisRibbonClientConfiguration.class) +public class PolarisRibbonAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public PolarisRibbonProperties polarisRibbonProperties() { + return new PolarisRibbonProperties(); + } + + @Bean(name = "polarisRoute") + @ConditionalOnMissingBean + public RouterAPI polarisRouter(SDKContext polarisContext) throws PolarisException { + return RouterAPIFactory.createRouterAPIByContext(polarisContext); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonClientConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonClientConfiguration.java new file mode 100644 index 00000000..5d0ed05b --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonClientConfiguration.java @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.config; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.IPing; +import com.netflix.loadbalancer.IRule; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import com.tencent.cloud.polaris.router.PolarisRoutingLoadBalancer; +import com.tencent.cloud.polaris.router.rule.PolarisLoadBalanceRule; +import com.tencent.cloud.polaris.router.rule.PolarisWeightedRandomRule; +import com.tencent.polaris.router.api.core.RouterAPI; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Haotian Zhang + */ +@Configuration +public class PolarisRibbonClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public IRule polarisRibbonRule(PolarisRibbonProperties polarisRibbonProperties) { + switch (PolarisLoadBalanceRule.fromStrategy(polarisRibbonProperties.getPolicy())) { + case WEIGHTED_RANDOM_RULE: + default: + return new PolarisWeightedRandomRule(); + } + } + + @Bean + @ConditionalOnMissingBean + public ILoadBalancer polarisRoutingLoadBalancer(IClientConfig iClientConfig, IRule iRule, IPing iPing, + ServerList serverList, RouterAPI polarisRouter) { + return new PolarisRoutingLoadBalancer(iClientConfig, iRule, iPing, serverList, polarisRouter); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonProperties.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonProperties.java new file mode 100644 index 00000000..6e6ab501 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/PolarisRibbonProperties.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Haotian Zhang + */ +@ConfigurationProperties("spring.cloud.polaris.ribbon") +public class PolarisRibbonProperties { + + /** + * 是否开启负载均衡 + */ + @Value("${spring.cloud.polaris.loadbalancer.enabled:#{true}}") + private Boolean loadbalancerEnabled; + + /** + * loadbalnce strategy + */ + @Value("${spring.cloud.polaris.loadbalancer.strategy:#{'weightedRandom'}}") + private String policy; + + public String getPolicy() { + return policy; + } + + public void setPolicy(String policy) { + this.policy = policy; + } + + public Boolean getLoadbalancerEnabled() { + return loadbalancerEnabled; + } + + public void setLoadbalancerEnabled(Boolean loadbalancerEnabled) { + this.loadbalancerEnabled = loadbalancerEnabled; + } + + @Override + public String toString() { + return "PolarisRibbonProperties{" + + "loadbalancerEnabled=" + loadbalancerEnabled + + ", policy='" + policy + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisLoadBalanceRule.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisLoadBalanceRule.java new file mode 100644 index 00000000..b39b9e17 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisLoadBalanceRule.java @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.rule; + +import java.util.Arrays; + +/** + * @author Haotian Zhang + */ +public enum PolarisLoadBalanceRule { + + /** + * 加权随机 + */ + WEIGHTED_RANDOM_RULE("weighted_random"); + + /** + * 策略 + */ + String policy; + + PolarisLoadBalanceRule(String strategy) { + this.policy = strategy; + } + + public static PolarisLoadBalanceRule fromStrategy(String strategy) { + return Arrays.stream(values()).filter(t -> t.getPolicy().equals(strategy)).findAny() + .orElse(WEIGHTED_RANDOM_RULE); + } + + /** + * {@link #policy}的getter方法。 + */ + public String getPolicy() { + return policy; + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisWeightedRandomRule.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisWeightedRandomRule.java new file mode 100644 index 00000000..a950fb53 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/rule/PolarisWeightedRandomRule.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.rule; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.AbstractLoadBalancerRule; +import com.netflix.loadbalancer.Server; +import com.tencent.cloud.polaris.pojo.PolarisServer; +import com.tencent.polaris.api.config.consumer.LoadBalanceConfig; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.router.api.core.RouterAPI; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceRequest; +import com.tencent.polaris.router.api.rpc.ProcessLoadBalanceResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * @author Haotian Zhang + */ +public class PolarisWeightedRandomRule extends AbstractLoadBalancerRule { + + private static final String POLICY = LoadBalanceConfig.LOAD_BALANCE_WEIGHTED_RANDOM; + + @Autowired + private RouterAPI polarisRouter; + + @Override + public void initWithNiwsConfig(IClientConfig clientConfig) { + + } + + @Override + public Server choose(Object key) { + List allServers = getLoadBalancer().getReachableServers(); + if (CollectionUtils.isEmpty(allServers)) { + return null; + } + Server server = allServers.get(0); + if (!(server instanceof PolarisServer)) { + throw new IllegalStateException("PolarisDiscoveryRule only support PolarisServer instances"); + } + PolarisServer polarisServer = (PolarisServer) server; + ProcessLoadBalanceRequest request = new ProcessLoadBalanceRequest(); + request.setDstInstances(polarisServer.getServiceInstances()); + request.setLbPolicy(POLICY); + ProcessLoadBalanceResponse processLoadBalanceResponse = polarisRouter.processLoadBalance(request); + Instance targetInstance = processLoadBalanceResponse.getTargetInstance(); + return new PolarisServer(polarisServer.getServiceInstances(), targetInstance); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..c94ac4e0 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,16 @@ +{ + "properties": [ + { + "name": "spring.cloud.polaris.loadbalancer.enabled", + "type": "java.lang.Boolean", + "defaultValue": "true", + "description": "polaris loadbalancer" + }, + { + "name": "spring.cloud.polaris.loadbalancer.strategy", + "type": "java.lang.String", + "defaultValue": "random", + "description": "retry,best_available,availability_filtering,round_robin,weighted_response_time,zone_avoidance,random,consistent_hash,weighted_random" + } + ] +} diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..75c19104 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.polaris.router.config.PolarisRibbonAutoConfiguration diff --git a/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java new file mode 100644 index 00000000..b6ec3abe --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.router.config; + +import com.tencent.polaris.router.api.core.RouterAPI; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; + +import static com.tencent.polaris.test.common.Consts.PORT; +import static com.tencent.polaris.test.common.Consts.SERVICE_PROVIDER; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author skyehtzhang + */ +public class PolarisRibbonAutoConfigurationTest { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + PolarisRibbonTest.class, + PolarisRibbonAutoConfiguration.class)) + .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) + .withPropertyValues("server.port=" + PORT) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); + + @Test + public void testDefaultInitialization() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(RouterAPI.class); + assertThat(context).hasSingleBean(PolarisRibbonProperties.class); + }); + } + + @Configuration + @EnableAutoConfiguration + static class PolarisRibbonTest { + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/pom.xml b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/pom.xml new file mode 100644 index 00000000..8169ff48 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/pom.xml @@ -0,0 +1,67 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-commons + Spring Cloud Tencent Commons + + + 3.2.2 + 2.5 + 2.6 + + + + + + com.tencent.nameservice + polaris-model + + + + + org.springframework.boot + spring-boot + true + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + true + + + + commons-collections + commons-collections + ${commons.collections.version} + + + + commons-lang + commons-lang + ${commons.lang.version} + + + + commons-io + commons-io + ${commons.io.version} + + + + com.google.guava + guava + 28.0-jre + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java new file mode 100644 index 00000000..5071628b --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ApplicationContextAwareUtils.java @@ -0,0 +1,69 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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 org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring Context Util + * + * @author Hongwei Zhu + */ +@Component +public class ApplicationContextAwareUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ApplicationContextAwareUtils.applicationContext = applicationContext; + } + + /** + * 获取上下文 + * + * @return Spring上下文 + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 获取Spring配置 + * + * @param key 配置名称 + * @return 配置值 + */ + public static String getProperties(String key) { + return applicationContext.getEnvironment().getProperty(key); + } + + /** + * 获取Spring配置
+ * 没有配置时,返回默认值 + * + * @param key 配置名称 + * @param defaultValue 默认值 + * @return 配置值 + */ + public static String getProperties(String key, String defaultValue) { + return applicationContext.getEnvironment().getProperty(key, defaultValue); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java new file mode 100644 index 00000000..b0aee0f4 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ReflectionUtils.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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 java.lang.reflect.Field; + +/** + * Reflection Utils + * + * @author Haotian Zhang + */ +public class ReflectionUtils { + + public static Object getFieldValue(Object instance, String fieldName) { + Field field = org.springframework.util.ReflectionUtils.findField(instance.getClass(), fieldName); + + field.setAccessible(true); + try { + return field.get(instance); + } catch (IllegalAccessException e) { + // ignore + } finally { + field.setAccessible(false); + } + return null; + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServer.java b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServer.java new file mode 100644 index 00000000..eeb8c74b --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServer.java @@ -0,0 +1,109 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.pojo; + +import com.google.common.base.Objects; +import com.netflix.loadbalancer.Server; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.pojo.ServiceInstances; +import org.apache.commons.lang.StringUtils; + +import java.util.Map; + +/** + * Polaris implementation of {@link Server} + * + * @author Haotian Zhang + */ +public class PolarisServer extends Server { + + private final ServiceInstances serviceInstances; + + private final Instance instance; + + private final MetaInfo metaInfo; + + public PolarisServer(ServiceInstances serviceInstances, Instance instance) { + super(instance.getHost(), instance.getPort()); + if (StringUtils.equalsIgnoreCase(instance.getProtocol(), "https")) { + setSchemea("https"); + } else { + setSchemea("http"); + } + this.serviceInstances = serviceInstances; + this.instance = instance; + this.metaInfo = new MetaInfo() { + @Override + public String getAppName() { + return instance.getService(); + } + + @Override + public String getServerGroup() { + return null; + } + + @Override + public String getServiceIdForDiscovery() { + return instance.getService(); + } + + @Override + public String getInstanceId() { + return instance.getId(); + } + }; + } + + public Instance getInstance() { + return instance; + } + + @Override + public MetaInfo getMetaInfo() { + return metaInfo; + } + + public Map getMetadata() { + return instance.getMetadata(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + PolarisServer that = (PolarisServer) o; + return Objects.equal(instance, that.instance); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), instance); + } + + public ServiceInstances getServiceInstances() { + return serviceInstances; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServiceInstance.java b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServiceInstance.java new file mode 100644 index 00000000..056b063f --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/polaris/pojo/PolarisServiceInstance.java @@ -0,0 +1,90 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.pojo; + +import com.tencent.polaris.api.pojo.Instance; +import org.apache.commons.lang.StringUtils; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; + +import java.net.URI; +import java.util.Map; + +/** + * Polaris implementation of {@link ServiceInstance} + * + * @author Haotian Zhang + */ +public class PolarisServiceInstance implements ServiceInstance { + + private final Instance instance; + + private final boolean isSecure; + + private final String scheme; + + public PolarisServiceInstance(Instance instance) { + this.instance = instance; + this.isSecure = StringUtils.equalsIgnoreCase(instance.getProtocol(), "https"); + if (isSecure) { + scheme = "https"; + } else { + scheme = "http"; + } + } + + @Override + public String getInstanceId() { + return ServiceInstance.super.getInstanceId(); + } + + @Override + public String getServiceId() { + return instance.getService(); + } + + @Override + public String getHost() { + return instance.getHost(); + } + + @Override + public int getPort() { + return instance.getPort(); + } + + @Override + public boolean isSecure() { + return this.isSecure; + } + + @Override + public URI getUri() { + return DefaultServiceInstance.getUri(this); + } + + @Override + public Map getMetadata() { + return instance.getMetadata(); + } + + @Override + public String getScheme() { + return this.scheme; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..7e757f5e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.common.util.ApplicationContextAwareUtils + diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/pom.xml b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/pom.xml new file mode 100644 index 00000000..fdfe663c --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/pom.xml @@ -0,0 +1,49 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-feign + Spring Cloud Tencent Feign + + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + + org.springframework.boot + spring-boot + true + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeign.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeign.java new file mode 100644 index 00000000..d350f3be --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeign.java @@ -0,0 +1,163 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import com.tencent.cloud.common.util.ReflectionUtils; +import feign.Contract; +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.hystrix.FallbackFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.cloud.openfeign.FeignContext; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.StringUtils; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Pluggable Feign builder. + * + * @author Haotian Zhang + */ +public class PluggableFeign { + + private static final Logger LOG = LoggerFactory.getLogger(PluggableFeign.class); + + private PluggableFeign() { + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends Feign.Builder implements ApplicationContextAware { + + private Contract contract = new Contract.Default(); + + private ApplicationContext applicationContext; + + private FeignContext feignContext; + + @Override + public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) { + throw new UnsupportedOperationException(); + } + + @Override + public Builder contract(Contract contract) { + this.contract = contract; + return this; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + feignContext = applicationContext.getBean(FeignContext.class); + } + + @Override + public Feign build() { + super.invocationHandlerFactory(new InvocationHandlerFactory() { + @Override + public InvocationHandler create(Target target, Map dispatch) { + Object feignClientFactoryBean = applicationContext.getBean("&" + target.type().getName()); + + Class fallback = (Class) ReflectionUtils.getFieldValue(feignClientFactoryBean, "fallback"); + Class fallbackFactory = (Class) ReflectionUtils.getFieldValue(feignClientFactoryBean, + "fallbackFactory"); + String beanName = (String) ReflectionUtils.getFieldValue(feignClientFactoryBean, "contextId"); + if (!StringUtils.hasText(beanName)) { + beanName = (String) ReflectionUtils.getFieldValue(feignClientFactoryBean, "name"); + } + + Object fallbackInstance; + FallbackFactory fallbackFactoryInstance; + + // Get FeignPlugins + List pluggableFeignPlugins = getSortedFeignPrePlugins(); + + if (void.class != fallback) { + fallbackInstance = getFallbackInstanceFromContext(beanName, "fallback", fallback, + target.type()); + return new PluggableFeignInvocationHandler(target, dispatch, + new FallbackFactory.Default<>(fallbackInstance), pluggableFeignPlugins); + } + + if (void.class != fallbackFactory) { + fallbackFactoryInstance = (FallbackFactory) getFallbackInstanceFromContext(beanName, + "fallbackFactory", + fallbackFactory, FallbackFactory.class); + return new PluggableFeignInvocationHandler(target, dispatch, fallbackFactoryInstance, + pluggableFeignPlugins); + } + + return new PluggableFeignInvocationHandler(target, dispatch, null, pluggableFeignPlugins); + } + + private Object getFallbackInstanceFromContext(String name, String type, Class fallbackType, + Class targetType) { + if (feignContext == null) { + feignContext = applicationContext.getBean(FeignContext.class); + } + Object fallbackInstance = feignContext.getInstance(name, fallbackType); + if (fallbackInstance == null) { + throw new IllegalStateException(String.format("No %s instance of type %s found for feign " + + "client %s", + type, fallbackType, name)); + } + + if (!targetType.isAssignableFrom(fallbackType)) { + throw new IllegalStateException(String.format( + "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to " + + "%s for feign client %s", + type, fallbackType, targetType, name)); + } + return fallbackInstance; + } + + /** + * Ascending, which means the lower order number, the earlier executing the feign plugin. + * + * @return sorted feign pre plugin list + */ + private List getSortedFeignPrePlugins() { + Map feignPrePluginMap = + applicationContext.getBeansOfType(PluggableFeignPlugin.class); + return new ArrayList<>(feignPrePluginMap.values()) + .stream() + .sorted(Comparator.comparing(PluggableFeignPlugin::getOrder)) + .collect(Collectors.toList()); + } + }); + + super.contract(new PluggableFeignContractHolder(contract)); + return super.build(); + } + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignAutoConfiguration.java new file mode 100644 index 00000000..87c1090c --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import feign.Feign; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Pluggable Feign Auto Configuration. + * + * @author Haotian Zhang + */ +@Configuration +public class PluggableFeignAutoConfiguration { + + @Configuration + @ConditionalOnProperty(name = "com.tencent.cloud.feign.enabled", matchIfMissing = true) + static class PluggableFeignConfig { + + @Bean + @ConditionalOnMissingBean + public Feign.Builder pluggableFeignBuilder() { + return PluggableFeign.builder(); + } + + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContext.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContext.java new file mode 100644 index 00000000..57484600 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContext.java @@ -0,0 +1,124 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import feign.FeignException; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.hystrix.FallbackFactory; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * Context used by PluggableFeign + * + * @author Haotian Zhang + */ +public class PluggableFeignContext { + + private Target target; + + private Map dispatch; + + private FallbackFactory fallbackFactory; + + private Map fallbackMethodMap; + + private Object proxy; + + private Method method; + + private Object[] args; + + private FeignException feignException; + + private Object result; + + public Target getTarget() { + return target; + } + + public void setTarget(Target target) { + this.target = target; + } + + public Map getDispatch() { + return dispatch; + } + + public void setDispatch(Map dispatch) { + this.dispatch = dispatch; + } + + public FallbackFactory getFallbackFactory() { + return fallbackFactory; + } + + public void setFallbackFactory(FallbackFactory fallbackFactory) { + this.fallbackFactory = fallbackFactory; + } + + public Map getFallbackMethodMap() { + return fallbackMethodMap; + } + + public void setFallbackMethodMap(Map fallbackMethodMap) { + this.fallbackMethodMap = fallbackMethodMap; + } + + public Object getProxy() { + return proxy; + } + + public void setProxy(Object proxy) { + this.proxy = proxy; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + + public Object[] getArgs() { + return args; + } + + public void setArgs(Object[] args) { + this.args = args; + } + + public FeignException getFeignException() { + return feignException; + } + + public void setFeignException(FeignException feignException) { + this.feignException = feignException; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContractHolder.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContractHolder.java new file mode 100644 index 00000000..9b246d35 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignContractHolder.java @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import feign.Contract; +import feign.MethodMetadata; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Contract for PluggableFeign + * + * @author Haotian Zhang + */ +public class PluggableFeignContractHolder implements Contract { + + private final Contract delegate; + + /** + * Key of metadata is full name of method including full name of class, name of method and types of parameters. + */ + public static final Map METHOD_METADATA = new HashMap<>(); + + public PluggableFeignContractHolder(Contract delegate) { + this.delegate = delegate; + } + + @Override + public List parseAndValidateMetadata(Class targetType) { + List metadataList = delegate.parseAndValidateMetadata(targetType); + metadataList.forEach(metadata -> + METHOD_METADATA.put(targetType.getPackage().getName() + "." + metadata.configKey(), metadata)); + return metadataList; + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignInvocationHandler.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignInvocationHandler.java new file mode 100644 index 00000000..f05d4a4f --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignInvocationHandler.java @@ -0,0 +1,172 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import feign.FeignException; +import feign.InvocationHandlerFactory; +import feign.Target; +import feign.hystrix.FallbackFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static feign.Util.checkNotNull; + +/** + * InvocationHandler used by PluggableFeign. + * + * @author Haotian Zhang + */ +public class PluggableFeignInvocationHandler implements InvocationHandler { + + private static final Logger LOG = LoggerFactory.getLogger(PluggableFeignInvocationHandler.class); + + private final Target target; + + private final Map dispatch; + + private FallbackFactory fallbackFactory; + + private Map fallbackMethodMap; + + private List prePluggableFeignPlugins; + + private List postPluggableFeignPlugins; + + private List exceptionPluggableFeignPlugins; + + PluggableFeignInvocationHandler(Target target, Map dispatch, + FallbackFactory fallbackFactory, List pluggableFeignPlugins) { + this.target = checkNotNull(target, "target"); + this.dispatch = checkNotNull(dispatch, "dispatch"); + this.fallbackFactory = fallbackFactory; + this.fallbackMethodMap = toFallbackMethod(dispatch); + + this.prePluggableFeignPlugins = new ArrayList<>(); + this.postPluggableFeignPlugins = new ArrayList<>(); + this.exceptionPluggableFeignPlugins = new ArrayList<>(); + for (PluggableFeignPlugin feignPlugin : pluggableFeignPlugins) { + if (feignPlugin.getType().equals(PluggableFeignPluginType.PRE)) { + prePluggableFeignPlugins.add(feignPlugin); + } else if (feignPlugin.getType().equals(PluggableFeignPluginType.POST)) { + postPluggableFeignPlugins.add(feignPlugin); + } else if (feignPlugin.getType().equals(PluggableFeignPluginType.EXCEPTION)) { + exceptionPluggableFeignPlugins.add(feignPlugin); + } + } + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("equals".equals(method.getName())) { + try { + Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; + return equals(otherHandler); + } catch (IllegalArgumentException e) { + return false; + } + } else if ("hashCode".equals(method.getName())) { + return hashCode(); + } else if ("toString".equals(method.getName())) { + return toString(); + } + + Object result = null; + PluggableFeignContext context = new PluggableFeignContext(); + try { + context.setTarget(target); + context.setDispatch(dispatch); + context.setFallbackFactory(fallbackFactory); + context.setFallbackMethodMap(fallbackMethodMap); + context.setProxy(proxy); + context.setMethod(method); + context.setArgs(args); + + // executing pre plugins + for (PluggableFeignPlugin prePlugin : this.prePluggableFeignPlugins) { + prePlugin.run(context); + } + + result = this.dispatch.get(method).invoke(args); + + context.setResult(result); + + // executing post plugins + for (PluggableFeignPlugin postPlugin : this.postPluggableFeignPlugins) { + postPlugin.run(context); + } + } catch (Throwable throwable) { + if (throwable.getCause() instanceof FeignException) { + context.setFeignException((FeignException) throwable.getCause()); + } + + // executing exception plugins + for (PluggableFeignPlugin exceptionPlugin : this.exceptionPluggableFeignPlugins) { + exceptionPlugin.run(context); + } + + // executing fallback logic + if (this.fallbackFactory != null) { + return this.fallbackMethodMap.get(method).invoke(fallbackFactory.create(throwable), args); + } else { + LOG.error("FallbackFactory is null!"); + throw throwable; + } + } + + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PluggableFeignInvocationHandler) { + PluggableFeignInvocationHandler other = (PluggableFeignInvocationHandler) obj; + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + + static Map toFallbackMethod(Map dispatch) { + Map result = new LinkedHashMap<>(); + if (!CollectionUtils.isEmpty(dispatch)) { + for (Method method : dispatch.keySet()) { + method.setAccessible(true); + result.put(method, method); + } + } + return result; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPlugin.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPlugin.java new file mode 100644 index 00000000..add79d7b --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPlugin.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +import org.springframework.core.Ordered; + +/** + * Pre plugin used by PluggableFeign. + * + * @author Haotian Zhang + */ +public interface PluggableFeignPlugin extends Ordered { + + /** + * Get name of plugin + * + * @return + */ + String getName(); + + /** + * Get type of plugin + * @see PluggableFeignPluginType + * + * @return + */ + PluggableFeignPluginType getType(); + + /** + * Run the plugin + */ + void run(PluggableFeignContext context); + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPluginType.java b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPluginType.java new file mode 100644 index 00000000..b21bc192 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/java/com/tencent/cloud/feign/PluggableFeignPluginType.java @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.feign; + +/** + * Type of PluggableFeign. + * + * @author Haotian Zhang + */ +public enum PluggableFeignPluginType { + + /** + * Pre feign plugin + */ + PRE, + + /** + * Post feign plugin + */ + POST, + + /** + * Exception feign plugin + */ + EXCEPTION + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..16838cb6 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "com.tencent.cloud.feign.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "If feign is enabled." + } + ] +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..107c6ed5 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-feign/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.feign.PluggableFeignAutoConfiguration diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/pom.xml b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/pom.xml new file mode 100644 index 00000000..e326c6bd --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/pom.xml @@ -0,0 +1,89 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-metadata + Spring Cloud Tencent Metadata + + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + com.tencent.cloud + spring-cloud-tencent-feign + + + + + org.springframework.boot + spring-boot + true + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.boot + spring-boot-starter-web + true + + + + org.springframework.boot + spring-boot-starter-webflux + true + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + org.springframework.boot + spring-boot-starter-test + test + true + + + + + org.powermock + powermock-module-junit4 + test + true + + + + + org.powermock + powermock-api-mockito2 + test + true + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataConfiguration.java new file mode 100644 index 00000000..ef4bc2eb --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataConfiguration.java @@ -0,0 +1,187 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.config; + +import com.tencent.cloud.feign.PluggableFeign; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.core.filter.MetadataReactiveFilter; +import com.tencent.cloud.metadata.core.filter.MetadataServletFilter; +import com.tencent.cloud.metadata.core.interceptor.feign.Metadata2HeaderFeignInterceptor; +import com.tencent.cloud.metadata.core.interceptor.resttemplate.MetadataRestTemplateInterceptor; +import com.tencent.cloud.metadata.core.plugin.feign.MetadataFirstFeignPlugin; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.util.CollectionUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +import static javax.servlet.DispatcherType.ASYNC; +import static javax.servlet.DispatcherType.ERROR; +import static javax.servlet.DispatcherType.FORWARD; +import static javax.servlet.DispatcherType.INCLUDE; +import static javax.servlet.DispatcherType.REQUEST; + +/** + * Metadata Configuration + * + * @author Haotian Zhang + */ +@Configuration +public class MetadataConfiguration { + + /** + * metadata properties. + */ + @Bean + public MetadataLocalProperties metadataLocalProperties() { + return new MetadataLocalProperties(); + } + + /** + * Create when web application type is SERVLET. + */ + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + static class MetadataServletFilterConfig { + + @Bean + public FilterRegistrationBean + metadataServletFilterRegistrationBean(MetadataServletFilter metadataServletFilter) { + FilterRegistrationBean filterRegistrationBean = + new FilterRegistrationBean<>(metadataServletFilter); + filterRegistrationBean.setDispatcherTypes(ASYNC, ERROR, FORWARD, INCLUDE, REQUEST); + filterRegistrationBean.setOrder(MetadataConstant.OrderConstant.FILTER_ORDER); + return filterRegistrationBean; + } + + @Bean + public MetadataServletFilter metadataServletFilter(MetadataLocalProperties metadataLocalProperties) { + return new MetadataServletFilter(metadataLocalProperties); + } + } + + /** + * Create when web application type is REACTIVE. + */ + @Configuration + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class MetadataReactiveFilterConfig { + + @Bean + public MetadataReactiveFilter metadataReactiveFilter(MetadataLocalProperties metadataLocalProperties) { + return new MetadataReactiveFilter(metadataLocalProperties); + } + } + + /** + * Create when Feign exists. + */ + @Configuration + @ConditionalOnClass(PluggableFeign.Builder.class) + static class MetadataFeignPluginConfig { + + @Bean + public MetadataFirstFeignPlugin metadataFirstFeignPlugin() { + return new MetadataFirstFeignPlugin(); + } + + @Bean + public Metadata2HeaderFeignInterceptor metadataFeignInterceptor() { + return new Metadata2HeaderFeignInterceptor(); + } + } + + /** + * Create when RestTemplate exists. + */ + @Configuration + @ConditionalOnClass(RestTemplate.class) + static class MetadataRestTemplateConfig implements ApplicationContextAware { + + private ApplicationContext context; + + @Bean + public MetadataRestTemplateInterceptor metadataRestTemplateInterceptor() { + return new MetadataRestTemplateInterceptor(); + } + + @Bean + BeanPostProcessor metadataRestTemplatePostProcessor( + MetadataRestTemplateInterceptor metadataRestTemplateInterceptor) { + // Coping with multiple bean injection scenarios + Map beans = this.context.getBeansOfType(RestTemplate.class); + // If the restTemplate has been created when the MetadataRestTemplatePostProcessor Bean + // is initialized, then manually set the interceptor. + if (!CollectionUtils.isEmpty(beans)) { + for (RestTemplate restTemplate : beans.values()) { + List interceptors = restTemplate.getInterceptors(); + // Avoid setting interceptor repeatedly. + if (null != interceptors && !interceptors.contains(metadataRestTemplateInterceptor)) { + interceptors.add(metadataRestTemplateInterceptor); + restTemplate.setInterceptors(interceptors); + } + } + } + return new MetadataRestTemplatePostProcessor(metadataRestTemplateInterceptor); + } + + public static class MetadataRestTemplatePostProcessor implements BeanPostProcessor { + + private MetadataRestTemplateInterceptor metadataRestTemplateInterceptor; + + MetadataRestTemplatePostProcessor( + MetadataRestTemplateInterceptor metadataRestTemplateInterceptor) { + this.metadataRestTemplateInterceptor = metadataRestTemplateInterceptor; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof RestTemplate) { + RestTemplate restTemplate = (RestTemplate) bean; + List interceptors = restTemplate.getInterceptors(); + // Avoid setting interceptor repeatedly. + if (null != interceptors && !interceptors.contains(metadataRestTemplateInterceptor)) { + interceptors.add(this.metadataRestTemplateInterceptor); + restTemplate.setInterceptors(interceptors); + } + } + return bean; + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = applicationContext; + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataLocalProperties.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataLocalProperties.java new file mode 100644 index 00000000..459c9f78 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/config/MetadataLocalProperties.java @@ -0,0 +1,67 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Metadata Properties from local properties file. + * + * @author Haotian Zhang + */ +@ConfigurationProperties(prefix = "spring.cloud.tencent.metadata") +public class MetadataLocalProperties { + + /** + * metadata content. + */ + private Map content; + + /** + * transitive metadata key list. + */ + private List transitive; + + public Map getContent() { + if (CollectionUtils.isEmpty(content)) { + content = new HashMap<>(); + } + return content; + } + + public void setContent(Map content) { + this.content = content; + } + + public List getTransitive() { + if (CollectionUtils.isEmpty(transitive)) { + transitive = new ArrayList<>(); + } + return transitive; + } + + public void setTransitive(List transitive) { + this.transitive = transitive; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/constant/MetadataConstant.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/constant/MetadataConstant.java new file mode 100644 index 00000000..88e6a27a --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/constant/MetadataConstant.java @@ -0,0 +1,111 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.constant; + +import org.springframework.core.Ordered; + +/** + * Constant for spring-cloud-tencent-metadata. + * + * @author Haotian Zhang + */ +public interface MetadataConstant { + + /** + * Metadata HTTP header name. + */ + interface HeaderName { + + /** + * Custom metadata + */ + String CUSTOM_METADATA = "SCT-CUSTOM-METADATA"; + + /** + * System Metadata + */ + String SYSTEM_METADATA = "SCT-SYSTEM-METADATA"; + + /** + * Metadata context + */ + String METADATA_CONTEXT = "SCT-METADATA-CONTEXT"; + } + + /** + * Order of filter, interceptor, ... + */ + interface OrderConstant { + + /** + * Order of filter + */ + int FILTER_ORDER = Ordered.HIGHEST_PRECEDENCE + 3; + + /** + * Order of MetadataFirstFeignPlugin + */ + int METADATA_FIRST_FEIGN_PLUGIN_ORDER = Ordered.HIGHEST_PRECEDENCE + 1; + + /** + * Order of Metadata2HeaderFeignInterceptor + */ + int METADATA_2_HEADER_FEIGN_INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE; + + /** + * Order of interceptor + */ + int INTERCEPTOR_ORDER = Ordered.LOWEST_PRECEDENCE; + } + + /** + * System metadata key + */ + interface SystemMetadataKey { + + /** + * Local namespace + */ + String LOCAL_NAMESPACE = "LOCAL_NAMESPACE"; + + /** + * Local service + */ + String LOCAL_SERVICE = "LOCAL_SERVICE"; + + /** + * Local path + */ + String LOCAL_PATH = "LOCAL_PATH"; + + /** + * Peer namespace + */ + String PEER_NAMESPACE = "PEER_NAMESPACE"; + + /** + * Peer service + */ + String PEER_SERVICE = "PEER_SERVICE"; + + /** + * Peer path + */ + String PEER_PATH = "PEER_PATH"; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContext.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContext.java new file mode 100644 index 00000000..61f28ffe --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContext.java @@ -0,0 +1,77 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.context; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Transitive Metadata Context + * + * @author Haotian Zhang + */ +public class MetadataContext { + + /** + * Transitive custom metadata content + */ + private Map transitiveCustomMetadata; + + /** + * System metadata content + */ + private Map systemMetadata; + + public MetadataContext() { + this.transitiveCustomMetadata = new HashMap<>(); + this.systemMetadata = new HashMap<>(); + } + + public Map getAllTransitiveCustomMetadata() { + return Collections.unmodifiableMap(this.transitiveCustomMetadata); + } + + public String getTransitiveCustomMetadata(String key) { + return this.transitiveCustomMetadata.get(key); + } + + public void putTransitiveCustomMetadata(String key, String value) { + this.transitiveCustomMetadata.put(key, value); + } + + public void putAllTransitiveCustomMetadata(Map customMetadata) { + this.transitiveCustomMetadata.putAll(customMetadata); + } + + public Map getAllSystemMetadata() { + return Collections.unmodifiableMap(this.systemMetadata); + } + + public String getSystemMetadata(String key) { + return this.systemMetadata.get(key); + } + + public void putSystemMetadata(String key, String value) { + this.systemMetadata.put(key, value); + } + + public void putAllSystemMetadata(Map systemMetadata) { + this.systemMetadata.putAll(systemMetadata); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContextHolder.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContextHolder.java new file mode 100644 index 00000000..50f85d12 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/context/MetadataContextHolder.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.context; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.common.util.ApplicationContextAwareUtils; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Metadata Context Holder + * + * @author Haotian Zhang + */ +public class MetadataContextHolder { + + private static final ThreadLocal METADATA_CONTEXT = new InheritableThreadLocal<>(); + + private static MetadataLocalProperties metadataLocalProperties; + + public static final String LOCAL_NAMESPACE = ApplicationContextAwareUtils.getProperties("spring.cloud" + + ".polaris.discovery.namespace", "Production"); + + private static final String LOCAL_SPRING_APPLICATION_NAME = + ApplicationContextAwareUtils.getProperties("spring.application.name", null); + + public static final String LOCAL_SERVICE = ApplicationContextAwareUtils.getProperties("spring.cloud.polaris" + + ".discovery.service", LOCAL_SPRING_APPLICATION_NAME); + + /** + * Get metadata context. + * Create if not existing. + * + * @return METADATA_CONTEXT + */ + public static MetadataContext get() { + if (null == METADATA_CONTEXT.get()) { + MetadataContext metadataContext = new MetadataContext(); + if (metadataLocalProperties == null) { + metadataLocalProperties = (MetadataLocalProperties) ApplicationContextAwareUtils + .getApplicationContext().getBean("metadataLocalProperties"); + } + + // init custom metadata and load local metadata + Map transitiveMetadataMap = getTransitiveMetadataMap(metadataLocalProperties.getContent(), + metadataLocalProperties.getTransitive()); + metadataContext.putAllTransitiveCustomMetadata(transitiveMetadataMap); + + // init system metadata + metadataContext.putSystemMetadata(MetadataConstant.SystemMetadataKey.LOCAL_NAMESPACE, + LOCAL_NAMESPACE); + metadataContext.putSystemMetadata(MetadataConstant.SystemMetadataKey.LOCAL_SERVICE, + LOCAL_SERVICE); + + METADATA_CONTEXT.set(metadataContext); + } + return METADATA_CONTEXT.get(); + } + + /** + * Filter and store the transitive metadata to transitive metadata context. + * + * @param source + * @param transitiveMetadataKeyList + * @return result + */ + private static Map getTransitiveMetadataMap(Map source, + List transitiveMetadataKeyList) { + Map result = new HashMap<>(); + for (String key : transitiveMetadataKeyList) { + if (source.containsKey(key)) { + result.put(key, source.get(key)); + } + } + return result; + } + + /** + * Set metadata context. + * + * @param metadataContext + */ + public static void set(MetadataContext metadataContext) { + METADATA_CONTEXT.set(metadataContext); + } + + /** + * Save metadata map to thread local. + * + * @param customMetadataMap + * @param systemMetadataMap + */ + public static void init(Map customMetadataMap, Map systemMetadataMap) { + // Init ThreadLocal. + MetadataContextHolder.remove(); + MetadataContext metadataContext = MetadataContextHolder.get(); + + // Save to ThreadLocal. + if (!CollectionUtils.isEmpty(customMetadataMap)) { + metadataContext.putAllTransitiveCustomMetadata(customMetadataMap); + } + if (!CollectionUtils.isEmpty(systemMetadataMap)) { + metadataContext.putAllSystemMetadata(systemMetadataMap); + } + MetadataContextHolder.set(metadataContext); + } + + /** + * Remove metadata context. + */ + public static void remove() { + METADATA_CONTEXT.remove(); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilter.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilter.java new file mode 100644 index 00000000..a4c50e51 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilter.java @@ -0,0 +1,101 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.filter; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.metadata.util.JacksonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; + +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_NAMESPACE; +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_PATH; +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_SERVICE; + +/** + * Filter used for storing the metadata from upstream temporarily when web application is REACTIVE. + * + * @author Haotian Zhang + */ +public class MetadataReactiveFilter implements WebFilter, Ordered { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataReactiveFilter.class); + + private MetadataLocalProperties metadataLocalProperties; + + public MetadataReactiveFilter(MetadataLocalProperties metadataLocalProperties) { + this.metadataLocalProperties = metadataLocalProperties; + } + + @Override + public int getOrder() { + return MetadataConstant.OrderConstant.FILTER_ORDER; + } + + @Override + public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { + // Get metadata string from http header. + ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest(); + HttpHeaders httpHeaders = serverHttpRequest.getHeaders(); + String customMetadataStr = httpHeaders.getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA); + try { + if (StringUtils.hasText(customMetadataStr)) { + customMetadataStr = URLDecoder.decode(customMetadataStr, "UTF-8"); + } + } catch (UnsupportedEncodingException e) { + LOG.error("Runtime system does not support utf-8 coding.", e); + } + LOG.debug("Get upstream metadata string: {}", customMetadataStr); + + // create custom metadata. + Map upstreamCustomMetadataMap = JacksonUtils.deserializeMetadataMap(customMetadataStr); + + // create system metadata. + Map systemMetadataMap = new HashMap<>(); + systemMetadataMap.put(LOCAL_NAMESPACE, MetadataContextHolder.LOCAL_NAMESPACE); + systemMetadataMap.put(LOCAL_SERVICE, MetadataContextHolder.LOCAL_SERVICE); + systemMetadataMap.put(LOCAL_PATH, serverHttpRequest.getURI().getRawPath()); + + try { + MetadataContextHolder.init(upstreamCustomMetadataMap, systemMetadataMap); + + // Save to ServerWebExchange. + serverWebExchange.getAttributes() + .put(MetadataConstant.HeaderName.METADATA_CONTEXT, MetadataContextHolder.get()); + return webFilterChain.filter(serverWebExchange).doFinally((type) -> MetadataContextHolder.remove()); + } catch (RuntimeException e) { + throw e; + } finally { + MetadataContextHolder.remove(); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilter.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilter.java new file mode 100644 index 00000000..f6953cb6 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilter.java @@ -0,0 +1,92 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.filter; + +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.util.JacksonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; + +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_NAMESPACE; +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_PATH; +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_SERVICE; + +/** + * Filter used for storing the metadata from upstream temporarily when web application is SERVLET. + * + * @author Haotian Zhang + */ +public class MetadataServletFilter extends OncePerRequestFilter { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataServletFilter.class); + + private MetadataLocalProperties metadataLocalProperties; + + public MetadataServletFilter(MetadataLocalProperties metadataLocalProperties) { + this.metadataLocalProperties = metadataLocalProperties; + } + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + FilterChain filterChain) throws ServletException, IOException { + // Get custom metadata string from http header. + String customMetadataStr = httpServletRequest.getHeader(MetadataConstant.HeaderName.CUSTOM_METADATA); + try { + if (StringUtils.hasText(customMetadataStr)) { + customMetadataStr = URLDecoder.decode(customMetadataStr, "UTF-8"); + } + } catch (UnsupportedEncodingException e) { + LOG.error("Runtime system does not support utf-8 coding.", e); + } + LOG.debug("Get upstream metadata string: {}", customMetadataStr); + + // create custom metadata. + Map upstreamCustomMetadataMap = JacksonUtils.deserializeMetadataMap(customMetadataStr); + + // create system metadata. + Map systemMetadataMap = new HashMap<>(); + systemMetadataMap.put(LOCAL_NAMESPACE, MetadataContextHolder.LOCAL_NAMESPACE); + systemMetadataMap.put(LOCAL_SERVICE, MetadataContextHolder.LOCAL_SERVICE); + systemMetadataMap.put(LOCAL_PATH, httpServletRequest.getRequestURI()); + + try { + MetadataContextHolder.init(upstreamCustomMetadataMap, systemMetadataMap); + + filterChain.doFilter(httpServletRequest, httpServletResponse); + } catch (IOException | ServletException | RuntimeException e) { + throw e; + } finally { + MetadataContextHolder.remove(); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/feign/Metadata2HeaderFeignInterceptor.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/feign/Metadata2HeaderFeignInterceptor.java new file mode 100644 index 00000000..0c6f0d2d --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/feign/Metadata2HeaderFeignInterceptor.java @@ -0,0 +1,80 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.interceptor.feign; + +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.util.JacksonUtils; +import com.tencent.cloud.metadata.context.MetadataContext; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.util.CollectionUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map; + +import static com.tencent.cloud.metadata.constant.MetadataConstant.HeaderName.CUSTOM_METADATA; + +/** + * Interceptor used for adding the metadata in http headers from context when web client is Feign. + * + * @author Haotian Zhang + */ +public class Metadata2HeaderFeignInterceptor implements RequestInterceptor, Ordered { + + private static final Logger LOG = LoggerFactory.getLogger(Metadata2HeaderFeignInterceptor.class); + + @Override + public int getOrder() { + return MetadataConstant.OrderConstant.METADATA_2_HEADER_FEIGN_INTERCEPTOR_ORDER; + } + + @Override + public void apply(RequestTemplate requestTemplate) { + // get metadata of current thread + MetadataContext metadataContext = MetadataContextHolder.get(); + + // add new metadata and cover old + if (!CollectionUtils.isEmpty(requestTemplate.headers()) + && !CollectionUtils.isEmpty(requestTemplate.headers().get(CUSTOM_METADATA))) { + for (String headerMetadataStr : requestTemplate.headers().get(CUSTOM_METADATA)) { + Map headerMetadataMap = JacksonUtils.deserializeMetadataMap(headerMetadataStr); + for (String key : headerMetadataMap.keySet()) { + metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key)); + } + } + } + + Map customMetadata = metadataContext.getAllTransitiveCustomMetadata(); + if (!CollectionUtils.isEmpty(customMetadata)) { + String metadataStr = JacksonUtils.serializeToJson(customMetadata); + requestTemplate.removeHeader(CUSTOM_METADATA); + try { + requestTemplate.header(CUSTOM_METADATA, + URLEncoder.encode(metadataStr, "UTF-8")); + } catch (UnsupportedEncodingException e) { + LOG.error("Set header failed.", e); + requestTemplate.header(CUSTOM_METADATA, metadataStr); + } + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/resttemplate/MetadataRestTemplateInterceptor.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/resttemplate/MetadataRestTemplateInterceptor.java new file mode 100644 index 00000000..025db778 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/interceptor/resttemplate/MetadataRestTemplateInterceptor.java @@ -0,0 +1,79 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.interceptor.resttemplate; + +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContext; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.metadata.util.JacksonUtils; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Interceptor used for adding the metadata in http headers from context when web client is RestTemplate. + * + * @author Haotian Zhang + */ +public class MetadataRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataRestTemplateInterceptor.class); + + @Override + public int getOrder() { + return MetadataConstant.OrderConstant.INTERCEPTOR_ORDER; + } + + @Override + public ClientHttpResponse intercept(HttpRequest httpRequest, + byte[] bytes, + ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { + // get metadata of current thread + MetadataContext metadataContext = MetadataContextHolder.get(); + + // add new metadata and cover old + String metadataStr = httpRequest.getHeaders().getFirst(MetadataConstant.HeaderName.CUSTOM_METADATA); + if (!StringUtils.isEmpty(metadataStr)) { + Map headerMetadataMap = JacksonUtils.deserializeMetadataMap(metadataStr); + for (String key : headerMetadataMap.keySet()) { + metadataContext.putTransitiveCustomMetadata(key, headerMetadataMap.get(key)); + } + } + Map customMetadata = metadataContext.getAllTransitiveCustomMetadata(); + if (!CollectionUtils.isEmpty(customMetadata)) { + metadataStr = JacksonUtils.serializeToJson(customMetadata); + try { + httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, + URLEncoder.encode(metadataStr, "UTF-8")); + } catch (UnsupportedEncodingException e) { + httpRequest.getHeaders().set(MetadataConstant.HeaderName.CUSTOM_METADATA, metadataStr); + } + } + return clientHttpRequestExecution.execute(httpRequest, bytes); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/plugin/feign/MetadataFirstFeignPlugin.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/plugin/feign/MetadataFirstFeignPlugin.java new file mode 100644 index 00000000..98d5d010 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/core/plugin/feign/MetadataFirstFeignPlugin.java @@ -0,0 +1,79 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.plugin.feign; + +import com.tencent.cloud.feign.PluggableFeignContext; +import com.tencent.cloud.feign.PluggableFeignContractHolder; +import com.tencent.cloud.feign.PluggableFeignPlugin; +import com.tencent.cloud.feign.PluggableFeignPluginType; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContext; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import feign.Feign; +import feign.MethodMetadata; +import feign.RequestTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Plugin used for adding the metadata in http headers from context when web client is Feign. + * + * @author Haotian Zhang + */ +public class MetadataFirstFeignPlugin implements PluggableFeignPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(MetadataFirstFeignPlugin.class); + + @Override + public int getOrder() { + return MetadataConstant.OrderConstant.METADATA_FIRST_FEIGN_PLUGIN_ORDER; + } + + @Override + public String getName() { + return MetadataFirstFeignPlugin.class.getName(); + } + + @Override + public PluggableFeignPluginType getType() { + return PluggableFeignPluginType.PRE; + } + + @Override + public void run(PluggableFeignContext context) { + if (context.getTarget() != null && context.getMethod() != null) { + MethodMetadata methodMetadata = PluggableFeignContractHolder.METHOD_METADATA + .get(context.getTarget().type().getPackage().getName() + "." + + Feign.configKey(context.getTarget().type(), context.getMethod())); + if (methodMetadata == null) { + return; + } + RequestTemplate requestTemplate = methodMetadata.template(); + // get metadata of current thread + MetadataContext metadataContext = MetadataContextHolder.get(); + + // TODO 对端命名空间暂时与本地命名空间相同 + MetadataContextHolder.get().putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_NAMESPACE, + metadataContext.getSystemMetadata(MetadataConstant.SystemMetadataKey.LOCAL_NAMESPACE)); + MetadataContextHolder.get().putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_SERVICE, + context.getTarget().name()); + MetadataContextHolder.get().putSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_PATH, + requestTemplate.path()); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/JacksonUtils.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/JacksonUtils.java new file mode 100644 index 00000000..010c893e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/JacksonUtils.java @@ -0,0 +1,74 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Haotian Zhang + */ +public class JacksonUtils { + + private static final Logger LOG = LoggerFactory.getLogger(JacksonUtils.class); + + /** + * Object Mapper + */ + public static final ObjectMapper OM = new ObjectMapper(); + + /** + * Object to Json + * + * @param object + * @param type of object + * @return Json String + */ + public static String serializeToJson(T object) { + try { + return OM.writeValueAsString(object); + } catch (JsonProcessingException e) { + LOG.error("Object to Json failed. {}", object, e); + throw new RuntimeException("Object to Json failed.", e); + } + } + + /** + * Json to Map + * + * @param jsonStr Json String + * @return Map + */ + public static Map deserializeMetadataMap(String jsonStr) { + try { + if (StringUtils.hasText(jsonStr)) { + return OM.readValue(jsonStr, Map.class); + } + return new HashMap<>(); + } catch (JsonProcessingException e) { + LOG.error("Json to map failed. {}", jsonStr, e); + throw new RuntimeException("Json to map failed.", e); + } + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/MetadataUtils.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/MetadataUtils.java new file mode 100644 index 00000000..f2f85fcc --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/java/com/tencent/cloud/metadata/util/MetadataUtils.java @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.util; + +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * Processing metadata. + * + * @author Haotian Zhang + */ +public class MetadataUtils { + + /** + * merge metadata map and new metadata map string. + * + * @param localCustomMetadataMap + * @param newMetadataStr + * @return + */ + public static Map loadAndMergeCustomMetadata(Map localCustomMetadataMap, + String newMetadataStr) { + // Load local metadata. + Map metadataMap = new HashMap<>(); + if (!CollectionUtils.isEmpty(localCustomMetadataMap)) { + metadataMap.putAll(localCustomMetadataMap); + } + + // Transfer string to map. + if (StringUtils.hasText(newMetadataStr)) { + Map requestMetadataMap = JacksonUtils.deserializeMetadataMap(newMetadataStr); + + // metadata from upstream cover local metadata for this thread. + for (String key : requestMetadataMap.keySet()) { + metadataMap.put(key, requestMetadataMap.get(key)); + } + } + + return metadataMap; + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..390f4915 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,14 @@ +{ + "properties": [ + { + "name": "spring.cloud.tencent.metadata.content", + "type": "java.util.Map", + "description": "Custom local metadata content." + }, + { + "name": "spring.cloud.tencent.metadata.transitive", + "type": "java.util.List", + "description": "Custom transitive metadata key list." + } + ] +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..89c425fe --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.metadata.config.MetadataConfiguration + diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataConfigurationTest.java new file mode 100644 index 00000000..5b086c6e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataConfigurationTest.java @@ -0,0 +1,126 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.config; + +import com.tencent.cloud.metadata.core.filter.MetadataReactiveFilter; +import com.tencent.cloud.metadata.core.filter.MetadataServletFilter; +import com.tencent.cloud.metadata.core.interceptor.feign.Metadata2HeaderFeignInterceptor; +import com.tencent.cloud.metadata.core.interceptor.resttemplate.MetadataRestTemplateInterceptor; +import com.tencent.cloud.metadata.core.plugin.feign.MetadataFirstFeignPlugin; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; + +/** + * Test of {@link MetadataConfiguration} + * + * @author skyehtzhang + */ +public class MetadataConfigurationTest { + + private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner(); + + private final WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner(); + + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = + new ReactiveWebApplicationContextRunner(); + + /** + * No any web application. + */ + @Test + public void test1() { + this.applicationContextRunner + .withConfiguration(AutoConfigurations.of(MetadataConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); + Assertions.assertThat(context) + .doesNotHaveBean(MetadataConfiguration.MetadataReactiveFilterConfig.class); + Assertions.assertThat(context).doesNotHaveBean(MetadataReactiveFilter.class); + Assertions.assertThat(context) + .doesNotHaveBean(MetadataConfiguration.MetadataServletFilterConfig.class); + Assertions.assertThat(context).doesNotHaveBean(MetadataServletFilter.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataFeignPluginConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataFirstFeignPlugin.class); + Assertions.assertThat(context).hasSingleBean(Metadata2HeaderFeignInterceptor.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataRestTemplateConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataRestTemplateInterceptor.class); + Assertions.assertThat(context).hasSingleBean(MetadataConfiguration + .MetadataRestTemplateConfig.MetadataRestTemplatePostProcessor.class); + }); + } + + /** + * web application. + */ + @Test + public void test2() { + this.webApplicationContextRunner + .withConfiguration(AutoConfigurations.of(MetadataConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); + Assertions.assertThat(context) + .doesNotHaveBean(MetadataConfiguration.MetadataReactiveFilterConfig.class); + Assertions.assertThat(context).doesNotHaveBean(MetadataReactiveFilter.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataServletFilterConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataServletFilter.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataFeignPluginConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataFirstFeignPlugin.class); + Assertions.assertThat(context).hasSingleBean(Metadata2HeaderFeignInterceptor.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataRestTemplateConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataRestTemplateInterceptor.class); + Assertions.assertThat(context).hasSingleBean(MetadataConfiguration + .MetadataRestTemplateConfig.MetadataRestTemplatePostProcessor.class); + }); + } + + /** + * reactive web application. + */ + @Test + public void test3() { + this.reactiveWebApplicationContextRunner + .withConfiguration(AutoConfigurations.of(MetadataConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataReactiveFilterConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataReactiveFilter.class); + Assertions.assertThat(context) + .doesNotHaveBean(MetadataConfiguration.MetadataServletFilterConfig.class); + Assertions.assertThat(context).doesNotHaveBean(MetadataServletFilter.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataFeignPluginConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataFirstFeignPlugin.class); + Assertions.assertThat(context).hasSingleBean(Metadata2HeaderFeignInterceptor.class); + Assertions.assertThat(context) + .hasSingleBean(MetadataConfiguration.MetadataRestTemplateConfig.class); + Assertions.assertThat(context).hasSingleBean(MetadataRestTemplateInterceptor.class); + Assertions.assertThat(context).hasSingleBean(MetadataConfiguration + .MetadataRestTemplateConfig.MetadataRestTemplatePostProcessor.class); + }); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataLocalPropertiesTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataLocalPropertiesTest.java new file mode 100644 index 00000000..d7b1f4a4 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/config/MetadataLocalPropertiesTest.java @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.config; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test of {@link MetadataLocalProperties} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = MetadataLocalPropertiesTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class MetadataLocalPropertiesTest { + + @Autowired + private MetadataLocalProperties metadataLocalProperties; + + @Test + public void test1() { + Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); + } + + @Test + public void test2() { + Assertions.assertThat(metadataLocalProperties.getTransitive().contains("b")).isTrue(); + } + + @SpringBootApplication + protected static class TestApplication { + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/context/MetadataContextHolderTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/context/MetadataContextHolderTest.java new file mode 100644 index 00000000..ab11f778 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/context/MetadataContextHolderTest.java @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.context; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +import static com.tencent.cloud.metadata.constant.MetadataConstant.SystemMetadataKey.LOCAL_NAMESPACE; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test for {@link MetadataContextHolder} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = MetadataContextHolderTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class MetadataContextHolderTest { + + @Test + public void test1() { + Map customMetadata = new HashMap<>(); + customMetadata.put("a", "1"); + customMetadata.put("b", "2"); + MetadataContext metadataContext = MetadataContextHolder.get(); + metadataContext.putAllTransitiveCustomMetadata(customMetadata); + MetadataContextHolder.set(metadataContext); + + customMetadata = MetadataContextHolder.get().getAllTransitiveCustomMetadata(); + Assertions.assertThat(customMetadata.get("a")).isEqualTo("1"); + Assertions.assertThat(customMetadata.get("b")).isEqualTo("2"); + + MetadataContextHolder.remove(); + + customMetadata = new HashMap<>(); + customMetadata.put("a", "1"); + customMetadata.put("b", "22"); + customMetadata.put("c", "3"); + Map systemMetadata = new HashMap<>(); + systemMetadata.put(LOCAL_NAMESPACE, "namespace"); + MetadataContextHolder.init(customMetadata, systemMetadata); + metadataContext = MetadataContextHolder.get(); + customMetadata = metadataContext.getAllTransitiveCustomMetadata(); + systemMetadata = metadataContext.getAllSystemMetadata(); + Assertions.assertThat(customMetadata.get("a")).isEqualTo("1"); + Assertions.assertThat(customMetadata.get("b")).isEqualTo("22"); + Assertions.assertThat(customMetadata.get("c")).isEqualTo("3"); + Assertions.assertThat(systemMetadata.get(LOCAL_NAMESPACE)).isEqualTo("namespace"); + } + + @Test + public void test2() { + Assertions.assertThat(MetadataContextHolder.LOCAL_NAMESPACE).isEqualTo("Production"); + Assertions.assertThat(MetadataContextHolder.LOCAL_SERVICE).isEqualTo("test"); + } + + @SpringBootApplication + protected static class TestApplication { + + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilterTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilterTest.java new file mode 100644 index 00000000..2b43ff05 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataReactiveFilterTest.java @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.filter; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test of {@link MetadataReactiveFilter} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = MetadataServletFilterTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class MetadataReactiveFilterTest { + + @Autowired + private MetadataLocalProperties metadataLocalProperties; + + private MetadataReactiveFilter metadataReactiveFilter; + + @Before + public void setUp() { + this.metadataReactiveFilter = new MetadataReactiveFilter(this.metadataLocalProperties); + } + + @Test + public void test1() { + Assertions.assertThat(this.metadataReactiveFilter.getOrder()).isEqualTo( + MetadataConstant.OrderConstant.FILTER_ORDER); + } + + @Test + public void test2() { + // Create mock WebFilterChain + WebFilterChain webFilterChain = new WebFilterChain() { + @Override + public Mono filter(ServerWebExchange serverWebExchange) { + return Mono.empty(); + } + }; + + // Mock request + MockServerHttpRequest request = MockServerHttpRequest + .get("test") + .header(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}").build(); + ServerWebExchange exchange = MockServerWebExchange.from(request); + + metadataReactiveFilter.filter(exchange, webFilterChain); + Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilterTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilterTest.java new file mode 100644 index 00000000..945ff4c3 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/filter/MetadataServletFilterTest.java @@ -0,0 +1,82 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.filter; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test of {@link MetadataServletFilter} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = MetadataServletFilterTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class MetadataServletFilterTest { + + @Autowired + private MetadataLocalProperties metadataLocalProperties; + + @Autowired + private MetadataServletFilter metadataServletFilter; + + @Test + public void test1() throws ServletException, IOException { + // Create mock FilterChain + FilterChain filterChain = new FilterChain() { + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) { + + } + }; + + // Mock request + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"c\": \"3\"}"); + MockHttpServletResponse response = new MockHttpServletResponse(); + metadataServletFilter.doFilter(request, response, filterChain); + Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); + } + + @SpringBootApplication + protected static class TestApplication { + + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/feign/Metadata2HeaderFeignInterceptorTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/feign/Metadata2HeaderFeignInterceptorTest.java new file mode 100644 index 00000000..0a88d17e --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/feign/Metadata2HeaderFeignInterceptorTest.java @@ -0,0 +1,106 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.intercepter.feign; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.metadata.core.interceptor.feign.Metadata2HeaderFeignInterceptor; +import com.tencent.cloud.metadata.util.JacksonUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; + +/** + * {@link Metadata2HeaderFeignInterceptor} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = DEFINED_PORT, + classes = Metadata2HeaderFeignInterceptorTest.TestApplication.class, + properties = {"server.port=8081", "spring.config.location = classpath:application-test.yml"}) +public class Metadata2HeaderFeignInterceptorTest { + + @Autowired + private MetadataLocalProperties metadataLocalProperties; + + @Autowired + private TestApplication.TestFeign testFeign; + + @Test + public void test1() { + String metadata = testFeign.test(); + Assertions.assertThat(metadata).isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}{\"LOCAL_SERVICE\":\"test" + + "\",\"LOCAL_PATH\":\"/test\",\"LOCAL_NAMESPACE\":\"Production\"}"); + Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a")).isEqualTo("11"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b")).isEqualTo("22"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c")).isEqualTo("33"); + } + + @SpringBootApplication + @EnableFeignClients + @RestController + protected static class TestApplication { + + @RequestMapping("/test") + public String test(@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) + throws UnsupportedEncodingException { + String systemMetadataStr = JacksonUtils.serializeToJson(MetadataContextHolder.get().getAllSystemMetadata()); + return URLDecoder.decode(customMetadataStr, "UTF-8") + systemMetadataStr; + } + + @FeignClient(name = "test-feign", url = "http://localhost:8081") + public interface TestFeign { + + @RequestMapping(value = "/test", headers = {MetadataConstant.HeaderName.CUSTOM_METADATA + "={\"a\":\"11" + + "\",\"b\":\"22\",\"c\":\"33\"}"}) + String test(); + } + + @Configuration + static class TestRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate template) { + template.header(MetadataConstant.HeaderName.CUSTOM_METADATA, + "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); + } + } + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/resttemplate/MetadataRestTemplateInterceptorTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/resttemplate/MetadataRestTemplateInterceptorTest.java new file mode 100644 index 00000000..7939c546 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/java/com/tencent/cloud/metadata/core/intercepter/resttemplate/MetadataRestTemplateInterceptorTest.java @@ -0,0 +1,101 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.metadata.core.intercepter.resttemplate; + +import com.tencent.cloud.metadata.config.MetadataLocalProperties; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.metadata.core.interceptor.resttemplate.MetadataRestTemplateInterceptor; +import com.tencent.cloud.metadata.util.JacksonUtils; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test for {@link MetadataRestTemplateInterceptor} + * + * @author skyehtzhang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = MetadataRestTemplateInterceptorTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class MetadataRestTemplateInterceptorTest { + + @Autowired + private MetadataLocalProperties metadataLocalProperties; + + @Autowired + private RestTemplate restTemplate; + + @LocalServerPort + private int localServerPort; + + @Test + public void test1() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set(MetadataConstant.HeaderName.CUSTOM_METADATA, "{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}"); + HttpEntity httpEntity = new HttpEntity<>(httpHeaders); + String metadata = restTemplate.exchange("http://localhost:" + localServerPort + "/test", + HttpMethod.GET, httpEntity, String.class).getBody(); + Assertions.assertThat(metadata).isEqualTo("{\"a\":\"11\",\"b\":\"22\",\"c\":\"33\"}{\"LOCAL_SERVICE\":\"test" + + "\",\"LOCAL_PATH\":\"/test\",\"LOCAL_NAMESPACE\":\"Production\"}"); + Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("a")).isEqualTo("11"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("b")).isEqualTo("22"); + Assertions.assertThat(MetadataContextHolder.get().getTransitiveCustomMetadata("c")).isEqualTo("33"); + } + + @SpringBootApplication + @RestController + protected static class TestApplication { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @RequestMapping("/test") + public String test(@RequestHeader(MetadataConstant.HeaderName.CUSTOM_METADATA) String customMetadataStr) + throws UnsupportedEncodingException { + String systemMetadataStr = + JacksonUtils.serializeToJson(MetadataContextHolder.get().getAllSystemMetadata()); + return URLDecoder.decode(customMetadataStr, "UTF-8") + systemMetadataStr; + } + + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/resources/application-test.yml b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/resources/application-test.yml new file mode 100644 index 00000000..f15a2267 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-metadata/src/test/resources/application-test.yml @@ -0,0 +1,11 @@ +spring: + application: + name: test + cloud: + tencent: + metadata: + content: + a: 1 + b: 2 + transitive: + - b \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/pom.xml b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/pom.xml new file mode 100644 index 00000000..07087fdd --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/pom.xml @@ -0,0 +1,131 @@ + + + + spring-cloud-tencent-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-polaris-context + Spring Cloud Tencent Polaris SDKContext + + + + + com.tencent.cloud + spring-cloud-tencent-commons + + + + + + com.tencent.nameservice + polaris-client + + + + com.tencent.nameservice + polaris-plugin-api + + + + com.tencent.nameservice + connector-polaris-grpc + + + + + com.tencent.nameservice + resource-cache-memory + + + + + com.tencent.nameservice + flow-cache-expired + + + + + com.tencent.nameservice + router-isolated + + + + com.tencent.nameservice + router-healthy + + + + com.tencent.nameservice + router-rule + + + + com.tencent.nameservice + router-nearby + + + + com.tencent.nameservice + router-metadata + + + + + com.tencent.nameservice + loadbalancer-random + + + + com.tencent.nameservice + loadbalancer-ringhash + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot + true + + + + org.springframework.boot + spring-boot-autoconfigure + true + + + + org.springframework.boot + spring-boot-starter + true + + + + org.springframework + spring-context-support + + + + org.springframework.cloud + spring-cloud-commons + + + + org.springframework.boot + spring-boot-starter-test + test + + + + \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisConfigModifier.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisConfigModifier.java new file mode 100644 index 00000000..5ab47d2c --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisConfigModifier.java @@ -0,0 +1,33 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import com.tencent.polaris.factory.config.ConfigurationImpl; + +/** + * @author Haotian Zhang + */ +public interface PolarisConfigModifier { + + /** + * 修改配置对象 + * + * @param configuration 配置对象 + */ + void modify(ConfigurationImpl configuration); +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java new file mode 100644 index 00000000..6c01dded --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import com.tencent.polaris.api.exception.PolarisException; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Configuration for Polaris {@link SDKContext} + * + * @author Haotian Zhang + */ +@EnableConfigurationProperties(PolarisContextProperties.class) +public class PolarisContextConfiguration { + + @Bean(name = "polarisContext", initMethod = "init", destroyMethod = "destroy") + @ConditionalOnMissingBean + public SDKContext polarisContext(PolarisContextProperties properties) throws PolarisException { + return SDKContext.initContextByConfig(properties.configuration()); + } + + @Bean + @ConditionalOnMissingBean + public PolarisConfigModifier polarisConfigModifier() { + return new ModifyAddress(); + } + + private static class ModifyAddress implements PolarisConfigModifier { + + @Autowired + private PolarisContextProperties properties; + + @Override + public void modify(ConfigurationImpl configuration) { + if (!StringUtils.isBlank(properties.getAddress())) { + URI uri = URI.create(properties.getAddress()); + List addresses = new ArrayList<>(); + addresses.add(uri.getAuthority()); + configuration.getGlobal().getServerConnector().setAddresses(addresses); + } + } + } + +} \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java new file mode 100644 index 00000000..e4aa2fb1 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java @@ -0,0 +1,83 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import com.tencent.polaris.api.config.Configuration; +import com.tencent.polaris.factory.ConfigAPIFactory; +import com.tencent.polaris.factory.config.ConfigurationImpl; +import java.util.Collection; +import java.util.List; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.commons.util.InetUtilsProperties; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; + +/** + * Properties for Polaris {@link com.tencent.polaris.client.api.SDKContext} + * + * @author Haotian Zhang + */ +@ConfigurationProperties(prefix = "spring.cloud.polaris") +public class PolarisContextProperties { + + /** + * polaris server 地址 + */ + private String address; + + @Autowired + private InetUtilsProperties inetUtilsProperties; + + @Autowired + private Environment environment; + + @Autowired + private List modifierList; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + protected Configuration configuration() { + ConfigurationImpl configuration = (ConfigurationImpl) ConfigAPIFactory + .defaultConfig(Configuration.DEFAULT_CONFIG_OPENSOURCE); + String defaultHost = getHost(); + configuration.getGlobal().getAPI().setBindIP(defaultHost); + Collection modifiers = modifierList; + if (!CollectionUtils.isEmpty(modifiers)) { + for (PolarisConfigModifier modifier : modifiers) { + modifier.modify(configuration); + } + } + return configuration; + } + + private String getHost() { + String defaultIpAddress = inetUtilsProperties.getDefaultIpAddress(); + if (!StringUtils.isBlank(defaultIpAddress) && !defaultIpAddress.equals("127.0.0.1")) { + return defaultIpAddress; + } + return environment.getProperty("spring.cloud.client.ip-address"); + } +} \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 00000000..9441937d --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json @@ -0,0 +1,18 @@ +{ + "groups": [ + { + "name": "spring.cloud.polaris", + "type": "com.tencent.cloud.polaris.context.PolarisContextProperties", + "sourceType": "com.tencent.cloud.polaris.context.PolarisContextProperties" + } + ], + "properties": [ + { + "name": "spring.cloud.polaris.address", + "type": "java.lang.String", + "description": "polaris server 地址", + "sourceType": "com.tencent.cloud.polaris.context.PolarisContextProperties" + } + ], + "hints": [] +} \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..f858b012 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.tencent.cloud.polaris.context.PolarisContextConfiguration \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextApplication.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextApplication.java new file mode 100644 index 00000000..6a0979c7 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextApplication.java @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PolarisContextApplication { + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextConfigurationTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextConfigurationTest.java new file mode 100644 index 00000000..dffed8f7 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextConfigurationTest.java @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import com.tencent.polaris.client.api.SDKContext; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.commons.util.UtilAutoConfiguration; + +/** + * @author liaochuntao + */ +public class PolarisContextConfigurationTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(UtilAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class)) + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:8083"); + + @Test + public void testProperties() { + contextRunner.run(context -> { + final SDKContext sdkContext = context.getBean(SDKContext.class); + Assert.assertNotNull(sdkContext); + }); + } + +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java new file mode 100644 index 00000000..53e31573 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. 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.polaris.context; + +import com.tencent.polaris.client.api.SDKContext; +import org.junit.Assert; +import org.junit.Test; +import org.junit.platform.commons.util.StringUtils; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PolarisContextApplication.class, properties = { + "spring.config.location = classpath:application-test.yml"}) +public class PolarisContextGetHostTest { + + @Autowired + private SDKContext polarisContext; + + @Test + public void testGetConfigHost() { + String bindIP = polarisContext.getConfig().getGlobal().getAPI().getBindIP(); + Assert.assertFalse(StringUtils.isBlank(bindIP)); + Assert.assertNotEquals(bindIP, "127.0.0.1"); + } +} diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml new file mode 100644 index 00000000..512acd15 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml @@ -0,0 +1,4 @@ +spring: + cloud: + polaris: + address: grpc://127.0.0.1:8091 \ No newline at end of file