diff --git a/README-zh.md b/README-zh.md index 288b85c9..d51045b7 100644 --- a/README-zh.md +++ b/README-zh.md @@ -35,7 +35,7 @@ Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。 **[RocketMQ](https://rocketmq.apache.org/)**:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 -**[Dubbo](https://github.com/apache/incubator-dubbo)**:Apache Dubbo™ (incubating) 是一款高性能 Java RPC 框架。 +**[Dubbo](https://github.com/apache/dubbo)**:Apache Dubbo™ 是一款高性能 Java RPC 框架。 **[Seata](https://github.com/seata/seata)**:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。 @@ -72,7 +72,7 @@ Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目 clone org.springframework.cloud spring-cloud-alibaba-dependencies - 0.2.1.RELEASE + 0.9.0.RELEASE pom import diff --git a/README.md b/README.md index c733e037..d816762c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ For more features, please refer to [Roadmap](https://github.com/spring-cloud-inc **[RocketMQ](https://rocketmq.apache.org/)**:A distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability. -**[Dubbo](https://github.com/apache/incubator-dubbo)**:A high-performance, Java based open source RPC framework. +**[Dubbo](https://github.com/apache/dubbo)**:A high-performance, Java based open source RPC framework. **[Seata](https://github.com/seata/seata)**:A distributed transaction solution with high performance and ease of use for microservices architecture. @@ -61,7 +61,7 @@ Spring Cloud uses Maven for most build-related activities, and you should be abl ## How to Use -### Add maven dependency +### Add maven dependency Version 0.2.1.RELEASE is compatible with the Spring Cloud Finchley. Version 0.1.1.RELEASE is compatible with the Spring Cloud Edgware. These artifacts are available from Maven Central and Spring Release repository via BOM: @@ -71,7 +71,7 @@ These artifacts are available from Maven Central and Spring Release repository v org.springframework.cloud spring-cloud-alibaba-dependencies - 0.2.1.RELEASE + 0.9.0.RELEASE pom import diff --git a/Roadmap-zh.md b/Roadmap-zh.md index 8bf69a79..b22e59ac 100644 --- a/Roadmap-zh.md +++ b/Roadmap-zh.md @@ -23,7 +23,7 @@ Apache RocketMQ™ 基于 Java 的高性能、高吞吐量的分布式消息和 **Dubbo** -Apache Dubbo™ (incubating) 是一款高性能 Java RPC 框架。 +Apache Dubbo™ 是一款高性能 Java RPC 框架。 **Fescar** diff --git a/Roadmap.md b/Roadmap.md index e7b99498..490789e1 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -26,7 +26,7 @@ Apache RocketMQ™ is an open source distributed messaging and streaming data pl **Dubbo** -Apache Dubbo™ (incubating) is a high-performance, Java based open source RPC framework. +Apache Dubbo™ is a high-performance, Java based open source RPC framework. **Fescar** diff --git a/pom.xml b/pom.xml index 4c7dc45e..b1d6042c 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ spring-cloud-alibaba-dependencies spring-cloud-alibaba-sentinel spring-cloud-alibaba-sentinel-datasource - spring-cloud-alibaba-sentinel-zuul + spring-cloud-alibaba-sentinel-gateway spring-cloud-alibaba-nacos-config spring-cloud-alibaba-nacos-discovery spring-cloud-alibaba-seata diff --git a/spring-cloud-alibaba-coverage/pom.xml b/spring-cloud-alibaba-coverage/pom.xml index b8420ca7..cad22a3c 100644 --- a/spring-cloud-alibaba-coverage/pom.xml +++ b/spring-cloud-alibaba-coverage/pom.xml @@ -13,7 +13,7 @@ Spring Cloud Alibaba Coverage - 0.9.0.BUILD-SNAPSHOT + 0.9.1.BUILD-SNAPSHOT @@ -98,4 +98,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 0de018f0..9c1c3ed0 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -17,23 +17,23 @@ Spring Cloud Alibaba Dependencies - 1.5.2 + 1.6.1 3.1.0 - 0.5.0 - 1.0.0 + 0.5.2 + 1.0.1 0.8.0 - 1.0.8 + 1.0.9 1.0.1 - 4.0.1 + 4.4.1 1.0.5 - 2.16.0 + 2.44.0 2.0.2 2.1.6 2.7.1 2.7.1 - 0.0.2 + 2.7.1 1.1.0 - 1.1.8 + 1.1.8.6 1.1.1 @@ -152,6 +152,11 @@ sentinel-zuul-adapter ${sentinel.version} + + com.alibaba.csp + sentinel-spring-cloud-gateway-adapter + ${sentinel.version} + com.alibaba.csp sentinel-transport-simple-http @@ -187,6 +192,17 @@ sentinel-cluster-client-default ${sentinel.version} + + com.alibaba.csp + sentinel-spring-webflux-adapter + ${sentinel.version} + + + com.alibaba.csp + sentinel-api-gateway-adapter-common + ${sentinel.version} + + @@ -227,7 +243,7 @@ - com.alibaba + org.apache.dubbo dubbo-registry-nacos ${dubbo-registry-nacos.version} @@ -252,7 +268,7 @@ org.springframework.cloud - spring-cloud-alibaba-sentinel-zuul + spring-cloud-alibaba-sentinel-gateway ${project.version} diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java index 3f1f376d..60c21f00 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/DefaultHttpRequest.java @@ -60,6 +60,7 @@ public class DefaultHttpRequest implements HttpRequest { return HttpMethod.resolve(getMethodValue()); } + @Override public String getMethodValue() { return method; } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/MutableHttpServerRequest.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/MutableHttpServerRequest.java index 62240f3b..1ff65e42 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/MutableHttpServerRequest.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/http/MutableHttpServerRequest.java @@ -73,6 +73,7 @@ public class MutableHttpServerRequest implements HttpServerRequest { } // Override method since Spring Framework 5.0 + @Override public String getMethodValue() { return httpMethod.name(); } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboRestServiceMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboRestServiceMetadata.java index 5c5ef3ad..bb7b4aa1 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboRestServiceMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboRestServiceMetadata.java @@ -44,8 +44,12 @@ public class DubboRestServiceMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof DubboRestServiceMetadata)) return false; + if (this == o) { + return true; + } + if (!(o instanceof DubboRestServiceMetadata)) { + return false; + } DubboRestServiceMetadata that = (DubboRestServiceMetadata) o; return Objects.equals(serviceRestMetadata, that.serviceRestMetadata) && Objects.equals(restMethodMetadata, that.restMethodMetadata); diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java index 6e7ab9ac..edb27b4c 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/DubboTransportedMethodMetadata.java @@ -77,8 +77,12 @@ public class DubboTransportedMethodMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof DubboTransportedMethodMetadata)) return false; + if (this == o) { + return true; + } + if (!(o instanceof DubboTransportedMethodMetadata)) { + return false; + } DubboTransportedMethodMetadata that = (DubboTransportedMethodMetadata) o; return Objects.equals(methodMetadata, that.methodMetadata) && Objects.equals(attributes, that.attributes); diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java index 9dfe6ea4..3a6da5b8 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodMetadata.java @@ -110,8 +110,12 @@ public class MethodMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MethodMetadata that = (MethodMetadata) o; return Objects.equals(name, that.name) && Objects.equals(returnType, that.returnType) && diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java index 0abf2b0e..1c3aaa26 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/MethodParameterMetadata.java @@ -61,8 +61,12 @@ public class MethodParameterMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MethodParameterMetadata that = (MethodParameterMetadata) o; return index == that.index && Objects.equals(name, that.name) && diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java index 29361b93..caa5e8d1 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RequestMetadata.java @@ -235,8 +235,12 @@ public class RequestMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RequestMetadata)) return false; + if (this == o) { + return true; + } + if (!(o instanceof RequestMetadata)) { + return false; + } RequestMetadata that = (RequestMetadata) o; return Objects.equals(method, that.method) && Objects.equals(path, that.path) && diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java index edbcf46a..b3055357 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/RestMethodMetadata.java @@ -183,8 +183,12 @@ public class RestMethodMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RestMethodMetadata)) return false; + if (this == o) { + return true; + } + if (!(o instanceof RestMethodMetadata)) { + return false; + } RestMethodMetadata that = (RestMethodMetadata) o; return queryMapEncoded == that.queryMapEncoded && Objects.equals(method, that.method) && diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java index 14cc3ac6..18850588 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/metadata/ServiceRestMetadata.java @@ -52,8 +52,12 @@ public class ServiceRestMetadata { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ServiceRestMetadata)) return false; + if (this == o) { + return true; + } + if (!(o instanceof ServiceRestMetadata)) { + return false; + } ServiceRestMetadata that = (ServiceRestMetadata) o; return Objects.equals(url, that.url) && Objects.equals(meta, that.meta); diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/AbstractSpringCloudRegistry.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/AbstractSpringCloudRegistry.java index b60c8247..7155ab65 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/AbstractSpringCloudRegistry.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/registry/AbstractSpringCloudRegistry.java @@ -64,7 +64,7 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry { protected static final String DUBBO_METADATA_SERVICE_CLASS_NAME = DubboMetadataService.class.getName(); - private static final Set schedulerTasks = new HashSet<>(); + private static final Set SCHEDULER_TASKS = new HashSet<>(); protected final Logger logger = LoggerFactory.getLogger(getClass()); @@ -164,7 +164,7 @@ public abstract class AbstractSpringCloudRegistry extends FailbackRegistry { private void submitSchedulerTaskIfAbsent(URL url, NotifyListener listener) { String taskId = url.toIdentityString(); - if (schedulerTasks.add(taskId)) { + if (SCHEDULER_TASKS.add(taskId)) { schedule(() -> doSubscribeDubboServiceURLs(url, listener)); } } diff --git a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/service/DubboGenericServiceFactory.java b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/service/DubboGenericServiceFactory.java index 4cfa3a22..2955df1b 100644 --- a/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/service/DubboGenericServiceFactory.java +++ b/spring-cloud-alibaba-dubbo/src/main/java/org/springframework/cloud/alibaba/dubbo/service/DubboGenericServiceFactory.java @@ -110,6 +110,7 @@ public class DubboGenericServiceFactory { dataBinder.registerCustomEditor(String.class, "listener", new StringTrimmerEditor(true)); dataBinder.registerCustomEditor(Map.class, "parameters", new PropertyEditorSupport() { + @Override public void setAsText(String text) throws java.lang.IllegalArgumentException { // Trim all whitespace String content = StringUtils.trimAllWhitespace(text); diff --git a/spring-cloud-alibaba-examples/oss-example/readme-zh.md b/spring-cloud-alibaba-examples/oss-example/readme-zh.md index 54cc88d2..bc491e1b 100644 --- a/spring-cloud-alibaba-examples/oss-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/oss-example/readme-zh.md @@ -128,7 +128,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,OSS Starter 也 Spring Boot1.x 可以通过访问 http://127.0.0.1:18084/oss 来查看 OSS Endpoint 的信息。 -Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/acutator/oss 来访问。 +Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/actuator/oss 来访问。 Endpoint 内部会显示所有的 OSSClient 配置信息,以及该 OSSClient 对应的 Bucket 列表。 diff --git a/spring-cloud-alibaba-examples/oss-example/readme.md b/spring-cloud-alibaba-examples/oss-example/readme.md index 11ea77c8..3e5fe291 100644 --- a/spring-cloud-alibaba-examples/oss-example/readme.md +++ b/spring-cloud-alibaba-examples/oss-example/readme.md @@ -114,7 +114,7 @@ You can verify results on the OSS console when you finish uploading or downloadi ## Endpoint -OSS starter also supports the implmentation of Spring Boot acutator endpoints. +OSS starter also supports the implementation of Spring Boot actuator endpoints. **Prerequisite:** @@ -127,7 +127,7 @@ To view the endpoint information, visit the following URLs: Spring Boot1.x: OSS Endpoint URL is http://127.0.0.1:18084/oss. -Spring Boot2.x: OSS Endpoint URL is http://127.0.0.1:18084/acutator/oss. +Spring Boot2.x: OSS Endpoint URL is http://127.0.0.1:18084/actuator/oss. Endpoint will show the configurations and the list of buckets of all OSSClients. diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index a5b3e948..addee644 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -21,6 +21,9 @@ sentinel-example/sentinel-dubbo-example/sentinel-dubbo-provider-example sentinel-example/sentinel-dubbo-example/sentinel-dubbo-consumer-example sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api + sentinel-example/sentinel-webflux-example + sentinel-example/sentinel-spring-cloud-gateway-example + sentinel-example/sentinel-zuul-example nacos-example/nacos-discovery-example nacos-example/nacos-config-example nacos-example/nacos-gateway-example diff --git a/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md b/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md index 5d2dd9ae..50af1a27 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/rocketmq-example/readme-zh.md @@ -60,7 +60,7 @@ public class RocketMQApplication { 配置 Binding 信息: ```properties # 配置rocketmq的nameserver地址 -spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 +spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 # 定义name为output的binding spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json @@ -125,7 +125,7 @@ server.port=28081 配置信息如下: ```properties -spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 +spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json diff --git a/spring-cloud-alibaba-examples/rocketmq-example/readme.md b/spring-cloud-alibaba-examples/rocketmq-example/readme.md index 7bdc2fbd..688a6156 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/readme.md +++ b/spring-cloud-alibaba-examples/rocketmq-example/readme.md @@ -56,7 +56,7 @@ public class RocketMQApplication { Configure Binding: ```properties # configure the nameserver of rocketmq -spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 +spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 # configure the output binding named output spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json @@ -121,7 +121,7 @@ And using two input bindings to subscribe messages. see the configuration below: ```properties -spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876 +spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876 spring.cloud.stream.bindings.output.destination=test-topic spring.cloud.stream.bindings.output.content-type=application/json diff --git a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQConsumerApplication.java b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQConsumerApplication.java index 25204919..e7a2385f 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQConsumerApplication.java +++ b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQConsumerApplication.java @@ -1,10 +1,15 @@ package org.springframework.cloud.alibaba.cloud.examples; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.alibaba.cloud.examples.RocketMQConsumerApplication.MySink; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.Input; +import org.springframework.cloud.stream.binder.PollableMessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.messaging.SubscribableChannel; /** @@ -27,10 +32,36 @@ public class RocketMQConsumerApplication { @Input("input4") SubscribableChannel input4(); + + @Input("input5") + PollableMessageSource input5(); } public static void main(String[] args) { SpringApplication.run(RocketMQConsumerApplication.class, args); } + @Bean + public ConsumerCustomRunner customRunner() { + return new ConsumerCustomRunner(); + } + + public static class ConsumerCustomRunner implements CommandLineRunner { + + @Autowired + private MySink mySink; + + @Override + public void run(String... args) throws InterruptedException { + while (true) { + mySink.input5().poll(m -> { + String payload = (String) m.getPayload(); + System.out.println("pull msg: " + payload); + }, new ParameterizedTypeReference() { + }); + Thread.sleep(2_000); + } + } + } + } diff --git a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/resources/application.properties index ec27539c..dd8bb6ef 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-consume-example/src/main/resources/application.properties @@ -24,6 +24,10 @@ spring.cloud.stream.bindings.input4.content-type=text/plain spring.cloud.stream.bindings.input4.group=transaction-group spring.cloud.stream.bindings.input4.consumer.concurrency=5 +spring.cloud.stream.bindings.input5.destination=pull-topic +spring.cloud.stream.bindings.input5.content-type=text/plain +spring.cloud.stream.bindings.input5.group=pull-topic-group + spring.application.name=rocketmq-consume-example server.port=28082 diff --git a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQProduceApplication.java b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQProduceApplication.java index bd157be0..9a55a4a4 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQProduceApplication.java +++ b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RocketMQProduceApplication.java @@ -9,6 +9,7 @@ import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.Output; import org.springframework.context.annotation.Bean; import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.MessageBuilder; /** * @author Jim @@ -23,6 +24,9 @@ public class RocketMQProduceApplication { @Output("output2") MessageChannel output2(); + + @Output("output3") + MessageChannel output3(); } public static void main(String[] args) { @@ -31,7 +35,12 @@ public class RocketMQProduceApplication { @Bean public CustomRunner customRunner() { - return new CustomRunner(); + return new CustomRunner("output1"); + } + + @Bean + public CustomRunner customRunner2() { + return new CustomRunner("output3"); } @Bean @@ -40,24 +49,45 @@ public class RocketMQProduceApplication { } public static class CustomRunner implements CommandLineRunner { + + private final String bindingName; + + public CustomRunner(String bindingName) { + this.bindingName = bindingName; + } + @Autowired private SenderService senderService; + @Autowired + private MySource mySource; + @Override public void run(String... args) throws Exception { - int count = 5; - for (int index = 1; index <= count; index++) { - String msgContent = "msg-" + index; - if (index % 3 == 0) { - senderService.send(msgContent); - } - else if (index % 3 == 1) { - senderService.sendWithTags(msgContent, "tagStr"); - } - else { - senderService.sendObject(new Foo(index, "foo"), "tagObj"); + if (this.bindingName.equals("output1")) { + int count = 5; + for (int index = 1; index <= count; index++) { + String msgContent = "msg-" + index; + if (index % 3 == 0) { + senderService.send(msgContent); + } + else if (index % 3 == 1) { + senderService.sendWithTags(msgContent, "tagStr"); + } + else { + senderService.sendObject(new Foo(index, "foo"), "tagObj"); + } } } + else if (this.bindingName.equals("output3")) { + int count = 50; + for (int index = 1; index <= count; index++) { + String msgContent = "pullMsg-" + index; + mySource.output3() + .send(MessageBuilder.withPayload(msgContent).build()); + } + } + } } diff --git a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/resources/application.properties index beca964a..a77b3084 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/rocketmq-example/rocketmq-produce-example/src/main/resources/application.properties @@ -12,6 +12,10 @@ spring.cloud.stream.bindings.output2.content-type=application/json spring.cloud.stream.rocketmq.bindings.output2.producer.transactional=true spring.cloud.stream.rocketmq.bindings.output2.producer.group=myTxProducerGroup +spring.cloud.stream.bindings.output3.destination=pull-topic +spring.cloud.stream.bindings.output3.content-type=text/plain +spring.cloud.stream.rocketmq.bindings.output3.producer.group=pull-binder-group + spring.application.name=rocketmq-produce-example server.port=28081 diff --git a/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/file.conf b/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/file.conf index 39beec98..cb1ab8bb 100644 --- a/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/file.conf +++ b/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/file.conf @@ -20,15 +20,35 @@ transport { worker-thread-size = 8 } } +## transaction log store store { - # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions - max-branch-session-size = 16384 - # globe session size , if exceeded throws exceptions - max-global-session-size = 512 - # file buffer size , if exceeded allocate new buffer - file-write-buffer-cache-size = 16384 - # when recover batch read size - session.reload.read_size = 100 + ## store mode: file、db + mode = "file" + + ## file store + file { + dir = "sessionStore" + + # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions + max-branch-session-size = 16384 + # globe session size , if exceeded throws exceptions + max-global-session-size = 512 + # file buffer size , if exceeded allocate new buffer + file-write-buffer-cache-size = 16384 + # when recover batch read size + session.reload.read_size = 100 + # async, sync + flush-disk-mode = async + } + + ## database store + db { + driver_class = "" + url = "" + user = "" + password = "" + } + } service { #vgroup->rgroup diff --git a/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/registry.conf b/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/registry.conf index 31bcace5..7d71afaa 100644 --- a/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/registry.conf +++ b/spring-cloud-alibaba-examples/seata-example/business-service/src/main/resources/registry.conf @@ -1,5 +1,5 @@ registry { - # file 、nacos 、eureka、redis、zk、consul + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "file" nacos { @@ -26,6 +26,19 @@ registry { cluster = "default" serverAddr = "127.0.0.1:8500" } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } file { name = "file.conf" } @@ -41,7 +54,7 @@ config { cluster = "default" } apollo { - app.id = "fescar-server" + app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { @@ -52,4 +65,4 @@ config { file { name = "file.conf" } -} +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/file.conf b/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/file.conf index 2ef4f39a..7213edea 100644 --- a/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/file.conf +++ b/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/file.conf @@ -20,15 +20,35 @@ transport { worker-thread-size = 8 } } +## transaction log store store { - # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions - max-branch-session-size = 16384 - # globe session size , if exceeded throws exceptions - max-global-session-size = 512 - # file buffer size , if exceeded allocate new buffer - file-write-buffer-cache-size = 16384 - # when recover batch read size - session.reload.read_size = 100 + ## store mode: file、db + mode = "file" + + ## file store + file { + dir = "sessionStore" + + # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions + max-branch-session-size = 16384 + # globe session size , if exceeded throws exceptions + max-global-session-size = 512 + # file buffer size , if exceeded allocate new buffer + file-write-buffer-cache-size = 16384 + # when recover batch read size + session.reload.read_size = 100 + # async, sync + flush-disk-mode = async + } + + ## database store + db { + driver_class = "" + url = "" + user = "" + password = "" + } + } service { #vgroup->rgroup diff --git a/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/registry.conf b/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/registry.conf index 31bcace5..7d71afaa 100644 --- a/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/registry.conf +++ b/spring-cloud-alibaba-examples/seata-example/order-service/src/main/resources/registry.conf @@ -1,5 +1,5 @@ registry { - # file 、nacos 、eureka、redis、zk、consul + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "file" nacos { @@ -26,6 +26,19 @@ registry { cluster = "default" serverAddr = "127.0.0.1:8500" } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } file { name = "file.conf" } @@ -41,7 +54,7 @@ config { cluster = "default" } apollo { - app.id = "fescar-server" + app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { @@ -52,4 +65,4 @@ config { file { name = "file.conf" } -} +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/seata-example/readme-zh.md b/spring-cloud-alibaba-examples/seata-example/readme-zh.md index 239099a3..9c12eb50 100644 --- a/spring-cloud-alibaba-examples/seata-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/seata-example/readme-zh.md @@ -101,7 +101,7 @@ CREATE TABLE `account_tbl` ( 进入解压之后的 bin 目录,执行如下命令来启动 ```$shell -sh seata-server.sh $LISTEN_PORT $MODE +sh seata-server.sh $LISTEN_PORT $MODE(file or db) ``` 在这个示例中,采用如下命令来启动 Seata Server @@ -110,7 +110,7 @@ sh seata-server.sh $LISTEN_PORT $MODE sh seata-server.sh 8091 file ``` -**注意** 如果你修改了endpoint且注册中心使用默认file类型,那么记得需要在各个示例工程中的 `file.conf` 文件中,修改 grouplist 的值。 +**注意** 如果你修改了endpoint且注册中心使用默认file类型,那么记得需要在各个示例工程中的 `file.conf` 文件中,修改 grouplist 的值(当registry.conf 中registry.type 或 config.type 为file 时会读取内部的file节点中的文件名,若type不为file将直接从配置类型的对应元数据的注册配置中心读取数据),推荐大家使用 nacos 作为配置注册中心。 ## 运行示例 diff --git a/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/file.conf b/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/file.conf index 6c1bebbb..4699d684 100644 --- a/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/file.conf +++ b/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/file.conf @@ -20,15 +20,35 @@ transport { worker-thread-size = 8 } } +## transaction log store store { - # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions - max-branch-session-size = 16384 - # globe session size , if exceeded throws exceptions - max-global-session-size = 512 - # file buffer size , if exceeded allocate new buffer - file-write-buffer-cache-size = 16384 - # when recover batch read size - session.reload.read_size = 100 + ## store mode: file、db + mode = "file" + + ## file store + file { + dir = "sessionStore" + + # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions + max-branch-session-size = 16384 + # globe session size , if exceeded throws exceptions + max-global-session-size = 512 + # file buffer size , if exceeded allocate new buffer + file-write-buffer-cache-size = 16384 + # when recover batch read size + session.reload.read_size = 100 + # async, sync + flush-disk-mode = async + } + + ## database store + db { + driver_class = "" + url = "" + user = "" + password = "" + } + } service { #vgroup->rgroup diff --git a/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/registry.conf b/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/registry.conf index 31bcace5..7d71afaa 100644 --- a/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/registry.conf +++ b/spring-cloud-alibaba-examples/seata-example/storage-service/src/main/resources/registry.conf @@ -1,5 +1,5 @@ registry { - # file 、nacos 、eureka、redis、zk、consul + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "file" nacos { @@ -26,6 +26,19 @@ registry { cluster = "default" serverAddr = "127.0.0.1:8500" } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } file { name = "file.conf" } @@ -41,7 +54,7 @@ config { cluster = "default" } apollo { - app.id = "fescar-server" + app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { @@ -52,4 +65,4 @@ config { file { name = "file.conf" } -} +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md index bfdc599b..8b3a9214 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md @@ -184,7 +184,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,Sentinel Starter * Spring Boot 1.x 中添加配置 `management.security.enabled=false` * Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*` -Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:18083/acutator/sentinel 来访问。 +Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:18083/actuator/sentinel 来访问。

diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties index baa1dba3..c3d0959d 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/application.properties @@ -1,6 +1,12 @@ spring.application.name=sentinel-example server.port=18083 management.endpoints.web.exposure.include=* +management.endpoint.health.show-details=always + +# we can disable health check, default is enable +management.health.diskspace.enabled=false +# management.health.sentinel.enabled=false + spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.eager=true @@ -19,4 +25,4 @@ spring.cloud.sentinel.datasource.ds4.file.file=classpath: system.json spring.cloud.sentinel.datasource.ds4.file.rule-type=system spring.cloud.sentinel.datasource.ds5.file.file=classpath: param-flow.json -spring.cloud.sentinel.datasource.ds5.file.rule-type=param-flow +spring.cloud.sentinel.datasource.ds5.file.rule-type=param_flow diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json index 8aacfc33..63afc91a 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/src/main/resources/flowrule.json @@ -1,6 +1,6 @@ [ { - "resource": "resource", + "resource": "/hello", "controlBehavior": 0, "count": 1, "grade": 1, diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md index 2a936115..25efd6dd 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/readme-zh.md @@ -6,7 +6,7 @@ [Sentinel](https://github.com/alibaba/Sentinel) 是阿里巴巴开源的分布式系统的流量防卫组件,Sentinel 把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。 -[Dubbo](http://dubbo.apache.org/)是一款高性能Java RPC框架,有对应的[SpringBoot工程](https://github.com/apache/incubator-dubbo-spring-boot-project)。 +[Dubbo](http://dubbo.apache.org/)是一款高性能Java RPC框架,有对应的[SpringBoot工程](https://github.com/apache/dubbo-spring-boot-project)。 本项目专注于Sentinel与Dubbo的整合,关于Sentinel的更多特性可以查看[sentinel-core-example](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example)。 diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/pom.xml new file mode 100644 index 00000000..e2979eb1 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/pom.xml @@ -0,0 +1,82 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba-examples + 0.9.1.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + + sentinel-spring-cloud-gateway-example + jar + Example demonstrating how to use sentinel with spring cloud gateway + + + + + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + org.springframework.cloud + spring-cloud-alibaba-sentinel-gateway + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MySCGConfiguration.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MySCGConfiguration.java new file mode 100644 index 00000000..0c39e3f7 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MySCGConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; + +/** + * @author Jim + */ +@Configuration +public class MySCGConfiguration { + + @Bean + public BlockRequestHandler blockRequestHandler() { + return new BlockRequestHandler() { + @Override + public Mono handleRequest(ServerWebExchange exchange, Throwable t) { + return ServerResponse.status(444) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .body(fromObject("SCS Sentinel block")); + } + }; + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesWebFluxController.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesWebFluxController.java new file mode 100644 index 00000000..7767a871 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesWebFluxController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import java.util.List; +import java.util.Set; + +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * @author Jim + */ +@RestController +public class RulesWebFluxController { + + @GetMapping("/api") + public Mono> apiRules() { + return Mono.just(GatewayApiDefinitionManager.getApiDefinitions()); + } + + @GetMapping("/gateway") + public Mono> apiGateway() { + return Mono.just(GatewayRuleManager.getRules()); + } + + @GetMapping("/flow") + public Mono> apiFlow() { + return Mono.just(FlowRuleManager.getRules()); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelSpringCloudGatewayApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelSpringCloudGatewayApplication.java new file mode 100644 index 00000000..786d8dcd --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelSpringCloudGatewayApplication.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Jim + */ +@SpringBootApplication +public class SentinelSpringCloudGatewayApplication { + + public static void main(String[] args) { + //GatewayCallbackManager.setRequestOriginParser(s -> "123"); + SpringApplication.run(SentinelSpringCloudGatewayApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/api.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/api.json new file mode 100644 index 00000000..6de3c4fb --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/api.json @@ -0,0 +1,32 @@ +[ + { + "apiName": "some_customized_api", + "predicateItems": [ + { + "pattern": "/product/baz" + }, + { + "pattern": "/product/foo/**", + "matchStrategy": 1 + }, + { + "items": [ + { + "pattern": "/spring-cloud/**" + }, + { + "pattern": "/spring-cloud-alibaba/**" + } + ] + } + ] + }, + { + "apiName": "another_customized_api", + "predicateItems": [ + { + "pattern": "/ahas" + } + ] + } +] diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/application.yaml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/application.yaml new file mode 100644 index 00000000..27ae0cb9 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/application.yaml @@ -0,0 +1,41 @@ +server: + port: 18085 +spring: + application: + name: sentinel-spring-cloud-gateway + cloud: + gateway: + enabled: true + discovery: + locator: + lower-case-service-id: true + routes: + # Add your routes here. + - id: aliyun_route + uri: https://www.aliyun.com/ + predicates: + - Path=/product/** + - id: httpbin_route + uri: https://httpbin.org + predicates: + - Path=/httpbin/** + filters: + - RewritePath=/httpbin/(?.*), /$\{segment} + + sentinel: + datasource.ds2.file: + file: "classpath: gateway.json" + ruleType: gw-flow + datasource.ds1.file: + file: "classpath: api.json" + ruleType: gw-api-group + transport: + dashboard: localhost:8080 + filter: + enabled: true + scg.fallback: + mode: response + response-status: 444 + response-body: 1234 + +management.endpoints.web.exposure.include: "*" \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/gateway.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/gateway.json new file mode 100644 index 00000000..b08f71fc --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-spring-cloud-gateway-example/src/main/resources/gateway.json @@ -0,0 +1,22 @@ +[ + { + "resource": "some_customized_api", + "count": 1 + }, + { + "resource": "httpbin_route", + "count": 0, + "paramItem": { + "parseStrategy": 2, + "fieldName": "Spring-Cloud-Alibaba" + } + }, + { + "resource": "httpbin_route", + "count": 0, + "paramItem": { + "parseStrategy": 3, + "fieldName": "name" + } + } +] diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/pom.xml new file mode 100644 index 00000000..c3b4f17c --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/pom.xml @@ -0,0 +1,72 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba-examples + 0.9.1.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + + sentinel-webflux-example + jar + Example demonstrating how to use sentinel with webflux + + + + + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MyConfiguration.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MyConfiguration.java new file mode 100644 index 00000000..9d95923a --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/MyConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebExchange; + +import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.BlockRequestHandler; + +import reactor.core.publisher.Mono; + +/** + * @author Jim + */ +@Configuration +public class MyConfiguration { + + @Bean + public BlockRequestHandler blockRequestHandler() { + return new BlockRequestHandler() { + @Override + public Mono handleRequest(ServerWebExchange exchange, + Throwable t) { + return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .body(fromObject("block")); + } + }; + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxApplication.java new file mode 100644 index 00000000..87a42503 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Jim + */ +@SpringBootApplication +public class SentinelWebFluxApplication { + + public static void main(String[] args) { + SpringApplication.run(SentinelWebFluxApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxController.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxController.java new file mode 100644 index 00000000..2c5f9f1d --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelWebFluxController.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Jim + */ +@RestController +public class SentinelWebFluxController { + + @GetMapping("/mono") + public Mono mono() { + return Mono.just("simple string") + // transform the publisher here. + .transform(new SentinelReactorTransformer<>("mono")); + } + + @GetMapping("/flux") + public Flux flux() { + return Flux.fromArray(new String[] { "a", "b", "c" }) + // transform the publisher here. + .transform(new SentinelReactorTransformer<>("flux")); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/application.properties new file mode 100644 index 00000000..e9750c02 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=sentinel-example +server.port=18084 +management.endpoints.web.exposure.include=* +spring.cloud.sentinel.transport.dashboard=localhost:8080 +spring.cloud.sentinel.eager=true + + +spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json +spring.cloud.sentinel.datasource.ds1.file.data-type=json +spring.cloud.sentinel.datasource.ds1.file.rule-type=flow \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/flowrule.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/flowrule.json new file mode 100644 index 00000000..10e022b8 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-webflux-example/src/main/resources/flowrule.json @@ -0,0 +1,18 @@ +[ + { + "resource": "/mono", + "controlBehavior": 0, + "count": 0, + "grade": 1, + "limitApp": "default", + "strategy": 0 + }, + { + "resource": "/flux", + "controlBehavior": 0, + "count": 0, + "grade": 1, + "limitApp": "default", + "strategy": 0 + } +] diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/pom.xml new file mode 100644 index 00000000..0f0b0c09 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/pom.xml @@ -0,0 +1,82 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba-examples + 0.9.1.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + + sentinel-zuul-example + jar + Example demonstrating how to use sentinel with zuul + + + + + + org.springframework.cloud + spring-cloud-starter-alibaba-sentinel + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + + + + org.springframework.cloud + spring-cloud-alibaba-sentinel-gateway + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesController.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesController.java new file mode 100644 index 00000000..01411ab7 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/RulesController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import java.util.List; +import java.util.Set; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; + +/** + * @author Jim + */ +@RestController +public class RulesController { + + @GetMapping("/api") + public Set apiRules() { + return GatewayApiDefinitionManager.getApiDefinitions(); + } + + @GetMapping("/gateway") + public Set apiGateway() { + return GatewayRuleManager.getRules(); + } + + @GetMapping("/flow") + public List apiFlow() { + return FlowRuleManager.getRules(); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelZuulApplication.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelZuulApplication.java new file mode 100644 index 00000000..7e99e220 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SentinelZuulApplication.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; + +/** + * @author Jim + */ +@SpringBootApplication +@EnableZuulProxy +public class SentinelZuulApplication { + + public static void main(String[] args) { + SpringApplication.run(SentinelZuulApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ZuulConfiguration.java b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ZuulConfiguration.java new file mode 100644 index 00000000..796241a2 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ZuulConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.cloud.examples; + +import javax.servlet.http.HttpServletRequest; + +import com.alibaba.csp.sentinel.adapter.gateway.zuul.callback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.BlockResponse; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackProvider; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Jim + */ +@Configuration +public class ZuulConfiguration { + + @Bean + public ZuulBlockFallbackProvider zuulBlockFallbackProvider1() { + return new ZuulBlockFallbackProvider() { + @Override + public String getRoute() { + return "*"; + } + + @Override + public BlockResponse fallbackResponse(String route, Throwable cause) { + if (route.equals("my-service3")) { + return new BlockResponse(433, "Sentinel Block3", route); + } else if (route.equals("my-service4")) { + return new BlockResponse(444, "my-service4", route); + } else { + return new BlockResponse(499, "Sentinel Block 499", route); + } + } + }; + } + + @Bean + public RequestOriginParser requestOriginParser() { + return new RequestOriginParser() { + + @Override + public String parseOrigin(HttpServletRequest request) { + return "123"; + } + }; + } + +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/api.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/api.json new file mode 100644 index 00000000..6de3c4fb --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/api.json @@ -0,0 +1,32 @@ +[ + { + "apiName": "some_customized_api", + "predicateItems": [ + { + "pattern": "/product/baz" + }, + { + "pattern": "/product/foo/**", + "matchStrategy": 1 + }, + { + "items": [ + { + "pattern": "/spring-cloud/**" + }, + { + "pattern": "/spring-cloud-alibaba/**" + } + ] + } + ] + }, + { + "apiName": "another_customized_api", + "predicateItems": [ + { + "pattern": "/ahas" + } + ] + } +] diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/application.yaml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/application.yaml new file mode 100644 index 00000000..0e9d9abf --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/application.yaml @@ -0,0 +1,40 @@ +server: + port: 18086 +spring: + application: + name: sentinel-zuul + cloud: + sentinel: + datasource.ds2.file: + file: "classpath: gateway.json" + ruleType: gw-flow + datasource.ds1.file: + file: "classpath: api.json" + ruleType: gw-api-group + transport: + dashboard: localhost:8080 + filter: + enabled: false + +management.endpoints.web.exposure.include: "*" + + +zuul.routes.my-service.path: "/product/foo/**" +zuul.routes.my-service.service-id: "my-service" + +zuul.routes.my-service2.path: "/my-service2/**" +zuul.routes.my-service2.service-id: "my-service2" + +zuul.routes.my-service3.path: "/my-service3/**" +zuul.routes.my-service3.service-id: "my-service3" + +zuul.routes.my-service4.path: "/my-service4/**" +zuul.routes.my-service4.service-id: "my-service4" + + +spring.cloud.sentinel.zuul.order.pre: 2000 +spring.cloud.sentinel.zuul.order.post: 500 +spring.cloud.sentinel.zuul.order.error: -100 + + +spring.cloud.sentinel.zuul.enabled: true \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/gateway.json b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/gateway.json new file mode 100644 index 00000000..b3effe88 --- /dev/null +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-zuul-example/src/main/resources/gateway.json @@ -0,0 +1,26 @@ +[ + { + "resource": "some_customized_api", + "count": 0 + }, + { + "resource": "my-service2", + "count": 0 + }, + { + "resource": "my-service3", + "count": 0, + "paramItem": { + "parseStrategy": 2, + "fieldName": "Spring-Cloud-Alibaba" + } + }, + { + "resource": "my-service4", + "count": 0, + "paramItem": { + "parseStrategy": 3, + "fieldName": "name" + } + } +] diff --git a/spring-cloud-alibaba-examples/sms-example/readme-zh.md b/spring-cloud-alibaba-examples/sms-example/readme-zh.md index b21875c9..c07b3608 100644 --- a/spring-cloud-alibaba-examples/sms-example/readme-zh.md +++ b/spring-cloud-alibaba-examples/sms-example/readme-zh.md @@ -306,7 +306,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,SMS Starter 也 Spring Boot1.x 可以通过访问 http://127.0.0.1:18084/sms-info 来查看 SMS Endpoint 的信息。 -Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/acutator/sms-info 来访问。 +Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/actuator/sms-info 来访问。 Endpoint 内部会显示最近 20 条单个短信发送的记录和批量短信发送的记录,以及当前短信消息的配置信息(包括是**SmsReport** 还是 **SmsUp**,**队列名称**,以及对应的 **MessageListener** )。 diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/README_CN.md b/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/README_CN.md index 8cd9fb05..70ddae33 100644 --- a/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/README_CN.md +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/README_CN.md @@ -157,7 +157,7 @@ spring: - `spring.application.name` : Spring 应用名称,用于 Spring Cloud 服务注册和发现。 > 该值在 Dubbo Spring Cloud 加持下被视作 `dubbo.application.name`,因此,无需再显示地配置 `dubbo.application.name` - `spring.main.allow-bean-definition-overriding` : 在 Spring Boot 2.1 以及更高的版本增加该设定, -因为 Spring Boot 默认调整了 Bean 定义覆盖行为。(推荐一个好的 Dubbo 讨论 [issue #3193](https://github.com/apache/incubator-dubbo/issues/3193#issuecomment-474340165)) +因为 Spring Boot 默认调整了 Bean 定义覆盖行为。(推荐一个好的 Dubbo 讨论 [issue #3193](https://github.com/apache/dubbo/issues/3193#issuecomment-474340165)) - `spring.cloud.nacos.discovery` : Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口 > 以上完整的 YAML 配置文件,请参考 `spring-cloud-dubbo-server-sample` [`bootstrap.yaml`](spring-cloud-dubbo-server-sample/src/main/resources/bootstrap.yaml) 文件 @@ -345,4 +345,4 @@ HTTP 响应为: - [spring-cloud-dubbo-provider-web-sample](spring-cloud-dubbo-provider-web-sample):Dubbo Spring Cloud 服务提供方示例(Web 应用) - [spring-cloud-dubbo-provider-sample](spring-cloud-dubbo-provider-sample):Dubbo Spring Cloud 服务提供方示例(非 Web 应用) - [spring-cloud-dubbo-consumer-sample](spring-cloud-dubbo-consumer-sample):Dubbo Spring Cloud 服务消费方示例 -- [spring-cloud-dubbo-servlet-gateway](spring-cloud-dubbo-servlet-gateway)-sample:Dubbo Spring Cloud Servlet 网关简易实现示例 \ No newline at end of file +- [spring-cloud-dubbo-servlet-gateway](spring-cloud-dubbo-servlet-gateway)-sample:Dubbo Spring Cloud Servlet 网关简易实现示例 diff --git a/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-servlet-gateway-sample/src/main/java/org/springframework/cloud/alibaba/dubbo/gateway/DubboGatewayServlet.java b/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-servlet-gateway-sample/src/main/java/org/springframework/cloud/alibaba/dubbo/gateway/DubboGatewayServlet.java index b3c56d24..21eab7e3 100644 --- a/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-servlet-gateway-sample/src/main/java/org/springframework/cloud/alibaba/dubbo/gateway/DubboGatewayServlet.java +++ b/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-servlet-gateway-sample/src/main/java/org/springframework/cloud/alibaba/dubbo/gateway/DubboGatewayServlet.java @@ -76,6 +76,7 @@ public class DubboGatewayServlet extends HttpServletBean { return serviceName; } + @Override public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String serviceName = resolveServiceName(request); diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosPropertySourceRepository.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosPropertySourceRepository.java index 0a0cc00c..6fa8e490 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosPropertySourceRepository.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosPropertySourceRepository.java @@ -28,25 +28,25 @@ import java.util.concurrent.ConcurrentHashMap; */ public class NacosPropertySourceRepository { - private final static ConcurrentHashMap nacosPropertySourceRepository = new ConcurrentHashMap<>(); + private final static ConcurrentHashMap NACOS_PROPERTY_SOURCE_REPOSITORY = new ConcurrentHashMap<>(); /** * @return all nacos properties from application context */ public static List getAll() { List result = new ArrayList<>(); - result.addAll(nacosPropertySourceRepository.values()); + result.addAll(NACOS_PROPERTY_SOURCE_REPOSITORY.values()); return result; } public static void collectNacosPropertySources( NacosPropertySource nacosPropertySource) { - nacosPropertySourceRepository.putIfAbsent(nacosPropertySource.getDataId(), + NACOS_PROPERTY_SOURCE_REPOSITORY.putIfAbsent(nacosPropertySource.getDataId(), nacosPropertySource); } public static NacosPropertySource getNacosPropertySource(String dataId) { - return nacosPropertySourceRepository.get(dataId); + return NACOS_PROPERTY_SOURCE_REPOSITORY.get(dataId); } } diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosConfigEndpoint.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosConfigEndpoint.java index 1160db1b..34ddd2a0 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosConfigEndpoint.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/endpoint/NacosConfigEndpoint.java @@ -41,7 +41,12 @@ public class NacosConfigEndpoint { private final NacosRefreshHistory refreshHistory; - private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private ThreadLocal dateFormat = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + }; public NacosConfigEndpoint(NacosConfigProperties properties, NacosRefreshHistory refreshHistory) { @@ -60,7 +65,7 @@ public class NacosConfigEndpoint { for (NacosPropertySource ps : all) { Map source = new HashMap<>(16); source.put("dataId", ps.getDataId()); - source.put("lastSynced", dateFormat.format(ps.getTimestamp())); + source.put("lastSynced", dateFormat.get().format(ps.getTimestamp())); sources.add(source); } result.put("Sources", sources); diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosRefreshHistory.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosRefreshHistory.java index ec5a83ca..149453b6 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosRefreshHistory.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosRefreshHistory.java @@ -27,10 +27,15 @@ public class NacosRefreshHistory { private LinkedList records = new LinkedList<>(); - private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private ThreadLocal dateFormat = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + }; public void add(String dataId, String md5) { - records.addFirst(new Record(dateFormat.format(new Date()), dataId, md5)); + records.addFirst(new Record(dateFormat.get().format(new Date()), dataId, md5)); if (records.size() > MAX_SIZE) { records.removeLast(); } diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryAutoConfiguration.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryAutoConfiguration.java index c092f62a..9e348a1a 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryAutoConfiguration.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryAutoConfiguration.java @@ -17,12 +17,9 @@ package org.springframework.cloud.alibaba.nacos; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -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.alibaba.nacos.discovery.NacosDiscoveryClientAutoConfiguration; import org.springframework.cloud.alibaba.nacos.registry.NacosAutoServiceRegistration; import org.springframework.cloud.alibaba.nacos.registry.NacosRegistration; import org.springframework.cloud.alibaba.nacos.registry.NacosServiceRegistry; diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/discovery/NacosWatch.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/discovery/NacosWatch.java index 0eefaf1a..861dc400 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/discovery/NacosWatch.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/discovery/NacosWatch.java @@ -118,57 +118,10 @@ public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycl } public void nacosServicesWatch() { - try { - boolean changed = false; - NamingService namingService = properties.namingServiceInstance(); + // nacos doesn't support watch now , publish an event every 30 seconds. + this.publisher.publishEvent( + new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement())); - ListView listView = properties.namingServiceInstance() - .getServicesOfServer(1, Integer.MAX_VALUE); - - List serviceList = listView.getData(); - - // if there are new services found, publish event - Set currentServices = new HashSet<>(serviceList); - currentServices.removeAll(cacheServices); - if (currentServices.size() > 0) { - changed = true; - } - - // if some services disappear, publish event - if (cacheServices.removeAll(new HashSet<>(serviceList)) - && cacheServices.size() > 0) { - changed = true; - - for (String serviceName : cacheServices) { - namingService.unsubscribe(serviceName, - subscribeListeners.get(serviceName)); - subscribeListeners.remove(serviceName); - } - } - - cacheServices = new HashSet<>(serviceList); - - // subscribe services's node change, publish event if nodes changed - for (String serviceName : cacheServices) { - if (!subscribeListeners.containsKey(serviceName)) { - EventListener eventListener = event -> NacosWatch.this.publisher - .publishEvent(new HeartbeatEvent(NacosWatch.this, - nacosWatchIndex.getAndIncrement())); - subscribeListeners.put(serviceName, eventListener); - namingService.subscribe(serviceName, eventListener); - - } - } - - if (changed) { - this.publisher.publishEvent( - new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement())); - } - - } - catch (Exception e) { - log.error("Error watching Nacos Service change", e); - } } } diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosRibbonClientConfiguration.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosRibbonClientConfiguration.java index ddd1696e..77231a89 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosRibbonClientConfiguration.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosRibbonClientConfiguration.java @@ -33,9 +33,16 @@ public class NacosRibbonClientConfiguration { @Bean @ConditionalOnMissingBean - public ServerList ribbonServerList(IClientConfig config, NacosDiscoveryProperties nacosDiscoveryProperties) { + public ServerList ribbonServerList(IClientConfig config, + NacosDiscoveryProperties nacosDiscoveryProperties) { NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties); serverList.initWithNiwsConfig(config); return serverList; } + + @Bean + @ConditionalOnMissingBean + public NacosServerIntrospector nacosServerIntrospector() { + return new NacosServerIntrospector(); + } } diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerIntrospector.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerIntrospector.java new file mode 100644 index 00000000..4e9d22cc --- /dev/null +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerIntrospector.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 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. + */ + +package org.springframework.cloud.alibaba.nacos.ribbon; + +import java.util.Map; + +import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector; + +import com.netflix.loadbalancer.Server; + +/** + * @author xiaojing + */ +public class NacosServerIntrospector extends DefaultServerIntrospector { + + @Override + public Map getMetadata(Server server) { + if (server instanceof NacosServer) { + return ((NacosServer) server).getMetadata(); + } + return super.getMetadata(server); + } + + @Override + public boolean isSecure(Server server) { + if (server instanceof NacosServer) { + return Boolean.valueOf(((NacosServer) server).getMetadata().get("secure")); + } + + return super.isSecure(server); + } +} diff --git a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClient.java b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClient.java index 9f4f2fb2..cf71842d 100644 --- a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClient.java +++ b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClient.java @@ -35,49 +35,44 @@ import org.springframework.util.StringUtils; */ public class SeataFeignClient implements Client { - private final Client delegate; - private final BeanFactory beanFactory; - private static final int MAP_SIZE = 16; + private final Client delegate; + private final BeanFactory beanFactory; + private static final int MAP_SIZE = 16; - SeataFeignClient(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - this.delegate = new Client.Default(null, null); - } + SeataFeignClient(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + this.delegate = new Client.Default(null, null); + } - SeataFeignClient(BeanFactory beanFactory, Client delegate) { - this.delegate = delegate; - this.beanFactory = beanFactory; - } + SeataFeignClient(BeanFactory beanFactory, Client delegate) { + this.delegate = delegate; + this.beanFactory = beanFactory; + } - @Override - public Response execute(Request request, Request.Options options) throws IOException { + @Override + public Response execute(Request request, Request.Options options) throws IOException { - Request modifiedRequest = getModifyRequest(request); + Request modifiedRequest = getModifyRequest(request); + return this.delegate.execute(modifiedRequest, options); + } - try { - return this.delegate.execute(modifiedRequest, options); - } finally { + private Request getModifyRequest(Request request) { - } - } + String xid = RootContext.getXID(); - private Request getModifyRequest(Request request) { + if (StringUtils.isEmpty(xid)) { + return request; + } - String xid = RootContext.getXID(); + Map> headers = new HashMap<>(MAP_SIZE); + headers.putAll(request.headers()); - if (StringUtils.isEmpty(xid)) { - return request; - } + List fescarXid = new ArrayList<>(); + fescarXid.add(xid); + headers.put(RootContext.KEY_XID, fescarXid); - Map> headers = new HashMap<>(MAP_SIZE); - headers.putAll(request.headers()); - - List fescarXid = new ArrayList<>(); - fescarXid.add(xid); - headers.put(RootContext.KEY_XID, fescarXid); - - return Request.create(request.method(), request.url(), headers, request.body(), - request.charset()); - } + return Request.create(request.method(), request.url(), headers, request.body(), + request.charset()); + } } \ No newline at end of file diff --git a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClientAutoConfiguration.java b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClientAutoConfiguration.java index 5440dc73..030b6b94 100644 --- a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClientAutoConfiguration.java +++ b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignClientAutoConfiguration.java @@ -65,7 +65,7 @@ public class SeataFeignClientAutoConfiguration { protected static class FeignBeanPostProcessorConfiguration { @Bean - SeataBeanPostProcessor seataBeanPostProcessor( + SeataBeanPostProcessor seataBeanPostProcessor( SeataFeignObjectWrapper seataFeignObjectWrapper) { return new SeataBeanPostProcessor(seataFeignObjectWrapper); } diff --git a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignContext.java b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignContext.java index 4b8c16e7..c90ac4d4 100644 --- a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignContext.java +++ b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataFeignContext.java @@ -32,7 +32,7 @@ public class SeataFeignContext extends FeignContext { private final FeignContext delegate; SeataFeignContext(SeataFeignObjectWrapper seataFeignObjectWrapper, - FeignContext delegate) { + FeignContext delegate) { this.seataFeignObjectWrapper = seataFeignObjectWrapper; this.delegate = delegate; } diff --git a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataLoadBalancerFeignClient.java b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataLoadBalancerFeignClient.java index 991ee166..598d1a28 100644 --- a/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataLoadBalancerFeignClient.java +++ b/spring-cloud-alibaba-seata/src/main/java/org/springframework/cloud/alibaba/seata/feign/SeataLoadBalancerFeignClient.java @@ -17,7 +17,13 @@ package org.springframework.cloud.alibaba.seata.feign; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import io.seata.core.context.RootContext; import org.springframework.beans.factory.BeanFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; @@ -26,23 +32,27 @@ import feign.Request; import feign.Response; import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; +import org.springframework.util.StringUtils; /** * @author xiaojing */ public class SeataLoadBalancerFeignClient extends LoadBalancerFeignClient { + private static final int MAP_SIZE = 16; + private final BeanFactory beanFactory; SeataLoadBalancerFeignClient(Client delegate, - CachingSpringLoadBalancerFactory lbClientFactory, - SpringClientFactory clientFactory, BeanFactory beanFactory) { + CachingSpringLoadBalancerFactory lbClientFactory, + SpringClientFactory clientFactory, BeanFactory beanFactory) { super(wrap(delegate, beanFactory), lbClientFactory, clientFactory); this.beanFactory = beanFactory; } @Override public Response execute(Request request, Request.Options options) throws IOException { + Request modifiedRequest = getModifyRequest(request); return super.execute(request, options); } @@ -50,4 +60,22 @@ public class SeataLoadBalancerFeignClient extends LoadBalancerFeignClient { return (Client) new SeataFeignObjectWrapper(beanFactory).wrap(delegate); } + private Request getModifyRequest(Request request) { + + String xid = RootContext.getXID(); + + if (StringUtils.isEmpty(xid)) { + return request; + } + + Map> headers = new HashMap<>(MAP_SIZE); + headers.putAll(request.headers()); + + List fescarXid = new ArrayList<>(); + fescarXid.add(xid); + headers.put(RootContext.KEY_XID, fescarXid); + + return Request.create(request.method(), request.url(), headers, request.body(), + request.charset()); + } } diff --git a/spring-cloud-alibaba-sentinel-datasource/pom.xml b/spring-cloud-alibaba-sentinel-datasource/pom.xml index 63203844..6dfee847 100644 --- a/spring-cloud-alibaba-sentinel-datasource/pom.xml +++ b/spring-cloud-alibaba-sentinel-datasource/pom.xml @@ -27,6 +27,12 @@ true
+ + com.alibaba.csp + sentinel-api-gateway-adapter-common + true + + com.alibaba.csp sentinel-datasource-nacos diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/RuleType.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/RuleType.java index e59a5735..2318f321 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/RuleType.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/RuleType.java @@ -6,6 +6,8 @@ import java.util.Optional; import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties; import org.springframework.util.StringUtils; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; @@ -40,7 +42,15 @@ public enum RuleType { /** * authority */ - AUTHORITY("authority", AuthorityRule.class); + AUTHORITY("authority", AuthorityRule.class), + /** + * gateway flow + */ + GW_FLOW("gw-flow", GatewayFlowRule.class), + /** + * api + */ + GW_API_GROUP("gw-api-group", ApiDefinition.class); /** * alias for {@link AbstractRule} diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java index 272252c2..0b830217 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/AbstractDataSourceProperties.java @@ -4,7 +4,10 @@ import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.springframework.cloud.alibaba.sentinel.datasource.RuleType; +import org.springframework.core.env.Environment; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; import com.alibaba.csp.sentinel.datasource.AbstractDataSource; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; @@ -28,6 +31,8 @@ public class AbstractDataSourceProperties { private String converterClass; @JsonIgnore private final String factoryBeanName; + @JsonIgnore + private Environment env; public AbstractDataSourceProperties(String factoryBeanName) { this.factoryBeanName = factoryBeanName; @@ -61,6 +66,14 @@ public class AbstractDataSourceProperties { return factoryBeanName; } + protected Environment getEnv() { + return env; + } + + public void setEnv(Environment env) { + this.env = env; + } + public void preCheck(String dataSourceName) { } @@ -82,6 +95,12 @@ public class AbstractDataSourceProperties { case AUTHORITY: AuthorityRuleManager.register2Property(dataSource.getProperty()); break; + case GW_FLOW: + GatewayRuleManager.register2Property(dataSource.getProperty()); + break; + case GW_API_GROUP: + GatewayApiDefinitionManager.register2Property(dataSource.getProperty()); + break; default: break; } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java index 9df4d473..12c6e7f7 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/NacosDataSourceProperties.java @@ -2,8 +2,6 @@ package org.springframework.cloud.alibaba.sentinel.datasource.config; import javax.validation.constraints.NotEmpty; -import org.springframework.cloud.alibaba.sentinel.datasource.RuleType; -import org.springframework.cloud.alibaba.sentinel.datasource.SentinelDataSourceConstants; import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceFactoryBean; import org.springframework.util.StringUtils; @@ -34,9 +32,13 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties { @Override public void preCheck(String dataSourceName) { - if (StringUtils.isEmpty(serverAddr) && acmPropertiesInvalid()) { - throw new IllegalArgumentException( - "NacosDataSource properties value not correct. serverAddr is empty but there is empty value in accessKey, secretKey, endpoint, namespace property"); + if (StringUtils.isEmpty(serverAddr)) { + serverAddr = this.getEnv().getProperty( + "spring.cloud.sentinel.datasource.nacos.server-addr", ""); + if (StringUtils.isEmpty(serverAddr)) { + throw new IllegalArgumentException( + "NacosDataSource server-addr is empty"); + } } } @@ -96,9 +98,4 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties { this.secretKey = secretKey; } - public boolean acmPropertiesInvalid() { - return StringUtils.isEmpty(endpoint) || StringUtils.isEmpty(accessKey) - || StringUtils.isEmpty(secretKey) || StringUtils.isEmpty(namespace); - } - } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java index 0ebfee4d..7bbece26 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/config/ZookeeperDataSourceProperties.java @@ -1,8 +1,7 @@ package org.springframework.cloud.alibaba.sentinel.datasource.config; -import javax.validation.constraints.NotEmpty; - import org.springframework.cloud.alibaba.sentinel.datasource.factorybean.ZookeeperDataSourceFactoryBean; +import org.springframework.util.StringUtils; /** * Zookeeper Properties class Using by {@link DataSourcePropertiesConfiguration} and @@ -16,7 +15,6 @@ public class ZookeeperDataSourceProperties extends AbstractDataSourceProperties super(ZookeeperDataSourceFactoryBean.class.getName()); } - @NotEmpty private String serverAddr; private String path; @@ -25,6 +23,18 @@ public class ZookeeperDataSourceProperties extends AbstractDataSourceProperties private String dataId; + @Override + public void preCheck(String dataSourceName) { + if (StringUtils.isEmpty(serverAddr)) { + serverAddr = this.getEnv() + .getProperty("spring.cloud.sentinel.datasource.zk.server-addr", ""); + if (StringUtils.isEmpty(serverAddr)) { + throw new IllegalArgumentException( + "ZookeeperDataSource server-addr is empty"); + } + } + } + public String getServerAddr() { return serverAddr; } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/SentinelConverter.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/SentinelConverter.java index 302bcd45..649f8f3b 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/SentinelConverter.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/converter/SentinelConverter.java @@ -1,7 +1,20 @@ package org.springframework.cloud.alibaba.sentinel.datasource.converter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.alibaba.sentinel.datasource.RuleType; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.datasource.Converter; -import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; @@ -13,15 +26,6 @@ import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.alibaba.sentinel.datasource.RuleType; -import org.springframework.util.StringUtils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; /** * Convert sentinel rules for json or xml array Using strict mode to parse json or xml @@ -34,8 +38,8 @@ import java.util.Optional; * @see ParamFlowRule * @see ObjectMapper */ -public abstract class SentinelConverter - implements Converter> { +public abstract class SentinelConverter + implements Converter> { private static final Logger log = LoggerFactory.getLogger(SentinelConverter.class); @@ -49,11 +53,20 @@ public abstract class SentinelConverter } @Override - public List convert(String source) { - List ruleList = new ArrayList<>(); + public Collection convert(String source) { + Collection ruleCollection; + + // hard code + if (ruleClass == GatewayFlowRule.class || ruleClass == ApiDefinition.class) { + ruleCollection = new HashSet<>(); + } + else { + ruleCollection = new ArrayList<>(); + } + if (StringUtils.isEmpty(source)) { log.warn("converter can not convert rules because source is empty"); - return ruleList; + return ruleCollection; } try { List sourceArray = objectMapper.readValue(source, @@ -70,11 +83,11 @@ public abstract class SentinelConverter } Optional.ofNullable(convertRule(item)) - .ifPresent(convertRule -> ruleList.add(convertRule)); + .ifPresent(convertRule -> ruleCollection.add(convertRule)); }); - if (ruleList.size() != sourceArray.size()) { - throw new IllegalArgumentException("convert " + ruleList.size() + if (ruleCollection.size() != sourceArray.size()) { + throw new IllegalArgumentException("convert " + ruleCollection.size() + " rules but there are " + sourceArray.size() + " rules from datasource. RuleClass: " + ruleClass.getSimpleName()); @@ -83,12 +96,12 @@ public abstract class SentinelConverter catch (Exception e) { throw new RuntimeException("convert error: " + e.getMessage(), e); } - return ruleList; + return ruleCollection; } - private AbstractRule convertRule(String ruleStr) { + private Object convertRule(String ruleStr) { try { - final AbstractRule rule = objectMapper.readValue(ruleStr, ruleClass); + final Object rule = objectMapper.readValue(ruleStr, ruleClass); RuleType ruleType = RuleType.getByClass(ruleClass).get(); switch (ruleType) { case FLOW: diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java index ddca34cb..ef616096 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ApolloDataSourceFactoryBean.java @@ -1,10 +1,10 @@ package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; +import org.springframework.beans.factory.FactoryBean; + import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.apollo.ApolloDataSource; -import org.springframework.beans.factory.FactoryBean; - /** * A {@link FactoryBean} for creating {@link ApolloDataSource} instance. * @@ -13,51 +13,51 @@ import org.springframework.beans.factory.FactoryBean; */ public class ApolloDataSourceFactoryBean implements FactoryBean { - private String namespaceName; - private String flowRulesKey; - private String defaultFlowRuleValue; - private Converter converter; + private String namespaceName; + private String flowRulesKey; + private String defaultFlowRuleValue; + private Converter converter; - @Override - public ApolloDataSource getObject() throws Exception { - return new ApolloDataSource(namespaceName, flowRulesKey, defaultFlowRuleValue, - converter); - } + @Override + public ApolloDataSource getObject() throws Exception { + return new ApolloDataSource(namespaceName, flowRulesKey, defaultFlowRuleValue, + converter); + } - @Override - public Class getObjectType() { - return ApolloDataSource.class; - } + @Override + public Class getObjectType() { + return ApolloDataSource.class; + } - public String getNamespaceName() { - return namespaceName; - } + public String getNamespaceName() { + return namespaceName; + } - public void setNamespaceName(String namespaceName) { - this.namespaceName = namespaceName; - } + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } - public String getFlowRulesKey() { - return flowRulesKey; - } + public String getFlowRulesKey() { + return flowRulesKey; + } - public void setFlowRulesKey(String flowRulesKey) { - this.flowRulesKey = flowRulesKey; - } + public void setFlowRulesKey(String flowRulesKey) { + this.flowRulesKey = flowRulesKey; + } - public String getDefaultFlowRuleValue() { - return defaultFlowRuleValue; - } + public String getDefaultFlowRuleValue() { + return defaultFlowRuleValue; + } - public void setDefaultFlowRuleValue(String defaultFlowRuleValue) { - this.defaultFlowRuleValue = defaultFlowRuleValue; - } + public void setDefaultFlowRuleValue(String defaultFlowRuleValue) { + this.defaultFlowRuleValue = defaultFlowRuleValue; + } - public Converter getConverter() { - return converter; - } + public Converter getConverter() { + return converter; + } - public void setConverter(Converter converter) { - this.converter = converter; - } + public void setConverter(Converter converter) { + this.converter = converter; + } } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java index 69ced480..13c3830b 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/FileRefreshableDataSourceFactoryBean.java @@ -3,11 +3,11 @@ package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; import java.io.File; import java.nio.charset.Charset; +import org.springframework.beans.factory.FactoryBean; + import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; -import org.springframework.beans.factory.FactoryBean; - /** * A {@link FactoryBean} for creating {@link FileRefreshableDataSource} instance. * @@ -23,7 +23,7 @@ public class FileRefreshableDataSourceFactoryBean private int bufSize; private Converter converter; - @Override + @Override public FileRefreshableDataSource getObject() throws Exception { return new FileRefreshableDataSource(new File(file), converter, recommendRefreshMs, bufSize, Charset.forName(charset)); diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java index d7894418..7d4e65b4 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceFactoryBean.java @@ -1,12 +1,13 @@ package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; +import java.util.Properties; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.StringUtils; + import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; import com.alibaba.nacos.api.PropertyKeyConst; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.util.StringUtils; - -import java.util.Properties; /** * A {@link FactoryBean} for creating {@link NacosDataSource} instance. diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java index b266f01f..ae7142c0 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/ZookeeperDataSourceFactoryBean.java @@ -1,11 +1,11 @@ package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; -import com.alibaba.csp.sentinel.datasource.Converter; -import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; - import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.FactoryBean; +import com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource; + /** * A {@link FactoryBean} for creating {@link ZookeeperDataSource} instance. * @@ -14,68 +14,69 @@ import org.springframework.beans.factory.FactoryBean; */ public class ZookeeperDataSourceFactoryBean implements FactoryBean { - private String serverAddr; + private String serverAddr; - private String path; + private String path; - private String groupId; - private String dataId; + private String groupId; + private String dataId; - private Converter converter; + private Converter converter; - @Override - public ZookeeperDataSource getObject() throws Exception { - if (StringUtils.isNotEmpty(groupId) && StringUtils.isNotEmpty(dataId)) { - // the path will be /{groupId}/{dataId} - return new ZookeeperDataSource(serverAddr, groupId, dataId, converter); - } else { - // using path directly - return new ZookeeperDataSource(serverAddr, path, converter); - } - } + @Override + public ZookeeperDataSource getObject() throws Exception { + if (StringUtils.isNotEmpty(groupId) && StringUtils.isNotEmpty(dataId)) { + // the path will be /{groupId}/{dataId} + return new ZookeeperDataSource(serverAddr, groupId, dataId, converter); + } + else { + // using path directly + return new ZookeeperDataSource(serverAddr, path, converter); + } + } - @Override - public Class getObjectType() { - return ZookeeperDataSource.class; - } + @Override + public Class getObjectType() { + return ZookeeperDataSource.class; + } - public String getServerAddr() { - return serverAddr; - } + public String getServerAddr() { + return serverAddr; + } - public void setServerAddr(String serverAddr) { - this.serverAddr = serverAddr; - } + public void setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + } - public String getPath() { - return path; - } + public String getPath() { + return path; + } - public void setPath(String path) { - this.path = path; - } + public void setPath(String path) { + this.path = path; + } - public String getGroupId() { - return groupId; - } + public String getGroupId() { + return groupId; + } - public void setGroupId(String groupId) { - this.groupId = groupId; - } + public void setGroupId(String groupId) { + this.groupId = groupId; + } - public String getDataId() { - return dataId; - } + public String getDataId() { + return dataId; + } - public void setDataId(String dataId) { - this.dataId = dataId; - } + public void setDataId(String dataId) { + this.dataId = dataId; + } - public Converter getConverter() { - return converter; - } + public Converter getConverter() { + return converter; + } - public void setConverter(Converter converter) { - this.converter = converter; - } + public void setConverter(Converter converter) { + this.converter = converter; + } } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/test/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelConverterTests.java b/spring-cloud-alibaba-sentinel-datasource/src/test/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelConverterTests.java index c6ae7a67..c4ffdc3b 100644 --- a/spring-cloud-alibaba-sentinel-datasource/src/test/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelConverterTests.java +++ b/spring-cloud-alibaba-sentinel-datasource/src/test/java/org/springframework/cloud/alibaba/sentinel/datasource/SentinelConverterTests.java @@ -46,7 +46,7 @@ public class SentinelConverterTests { @Test public void testJsonConverter() { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); - List flowRules = jsonConverter + List flowRules = (List) jsonConverter .convert(readFileContent("classpath: flowrule.json")); assertEquals("json converter flow rule size was wrong", 1, flowRules.size()); assertEquals("json converter flow rule resource name was wrong", "resource", @@ -67,7 +67,7 @@ public class SentinelConverterTests { @Test public void testConverterEmptyContent() { JsonConverter jsonConverter = new JsonConverter(objectMapper, FlowRule.class); - List flowRules = jsonConverter.convert(""); + List flowRules = (List) jsonConverter.convert(""); assertEquals("json converter flow rule size was not empty", 0, flowRules.size()); } @@ -86,7 +86,7 @@ public class SentinelConverterTests { @Test public void testXmlConverter() { XmlConverter jsonConverter = new XmlConverter(xmlMapper, FlowRule.class); - List flowRules = jsonConverter + List flowRules = (List) jsonConverter .convert(readFileContent("classpath: flowrule.xml")); assertEquals("xml converter flow rule size was wrong", 2, flowRules.size()); assertEquals("xml converter flow rule1 resource name was wrong", "resource", diff --git a/spring-cloud-alibaba-sentinel-zuul/README.md b/spring-cloud-alibaba-sentinel-gateway/README.md similarity index 98% rename from spring-cloud-alibaba-sentinel-zuul/README.md rename to spring-cloud-alibaba-sentinel-gateway/README.md index 2d0d2fd1..8c8c4a02 100755 --- a/spring-cloud-alibaba-sentinel-zuul/README.md +++ b/spring-cloud-alibaba-sentinel-gateway/README.md @@ -14,7 +14,7 @@ Sentinel can provide `ServiceId` level and `API Path` level flow control for spr ```xml org.springframework.cloud - spring-cloud-alibaba-sentinel-zuul + spring-cloud-alibaba-sentinel-gateway x.y.z diff --git a/spring-cloud-alibaba-sentinel-zuul/pom.xml b/spring-cloud-alibaba-sentinel-gateway/pom.xml similarity index 73% rename from spring-cloud-alibaba-sentinel-zuul/pom.xml rename to spring-cloud-alibaba-sentinel-gateway/pom.xml index 3f59bc1f..7675db8f 100644 --- a/spring-cloud-alibaba-sentinel-zuul/pom.xml +++ b/spring-cloud-alibaba-sentinel-gateway/pom.xml @@ -10,19 +10,28 @@ 4.0.0 org.springframework.cloud - spring-cloud-alibaba-sentinel-zuul - Spring Cloud Alibaba Sentinel Zuul + spring-cloud-alibaba-sentinel-gateway + Spring Cloud Alibaba Sentinel Gateway org.springframework.cloud spring-cloud-starter-netflix-zuul - provided + true com.alibaba.csp sentinel-zuul-adapter + + com.alibaba.csp + sentinel-spring-cloud-gateway-adapter + + + org.springframework.cloud + spring-cloud-starter-gateway + true + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/ConfigConstants.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/ConfigConstants.java new file mode 100644 index 00000000..e3766d88 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/ConfigConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway; + +/** + * @author Jim + */ +public interface ConfigConstants { + + String APP_TYPE_ZUUL_GATEWAY = "12"; + + String ZUUl_PREFIX = "spring.cloud.sentinel.zuul"; + + String GATEWAY_PREFIX = "spring.cloud.sentinel.scg"; + + String FALLBACK_MSG_RESPONSE = "response"; + String FALLBACK_REDIRECT = "redirect"; + +} diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/FallbackProperties.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/FallbackProperties.java new file mode 100644 index 00000000..5899c1e9 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/FallbackProperties.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +/** + * @author Jim + */ +public class FallbackProperties { + + /** + * The fallback mode for sentinel spring-cloud-gateway. choose `redirect` or + * `response`. + */ + private String mode; + /** + * Redirect Url for `redirect` mode. + */ + private String redirect; + /** + * Response Body for `response` mode. + */ + private String responseBody; + /** + * Response Status for `response` mode. + */ + private Integer responseStatus = HttpStatus.TOO_MANY_REQUESTS.value(); + /** + * Content-Type for `response` mode. + */ + private String contentType = MediaType.APPLICATION_JSON_UTF8.toString(); + + public String getMode() { + return mode; + } + + public FallbackProperties setMode(String mode) { + this.mode = mode; + return this; + } + + public String getRedirect() { + return redirect; + } + + public FallbackProperties setRedirect(String redirect) { + this.redirect = redirect; + return this; + } + + public String getResponseBody() { + return responseBody; + } + + public FallbackProperties setResponseBody(String responseBody) { + this.responseBody = responseBody; + return this; + } + + public Integer getResponseStatus() { + return responseStatus; + } + + public FallbackProperties setResponseStatus(Integer responseStatus) { + this.responseStatus = responseStatus; + return this; + } + + public String getContentType() { + return contentType; + } + + public FallbackProperties setContentType(String contentType) { + this.contentType = contentType; + return this; + } +} diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelGatewayProperties.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelGatewayProperties.java new file mode 100644 index 00000000..cfbdf9ab --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelGatewayProperties.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway.scg; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.cloud.alibaba.sentinel.gateway.ConfigConstants; +import org.springframework.cloud.alibaba.sentinel.gateway.FallbackProperties; + +/** + * @author Jim + */ +@ConfigurationProperties(prefix = ConfigConstants.GATEWAY_PREFIX) +public class SentinelGatewayProperties { + + @NestedConfigurationProperty + private FallbackProperties fallback; + + public FallbackProperties getFallback() { + return fallback; + } + + public SentinelGatewayProperties setFallback(FallbackProperties fallback) { + this.fallback = fallback; + return this; + } + +} diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelSCGAutoConfiguration.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelSCGAutoConfiguration.java new file mode 100644 index 00000000..393e63cc --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/scg/SentinelSCGAutoConfiguration.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway.scg; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alibaba.sentinel.gateway.ConfigConstants; +import org.springframework.cloud.alibaba.sentinel.gateway.FallbackProperties; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.result.view.ViewResolver; +import org.springframework.web.server.ServerWebExchange; + +import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; +import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter; +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler; +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.RedirectBlockRequestHandler; +import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.util.StringUtil; + +import reactor.core.publisher.Mono; + +/** + * @author Jim + */ +@Configuration +@ConditionalOnClass(GlobalFilter.class) +@ConditionalOnProperty(prefix = ConfigConstants.GATEWAY_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(SentinelGatewayProperties.class) +public class SentinelSCGAutoConfiguration { + + private static final Logger logger = LoggerFactory + .getLogger(SentinelSCGAutoConfiguration.class); + + private final List viewResolvers; + private final ServerCodecConfigurer serverCodecConfigurer; + + @Autowired + private Optional blockRequestHandlerOptional; + + @Autowired + private SentinelGatewayProperties gatewayProperties; + + @PostConstruct + private void init() { + // blockRequestHandlerOptional has low priority + blockRequestHandlerOptional.ifPresent(GatewayCallbackManager::setBlockHandler); + initAppType(); + initFallback(); + } + + public SentinelSCGAutoConfiguration( + ObjectProvider> viewResolversProvider, + ServerCodecConfigurer serverCodecConfigurer) { + this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); + this.serverCodecConfigurer = serverCodecConfigurer; + } + + private void initAppType() { + System.setProperty(SentinelConfig.APP_TYPE, + String.valueOf(SentinelGatewayConstants.APP_TYPE_GATEWAY)); + } + + private void initFallback() { + FallbackProperties fallbackProperties = gatewayProperties.getFallback(); + if (fallbackProperties == null + || StringUtil.isBlank(fallbackProperties.getMode())) { + return; + } + if (ConfigConstants.FALLBACK_MSG_RESPONSE.equals(fallbackProperties.getMode())) { + if (StringUtil.isNotBlank(fallbackProperties.getResponseBody())) { + GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() { + @Override + public Mono handleRequest(ServerWebExchange exchange, + Throwable t) { + return ServerResponse + .status(fallbackProperties.getResponseStatus()) + .contentType(MediaType + .valueOf(fallbackProperties.getContentType())) + .body(fromObject(fallbackProperties.getResponseBody())); + } + }); + logger.info( + "[Sentinel SpringCloudGateway] using AnonymousBlockRequestHandler, responseStatus: " + + fallbackProperties.getResponseStatus() + + ", responseBody: " + + fallbackProperties.getResponseStatus()); + } + } + String redirectUrl = fallbackProperties.getRedirect(); + if (ConfigConstants.FALLBACK_REDIRECT.equals(fallbackProperties.getMode()) + && StringUtil.isNotBlank(redirectUrl)) { + GatewayCallbackManager + .setBlockHandler(new RedirectBlockRequestHandler(redirectUrl)); + logger.info( + "[Sentinel SpringCloudGateway] using RedirectBlockRequestHandler, redirectUrl: " + + redirectUrl); + } + } + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + @ConditionalOnMissingBean + public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { + // Register the block exception handler for Spring Cloud Gateway. + logger.info( + "[Sentinel SpringCloudGateway] register SentinelGatewayBlockExceptionHandler"); + return new SentinelGatewayBlockExceptionHandler(viewResolvers, + serverCodecConfigurer); + } + + @Bean + @Order(-1) + @ConditionalOnMissingBean + public SentinelGatewayFilter sentinelGatewayFilter() { + logger.info("[Sentinel SpringCloudGateway] register SentinelGatewayFilter"); + return new SentinelGatewayFilter(); + } + +} diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/handler/FallBackProviderHandler.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/FallBackProviderHandler.java similarity index 72% rename from spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/handler/FallBackProviderHandler.java rename to spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/FallBackProviderHandler.java index 564fc446..be27b988 100644 --- a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/handler/FallBackProviderHandler.java +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/FallBackProviderHandler.java @@ -1,16 +1,16 @@ -package org.springframework.cloud.alibaba.sentinel.zuul.handler; +package org.springframework.cloud.alibaba.sentinel.gateway.zuul; import java.util.Map; -import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.util.CollectionUtils; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultBlockFallbackProvider; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.ZuulBlockFallbackManager; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.ZuulBlockFallbackProvider; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.DefaultBlockFallbackProvider; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.fallback.ZuulBlockFallbackProvider; /** * @author tiger @@ -30,7 +30,7 @@ public class FallBackProviderHandler implements SmartInitializingSingleton { public void afterSingletonsInstantiated() { Map providerMap = beanFactory .getBeansOfType(ZuulBlockFallbackProvider.class); - if (MapUtils.isNotEmpty(providerMap)) { + if (!CollectionUtils.isEmpty(providerMap)) { providerMap.forEach((k, v) -> { logger.info("[Sentinel Zuul] Register provider name:{}, instance: {}", k, v); diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulAutoConfiguration.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulAutoConfiguration.java new file mode 100644 index 00000000..0867df6a --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulAutoConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway.zuul; + +import java.util.Optional; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +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.alibaba.sentinel.gateway.ConfigConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.csp.sentinel.adapter.gateway.zuul.callback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.callback.ZuulGatewayCallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter; +import com.alibaba.csp.sentinel.config.SentinelConfig; + +import com.netflix.zuul.http.ZuulServlet; + +/** + * Sentinel Spring Cloud Zuul AutoConfiguration + * + * @author tiger + */ +@Configuration +@ConditionalOnClass(ZuulServlet.class) +@ConditionalOnProperty(prefix = ConfigConstants.ZUUl_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(SentinelZuulProperties.class) +public class SentinelZuulAutoConfiguration { + + private static final Logger logger = LoggerFactory + .getLogger(SentinelZuulAutoConfiguration.class); + + @Autowired + private Optional requestOriginParserOptional; + + @Autowired + private SentinelZuulProperties zuulProperties; + + @PostConstruct + private void init() { + requestOriginParserOptional + .ifPresent(ZuulGatewayCallbackManager::setOriginParser); + System.setProperty(SentinelConfig.APP_TYPE, + String.valueOf(ConfigConstants.APP_TYPE_ZUUL_GATEWAY)); + } + + @Bean + @ConditionalOnMissingBean + public SentinelZuulPreFilter sentinelZuulPreFilter() { + logger.info("[Sentinel Zuul] register SentinelZuulPreFilter {}", + zuulProperties.getOrder().getPre()); + return new SentinelZuulPreFilter(zuulProperties.getOrder().getPre()); + } + + @Bean + @ConditionalOnMissingBean + public SentinelZuulPostFilter sentinelZuulPostFilter() { + logger.info("[Sentinel Zuul] register SentinelZuulPostFilter {}", + zuulProperties.getOrder().getPost()); + return new SentinelZuulPostFilter(zuulProperties.getOrder().getPost()); + } + + @Bean + @ConditionalOnMissingBean + public SentinelZuulErrorFilter sentinelZuulErrorFilter() { + logger.info("[Sentinel Zuul] register SentinelZuulErrorFilter {}", + zuulProperties.getOrder().getError()); + return new SentinelZuulErrorFilter(zuulProperties.getOrder().getError()); + } + + @Bean + public FallBackProviderHandler fallBackProviderHandler( + DefaultListableBeanFactory beanFactory) { + return new FallBackProviderHandler(beanFactory); + } + +} diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulProperties.java b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulProperties.java new file mode 100644 index 00000000..102baae8 --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/java/org/springframework/cloud/alibaba/sentinel/gateway/zuul/SentinelZuulProperties.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.alibaba.sentinel.gateway.zuul; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.cloud.alibaba.sentinel.gateway.ConfigConstants; + +import com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulErrorFilter; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPostFilter; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.filters.SentinelZuulPreFilter; + +/** + * @author Jim + */ +@ConfigurationProperties(prefix = ConfigConstants.ZUUl_PREFIX) +public class SentinelZuulProperties { + + @NestedConfigurationProperty + private SentinelZuulProperties.Order order; + + public Order getOrder() { + return order; + } + + public SentinelZuulProperties setOrder(Order order) { + this.order = order; + return this; + } + + public static class Order { + + /** + * The order of {@link SentinelZuulPreFilter}. + */ + private int pre = 10000; + + /** + * The order of {@link SentinelZuulPostFilter}. + */ + private int post = ZuulConstant.SEND_RESPONSE_FILTER_ORDER; + + /** + * The order of {@link SentinelZuulErrorFilter}. + */ + private int error = -1; + + public int getPre() { + return pre; + } + + public void setPre(int pre) { + this.pre = pre; + } + + public int getPost() { + return post; + } + + public void setPost(int post) { + this.post = post; + } + + public int getError() { + return error; + } + + public void setError(int error) { + this.error = error; + } + } + +} diff --git a/spring-cloud-alibaba-sentinel-gateway/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel-gateway/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..15297fce --- /dev/null +++ b/spring-cloud-alibaba-sentinel-gateway/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.alibaba.sentinel.gateway.zuul.SentinelZuulAutoConfiguration,\ +org.springframework.cloud.alibaba.sentinel.gateway.scg.SentinelSCGAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java b/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java deleted file mode 100644 index 35577cae..00000000 --- a/spring-cloud-alibaba-sentinel-zuul/src/main/java/org/springframework/cloud/alibaba/sentinel/zuul/SentinelZuulAutoConfiguration.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * 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. - */ - -package org.springframework.cloud.alibaba.sentinel.zuul; - -import static org.springframework.cloud.alibaba.sentinel.zuul.SentinelZuulAutoConfiguration.PREFIX; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.alibaba.sentinel.zuul.handler.FallBackProviderHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultRequestOriginParser; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.DefaultUrlCleaner; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.RequestOriginParser; -import com.alibaba.csp.sentinel.adapter.zuul.fallback.UrlCleaner; -import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelErrorFilter; -import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelPostFilter; -import com.alibaba.csp.sentinel.adapter.zuul.filters.SentinelPreFilter; -import com.alibaba.csp.sentinel.adapter.zuul.properties.SentinelZuulProperties; -import com.alibaba.csp.sentinel.util.StringUtil; - -import com.netflix.zuul.ZuulFilter; - -/** - * Sentinel Spring Cloud Zuul AutoConfiguration - * - * @author tiger - */ -@Configuration -@ConditionalOnProperty(prefix = PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) -public class SentinelZuulAutoConfiguration { - - @Autowired - private Environment environment; - - public static final String PREFIX = "spring.cloud.sentinel.zuul"; - - @Bean - public SentinelZuulProperties sentinelZuulProperties() { - SentinelZuulProperties properties = new SentinelZuulProperties(); - String enabledStr = environment.getProperty(PREFIX + "." + "enabled"); - String preOrderStr = environment.getProperty(PREFIX + "." + "order.pre"); - String postOrderStr = environment.getProperty(PREFIX + "." + "order.post"); - String errorOrderStr = environment.getProperty(PREFIX + "." + "order.error"); - if (StringUtil.isNotEmpty(enabledStr)) { - Boolean enabled = Boolean.valueOf(enabledStr); - properties.setEnabled(enabled); - } - if (StringUtil.isNotEmpty(preOrderStr)) { - properties.getOrder().setPre(Integer.parseInt(preOrderStr)); - } - if (StringUtil.isNotEmpty(postOrderStr)) { - properties.getOrder().setPost(Integer.parseInt(postOrderStr)); - } - if (StringUtil.isNotEmpty(errorOrderStr)) { - properties.getOrder().setError(Integer.parseInt(errorOrderStr)); - } - return properties; - } - - @Bean - @ConditionalOnMissingBean(UrlCleaner.class) - public UrlCleaner urlCleaner() { - return new DefaultUrlCleaner(); - } - - @Bean - @ConditionalOnMissingBean(RequestOriginParser.class) - public RequestOriginParser requestOriginParser() { - return new DefaultRequestOriginParser(); - } - - @Bean - public ZuulFilter preFilter(SentinelZuulProperties sentinelZuulProperties, - UrlCleaner urlCleaner, RequestOriginParser requestOriginParser) { - return new SentinelPreFilter(sentinelZuulProperties, urlCleaner, - requestOriginParser); - } - - @Bean - public ZuulFilter postFilter(SentinelZuulProperties sentinelZuulProperties) { - return new SentinelPostFilter(sentinelZuulProperties); - } - - @Bean - public ZuulFilter errorFilter(SentinelZuulProperties sentinelZuulProperties) { - return new SentinelErrorFilter(sentinelZuulProperties); - } - - @Bean - public FallBackProviderHandler fallBackProviderListener( - DefaultListableBeanFactory beanFactory) { - return new FallBackProviderHandler(beanFactory); - } - -} diff --git a/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories deleted file mode 100644 index b4ced151..00000000 --- a/spring-cloud-alibaba-sentinel-zuul/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.alibaba.sentinel.zuul.SentinelZuulAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index 1ac1a212..a832136b 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -15,11 +15,6 @@ - - com.alibaba.csp - sentinel-web-servlet - - com.alibaba.csp sentinel-transport-simple-http @@ -42,6 +37,28 @@ true + + com.alibaba.csp + sentinel-web-servlet + + + + org.springframework.boot + spring-boot-starter-web + true + + + + com.alibaba.csp + sentinel-spring-webflux-adapter + + + + org.springframework.boot + spring-boot-starter-webflux + true + + org.springframework.cloud spring-cloud-starter-openfeign @@ -88,6 +105,11 @@ provided + + com.alibaba.csp + sentinel-api-gateway-adapter-common + + @@ -129,12 +151,6 @@ provided true - - org.springframework.boot - spring-boot-starter-web - provided - true - org.springframework.boot diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java index 5a73f2cb..37d77137 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebAutoConfiguration.java @@ -18,26 +18,35 @@ package org.springframework.cloud.alibaba.sentinel; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import javax.annotation.PostConstruct; import javax.servlet.Filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; +import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler; +import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; +import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; /** * @author xiaojing */ @Configuration -@ConditionalOnWebApplication +@ConditionalOnWebApplication(type = Type.SERVLET) +@ConditionalOnClass(CommonFilter.class) @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) @EnableConfigurationProperties(SentinelProperties.class) public class SentinelWebAutoConfiguration { @@ -48,6 +57,22 @@ public class SentinelWebAutoConfiguration { @Autowired private SentinelProperties properties; + @Autowired + private Optional urlCleanerOptional; + + @Autowired + private Optional urlBlockHandlerOptional; + + @Autowired + private Optional requestOriginParserOptional; + + @PostConstruct + public void init() { + urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); + urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); + requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser); + } + @Bean @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) public FilterRegistrationBean sentinelFilter() { @@ -66,7 +91,8 @@ public class SentinelWebAutoConfiguration { Filter filter = new CommonFilter(); registration.setFilter(filter); registration.setOrder(filterConfig.getOrder()); - log.info("[Sentinel Starter] register Sentinel with urlPatterns: {}.", + log.info( + "[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns()); return registration; diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebFluxAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebFluxAutoConfiguration.java new file mode 100644 index 00000000..f36f326e --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelWebFluxAutoConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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. + */ + +package org.springframework.cloud.alibaba.sentinel; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.web.reactive.result.view.ViewResolver; + +import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; +import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter; +import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.BlockRequestHandler; +import com.alibaba.csp.sentinel.adapter.spring.webflux.callback.WebFluxCallbackManager; +import com.alibaba.csp.sentinel.adapter.spring.webflux.exception.SentinelBlockExceptionHandler; + +/** + * @author Jim + */ +@Configuration +@ConditionalOnWebApplication(type = Type.REACTIVE) +@ConditionalOnClass(SentinelReactorTransformer.class) +@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) +@EnableConfigurationProperties(SentinelProperties.class) +public class SentinelWebFluxAutoConfiguration { + + private static final Logger log = LoggerFactory + .getLogger(SentinelWebFluxAutoConfiguration.class); + + private final List viewResolvers; + private final ServerCodecConfigurer serverCodecConfigurer; + + @Autowired + private Optional blockRequestHandler; + + public SentinelWebFluxAutoConfiguration( + ObjectProvider> viewResolvers, + ServerCodecConfigurer serverCodecConfigurer) { + this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList); + this.serverCodecConfigurer = serverCodecConfigurer; + } + + @PostConstruct + public void init() { + blockRequestHandler.ifPresent(WebFluxCallbackManager::setBlockHandler); + } + + @Bean + @Order(-2) + @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) + public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { + return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); + } + + @Bean + @Order(-1) + @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) + public SentinelWebFluxFilter sentinelWebFluxFilter() { + log.info("[Sentinel Starter] register Sentinel SentinelWebFluxFilter"); + return new SentinelWebFluxFilter(); + } + +} \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java index d2423e00..00f7576e 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelAutoConfiguration.java @@ -16,7 +16,11 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import java.util.Optional; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import javax.annotation.PostConstruct; @@ -33,12 +37,14 @@ import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConver import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; -import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; -import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler; -import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; -import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateGroupItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect; import com.alibaba.csp.sentinel.config.SentinelConfig; @@ -52,7 +58,15 @@ import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.util.AppNameUtil; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; /** @@ -71,15 +85,6 @@ public class SentinelAutoConfiguration { @Autowired private SentinelProperties properties; - @Autowired - private Optional urlCleanerOptional; - - @Autowired - private Optional urlBlockHandlerOptional; - - @Autowired - private Optional requestOriginParserOptional; - @PostConstruct private void init() { if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) @@ -142,10 +147,6 @@ public class SentinelAutoConfiguration { WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); } - urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); - urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); - requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser); - // earlier initialize if (properties.isEager()) { InitExecutor.doInit(); @@ -170,72 +171,164 @@ public class SentinelAutoConfiguration { @Bean public SentinelDataSourceHandler sentinelDataSourceHandler( - DefaultListableBeanFactory beanFactory) { - return new SentinelDataSourceHandler(beanFactory); + DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties, + Environment env) { + return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env); } @ConditionalOnClass(ObjectMapper.class) + @Configuration protected static class SentinelConverterConfiguration { - private ObjectMapper objectMapper = new ObjectMapper(); + static class ApiPredicateItemDeserializer + extends StdDeserializer { + private Map> registry = new HashMap>(); - @Bean("sentinel-json-flow-converter") - public JsonConverter jsonFlowConverter() { - return new JsonConverter(objectMapper, FlowRule.class); + ApiPredicateItemDeserializer() { + super(ApiPredicateItem.class); + } + + void registerApiPredicateItem(String uniqueAttribute, + Class apiPredicateItemClass) { + registry.put(uniqueAttribute, apiPredicateItemClass); + } + + @Override + public ApiPredicateItem deserialize(JsonParser jp, + DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + ObjectNode root = mapper.readTree(jp); + Class apiPredicateItemClass = null; + Iterator> elementsIterator = root.fields(); + while (elementsIterator.hasNext()) { + Entry element = elementsIterator.next(); + String name = element.getKey(); + if (registry.containsKey(name)) { + apiPredicateItemClass = registry.get(name); + break; + } + } + if (apiPredicateItemClass == null) { + return null; + } + return mapper.readValue(root.toString(), apiPredicateItemClass); + } } - @Bean("sentinel-json-degrade-converter") - public JsonConverter jsonDegradeConverter() { - return new JsonConverter(objectMapper, DegradeRule.class); + @Configuration + protected static class SentinelJsonConfiguration { + + private ObjectMapper objectMapper = new ObjectMapper(); + + public SentinelJsonConfiguration() { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + + ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer(); + deserializer.registerApiPredicateItem("pattern", + ApiPathPredicateItem.class); + deserializer.registerApiPredicateItem("items", + ApiPredicateGroupItem.class); + SimpleModule module = new SimpleModule( + "PolymorphicApiPredicateItemDeserializerModule", + new Version(1, 0, 0, null)); + module.addDeserializer(ApiPredicateItem.class, deserializer); + objectMapper.registerModule(module); + } + + @Bean("sentinel-json-flow-converter") + public JsonConverter jsonFlowConverter() { + return new JsonConverter(objectMapper, FlowRule.class); + } + + @Bean("sentinel-json-degrade-converter") + public JsonConverter jsonDegradeConverter() { + return new JsonConverter(objectMapper, DegradeRule.class); + } + + @Bean("sentinel-json-system-converter") + public JsonConverter jsonSystemConverter() { + return new JsonConverter(objectMapper, SystemRule.class); + } + + @Bean("sentinel-json-authority-converter") + public JsonConverter jsonAuthorityConverter() { + return new JsonConverter(objectMapper, AuthorityRule.class); + } + + @Bean("sentinel-json-param-flow-converter") + public JsonConverter jsonParamFlowConverter() { + return new JsonConverter(objectMapper, ParamFlowRule.class); + } + + @Bean("sentinel-json-gw-flow-converter") + public JsonConverter jsonGatewayFlowConverter() { + return new JsonConverter(objectMapper, GatewayFlowRule.class); + } + + @Bean("sentinel-json-gw-api-group-converter") + public JsonConverter jsonApiConverter() { + return new JsonConverter(objectMapper, ApiDefinition.class); + } } - @Bean("sentinel-json-system-converter") - public JsonConverter jsonSystemConverter() { - return new JsonConverter(objectMapper, SystemRule.class); + @ConditionalOnClass(XmlMapper.class) + @Configuration + protected static class SentinelXmlConfiguration { + + private XmlMapper xmlMapper = new XmlMapper(); + + public SentinelXmlConfiguration() { + xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer(); + deserializer.registerApiPredicateItem("pattern", + ApiPathPredicateItem.class); + deserializer.registerApiPredicateItem("items", + ApiPredicateGroupItem.class); + SimpleModule module = new SimpleModule( + "PolymorphicGatewayDeserializerModule", + new Version(1, 0, 0, null)); + module.addDeserializer(ApiPredicateItem.class, deserializer); + xmlMapper.registerModule(module); + } + + @Bean("sentinel-xml-flow-converter") + public XmlConverter xmlFlowConverter() { + return new XmlConverter(xmlMapper, FlowRule.class); + } + + @Bean("sentinel-xml-degrade-converter") + public XmlConverter xmlDegradeConverter() { + return new XmlConverter(xmlMapper, DegradeRule.class); + } + + @Bean("sentinel-xml-system-converter") + public XmlConverter xmlSystemConverter() { + return new XmlConverter(xmlMapper, SystemRule.class); + } + + @Bean("sentinel-xml-authority-converter") + public XmlConverter xmlAuthorityConverter() { + return new XmlConverter(xmlMapper, AuthorityRule.class); + } + + @Bean("sentinel-xml-param-flow-converter") + public XmlConverter xmlParamFlowConverter() { + return new XmlConverter(xmlMapper, ParamFlowRule.class); + } + + @Bean("sentinel-xml-gw-flow-converter") + public XmlConverter xmlGatewayFlowConverter() { + return new XmlConverter(xmlMapper, GatewayFlowRule.class); + } + + @Bean("sentinel-xml-gw-api-group-converter") + public XmlConverter xmlApiConverter() { + return new XmlConverter(xmlMapper, ApiDefinition.class); + } + } - - @Bean("sentinel-json-authority-converter") - public JsonConverter jsonAuthorityConverter() { - return new JsonConverter(objectMapper, AuthorityRule.class); - } - - @Bean("sentinel-json-param-flow-converter") - public JsonConverter jsonParamFlowConverter() { - return new JsonConverter(objectMapper, ParamFlowRule.class); - } - - } - - @ConditionalOnClass(XmlMapper.class) - protected static class SentinelXmlConfiguration { - - private XmlMapper xmlMapper = new XmlMapper(); - - @Bean("sentinel-xml-flow-converter") - public XmlConverter xmlFlowConverter() { - return new XmlConverter(xmlMapper, FlowRule.class); - } - - @Bean("sentinel-xml-degrade-converter") - public XmlConverter xmlDegradeConverter() { - return new XmlConverter(xmlMapper, DegradeRule.class); - } - - @Bean("sentinel-xml-system-converter") - public XmlConverter xmlSystemConverter() { - return new XmlConverter(xmlMapper, SystemRule.class); - } - - @Bean("sentinel-xml-authority-converter") - public XmlConverter xmlAuthorityConverter() { - return new XmlConverter(xmlMapper, AuthorityRule.class); - } - - @Bean("sentinel-xml-param-flow-converter") - public XmlConverter xmlParamFlowConverter() { - return new XmlConverter(xmlMapper, ParamFlowRule.class); - } - } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java index df57f919..f1dd0cfc 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java @@ -1,23 +1,5 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import com.alibaba.csp.sentinel.datasource.AbstractDataSource; -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; -import com.alibaba.csp.sentinel.slots.block.AbstractRule; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.cloud.alibaba.sentinel.SentinelProperties; -import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties; -import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; -import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; @@ -25,6 +7,22 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; +import org.springframework.core.env.Environment; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; + /** * Sentinel {@link ReadableDataSource} Handler Handle the configurations of * 'spring.cloud.sentinel.datasource' @@ -47,12 +45,16 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton { private final DefaultListableBeanFactory beanFactory; - public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory) { - this.beanFactory = beanFactory; - } + private final SentinelProperties sentinelProperties; - @Autowired - private SentinelProperties sentinelProperties; + private final Environment env; + + public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory, + SentinelProperties sentinelProperties, Environment env) { + this.beanFactory = beanFactory; + this.sentinelProperties = sentinelProperties; + this.env = env; + } @Override public void afterSingletonsInstantiated() { @@ -68,6 +70,7 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton { } AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties .getValidDataSourceProperties(); + abstractDataSourceProperties.setEnv(env); abstractDataSourceProperties.preCheck(dataSourceName); registerBean(abstractDataSourceProperties, dataSourceName + "-sentinel-" + validFields.get(0) + "-datasource"); @@ -181,56 +184,8 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton { AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory .getBean(dataSourceName); - logAndCheckRuleType(newDataSource, dataSourceName, - dataSourceProperties.getRuleType().getClazz()); - // register property in RuleManager dataSourceProperties.postRegister(newDataSource); } - private void logAndCheckRuleType(AbstractDataSource dataSource, String dataSourceName, - Class ruleClass) { - Object ruleConfig; - try { - ruleConfig = dataSource.loadConfig(); - } - catch (Exception e) { - log.error("[Sentinel Starter] DataSource " + dataSourceName - + " loadConfig error: " + e.getMessage(), e); - return; - } - if (ruleConfig instanceof List) { - List convertedRuleList = (List) ruleConfig; - if (CollectionUtils.isEmpty(convertedRuleList)) { - log.warn("[Sentinel Starter] DataSource {} rule list is empty.", - dataSourceName); - return; - } - if (convertedRuleList.stream() - .noneMatch(rule -> rule.getClass() == ruleClass)) { - log.error("[Sentinel Starter] DataSource {} none rules are {} type.", - dataSourceName, ruleClass.getSimpleName()); - throw new IllegalArgumentException("[Sentinel Starter] DataSource " - + dataSourceName + " none rules are " + ruleClass.getSimpleName() - + " type."); - } - else if (!convertedRuleList.stream() - .allMatch(rule -> rule.getClass() == ruleClass)) { - log.warn("[Sentinel Starter] DataSource {} all rules are not {} type.", - dataSourceName, ruleClass.getSimpleName()); - } - else { - log.info("[Sentinel Starter] DataSource {} load {} {}", dataSourceName, - convertedRuleList.size(), ruleClass.getSimpleName()); - } - } - else { - log.error("[Sentinel Starter] DataSource " + dataSourceName - + " rule class is not List<" + ruleClass.getSimpleName() - + ">. Class: " + ruleConfig.getClass()); - throw new IllegalArgumentException("[Sentinel Starter] DataSource " - + dataSourceName + " rule class is not List<" - + ruleClass.getSimpleName() + ">. Class: " + ruleConfig.getClass()); - } - } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java index 7c9b5d1c..9af7be97 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java @@ -16,7 +16,9 @@ package org.springframework.cloud.alibaba.sentinel.endpoint; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -38,4 +40,10 @@ public class SentinelEndpointAutoConfiguration { return new SentinelEndpoint(sentinelProperties); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledHealthIndicator("sentinel") + public SentinelHealthIndicator sentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) { + return new SentinelHealthIndicator(beanFactory, sentinelProperties); + } } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java new file mode 100644 index 00000000..8ca863ab --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 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. + */ + +package org.springframework.cloud.alibaba.sentinel.endpoint; + +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; +import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; +import com.alibaba.csp.sentinel.transport.HeartbeatSender; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link HealthIndicator} for Sentinel, which checks the status of + * Sentinel Dashboard and DataSource. + * + *

+ * Check the status of Sentinel Dashboard by sending a heartbeat message to it. + * If return true, it's OK. + * + * Check the status of Sentinel DataSource by calling loadConfig method of {@link AbstractDataSource}. + * If no Exception thrown, it's OK. + * + * If Dashboard and DataSource are both OK, the health status is UP. + *

+ * + *

+ * Note: + * If Sentinel isn't enabled, the health status is up. + * If Sentinel Dashboard isn't configured, it's OK and mark the status of Dashboard with UNKNOWN. + * More informations are provided in details. + *

+ * + * @author cdfive + */ +public class SentinelHealthIndicator extends AbstractHealthIndicator { + + private DefaultListableBeanFactory beanFactory; + + private SentinelProperties sentinelProperties; + + public SentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) { + this.beanFactory = beanFactory; + this.sentinelProperties = sentinelProperties; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + Map detailMap = new HashMap<>(); + + // If sentinel isn't enabled, set the status up and set the enabled to false in detail + if (!sentinelProperties.isEnabled()) { + detailMap.put("enabled", false); + builder.up().withDetails(detailMap); + return; + } + + detailMap.put("enabled", true); + + // Check health of Dashboard + boolean dashboardUp = true; + String consoleServer = TransportConfig.getConsoleServer(); + if (StringUtils.isEmpty(consoleServer)) { + // If Dashboard isn't configured, it's OK and mark the status of Dashboard with UNKNOWN. + detailMap.put("dashboard", new Status(Status.UNKNOWN.getCode(), "dashboard isn't configured")); + } else { + // If Dashboard is configured, send a heartbeat message to it and check the result + HeartbeatSender heartbeatSender = HeartbeatSenderProvider.getHeartbeatSender(); + boolean result = heartbeatSender.sendHeartbeat(); + if (result) { + detailMap.put("dashboard", Status.UP); + } else { + // If failed to send heartbeat message, means that the Dashboard is DOWN + dashboardUp = false; + detailMap.put("dashboard", new Status(Status.DOWN.getCode(), consoleServer + " can't be connected")); + } + } + + // Check health of DataSource + boolean dataSourceUp = true; + Map dataSourceDetailMap = new HashMap<>(); + detailMap.put("dataSource", dataSourceDetailMap); + + // Get all DataSources and each call loadConfig to check if it's OK + // If no Exception thrown, it's OK + // Note: + // Even if the dynamic config center is down, the loadConfig() might return successfully + // e.g. for Nacos client, it might retrieve from the local cache) + // But in most circumstances it's okay + Map dataSourceMap = beanFactory.getBeansOfType(AbstractDataSource.class); + for (Map.Entry dataSourceMapEntry : dataSourceMap.entrySet()) { + String dataSourceBeanName = dataSourceMapEntry.getKey(); + AbstractDataSource dataSource = dataSourceMapEntry.getValue(); + try { + dataSource.loadConfig(); + dataSourceDetailMap.put(dataSourceBeanName, Status.UP); + } catch (Exception e) { + // If one DataSource failed to loadConfig, means that the DataSource is DOWN + dataSourceUp = false; + dataSourceDetailMap.put(dataSourceBeanName, new Status(Status.DOWN.getCode(), e.getMessage())); + } + } + + // If Dashboard and DataSource are both OK, the health status is UP + if (dashboardUp && dataSourceUp) { + builder.up().withDetails(detailMap); + } else { + builder.down().withDetails(detailMap); + } + } +} diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java index 6056e800..e1a43d2e 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java @@ -25,7 +25,7 @@ import feign.MethodMetadata; /** * - * Using static field {@link SentinelContractHolder#metadataMap} to hold + * Using static field {@link SentinelContractHolder#METADATA_MAP} to hold * {@link MethodMetadata} data * * @author Jim @@ -38,7 +38,7 @@ public class SentinelContractHolder implements Contract { * map key is constructed by ClassFullName + configKey. configKey is constructed by * {@link feign.Feign#configKey} */ - public final static Map metadataMap = new HashMap(); + public final static Map METADATA_MAP = new HashMap<>(); public SentinelContractHolder(Contract delegate) { this.delegate = delegate; @@ -47,7 +47,7 @@ public class SentinelContractHolder implements Contract { @Override public List parseAndValidatateMetadata(Class targetType) { List metadatas = delegate.parseAndValidatateMetadata(targetType); - metadatas.forEach(metadata -> metadataMap + metadatas.forEach(metadata -> METADATA_MAP .put(targetType.getName() + metadata.configKey(), metadata)); return metadatas; } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java index f1f34fd5..7e7614e8 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java @@ -90,9 +90,9 @@ public class SentinelInvocationHandler implements InvocationHandler { // only handle by HardCodedTarget if (target instanceof Target.HardCodedTarget) { Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; - MethodMetadata methodMetadata = SentinelContractHolder.metadataMap - .get(method.getDeclaringClass().getName() - + Feign.configKey(method.getDeclaringClass(), method)); + MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP + .get(hardCodedTarget.type().getName() + + Feign.configKey(hardCodedTarget.type(), method)); // resource default is HttpMethod:protocol://url String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url() + methodMetadata.template().path(); diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 21713de7..ec4c63d7 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -88,6 +88,18 @@ "type": "java.lang.String", "defaultValue": "3", "description": "sentinel the cold factor." + }, + { + "name": "management.health.sentinel.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable sentinel health check.", + "defaultValue": true + }, + { + "defaultValue": "false", + "name": "feign.sentinel.enabled", + "description": "If true, an OpenFeign client will be wrapped with a Sentinel circuit breaker.", + "type": "java.lang.Boolean" } ] } diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories index d964e3b0..20f314cc 100644 --- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\ +org.springframework.cloud.alibaba.sentinel.SentinelWebFluxAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java index b7d03670..accd536f 100644 --- a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java @@ -60,6 +60,9 @@ public class SentinelFeignTests { @Autowired private BarService barService; + @Autowired + private BazService bazService; + @Before public void setUp() { FlowRule rule1 = new FlowRule(); @@ -83,7 +86,14 @@ public class SentinelFeignTests { rule3.setLimitApp("default"); rule3.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rule3.setStrategy(RuleConstant.STRATEGY_DIRECT); - FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3)); + FlowRule rule4 = new FlowRule(); + rule4.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule4.setCount(0); + rule4.setResource("GET:http://baz-service/baz"); + rule4.setLimitApp("default"); + rule4.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); + rule4.setStrategy(RuleConstant.STRATEGY_DIRECT); + FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3,rule4)); } @Test @@ -101,6 +111,9 @@ public class SentinelFeignTests { assertThatExceptionOfType(Exception.class).isThrownBy(() -> { barService.bar(); }); + assertThatExceptionOfType(Exception.class).isThrownBy(() -> { + bazService.baz(); + }); assertNotEquals("ToString method invoke was not in SentinelInvocationHandler", echoService.toString(), fooService.toString()); @@ -146,6 +159,15 @@ public class SentinelFeignTests { String bar(); } + public interface BazService { + @RequestMapping(path = "baz") + String baz(); + } + + @FeignClient(value = "baz-service") + public interface BazClient extends BazService { + } + public static class EchoServiceFallback implements EchoService { @Override diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java new file mode 100644 index 00000000..6245c4a1 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 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. + */ + +package org.springframework.cloud.alibaba.sentinel.endpoint; + +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; +import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource; +import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; +import com.alibaba.csp.sentinel.transport.HeartbeatSender; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.cloud.alibaba.sentinel.SentinelProperties; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test cases for {@link SentinelHealthIndicator}. + * + * @author cdfive + */ +public class SentinelHealthIndicatorTests { + + private SentinelHealthIndicator sentinelHealthIndicator; + + private DefaultListableBeanFactory beanFactory; + + private SentinelProperties sentinelProperties; + + private HeartbeatSender heartbeatSender; + + @Before + public void setUp() { + beanFactory = mock(DefaultListableBeanFactory.class); + sentinelProperties = mock(SentinelProperties.class); + sentinelHealthIndicator = new SentinelHealthIndicator(beanFactory, sentinelProperties); + + SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ""); + + heartbeatSender = mock(HeartbeatSender.class); + Field heartbeatSenderField = ReflectionUtils.findField(HeartbeatSenderProvider.class, "heartbeatSender"); + heartbeatSenderField.setAccessible(true); + ReflectionUtils.setField(heartbeatSenderField, null, heartbeatSender); + } + + @Test + public void testSentinelNotEnabled() { + when(sentinelProperties.isEnabled()).thenReturn(false); + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("enabled")).isEqualTo(false); + } + + @Test + public void testSentinelDashboardNotConfigured() { + when(sentinelProperties.isEnabled()).thenReturn(true); + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("dashboard")).isEqualTo(Status.UNKNOWN); + } + + @Test + public void testSentinelDashboardConfiguredSuccess() throws Exception { + when(sentinelProperties.isEnabled()).thenReturn(true); + SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); + when(heartbeatSender.sendHeartbeat()).thenReturn(true); + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + public void testSentinelDashboardConfiguredFailed() throws Exception { + when(sentinelProperties.isEnabled()).thenReturn(true); + SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); + when(heartbeatSender.sendHeartbeat()).thenReturn(false); + + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails().get("dashboard")).isEqualTo(new Status(Status.DOWN.getCode(), "localhost:8080 can't be connected")); + } + + @Test + public void testSentinelDataSourceSuccess() throws Exception { + when(sentinelProperties.isEnabled()).thenReturn(true); + SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); + when(heartbeatSender.sendHeartbeat()).thenReturn(true); + + Map dataSourceMap = new HashMap<>(); + + FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class); + dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1); + + FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class); + dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2); + + when(beanFactory.getBeansOfType(AbstractDataSource.class)).thenReturn(dataSourceMap); + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.UP); + Map dataSourceDetailMap = (Map) health.getDetails().get("dataSource"); + assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")).isEqualTo(Status.UP); + assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")).isEqualTo(Status.UP); + } + + @Test + public void testSentinelDataSourceFailed() throws Exception { + when(sentinelProperties.isEnabled()).thenReturn(true); + SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); + when(heartbeatSender.sendHeartbeat()).thenReturn(true); + + Map dataSourceMap = new HashMap<>(); + + FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class); + dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1); + + FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class); + when(fileDataSource2.loadConfig()).thenThrow(new RuntimeException("fileDataSource2 error")); + dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2); + + when(beanFactory.getBeansOfType(AbstractDataSource.class)).thenReturn(dataSourceMap); + + Health health = sentinelHealthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + Map dataSourceDetailMap = (Map) health.getDetails().get("dataSource"); + assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")).isEqualTo(Status.UP); + assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")).isEqualTo(new Status(Status.DOWN.getCode(), "fileDataSource2 error")); + } +} diff --git a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java index 0133fe24..6952fa89 100644 --- a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java +++ b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java @@ -44,7 +44,12 @@ public class AcmEndpoint { private final AcmPropertySourceRepository propertySourceRepository; - private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private ThreadLocal dateFormat = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + }; public AcmEndpoint(AcmProperties properties, AcmRefreshHistory refreshHistory, AcmPropertySourceRepository propertySourceRepository) { @@ -65,7 +70,7 @@ public class AcmEndpoint { for (AcmPropertySource ps : all) { Map source = new HashMap<>(); source.put("dataId", ps.getDataId()); - source.put("lastSynced", dateFormat.format(ps.getTimestamp())); + source.put("lastSynced", dateFormat.get().format(ps.getTimestamp())); sources.add(source); } runtime.put("sources", sources); diff --git a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java index 439e6360..19197b89 100644 --- a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java +++ b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java @@ -30,10 +30,15 @@ public class AcmRefreshHistory { private LinkedList records = new LinkedList<>(); - private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private ThreadLocal dateFormat = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + }; public void add(String dataId, String md5) { - records.addFirst(new Record(dateFormat.format(new Date()), dataId, md5)); + records.addFirst(new Record(dateFormat.get().format(new Date()), dataId, md5)); if (records.size() > MAX_SIZE) { records.removeLast(); } diff --git a/spring-cloud-alicloud-context/pom.xml b/spring-cloud-alicloud-context/pom.xml index 911cabb5..ab98c6f3 100644 --- a/spring-cloud-alicloud-context/pom.xml +++ b/spring-cloud-alicloud-context/pom.xml @@ -50,12 +50,6 @@ provided
- - com.alibaba.ans - ans-sdk - provided - - com.aliyun.oss aliyun-sdk-oss diff --git a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsProperties.java b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsProperties.java index 33577863..11ba8cb7 100644 --- a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsProperties.java +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsProperties.java @@ -12,12 +12,12 @@ public class SmsProperties { /** * Product name. */ - public static final String smsProduct = "Dysmsapi"; + public static final String SMS_PRODUCT = "Dysmsapi"; /** * Product domain. */ - public static final String smsDomain = "dysmsapi.aliyuncs.com"; + public static final String SMS_DOMAIN = "dysmsapi.aliyuncs.com"; /** * Report queue name. diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializerEventListener.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializerEventListener.java index 78253878..4133d498 100644 --- a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializerEventListener.java +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializerEventListener.java @@ -60,7 +60,7 @@ public class SmsInitializerEventListener // 初始化acsClient,暂不支持region化 try { DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", - SmsProperties.smsProduct, SmsProperties.smsDomain); + SmsProperties.SMS_PRODUCT, SmsProperties.SMS_DOMAIN); Collection messageListeners = event.getApplicationContext() .getBeansOfType(MessageListener.class).values(); if (messageListeners.isEmpty()) { diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/DefaultAlicomMessagePuller.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/DefaultAlicomMessagePuller.java index df03a071..4d23ccff 100755 --- a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/DefaultAlicomMessagePuller.java +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/DefaultAlicomMessagePuller.java @@ -85,8 +85,8 @@ public class DefaultAlicomMessagePuller { this.executorService = executorService; } - protected static final Map sLockObjMap = new HashMap(); - protected static Map sPollingMap = new ConcurrentHashMap(); + protected static final Map S_LOCK_OBJ_MAP = new HashMap<>(); + protected static Map sPollingMap = new ConcurrentHashMap<>(); protected Object lockObj; public boolean setPolling(String queueName) { @@ -309,11 +309,11 @@ public class DefaultAlicomMessagePuller { task.messageType = messageType; task.queueName = queueName; - synchronized (sLockObjMap) { - lockObj = sLockObjMap.get(queueName); + synchronized (S_LOCK_OBJ_MAP) { + lockObj = S_LOCK_OBJ_MAP.get(queueName); if (lockObj == null) { lockObj = new Object(); - sLockObjMap.put(queueName, lockObj); + S_LOCK_OBJ_MAP.put(queueName, lockObj); } } @@ -355,11 +355,11 @@ public class DefaultAlicomMessagePuller { task.messageType = messageType; task.queueName = queueName; - synchronized (sLockObjMap) { - lockObj = sLockObjMap.get(queueName); + synchronized (S_LOCK_OBJ_MAP) { + lockObj = S_LOCK_OBJ_MAP.get(queueName); if (lockObj == null) { lockObj = new Object(); - sLockObjMap.put(queueName, lockObj); + S_LOCK_OBJ_MAP.put(queueName, lockObj); } } @@ -402,11 +402,11 @@ public class DefaultAlicomMessagePuller { task.messageType = messageType; task.queueName = queueName; - synchronized (sLockObjMap) { - lockObj = sLockObjMap.get(queueName); + synchronized (S_LOCK_OBJ_MAP) { + lockObj = S_LOCK_OBJ_MAP.get(queueName); if (lockObj == null) { lockObj = new Object(); - sLockObjMap.put(queueName, lockObj); + S_LOCK_OBJ_MAP.put(queueName, lockObj); } } diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueRequest.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueRequest.java index 956ecadb..1de389ca 100644 --- a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueRequest.java +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueRequest.java @@ -76,6 +76,7 @@ public class QueryTokenForMnsQueueRequest } + @Override public Class getResponseClass() { return QueryTokenForMnsQueueResponse.class; } diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponse.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponse.java index 56c40171..5e296ed6 100644 --- a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponse.java +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponse.java @@ -60,6 +60,7 @@ public class QueryTokenForMnsQueueResponse extends AcsResponse { this.messageTokenDTO = messageTokenDTO; } + @Override public QueryTokenForMnsQueueResponse getInstance(UnmarshallerContext context) { return QueryTokenForMnsQueueResponseUnmarshaller.unmarshall(this, context); } diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenGetterForAlicom.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenGetterForAlicom.java index e312ec36..3daf1c83 100755 --- a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenGetterForAlicom.java +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenGetterForAlicom.java @@ -50,7 +50,7 @@ public class TokenGetterForAlicom { private String domainForPop; private IAcsClient iAcsClient; private Long ownerId; - private final static String productName = "Dybaseapi"; + private final static String PRODUCT_NAME = "Dybaseapi"; private long bufferTime = 1000 * 60 * 2;// 过期时间小于2分钟则重新获取,防止服务器时间误差 private final Object lock = new Object(); private ConcurrentMap tokenMap = new ConcurrentHashMap(); @@ -68,7 +68,7 @@ public class TokenGetterForAlicom { } private void init() throws ClientException { - DefaultProfile.addEndpoint(endpointNameForPop, regionIdForPop, productName, + DefaultProfile.addEndpoint(endpointNameForPop, regionIdForPop, PRODUCT_NAME, domainForPop); IClientProfile profile = DefaultProfile.getProfile(regionIdForPop, accessKeyId, accessKeySecret); diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinder.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinder.java index 38776db5..6df97fca 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinder.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQMessageChannelBinder.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties; import org.apache.rocketmq.spring.core.RocketMQTemplate; @@ -34,6 +35,7 @@ import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder; import org.springframework.cloud.stream.binder.rocketmq.consuming.RocketMQListenerBindingContainer; import org.springframework.cloud.stream.binder.rocketmq.integration.RocketMQInboundChannelAdapter; import org.springframework.cloud.stream.binder.rocketmq.integration.RocketMQMessageHandler; +import org.springframework.cloud.stream.binder.rocketmq.integration.RocketMQMessageSource; import org.springframework.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; @@ -42,9 +44,13 @@ import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQProdu import org.springframework.cloud.stream.binder.rocketmq.provisioning.RocketMQTopicProvisioner; import org.springframework.cloud.stream.provisioning.ConsumerDestination; import org.springframework.cloud.stream.provisioning.ProducerDestination; +import org.springframework.integration.StaticMessageHeaderAccessor; +import org.springframework.integration.acks.AcknowledgmentCallback; +import org.springframework.integration.acks.AcknowledgmentCallback.Status; import org.springframework.integration.core.MessageProducer; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.ObjectMapper; @@ -83,7 +89,7 @@ public class RocketMQMessageChannelBinder extends MessageChannel errorChannel) throws Exception { if (producerProperties.getExtension().getEnabled()) { - // if producerGroup is empty, using destination + // if producerGroup is empty, using destination String extendedProducerGroup = producerProperties.getExtension().getGroup(); String producerGroup = StringUtils.isEmpty(extendedProducerGroup) ? destination.getName() @@ -121,8 +127,8 @@ public class RocketMQMessageChannelBinder extends mergedProperties.isEnableMsgTrace(), mergedProperties.getCustomizedTraceTopic()); producer.setVipChannelEnabled(false); - producer.setInstanceName( - RocketMQUtil.getInstanceName(rpcHook, destination.getName())); + producer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook, + destination.getName() + "|" + UtilAll.getPid())); } else { producer = new DefaultMQProducer(producerGroup); @@ -206,6 +212,39 @@ public class RocketMQMessageChannelBinder extends return rocketInboundChannelAdapter; } + @Override + protected PolledConsumerResources createPolledConsumerResources(String name, + String group, ConsumerDestination destination, + ExtendedConsumerProperties consumerProperties) { + RocketMQMessageSource rocketMQMessageSource = new RocketMQMessageSource( + rocketBinderConfigurationProperties, consumerProperties, name, group); + return new PolledConsumerResources(rocketMQMessageSource, + registerErrorInfrastructure(destination, group, consumerProperties, + true)); + } + + @Override + protected MessageHandler getPolledConsumerErrorMessageHandler( + ConsumerDestination destination, String group, + ExtendedConsumerProperties properties) { + return message -> { + if (message.getPayload() instanceof MessagingException) { + AcknowledgmentCallback ack = StaticMessageHeaderAccessor + .getAcknowledgmentCallback( + ((MessagingException) message.getPayload()) + .getFailedMessage()); + if (ack != null) { + if (properties.getExtension().shouldRequeue()) { + ack.acknowledge(Status.REQUEUE); + } + else { + ack.acknowledge(Status.REJECT); + } + } + } + }; + } + @Override public RocketMQConsumerProperties getExtendedConsumerProperties(String channelName) { return extendedBindingProperties.getExtendedConsumerProperties(channelName); diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQListenerBindingContainer.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQListenerBindingContainer.java index 35f29ae8..de19d262 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQListenerBindingContainer.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQListenerBindingContainer.java @@ -31,6 +31,7 @@ import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.spring.annotation.ConsumeMode; @@ -206,7 +207,8 @@ public class RocketMQListenerBindingContainer new AllocateMessageQueueAveragely(), rocketBinderConfigurationProperties.isEnableMsgTrace(), rocketBinderConfigurationProperties.getCustomizedTraceTopic()); - consumer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook, topic)); + consumer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook, + topic + "|" + UtilAll.getPid())); consumer.setVipChannelEnabled(false); } else { diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQMessageQueueChooser.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQMessageQueueChooser.java new file mode 100644 index 00000000..03eacbe5 --- /dev/null +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/consuming/RocketMQMessageQueueChooser.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.stream.binder.rocketmq.consuming; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.rocketmq.common.message.MessageQueue; + +/** + * @author Jim + */ +public class RocketMQMessageQueueChooser { + + private volatile int queueIndex = 0; + + private volatile List messageQueues; + + public MessageQueue choose() { + return messageQueues.get(queueIndex); + } + + public int requeue() { + if (queueIndex - 1 < 0) { + this.queueIndex = messageQueues.size() - 1; + } + else { + this.queueIndex = this.queueIndex - 1; + } + return this.queueIndex; + } + + public void increment() { + this.queueIndex = (this.queueIndex + 1) % messageQueues.size(); + } + + public void reset(Set queueSet) { + this.messageQueues = null; + this.messageQueues = new ArrayList<>(queueSet); + this.queueIndex = 0; + } + + public List getMessageQueues() { + return messageQueues; + } +} diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageSource.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageSource.java new file mode 100644 index 00000000..d7f13be6 --- /dev/null +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageSource.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2018 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 + * + * https://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. + */ + +package org.springframework.cloud.stream.binder.rocketmq.integration; + +import java.util.List; +import java.util.Set; + +import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; +import org.apache.rocketmq.client.consumer.MessageQueueListener; +import org.apache.rocketmq.client.consumer.MessageSelector; +import org.apache.rocketmq.client.consumer.PullResult; +import org.apache.rocketmq.client.consumer.PullStatus; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.spring.support.RocketMQUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.cloud.stream.binder.ExtendedConsumerProperties; +import org.springframework.cloud.stream.binder.rocketmq.consuming.RocketMQMessageQueueChooser; +import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; +import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQConsumerProperties; +import org.springframework.context.Lifecycle; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.acks.AcknowledgmentCallback; +import org.springframework.integration.acks.AcknowledgmentCallbackFactory; +import org.springframework.integration.endpoint.AbstractMessageSource; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Jim + */ +public class RocketMQMessageSource extends AbstractMessageSource + implements DisposableBean, Lifecycle { + + private final static Logger log = LoggerFactory + .getLogger(RocketMQMessageSource.class); + + private final RocketMQCallbackFactory ackCallbackFactory; + + private final RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties; + + private final ExtendedConsumerProperties rocketMQConsumerProperties; + + private final String topic; + + private final String group; + + private final Object consumerMonitor = new Object(); + + private DefaultMQPullConsumer consumer; + + private boolean running; + + private MessageSelector messageSelector; + + private RocketMQMessageQueueChooser messageQueueChooser = new RocketMQMessageQueueChooser(); + + public RocketMQMessageSource( + RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties, + ExtendedConsumerProperties rocketMQConsumerProperties, + String topic, String group) { + this(new RocketMQCallbackFactory(), rocketMQBinderConfigurationProperties, + rocketMQConsumerProperties, topic, group); + } + + public RocketMQMessageSource(RocketMQCallbackFactory ackCallbackFactory, + RocketMQBinderConfigurationProperties rocketMQBinderConfigurationProperties, + ExtendedConsumerProperties rocketMQConsumerProperties, + String topic, String group) { + this.ackCallbackFactory = ackCallbackFactory; + this.rocketMQBinderConfigurationProperties = rocketMQBinderConfigurationProperties; + this.rocketMQConsumerProperties = rocketMQConsumerProperties; + this.topic = topic; + this.group = group; + } + + @Override + public synchronized void start() { + if (this.isRunning()) { + throw new IllegalStateException( + "pull consumer already running. " + this.toString()); + } + try { + consumer = new DefaultMQPullConsumer(group); + consumer.setNamesrvAddr( + rocketMQBinderConfigurationProperties.getNameServer()); + consumer.setConsumerPullTimeoutMillis( + rocketMQConsumerProperties.getExtension().getPullTimeout()); + consumer.setMessageModel(MessageModel.CLUSTERING); + + String tags = rocketMQConsumerProperties.getExtension().getTags(); + String sql = rocketMQConsumerProperties.getExtension().getSql(); + + if (!StringUtils.isEmpty(tags) && !StringUtils.isEmpty(sql)) { + messageSelector = MessageSelector.byTag(tags); + } + else if (!StringUtils.isEmpty(tags)) { + messageSelector = MessageSelector.byTag(tags); + } + else if (!StringUtils.isEmpty(sql)) { + messageSelector = MessageSelector.bySql(sql); + } + + consumer.registerMessageQueueListener(topic, new MessageQueueListener() { + @Override + public void messageQueueChanged(String topic, Set mqAll, + Set mqDivided) { + log.info( + "messageQueueChanged, topic='{}', mqAll=`{}`, mqDivided=`{}`", + topic, mqAll, mqDivided); + switch (consumer.getMessageModel()) { + case BROADCASTING: + RocketMQMessageSource.this.resetMessageQueues(mqAll); + break; + case CLUSTERING: + RocketMQMessageSource.this.resetMessageQueues(mqDivided); + break; + default: + break; + } + } + }); + consumer.start(); + } + catch (MQClientException e) { + log.error("DefaultMQPullConsumer startup error: " + e.getMessage(), e); + } + this.setRunning(true); + } + + @Override + public synchronized void stop() { + if (this.isRunning()) { + this.setRunning(false); + consumer.shutdown(); + } + } + + @Override + public synchronized boolean isRunning() { + return running; + } + + @Override + protected synchronized Object doReceive() { + if (messageQueueChooser.getMessageQueues() == null + || messageQueueChooser.getMessageQueues().size() == 0) { + return null; + } + try { + int count = 0; + while (count < messageQueueChooser.getMessageQueues().size()) { + MessageQueue messageQueue; + synchronized (this.consumerMonitor) { + messageQueue = messageQueueChooser.choose(); + messageQueueChooser.increment(); + } + + long offset = consumer.fetchConsumeOffset(messageQueue, + rocketMQConsumerProperties.getExtension().isFromStore()); + + log.debug("topic='{}', group='{}', messageQueue='{}', offset now='{}'", + this.topic, this.group, messageQueue, offset); + + PullResult pullResult; + if (messageSelector != null) { + pullResult = consumer.pull(messageQueue, messageSelector, offset, 1); + } + else { + pullResult = consumer.pull(messageQueue, (String) null, offset, 1); + } + + if (pullResult.getPullStatus() == PullStatus.FOUND) { + List messageExtList = pullResult.getMsgFoundList(); + + Message message = RocketMQUtil + .convertToSpringMessage(messageExtList.get(0)); + + AcknowledgmentCallback ackCallback = this.ackCallbackFactory + .createCallback(new RocketMQAckInfo(messageQueue, pullResult, + consumer, offset)); + + Message messageResult = MessageBuilder.fromMessage(message).setHeader( + IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK, + ackCallback).build(); + return messageResult; + } + else { + log.debug("messageQueue='{}' PullResult='{}' with topic `{}`", + messageQueueChooser.getMessageQueues(), + pullResult.getPullStatus(), topic); + } + count++; + } + } + catch (Exception e) { + log.error("Consumer pull error: " + e.getMessage(), e); + } + return null; + } + + @Override + public String getComponentType() { + return "rocketmq:message-source"; + } + + public synchronized void setRunning(boolean running) { + this.running = running; + } + + public synchronized void resetMessageQueues(Set queueSet) { + log.info("resetMessageQueues, topic='{}', messageQueue=`{}`", topic, queueSet); + synchronized (this.consumerMonitor) { + this.messageQueueChooser.reset(queueSet); + } + } + + public static class RocketMQCallbackFactory + implements AcknowledgmentCallbackFactory { + + @Override + public AcknowledgmentCallback createCallback(RocketMQAckInfo info) { + return new RocketMQAckCallback(info); + } + + } + + public static class RocketMQAckCallback implements AcknowledgmentCallback { + + private final RocketMQAckInfo ackInfo; + + private boolean acknowledged; + + private boolean autoAckEnabled = true; + + public RocketMQAckCallback(RocketMQAckInfo ackInfo) { + this.ackInfo = ackInfo; + } + + protected void setAcknowledged(boolean acknowledged) { + this.acknowledged = acknowledged; + } + + @Override + public boolean isAcknowledged() { + return this.acknowledged; + } + + @Override + public void noAutoAck() { + this.autoAckEnabled = false; + } + + @Override + public boolean isAutoAck() { + return this.autoAckEnabled; + } + + @Override + public void acknowledge(Status status) { + Assert.notNull(status, "'status' cannot be null"); + if (this.acknowledged) { + throw new IllegalStateException("Already acknowledged"); + } + log.debug("acknowledge(" + status.name() + ") for " + this); + synchronized (this.ackInfo.getConsumerMonitor()) { + try { + switch (status) { + case ACCEPT: + case REJECT: + ackInfo.getConsumer().updateConsumeOffset( + ackInfo.getMessageQueue(), + ackInfo.getPullResult().getNextBeginOffset()); + log.debug("messageQueue='{}' offset update to `{}`", + ackInfo.getMessageQueue(), String.valueOf( + ackInfo.getPullResult().getNextBeginOffset())); + break; + case REQUEUE: + // decrease index and update offset of messageQueue of ackInfo + int oldIndex = ackInfo.getMessageQueueChooser().requeue(); + ackInfo.getConsumer().updateConsumeOffset( + ackInfo.getMessageQueue(), ackInfo.getOldOffset()); + log.debug( + "messageQueue='{}' offset requeue to index:`{}`, oldOffset:'{}'", + ackInfo.getMessageQueue(), oldIndex, + ackInfo.getOldOffset()); + break; + default: + break; + } + } + catch (MQClientException e) { + log.error("acknowledge error: " + e.getErrorMessage(), e); + } + finally { + this.acknowledged = true; + } + } + } + + @Override + public String toString() { + return "RocketMQAckCallback{" + "ackInfo=" + ackInfo + ", acknowledged=" + + acknowledged + ", autoAckEnabled=" + autoAckEnabled + '}'; + } + } + + public class RocketMQAckInfo { + + private final MessageQueue messageQueue; + + private final PullResult pullResult; + + private final DefaultMQPullConsumer consumer; + + private final long oldOffset; + + public RocketMQAckInfo(MessageQueue messageQueue, PullResult pullResult, + DefaultMQPullConsumer consumer, long oldOffset) { + this.messageQueue = messageQueue; + this.pullResult = pullResult; + this.consumer = consumer; + this.oldOffset = oldOffset; + } + + public MessageQueue getMessageQueue() { + return messageQueue; + } + + public PullResult getPullResult() { + return pullResult; + } + + public DefaultMQPullConsumer getConsumer() { + return consumer; + } + + public RocketMQMessageQueueChooser getMessageQueueChooser() { + return RocketMQMessageSource.this.messageQueueChooser; + } + + public long getOldOffset() { + return oldOffset; + } + + public Object getConsumerMonitor() { + return RocketMQMessageSource.this.consumerMonitor; + } + + @Override + public String toString() { + return "RocketMQAckInfo{" + "messageQueue=" + messageQueue + ", pullResult=" + + pullResult + ", consumer=" + consumer + ", oldOffset=" + oldOffset + + '}'; + } + } + +} diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQConsumerProperties.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQConsumerProperties.java index 6cfe9b84..343c9056 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQConsumerProperties.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQConsumerProperties.java @@ -18,6 +18,7 @@ package org.springframework.cloud.stream.binder.rocketmq.properties; import org.apache.rocketmq.client.consumer.MQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; @@ -51,7 +52,9 @@ public class RocketMQConsumerProperties { private Boolean orderly = false; /** - * for concurrently listener. message consume retry strategy + * for concurrently listener. message consume retry strategy. see + * {@link ConsumeConcurrentlyContext#delayLevelWhenNextConsume}. -1 means dlq(or + * discard, see {@link this#shouldRequeue}), others means requeue */ private int delayLevelWhenNextConsume = 0; @@ -62,6 +65,14 @@ public class RocketMQConsumerProperties { private Boolean enabled = true; + // ------------ For Pull Consumer ------------ + + private long pullTimeout = 10 * 1000; + + private boolean fromStore; + + // ------------ For Pull Consumer ------------ + public String getTags() { return tags; } @@ -117,4 +128,24 @@ public class RocketMQConsumerProperties { public void setSuspendCurrentQueueTimeMillis(long suspendCurrentQueueTimeMillis) { this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis; } + + public long getPullTimeout() { + return pullTimeout; + } + + public void setPullTimeout(long pullTimeout) { + this.pullTimeout = pullTimeout; + } + + public boolean isFromStore() { + return fromStore; + } + + public void setFromStore(boolean fromStore) { + this.fromStore = fromStore; + } + + public boolean shouldRequeue() { + return delayLevelWhenNextConsume != -1; + } } diff --git a/spring-cloud-stream-binder-rocketmq/src/test/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java b/spring-cloud-stream-binder-rocketmq/src/test/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java index 4a787e65..33bd1187 100644 --- a/spring-cloud-stream-binder-rocketmq/src/test/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java +++ b/spring-cloud-stream-binder-rocketmq/src/test/java/org/springframework/cloud/stream/binder/rocketmq/RocketMQAutoConfigurationTests.java @@ -34,7 +34,7 @@ public class RocketMQAutoConfigurationTests { .withConfiguration( AutoConfigurations.of(RocketMQBinderAutoConfiguration.class)) .withPropertyValues( - "spring.cloud.stream.rocketmq.binder.namesrv-addr=127.0.0.1:9876", + "spring.cloud.stream.rocketmq.binder.name-server=127.0.0.1:9876", "spring.cloud.stream.bindings.output.destination=TopicOrderTest", "spring.cloud.stream.bindings.output.content-type=application/json", "spring.cloud.stream.bindings.input1.destination=TopicOrderTest",