diff --git a/README-zh.md b/README-zh.md index bd633778..c76e149e 100644 --- a/README-zh.md +++ b/README-zh.md @@ -9,9 +9,9 @@ Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。 -参考文档 请查看 [WIKI](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki) +参考文档 请查看 [WIKI](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki) 。 -为 Spring Cloud Alibaba 贡献代码请参考 [如何贡献](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki/%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE%E4%BB%A3%E7%A0%81) +为 Spring Cloud Alibaba 贡献代码请参考 [如何贡献](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki/%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE%E4%BB%A3%E7%A0%81) 。 ## 主要功能 @@ -46,7 +46,7 @@ Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。 * master 分支对应的是 Spring Cloud Finchley,最低支持 JDK 1.8。 * 1.x 分支对应的是 Spring Cloud Edgware,最低支持 JDK 1.7。 -Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目clone到本地,然后执行以下命令: +Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目 clone 到本地,然后执行以下命令: ./mvnw install @@ -71,7 +71,7 @@ Spring Cloud 使用 Maven 来构建,最快的使用方式是将本项目clone -然后再 `dependencies` 中添加自己所需使用的依赖即可使用。 +然后在 `dependencies` 中添加自己所需使用的依赖即可使用。 如果您想体验最新的 BUILD-SNAPSHOT 的新功能,则可以将版本换成最新的版本,但是需要在 pom.xml 中配置 Spring BUILDSNAPSHOT 仓库,**注意: SNAPSHOT 版本随时可能更新** @@ -109,7 +109,7 @@ Example 列表: [Alibaba Cloud SchedulerX Example](https://github.com/xiaolongzuo/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/readme-zh.md) ## 版本管理规范 -项目的版本号格式为 x.x.x 的形式,其中 x 的数值类型为数字,从0开始取值,且不限于 0~9 这个范围。项目处于孵化器阶段时,第一位版本号固定使用0,即版本号为 0.x.x 的格式。 +项目的版本号格式为 x.x.x 的形式,其中 x 的数值类型为数字,从 0 开始取值,且不限于 0~9 这个范围。项目处于孵化器阶段时,第一位版本号固定使用 0,即版本号为 0.x.x 的格式。 由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且 spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此我们使用了两个不同分支来分别支持 Spring Boot 1 和 Spring Boot 2: * 0.1.x 版本适用于 Spring Boot 1 diff --git a/Roadmap-zh.md b/Roadmap-zh.md index c28fe972..78e09e0e 100644 --- a/Roadmap-zh.md +++ b/Roadmap-zh.md @@ -4,29 +4,42 @@ Spring Cloud Alibaba 致力于提供分布式应用服务开发的一站式解 此项目包含的组件内容,主要选取自阿里巴巴开源中间件和阿里云商业化产品,但也不限定于这些产品。 -如果您对 Roadmap 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出,一起讨论。 +如果您对 Roadmap 有任何建议或想法,欢迎在 issues 中或者通过其他社区渠道向我们提出,一起讨论。 ## 已包含的组件 **Sentinel** -阿里巴巴开源产品,把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。 + +阿里巴巴开源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 **Nacos** + 阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 **Alibaba Cloud OSS** + 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 -## 即将加入的组件 -**Dubbo** -Apache Dubbo™ (incubating) 是一款高性能Java RPC框架。 - **RocketMQ** -Apache RocketMQ™ 基于Java的高性能、高吞吐量的分布式消息和流计算平台。 -**Alibaba Cloud Schedulerx** +Apache RocketMQ™ 基于 Java 的高性能、高吞吐量的分布式消息和流计算平台。 + +**Alibaba Cloud SchedulerX** + 阿里中间件团队开发的一款分布式任务调度产品,支持周期性的任务与固定时间点触发任务。 + +## 即将加入的组件 + +**Dubbo** + +Apache Dubbo™ (incubating) 是一款高性能 Java RPC 框架。 + +**Fescar** + +阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。 + **Alibaba Cloud SLS** + 针对日志类数据的一站式服务,在阿里巴巴集团经历大量大数据场景锤炼而成。您无需开发就能快捷完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。 \ No newline at end of file diff --git a/Roadmap.md b/Roadmap.md index 7b392f2a..66cb84f6 100644 --- a/Roadmap.md +++ b/Roadmap.md @@ -20,20 +20,28 @@ An open-source project of Alibaba, Sentinel takes "flow" as breakthrough point, An opensource project of Alibaba, an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. +**RocketMQ** + +Apache RocketMQ™ is an open source distributed messaging and streaming data platform. + **Alibaba Cloud OSS** Alibaba Cloud Object Storage Service, An encrypted and secure cloud storage service which stores, processes and accesses massive amounts of data from anywhere in the world. +**Alibaba Cloud Schedulerx** + +A distributed task scheduling product developed by Alibaba Middleware team. It supports both periodical tasks and tasks to be triggered at specified time points. + More components will be supported by Spring Cloud Alibaba in the future, which may include but are not limited to the following: **Dubbo** + Apache Dubbo™ (incubating) is a high-performance, Java based open source RPC framework. -**RocketMQ** -Apache RocketMQ™ is an open source distributed messaging and streaming data platform. +**Fescar** -**Alibaba Cloud Schedulerx** -A distributed task scheduling product developed by Alibaba Middleware team. It supports both periodical tasks and tasks to be triggered at specified time points. +A distributed transaction solution with high performance and ease of use for microservices architecture. **Alibaba Cloud SLS** -Aliyun Log Service is an all-in-one service for log-type data. It helps increase Operations & Management and operational efficiency, as well as build the processing capability to deal with massive logs. \ No newline at end of file + +Aliyun Log Service is an all-in-one service for log-type data. It helps increase Operations & Management and operational efficiency, as well as build the processing capability to deal with massive logs. diff --git a/pom.xml b/pom.xml index da119e47..6e4ff453 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,8 @@ 2.8.2 2.21.0 1.6 + 2.1.1 + 2.7 @@ -102,6 +104,7 @@ spring-cloud-alicloud-acm spring-cloud-alicloud-ans spring-cloud-alicloud-schedulerx + spring-cloud-alicloud-sms @@ -155,6 +158,14 @@ import + + net.sourceforge.cobertura + cobertura-runtime + ${cobertura.version} + provided + pom + + org.springframework.cloud spring-cloud-alibaba-dependencies @@ -167,6 +178,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.apache.maven.plugins @@ -194,10 +213,12 @@ cobertura-maven-plugin 2.7 + true html xml + diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 82ba5769..8e0d4305 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -31,6 +31,10 @@ 2.6.5 0.2.1-SNAPSHOT 0.0.2 + 1.1.0 + 1.1.8 + 1.1.0 + 1.1.1 @@ -74,6 +78,19 @@ ${schedulerX.client.version} + + + com.aliyun.mns + aliyun-sdk-mns + ${aliyun.sdk.mns} + + + + com.aliyun + aliyun-java-sdk-dysmsapi + ${aliyun.java.sdk.dysmsapi} + + com.alibaba.nacos @@ -259,6 +276,11 @@ spring-cloud-alicloud-schedulerx ${project.version} + + org.springframework.cloud + spring-cloud-alicloud-sms + ${project.version} + org.springframework.cloud spring-cloud-alicloud-context @@ -329,6 +351,13 @@ ${project.version} + + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + ${project.version} + + @@ -389,4 +418,26 @@ + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + diff --git a/spring-cloud-alibaba-docs/pom.xml b/spring-cloud-alibaba-docs/pom.xml index 44d6fad1..cb755091 100644 --- a/spring-cloud-alibaba-docs/pom.xml +++ b/spring-cloud-alibaba-docs/pom.xml @@ -50,5 +50,27 @@ + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sms.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sms.adoc new file mode 100644 index 00000000..fed8a525 --- /dev/null +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/sms.adoc @@ -0,0 +1,276 @@ +== Spring Cloud Alibaba Cloud SMS + +短信服务(Short Message Service)是阿里云为用户提供的一种通信服务的能力。 Spring Cloud AliCloud SMS 实现了与 SMS 的简单集成,提供更为简单易用的 API,可以基于 Spring Cloud Alibaba SMS 来快速的接入阿里云的 SMS 服务。 + +=== 如何引入 Spring Cloud AliCloud SMS + +Spring Cloud Alibaba 已经发布了 0.2.2.BUILD-SNAPSHOT 版本,需要首先导入依赖管理 POM。 + +[source,xml] +---- + + + + org.springframework.cloud + spring-cloud-dependencies + Finchley.SR2 + pom + import + + + org.springframework.cloud + spring-cloud-alibaba-dependencies + 0.2.2.BUILD-SNAPSHOT + pom + import + + + +---- + +接下来引入 Spring Cloud AliCloud SMS Starter 即可。 + +[source,xml] +---- + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + +---- + +=== 如何使用 SMS API + +==== 配置 SMS + +使用 Spring Cloud AliCloud SMS 之前,需要在 application.properties 中加入以下配置。 + +[source,properties] +---- +spring.cloud.alicloud.access-key=你的阿里云 AK +spring.cloud.alicloud.secret-key=你的阿里云 SK +---- + +access-key 和 secret-key 是阿里云账号的 AK/SK,需要首先注册阿里云账号,然后登陆 https://usercenter.console.aliyun.com/#/manage/ak[阿里云AK/SK管理页面] ,即可看到 AccessKey ID 和 Access Key Secret ,如果没有的话,需要点击"创建AccessKey"按钮创建。 + + +==== 引入 SMS API + +Spring Cloud Alicloud SMS 中的 SMS API 基于阿里云官方 SMS SDK 提供,具备单个短信发送、多个短信批量发送、短信查询、短信消息(`短信回执消息` 和 `上行短信消息`) 类行操作API。 + +一个简单的使用 SMS API 发送短信的应用如下。 + +[source,java] +---- +@SpringBootApplication +public class SmsApplication { + + @Autowired + private ISmsService smsService; + + /** + * 短信发送 Example + * @param code + * @return + */ + @RequestMapping("/batch-sms-send.do") + + public SendBatchSmsResponse batchsendCheckCode( + @RequestParam(name = "code") String code) { + + // 组装请求对象-具体描述见控制台-文档部分内容 + SendSmsRequest request = new SendSmsRequest(); + // 必填:待发送手机号 + request.setPhoneNumbers("152******"); + // 必填:短信签名-可在短信控制台中找到 + request.setSignName("******"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 可选:模板中的变量替换JSON串,如模板内容为"【企业级分布式应用服务】,您的验证码为${code}"时,此处的值为 + request.setTemplateParam("{\"code\":\"" + code + "\"}"); + SendSmsResponse sendSmsResponse ; + try { + sendSmsResponse = smsService.sendSmsRequest(request); + } + catch (ClientException e) { + e.printStackTrace(); + sendSmsResponse = new SendSmsResponse(); + } + return sendSmsResponse ; + } + + public static void main(String[] args) throws URISyntaxException { + + SpringApplication.run(SmsApplication.class, args); + } + +} +---- + +在发送短信之前,首先需要 https://account.aliyun.com/register/register.htm?spm=5176.8142029.388261.26.e9396d3eaYK2sG&oauth_callback=https%3A%2F%2Fwww.aliyun.com%2F[注册阿里云账号] ,如果已经有的话,请 https://dysms.console.aliyun.com/dysms.htm?spm=5176.8195934.1283918..18924183bHPct2&accounttraceid=c8cb4243-3080-4eb1-96b0-1f2316584269#/[开通 SMS 服务]。 + +更多关于 SMS 发送短信的步骤,可以参考 SMS 官方 https://help.aliyun.com/document_detail/55284.html?spm=a2c4g.11186623.6.568.715e4f30ZiVkbI[短信发送API(SendSms)---JAVA] 文档。 + +NOTE: 由于早期的 SMS sdk 版本的问题,如果短信发送失败,请将代码中含有明确指定 MethodType 为 POST 的这行代码给删除掉。如果还有问题,请第一时间联系我们。 + + +=== SMS Api 的高级功能 + +Spring Cloud Alicloud SMS 封装的 API 接口为了降低学习的成本,尽量保持和官网提供的 API 以及 Example 保持一致。 + +* 批量短信发送 + +参考以下的 Example ,来快速开发一个具有批量短信发送的功能。在 Controller 中或者新建一个 Controler 新增如下代码: + +[source,java] +---- +/** + * 批量短信发送 Example + * @param code + * @return + */ +@RequestMapping("/batch-sms-send.do") +public SendBatchSmsResponse batchsendCheckCode( + @RequestParam(name = "code") String code) { + // 组装请求对象 + SendBatchSmsRequest request = new SendBatchSmsRequest(); + // 使用 GET 提交 + request.setMethod(MethodType.GET); + // 必填:待发送手机号。支持JSON格式的批量调用,批量上限为100个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式 + request.setPhoneNumberJson("[\"177********\",\"130********\"]"); + // 必填:短信签名-支持不同的号码发送不同的短信签名 + request.setSignNameJson("[\"*******\",\"*******\"]"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 必填:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 + // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败 + request.setTemplateParamJson( + "[{\"code\":\"" + code + "\"},{\"code\":\"" + code + "\"}]"); + SendBatchSmsResponse sendSmsResponse ; + try { + sendSmsResponse = smsService + .sendSmsBatchRequest(request); + return sendSmsResponse; + } + catch (ClientException e) { + e.printStackTrace(); + sendSmsResponse = new SendBatchSmsResponse(); + } + return sendSmsResponse ; +} +---- + +NOTE: 这里设置请求的 MethodType 为 GET,和官网给出的例子还有些不一样。这是因为依赖的阿里云 POP API 版本不一致导致不兼容的问题,设置为 GET 即可。 + +更多的参数说明可 https://help.aliyun.com/document_detail/66041.html?spm=a2c4g.11186623.6.571.631315e8AauJhP[参考这里] + +* 短信查询 + +参考以下的 Example ,可以快速开发根据某个指定的号码查询短信历史发送状态。在 Controller 中或者新建一个 Controler 新增如下代码: + +[source,java] +---- +/** + * + * 短信查询 Example + * @param telephone + * @return + */ +@RequestMapping("/query.do") +public QuerySendDetailsResponse querySendDetailsResponse( + @RequestParam(name = "tel") String telephone) { + // 组装请求对象 + QuerySendDetailsRequest request = new QuerySendDetailsRequest(); + // 必填-号码 + request.setPhoneNumber(telephone); + // 必填-短信发送的日期 支持30天内记录查询(可查其中一天的发送数据),格式yyyyMMdd + request.setSendDate("20190103"); + // 必填-页大小 + request.setPageSize(10L); + // 必填-当前页码从1开始计数 + request.setCurrentPage(1L); + try { + QuerySendDetailsResponse response = smsService.querySendDetails(request); + return response; + } + catch (ClientException e) { + e.printStackTrace(); + } + + return new QuerySendDetailsResponse(); +} + +---- + +更多的参数说明,可 https://help.aliyun.com/document_detail/55289.html?spm=a2c4g.11186623.6.569.4f852c78mugEfx[参考这里] + +* 短信回执消息 + +通过订阅 SmsReport 短信状态报告,可以获知每条短信的发送情况,了解短信是否达到终端用户的状态与相关信息。这些工作已经都被 Spring Cloud AliCloud SMS 封装在内部了。你只需要完成以下两步即可。 + +1、在 `application.properties` 配置文件中(也可以是 application.yaml)配置 SmsReport 的队列名称。 + +.application.properties +---- +spring.cloud.alicloud.sms.report-queue-name=Alicom-Queue-********-SmsReport +---- + +2、 实现 SmsReportMessageListener 接口,并初始化一个 Spring Bean 。 + +[source,java] +---- +/** + * 如果需要监听短信是否被对方成功接收,只需实现这个接口并初始化一个 Spring Bean 即可。 + */ +@Component +public class SmsReportMessageListener + implements org.springframework.cloud.alicloud.sms.SmsReportMessageListener { + + @Override + public boolean dealMessage(Message message) { + // 在这里添加你的处理逻辑 + + //do something + + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } +} +---- + +更多关于 Message 的消息体格式可 https://help.aliyun.com/document_detail/55496.html?spm=a2c4g.11186623.6.570.7f792c78rOiWXO[参考这里]。 + +* 上行短信消息 + +通过订阅SmsUp上行短信消息,可以获知终端用户回复短信的内容。这些工作也已经被 Spring Cloud AliCloud SMS 封装好了。你只需要完成以下两步即可。 + +1、 在 `application.properties` 配置文件中(也可以是 application.yaml)配置 SmsReport 的队列名称。 + +.application.properties +---- +spring.cloud.alicloud.sms.up-queue-name=Alicom-Queue-********-SmsUp +---- + +2、实现 SmsUpMessageListener 接口,并初始化一个 Spring Bean 。 + +[source,java] +---- +/** + * 如果发送的短信需要接收对方回复的状态消息,只需实现该接口并初始化一个 Spring Bean 即可。 + */ +@Component +public class SmsUpMessageListener + implements org.springframework.cloud.alicloud.sms.SmsUpMessageListener { + + @Override + public boolean dealMessage(Message message) { + // 在这里添加你的处理逻辑 + + //do something + + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } +} +---- + +更多关于 Message 的消息体格式可 https://help.aliyun.com/document_detail/55496.html?spm=a2c4g.11186623.6.570.7f792c78rOiWXO[参考这里]。 diff --git a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc index 400d8a7a..f74d82eb 100644 --- a/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc +++ b/spring-cloud-alibaba-docs/src/main/asciidoc-zh/spring-cloud-alibaba.adoc @@ -31,4 +31,6 @@ include::oss.adoc[] include::schedulerx.adoc[] +include::sms.adoc[] + diff --git a/spring-cloud-alibaba-examples/acm-example/acm-local-example/pom.xml b/spring-cloud-alibaba-examples/acm-example/acm-local-example/pom.xml index 250a60fb..c7847d51 100644 --- a/spring-cloud-alibaba-examples/acm-example/acm-local-example/pom.xml +++ b/spring-cloud-alibaba-examples/acm-example/acm-local-example/pom.xml @@ -23,11 +23,28 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot spring-boot-maven-plugin + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/pom.xml b/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/pom.xml index af0c708c..2e6ddcb8 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/pom.xml +++ b/spring-cloud-alibaba-examples/ans-example/ans-consumer-feign-example/pom.xml @@ -29,4 +29,25 @@ spring-boot-starter-actuator + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/pom.xml b/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/pom.xml index b66c22ff..6289c404 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/pom.xml +++ b/spring-cloud-alibaba-examples/ans-example/ans-consumer-ribbon-example/pom.xml @@ -25,4 +25,25 @@ spring-boot-starter-actuator + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/ans-example/ans-provider-example/pom.xml b/spring-cloud-alibaba-examples/ans-example/ans-provider-example/pom.xml index fc8f4b67..67ce3b6b 100644 --- a/spring-cloud-alibaba-examples/ans-example/ans-provider-example/pom.xml +++ b/spring-cloud-alibaba-examples/ans-example/ans-provider-example/pom.xml @@ -27,11 +27,28 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot spring-boot-maven-plugin + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/env-extension/pom.xml b/spring-cloud-alibaba-examples/env-extension/pom.xml new file mode 100644 index 00000000..35d98346 --- /dev/null +++ b/spring-cloud-alibaba-examples/env-extension/pom.xml @@ -0,0 +1,37 @@ + + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.6.RELEASE + + + + 4.0.0 + + alibaba.com + env-extension + 0.2.2.BUILD-SNAPSHOT + jar + + env-extension + Demo project for Spring Boot + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + diff --git a/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/ImportExtraConfig.java b/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/ImportExtraConfig.java new file mode 100644 index 00000000..1585c4a9 --- /dev/null +++ b/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/ImportExtraConfig.java @@ -0,0 +1,16 @@ +package org.springframework.alicloud.env.extension; + + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ImportExtraConfig { + + String[] name() default ""; +} diff --git a/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/LoadExtraConfigApplicationListener.java b/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/LoadExtraConfigApplicationListener.java new file mode 100644 index 00000000..747ca68a --- /dev/null +++ b/spring-cloud-alibaba-examples/env-extension/src/main/java/org/springframework/alicloud/env/extension/LoadExtraConfigApplicationListener.java @@ -0,0 +1,61 @@ +/* + * 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.alicloud.env.extension; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.PropertiesPropertySource; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * @author pbting + * @date 2019-01-09 9:00 PM + */ +public class LoadExtraConfigApplicationListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + SpringApplication springApplication = event.getSpringApplication(); + Class clazz = springApplication.getMainApplicationClass(); + if (!clazz.isAnnotationPresent(ImportExtraConfig.class)) { + return; + } + ImportExtraConfig annotation = (ImportExtraConfig) clazz + .getAnnotation(ImportExtraConfig.class); + + String[] extraConfig = annotation.name(); + + if (extraConfig == null || extraConfig.length == 0) { + return; + } + + for (String config : extraConfig) { + try { + Properties properties = new Properties(); + properties.load(new FileInputStream(config)); + event.getEnvironment().getPropertySources() + .addFirst(new PropertiesPropertySource(config, properties)); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/env-extension/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-examples/env-extension/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..d264a372 --- /dev/null +++ b/spring-cloud-alibaba-examples/env-extension/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.context.ApplicationListener=\ + org.springframework.alicloud.env.extension.LoadExtraConfigApplicationListener \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/pom.xml index 565a732d..8d964fb7 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/pom.xml @@ -34,6 +34,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -47,6 +55,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml index 6a3af32b..aabf1ec1 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-consumer-example/pom.xml @@ -48,6 +48,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -61,6 +69,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/pom.xml index 60fd7050..52bac0cf 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/nacos-discovery-provider-example/pom.xml @@ -33,6 +33,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -46,6 +54,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml index 5d37f883..bea5a0b0 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/pom.xml @@ -15,47 +15,31 @@ pom Example demonstrating how to use nacos discovery - - 2.6.5 - nacos-discovery-consumer-example nacos-discovery-provider-example - - - - - com.alibaba.boot - dubbo-spring-boot-starter - - - - com.alibaba - dubbo - ${dubbo.version} - - - - - io.netty - netty-all - - - - - com.alibaba - dubbo-registry-nacos - - - - - com.alibaba.nacos - nacos-client - - - - + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/pom.xml new file mode 100644 index 00000000..bac2b278 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/pom.xml @@ -0,0 +1,71 @@ + + + + + org.springframework.cloud + nacos-gateway-example + 0.2.2.BUILD-SNAPSHOT + + 4.0.0 + + + nacos-gateway-discovery-example + jar + Example demonstrating how to use gateway with nacos + + + + + org.springframework.cloud + spring-cloud-starter-gateway + 2.0.2.RELEASE + + + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/GatewayApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/GatewayApplication.java new file mode 100644 index 00000000..c581d1ff --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/GatewayApplication.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * @author lengleng + */ +@SpringBootApplication +@EnableDiscoveryClient +public class GatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } + +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/resources/application.properties new file mode 100644 index 00000000..61394891 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-discovery-example/src/main/resources/application.properties @@ -0,0 +1,11 @@ +server.port=18085 +spring.application.name=service-gateway +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 +management.endpoints.web.exposure.include=* + +# spring cloud route config +spring.cloud.gateway.routes[0].id=nacos-route +spring.cloud.gateway.routes[0].uri=lb://service-gateway-provider +spring.cloud.gateway.routes[0].predicates[0].name=Path +spring.cloud.gateway.routes[0].predicates[0].args[pattern]=/nacos/** +spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1 \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/pom.xml new file mode 100644 index 00000000..8a121fa8 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/pom.xml @@ -0,0 +1,69 @@ + + + + + org.springframework.cloud + nacos-gateway-example + 0.2.2.BUILD-SNAPSHOT + + 4.0.0 + + + nacos-gateway-provider-example + jar + Example demonstrating how to use gateway with nacos + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java new file mode 100644 index 00000000..843f5cbe --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/ProviderApplication.java @@ -0,0 +1,35 @@ +package org.springframework.cloud.alibaba.cloud.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author xiaojing + */ +@SpringBootApplication +@EnableDiscoveryClient +public class ProviderApplication { + + public static void main(String[] args) { + SpringApplication.run(ProviderApplication.class, args); + } + + @RestController + class EchoController { + @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET) + public String echo(@PathVariable String string) { + return "hello Nacos Discovery " + string; + } + + @RequestMapping(value = "/divide", method = RequestMethod.GET) + public String divide(@RequestParam Integer a, @RequestParam Integer b) { + return String.valueOf(a / b); + } + } +} diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/resources/application.properties new file mode 100644 index 00000000..9a061092 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/nacos-gateway-provider-example/src/main/resources/application.properties @@ -0,0 +1,4 @@ +server.port=18086 +spring.application.name=service-gateway-provider +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 +management.endpoints.web.exposure.include=* \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/pom.xml b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/pom.xml new file mode 100644 index 00000000..9b7f638e --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/pom.xml @@ -0,0 +1,45 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba-examples + 0.2.2.BUILD-SNAPSHOT + ../../pom.xml + + 4.0.0 + + + nacos-gateway-example + pom + Example demonstrating how to use gateway with nacos + + + + nacos-gateway-discovery-example + nacos-gateway-provider-example + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme-zh.md b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme-zh.md new file mode 100644 index 00000000..6fc875fe --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme-zh.md @@ -0,0 +1,97 @@ +# Spring Cloud Gateway、 Nacos Discovery Example + +## 项目说明 + +本项目演示如何使用 Nacos Discovery Starter 、 Spring Cloud Gateway Starter 完成 Spring Cloud 服务路由。 + +[Nacos](https://github.com/alibaba/Nacos) 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 +[Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 是spring cloud 官方开源的一个在SpringMVC 上可以构建API网关的库。 + +## 示例 + +### 如何接入 +在启动示例进行演示之前,我们先了解一下 Spring Cloud 应用如何接入 Spring Cloud 如何接入Nacos Discovery、Spring Cloud Gateway。 +**注意 本章节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您无需再进行修改。** + +1. 首先,修改 pom.xml 文件,引入 Nacos Discovery Starter、Spring Cloud Gateway Starter。 + +```xml + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + org.springframework.cloud + spring-cloud-starter-gateway + +``` + +2. 在应用的 /src/main/resources/application.properties 配置文件中配置 Nacos Server 地址 + + spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 + +3. 在应用的 /src/main/resources/application.properties 配置文件中配置 Spring Cloud Gateway 路由 + +```properties +spring.cloud.gateway.routes[0].id=nacos-route +spring.cloud.gateway.routes[0].uri=lb://service-gateway-provider +spring.cloud.gateway.routes[0].predicates[0].name=Path +spring.cloud.gateway.routes[0].predicates[0].args[pattern]=/nacos/** +spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1 +``` + +4. 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能 + +```java + @SpringBootApplication + @EnableDiscoveryClient + public class GatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } + + } +``` + +### 启动 Nacos Server + +1. 首先需要获取 Nacos Server,支持直接下载和源码构建两种方式。 + + 1. 直接下载:[Nacos Server 下载页](https://github.com/alibaba/nacos/releases) + 2. 源码构建:进入 Nacos [Github 项目页面](https://github.com/alibaba/nacos),将代码 git clone 到本地自行编译打包,[参考此文档](https://nacos.io/zh-cn/docs/quick-start.html)。**推荐使用源码构建方式以获取最新版本** + +2. 启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。 + + 1. Linux/Unix/Mac 操作系统,执行命令 `sh startup.sh -m standalone` + 1. Windows 操作系统,执行命令 `cmd startup.cmd` + +### Spring Cloud Gateway应用启动 +启动应用,支持 IDE 直接启动和编译打包后启动。 + +1. IDE直接启动:找到 nacos-gateway-discovery-example 项目的主类 `GatewayApplication`,执行 main 方法启动应用。 +2. 打包编译后启动:在 nacos-gateway-discovery-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-gateway-discovery-example.jar`启动应用。 + +### 服务提供方应用启动 +启动应用,支持 IDE 直接启动和编译打包后启动。 +1. IDE直接启动:找到 nacos-gateway-provider-example 项目的主类 `ProviderApplication`,执行 main 方法启动应用。 +2. 打包编译后启动:在 nacos-gateway-provider-example 项目中执行 `mvn clean package` 将工程编译打包,然后执行 `java -jar nacos-gateway-provider-example.jar`启动应用。 + +### 验证 +1. +```bash + curl 'http://127.0.0.1:18085/nacos/echo/hello-world' + + hello Nacos Discovery hello-world⏎ +``` +1. +```bash + curl 'http://127.0.0.1:18085/nacos/divide?a=6&b=2' + + 3⏎ +``` +#### 更多介绍 +Nacos为用户提供包括动态服务发现,配置管理,服务管理等服务基础设施,帮助用户更灵活,更轻松地构建,交付和管理他们的微服务平台,基于Nacos, 用户可以更快速的构建以“服务”为中心的现代云原生应用。Nacos可以和Spring Cloud、Kubernetes/CNCF、Dubbo 等微服务生态无缝融合,为用户提供更卓越的体验。更多 Nacos 相关的信息,请参考 [Nacos 项目](https://github.com/alibaba/Nacos)。 + +如果您对 Spring Cloud Nacos Discovery 有任何建议或想法,欢迎在 issue 中或者通过其他社区渠道向我们提出。 + diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme.md b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme.md new file mode 100644 index 00000000..07e717b4 --- /dev/null +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-gateway-example/readme.md @@ -0,0 +1,105 @@ +# Spring Cloud Gateway、 Nacos Discovery Example + +## Project Instruction + +This example illustrates how to use Nacos Discovery Starter、 Spring Cloud Gateway Starter implement Service route for Spring Cloud applications. + +[Nacos](https://github.com/alibaba/Nacos) an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. +[Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) provides a library for building an API Gateway on top of Spring MVC. + + +## Demo + +### Connect to Nacos Discovery +Before we start the demo, let's learn how to connect Nacos Config to a Spring Cloud application. **Note: This section is to show you how to connect to Nacos Discovery、Nacos Discovery、Spring Cloud Gateway. The configurations have been completed in the following example, so you don't need modify the code any more.** + +1. Add Nacos Discovery Starter、Spring Cloud Gateway Starter in the pom.xml file in your Spring Cloud project. + +```xml + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + org.springframework.cloud + spring-cloud-starter-gateway + +``` +2. Add Nacos server address configurations to file /src/main/resources/application.properties. + + spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 + +3. Add Spring Cloud Gateway configurations to file /src/main/resources/application.properties. + +```properties +spring.cloud.gateway.routes[0].id=nacos-route +spring.cloud.gateway.routes[0].uri=lb://service-gateway-provider +spring.cloud.gateway.routes[0].predicates[0].name=Path +spring.cloud.gateway.routes[0].predicates[0].args[pattern]=/nacos/** +spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1 +``` +4. Use the @EnableDiscoveryClient annotation to turn on service registration and discovery. + +```java + @SpringBootApplication + @EnableDiscoveryClient + public class GatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } + + } +``` + +### Start Nacos Server + +1. Install Nacos Server by downloading or build from source code.**Recommended latest version Nacos Server** + + 1. Download: Download Nacos Server [download page](https://github.com/alibaba/nacos/releases) + 2. Build from source code: Get source code by git clone git@github.com:alibaba/Nacos.git from Github Nacos and build your code. See [build reference](https://nacos.io/en-us/docs/quick-start.html) for details. + + + +2. Unzip the downloaded file and go to the nacos/bin folder(), And according to the actual situation of the operating system, execute the following command。[see reference for more detail](https://nacos.io/en-us/docs/quick-start.html)。 + + 1. Linux/Unix/Mac , execute `sh startup.sh -m standalone` + 1. Windows , execute `cmd startup.cmd` + +### Start Spring Cloud Gateway Application +Start the application in IDE or by building a fatjar. + +1. Start in IDE: Find main class `GatewayApplication ` in project `nacos-gateway-discovery-example`, and execute the main method. +2. Build a fatjar:Execute command `mvn clean package` in project `nacos-gateway-discovery-example` to build a fatjar,and run command `java -jar nacos-gateway-discovery-example.jar` to start the application. + + +### Start Service provider Application + +Start the application in IDE or by building a fatjar. + +1. Start in IDE: Find main class `ProviderApplication ` in project `nacos-gateway-provider-example`, and execute the main method. +2. Build a fatjar:Execute command `mvn clean package` in project `nacos-gateway-provider-example` to build a fatjar,and run command `java -jar nacos-gateway-provider-example.jar` to start the application. + + +### Verification +1. +```bash + curl 'http://127.0.0.1:18085/nacos/echo/hello-world' + + hello Nacos Discovery hello-world⏎ +``` +1. +```bash + curl 'http://127.0.0.1:18085/nacos/divide?a=6&b=2' + + 3⏎ +``` + +#### More introduction + +[Nacos ](https://github.com/alibaba/Nacos) is committed to help you discover, configure, and manage your microservices. It provides a set of simple and useful features enabling you to realize dynamic service discovery, service configuration, service metadata and traffic management. + +Nacos makes it easier and faster to construct, deliver and manage your microservices platform. It is the infrastructure that supports a service-centered modern application architecture with a microservices or cloud-native approach. + +If you have any ideas or suggestions for Nacos Discovery starter, please don't hesitate to tell us by submitting github issues. + diff --git a/spring-cloud-alibaba-examples/oss-example/pom.xml b/spring-cloud-alibaba-examples/oss-example/pom.xml index 624b3e66..75cbfd2f 100644 --- a/spring-cloud-alibaba-examples/oss-example/pom.xml +++ b/spring-cloud-alibaba-examples/oss-example/pom.xml @@ -33,6 +33,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -46,6 +54,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index 5f0f83c3..6e2790f9 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -23,6 +23,8 @@ sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api nacos-example/nacos-discovery-example nacos-example/nacos-config-example + nacos-example/nacos-gateway-example + env-extension oss-example ans-example/ans-consumer-feign-example ans-example/ans-consumer-ribbon-example @@ -34,6 +36,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.apache.maven.plugins @@ -43,6 +53,16 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + diff --git a/spring-cloud-alibaba-examples/rocketmq-example/pom.xml b/spring-cloud-alibaba-examples/rocketmq-example/pom.xml index 84ef5029..12bfbb4f 100644 --- a/spring-cloud-alibaba-examples/rocketmq-example/pom.xml +++ b/spring-cloud-alibaba-examples/rocketmq-example/pom.xml @@ -40,6 +40,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -53,6 +61,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/pom.xml b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/pom.xml index 57cb2df5..7cb076a9 100644 --- a/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/pom.xml +++ b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/pom.xml @@ -27,11 +27,28 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot spring-boot-maven-plugin + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SimpleTask.java b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SimpleTask.java index e49c0a05..09055da1 100644 --- a/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SimpleTask.java +++ b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/SimpleTask.java @@ -19,17 +19,22 @@ package org.springframework.cloud.alibaba.cloud.examples; import com.alibaba.edas.schedulerx.ProcessResult; import com.alibaba.edas.schedulerx.ScxSimpleJobContext; import com.alibaba.edas.schedulerx.ScxSimpleJobProcessor; +import org.springframework.beans.factory.annotation.Autowired; /** * @author xiaolongzuo */ public class SimpleTask implements ScxSimpleJobProcessor { - @Override - public ProcessResult process(ScxSimpleJobContext context) { - System.out.println("-----------Hello world---------------"); - ProcessResult processResult = new ProcessResult(true); - return processResult; - } + @Autowired + private TestService testService; + + @Override + public ProcessResult process(ScxSimpleJobContext context) { + System.out.println("-----------Hello world---------------"); + testService.test(); + ProcessResult processResult = new ProcessResult(true); + return processResult; + } } diff --git a/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestService.java b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestService.java new file mode 100644 index 00000000..1786d197 --- /dev/null +++ b/spring-cloud-alibaba-examples/schedulerx-example/schedulerx-simple-task-example/src/main/java/org/springframework/cloud/alibaba/cloud/examples/TestService.java @@ -0,0 +1,30 @@ +/* + * 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.cloud.examples; + +import org.springframework.stereotype.Service; + +/** + * @author xiaolongzuo + */ +@Service +public class TestService { + + public void test() { + System.out.println("---------IOC Success--------"); + } +} diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml index 507e8498..25e56597 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/pom.xml @@ -51,6 +51,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -64,6 +72,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api/pom.xml index 5e0b8f4a..d594344e 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api/pom.xml +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-api/pom.xml @@ -16,6 +16,14 @@ api for sentinel dubbo example + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.apache.maven.plugins @@ -25,6 +33,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-consumer-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-consumer-example/pom.xml index f10efec5..f3bd3370 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-consumer-example/pom.xml +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-consumer-example/pom.xml @@ -40,6 +40,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -53,6 +61,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-provider-example/pom.xml b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-provider-example/pom.xml index 5fde98b0..6aff02b3 100644 --- a/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-provider-example/pom.xml +++ b/spring-cloud-alibaba-examples/sentinel-example/sentinel-dubbo-example/sentinel-dubbo-provider-example/pom.xml @@ -39,6 +39,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -52,6 +60,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-examples/sms-example/pom.xml b/spring-cloud-alibaba-examples/sms-example/pom.xml new file mode 100644 index 00000000..f466cc66 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + alibaba.com + sms-example + 0.0.1-SNAPSHOT + jar + + sms-example + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.6.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Finchley.SR2 + 0.2.2.BUILD-SNAPSHOT + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + org.springframework.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba-alicloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + + + alibaba.com + env-extension + 0.2.2.BUILD-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/spring-cloud-alibaba-examples/sms-example/readme-zh.md b/spring-cloud-alibaba-examples/sms-example/readme-zh.md new file mode 100644 index 00000000..b21875c9 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/readme-zh.md @@ -0,0 +1,314 @@ +# SMS Example + +## 项目说明 + +如果您的应用是 Spring Cloud/Boot 应用,且需要使用阿里云的 SMS 服务来发送短信,例如登录验证码,那么您可以使用 SMS starter 完成 Spring Cloud/Boot 应用的短信发送。 + +短信服务(Short Message Service)是阿里云为用户提供的一种通信服务的能力。支持国内和国际快速发送验证码、短信通知和推广短信,服务范围覆盖全球200多个国家和地区。更多可参考 [官网文档](https://help.aliyun.com/document_detail/60704.html?spm=5176.8195934.1283918.6.18924183bHPct2) + +## 示例 + +### 接入 SMS + +在启动示例进行演示之前,我们先了解一下如何接入 SMS。 + +**注意:本节只是为了便于您理解接入方式,本示例代码中已经完成接入工作,您只需修改 accessKey、secretKey 即可。** + +1. 修改 pom.xml 文件,引入 alicloud-sms starter。 + + ```xml + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + + ``` + +1. 在配置文件中配置 SMS 服务对应的 accessKey、secretKey 。 + + ```properties + spring.cloud.alicloud.access-key=your-ak + spring.cloud.alicloud.secret-key=your-sk + ``` + + 以阿里云 accessKey、secretKey 为例,获取方式如下。 + + i. 在阿里云控制台界面,单击右上角头像,选择 accesskeys,或者直接登录[用户信息管理界面](https://usercenter.console.aliyun.com/): + + ![undefined](https://cdn.nlark.com/lark/0/2018/png/64647/1535371973274-3ebec90a-ebde-4eb7-96ed-5372f6b32fe0.png) + + ii. 获取 accessKey、secretKey: + + ![undefined](https://cdn.nlark.com/lark/0/2018/png/64647/1535372168883-b94a3d77-3f81-4938-b409-611945a9e21c.png) + + **注意:**如果您使用了阿里云 [STS服务](https://help.aliyun.com/document_detail/28756.html) 进行短期访问权限管理,则除了 accessKey、secretKey 以外,还需配置 securityToken。 + +1. 注入 ISmsService 实例并进行短信发送等操作。 + + ```java + @RestController + public class SmsController { + @Autowired + private ISmsService smsService ; + + @RequestMapping("/send.do") + public SendSmsResponse sendMessage(String telphone,String code) { + // 组装请求对象-具体描述见控制台-文档部分内容 + SendSmsRequest request = new SendSmsRequest(); + // 必填:待发送手机号 + request.setPhoneNumbers(telphone); + // 必填:短信签名-可在短信控制台中找到 + request.setSignName("******"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 可选:模板中的变量替换JSON串,如模板内容为"【企业级分布式应用服务】,您的验证码为${code}"时,此处的值为 + request.setTemplateParam("{\"code\":\"" + code + "\"}"); + SendSmsResponse sendSmsResponse ; + try { + sendSmsResponse = smsService.sendSmsRequest(request); + } + catch (ClientException e) { + e.printStackTrace(); + sendSmsResponse = new SendSmsResponse(); + } + return sendSmsResponse ; + } + } + ``` + + **说明:** 直接注入 ISmsService 方式即可。 + +### 启动应用 + + +1. 在应用的 /src/main/resources/application.properties 中添加基本配置信息。 + + ```properties + spring.application.name=sms-example + server.port=18084 + spring.cloud.alicloud.access-key=your-ak + spring.cloud.alicloud.secret-key=your-sk + ``` + +2. 通过 IDE 直接启动或者编译打包后启动应用。 + + - IDE直接启动:找到主类 `SMSApplication`,执行 main 方法启动应用。 + - 打包编译后启动: + 1. 执行 `mvn clean package` 将工程编译打包; + 2. 执行 `java -jar sms-example.jar`启动应用。 + +应用启动后访问 http://localhost:18084/send.do?telphone=******&code=6580。 查看返回的 SendSmsResponse 的 json 串结果。 + + +### 批量短信发送 + +参考以下的 Example ,来快速开发一个具有批量短信发送的功能。在 Controller 中或者新建一个 Controler 新增如下代码: + + +```java +@RequestMapping("/batch-sms-send.do") +public SendBatchSmsResponse batchsendCheckCode( + @RequestParam(name = "code") String code) { + // 组装请求对象 + SendBatchSmsRequest request = new SendBatchSmsRequest(); + // 使用 GET 提交 + request.setMethod(MethodType.GET); + // 必填:待发送手机号。支持JSON格式的批量调用,批量上限为100个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式 + request.setPhoneNumberJson("[\"177********\",\"130********\"]"); + // 必填:短信签名-支持不同的号码发送不同的短信签名 + request.setSignNameJson("[\"*******\",\"*******\"]"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 必填:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 + // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败 + request.setTemplateParamJson( + "[{\"code\":\"" + code + "\"},{\"code\":\"" + code + "\"}]"); + SendBatchSmsResponse sendSmsResponse ; + try { + sendSmsResponse = smsService + .sendSmsBatchRequest(request); + return sendSmsResponse; + } + catch (ClientException e) { + e.printStackTrace(); + sendSmsResponse = new SendBatchSmsResponse(); + } + return sendSmsResponse ; +} +``` + +### 短信查询 + +参考以下的 Example ,可以快速开发根据某个指定的号码查询短信历史发送状态。在 Controller 中或者新建一个 Controler 新增如下代码: + +```java +/** + * + * 短信查询 Example + * @param telephone + * @return + */ +@RequestMapping("/query.do") +public QuerySendDetailsResponse querySendDetailsResponse( + @RequestParam(name = "tel") String telephone) { + // 组装请求对象 + QuerySendDetailsRequest request = new QuerySendDetailsRequest(); + // 必填-号码 + request.setPhoneNumber(telephone); + // 必填-短信发送的日期 支持30天内记录查询(可查其中一天的发送数据),格式yyyyMMdd + request.setSendDate("20190103"); + // 必填-页大小 + request.setPageSize(10L); + // 必填-当前页码从1开始计数 + request.setCurrentPage(1L); + try { + QuerySendDetailsResponse response = smsService.querySendDetails(request); + return response; + } + catch (ClientException e) { + e.printStackTrace(); + } + + return new QuerySendDetailsResponse(); +} + +``` + +查询成功后的返回结果如下所示: + + ```plain + { + "requestId": "0030EE65-25B1-43EE-BA90-D8FDACC45DC7", + "code": "OK", + "message": "OK", + "totalCount": "3", + "smsSendDetailDTOs": [ + { + "phoneNum": "152********", + "sendStatus": 3, + "errCode": "DELIVRD", + "templateCode": "SMS_******", + "content": "【企业级分布式应用服务】验证码为:1080,您正在注册成为平台会员,感谢您的支持!", + "sendDate": "2019-01-03 22:09:09", + "receiveDate": "2019-01-03 22:09:21", + "outId": "edasTraceId" + }, + { + "phoneNum": "152********", + "sendStatus": 3, + "errCode": "DELIVRD", + "templateCode": "SMS_******", + "content": "【企业级分布式应用服务】验证码为:1865,您正在注册成为平台会员,感谢您的支持!", + "sendDate": "2019-01-03 21:13:30", + "receiveDate": "2019-01-03 21:13:37", + "outId": "edasTraceId" + }, + { + "phoneNum": "152********", + "sendStatus": 3, + "errCode": "DELIVRD", + "templateCode": "SMS_*******", + "content": "【企业级分布式应用服务】验证码为:9787,您正在注册成为平台会员,感谢您的支持!", + "sendDate": "2019-01-03 17:19:11", + "receiveDate": "2019-01-03 17:19:15", + "outId": "edasTraceId" + } + ] + } + ``` + +### 短信回执消息 + +通过订阅 SmsReport 短信状态报告,可以获知每条短信的发送情况,了解短信是否达到终端用户的状态与相关信息。这些工作已经都被 Spring Cloud AliCloud SMS 封装在内部了。你只需要完成以下两步即可。 + +1. 在 **application.properties** 配置文件中(也可以是 application.yaml)配置 SmsReport 的队列名称。 + + ```properties + spring.cloud.alicloud.sms.report-queue-name=Alicom-Queue-********-SmsReport + ``` + +2. 实现 SmsReportMessageListener 接口,并初始化一个 Spring Bean . + + ```java + /** + * 如果需要监听短信是否被对方成功接收,只需实现这个接口并初始化一个 Spring Bean 即可。 + */ + @Component + public class SmsReportMessageListener + implements org.springframework.cloud.alicloud.sms.SmsReportMessageListener { + + @Override + public boolean dealMessage(Message message) { + // 在这里添加你的处理逻辑 + + //do something + + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } + } + ``` + +发送状态的回执消息如下所示: + +```plain +org.springframework.cloud.alibaba.cloud.example.SmsReportMessageListener; MessageID:9F3CFCE6BB3B2C8F-2-1682D84D9AD-20000000A,MessageMD5:C6AFEE0EE560BBC3380252337AC36985,RequestID:5C349CCEB8C115CCF344A3EB,MessageBody:"{"send_time":"2019-01-08 20:51:40","report_time":"2019-01-08 20:51:47","success":true,"err_msg":"用户接收成功","err_code":"DELIVERED","phone_number":"152********","sms_size":"1","biz_id":"667618746951900475^0","out_id":"edasTraceId"}",ReceiptHandle:"1-ODU4OTkzNDYwMi0xNTQ2OTUxOTM3LTItOA==",DequeueCount:"1",EnqueueTime:"Tue Jan 08 20:51:47 CST 2019",FirstDequeueTime:"Tue Jan 08 20:51:47 CST 2019",NextVisibleTime:"Tue Jan 08 20:52:17 CST 2019",Priority:"8" +``` + +### 上行短信消息 + + +通过订阅SmsUp上行短信消息,可以获知终端用户回复短信的内容。这些工作也已经被 Spring Cloud AliCloud SMS 封装好了。你只需要完成以下两步即可。 + +1. 在 **application.properties** 配置文件中(也可以是 application.yaml)配置 SmsReport 的队列名称。 + + ```properties + spring.cloud.alicloud.sms.up-queue-name=Alicom-Queue-********-SmsUp + ``` + +1. 实现 SmsUpMessageListener 接口,并初始化一个 Spring Bean 。 + + ```java + /** + * 如果发送的短信需要接收对方回复的状态消息,只需实现该接口并初始化一个 Spring Bean 即可。 + */ + @Component + public class SmsUpMessageListener + implements org.springframework.cloud.alicloud.sms.SmsUpMessageListener { + + @Override + public boolean dealMessage(Message message) { + // 在这里添加你的处理逻辑 + + //do something + + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } + } + ``` + +短信成功恢复后,上行短信消息 SmsUpMessageListener 回调后的信息如下所示: + +```plain +org.springframework.cloud.alibaba.cloud.example.SmsUpMessageListener; MessageID:BF030215BA85BB41-1-1682D85425F-400000003,MessageMD5:D1AF5C2D7410EF190532CBF8E17FE2B7,RequestID:5C349CEE36AF628D2A847D50,MessageBody:"{"dest_code":"2493559","send_time":"2019-01-08 20:52:14","sign_name":"【企业级分布式应用服务】","sequence_id":568585703,"phone_number":"152********","content":"5279"}",ReceiptHandle:"1-MTcxNzk4NjkxODctMTU0Njk1MTk2NC0xLTg=",DequeueCount:"1",EnqueueTime:"Tue Jan 08 20:52:14 CST 2019",FirstDequeueTime:"Tue Jan 08 20:52:14 CST 2019",NextVisibleTime:"Tue Jan 08 20:52:44 CST 2019",Priority:"8" +``` + +## 查看 Endpoint 信息 + +Spring Boot 应用支持通过 Endpoint 来暴露相关信息,SMS Starter 也支持这一点。 + +**前提条件:** + +在 maven 中添加 `spring-boot-starter-actuator`依赖,并在配置中允许 Endpoints 的访问。 + +- Spring Boot1.x 中添加配置 `management.security.enabled=false` +- Spring Boot2.x 中添加配置 `management.endpoints.web.exposure.include=*` + +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 来访问。 + +Endpoint 内部会显示最近 20 条单个短信发送的记录和批量短信发送的记录,以及当前短信消息的配置信息(包括是**SmsReport** 还是 **SmsUp**,**队列名称**,以及对应的 **MessageListener** )。 + + +如果您对 Spring Cloud SMS Starter 有任何建议或想法,欢迎提交 issue 中或者通过其他社区渠道向我们反馈。 diff --git a/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsApplication.java b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsApplication.java new file mode 100644 index 00000000..83f25a94 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsApplication.java @@ -0,0 +1,32 @@ +/* + * 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.cloud.example; + +import org.springframework.alicloud.env.extension.ImportExtraConfig; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * + */ +@SpringBootApplication +@ImportExtraConfig(name = "/Users/toava/sms.properties") +public class SmsApplication { + + public static void main(String[] args) throws Exception{ + SpringApplication.run(SmsApplication.class, args); + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsController.java b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsController.java new file mode 100644 index 00000000..97308e66 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsController.java @@ -0,0 +1,139 @@ +package org.springframework.cloud.alibaba.cloud.example; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.alicloud.sms.ISmsService; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.aliyun.mns.model.Message; +import com.aliyuncs.dysmsapi.model.v20170525.*; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.http.MethodType; + +@RestController +public class SmsController { + + @Autowired + private Environment environment; + + @Autowired + private ISmsService smsService; + + @Autowired + private SmsReportMessageListener smsReportMessageListener; + + @GetMapping("/report-queue.do") + public String getSmsReportQueuename(){ + + return environment.getProperty("spring.cloud.alicloud.sms.up-queue-name"); + } + + /** + * 短信发送 Example + * @param code + * @return + */ + @RequestMapping("/batch-sms-send.do") + public SendBatchSmsResponse batchsendCheckCode( + @RequestParam(name = "code") String code) { + // 组装请求对象 + SendBatchSmsRequest request = new SendBatchSmsRequest(); + // 使用post提交 + request.setMethod(MethodType.GET); + // 必填:待发送手机号。支持JSON格式的批量调用,批量上限为100个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式 + request.setPhoneNumberJson("[\"177********\",\"130********\"]"); + // 必填:短信签名-支持不同的号码发送不同的短信签名 + request.setSignNameJson("[\"*******\",\"*******\"]"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 必填:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 + // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败 + request.setTemplateParamJson( + "[{\"code\":\"" + code + "\"},{\"code\":\"" + code + "\"}]"); + // 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段) + // request.setSmsUpExtendCodeJson("[\"90997\",\"90998\"]"); + try { + SendBatchSmsResponse sendSmsResponse = smsService + .sendSmsBatchRequest(request); + return sendSmsResponse; + } + catch (ClientException e) { + e.printStackTrace(); + } + return new SendBatchSmsResponse(); + } + + /** + * 短信发送 Example + * @param code + * @return + */ + @RequestMapping("/sms-send.do") + public SendSmsResponse sendCheckCode(@RequestParam(name = "code") String code) { + // 组装请求对象-具体描述见控制台-文档部分内容 + SendSmsRequest request = new SendSmsRequest(); + // 必填:待发送手机号 + request.setPhoneNumbers("******"); + // 必填:短信签名-可在短信控制台中找到 + request.setSignName("******"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("******"); + // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 + request.setTemplateParam("{\"code\":\"" + code + "\"}"); + + // 选填-上行短信扩展码(无特殊需求用户请忽略此字段) + // request.setSmsUpExtendCode("90997"); + + // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 + request.setOutId("****TraceId"); + try { + SendSmsResponse sendSmsResponse = smsService.sendSmsRequest(request); + return sendSmsResponse; + } + catch (ClientException e) { + e.printStackTrace(); + } + return new SendSmsResponse(); + } + + /** + * + * 短信查询 Example + * @param telephone + * @return + */ + @RequestMapping("/query.do") + public QuerySendDetailsResponse querySendDetailsResponse( + @RequestParam(name = "tel") String telephone) { + // 组装请求对象 + QuerySendDetailsRequest request = new QuerySendDetailsRequest(); + // 必填-号码 + request.setPhoneNumber(telephone); + // 必填-短信发送的日期 支持30天内记录查询(可查其中一天的发送数据),格式yyyyMMdd + request.setSendDate("20190103"); + // 必填-页大小 + request.setPageSize(10L); + // 必填-当前页码从1开始计数 + request.setCurrentPage(1L); + try { + QuerySendDetailsResponse response = smsService.querySendDetails(request); + return response; + } + catch (ClientException e) { + e.printStackTrace(); + } + + return new QuerySendDetailsResponse(); + } + + @RequestMapping("/sms-report.do") + public List smsReport() { + + return smsReportMessageListener.getSmsReportMessageSet(); + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsReportMessageListener.java b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsReportMessageListener.java new file mode 100644 index 00000000..aa092789 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsReportMessageListener.java @@ -0,0 +1,28 @@ +package org.springframework.cloud.alibaba.cloud.example; + +import com.aliyun.mns.model.Message; +import org.springframework.stereotype.Component; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author 如果需要监听短信是否被对方成功接收,只需实现这个接口并初始化一个 Spring Bean 即可。 + */ +@Component +public class SmsReportMessageListener + implements org.springframework.cloud.alicloud.sms.SmsReportMessageListener { + private List smsReportMessageSet = new LinkedList<>(); + + @Override + public boolean dealMessage(Message message) { + smsReportMessageSet.add(message); + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } + + public List getSmsReportMessageSet() { + + return smsReportMessageSet; + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsUpMessageListener.java b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsUpMessageListener.java new file mode 100644 index 00000000..22f4efec --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsUpMessageListener.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.alibaba.cloud.example; + +import org.springframework.stereotype.Component; + +import com.aliyun.mns.model.Message; + +/** + * @author 如果发送的短信需要接收对方回复的状态消息,只需实现该接口并初始化一个 Spring Bean 即可。 + */ +@Component +public class SmsUpMessageListener + implements org.springframework.cloud.alicloud.sms.SmsUpMessageListener { + + @Override + public boolean dealMessage(Message message) { + System.err.println(this.getClass().getName() + "; " + message.toString()); + return true; + } +} \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/sms-example/src/main/resources/application.properties b/spring-cloud-alibaba-examples/sms-example/src/main/resources/application.properties new file mode 100644 index 00000000..8c689827 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/resources/application.properties @@ -0,0 +1,4 @@ +spring.application.name=sca-sms-example +server.port=9051 +# config management +management.endpoints.web.exposure.include=* \ No newline at end of file diff --git a/spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/pom.xml b/spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/pom.xml index 060bb532..6874be2d 100644 --- a/spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/pom.xml +++ b/spring-cloud-alibaba-examples/spring-cloud-bus-rocketmq-example/pom.xml @@ -35,6 +35,14 @@ + + + + org.codehaus.mojo + cobertura-maven-plugin + + + org.springframework.boot @@ -48,6 +56,15 @@ true + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigBootstrapConfiguration.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigBootstrapConfiguration.java index 076d884f..7fedcc16 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigBootstrapConfiguration.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigBootstrapConfiguration.java @@ -27,15 +27,16 @@ import org.springframework.context.annotation.Configuration; @Configuration public class NacosConfigBootstrapConfiguration { - @Bean - public NacosPropertySourceLocator nacosPropertySourceLocator() { - return new NacosPropertySourceLocator(); - } - @Bean @ConditionalOnMissingBean public NacosConfigProperties nacosConfigProperties() { return new NacosConfigProperties(); } + @Bean + public NacosPropertySourceLocator nacosPropertySourceLocator( + NacosConfigProperties nacosConfigProperties) { + return new NacosPropertySourceLocator(nacosConfigProperties); + } + } diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigProperties.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigProperties.java index 94167220..4fefa863 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigProperties.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/NacosConfigProperties.java @@ -18,8 +18,8 @@ package org.springframework.cloud.alibaba.nacos; import com.alibaba.nacos.api.NacosFactory; import com.alibaba.nacos.api.config.ConfigService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.env.Environment; @@ -30,7 +30,14 @@ import java.util.List; import java.util.Objects; import java.util.Properties; -import static com.alibaba.nacos.api.PropertyKeyConst.*; +import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY; +import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME; +import static com.alibaba.nacos.api.PropertyKeyConst.CONTEXT_PATH; +import static com.alibaba.nacos.api.PropertyKeyConst.ENCODE; +import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT; +import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE; +import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY; +import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; /** * nacos properties @@ -42,10 +49,9 @@ import static com.alibaba.nacos.api.PropertyKeyConst.*; @ConfigurationProperties(NacosConfigProperties.PREFIX) public class NacosConfigProperties { - public static final String PREFIX = "spring.cloud.nacos.config"; + static final String PREFIX = "spring.cloud.nacos.config"; - private static final Logger LOGGER = LoggerFactory - .getLogger(NacosConfigProperties.class); + private static final Log log = LogFactory.getLog(NacosConfigProperties.class); /** * nacos config server address @@ -348,7 +354,9 @@ public class NacosConfigProperties { return configService; } catch (Exception e) { - LOGGER.error("create config service error!properties={},e=,", this, e); + log.error( + "create config service error!properties=" + this.toString() + ",e=,", + e); return null; } } diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceBuilder.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceBuilder.java index b900a674..c820d8b0 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceBuilder.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceBuilder.java @@ -18,23 +18,26 @@ package org.springframework.cloud.alibaba.nacos.client; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.cloud.alibaba.nacos.NacosPropertySourceRepository; import org.springframework.core.io.ByteArrayResource; import org.springframework.util.StringUtils; import java.io.StringReader; -import java.util.*; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; /** * @author xiaojing * @author pbting */ public class NacosPropertySourceBuilder { - private static final Logger LOGGER = LoggerFactory - .getLogger(NacosPropertySourceBuilder.class); + private static final Log log = LogFactory.getLog(NacosPropertySourceBuilder.class); private static final Properties EMPTY_PROPERTIES = new Properties(); private ConfigService configService; @@ -68,9 +71,6 @@ public class NacosPropertySourceBuilder { NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) { Properties p = loadNacosData(dataId, group, fileExtension); - if (p == null) { - p = EMPTY_PROPERTIES; - } NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable); NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource); @@ -82,7 +82,7 @@ public class NacosPropertySourceBuilder { try { data = configService.getConfig(dataId, group, timeout); if (!StringUtils.isEmpty(data)) { - LOGGER.info(String.format("Loading nacos data, dataId: '%s', group: '%s'", + log.info(String.format("Loading nacos data, dataId: '%s', group: '%s'", dataId, group)); if (fileExtension.equalsIgnoreCase("properties")) { @@ -101,13 +101,13 @@ public class NacosPropertySourceBuilder { } } catch (NacosException e) { - LOGGER.error("get data from Nacos error,dataId:{}, ", dataId, e); + log.error("get data from Nacos error,dataId:" + dataId + ", ", e); } catch (Exception e) { - LOGGER.error("parse data from Nacos error,dataId:{},data:{},", dataId, data, - e); + log.error("parse data from Nacos error,dataId:" + dataId + ",data:" + data + + ",", e); } - return null; + return EMPTY_PROPERTIES; } @SuppressWarnings("unchecked") diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceLocator.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceLocator.java index e0cabb38..5ffb7ac3 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceLocator.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/client/NacosPropertySourceLocator.java @@ -17,9 +17,8 @@ package org.springframework.cloud.alibaba.nacos.client; import com.alibaba.nacos.api.config.ConfigService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.cloud.alibaba.nacos.NacosConfigProperties; import org.springframework.cloud.alibaba.nacos.NacosPropertySourceRepository; import org.springframework.cloud.alibaba.nacos.refresh.NacosContextRefresher; @@ -40,8 +39,7 @@ import java.util.List; @Order(0) public class NacosPropertySourceLocator implements PropertySourceLocator { - private static final Logger LOGGER = LoggerFactory - .getLogger(NacosPropertySourceLocator.class); + private static final Log log = LogFactory.getLog(NacosPropertySourceLocator.class); private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS"; private static final String SEP1 = "-"; private static final String DOT = "."; @@ -49,22 +47,21 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { private static final List SUPPORT_FILE_EXTENSION = Arrays.asList("properties", "yaml", "yml"); - @Autowired + private NacosPropertySourceBuilder nacosPropertySourceBuilder; + private NacosConfigProperties nacosConfigProperties; - public NacosPropertySourceLocator() { + public NacosPropertySourceLocator(NacosConfigProperties nacosConfigProperties) { + this.nacosConfigProperties = nacosConfigProperties; } - private NacosPropertySourceBuilder nacosPropertySourceBuilder; - @Override public PropertySource locate(Environment env) { ConfigService configService = nacosConfigProperties.configServiceInstance(); if (null == configService) { - LOGGER.warn( - "no instance of config service found, can't load config from nacos"); + log.warn("no instance of config service found, can't load config from nacos"); return null; } long timeout = nacosConfigProperties.getTimeout(); @@ -167,7 +164,7 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) { - if (NacosContextRefresher.loadCount.get() != 0) { + if (NacosContextRefresher.getRefreshCount() != 0) { NacosPropertySource ps; if (!isRefreshable) { ps = NacosPropertySourceRepository.getNacosPropertySource(dataId); @@ -187,13 +184,18 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { private static void checkDataIdFileExtension(String[] sharedDataIdArry) { StringBuilder stringBuilder = new StringBuilder(); - outline: for (int i = 0; i < sharedDataIdArry.length; i++) { + for (int i = 0; i < sharedDataIdArry.length; i++) { + boolean isLegal = false; for (String fileExtension : SUPPORT_FILE_EXTENSION) { if (sharedDataIdArry[i].indexOf(fileExtension) > 0) { - continue outline; + isLegal = true; + break; } } - stringBuilder.append(sharedDataIdArry[i] + ","); + // add tips + if (!isLegal) { + stringBuilder.append(sharedDataIdArry[i] + ","); + } } if (stringBuilder.length() > 0) { diff --git a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosContextRefresher.java b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosContextRefresher.java index c726f948..fcc6d9df 100644 --- a/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosContextRefresher.java +++ b/spring-cloud-alibaba-nacos-config/src/main/java/org/springframework/cloud/alibaba/nacos/refresh/NacosContextRefresher.java @@ -19,8 +19,8 @@ package org.springframework.cloud.alibaba.nacos.refresh; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.alibaba.nacos.NacosPropertySourceRepository; import org.springframework.cloud.alibaba.nacos.client.NacosPropertySource; @@ -51,10 +51,9 @@ import java.util.concurrent.atomic.AtomicLong; public class NacosContextRefresher implements ApplicationListener, ApplicationContextAware { - private final static Logger LOGGER = LoggerFactory - .getLogger(NacosContextRefresher.class); + private final static Log log = LogFactory.getLog(NacosContextRefresher.class); - public static final AtomicLong loadCount = new AtomicLong(0); + private static final AtomicLong REFRESH_COUNT = new AtomicLong(0); private final NacosRefreshProperties refreshProperties; @@ -108,7 +107,7 @@ public class NacosContextRefresher Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() { @Override public void receiveConfigInfo(String configInfo) { - loadCount.incrementAndGet(); + refreshCountIncrement(); String md5 = ""; if (!StringUtils.isEmpty(configInfo)) { try { @@ -117,14 +116,14 @@ public class NacosContextRefresher .toString(16); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - LOGGER.warn("[Nacos] unable to get md5 for dataId: " + dataId, e); + log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e); } } refreshHistory.add(dataId, md5); applicationContext.publishEvent( new RefreshEvent(this, null, "Refresh Nacos config")); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Refresh Nacos config group{},dataId{}", group, dataId); + if (log.isDebugEnabled()) { + log.debug("Refresh Nacos config group " + group + ",dataId" + dataId); } } @@ -142,4 +141,11 @@ public class NacosContextRefresher } } + public static long getRefreshCount() { + return REFRESH_COUNT.get(); + } + + public static void refreshCountIncrement() { + REFRESH_COUNT.incrementAndGet(); + } } diff --git a/spring-cloud-alibaba-nacos-discovery/pom.xml b/spring-cloud-alibaba-nacos-discovery/pom.xml index d0a5ddd1..87df397d 100644 --- a/spring-cloud-alibaba-nacos-discovery/pom.xml +++ b/spring-cloud-alibaba-nacos-discovery/pom.xml @@ -82,5 +82,28 @@ - + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + org/springframework/cloud/alibaba/nacos/**.*class + + + + + + + diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java index 178333fa..9333d8ae 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/NacosDiscoveryClient.java @@ -23,7 +23,11 @@ import org.slf4j.LoggerFactory; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author xiaojing @@ -65,7 +69,7 @@ public class NacosDiscoveryClient implements DiscoveryClient { nacosServiceInstance.setHost(instance.getIp()); nacosServiceInstance.setPort(instance.getPort()); nacosServiceInstance.setServiceId(serviceId); - Map metadata = new HashMap(); + Map metadata = new HashMap<>(); metadata.put("instanceId", instance.getInstanceId()); metadata.put("weight", instance.getWeight() + ""); metadata.put("healthy", instance.isHealthy() + ""); @@ -82,7 +86,7 @@ public class NacosDiscoveryClient implements DiscoveryClient { private static List hostToServiceInstanceList( List instances, String serviceId) { - List result = new ArrayList(instances.size()); + List result = new ArrayList<>(instances.size()); for (Instance instance : instances) { result.add(hostToServiceInstance(instance, serviceId)); } diff --git a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java index c2b4990f..02195546 100644 --- a/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java +++ b/spring-cloud-alibaba-nacos-discovery/src/main/java/org/springframework/cloud/alibaba/nacos/ribbon/NacosServerList.java @@ -16,6 +16,7 @@ package org.springframework.cloud.alibaba.nacos.ribbon; +import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractServerList; import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; @@ -23,8 +24,6 @@ import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties; import java.util.ArrayList; import java.util.List; -import com.alibaba.nacos.api.naming.pojo.Instance; - /** * @author xiaojing * @author renhaojun @@ -79,4 +78,4 @@ public class NacosServerList extends AbstractServerList { public void initWithNiwsConfig(IClientConfig iClientConfig) { this.serviceId = iClientConfig.getClientName(); } -} +} \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/NacosDataSourceWithAuthorization.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/NacosDataSourceWithAuthorization.java deleted file mode 100644 index 6d63c401..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/NacosDataSourceWithAuthorization.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.datasource; - -import java.util.Properties; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; -import com.alibaba.csp.sentinel.datasource.AbstractDataSource; -import com.alibaba.csp.sentinel.datasource.Converter; -import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; -import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.util.StringUtil; -import com.alibaba.nacos.api.NacosFactory; -import com.alibaba.nacos.api.config.ConfigService; -import com.alibaba.nacos.api.config.listener.Listener; - -/** - * {@link NacosDataSource} now is not support ak、sk,namespace and endpoint. This class may - * be delete when {@link NacosDataSource} support commercialized - * - * @author Jim - */ -public class NacosDataSourceWithAuthorization extends AbstractDataSource { - - private static final int DEFAULT_TIMEOUT = 3000; - - private final ExecutorService pool = new ThreadPoolExecutor(1, 1, 0, - TimeUnit.MILLISECONDS, new ArrayBlockingQueue(1), - new NamedThreadFactory("sentinel-nacos-auth-ds-update"), - new ThreadPoolExecutor.DiscardOldestPolicy()); - - private final Listener configListener; - private final Properties properties; - private final String dataId; - private final String groupId; - - private ConfigService configService = null; - - public NacosDataSourceWithAuthorization(final Properties properties, - final String groupId, final String dataId, Converter parser) { - super(parser); - if (StringUtil.isBlank(groupId) || StringUtil.isBlank(dataId)) { - throw new IllegalArgumentException(String - .format("Bad argument: groupId=[%s], dataId=[%s]", groupId, dataId)); - } - this.groupId = groupId; - this.dataId = dataId; - this.properties = properties; - this.configListener = new Listener() { - @Override - public Executor getExecutor() { - return pool; - } - - @Override - public void receiveConfigInfo(final String configInfo) { - RecordLog.info(String.format( - "[NacosDataSourceWithAuthorization] New property value received for %s", - properties.toString())); - T newValue = NacosDataSourceWithAuthorization.this.parser - .convert(configInfo); - // Update the new value to the property. - getProperty().updateValue(newValue); - } - }; - initNacosListener(); - loadInitialConfig(); - } - - private void loadInitialConfig() { - try { - T newValue = loadConfig(); - if (newValue == null) { - RecordLog.warn( - "[NacosDataSourceWithAuthorization] WARN: initial config is null, you may have to check your data source"); - } - getProperty().updateValue(newValue); - } - catch (Exception ex) { - RecordLog.warn( - "[NacosDataSourceWithAuthorization] Error when loading initial config", - ex); - } - } - - private void initNacosListener() { - try { - this.configService = NacosFactory.createConfigService(properties); - // Add config listener. - configService.addListener(dataId, groupId, configListener); - } - catch (Exception e) { - RecordLog.warn( - "[NacosDataSourceWithAuthorization] Error occurred when initializing Nacos data source", - e); - e.printStackTrace(); - } - } - - @Override - public String readSource() throws Exception { - if (configService == null) { - throw new IllegalStateException( - "Nacos config service has not been initialized or error occurred"); - } - return configService.getConfig(dataId, groupId, DEFAULT_TIMEOUT); - } - - @Override - public void close() { - if (configService != null) { - configService.removeListener(dataId, groupId, configListener); - } - pool.shutdownNow(); - } -} 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 5be00c78..e5795caa 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 @@ -1,13 +1,12 @@ 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.cloud.alibaba.sentinel.datasource.factorybean.NacosDataSourceWithAuthorizationFactoryBean; import org.springframework.util.StringUtils; +import javax.validation.constraints.NotEmpty; + /** * Nacos Properties class Using by {@link DataSourcePropertiesConfiguration} and * {@link NacosDataSourceFactoryBean} @@ -40,8 +39,6 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties { if (!StringUtils.isEmpty(System.getProperties() .getProperty(SentinelDataSourceConstants.NACOS_DATASOURCE_ENDPOINT))) { this.setServerAddr(null); - this.setFactoryBeanName( - NacosDataSourceWithAuthorizationFactoryBean.class.getName()); this.setEndpoint(System.getProperties() .getProperty(SentinelDataSourceConstants.NACOS_DATASOURCE_ENDPOINT)); this.setNamespace(System.getProperties() @@ -119,8 +116,6 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties { public static NacosDataSourceProperties buildByEDAS(String type) { NacosDataSourceProperties result = new NacosDataSourceProperties(); - result.setFactoryBeanName( - NacosDataSourceWithAuthorizationFactoryBean.class.getName()); result.setEndpoint(System.getProperties() .getProperty(SentinelDataSourceConstants.NACOS_DATASOURCE_ENDPOINT)); result.setNamespace(System.getProperties() 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 cc884d1c..1683b182 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 @@ -2,8 +2,12 @@ package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; 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.cloud.alibaba.sentinel.datasource.SentinelDataSourceConstants; +import org.springframework.util.StringUtils; + +import java.util.Properties; /** * A {@link FactoryBean} for creating {@link NacosDataSource} instance. @@ -13,50 +17,96 @@ import org.springframework.beans.factory.FactoryBean; */ public class NacosDataSourceFactoryBean implements FactoryBean { - private String serverAddr; - private String groupId; - private String dataId; - private Converter converter; + private String serverAddr; + private String groupId; + private String dataId; + private Converter converter; - @Override - public NacosDataSource getObject() throws Exception { - return new NacosDataSource(serverAddr, groupId, dataId, converter); - } + private String endpoint; + private String namespace; + private String accessKey; + private String secretKey; - @Override - public Class getObjectType() { - return NacosDataSource.class; - } + @Override + public NacosDataSource getObject() throws Exception { + if (!StringUtils.isEmpty(System.getProperties() + .getProperty(SentinelDataSourceConstants.NACOS_DATASOURCE_ENDPOINT))) { + Properties properties = new Properties(); + properties.setProperty(PropertyKeyConst.ACCESS_KEY, this.accessKey); + properties.setProperty(PropertyKeyConst.SERVER_ADDR, this.secretKey); + properties.setProperty(PropertyKeyConst.ENDPOINT, this.endpoint); + properties.setProperty(PropertyKeyConst.NAMESPACE, this.namespace); + return new NacosDataSource(properties, groupId, dataId, converter); + } + return new NacosDataSource(serverAddr, groupId, dataId, converter); + } - public String getServerAddr() { - return serverAddr; - } + @Override + public Class getObjectType() { + return NacosDataSource.class; + } - public void setServerAddr(String serverAddr) { - this.serverAddr = serverAddr; - } + public String getServerAddr() { + return serverAddr; + } - public String getGroupId() { - return groupId; - } + public void setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + } - public void setGroupId(String groupId) { - this.groupId = groupId; - } + public String getGroupId() { + return groupId; + } - public String getDataId() { - return dataId; - } + public void setGroupId(String groupId) { + this.groupId = groupId; + } - public void setDataId(String dataId) { - this.dataId = dataId; - } + public String getDataId() { + return dataId; + } - public Converter getConverter() { - return converter; - } + public void setDataId(String dataId) { + this.dataId = dataId; + } - public void setConverter(Converter converter) { - this.converter = converter; - } + public Converter getConverter() { + return converter; + } + + public void setConverter(Converter converter) { + this.converter = converter; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } } diff --git a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceWithAuthorizationFactoryBean.java b/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceWithAuthorizationFactoryBean.java deleted file mode 100644 index 27f0de86..00000000 --- a/spring-cloud-alibaba-sentinel-datasource/src/main/java/org/springframework/cloud/alibaba/sentinel/datasource/factorybean/NacosDataSourceWithAuthorizationFactoryBean.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.springframework.cloud.alibaba.sentinel.datasource.factorybean; - -import java.util.Properties; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.cloud.alibaba.sentinel.datasource.NacosDataSourceWithAuthorization; - -import com.alibaba.csp.sentinel.datasource.Converter; -import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; -import com.alibaba.nacos.api.PropertyKeyConst; - -/** - * A {@link FactoryBean} for creating {@link NacosDataSource} instance. - * - * @author Jim - * @see NacosDataSource - */ -public class NacosDataSourceWithAuthorizationFactoryBean - implements FactoryBean { - - private String endpoint; - private String namespace; - private String accessKey; - private String secretKey; - - private String groupId; - private String dataId; - private Converter converter; - - @Override - public NacosDataSourceWithAuthorization getObject() throws Exception { - Properties properties = new Properties(); - properties.put(PropertyKeyConst.ACCESS_KEY, accessKey); - properties.put(PropertyKeyConst.SECRET_KEY, secretKey); - properties.put(PropertyKeyConst.NAMESPACE, namespace); - properties.put(PropertyKeyConst.ENDPOINT, endpoint); - return new NacosDataSourceWithAuthorization(properties, groupId, dataId, - converter); - } - - @Override - public Class getObjectType() { - return NacosDataSourceWithAuthorization.class; - } - - public String getEndpoint() { - return endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getAccessKey() { - return accessKey; - } - - public void setAccessKey(String accessKey) { - this.accessKey = accessKey; - } - - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public Converter getConverter() { - return converter; - } - - public void setConverter(Converter converter) { - this.converter = converter; - } -} diff --git a/spring-cloud-alibaba-sentinel/pom.xml b/spring-cloud-alibaba-sentinel/pom.xml index b5707134..9410a293 100644 --- a/spring-cloud-alibaba-sentinel/pom.xml +++ b/spring-cloud-alibaba-sentinel/pom.xml @@ -42,6 +42,19 @@ true + + org.springframework.cloud + spring-cloud-commons + true + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + provided + true + + com.alibaba.csp sentinel-parameter-flow-control diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java index 714cc08b..0c2a9d09 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelConstants.java @@ -23,6 +23,9 @@ public interface SentinelConstants { String PROPERTY_PREFIX = "spring.cloud.sentinel"; + String BLOCK_TYPE = "block"; + String FALLBACK_TYPE = "fallback"; + // commercialization String FLOW_DATASOURCE_NAME = "edas-flow"; diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java index 63ce6865..d4621bd7 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/SentinelProperties.java @@ -16,21 +16,21 @@ package org.springframework.cloud.alibaba.sentinel; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.LogBase; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePropertiesConfiguration; import org.springframework.core.Ordered; import org.springframework.validation.annotation.Validated; -import com.alibaba.csp.sentinel.config.SentinelConfig; -import com.alibaba.csp.sentinel.log.LogBase; -import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; /** + * {@link ConfigurationProperties} for Sentinel. + * * @author xiaojing * @author hengyunabc * @author jiashuai.xie @@ -41,58 +41,51 @@ import com.alibaba.csp.sentinel.transport.config.TransportConfig; public class SentinelProperties { /** - * earlier initialize heart-beat when the spring container starts when the - * transport dependency is on classpath ,the configuration is effective + * Earlier initialize heart-beat when the spring container starts when the transport + * dependency is on classpath, the configuration is effective. */ private boolean eager = false; /** - * enable sentinel auto configure, the default value is true + * Enable sentinel auto configure, the default value is true. */ private boolean enabled = true; /** - * configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper' + * Configurations about datasource, like 'nacos', 'apollo', 'file', 'zookeeper'. */ private Map datasource = new TreeMap<>( String.CASE_INSENSITIVE_ORDER); /** - * transport configuration about dashboard and client + * Transport configuration about dashboard and client. */ - @NestedConfigurationProperty private Transport transport = new Transport(); /** - * metric configuration about resource + * Metric configuration about resource. */ - @NestedConfigurationProperty private Metric metric = new Metric(); /** - * web servlet configuration when the application is web ,the configuration is - * effective + * Web servlet configuration when the application is web, the configuration is + * effective. */ - @NestedConfigurationProperty private Servlet servlet = new Servlet(); /** - * sentinel filter when the application is web ,the configuration is effective - * + * Sentinel filter when the application is web, the configuration is effective. */ - @NestedConfigurationProperty private Filter filter = new Filter(); /** - * flow configuration + * Sentinel Flow configuration. */ - @NestedConfigurationProperty private Flow flow = new Flow(); /** - * sentinel log configuration {@link LogBase} + * Sentinel log configuration {@link LogBase}. */ - @NestedConfigurationProperty private Log log = new Log(); public boolean isEager() { @@ -170,7 +163,7 @@ public class SentinelProperties { public static class Flow { /** - * the cold factor {@link SentinelConfig#COLD_FACTOR} + * The cold factor {@link SentinelConfig#COLD_FACTOR}. */ private String coldFactor = "3"; @@ -187,7 +180,7 @@ public class SentinelProperties { public static class Servlet { /** - * The process page when the flow control is triggered + * The process page when the flow control is triggered. */ private String blockPage; @@ -203,17 +196,17 @@ public class SentinelProperties { public static class Metric { /** - * the metric file size {@link SentinelConfig#SINGLE_METRIC_FILE_SIZE} + * The metric file size {@link SentinelConfig#SINGLE_METRIC_FILE_SIZE}. */ private String fileSingleSize; /** - * the total metric file count {@link SentinelConfig#TOTAL_METRIC_FILE_COUNT} + * The total metric file count {@link SentinelConfig#TOTAL_METRIC_FILE_COUNT}. */ private String fileTotalCount; /** - * charset when sentinel write or search metric file + * Charset when sentinel write or search metric file. * {@link SentinelConfig#CHARSET} */ private String charset = "UTF-8"; @@ -246,22 +239,28 @@ public class SentinelProperties { public static class Transport { /** - * sentinel api port,default value is 8719 {@link TransportConfig#SERVER_PORT} + * Sentinel api port, default value is 8719 {@link TransportConfig#SERVER_PORT}. */ private String port = "8719"; /** - * sentinel dashboard address, won't try to connect dashboard when address is - * empty {@link TransportConfig#CONSOLE_SERVER} + * Sentinel dashboard address, won't try to connect dashboard when address is + * empty {@link TransportConfig#CONSOLE_SERVER}. */ private String dashboard = ""; /** - * send heartbeat interval millisecond - * {@link TransportConfig#HEARTBEAT_INTERVAL_MS} + * Send heartbeat interval millisecond + * {@link TransportConfig#HEARTBEAT_INTERVAL_MS}. */ private String heartbeatIntervalMs; + /** + * Get heartbeat client local ip. If the client ip not configured, it will be the + * address of local host. + */ + private String clientIp; + public String getHeartbeatIntervalMs() { return heartbeatIntervalMs; } @@ -286,17 +285,24 @@ public class SentinelProperties { this.dashboard = dashboard; } + public String getClientIp() { + return clientIp; + } + + public void setClientIp(String clientIp) { + this.clientIp = clientIp; + } } public static class Filter { /** - * sentinel filter chain order. + * Sentinel filter chain order. */ private int order = Ordered.HIGHEST_PRECEDENCE; /** - * URL pattern for sentinel filter,default is /* + * URL pattern for sentinel filter, default is /* */ private List urlPatterns; @@ -320,12 +326,12 @@ public class SentinelProperties { public static class Log { /** - * sentinel log base dir + * Sentinel log base dir. */ private String dir; /** - * distinguish the log file by pid number + * Distinguish the log file by pid number. */ private boolean switchPid = false; 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 8428632f..c9699e66 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 @@ -16,11 +16,7 @@ package org.springframework.cloud.alibaba.sentinel; -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.Filter; - +import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +27,9 @@ 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 javax.servlet.Filter; +import java.util.ArrayList; +import java.util.List; /** * @author xiaojing @@ -54,11 +52,6 @@ public class SentinelWebAutoConfiguration { SentinelProperties.Filter filterConfig = properties.getFilter(); - if (null == filterConfig) { - filterConfig = new SentinelProperties.Filter(); - properties.setFilter(filterConfig); - } - if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) { List defaultPatterns = new ArrayList<>(); 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 4349cf70..26613a42 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,23 +16,7 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import java.util.Optional; - -import javax.annotation.PostConstruct; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -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.SentinelProperties; -import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; -import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -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; @@ -48,9 +32,25 @@ import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; 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.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +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.SentinelProperties; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; +import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.util.Optional; /** * @author xiaojing @@ -74,8 +74,20 @@ public class SentinelAutoConfiguration { @Autowired private Optional urlBlockHandlerOptional; + @Autowired + private Optional requestOriginParserOptional; + @PostConstruct private void init() { + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) + && StringUtils.hasText(properties.getLog().getDir())) { + System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); + } + if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) + && properties.getLog().isSwitchPid()) { + System.setProperty(LogBase.LOG_NAME_USE_PID, + String.valueOf(properties.getLog().isSwitchPid())); + } if (StringUtils.isEmpty(System.getProperty(AppNameUtil.APP_NAME)) && StringUtils.hasText(projectName)) { System.setProperty(AppNameUtil.APP_NAME, projectName); @@ -96,6 +108,11 @@ public class SentinelAutoConfiguration { System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS, properties.getTransport().getHeartbeatIntervalMs()); } + if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_CLIENT_IP)) + && StringUtils.hasText(properties.getTransport().getClientIp())) { + System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP, + properties.getTransport().getClientIp()); + } if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET)) && StringUtils.hasText(properties.getMetric().getCharset())) { System.setProperty(SentinelConfig.CHARSET, @@ -121,18 +138,10 @@ public class SentinelAutoConfiguration { if (StringUtils.hasText(properties.getServlet().getBlockPage())) { WebServletConfig.setBlockPage(properties.getServlet().getBlockPage()); } - if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR)) - && StringUtils.hasText(properties.getLog().getDir())) { - System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir()); - } - if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID)) - && properties.getLog().isSwitchPid()) { - System.setProperty(LogBase.LOG_NAME_USE_PID, - String.valueOf(properties.getLog().isSwitchPid())); - } urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler); urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner); + requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser); // earlier initialize if (properties.isEager()) { @@ -150,13 +159,15 @@ public class SentinelAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") - public SentinelBeanPostProcessor sentinelBeanPostProcessor() { - return new SentinelBeanPostProcessor(); + public SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); } @Bean - public SentinelDataSourceHandler sentinelDataSourceHandler() { - return new SentinelDataSourceHandler(); + public SentinelDataSourceHandler sentinelDataSourceHandler( + DefaultListableBeanFactory beanFactory) { + return new SentinelDataSourceHandler(beanFactory); } protected static class SentinelConverterConfiguration { diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java index dd84109e..4b222d01 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelBeanPostProcessor.java @@ -16,22 +16,31 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import java.util.concurrent.ConcurrentHashMap; - +import com.alibaba.csp.sentinel.slots.block.BlockException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.cloud.alibaba.sentinel.SentinelConstants; import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.context.ApplicationContext; import org.springframework.core.type.StandardMethodMetadata; import org.springframework.core.type.classreading.MethodMetadataReadingVisitor; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + /** * PostProcessor handle @SentinelRestTemplate Annotation, add interceptor for RestTemplate * @@ -41,8 +50,14 @@ import org.springframework.web.client.RestTemplate; */ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProcessor { - @Autowired - private ApplicationContext applicationContext; + private static final Logger logger = LoggerFactory + .getLogger(SentinelBeanPostProcessor.class); + + private final ApplicationContext applicationContext; + + public SentinelBeanPostProcessor(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } private ConcurrentHashMap cache = new ConcurrentHashMap<>(); @@ -60,10 +75,74 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces sentinelRestTemplate = beanDefinition.getResolvedFactoryMethod() .getAnnotation(SentinelRestTemplate.class); } + // check class and method validation + checkSentinelRestTemplate(sentinelRestTemplate, beanName); cache.put(beanName, sentinelRestTemplate); } } + private void checkSentinelRestTemplate(SentinelRestTemplate sentinelRestTemplate, + String beanName) { + checkBlock4RestTemplate(sentinelRestTemplate.blockHandlerClass(), + sentinelRestTemplate.blockHandler(), beanName, + SentinelConstants.BLOCK_TYPE); + checkBlock4RestTemplate(sentinelRestTemplate.fallbackClass(), + sentinelRestTemplate.fallback(), beanName, + SentinelConstants.FALLBACK_TYPE); + } + + private void checkBlock4RestTemplate(Class blockClass, String blockMethod, + String beanName, String type) { + if (blockClass == void.class && StringUtils.isEmpty(blockMethod)) { + return; + } + if (blockClass != void.class && StringUtils.isEmpty(blockMethod)) { + logger.error( + "{} class attribute exists but {} method attribute is not exists in bean[{}]", + type, type, beanName); + throw new IllegalArgumentException(type + " class attribute exists but " + + type + " method attribute is not exists in bean[" + beanName + "]"); + } + else if (blockClass == void.class && !StringUtils.isEmpty(blockMethod)) { + logger.error( + "{} method attribute exists but {} class attribute is not exists in bean[{}]", + type, type, beanName); + throw new IllegalArgumentException(type + " method attribute exists but " + + type + " class attribute is not exists in bean[" + beanName + "]"); + } + Class[] args = new Class[] { HttpRequest.class, byte[].class, + ClientHttpRequestExecution.class, BlockException.class }; + String argsStr = Arrays.toString( + Arrays.stream(args).map(clazz -> clazz.getSimpleName()).toArray()); + Method foundMethod = ClassUtils.getStaticMethod(blockClass, blockMethod, args); + if (foundMethod == null) { + logger.error( + "{} static method can not be found in bean[{}]. The right method signature is {}#{}{}, please check your class name, method name and arguments", + type, beanName, blockClass.getName(), blockMethod, argsStr); + throw new IllegalArgumentException(type + + " static method can not be found in bean[" + beanName + + "]. The right method signature is " + blockClass.getName() + "#" + + blockMethod + argsStr + + ", please check your class name, method name and arguments"); + } + + if (!ClientHttpResponse.class.isAssignableFrom(foundMethod.getReturnType())) { + logger.error( + "{} method return value in bean[{}] is not ClientHttpResponse: {}#{}{}", + type, beanName, blockClass.getName(), blockMethod, argsStr); + throw new IllegalArgumentException(type + " method return value in bean[" + + beanName + "] is not ClientHttpResponse: " + blockClass.getName() + + "#" + blockMethod + argsStr); + } + if (type.equals(SentinelConstants.BLOCK_TYPE)) { + BlockClassRegistry.updateBlockHandlerFor(blockClass, blockMethod, + foundMethod); + } + else { + BlockClassRegistry.updateFallbackFor(blockClass, blockMethod, foundMethod); + } + } + private boolean checkSentinelProtect(RootBeanDefinition beanDefinition, Class beanType) { return beanType == RestTemplate.class @@ -103,7 +182,7 @@ public class SentinelBeanPostProcessor implements MergedBeanDefinitionPostProces SentinelProtectInterceptor sentinelProtectInterceptor = applicationContext .getBean(interceptorBeanName.toString(), SentinelProtectInterceptor.class); - restTemplate.getInterceptors().add(sentinelProtectInterceptor); + restTemplate.getInterceptors().add(0, sentinelProtectInterceptor); } return bean; } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCircuitBreakerConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCircuitBreakerConfiguration.java new file mode 100644 index 00000000..f8d61034 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelCircuitBreakerConfiguration.java @@ -0,0 +1,12 @@ +package org.springframework.cloud.alibaba.sentinel.custom; + +import org.springframework.context.annotation.Configuration; + +/** + * @author lengleng + *

+ * support @EnableCircuitBreaker ,Do nothing + */ +@Configuration +public class SentinelCircuitBreakerConfiguration { +} 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 f9776d27..6b3e6e8b 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,21 +1,16 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; - +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; 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.boot.context.event.ApplicationStartedEvent; import org.springframework.cloud.alibaba.sentinel.SentinelConstants; import org.springframework.cloud.alibaba.sentinel.SentinelProperties; import org.springframework.cloud.alibaba.sentinel.datasource.SentinelDataSourceConstants; @@ -24,16 +19,17 @@ import org.springframework.cloud.alibaba.sentinel.datasource.config.DataSourcePr import org.springframework.cloud.alibaba.sentinel.datasource.config.NacosDataSourceProperties; import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter; import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter; -import org.springframework.context.event.EventListener; import org.springframework.util.CollectionUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; -import com.alibaba.csp.sentinel.datasource.AbstractDataSource; -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; -import com.alibaba.csp.sentinel.slots.block.AbstractRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; /** * Sentinel {@link ReadableDataSource} Handler Handle the configurations of @@ -44,29 +40,28 @@ import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; * @see JsonConverter * @see XmlConverter */ -public class SentinelDataSourceHandler { +public class SentinelDataSourceHandler implements SmartInitializingSingleton { private static final Logger logger = LoggerFactory .getLogger(SentinelDataSourceHandler.class); private List dataTypeList = Arrays.asList("json", "xml"); - private List dataSourceBeanNameList = Collections - .synchronizedList(new ArrayList<>()); + private final String DATA_TYPE_FIELD = "dataType"; + private final String CUSTOM_DATA_TYPE = "custom"; + private final String CONVERTER_CLASS_FIELD = "converterClass"; - private final String DATATYPE_FIELD = "dataType"; - private final String CUSTOM_DATATYPE = "custom"; - private final String CONVERTERCLASS_FIELD = "converterClass"; + private final DefaultListableBeanFactory beanFactory; + + public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory) { + this.beanFactory = beanFactory; + } @Autowired private SentinelProperties sentinelProperties; - @EventListener(classes = ApplicationStartedEvent.class) - public void buildDataSource(ApplicationStartedEvent event) throws Exception { - - DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) event - .getApplicationContext().getAutowireCapableBeanFactory(); - + @Override + public void afterSingletonsInstantiated() { // commercialization if (!StringUtils.isEmpty(System.getProperties() .getProperty(SentinelDataSourceConstants.NACOS_DATASOURCE_ENDPOINT))) { @@ -101,9 +96,8 @@ public class SentinelDataSourceHandler { AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties .getValidDataSourceProperties(); abstractDataSourceProperties.preCheck(dataSourceName); - registerBean(beanFactory, abstractDataSourceProperties, - dataSourceName + "-sentinel-" + validFields.get(0) - + "-datasource"); + registerBean(abstractDataSourceProperties, dataSourceName + + "-sentinel-" + validFields.get(0) + "-datasource"); } catch (Exception e) { logger.error("[Sentinel Starter] DataSource " + dataSourceName @@ -112,8 +106,7 @@ public class SentinelDataSourceHandler { }); } - private void registerBean(DefaultListableBeanFactory beanFactory, - final AbstractDataSourceProperties dataSourceProperties, + private void registerBean(final AbstractDataSourceProperties dataSourceProperties, String dataSourceName) { Map propertyMap = Arrays @@ -132,8 +125,8 @@ public class SentinelDataSourceHandler { e); } }, HashMap::putAll); - propertyMap.put(CONVERTERCLASS_FIELD, dataSourceProperties.getConverterClass()); - propertyMap.put(DATATYPE_FIELD, dataSourceProperties.getDataType()); + propertyMap.put(CONVERTER_CLASS_FIELD, dataSourceProperties.getConverterClass()); + propertyMap.put(DATA_TYPE_FIELD, dataSourceProperties.getDataType()); BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(dataSourceProperties.getFactoryBeanName()); @@ -141,81 +134,78 @@ public class SentinelDataSourceHandler { propertyMap.forEach((propertyName, propertyValue) -> { Field field = ReflectionUtils.findField(dataSourceProperties.getClass(), propertyName); - if (field != null) { - if (DATATYPE_FIELD.equals(propertyName)) { - String dataType = StringUtils - .trimAllWhitespace(propertyValue.toString()); - if (CUSTOM_DATATYPE.equals(dataType)) { - try { - if (StringUtils - .isEmpty(dataSourceProperties.getConverterClass())) { - throw new RuntimeException( - "[Sentinel Starter] DataSource " + dataSourceName - + "dataType is custom, please set converter-class " - + "property"); - } - // construct custom Converter with 'converterClass' - // configuration and register - String customConvertBeanName = "sentinel-" - + dataSourceProperties.getConverterClass(); - if (!beanFactory.containsBean(customConvertBeanName)) { - beanFactory.registerBeanDefinition(customConvertBeanName, - BeanDefinitionBuilder - .genericBeanDefinition( - Class.forName(dataSourceProperties - .getConverterClass())) - .getBeanDefinition()); - } - builder.addPropertyReference("converter", - customConvertBeanName); - } - catch (ClassNotFoundException e) { - logger.error("[Sentinel Starter] DataSource " + dataSourceName - + " handle " - + dataSourceProperties.getClass().getSimpleName() - + " error, class name: " - + dataSourceProperties.getConverterClass()); - throw new RuntimeException( - "[Sentinel Starter] DataSource " + dataSourceName - + " handle " - + dataSourceProperties.getClass() - .getSimpleName() - + " error, class name: " - + dataSourceProperties.getConverterClass(), - e); - } - } - else { - if (!dataTypeList.contains(StringUtils - .trimAllWhitespace(propertyValue.toString()))) { + if (null == field) { + return; + } + if (DATA_TYPE_FIELD.equals(propertyName)) { + String dataType = StringUtils.trimAllWhitespace(propertyValue.toString()); + if (CUSTOM_DATA_TYPE.equals(dataType)) { + try { + if (StringUtils + .isEmpty(dataSourceProperties.getConverterClass())) { throw new RuntimeException("[Sentinel Starter] DataSource " - + dataSourceName + " dataType: " + propertyValue - + " is not support now. please using these types: " - + dataTypeList.toString()); + + dataSourceName + + "dataType is custom, please set converter-class " + + "property"); } - // converter type now support xml or json. - // The bean name of these converters wrapped by - // 'sentinel-{converterType}-{ruleType}-converter' - builder.addPropertyReference("converter", - "sentinel-" + propertyValue.toString() + "-" - + dataSourceProperties.getRuleType().getName() - + "-converter"); + // construct custom Converter with 'converterClass' + // configuration and register + String customConvertBeanName = "sentinel-" + + dataSourceProperties.getConverterClass(); + if (!this.beanFactory.containsBean(customConvertBeanName)) { + this.beanFactory.registerBeanDefinition(customConvertBeanName, + BeanDefinitionBuilder + .genericBeanDefinition( + Class.forName(dataSourceProperties + .getConverterClass())) + .getBeanDefinition()); + } + builder.addPropertyReference("converter", customConvertBeanName); + } + catch (ClassNotFoundException e) { + logger.error("[Sentinel Starter] DataSource " + dataSourceName + + " handle " + + dataSourceProperties.getClass().getSimpleName() + + " error, class name: " + + dataSourceProperties.getConverterClass()); + throw new RuntimeException("[Sentinel Starter] DataSource " + + dataSourceName + " handle " + + dataSourceProperties.getClass().getSimpleName() + + " error, class name: " + + dataSourceProperties.getConverterClass(), e); } - } - else if (CONVERTERCLASS_FIELD.equals(propertyName)) { - return; } else { - // wired properties - Optional.ofNullable(propertyValue) - .ifPresent(v -> builder.addPropertyValue(propertyName, v)); + if (!dataTypeList.contains( + StringUtils.trimAllWhitespace(propertyValue.toString()))) { + throw new RuntimeException("[Sentinel Starter] DataSource " + + dataSourceName + " dataType: " + propertyValue + + " is not support now. please using these types: " + + dataTypeList.toString()); + } + // converter type now support xml or json. + // The bean name of these converters wrapped by + // 'sentinel-{converterType}-{ruleType}-converter' + builder.addPropertyReference("converter", + "sentinel-" + propertyValue.toString() + "-" + + dataSourceProperties.getRuleType().getName() + + "-converter"); } } + else if (CONVERTER_CLASS_FIELD.equals(propertyName)) { + return; + } + else { + // wired properties + Optional.ofNullable(propertyValue) + .ifPresent(v -> builder.addPropertyValue(propertyName, v)); + } }); - beanFactory.registerBeanDefinition(dataSourceName, builder.getBeanDefinition()); + this.beanFactory.registerBeanDefinition(dataSourceName, + builder.getBeanDefinition()); // init in Spring - AbstractDataSource newDataSource = (AbstractDataSource) beanFactory + AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory .getBean(dataSourceName); logAndCheckRuleType(newDataSource, dataSourceName, @@ -234,7 +224,6 @@ public class SentinelDataSourceHandler { DegradeRuleManager.register2Property(newDataSource.getProperty()); } } - dataSourceBeanNameList.add(dataSourceName); } private void logAndCheckRuleType(AbstractDataSource dataSource, String dataSourceName, @@ -282,9 +271,4 @@ public class SentinelDataSourceHandler { + ruleClass.getSimpleName() + ">. Class: " + ruleConfig.getClass()); } } - - public List getDataSourceBeanNameList() { - return dataSourceBeanNameList; - } - } diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java index a1ab9e22..a6dcb3c0 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelProtectInterceptor.java @@ -16,11 +16,12 @@ package org.springframework.cloud.alibaba.sentinel.custom; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Arrays; - +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; @@ -29,27 +30,23 @@ import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.ClassUtils; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.SphU; -import com.alibaba.csp.sentinel.Tracer; -import com.alibaba.csp.sentinel.context.ContextUtil; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; -import com.alibaba.csp.sentinel.util.StringUtil; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; /** * Interceptor using by SentinelRestTemplate * - * @author fangjian + * @author Jim */ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor { private static final Logger logger = LoggerFactory .getLogger(SentinelProtectInterceptor.class); - private SentinelRestTemplate sentinelRestTemplate; + private final SentinelRestTemplate sentinelRestTemplate; public SentinelProtectInterceptor(SentinelRestTemplate sentinelRestTemplate) { this.sentinelRestTemplate = sentinelRestTemplate; @@ -82,18 +79,7 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor throw new IllegalStateException(e); } else { - try { - return handleBlockException(request, body, execution, - (BlockException) e); - } - catch (Exception ex) { - if (ex instanceof IllegalStateException) { - throw (IllegalStateException) ex; - } - throw new IllegalStateException( - "sentinel handle BlockException error: " + ex.getMessage(), - ex); - } + return handleBlockException(request, body, execution, (BlockException) e); } } finally { @@ -109,84 +95,49 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor } private ClientHttpResponse handleBlockException(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution, BlockException ex) throws Exception { + ClientHttpRequestExecution execution, BlockException ex) { Object[] args = new Object[] { request, body, execution, ex }; // handle degrade if (isDegradeFailure(ex)) { - Method method = extractFallbackMethod(sentinelRestTemplate.fallback(), + Method fallbackMethod = extractFallbackMethod(sentinelRestTemplate.fallback(), sentinelRestTemplate.fallbackClass()); - if (method != null) { - return (ClientHttpResponse) method.invoke(null, args); + if (fallbackMethod != null) { + return methodInvoke(fallbackMethod, args); } else { return new SentinelClientHttpResponse(); } } - // handle block + // handle flow Method blockHandler = extractBlockHandlerMethod( sentinelRestTemplate.blockHandler(), sentinelRestTemplate.blockHandlerClass()); if (blockHandler != null) { - return (ClientHttpResponse) blockHandler.invoke(null, args); + return methodInvoke(blockHandler, args); } else { return new SentinelClientHttpResponse(); } } + private ClientHttpResponse methodInvoke(Method method, Object... args) { + try { + return (ClientHttpResponse) method.invoke(null, args); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + private Method extractFallbackMethod(String fallback, Class fallbackClass) { - if (StringUtil.isBlank(fallback) || fallbackClass == void.class) { - return null; - } - Method cachedMethod = BlockClassRegistry.lookupFallback(fallbackClass, fallback); - Class[] args = new Class[] { HttpRequest.class, byte[].class, - ClientHttpRequestExecution.class, BlockException.class }; - if (cachedMethod == null) { - cachedMethod = ClassUtils.getStaticMethod(fallbackClass, fallback, args); - if (cachedMethod != null) { - if (!ClientHttpResponse.class - .isAssignableFrom(cachedMethod.getReturnType())) { - throw new IllegalStateException(String.format( - "the return type of method [%s] in class [%s] is not ClientHttpResponse in degrade", - cachedMethod.getName(), fallbackClass.getCanonicalName())); - } - BlockClassRegistry.updateFallbackFor(fallbackClass, fallback, - cachedMethod); - } - else { - throw new IllegalStateException(String.format( - "Cannot find method [%s] in class [%s] with parameters %s in degrade", - fallback, fallbackClass.getCanonicalName(), Arrays.asList(args))); - } - } - return cachedMethod; + return BlockClassRegistry.lookupFallback(fallbackClass, fallback); } private Method extractBlockHandlerMethod(String block, Class blockClass) { - if (StringUtil.isBlank(block) || blockClass == void.class) { - return null; - } - Method cachedMethod = BlockClassRegistry.lookupBlockHandler(blockClass, block); - Class[] args = new Class[] { HttpRequest.class, byte[].class, - ClientHttpRequestExecution.class, BlockException.class }; - if (cachedMethod == null) { - cachedMethod = ClassUtils.getStaticMethod(blockClass, block, args); - if (cachedMethod != null) { - if (!ClientHttpResponse.class - .isAssignableFrom(cachedMethod.getReturnType())) { - throw new IllegalStateException(String.format( - "the return type of method [%s] in class [%s] is not ClientHttpResponse in flow control", - cachedMethod.getName(), blockClass.getCanonicalName())); - } - BlockClassRegistry.updateBlockHandlerFor(blockClass, block, cachedMethod); - } - else { - throw new IllegalStateException(String.format( - "Cannot find method [%s] in class [%s] with parameters %s in flow control", - block, blockClass.getCanonicalName(), Arrays.asList(args))); - } - } - return cachedMethod; + return BlockClassRegistry.lookupBlockHandler(blockClass, block); } private boolean isDegradeFailure(BlockException ex) { diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java index 03b6c0f8..f42898bc 100644 --- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java +++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpoint.java @@ -16,26 +16,21 @@ package org.springframework.cloud.alibaba.sentinel.endpoint; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; +import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.LogBase; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; +import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.cloud.alibaba.sentinel.SentinelProperties; -import org.springframework.cloud.alibaba.sentinel.custom.SentinelDataSourceHandler; -import org.springframework.context.ApplicationContext; -import com.alibaba.csp.sentinel.datasource.ReadableDataSource; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; -import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; -import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; -import com.alibaba.csp.sentinel.slots.system.SystemRule; -import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; +import java.util.HashMap; +import java.util.Map; /** * Endpoint for Sentinel, contains ans properties and rules @@ -44,43 +39,39 @@ import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; @Endpoint(id = "sentinel") public class SentinelEndpoint { - @Autowired - private SentinelProperties sentinelProperties; + private final SentinelProperties sentinelProperties; - @Autowired - private SentinelDataSourceHandler dataSourceHandler; - - @Autowired - private ApplicationContext applicationContext; + public SentinelEndpoint(SentinelProperties sentinelProperties) { + this.sentinelProperties = sentinelProperties; + } @ReadOperation public Map invoke() { final Map result = new HashMap<>(); + if (sentinelProperties.isEnabled()) { - List flowRules = FlowRuleManager.getRules(); - List degradeRules = DegradeRuleManager.getRules(); - List systemRules = SystemRuleManager.getRules(); - List paramFlowRules = ParamFlowRuleManager.getRules(); - result.put("properties", sentinelProperties); - result.put("FlowRules", flowRules); - result.put("DegradeRules", degradeRules); - result.put("SystemRules", systemRules); - result.put("ParamFlowRule", paramFlowRules); - result.put("datasources", new HashMap()); - dataSourceHandler.getDataSourceBeanNameList().forEach(dataSourceBeanName -> { - ReadableDataSource dataSource = applicationContext.getBean(dataSourceBeanName, - ReadableDataSource.class); - try { - ((HashMap) result.get("datasources")).put(dataSourceBeanName, - dataSource.loadConfig()); - } - catch (Exception e) { - ((HashMap) result.get("datasources")).put(dataSourceBeanName, - "load error: " + e.getMessage()); - } - - }); + result.put("logDir", LogBase.getLogBaseDir()); + result.put("logUsePid", LogBase.isLogNameUsePid()); + result.put("blockPage", WebServletConfig.getBlockPage()); + result.put("metricsFileSize", SentinelConfig.singleMetricFileSize()); + result.put("metricsFileCharset", SentinelConfig.charset()); + result.put("totalMetricsFileCount", SentinelConfig.totalMetricFileCount()); + result.put("consoleServer", TransportConfig.getConsoleServer()); + result.put("clientIp", TransportConfig.getHeartbeatClientIp()); + result.put("heartbeatIntervalMs", TransportConfig.getHeartbeatIntervalMs()); + result.put("clientPort", TransportConfig.getPort()); + result.put("coldFactor", sentinelProperties.getFlow().getColdFactor()); + result.put("filter", sentinelProperties.getFilter()); + result.put("datasource", sentinelProperties.getDatasource()); + final Map rules = new HashMap<>(); + result.put("rules", rules); + rules.put("flowRules", FlowRuleManager.getRules()); + rules.put("degradeRules", DegradeRuleManager.getRules()); + rules.put("systemRules", SystemRuleManager.getRules()); + rules.put("authorityRule", AuthorityRuleManager.getRules()); + rules.put("paramFlowRule", ParamFlowRuleManager.getRules()); + } return result; } 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 098822a2..7c9b5d1c 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 @@ -34,8 +34,8 @@ public class SentinelEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean @ConditionalOnEnabledEndpoint - public SentinelEndpoint sentinelEndPoint() { - return new SentinelEndpoint(); + public SentinelEndpoint sentinelEndPoint(SentinelProperties sentinelProperties) { + return new SentinelEndpoint(sentinelProperties); } } 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 d05b0863..b445fc6a 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 @@ -18,6 +18,11 @@ "defaultValue": "8719", "description": "sentinel api port." }, + { + "name": "spring.cloud.sentinel.transport.clientIp", + "type": "java.lang.String", + "description": "sentinel client ip connect to dashboard." + }, { "name": "spring.cloud.sentinel.transport.dashboard", "type": "java.lang.String", 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 702bb5bb..d964e3b0 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 @@ -3,3 +3,6 @@ org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\ org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration + +org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\ +org.springframework.cloud.alibaba.sentinel.custom.SentinelCircuitBreakerConfiguration diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java index 1ba2c170..138c6502 100644 --- a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelAutoConfigurationTests.java @@ -16,76 +16,267 @@ package org.springframework.cloud.alibaba.sentinel; -import static org.assertj.core.api.Assertions.assertThat; - +import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.log.LogBase; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.transport.config.TransportConfig; +import org.junit.Before; import org.junit.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; -import org.springframework.cloud.alibaba.sentinel.custom.SentinelProtectInterceptor; +import org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpoint; +import org.springframework.cloud.alibaba.sentinel.rest.SentinelClientHttpResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; -import com.alibaba.csp.sentinel.slots.block.BlockException; +import java.util.Arrays; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** - * @author fangjian + * @author Jim * @author jiashuai.xie */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { + SentinelAutoConfigurationTests.TestConfig.class }, properties = { + "spring.cloud.sentinel.filter.order=123", + "spring.cloud.sentinel.filter.urlPatterns=/*,/test", + "spring.cloud.sentinel.metric.fileSingleSize=9999", + "spring.cloud.sentinel.metric.fileTotalCount=100", + "spring.cloud.sentinel.servlet.blockPage=/error", + "spring.cloud.sentinel.flow.coldFactor=3", + "spring.cloud.sentinel.eager=true", + "spring.cloud.sentinel.log.switchPid=true", + "spring.cloud.sentinel.transport.dashboard=http://localhost:8080", + "spring.cloud.sentinel.transport.port=9999", + "spring.cloud.sentinel.transport.clientIp=1.1.1.1", + "spring.cloud.sentinel.transport.heartbeatIntervalMs=20000" }, webEnvironment = RANDOM_PORT) public class SentinelAutoConfigurationTests { - private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SentinelAutoConfiguration.class, - SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class)) - .withPropertyValues("spring.cloud.sentinel.transport.port=8888") - .withPropertyValues("spring.cloud.sentinel.filter.order=123") - .withPropertyValues("spring.cloud.sentinel.filter.urlPatterns=/*,/test"); + @Autowired + private SentinelProperties sentinelProperties; + + @Autowired + private FilterRegistrationBean filterRegistrationBean; + + @Autowired + private SentinelBeanPostProcessor sentinelBeanPostProcessor; + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private RestTemplate restTemplateWithBlockClass; + + @Autowired + private RestTemplate restTemplateWithoutBlockClass; + + @Autowired + private RestTemplate restTemplateWithFallbackClass; + + @LocalServerPort + private int port; + + private String url = "http://localhost:" + port; + + @Before + public void setUp() { + FlowRule rule = new FlowRule(); + rule.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setCount(0); + rule.setResource(url); + rule.setLimitApp("default"); + rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); + rule.setStrategy(RuleConstant.STRATEGY_DIRECT); + FlowRuleManager.loadRules(Arrays.asList(rule)); + + DegradeRule degradeRule = new DegradeRule(); + degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); + degradeRule.setResource(url + "/test"); + degradeRule.setCount(0); + degradeRule.setTimeWindow(60); + DegradeRuleManager.loadRules(Arrays.asList(degradeRule)); + } + + @Test + public void contextLoads() throws Exception { + assertNotNull("FilterRegistrationBean was not created", filterRegistrationBean); + assertNotNull("SentinelProperties was not created", sentinelProperties); + assertNotNull("SentinelBeanPostProcessor was not created", + sentinelBeanPostProcessor); + + checkSentinelLog(); + checkSentinelEager(); + checkSentinelTransport(); + checkSentinelColdFactor(); + checkSentinelMetric(); + checkSentinelFilter(); + checkEndpoint(); + } + + private void checkEndpoint() { + SentinelEndpoint sentinelEndpoint = new SentinelEndpoint(sentinelProperties); + Map map = sentinelEndpoint.invoke(); + assertEquals("Endpoint Sentinel log pid was wrong", true, map.get("logUsePid")); + assertEquals("Endpoint Sentinel transport console server was wrong", + "http://localhost:8080", map.get("consoleServer")); + assertEquals("Endpoint Sentinel transport port was wrong", "9999", + map.get("clientPort")); + assertEquals("Endpoint Sentinel transport heartbeatIntervalMs was wrong", 20000l, + map.get("heartbeatIntervalMs")); + assertEquals("Endpoint Sentinel transport clientIp was wrong", "1.1.1.1", + map.get("clientIp")); + assertEquals("Endpoint Sentinel metric file size was wrong", 9999l, + map.get("metricsFileSize")); + assertEquals("Endpoint Sentinel metric file count was wrong", 100, + map.get("totalMetricsFileCount")); + assertEquals("Endpoint Sentinel metric file charset was wrong", "UTF-8", + map.get("metricsFileCharset")); + assertEquals("Endpoint Sentinel block page was wrong", "/error", + map.get("blockPage")); + } + + private void checkSentinelFilter() { + assertEquals("SentinelProperties filter order was wrong", 123, + sentinelProperties.getFilter().getOrder()); + assertEquals("SentinelProperties filter url pattern size was wrong", 2, + sentinelProperties.getFilter().getUrlPatterns().size()); + assertEquals("SentinelProperties filter url pattern item was wrong", "/*", + sentinelProperties.getFilter().getUrlPatterns().get(0)); + assertEquals("SentinelProperties filter url pattern item was wrong", "/test", + sentinelProperties.getFilter().getUrlPatterns().get(1)); + } + + private void checkSentinelMetric() { + assertEquals("SentinelProperties metric charset was wrong", "UTF-8", + sentinelProperties.getMetric().getCharset()); + assertEquals("SentinelProperties metric file single size was wrong", "9999", + sentinelProperties.getMetric().getFileSingleSize()); + assertEquals("SentinelProperties metric file total count was wrong", "100", + sentinelProperties.getMetric().getFileTotalCount()); + } + + private void checkSentinelColdFactor() { + assertEquals("SentinelProperties coldFactor was wrong", "3", + sentinelProperties.getFlow().getColdFactor()); + } + + private void checkSentinelTransport() { + assertEquals("SentinelProperties transport port was wrong", "9999", + sentinelProperties.getTransport().getPort()); + assertEquals("SentinelProperties transport dashboard was wrong", + "http://localhost:8080", + sentinelProperties.getTransport().getDashboard()); + assertEquals("SentinelProperties transport clientIp was wrong", "1.1.1.1", + sentinelProperties.getTransport().getClientIp()); + assertEquals("SentinelProperties transport heartbeatIntervalMs was wrong", + "20000", sentinelProperties.getTransport().getHeartbeatIntervalMs()); + } + + private void checkSentinelEager() { + assertEquals("SentinelProperties eager was wrong", true, + sentinelProperties.isEager()); + } + + private void checkSentinelLog() { + assertEquals("SentinelProperties log file pid was wrong", true, + sentinelProperties.getLog().isSwitchPid()); + } @Test public void testFilter() { - this.contextRunner.run(context -> { - assertThat(context.getBean("servletRequestListener") - .getClass() == FilterRegistrationBean.class).isTrue(); + assertEquals("Sentinel Filter order was wrong", filterRegistrationBean.getOrder(), + 123); + assertEquals("Sentinel Filter url-pattern was wrong", + filterRegistrationBean.getUrlPatterns().size(), 2); + } + + @Test + public void testSentinelSystemProperties() { + assertEquals("Sentinel log pid was wrong", true, LogBase.isLogNameUsePid()); + assertEquals("Sentinel transport console server was wrong", + "http://localhost:8080", TransportConfig.getConsoleServer()); + assertEquals("Sentinel transport port was wrong", "9999", + TransportConfig.getPort()); + assertEquals("Sentinel transport heartbeatIntervalMs was wrong", 20000l, + TransportConfig.getHeartbeatIntervalMs().longValue()); + assertEquals("Sentinel transport clientIp was wrong", "1.1.1.1", + TransportConfig.getHeartbeatClientIp()); + assertEquals("Sentinel metric file size was wrong", 9999, + SentinelConfig.singleMetricFileSize()); + assertEquals("Sentinel metric file count was wrong", 100, + SentinelConfig.totalMetricFileCount()); + assertEquals("Sentinel metric file charset was wrong", "UTF-8", + SentinelConfig.charset()); + assertEquals("Sentinel block page was wrong", "/error", + WebServletConfig.getBlockPage()); + } + + @Test + public void testFlowRestTemplate() { + assertEquals("RestTemplate interceptors size was wrong", 2, + restTemplate.getInterceptors().size()); + assertEquals("RestTemplateWithBlockClass interceptors size was wrong", 1, + restTemplateWithBlockClass.getInterceptors().size()); + ResponseEntity responseEntityBlock = restTemplateWithBlockClass.getForEntity(url, + String.class); + assertEquals("RestTemplateWithBlockClass Sentinel Block Message was wrong", + "Oops", responseEntityBlock.getBody()); + assertEquals( + "RestTemplateWithBlockClass Sentinel Block Http Status Code was wrong", + HttpStatus.OK, responseEntityBlock.getStatusCode()); + ResponseEntity responseEntityRaw = restTemplate.getForEntity(url, String.class); + assertEquals("RestTemplate Sentinel Block Message was wrong", + "RestTemplate request block by sentinel", responseEntityRaw.getBody()); + assertEquals("RestTemplate Sentinel Block Http Status Code was wrong", + HttpStatus.OK, responseEntityRaw.getStatusCode()); + } + + @Test + public void testNormalRestTemplate() { + assertEquals("RestTemplateWithoutBlockClass interceptors size was wrong", 0, + restTemplateWithoutBlockClass.getInterceptors().size()); + assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> { + restTemplateWithoutBlockClass.getForEntity(url, String.class); }); } @Test - public void testBeanPostProcessor() { - this.contextRunner.run(context -> { - assertThat(context.getBean("sentinelBeanPostProcessor") - .getClass() == SentinelBeanPostProcessor.class).isTrue(); - }); - } - - @Test - public void testProperties() { - this.contextRunner.run(context -> { - SentinelProperties sentinelProperties = context - .getBean(SentinelProperties.class); - assertThat(sentinelProperties.getTransport().getPort()).isEqualTo("8888"); - assertThat(sentinelProperties.getFilter().getUrlPatterns().size()) - .isEqualTo(2); - assertThat(sentinelProperties.getFilter().getUrlPatterns().get(0)) - .isEqualTo("/*"); - assertThat(sentinelProperties.getFilter().getUrlPatterns().get(1)) - .isEqualTo("/test"); - }); - } - - @Test - public void testRestTemplate() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(RestTemplate.class).size()).isEqualTo(2); - RestTemplate restTemplate = context.getBean("restTemplateWithBlockClass", - RestTemplate.class); - assertThat(restTemplate.getInterceptors().size()).isEqualTo(1); - assertThat(restTemplate.getInterceptors().get(0).getClass()) - .isEqualTo(SentinelProtectInterceptor.class); - }); + public void testFallbackRestTemplate() { + ResponseEntity responseEntity = restTemplateWithFallbackClass + .getForEntity(url + "/test", String.class); + assertEquals("RestTemplateWithFallbackClass Sentinel Message was wrong", + "Oops fallback", responseEntity.getBody()); + assertEquals("RestTemplateWithFallbackClass Sentinel Http Status Code was wrong", + HttpStatus.OK, responseEntity.getStatusCode()); } @Configuration @@ -94,7 +285,9 @@ public class SentinelAutoConfigurationTests { @Bean @SentinelRestTemplate RestTemplate restTemplate() { - return new RestTemplate(); + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getInterceptors().add(mock(ClientHttpRequestInterceptor.class)); + return restTemplate; } @Bean @@ -103,12 +296,38 @@ public class SentinelAutoConfigurationTests { return new RestTemplate(); } + @Bean + @SentinelRestTemplate(fallbackClass = ExceptionUtil.class, fallback = "fallbackException") + RestTemplate restTemplateWithFallbackClass() { + return new RestTemplate(); + } + + @Bean + RestTemplate restTemplateWithoutBlockClass() { + return new RestTemplate(); + } + } - static class ExceptionUtil { - public static void handleException(BlockException ex) { + public static class ExceptionUtil { + public static SentinelClientHttpResponse handleException(HttpRequest request, + byte[] body, ClientHttpRequestExecution execution, BlockException ex) { System.out.println("Oops: " + ex.getClass().getCanonicalName()); + return new SentinelClientHttpResponse("Oops"); + } + + public static SentinelClientHttpResponse fallbackException(HttpRequest request, + byte[] body, ClientHttpRequestExecution execution, BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + return new SentinelClientHttpResponse("Oops fallback"); } } + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({ SentinelAutoConfiguration.class, + SentinelWebAutoConfiguration.class, SentinelTestConfiguration.class }) + public static class TestConfig { + } + } diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelBeanAutowiredTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelBeanAutowiredTests.java new file mode 100644 index 00000000..ed24c33f --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelBeanAutowiredTests.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; + +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.servlet.util.FilterUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Jim + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { SentinelBeanAutowiredTests.TestConfig.class }, properties = { + "spring.cloud.sentinel.filter.order=111" }) +public class SentinelBeanAutowiredTests { + + @Autowired + private UrlCleaner urlCleaner; + + @Autowired + private UrlBlockHandler urlBlockHandler; + + @Autowired + private RequestOriginParser requestOriginParser; + + @Autowired + private SentinelProperties sentinelProperties; + + @Test + public void contextLoads() throws Exception { + assertNotNull("UrlCleaner was not created", urlCleaner); + assertNotNull("UrlBlockHandler was not created", urlBlockHandler); + assertNotNull("RequestOriginParser was not created", requestOriginParser); + assertNotNull("SentinelProperties was not created", sentinelProperties); + + checkUrlPattern(); + } + + private void checkUrlPattern() { + assertEquals("SentinelProperties filter order was wrong", 111, + sentinelProperties.getFilter().getOrder()); + assertEquals("SentinelProperties filter url pattern size was wrong", 1, + sentinelProperties.getFilter().getUrlPatterns().size()); + assertEquals("SentinelProperties filter url pattern was wrong", "/*", + sentinelProperties.getFilter().getUrlPatterns().get(0)); + } + + @Test + public void testBeanAutowired() { + assertEquals("UrlCleaner was not autowired", urlCleaner, + WebCallbackManager.getUrlCleaner()); + assertEquals("UrlBlockHandler was not autowired", urlBlockHandler, + WebCallbackManager.getUrlBlockHandler()); + assertEquals("RequestOriginParser was not autowired", requestOriginParser, + WebCallbackManager.getRequestOriginParser()); + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({ SentinelAutoConfiguration.class, + SentinelWebAutoConfiguration.class }) + public static class TestConfig { + + @Bean + public UrlCleaner urlCleaner() { + return new UrlCleaner() { + @Override + public String clean(String s) { + return s; + } + }; + } + + @Bean + public RequestOriginParser requestOriginParser() { + return new RequestOriginParser() { + @Override + public String parseOrigin(HttpServletRequest httpServletRequest) { + return httpServletRequest.getRemoteAddr(); + } + }; + } + + @Bean + public UrlBlockHandler urlBlockHandler() { + return new UrlBlockHandler() { + @Override + public void blocked(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, BlockException e) + throws IOException { + FilterUtil.blockRequest(httpServletRequest, httpServletResponse); + } + }; + } + + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelDataSourceTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelDataSourceTests.java new file mode 100644 index 00000000..1c3be389 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelDataSourceTests.java @@ -0,0 +1,106 @@ +/* + * 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration; +import org.springframework.cloud.alibaba.sentinel.datasource.RuleType; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Jim + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { SentinelDataSourceTests.TestConfig.class }, properties = { + "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", + + "spring.cloud.sentinel.datasource.ds2.file.file=classpath: degraderule.json", + "spring.cloud.sentinel.datasource.ds2.file.data-type=json", + "spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade", + + "spring.cloud.sentinel.datasource.ds3.file.file=classpath: authority.json", + "spring.cloud.sentinel.datasource.ds3.file.rule-type=authority", + + "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.data-type=custom", + "spring.cloud.sentinel.datasource.ds5.file.converter-class=org.springframework.cloud.alibaba.sentinel.TestConverter", + "spring.cloud.sentinel.datasource.ds5.file.rule-type=param-flow" }) +public class SentinelDataSourceTests { + + @Autowired + private SentinelProperties sentinelProperties; + + @Test + public void contextLoads() throws Exception { + assertNotNull("SentinelProperties was not created", sentinelProperties); + + checkUrlPattern(); + } + + private void checkUrlPattern() { + assertEquals("SentinelProperties filter order was wrong", Integer.MIN_VALUE, + sentinelProperties.getFilter().getOrder()); + assertEquals("SentinelProperties filter url pattern size was wrong", 1, + sentinelProperties.getFilter().getUrlPatterns().size()); + assertEquals("SentinelProperties filter url pattern was wrong", "/*", + sentinelProperties.getFilter().getUrlPatterns().get(0)); + } + + @Test + public void testDataSource() { + assertEquals("DataSource size was wrong", 5, + sentinelProperties.getDatasource().size()); + assertNull("DataSource ds1 apollo is not null", + sentinelProperties.getDatasource().get("ds1").getApollo()); + assertNull("DataSource ds1 nacos is not null", + sentinelProperties.getDatasource().get("ds1").getNacos()); + assertNull("DataSource ds1 zk is not null", + sentinelProperties.getDatasource().get("ds1").getZk()); + assertNotNull("DataSource ds1 file is null", + sentinelProperties.getDatasource().get("ds1").getFile()); + + assertEquals("DataSource ds1 file dataType was wrong", "json", + sentinelProperties.getDatasource().get("ds1").getFile().getDataType()); + assertEquals("DataSource ds1 file ruleType was wrong", RuleType.FLOW, + sentinelProperties.getDatasource().get("ds1").getFile().getRuleType()); + + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({ SentinelAutoConfiguration.class, + SentinelWebAutoConfiguration.class }) + public static class TestConfig { + + } + +} 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 new file mode 100644 index 00000000..b7d03670 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java @@ -0,0 +1,177 @@ +/* + * 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 com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Jim + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = { SentinelFeignTests.TestConfig.class }, properties = { + "feign.sentinel.enabled=true" }) +public class SentinelFeignTests { + + @Autowired + private EchoService echoService; + + @Autowired + private FooService fooService; + + @Autowired + private BarService barService; + + @Before + public void setUp() { + FlowRule rule1 = new FlowRule(); + rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule1.setCount(0); + rule1.setResource("GET:http://test-service/echo/{str}"); + rule1.setLimitApp("default"); + rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); + rule1.setStrategy(RuleConstant.STRATEGY_DIRECT); + FlowRule rule2 = new FlowRule(); + rule2.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule2.setCount(0); + rule2.setResource("GET:http://foo-service/echo/{str}"); + rule2.setLimitApp("default"); + rule2.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); + rule2.setStrategy(RuleConstant.STRATEGY_DIRECT); + FlowRule rule3 = new FlowRule(); + rule3.setGrade(RuleConstant.FLOW_GRADE_QPS); + rule3.setCount(0); + rule3.setResource("GET:http://bar-service/bar"); + rule3.setLimitApp("default"); + rule3.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); + rule3.setStrategy(RuleConstant.STRATEGY_DIRECT); + FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3)); + } + + @Test + public void contextLoads() throws Exception { + assertNotNull("EchoService was not created", echoService); + assertNotNull("FooService was not created", fooService); + } + + @Test + public void testFeignClient() { + assertEquals("Sentinel Feign Client fallback success", "echo fallback", + echoService.echo("test")); + assertEquals("Sentinel Feign Client fallbackFactory success", "foo fallback", + fooService.echo("test")); + assertThatExceptionOfType(Exception.class).isThrownBy(() -> { + barService.bar(); + }); + + assertNotEquals("ToString method invoke was not in SentinelInvocationHandler", + echoService.toString(), fooService.toString()); + assertNotEquals("HashCode method invoke was not in SentinelInvocationHandler", + echoService.hashCode(), fooService.hashCode()); + assertFalse("Equals method invoke was not in SentinelInvocationHandler", + echoService.equals(fooService)); + } + + @Configuration + @EnableAutoConfiguration + @ImportAutoConfiguration({ SentinelFeignAutoConfiguration.class }) + @EnableFeignClients + public static class TestConfig { + + @Bean + public EchoServiceFallback echoServiceFallback() { + return new EchoServiceFallback(); + } + + @Bean + public CustomFallbackFactory customFallbackFactory() { + return new CustomFallbackFactory(); + } + + } + + @FeignClient(value = "test-service", fallback = EchoServiceFallback.class) + public interface EchoService { + @RequestMapping(path = "echo/{str}") + String echo(@RequestParam("str") String param); + } + + @FeignClient(value = "foo-service", fallbackFactory = CustomFallbackFactory.class) + public interface FooService { + @RequestMapping(path = "echo/{str}") + String echo(@RequestParam("str") String param); + } + + @FeignClient(value = "bar-service") + public interface BarService { + @RequestMapping(path = "bar") + String bar(); + } + + public static class EchoServiceFallback implements EchoService { + + @Override + public String echo(@RequestParam("str") String param) { + return "echo fallback"; + } + + } + + public static class FooServiceFallback implements FooService { + + @Override + public String echo(@RequestParam("str") String param) { + return "foo fallback"; + } + } + + public static class CustomFallbackFactory + implements feign.hystrix.FallbackFactory { + + private FooService fooService = new FooServiceFallback(); + + @Override + public FooService create(Throwable throwable) { + return fooService; + } + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelRestTemplateTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelRestTemplateTests.java new file mode 100644 index 00000000..f95c4616 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelRestTemplateTests.java @@ -0,0 +1,247 @@ +/* + * 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 com.alibaba.csp.sentinel.slots.block.BlockException; +import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; +import org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor; +import org.springframework.cloud.alibaba.sentinel.rest.SentinelClientHttpResponse; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertEquals; + +/** + * @author Jim + */ +public class SentinelRestTemplateTests { + + @Test(expected = BeanCreationException.class) + public void testFbkMethod() { + new AnnotationConfigApplicationContext(TestConfig1.class); + } + + @Test(expected = BeanCreationException.class) + public void testFbkClass() { + new AnnotationConfigApplicationContext(TestConfig2.class); + } + + @Test(expected = BeanCreationException.class) + public void testblkMethod() { + new AnnotationConfigApplicationContext(TestConfig3.class); + } + + @Test(expected = BeanCreationException.class) + public void testblkClass() { + new AnnotationConfigApplicationContext(TestConfig4.class); + } + + @Test + public void testNormal() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + TestConfig5.class); + assertEquals("RestTemplate size was wrong", 1, + context.getBeansOfType(RestTemplate.class).size()); + } + + @Test(expected = BeanCreationException.class) + public void testBlkMethodExists() { + new AnnotationConfigApplicationContext(TestConfig6.class); + } + + @Test(expected = BeanCreationException.class) + public void testFbkMethodExists() { + new AnnotationConfigApplicationContext(TestConfig7.class); + } + + @Test(expected = BeanCreationException.class) + public void testBlkReturnValue() { + new AnnotationConfigApplicationContext(TestConfig8.class); + } + + @Test(expected = BeanCreationException.class) + public void testFbkReturnValue() { + new AnnotationConfigApplicationContext(TestConfig9.class); + } + + @Configuration + public static class TestConfig1 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(fallback = "fbk") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig2 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(fallbackClass = ExceptionUtil.class) + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig3 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(blockHandler = "blk") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig4 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(blockHandlerClass = ExceptionUtil.class) + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig5 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException", fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig6 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException1") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig7 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException1") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig8 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(blockHandlerClass = SentinelRestTemplateTests.ExceptionUtil.class, blockHandler = "handleException2") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + @Configuration + public static class TestConfig9 { + @Bean + SentinelBeanPostProcessor sentinelBeanPostProcessor( + ApplicationContext applicationContext) { + return new SentinelBeanPostProcessor(applicationContext); + } + + @Bean + @SentinelRestTemplate(fallbackClass = SentinelRestTemplateTests.ExceptionUtil.class, fallback = "fallbackException2") + RestTemplate restTemplate() { + return new RestTemplate(); + } + } + + public static class ExceptionUtil { + public static SentinelClientHttpResponse handleException(HttpRequest request, + byte[] body, ClientHttpRequestExecution execution, BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + return new SentinelClientHttpResponse("Oops"); + } + + public static void handleException2(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution, BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + } + + public static SentinelClientHttpResponse fallbackException(HttpRequest request, + byte[] body, ClientHttpRequestExecution execution, BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + return new SentinelClientHttpResponse("Oops fallback"); + } + + public static void fallbackException2(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution, BlockException ex) { + System.out.println("Oops: " + ex.getClass().getCanonicalName()); + } + } + +} diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/TestConverter.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/TestConverter.java new file mode 100644 index 00000000..43012442 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/TestConverter.java @@ -0,0 +1,44 @@ +/* + * 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 com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.List; + +/** + * @author Jim + */ +public class TestConverter implements Converter> { + private ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public List convert(String s) { + try { + return objectMapper.readValue(s, new TypeReference>() { + }); + } + catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/spring-cloud-alibaba-sentinel/src/test/resources/authority.json b/spring-cloud-alibaba-sentinel/src/test/resources/authority.json new file mode 100644 index 00000000..3fb4b249 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/resources/authority.json @@ -0,0 +1,17 @@ +[ + { + "resource": "good", + "limitApp": "abc", + "strategy": 0 + }, + { + "resource": "bad", + "limitApp": "bcd", + "strategy": 1 + }, + { + "resource": "terrible", + "limitApp": "aaa", + "strategy": 1 + } +] diff --git a/spring-cloud-alibaba-sentinel/src/test/resources/degraderule.json b/spring-cloud-alibaba-sentinel/src/test/resources/degraderule.json new file mode 100644 index 00000000..5977c5fc --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/resources/degraderule.json @@ -0,0 +1,16 @@ +[ + { + "resource": "abc0", + "count": 20.0, + "grade": 0, + "passCount": 0, + "timeWindow": 10 + }, + { + "resource": "abc1", + "count": 15.0, + "grade": 0, + "passCount": 0, + "timeWindow": 10 + } +] \ No newline at end of file diff --git a/spring-cloud-alibaba-sentinel/src/test/resources/flowrule.json b/spring-cloud-alibaba-sentinel/src/test/resources/flowrule.json new file mode 100644 index 00000000..d798f805 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/resources/flowrule.json @@ -0,0 +1,26 @@ +[ + { + "resource": "resource", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + }, + { + "resource": "p", + "controlBehavior": 0, + "count": 1, + "grade": 1, + "limitApp": "default", + "strategy": 0 + }, + { + "resource": "http://www.taobao.com", + "controlBehavior": 0, + "count": 0, + "grade": 1, + "limitApp": "default", + "strategy": 0 + } +] diff --git a/spring-cloud-alibaba-sentinel/src/test/resources/param-flow.json b/spring-cloud-alibaba-sentinel/src/test/resources/param-flow.json new file mode 100644 index 00000000..72e1c2dc --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/resources/param-flow.json @@ -0,0 +1,16 @@ +[ + { + "resource": "hotResource", + "count": 0, + "grade": 1, + "limitApp": "default", + "paramIdx": 0, + "paramFlowItemList": [ + { + "object": "2", + "classType": "int", + "count": 1 + } + ] + } +] diff --git a/spring-cloud-alibaba-sentinel/src/test/resources/system.json b/spring-cloud-alibaba-sentinel/src/test/resources/system.json new file mode 100644 index 00000000..7aa623a7 --- /dev/null +++ b/spring-cloud-alibaba-sentinel/src/test/resources/system.json @@ -0,0 +1,8 @@ +[ + { + "highestSystemLoad": -1, + "qps": 100, + "avgRt": -1, + "maxThread": 10 + } +] diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsAutoConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsAutoConfiguration.java index a14c6e12..d563632e 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsAutoConfiguration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsAutoConfiguration.java @@ -21,12 +21,16 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alicloud.ans.migrate.MigrateOnConditionMissingClass; import org.springframework.cloud.alicloud.ans.registry.AnsAutoServiceRegistration; import org.springframework.cloud.alicloud.ans.registry.AnsRegistration; import org.springframework.cloud.alicloud.ans.registry.AnsServiceRegistry; +import org.springframework.cloud.alicloud.context.ans.AnsProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** @@ -34,6 +38,7 @@ import org.springframework.context.annotation.Configuration; */ @Configuration @EnableConfigurationProperties +@Conditional(MigrateOnConditionMissingClass.class) @ConditionalOnClass(name = "org.springframework.boot.web.context.WebServerInitializedEvent") @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) @ConditionalOnAnsEnabled @@ -49,8 +54,9 @@ public class AnsAutoConfiguration { @Bean @ConditionalOnBean(AutoServiceRegistrationProperties.class) @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) - public AnsRegistration ansRegistration() { - return new AnsRegistration(); + public AnsRegistration ansRegistration(AnsProperties ansProperties, + ApplicationContext applicationContext) { + return new AnsRegistration(ansProperties, applicationContext); } @Bean @@ -63,4 +69,5 @@ public class AnsAutoConfiguration { return new AnsAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration); } + } diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClient.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClient.java index c95757b2..36c28c9f 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClient.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClient.java @@ -16,13 +16,17 @@ package org.springframework.cloud.alicloud.ans; -import java.util.*; - +import com.alibaba.ans.core.NamingService; +import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; -import com.alibaba.ans.core.NamingService; -import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @author xiaolongzuo @@ -75,8 +79,9 @@ public class AnsDiscoveryClient implements DiscoveryClient { @Override public List getServices() { - + Set publishers = NamingService.getPublishes(); Set doms = NamingService.getDomsSubscribed(); + doms.addAll(publishers); List result = new LinkedList<>(); for (String service : doms) { result.add(service); diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClientAutoConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClientAutoConfiguration.java index 7d286976..c19296f4 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/AnsDiscoveryClientAutoConfiguration.java @@ -19,15 +19,18 @@ package org.springframework.cloud.alicloud.ans; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alicloud.ans.migrate.MigrateOnConditionMissingClass; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @author xiaolongzuo */ @Configuration +@Conditional(MigrateOnConditionMissingClass.class) @ConditionalOnMissingBean(DiscoveryClient.class) @EnableConfigurationProperties @AutoConfigureBefore(SimpleDiscoveryClientAutoConfiguration.class) diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpoint.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpoint.java index a8bc64c7..87cdcd59 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpoint.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpoint.java @@ -16,19 +16,18 @@ package org.springframework.cloud.alicloud.ans.endpoint; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.alibaba.ans.core.NamingService; +import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.cloud.alicloud.context.ans.AnsProperties; -import com.alibaba.ans.core.NamingService; -import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * @author xiaolongzuo @@ -36,7 +35,7 @@ import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; @Endpoint(id = "ans") public class AnsEndpoint { - private static final Logger LOGGER = LoggerFactory.getLogger(AnsEndpoint.class); + private static final Log log = LogFactory.getLog(AnsEndpoint.class); private AnsProperties ansProperties; @@ -50,7 +49,7 @@ public class AnsEndpoint { @ReadOperation public Map invoke() { Map ansEndpoint = new HashMap<>(); - LOGGER.info("ANS endpoint invoke, ansProperties is {}", ansProperties); + log.info("ANS endpoint invoke, ansProperties is " + ansProperties); ansEndpoint.put("ansProperties", ansProperties); Map subscribes = new HashMap<>(); @@ -65,7 +64,7 @@ public class AnsEndpoint { } } ansEndpoint.put("subscribes", subscribes); - LOGGER.info("ANS endpoint invoke, subscribes is {}", subscribes); + log.info("ANS endpoint invoke, subscribes is " + subscribes); return ansEndpoint; } diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpoint.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpoint.java new file mode 100644 index 00000000..16b99d7b --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpoint.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +@Endpoint(id = "migrate") +public class MigrateEndpoint { + + private static final Log log = LogFactory.getLog(MigrateEndpoint.class); + + public MigrateEndpoint() { + } + + /** + * @return ans endpoint + */ + @ReadOperation + public Map> invoke() { + + Map> result = ServerListInvocationHandler + .getServerRegistry(); + + log.info("migrate server list :" + result); + return result; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpointAutoConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpointAutoConfiguration.java new file mode 100644 index 00000000..385d5a5d --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpointAutoConfiguration.java @@ -0,0 +1,18 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; + +@ConditionalOnWebApplication +@ConditionalOnClass(value = Endpoint.class) +@Conditional(MigrateOnConditionClass.class) +public class MigrateEndpointAutoConfiguration { + + @Bean + public MigrateEndpoint ansEndpoint() { + return new MigrateEndpoint(); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnCondition.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnCondition.java new file mode 100644 index 00000000..96050181 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnCondition.java @@ -0,0 +1,49 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.ClassUtils; + +/** + * @author pbting + */ +public abstract class MigrateOnCondition implements Condition, BeanClassLoaderAware { + + final String[] conditionOnClass = new String[] { + "org.springframework.cloud.consul.serviceregistry.ConsulAutoServiceRegistration", + "org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration" }; + + ClassLoader classLoader; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public abstract boolean matches(ConditionContext context, + AnnotatedTypeMetadata metadata); + + boolean isPresent(String className, ClassLoader classLoader) { + if (classLoader == null) { + classLoader = ClassUtils.getDefaultClassLoader(); + } + + try { + forName(className, classLoader); + return true; + } + catch (Throwable var3) { + return false; + } + } + + Class forName(String className, ClassLoader classLoader) + throws ClassNotFoundException { + return classLoader != null ? classLoader.loadClass(className) + : Class.forName(className); + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionClass.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionClass.java new file mode 100644 index 00000000..23c58036 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionClass.java @@ -0,0 +1,22 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * @author pbting + */ +public class MigrateOnConditionClass extends MigrateOnCondition { + + protected static final Log log = LogFactory.getLog(MigrateOnConditionClass.class); + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + boolean result = isPresent(conditionOnClass[0], classLoader) + || isPresent(conditionOnClass[1], classLoader); + log.info("the result of match is :" + result); + return result; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionMissingClass.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionMissingClass.java new file mode 100644 index 00000000..8501ed12 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionMissingClass.java @@ -0,0 +1,23 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * @author pbting + */ +public class MigrateOnConditionMissingClass extends MigrateOnConditionClass { + + protected static final Log log = LogFactory + .getLog(MigrateOnConditionMissingClass.class); + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + boolean result = !super.matches(context, metadata); + log.info("the result of match is :" + result); + return result; + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateProxyManager.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateProxyManager.java new file mode 100644 index 00000000..23427941 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateProxyManager.java @@ -0,0 +1,97 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import org.aopalliance.aop.Advice; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.AfterReturningAdvice; +import org.springframework.aop.framework.ProxyFactory; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author pbting + */ +final class MigrateProxyManager { + + private final static Log log = LogFactory.getLog(MigrateProxyManager.class); + private final static AtomicBoolean IS_PROXY = new AtomicBoolean(true); + + private final static Set SERVICES_ID = new ConcurrentSkipListSet<>(); + + private static Object springProxyFactory(Object target, ClassLoader classLoader, + List adviceList, Class... interfaces) { + final ProxyFactory proxyFactory = new ProxyFactory(interfaces); + proxyFactory.setTarget(target); + adviceList.forEach(advice -> proxyFactory.addAdvice(advice)); + return proxyFactory.getProxy(classLoader); + } + + static Object newServerListProxy(Object bean, ClassLoader classLoader, + IClientConfig clientConfig) { + bean = springProxyFactory(bean, classLoader, + Arrays.asList(new ServerListInvocationHandler(clientConfig)), + new Class[] { ServerList.class }); + log.info("[service id]" + clientConfig.getClientName() + + " new a ServerList proxy instance for spring cloud netflix to spring cloud alibaba "); + collectServiceId(clientConfig.getClientName()); + return bean; + } + + static Object newLoadBalancerProxy(Object bean, ClassLoader classLoader, + final IClientConfig clientConfig) { + + bean = springProxyFactory(bean, classLoader, + Arrays.asList(new AfterReturningAdvice() { + private final IClientConfig iclientConfig = clientConfig; + + @Override + public void afterReturning(Object returnValue, Method method, + Object[] args, Object target) { + String methodName = method.getName(); + if ("chooseServer".equals(methodName)) { + String serviceId = iclientConfig.getClientName(); + Server server = (Server) returnValue; + server = ServerListInvocationHandler + .checkAndGetServiceServer(serviceId, server); + ServerListInvocationHandler.incrementCallService(serviceId, + server); + } + } + }), new Class[] { ILoadBalancer.class }); + log.info("[service id]" + clientConfig.getClientName() + + " new a ILoadBalancer proxy instance for spring cloud netflix to spring cloud alibaba "); + return bean; + } + + static void migrateProxyClose() { + IS_PROXY.set(false); + } + + static void migrateProxyUp() { + IS_PROXY.set(true); + } + + static boolean isMigrateProxy() { + + return IS_PROXY.get(); + } + + static void collectServiceId(String serviceId) { + SERVICES_ID.add(serviceId); + } + + static Set getServicesId() { + + return Collections.unmodifiableSet(SERVICES_ID); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRefreshEventListener.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRefreshEventListener.java new file mode 100644 index 00000000..70d12113 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRefreshEventListener.java @@ -0,0 +1,78 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.loadbalancer.ILoadBalancer; +import org.springframework.cloud.context.named.NamedContextFactory; +import org.springframework.cloud.endpoint.event.RefreshEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author pbting + */ +@Component +public class MigrateRefreshEventListener implements ApplicationListener { + + private final static int CHECK_INTERVAL = 1; + + private final static String MIGRATE_SWITCH = "spring.cloud.alicloud.migrate.ans.switch"; + + private volatile String lastScaMigrateAnsSwitchValue = "true"; + + private Environment environment; + + private NamedContextFactory namedContextFactory; + + public MigrateRefreshEventListener(Environment environment, + NamedContextFactory namedContextFactory) { + this.environment = environment; + this.namedContextFactory = namedContextFactory; + } + + @PostConstruct + public void initTimerCheck() { + Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay( + () -> onApplicationEvent(null), CHECK_INTERVAL, CHECK_INTERVAL, + TimeUnit.SECONDS); + } + + @Override + public void onApplicationEvent(RefreshEvent event) { + String value = environment.getProperty(MIGRATE_SWITCH, "true"); + // check 1: check the value + if (value.equals(lastScaMigrateAnsSwitchValue)) { + return; + } + + updateLastScaMigrateAnsResetValue(value); + + // step 1: migrate up + if ("true".equals(value)) { + MigrateProxyManager.migrateProxyUp(); + serviceIdContextInit(); + return; + } + + // step 2: migrate close + if ("false".equals(value)) { + MigrateProxyManager.migrateProxyClose(); + serviceIdContextInit(); + return; + } + } + + private void serviceIdContextInit() { + namedContextFactory.destroy(); + // initializer each spring context for service id + MigrateProxyManager.getServicesId().forEach(serviceId -> namedContextFactory + .getInstance(serviceId, ILoadBalancer.class)); + } + + private synchronized void updateLastScaMigrateAnsResetValue(String value) { + this.lastScaMigrateAnsSwitchValue = value; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRibbonBeanPostProcessor.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRibbonBeanPostProcessor.java new file mode 100644 index 00000000..98fe851e --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRibbonBeanPostProcessor.java @@ -0,0 +1,52 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.ServerList; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.config.BeanPostProcessor; + +public class MigrateRibbonBeanPostProcessor + implements BeanPostProcessor, BeanClassLoaderAware { + + protected static final Log log = LogFactory.getLog(MigrateOnCondition.class); + + private ClassLoader classLoader; + private IClientConfig clientConfig; + + public MigrateRibbonBeanPostProcessor(IClientConfig clientConfig) { + this.clientConfig = clientConfig; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + + // step 1 : check the bean whether proxy or not + if (!MigrateProxyManager.isMigrateProxy()) { + log.info("Migrate proxy is Close."); + return bean; + } + + // step 2 : proxy the designated bean + if (bean instanceof ServerList) { + bean = MigrateProxyManager.newServerListProxy(bean, classLoader, + clientConfig); + } + + if (bean instanceof ILoadBalancer) { + bean = MigrateProxyManager.newLoadBalancerProxy(bean, classLoader, + clientConfig); + } + return bean; + } + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateServiceRegistry.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateServiceRegistry.java new file mode 100644 index 00000000..5491e98d --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateServiceRegistry.java @@ -0,0 +1,51 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.cloud.alicloud.ans.registry.AnsRegistration; +import org.springframework.cloud.alicloud.ans.registry.AnsServiceRegistry; +import org.springframework.cloud.alicloud.context.ans.AnsProperties; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author pbting + */ +@Component +public class MigrateServiceRegistry { + + private static final Log log = LogFactory.getLog(MigrateServiceRegistry.class); + + private AtomicBoolean running = new AtomicBoolean(false); + + private ServiceRegistry serviceRegistry; + private AnsRegistration ansRegistration; + + public MigrateServiceRegistry(AnsProperties ansProperties, + ApplicationContext context) { + this.ansRegistration = new AnsRegistration(ansProperties, context); + this.ansRegistration.init(); + this.serviceRegistry = new AnsServiceRegistry(); + } + + @EventListener(WebServerInitializedEvent.class) + public void onApplicationEvent(WebServerInitializedEvent event) { + int serverPort = event.getWebServer().getPort(); + this.ansRegistration.setPort(serverPort); + log.info("[ Migrate ] change the port to " + serverPort); + if (!this.running.get()) { + long s = System.currentTimeMillis(); + log.info("[Migrate] start to registry server to ANS"); + this.serviceRegistry.register(this.ansRegistration); + log.info("[migrate] end to registry server to ANS cost time with " + + (System.currentTimeMillis() - s) + " ms."); + this.running.set(true); + } + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrationAutoconfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrationAutoconfiguration.java new file mode 100644 index 00000000..7564b4d7 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrationAutoconfiguration.java @@ -0,0 +1,36 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alicloud.ans.ConditionalOnAnsEnabled; +import org.springframework.cloud.alicloud.context.ans.AnsProperties; +import org.springframework.cloud.context.named.NamedContextFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +@EnableConfigurationProperties +@Conditional(MigrateOnConditionClass.class) +@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) +@ConditionalOnAnsEnabled +public class MigrationAutoconfiguration { + + @Bean + public MigrateServiceRegistry migrationManger(AnsProperties ansProperties, + ApplicationContext applicationContext) { + + return new MigrateServiceRegistry(ansProperties, applicationContext); + } + + @Bean + public MigrateRefreshEventListener migrateRefreshEventListener( + Environment environment, + @Qualifier(value = "springClientFactory") NamedContextFactory namedContextFactory) { + + return new MigrateRefreshEventListener(environment, namedContextFactory); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerListInvocationHandler.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerListInvocationHandler.java new file mode 100644 index 00000000..16a31147 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerListInvocationHandler.java @@ -0,0 +1,169 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.Server; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.alicloud.ans.ribbon.AnsServer; +import org.springframework.cloud.alicloud.ans.ribbon.AnsServerList; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * + */ +class ServerListInvocationHandler implements MethodInterceptor { + + private final static Log log = LogFactory.getLog(ServerListInvocationHandler.class); + + private final static ConcurrentMap SERVER_LIST_CONCURRENT_MAP = new ConcurrentHashMap<>(); + + private final static ConcurrentMap> CALL_SERVICE_COUNT = new ConcurrentHashMap<>(); + + private final static Set INTERCEPTOR_METHOD_NAME = new ConcurrentSkipListSet(); + + private IClientConfig clientConfig; + private AnsServerList ansServerList; + private AtomicBoolean isFirst = new AtomicBoolean(false); + + static { + INTERCEPTOR_METHOD_NAME.add("getInitialListOfServers"); + INTERCEPTOR_METHOD_NAME.add("getUpdatedListOfServers"); + } + + ServerListInvocationHandler(IClientConfig clientConfig) { + this.clientConfig = clientConfig; + this.ansServerList = new AnsServerList(clientConfig.getClientName()); + SERVER_LIST_CONCURRENT_MAP.put(ansServerList.getDom(), ansServerList); + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + String methodName = invocation.getMethod().getName(); + // step 1: check the method is not interceptor + if (!INTERCEPTOR_METHOD_NAME.contains(methodName)) { + return invocation.proceed(); + } + + // step 2: interceptor the method + List serverList = (List) invocation.proceed(); + long s = System.currentTimeMillis(); + log.info("[ START ] merage server list for " + clientConfig.getClientName()); + serverList = mergeAnsServerList(serverList); + log.info("[ END ] merage server list for " + clientConfig.getClientName() + + ", cost time " + (System.currentTimeMillis() - s) + " ms ."); + return serverList; + } + + /** + * 后台线程 和 Eureka 两个注册中心进行 Merage 的时候,List 表中始终保持是有效的 Server. 即不考虑 ANS 客户端本地容灾的情况 + */ + private List mergeAnsServerList(final List source) { + if (isFirst.compareAndSet(false, true)) { + return source; + } + + // step 1: get all of server list and filter the alive + List ansServerList = filterAliveAnsServer( + this.ansServerList.getInitialListOfServers()); + + if (ansServerList.isEmpty()) { + return source; + } + + log.info("[" + this.clientConfig.getClientName() + "] Get Server List from ANS:" + + ansServerList + "; loadbalancer server list override before:" + source); + + // step 2:remove servers of that have in load balancer list + final Iterator serverIterator = source.iterator(); + while (serverIterator.hasNext()) { + final Server server = serverIterator.next(); + ansServerList.forEach(ansServer -> { + if (server.getHostPort() + .equals(ansServer.getHealthService().toInetAddr())) { + // by: ZoneAffinityPredicate + serverIterator.remove(); + log.info("Source Server is remove " + server.getHostPort() + + ", and from ANS Server is override:" + + ansServer.toString()); + } + // fix bug: mast be set the zone, update server list,will filter + ansServer.setZone(server.getZone()); + ansServer.setSchemea(server.getScheme()); + ansServer.setId(ansServer.getHealthService().toInetAddr()); + ansServer.setReadyToServe(true); + }); + } + + ansServerList.forEach(ansServer -> source.add(ansServer)); + log.info("[" + this.clientConfig.getClientName() + "] " + + "; loadbalancer server list override after:" + source); + // override + return source; + } + + private List filterAliveAnsServer(List sourceServerList) { + final List resultServerList = new LinkedList<>(); + sourceServerList.forEach(ansServer -> { + boolean isAlive = ansServer.isAlive(3); + if (isAlive) { + resultServerList.add(ansServer); + } + log.warn(ansServer.toString() + " isAlive :" + isAlive); + }); + return resultServerList; + } + + static Map> getServerRegistry() { + + return Collections.unmodifiableMap(CALL_SERVICE_COUNT); + } + + static Server checkAndGetServiceServer(String serviceId, Server server) { + if (server != null) { + return server; + } + + log.warn(String.format("[%s] refers the server is null", server)); + + List ansServerList = SERVER_LIST_CONCURRENT_MAP.get(serviceId) + .getInitialListOfServers(); + + if (!ansServerList.isEmpty()) { + return ansServerList.get(0); + } + + return server; + } + + static void incrementCallService(String serviceId, Server server) { + ConcurrentMap concurrentHashMap = CALL_SERVICE_COUNT + .putIfAbsent(serviceId, new ConcurrentHashMap<>()); + + if (concurrentHashMap == null) { + concurrentHashMap = CALL_SERVICE_COUNT.get(serviceId); + } + + String ipPort = server.getHostPort(); + ServerWrapper serverWraper = concurrentHashMap.putIfAbsent(ipPort, + new ServerWrapper(server, new AtomicLong())); + + if (serverWraper == null) { + serverWraper = concurrentHashMap.get(ipPort); + } + serverWraper.setServer(server); + serverWraper.getCallCount().incrementAndGet(); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerWrapper.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerWrapper.java new file mode 100644 index 00000000..aeaa5a60 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerWrapper.java @@ -0,0 +1,35 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.loadbalancer.Server; + +import java.util.concurrent.atomic.AtomicLong; + +public class ServerWrapper { + + private Server server; + private AtomicLong callCount; + + public ServerWrapper() { + } + + public ServerWrapper(Server server, AtomicLong callCount) { + this.server = server; + this.callCount = callCount; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } + + public AtomicLong getCallCount() { + return callCount; + } + + public void setCallCount(AtomicLong callCount) { + this.callCount = callCount; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsAutoServiceRegistration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsAutoServiceRegistration.java index 0774f185..82d0d8eb 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsAutoServiceRegistration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsAutoServiceRegistration.java @@ -16,8 +16,8 @@ package org.springframework.cloud.alicloud.ans.registry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; @@ -30,8 +30,7 @@ import org.springframework.util.StringUtils; */ public class AnsAutoServiceRegistration extends AbstractAutoServiceRegistration { - private static final Logger LOGGER = LoggerFactory - .getLogger(AnsAutoServiceRegistration.class); + private static final Log log = LogFactory.getLog(AnsAutoServiceRegistration.class); @Autowired private AnsRegistration registration; @@ -65,7 +64,7 @@ public class AnsAutoServiceRegistration @Override protected void register() { if (!this.registration.getAnsProperties().isRegisterEnabled()) { - LOGGER.debug("Registration disabled."); + log.debug("Registration disabled."); return; } if (this.registration.getPort() < 0) { diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsRegistration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsRegistration.java index 1fcb292f..cd763fdd 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsRegistration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsRegistration.java @@ -16,12 +16,6 @@ package org.springframework.cloud.alicloud.ans.registry; -import java.net.URI; -import java.util.Map; - -import javax.annotation.PostConstruct; - -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.alicloud.context.ans.AnsProperties; import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; @@ -31,6 +25,10 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; +import javax.annotation.PostConstruct; +import java.net.URI; +import java.util.Map; + /** * @author xiaolongzuo */ @@ -40,12 +38,14 @@ public class AnsRegistration implements Registration, ServiceInstance { private static final String MANAGEMENT_CONTEXT_PATH = "management.context-path"; private static final String MANAGEMENT_ADDRESS = "management.address"; - @Autowired private AnsProperties ansProperties; - - @Autowired private ApplicationContext context; + public AnsRegistration(AnsProperties ansProperties, ApplicationContext context) { + this.ansProperties = ansProperties; + this.context = context; + } + @PostConstruct public void init() { diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsServiceRegistry.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsServiceRegistry.java index ebe65ee3..1ff187a3 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsServiceRegistry.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/registry/AnsServiceRegistry.java @@ -16,24 +16,23 @@ package org.springframework.cloud.alicloud.ans.registry; +import com.alibaba.ans.core.NamingService; +import com.alibaba.ans.shaded.com.taobao.vipserver.client.ipms.NodeReactor; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.client.serviceregistry.ServiceRegistry; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.client.serviceregistry.ServiceRegistry; - -import com.alibaba.ans.core.NamingService; -import com.alibaba.ans.shaded.com.taobao.vipserver.client.ipms.NodeReactor; - /** * @author xiaolongzuo */ public class AnsServiceRegistry implements ServiceRegistry { - private static Logger logger = LoggerFactory.getLogger(AnsServiceRegistry.class); + private static Log log = LogFactory.getLog(AnsServiceRegistry.class); private static final String SEPARATOR = ","; @@ -41,11 +40,11 @@ public class AnsServiceRegistry implements ServiceRegistry { public void register(AnsRegistration registration) { if (!registration.isRegisterEnabled()) { - logger.info("Registration is disabled..."); + log.info("Registration is disabled..."); return; } if (StringUtils.isEmpty(registration.getServiceId())) { - logger.info("No service to register for client..."); + log.info("No service to register for client..."); return; } @@ -63,13 +62,14 @@ public class AnsServiceRegistry implements ServiceRegistry { NamingService.regDom(dom, registration.getHost(), registration.getPort(), registration.getRegisterWeight(dom), registration.getCluster(), tags); - logger.info("INFO_ANS_REGISTER, {} {}:{} register finished", dom, - registration.getAnsProperties().getClientIp(), - registration.getAnsProperties().getClientPort()); + log.info("INFO_ANS_REGISTER, " + dom + " " + + registration.getAnsProperties().getClientIp() + ":" + + registration.getAnsProperties().getClientPort() + + " register finished"); } catch (Exception e) { - logger.error("ERR_ANS_REGISTER, {} register failed...{},", dom, - registration.toString(), e); + log.error("ERR_ANS_REGISTER, " + dom + " register failed..." + + registration.toString() + ",", e); } } } @@ -77,10 +77,10 @@ public class AnsServiceRegistry implements ServiceRegistry { @Override public void deregister(AnsRegistration registration) { - logger.info("De-registering from ANSServer now..."); + log.info("De-registering from ANSServer now..."); if (StringUtils.isEmpty(registration.getServiceId())) { - logger.info("No dom to de-register for client..."); + log.info("No dom to de-register for client..."); return; } @@ -89,11 +89,11 @@ public class AnsServiceRegistry implements ServiceRegistry { registration.getPort(), registration.getCluster()); } catch (Exception e) { - logger.error("ERR_ANS_DEREGISTER, de-register failed...{},", - registration.toString(), e); + log.error("ERR_ANS_DEREGISTER, de-register failed..." + + registration.toString() + ",", e); } - logger.info("De-registration finished."); + log.info("De-registration finished."); } @Override diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsRibbonClientConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsRibbonClientConfiguration.java index 7734e037..3f1f1516 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsRibbonClientConfiguration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsRibbonClientConfiguration.java @@ -16,22 +16,24 @@ package org.springframework.cloud.alicloud.ans.ribbon; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ServerList; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.alicloud.ans.migrate.MigrateOnConditionMissingClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; /** * @author xiaolongzuo */ @Configuration +@Conditional(MigrateOnConditionMissingClass.class) public class AnsRibbonClientConfiguration { @Bean @ConditionalOnMissingBean - public ServerList ribbonServerList(IClientConfig config) { + public ServerList ansRibbonServerList(IClientConfig config) { AnsServerList serverList = new AnsServerList(config.getClientName()); return serverList; } diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServer.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServer.java index 8438a7f3..d19f6287 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServer.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServer.java @@ -16,12 +16,16 @@ package org.springframework.cloud.alicloud.ans.ribbon; -import java.util.Collections; -import java.util.Map; - import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; import com.netflix.loadbalancer.Server; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + /** * @author xiaolongzuo */ @@ -34,7 +38,8 @@ public class AnsServer extends Server { public AnsServer(final Host host, final String dom) { super(host.getIp(), host.getPort()); this.host = host; - this.metadata = Collections.emptyMap(); + this.metadata = new HashMap(); + this.metadata.put("source", "ANS"); metaInfo = new MetaInfo() { @Override public String getAppName() { @@ -48,16 +53,44 @@ public class AnsServer extends Server { @Override public String getServiceIdForDiscovery() { - return null; + return dom; } @Override public String getInstanceId() { - return null; + return AnsServer.this.host.getIp() + ":" + dom + ":" + + AnsServer.this.host.getPort(); } }; } + @Override + public boolean isAlive() { + + return true; + } + + /** + * + * @param timeOut Unit: Seconds + * @return + */ + public boolean isAlive(long timeOut) { + try { + String hostName = this.host.getHostname(); + hostName = hostName != null && hostName.trim().length() > 0 ? hostName + : this.host.getIp(); + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(hostName, this.host.getPort()), + (int) TimeUnit.SECONDS.toMillis(timeOut)); + socket.close(); + return true; + } + catch (IOException e) { + return false; + } + } + @Override public MetaInfo getMetaInfo() { return metaInfo; @@ -71,4 +104,9 @@ public class AnsServer extends Server { return metadata; } + @Override + public String toString() { + return "AnsServer{" + "metaInfo=" + metaInfo + ", host=" + host + ", metadata=" + + metadata + '}'; + } } diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServerList.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServerList.java index f34f19ff..74472369 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServerList.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/AnsServerList.java @@ -16,19 +16,21 @@ package org.springframework.cloud.alicloud.ans.ribbon; -import java.util.ArrayList; -import java.util.List; - import com.alibaba.ans.core.NamingService; import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractServerList; +import java.util.ArrayList; +import java.util.List; + /** * @author xiaolongzuo */ public class AnsServerList extends AbstractServerList { + private final static int CONNECT_TIME_OUT = 3; + private String dom; public AnsServerList(String dom) { @@ -60,10 +62,12 @@ public class AnsServerList extends AbstractServerList { List result = new ArrayList(hosts.size()); for (Host host : hosts) { if (host.isValid()) { - result.add(hostToServer(host)); + AnsServer ansServer = hostToServer(host); + if (ansServer.isAlive(CONNECT_TIME_OUT)) { + result.add(ansServer); + } } } - return result; } diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/MigrateRibbonCofiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/MigrateRibbonCofiguration.java new file mode 100644 index 00000000..9074b786 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/MigrateRibbonCofiguration.java @@ -0,0 +1,19 @@ +package org.springframework.cloud.alicloud.ans.ribbon; + +import com.netflix.client.config.IClientConfig; +import org.springframework.cloud.alicloud.ans.migrate.MigrateOnConditionClass; +import org.springframework.cloud.alicloud.ans.migrate.MigrateRibbonBeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Conditional(MigrateOnConditionClass.class) +public class MigrateRibbonCofiguration { + + @Bean + public MigrateRibbonBeanPostProcessor migrateBeanPostProcessor(IClientConfig clientConfig) { + + return new MigrateRibbonBeanPostProcessor(clientConfig); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/RibbonAnsAutoConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/RibbonAnsAutoConfiguration.java index 4333cb53..cf7d52ff 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/RibbonAnsAutoConfiguration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/RibbonAnsAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnBean(SpringClientFactory.class) @ConditionalOnRibbonAns @AutoConfigureAfter(RibbonAutoConfiguration.class) -@RibbonClients(defaultConfiguration = AnsRibbonClientConfiguration.class) +@RibbonClients(defaultConfiguration = { AnsRibbonClientConfiguration.class, + MigrateRibbonCofiguration.class }) public class RibbonAnsAutoConfiguration { } diff --git a/spring-cloud-alicloud-ans/src/main/resources/META-INF/spring.factories b/spring-cloud-alicloud-ans/src/main/resources/META-INF/spring.factories index 63d6cd5c..f733a74e 100644 --- a/spring-cloud-alicloud-ans/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alicloud-ans/src/main/resources/META-INF/spring.factories @@ -1,6 +1,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alicloud.ans.endpoint.AnsEndpointAutoConfiguration,\ org.springframework.cloud.alicloud.ans.ribbon.RibbonAnsAutoConfiguration,\ - org.springframework.cloud.alicloud.ans.AnsAutoConfiguration + org.springframework.cloud.alicloud.ans.AnsAutoConfiguration,\ + org.springframework.cloud.alicloud.ans.migrate.MigrateEndpointAutoConfiguration,\ + org.springframework.cloud.alicloud.ans.migrate.MigrationAutoconfiguration org.springframework.cloud.client.discovery.EnableDiscoveryClient=\ - org.springframework.cloud.alicloud.ans.AnsDiscoveryClientAutoConfiguration + org.springframework.cloud.alicloud.ans.AnsDiscoveryClientAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-alicloud-context/pom.xml b/spring-cloud-alicloud-context/pom.xml index 39e73ab5..496aea37 100644 --- a/spring-cloud-alicloud-context/pom.xml +++ b/spring-cloud-alicloud-context/pom.xml @@ -50,6 +50,12 @@ 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/scx/ScxContextAutoConfiguration.java b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/scx/ScxContextAutoConfiguration.java index 0d43e86f..ab391d21 100644 --- a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/scx/ScxContextAutoConfiguration.java +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/scx/ScxContextAutoConfiguration.java @@ -16,27 +16,18 @@ package org.springframework.cloud.alicloud.context.scx; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; +import com.alibaba.cloud.context.edas.AliCloudEdasSdk; +import com.alibaba.cloud.context.scx.AliCloudScxInitializer; +import com.alibaba.edas.schedulerx.SchedulerXClient; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.alicloud.context.AliCloudProperties; import org.springframework.cloud.alicloud.context.edas.EdasContextAutoConfiguration; import org.springframework.cloud.alicloud.context.edas.EdasProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import com.alibaba.cloud.context.edas.AliCloudEdasSdk; -import com.alibaba.cloud.context.edas.EdasChangeOrderConfiguration; -import com.alibaba.cloud.context.edas.EdasChangeOrderConfigurationFactory; -import com.alibaba.dts.common.exception.InitException; -import com.alibaba.edas.schedulerx.SchedulerXClient; -import com.aliyuncs.edas.model.v20170801.GetSecureTokenResponse; /** * @author xiaolongzuo @@ -45,77 +36,15 @@ import com.aliyuncs.edas.model.v20170801.GetSecureTokenResponse; @ConditionalOnClass(name = "org.springframework.cloud.alicloud.scx.ScxAutoConfiguration") @EnableConfigurationProperties(ScxProperties.class) @ImportAutoConfiguration(EdasContextAutoConfiguration.class) -public class ScxContextAutoConfiguration implements InitializingBean { +public class ScxContextAutoConfiguration { - private static final Logger log = LoggerFactory - .getLogger(ScxContextAutoConfiguration.class); - - private static final String TEST_REGION = "cn-test"; - - private static final String DEFAULT_KEY = "123456"; - - @Autowired - private SchedulerXClient schedulerXClient; - - @Bean + @Bean(initMethod = "init") + @ConditionalOnMissingBean public SchedulerXClient schedulerXClient(AliCloudProperties aliCloudProperties, - EdasProperties edasProperties, ScxProperties scxProperties, - AliCloudEdasSdk aliCloudEdasSdk) { - Assert.isTrue(!StringUtils.isEmpty(scxProperties.getGroupId()), - "${spring.cloud.alicloud.scx.group-id} can't be null."); - SchedulerXClient schedulerXClient = new SchedulerXClient(); - schedulerXClient.setGroupId(scxProperties.getGroupId()); - - EdasChangeOrderConfiguration edasChangeOrderConfiguration = EdasChangeOrderConfigurationFactory - .getEdasChangeOrderConfiguration(); - if (edasChangeOrderConfiguration.isEdasManaged()) { - if (edasChangeOrderConfiguration.getRegionId() != null) { - schedulerXClient - .setRegionName(edasChangeOrderConfiguration.getRegionId()); - } - else { - Assert.isTrue(!StringUtils.isEmpty(edasProperties.getNamespace()), - "${spring.cloud.alicloud.edas.namespace} can't be null."); - schedulerXClient.setRegionName(edasProperties.getRegionId()); - } - schedulerXClient.setDomainName(scxProperties.getDomainName()); - schedulerXClient - .setAccessKey(edasChangeOrderConfiguration.getDauthAccessKey()); - schedulerXClient - .setSecretKey(edasChangeOrderConfiguration.getDauthSecretKey()); - } - else if (TEST_REGION.equals(edasProperties.getNamespace())) { - Assert.isTrue(!StringUtils.isEmpty(edasProperties.getNamespace()), - "${spring.cloud.alicloud.edas.namespace} can't be null."); - schedulerXClient.setRegionName(edasProperties.getNamespace()); - schedulerXClient.setAccessKey(DEFAULT_KEY); - schedulerXClient.setSecretKey(DEFAULT_KEY); - } - else { - Assert.isTrue(!StringUtils.isEmpty(edasProperties.getNamespace()), - "${spring.cloud.alicloud.edas.namespace} can't be null."); - Assert.isTrue(!StringUtils.isEmpty(aliCloudProperties.getAccessKey()), - "${spring.cloud.alicloud.access-key} can't be empty."); - Assert.isTrue(!StringUtils.isEmpty(aliCloudProperties.getSecretKey()), - "${spring.cloud.alicloud.secret-key} can't be empty."); - GetSecureTokenResponse.SecureToken secureToken = aliCloudEdasSdk - .getSecureToken(edasProperties.getNamespace()); - schedulerXClient.setRegionName(edasProperties.getRegionId()); - schedulerXClient.setDomainName(scxProperties.getDomainName()); - schedulerXClient.setAccessKey(secureToken.getAccessKey()); - schedulerXClient.setSecretKey(secureToken.getSecretKey()); - } - return schedulerXClient; + EdasProperties edasProperties, ScxProperties scxProperties, + AliCloudEdasSdk aliCloudEdasSdk) { + return AliCloudScxInitializer.initialize(aliCloudProperties, edasProperties, + scxProperties, aliCloudEdasSdk); } - @Override - public void afterPropertiesSet() { - try { - schedulerXClient.init(); - } - catch (InitException e) { - log.error("Init SchedulerX failed.", e); - throw new RuntimeException(e); - } - } } diff --git a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigProperties.java b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigProperties.java new file mode 100644 index 00000000..022c5e19 --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigProperties.java @@ -0,0 +1,107 @@ +package org.springframework.cloud.alicloud.context.sms; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.alicloud.context.AliCloudProperties; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +import java.io.Serializable; + +/** + * @author pbting + */ +@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms") +public class SmsConfigProperties implements Serializable { + + // 产品名称:云通信短信API产品,开发者无需替换 + public static final String smsProduct = "Dysmsapi"; + // 产品域名,开发者无需替换 + public static final String smsDomain = "dysmsapi.aliyuncs.com"; + + private AliCloudProperties aliCloudProperties; + + /** + * + */ + private String reportQueueName; + /** + * + */ + private String upQueueName; + + /** + * + */ + protected String connnectTimeout = "10000"; + + /** + * + */ + protected String readTimeout = "10000"; + + public SmsConfigProperties(AliCloudProperties aliCloudProperties) { + this.aliCloudProperties = aliCloudProperties; + } + + public String getConnnectTimeout() { + return connnectTimeout; + } + + public void setConnnectTimeout(String connnectTimeout) { + this.connnectTimeout = connnectTimeout; + } + + public String getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(String readTimeout) { + this.readTimeout = readTimeout; + } + + public void overiideFromEnv(Environment environment) { + overiideCustomFromEnv(environment); + if (StringUtils.isEmpty(connnectTimeout)) { + String resolveResult = environment.resolveRequiredPlaceholders( + "${spring.cloud.alibaba.sms.connect-timeout:}"); + this.setConnnectTimeout( + StringUtils.isEmpty(resolveResult) ? "10000" : resolveResult); + } + + if (StringUtils.isEmpty(readTimeout)) { + String resolveResult = environment.resolveRequiredPlaceholders( + "${spring.cloud.alibaba.sms.read-timeout:}"); + this.setReadTimeout( + StringUtils.isEmpty(resolveResult) ? "10000" : resolveResult); + } + } + + public void overiideCustomFromEnv(Environment environment) { + // nothing to do + } + + public String getReportQueueName() { + return reportQueueName; + } + + public void setReportQueueName(String reportQueueName) { + this.reportQueueName = reportQueueName; + } + + public String getUpQueueName() { + return upQueueName; + } + + public String getAccessKeyId() { + return aliCloudProperties.getAccessKey(); + } + + public String getAccessKeySecret() { + return aliCloudProperties.getSecretKey(); + } + + public void setUpQueueName(String upQueueName) { + this.upQueueName = upQueueName; + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigRegistration.java b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigRegistration.java new file mode 100644 index 00000000..55564262 --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigRegistration.java @@ -0,0 +1,26 @@ +package org.springframework.cloud.alicloud.context.sms; + +import org.springframework.core.env.Environment; + +import javax.annotation.PostConstruct; + +/** + * @author pbting + */ +public class SmsConfigRegistration { + + private Environment environment; + + private SmsConfigProperties smsConfigProperties; + + public SmsConfigRegistration(Environment environment, + SmsConfigProperties smsConfigProperties) { + this.environment = environment; + this.smsConfigProperties = smsConfigProperties; + } + + @PostConstruct + public void initSmsConfigRegistration() { + smsConfigProperties.overiideFromEnv(environment); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsContextAutoConfiguration.java b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsContextAutoConfiguration.java new file mode 100644 index 00000000..6f1e4416 --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsContextAutoConfiguration.java @@ -0,0 +1,30 @@ +package org.springframework.cloud.alicloud.context.sms; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alicloud.context.AliCloudProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +@EnableConfigurationProperties +@ConditionalOnClass(name = "com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest") +@ConditionalOnProperty(value = "spring.cloud.alibaba.deshao.enable.sms", matchIfMissing = true) +public class SmsContextAutoConfiguration { + + @Bean + public SmsConfigProperties smsConfigProperties( + AliCloudProperties aliCloudProperties) { + + return new SmsConfigProperties(aliCloudProperties); + } + + @Bean + public SmsConfigRegistration smsConfigRegistration(Environment environment, + SmsConfigProperties smsConfigProperties) { + + return new SmsConfigRegistration(environment, smsConfigProperties); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-context/src/main/resources/META-INF/spring.factories b/spring-cloud-alicloud-context/src/main/resources/META-INF/spring.factories index a14c7077..f99f0502 100644 --- a/spring-cloud-alicloud-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-alicloud-context/src/main/resources/META-INF/spring.factories @@ -6,7 +6,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.alicloud.context.ans.AnsContextAutoConfiguration,\ org.springframework.cloud.alicloud.context.oss.OssContextAutoConfiguration,\ org.springframework.cloud.alicloud.context.scx.ScxContextAutoConfiguration,\ - org.springframework.cloud.alicloud.context.statistics.StatisticsTaskStarter + org.springframework.cloud.alicloud.context.statistics.StatisticsTaskStarter,\ + org.springframework.cloud.alicloud.context.sms.SmsContextAutoConfiguration org.springframework.context.ApplicationListener=\ org.springframework.cloud.alicloud.context.ans.AnsContextApplicationListener,\ org.springframework.cloud.alicloud.context.nacos.NacosParameterInitListener,\ diff --git a/spring-cloud-alicloud-sms/pom.xml b/spring-cloud-alicloud-sms/pom.xml new file mode 100644 index 00000000..001a46ad --- /dev/null +++ b/spring-cloud-alicloud-sms/pom.xml @@ -0,0 +1,83 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba + 0.2.2.BUILD-SNAPSHOT + + 4.0.0 + + org.springframework.cloud + spring-cloud-alicloud-sms + Spring Cloud Alibaba Cloud SMS + + + + + org.springframework.cloud + spring-cloud-alicloud-context + + + com.aliyun + aliyun-java-sdk-core + + + + + + + com.aliyun + aliyun-java-sdk-core + + + com.aliyun + aliyun-java-sdk-dysmsapi + + + com.aliyun.mns + aliyun-sdk-mns + + + com.aliyun + aliyun-java-sdk-dysmsapi + + + + org.springframework.boot + spring-boot-actuator + provided + true + + + + org.springframework.boot + spring-boot-actuator-autoconfigure + provided + true + + + + org.springframework.boot + spring-boot-configuration-processor + provided + true + + + + org.slf4j + slf4j-api + provided + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/AbstractSmsService.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/AbstractSmsService.java new file mode 100644 index 00000000..02273a69 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/AbstractSmsService.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.alicloud.sms; + +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.profile.DefaultProfile; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * @author pbting + */ +public abstract class AbstractSmsService implements ISmsService { + + private ConcurrentHashMap acsClientConcurrentHashMap = new ConcurrentHashMap<>(); + + public IAcsClient getHangZhouRegionClientProfile(String accessKeyId, + String accessKeySecret) { + + return acsClientConcurrentHashMap.computeIfAbsent( + getKey("cn-hangzhou", accessKeyId, accessKeySecret), + (iacsClient) -> new DefaultAcsClient(DefaultProfile + .getProfile("cn-hangzhou", accessKeyId, accessKeySecret))); + } + + private String getKey(String regionId, String accessKeyId, String accessKeySecret) { + + return regionId + ":" + accessKeyId + ":" + accessKeySecret; + } + +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/ISmsService.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/ISmsService.java new file mode 100644 index 00000000..54a8201c --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/ISmsService.java @@ -0,0 +1,115 @@ +/* + * 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.alicloud.sms; + +import com.aliyuncs.IAcsClient; +import com.aliyuncs.dysmsapi.model.v20170525.*; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; + +/** + * @author pbting + */ +public interface ISmsService { + + /** + * + * @param accessKeyId + * @param secret + * @return IAcsClient + */ + IAcsClient getHangZhouRegionClientProfile(String accessKeyId, String secret); + + /** + * + * @param sendSmsRequest + * @throws ServerException + * @throws ClientException + * @return SendSmsResponse + */ + SendSmsResponse sendSmsRequest(SendSmsRequest sendSmsRequest) + throws ServerException, ClientException; + + /** + * + * @param sendBatchSmsRequest + * @throws ServerException + * @throws ClientException + * @return SendBatchSmsResponse + */ + SendBatchSmsResponse sendSmsBatchRequest(SendBatchSmsRequest sendBatchSmsRequest) + throws ServerException, ClientException; + + /** + * 因为阿里云支持多个 + * accessKeyId/accessKeySecret,当不想使用默认的配置accessKeyId/accessKeySecret时,可以使用这个方法来支持额外 + * 的accessKeyId/accessKeySecret 发送 + * @param sendSmsRequest + * @param accessKeyId + * @param accessKeySecret + * @return + * @throws ServerException + * @throws ClientException + */ + SendSmsResponse sendSmsRequest(SendSmsRequest sendSmsRequest, String accessKeyId, + String accessKeySecret) throws ServerException, ClientException; + + /** + * + * @param sendSmsRequest + * @param accessKeyId + * @param accessKeySecret + * @throws ServerException + * @throws ClientException + * @return SendBatchSmsResponse + */ + SendBatchSmsResponse sendSmsBatchRequest(SendBatchSmsRequest sendSmsRequest, + String accessKeyId, String accessKeySecret) + throws ServerException, ClientException; + + /** + * + * @param smsReportMessageListener + * @return boolean + */ + boolean startSmsReportMessageListener( + SmsReportMessageListener smsReportMessageListener); + + /** + * + * @param smsUpMessageListener + * @return boolean + */ + boolean startSmsUpMessageListener(SmsUpMessageListener smsUpMessageListener); + + /** + * + * @param request + * @param accessKeyId + * @param accessKeySecret + * @return QuerySendDetailsResponse + */ + QuerySendDetailsResponse querySendDetails(QuerySendDetailsRequest request, + String accessKeyId, String accessKeySecret) throws ClientException; + + /** + * + * @param request + * @return QuerySendDetailsResponse + */ + QuerySendDetailsResponse querySendDetails(QuerySendDetailsRequest request) + throws ClientException; +} \ No newline at end of file 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 new file mode 100644 index 00000000..0556ea06 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializerEventListener.java @@ -0,0 +1,105 @@ +/* + * 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.alicloud.sms; + +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.profile.DefaultProfile; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.cloud.alicloud.context.sms.SmsConfigProperties; +import org.springframework.cloud.alicloud.sms.base.MessageListener; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author pbting + */ +@Component +public class SmsInitializerEventListener + implements ApplicationListener { + + private final AtomicBoolean isCalled = new AtomicBoolean(false); + + private SmsConfigProperties msConfigProperties; + + private ISmsService smsService; + + public SmsInitializerEventListener(SmsConfigProperties msConfigProperties, + ISmsService smsService) { + this.msConfigProperties = msConfigProperties; + this.smsService = smsService; + } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + if (!isCalled.compareAndSet(false, true)) { + return; + } + + // 整个application context refreshed then do + // 可自助调整超时时间 + System.setProperty("sun.net.client.defaultConnectTimeout", + msConfigProperties.getConnnectTimeout()); + System.setProperty("sun.net.client.defaultReadTimeout", + msConfigProperties.getReadTimeout()); + // 初始化acsClient,暂不支持region化 + try { + DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", + SmsConfigProperties.smsProduct, SmsConfigProperties.smsDomain); + Collection messageListeners = event.getApplicationContext() + .getBeansOfType(MessageListener.class).values(); + if (messageListeners.isEmpty()) { + return; + } + + for (MessageListener messageListener : messageListeners) { + if (SmsReportMessageListener.class.isInstance(messageListener)) { + if (msConfigProperties.getReportQueueName() != null + && msConfigProperties.getReportQueueName().trim() + .length() > 0) { + smsService.startSmsReportMessageListener( + (SmsReportMessageListener) messageListener); + continue; + } + + throw new IllegalArgumentException("the SmsReport queue name for " + + messageListener.getClass().getCanonicalName() + + " must be set."); + } + + if (SmsUpMessageListener.class.isInstance(messageListener)) { + + if (msConfigProperties.getUpQueueName() != null + && msConfigProperties.getUpQueueName().trim().length() > 0) { + smsService.startSmsUpMessageListener( + (SmsUpMessageListener) messageListener); + continue; + } + + throw new IllegalArgumentException("the SmsUp queue name for " + + messageListener.getClass().getCanonicalName() + + " must be set."); + } + } + } + catch (ClientException e) { + throw new RuntimeException( + "initialize sms profile end point cause an exception"); + } + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsMessageListener.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsMessageListener.java new file mode 100644 index 00000000..5fc34dd5 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsMessageListener.java @@ -0,0 +1,24 @@ +/* + * 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.alicloud.sms; + +import org.springframework.cloud.alicloud.sms.base.MessageListener; + +/** + * @author pbting + */ +public interface SmsMessageListener extends MessageListener { +} diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsReportMessageListener.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsReportMessageListener.java new file mode 100644 index 00000000..c214a628 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsReportMessageListener.java @@ -0,0 +1,22 @@ +/* + * 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.alicloud.sms; + +/** + * @author pbting + */ +public interface SmsReportMessageListener extends SmsMessageListener { +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsServiceImpl.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsServiceImpl.java new file mode 100644 index 00000000..d32c8c64 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsServiceImpl.java @@ -0,0 +1,187 @@ +/* + * 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.alicloud.sms; + +import com.aliyuncs.dysmsapi.model.v20170525.*; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.alicloud.context.sms.SmsConfigProperties; +import org.springframework.cloud.alicloud.sms.base.DefaultAlicomMessagePuller; +import org.springframework.cloud.alicloud.sms.endpoint.EndpointManager; +import org.springframework.cloud.alicloud.sms.endpoint.ReceiveMessageEntity; + +import java.text.ParseException; + +/** + * @author pbting + */ +public final class SmsServiceImpl extends AbstractSmsService { + + private static final Log log = LogFactory.getLog(SmsServiceImpl.class); + /** + * will expose user to call this method send sms message + * @param sendSmsRequest + * @return + */ + private SmsConfigProperties smsConfigProperties; + + public SmsServiceImpl(SmsConfigProperties smsConfigProperties) { + this.smsConfigProperties = smsConfigProperties; + } + + public SendSmsResponse sendSmsRequest(SendSmsRequest sendSmsRequest) + throws ClientException { + + return sendSmsRequest(sendSmsRequest, smsConfigProperties.getAccessKeyId(), + smsConfigProperties.getAccessKeySecret()); + } + + /** + * 因为阿里云支持多个 + * accessKeyId/accessKeySecret,当不想使用默认的配置accessKeyId/accessKeySecret时,可以使用这个方法来支持额外 + * 的accessKeyId/accessKeySecret 发送 + * @param sendSmsRequest + * @param accessKeyId + * @param accessKeySecret + * @throws ServerException + * @throws ClientException + * @return SendSmsResponse + */ + public SendSmsResponse sendSmsRequest(SendSmsRequest sendSmsRequest, + String accessKeyId, String accessKeySecret) + throws ServerException, ClientException { + EndpointManager.addSendSmsRequest(sendSmsRequest); + // hint 此处可能会抛出异常,注意catch + return getHangZhouRegionClientProfile(accessKeyId, accessKeySecret) + .getAcsResponse(sendSmsRequest); + } + + /** + * + * @param smsReportMessageListener + * @return boolean + */ + public boolean startSmsReportMessageListener( + SmsReportMessageListener smsReportMessageListener) { + String messageType = "SmsReport";// 短信回执:SmsReport,短信上行:SmsUp + String queueName = smsConfigProperties.getReportQueueName(); + return startReceiveMsg(messageType, queueName, smsReportMessageListener); + } + + /** + * + * @param smsUpMessageListener + * @return boolean + */ + public boolean startSmsUpMessageListener(SmsUpMessageListener smsUpMessageListener) { + String messageType = "SmsUp";// 短信回执:SmsReport,短信上行:SmsUp + String queueName = smsConfigProperties.getUpQueueName(); + return startReceiveMsg(messageType, queueName, smsUpMessageListener); + } + + /** + * + * @param messageType + * @param queueName + * @param messageListener + * @return boolean + */ + private boolean startReceiveMsg(String messageType, String queueName, + SmsMessageListener messageListener) { + String accessKeyId = smsConfigProperties.getAccessKeyId(); + String accessKeySecret = smsConfigProperties.getAccessKeySecret(); + boolean result = true; + try { + new DefaultAlicomMessagePuller().startReceiveMsg(accessKeyId, accessKeySecret, + messageType, queueName, messageListener); + EndpointManager.addReceiveMessageEntity( + new ReceiveMessageEntity(messageType, queueName, messageListener)); + } + catch (ClientException e) { + log.error("start sms report message listener cause an exception", e); + result = false; + } + catch (ParseException e) { + log.error("start sms report message listener cause an exception", e); + result = false; + } + return result; + } + + /** + * + * @param sendBatchSmsRequest + * @throws ServerException + * @throws ClientException + * @return SendBatchSmsResponse + */ + @Override + public SendBatchSmsResponse sendSmsBatchRequest( + SendBatchSmsRequest sendBatchSmsRequest) + throws ServerException, ClientException { + + return sendSmsBatchRequest(sendBatchSmsRequest, + smsConfigProperties.getAccessKeyId(), + smsConfigProperties.getAccessKeySecret()); + } + + /** + * + * @param sendBatchSmsRequest + * @param accessKeyId + * @param accessKeySecret + * @throws ClientException + * @return SendBatchSmsResponse + */ + @Override + public SendBatchSmsResponse sendSmsBatchRequest( + SendBatchSmsRequest sendBatchSmsRequest, String accessKeyId, + String accessKeySecret) throws ClientException { + EndpointManager.addSendBatchSmsRequest(sendBatchSmsRequest); + return getHangZhouRegionClientProfile(accessKeyId, accessKeySecret) + .getAcsResponse(sendBatchSmsRequest); + } + + /** + * + * @param request + * @param accessKeyId + * @param accessKeySecret + * @throws ClientException + * @return QuerySendDetailsResponse + */ + @Override + public QuerySendDetailsResponse querySendDetails(QuerySendDetailsRequest request, + String accessKeyId, String accessKeySecret) throws ClientException { + return getHangZhouRegionClientProfile(accessKeyId, accessKeySecret) + .getAcsResponse(request); + } + + /** + * + * @param request + * @throws ClientException + * @return QuerySendDetailsResponse + */ + @Override + public QuerySendDetailsResponse querySendDetails(QuerySendDetailsRequest request) + throws ClientException { + return querySendDetails(request, smsConfigProperties.getAccessKeyId(), + smsConfigProperties.getAccessKeySecret()); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsUpMessageListener.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsUpMessageListener.java new file mode 100644 index 00000000..4da61438 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsUpMessageListener.java @@ -0,0 +1,22 @@ +/* + * 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.alicloud.sms; + +/** + * @author pbting + */ +public interface SmsUpMessageListener extends SmsMessageListener { +} \ No newline at end of file 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 new file mode 100755 index 00000000..50fe2ed6 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/DefaultAlicomMessagePuller.java @@ -0,0 +1,429 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyun.mns.client.CloudQueue; +import com.aliyun.mns.common.ClientException; +import com.aliyun.mns.common.ServiceException; +import com.aliyun.mns.model.Message; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +/** + * 阿里通信官方消息默认拉取工具类 + */ +public class DefaultAlicomMessagePuller { + + private Log logger = LogFactory.getLog(DefaultAlicomMessagePuller.class); + + private String mnsAccountEndpoint = "https://1943695596114318.mns.cn-hangzhou.aliyuncs.com/";// 阿里通信消息的endpoint,固定。 + private String endpointNameForPop = "cn-hangzhou"; + private String regionIdForPop = "cn-hangzhou"; + private String domainForPop = "dybaseapi.aliyuncs.com"; + private TokenGetterForAlicom tokenGetter; + private MessageListener messageListener; + private boolean isRunning = false; + private Integer pullMsgThreadSize = 1; + private boolean debugLogOpen = false; + private Integer sleepSecondWhenNoData = 30; + + public void openDebugLog(boolean debugLogOpen) { + this.debugLogOpen = debugLogOpen; + } + + public Integer getSleepSecondWhenNoData() { + return sleepSecondWhenNoData; + } + + public void setSleepSecondWhenNoData(Integer sleepSecondWhenNoData) { + this.sleepSecondWhenNoData = sleepSecondWhenNoData; + } + + public Integer getPullMsgThreadSize() { + return pullMsgThreadSize; + } + + public void setPullMsgThreadSize(Integer pullMsgThreadSize) { + if (pullMsgThreadSize != null && pullMsgThreadSize > 1) { + this.pullMsgThreadSize = pullMsgThreadSize; + } + } + + private ExecutorService executorService; + + public ExecutorService getExecutorService() { + return executorService; + } + + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + protected static final Map sLockObjMap = new HashMap(); + protected static Map sPollingMap = new ConcurrentHashMap(); + protected Object lockObj; + + public boolean setPolling(String queueName) { + synchronized (lockObj) { + Boolean ret = sPollingMap.get(queueName); + if (ret == null || !ret) { + sPollingMap.put(queueName, true); + return true; + } + return false; + } + } + + public void clearPolling(String queueName) { + synchronized (lockObj) { + sPollingMap.put(queueName, false); + lockObj.notifyAll(); + if (debugLogOpen) { + logger.info("PullMessageTask_WakeUp:Everyone WakeUp and Work!"); + } + } + } + + public boolean isRunning() { + return isRunning; + } + + public void setRunning(boolean running) { + isRunning = running; + } + + private class PullMessageTask implements Runnable { + private String messageType; + private String queueName; + + @Override + public void run() { + + boolean polling = false; + while (isRunning) { + try { + synchronized (lockObj) { + Boolean p = sPollingMap.get(queueName); + if (p != null && p) { + try { + if (debugLogOpen) { + logger.info("PullMessageTask_sleep:" + + Thread.currentThread().getName() + + " Have a nice sleep!"); + } + polling = false; + lockObj.wait(); + } + catch (InterruptedException e) { + if (debugLogOpen) { + logger.info("PullMessageTask_Interrupted!" + + Thread.currentThread().getName() + + " QueueName is " + queueName); + } + continue; + } + } + } + + TokenForAlicom tokenObject = tokenGetter.getTokenByMessageType( + messageType, queueName, mnsAccountEndpoint); + CloudQueue queue = tokenObject.getQueue(); + Message popMsg = null; + if (!polling) { + popMsg = queue.popMessage(); + if (debugLogOpen) { + SimpleDateFormat format = new SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss"); + logger.info("PullMessageTask_popMessage:" + + Thread.currentThread().getName() + "-popDone at " + + "," + format.format(new Date()) + " msgSize=" + + (popMsg == null ? 0 : popMsg.getMessageId())); + } + if (popMsg == null) { + polling = true; + continue; + } + } + else { + if (setPolling(queueName)) { + if (debugLogOpen) { + logger.info("PullMessageTask_setPolling:" + + Thread.currentThread().getName() + " Polling!"); + } + } + else { + continue; + } + do { + if (debugLogOpen) { + logger.info("PullMessageTask_Keep_Polling" + + Thread.currentThread().getName() + + "KEEP Polling!"); + } + try { + popMsg = queue.popMessage(sleepSecondWhenNoData); + } + catch (ClientException e) { + if (debugLogOpen) { + logger.info( + "PullMessageTask_Pop_Message:ClientException Refresh accessKey" + + e); + } + tokenObject = tokenGetter.getTokenByMessageType( + messageType, queueName, mnsAccountEndpoint); + queue = tokenObject.getQueue(); + + } + catch (ServiceException e) { + if (debugLogOpen) { + logger.info( + "PullMessageTask_Pop_Message:ServiceException Refresh accessKey" + + e); + } + tokenObject = tokenGetter.getTokenByMessageType( + messageType, queueName, mnsAccountEndpoint); + queue = tokenObject.getQueue(); + + } + catch (Exception e) { + if (debugLogOpen) { + logger.info( + "PullMessageTask_Pop_Message:Exception Happened when polling popMessage: " + + e); + } + } + } + while (popMsg == null && isRunning); + clearPolling(queueName); + } + boolean dealResult = messageListener.dealMessage(popMsg); + if (dealResult) { + // remember to delete message when consume message successfully. + if (debugLogOpen) { + logger.info("PullMessageTask_Deal_Message:" + + Thread.currentThread().getName() + "deleteMessage " + + popMsg.getMessageId()); + } + queue.deleteMessage(popMsg.getReceiptHandle()); + } + } + catch (ClientException e) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName, e); + break; + + } + catch (ServiceException e) { + if (e.getErrorCode().equals("AccessDenied")) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName + + ",please check messageType and queueName", e); + } + else { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName, e); + } + break; + + } + catch (com.aliyuncs.exceptions.ClientException e) { + if (e.getErrCode().equals("InvalidAccessKeyId.NotFound")) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName + + ",please check AccessKeyId", e); + } + if (e.getErrCode().equals("SignatureDoesNotMatch")) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName + + ",please check AccessKeySecret", e); + } + else { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName, e); + } + break; + + } + catch (Exception e) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName, e); + try { + Thread.sleep(sleepSecondWhenNoData); + } + catch (InterruptedException e1) { + logger.error("PullMessageTask_execute_error,messageType:" + + messageType + ",queueName:" + queueName, e); + } + } + } + + } + + } + + /** + * @param accessKeyId accessKeyId + * @param accessKeySecret accessKeySecret + * @param messageType 消息类型 + * @param queueName 队列名称 + * @param messageListener 回调的listener,用户自己实现 + * @throws com.aliyuncs.exceptions.ClientException + * @throws ParseException + */ + public void startReceiveMsg(String accessKeyId, String accessKeySecret, + String messageType, String queueName, MessageListener messageListener) + throws com.aliyuncs.exceptions.ClientException, ParseException { + + tokenGetter = new TokenGetterForAlicom(accessKeyId, accessKeySecret, + endpointNameForPop, regionIdForPop, domainForPop, null); + + this.messageListener = messageListener; + isRunning = true; + PullMessageTask task = new PullMessageTask(); + task.messageType = messageType; + task.queueName = queueName; + + synchronized (sLockObjMap) { + lockObj = sLockObjMap.get(queueName); + if (lockObj == null) { + lockObj = new Object(); + sLockObjMap.put(queueName, lockObj); + } + } + + if (executorService == null) { + ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor( + pullMsgThreadSize, + new BasicThreadFactory.Builder() + .namingPattern( + "PullMessageTask-" + messageType + "-thread-pool-%d") + .daemon(true).build()); + executorService = scheduledExecutorService; + } + for (int i = 0; i < pullMsgThreadSize; i++) { + executorService.execute(task); + } + } + + /** + * @param accessKeyId accessKeyId + * @param accessKeySecret accessKeySecret + * @param messageType 消息类型 + * @param queueName 队列名称 + * @param messageListener 回调的listener,用户自己实现 + * @throws com.aliyuncs.exceptions.ClientException + * @throws ParseException + */ + public void startReceiveMsgForVPC(String accessKeyId, String accessKeySecret, + String messageType, String queueName, String regionIdForPop, + String endpointNameForPop, String domainForPop, String mnsAccountEndpoint, + MessageListener messageListener) + throws com.aliyuncs.exceptions.ClientException, ParseException { + this.mnsAccountEndpoint = mnsAccountEndpoint; + tokenGetter = new TokenGetterForAlicom(accessKeyId, accessKeySecret, + endpointNameForPop, regionIdForPop, domainForPop, null); + + this.messageListener = messageListener; + isRunning = true; + PullMessageTask task = new PullMessageTask(); + task.messageType = messageType; + task.queueName = queueName; + + synchronized (sLockObjMap) { + lockObj = sLockObjMap.get(queueName); + if (lockObj == null) { + lockObj = new Object(); + sLockObjMap.put(queueName, lockObj); + } + } + + if (executorService == null) { + ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor( + pullMsgThreadSize, + new BasicThreadFactory.Builder() + .namingPattern( + "PullMessageTask-" + messageType + "-thread-pool-%d") + .daemon(true).build()); + executorService = scheduledExecutorService; + } + for (int i = 0; i < pullMsgThreadSize; i++) { + executorService.execute(task); + } + } + + /** + * 虚商用户定制接收消息方法 + * @param accessKeyId accessKeyId + * @param accessKeySecret accessKeySecret + * @param ownerId 实际的ownerId + * @param messageType 消息类型 + * @param queueName 队列名称 + * @param messageListener 回调listener + * @throws com.aliyuncs.exceptions.ClientException + * @throws ParseException + */ + public void startReceiveMsgForPartnerUser(String accessKeyId, String accessKeySecret, + Long ownerId, String messageType, String queueName, + MessageListener messageListener) + throws com.aliyuncs.exceptions.ClientException, ParseException { + + tokenGetter = new TokenGetterForAlicom(accessKeyId, accessKeySecret, + endpointNameForPop, regionIdForPop, domainForPop, ownerId); + + this.messageListener = messageListener; + isRunning = true; + PullMessageTask task = new PullMessageTask(); + task.messageType = messageType; + task.queueName = queueName; + + synchronized (sLockObjMap) { + lockObj = sLockObjMap.get(queueName); + if (lockObj == null) { + lockObj = new Object(); + sLockObjMap.put(queueName, lockObj); + } + } + + if (executorService == null) { + ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor( + pullMsgThreadSize, + new BasicThreadFactory.Builder() + .namingPattern( + "PullMessageTask-" + messageType + "-thread-pool-%d") + .daemon(true).build()); + executorService = scheduledExecutorService; + } + for (int i = 0; i < pullMsgThreadSize; i++) { + executorService.execute(task); + } + } + + public void stop() { + isRunning = false; + } + +} diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/MessageListener.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/MessageListener.java new file mode 100755 index 00000000..9921232a --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/MessageListener.java @@ -0,0 +1,24 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyun.mns.model.Message; + +public interface MessageListener { + + boolean dealMessage(Message message); + +} 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 new file mode 100644 index 00000000..956ecadb --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueRequest.java @@ -0,0 +1,82 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyuncs.RpcAcsRequest; + +public class QueryTokenForMnsQueueRequest + extends RpcAcsRequest { + private String resourceOwnerAccount; + private String messageType; + private Long resourceOwnerId; + private Long ownerId; + + public QueryTokenForMnsQueueRequest() { + super("Dybaseapi", "2017-05-25", "QueryTokenForMnsQueue"); + } + + public String getResourceOwnerAccount() { + return this.resourceOwnerAccount; + } + + public void setResourceOwnerAccount(String resourceOwnerAccount) { + this.resourceOwnerAccount = resourceOwnerAccount; + if (resourceOwnerAccount != null) { + this.putQueryParameter("ResourceOwnerAccount", resourceOwnerAccount); + } + + } + + public String getMessageType() { + return this.messageType; + } + + public void setMessageType(String messageType) { + this.messageType = messageType; + if (messageType != null) { + this.putQueryParameter("MessageType", messageType); + } + + } + + public Long getResourceOwnerId() { + return this.resourceOwnerId; + } + + public void setResourceOwnerId(Long resourceOwnerId) { + this.resourceOwnerId = resourceOwnerId; + if (resourceOwnerId != null) { + this.putQueryParameter("ResourceOwnerId", resourceOwnerId.toString()); + } + + } + + public Long getOwnerId() { + return this.ownerId; + } + + public void setOwnerId(Long ownerId) { + this.ownerId = ownerId; + if (ownerId != null) { + this.putQueryParameter("OwnerId", ownerId.toString()); + } + + } + + public Class getResponseClass() { + return QueryTokenForMnsQueueResponse.class; + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..56c40171 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponse.java @@ -0,0 +1,117 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyuncs.AcsResponse; +import com.aliyuncs.transform.UnmarshallerContext; + +public class QueryTokenForMnsQueueResponse extends AcsResponse { + private String requestId; + private String code; + private String message; + private QueryTokenForMnsQueueResponse.MessageTokenDTO messageTokenDTO; + + public QueryTokenForMnsQueueResponse() { + } + + public String getRequestId() { + return this.requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getCode() { + return this.code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } + + public QueryTokenForMnsQueueResponse.MessageTokenDTO getMessageTokenDTO() { + return this.messageTokenDTO; + } + + public void setMessageTokenDTO( + QueryTokenForMnsQueueResponse.MessageTokenDTO messageTokenDTO) { + this.messageTokenDTO = messageTokenDTO; + } + + public QueryTokenForMnsQueueResponse getInstance(UnmarshallerContext context) { + return QueryTokenForMnsQueueResponseUnmarshaller.unmarshall(this, context); + } + + public static class MessageTokenDTO { + private String accessKeyId; + private String accessKeySecret; + private String securityToken; + private String createTime; + private String expireTime; + + public MessageTokenDTO() { + } + + public String getAccessKeyId() { + return this.accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return this.accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public String getSecurityToken() { + return this.securityToken; + } + + public void setSecurityToken(String securityToken) { + this.securityToken = securityToken; + } + + public String getCreateTime() { + return this.createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getExpireTime() { + return this.expireTime; + } + + public void setExpireTime(String expireTime) { + this.expireTime = expireTime; + } + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponseUnmarshaller.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponseUnmarshaller.java new file mode 100644 index 00000000..627e8bb5 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/QueryTokenForMnsQueueResponseUnmarshaller.java @@ -0,0 +1,48 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyuncs.transform.UnmarshallerContext; + +public class QueryTokenForMnsQueueResponseUnmarshaller { + + public QueryTokenForMnsQueueResponseUnmarshaller() { + } + + public static QueryTokenForMnsQueueResponse unmarshall( + QueryTokenForMnsQueueResponse queryTokenForMnsQueueResponse, + UnmarshallerContext context) { + queryTokenForMnsQueueResponse.setRequestId( + context.stringValue("QueryTokenForMnsQueueResponse.RequestId")); + queryTokenForMnsQueueResponse + .setCode(context.stringValue("QueryTokenForMnsQueueResponse.Code")); + queryTokenForMnsQueueResponse + .setMessage(context.stringValue("QueryTokenForMnsQueueResponse.Message")); + QueryTokenForMnsQueueResponse.MessageTokenDTO messageTokenDTO = new QueryTokenForMnsQueueResponse.MessageTokenDTO(); + messageTokenDTO.setAccessKeyId(context.stringValue( + "QueryTokenForMnsQueueResponse.MessageTokenDTO.AccessKeyId")); + messageTokenDTO.setAccessKeySecret(context.stringValue( + "QueryTokenForMnsQueueResponse.MessageTokenDTO.AccessKeySecret")); + messageTokenDTO.setSecurityToken(context.stringValue( + "QueryTokenForMnsQueueResponse.MessageTokenDTO.SecurityToken")); + messageTokenDTO.setCreateTime(context + .stringValue("QueryTokenForMnsQueueResponse.MessageTokenDTO.CreateTime")); + messageTokenDTO.setExpireTime(context + .stringValue("QueryTokenForMnsQueueResponse.MessageTokenDTO.ExpireTime")); + queryTokenForMnsQueueResponse.setMessageTokenDTO(messageTokenDTO); + return queryTokenForMnsQueueResponse; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenForAlicom.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenForAlicom.java new file mode 100755 index 00000000..8af1a63c --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenForAlicom.java @@ -0,0 +1,96 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyun.mns.client.CloudQueue; +import com.aliyun.mns.client.MNSClient; + +/** + * 用于接收云通信消息的临时token + * + */ +public class TokenForAlicom { + private String messageType; + private String token; + private Long expireTime; + private String tempAccessKeyId; + private String tempAccessKeySecret; + private MNSClient client; + private CloudQueue queue; + + public String getMessageType() { + return messageType; + } + + public void setMessageType(String messageType) { + this.messageType = messageType; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Long getExpireTime() { + return expireTime; + } + + public void setExpireTime(Long expireTime) { + this.expireTime = expireTime; + } + + public String getTempAccessKeyId() { + return tempAccessKeyId; + } + + public void setTempAccessKeyId(String tempAccessKeyId) { + this.tempAccessKeyId = tempAccessKeyId; + } + + public String getTempAccessKeySecret() { + return tempAccessKeySecret; + } + + public void setTempAccessKeySecret(String tempAccessKeySecret) { + this.tempAccessKeySecret = tempAccessKeySecret; + } + + public MNSClient getClient() { + return client; + } + + public void setClient(MNSClient client) { + this.client = client; + } + + public CloudQueue getQueue() { + return queue; + } + + public void setQueue(CloudQueue queue) { + this.queue = queue; + } + + public void closeClient() { + if (client != null) { + this.client.close(); + } + } + +} 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 new file mode 100755 index 00000000..270f9a56 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/base/TokenGetterForAlicom.java @@ -0,0 +1,142 @@ +/* + * 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.alicloud.sms.base; + +import com.aliyun.mns.client.CloudAccount; +import com.aliyun.mns.client.CloudQueue; +import com.aliyun.mns.client.MNSClient; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.http.FormatType; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.http.ProtocolType; +import com.aliyuncs.profile.DefaultProfile; +import com.aliyuncs.profile.IClientProfile; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 获取接收云通信消息的临时token + * + */ +public class TokenGetterForAlicom { + private Log logger = LogFactory.getLog(TokenGetterForAlicom.class); + private String accessKeyId; + private String accessKeySecret; + private String endpointNameForPop; + private String regionIdForPop; + private String domainForPop; + private IAcsClient iAcsClient; + private Long ownerId; + private final static String productName = "Dybaseapi"; + private long bufferTime = 1000 * 60 * 2;// 过期时间小于2分钟则重新获取,防止服务器时间误差 + private final Object lock = new Object(); + private ConcurrentMap tokenMap = new ConcurrentHashMap(); + + public TokenGetterForAlicom(String accessKeyId, String accessKeySecret, + String endpointNameForPop, String regionIdForPop, String domainForPop, + Long ownerId) throws ClientException { + this.accessKeyId = accessKeyId; + this.accessKeySecret = accessKeySecret; + this.endpointNameForPop = endpointNameForPop; + this.regionIdForPop = regionIdForPop; + this.domainForPop = domainForPop; + this.ownerId = ownerId; + init(); + } + + private void init() throws ClientException { + DefaultProfile.addEndpoint(endpointNameForPop, regionIdForPop, productName, + domainForPop); + IClientProfile profile = DefaultProfile.getProfile(regionIdForPop, accessKeyId, + accessKeySecret); + profile.getHttpClientConfig().setCompatibleMode(true); + iAcsClient = new DefaultAcsClient(profile); + } + + private TokenForAlicom getTokenFromRemote(String messageType) + throws ServerException, ClientException, ParseException { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + df.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); + QueryTokenForMnsQueueRequest request = new QueryTokenForMnsQueueRequest(); + request.setAcceptFormat(FormatType.JSON); + request.setMessageType(messageType); + request.setOwnerId(ownerId); + request.setProtocol(ProtocolType.HTTPS); + request.setMethod(MethodType.POST); + QueryTokenForMnsQueueResponse response = iAcsClient.getAcsResponse(request); + String resultCode = response.getCode(); + if (resultCode != null && "OK".equals(resultCode)) { + QueryTokenForMnsQueueResponse.MessageTokenDTO dto = response + .getMessageTokenDTO(); + TokenForAlicom token = new TokenForAlicom(); + String timeStr = dto.getExpireTime(); + token.setMessageType(messageType); + token.setExpireTime(df.parse(timeStr).getTime()); + token.setToken(dto.getSecurityToken()); + token.setTempAccessKeyId(dto.getAccessKeyId()); + token.setTempAccessKeySecret(dto.getAccessKeySecret()); + return token; + } + else { + logger.error("getTokenFromRemote_error,messageType:" + messageType + ",code:" + + response.getCode() + ",message:" + response.getMessage()); + throw new ServerException(response.getCode(), response.getMessage()); + } + } + + public TokenForAlicom getTokenByMessageType(String messageType, String queueName, + String mnsAccountEndpoint) + throws ServerException, ClientException, ParseException { + TokenForAlicom token = tokenMap.get(messageType); + Long now = System.currentTimeMillis(); + if (token == null || (token.getExpireTime() - now) < bufferTime) {// 过期时间小于2分钟则重新获取,防止服务器时间误差 + synchronized (lock) { + token = tokenMap.get(messageType); + if (token == null || (token.getExpireTime() - now) < bufferTime) { + TokenForAlicom oldToken = null; + if (token != null) { + oldToken = token; + } + token = getTokenFromRemote(messageType); + // 因为换token时需要重建client和关闭老的client,所以创建client的代码和创建token放在一起 + CloudAccount account = new CloudAccount(token.getTempAccessKeyId(), + token.getTempAccessKeySecret(), mnsAccountEndpoint, + token.getToken()); + // logger.warn("ak:"+token.getTempAccessKey()); + // logger.warn("token:"+token.getToken()); + MNSClient client = account.getMNSClient(); + CloudQueue queue = client.getQueueRef(queueName); + token.setClient(client); + token.setQueue(queue); + tokenMap.put(messageType, token); + if (oldToken != null) { + oldToken.closeClient(); + } + } + } + } + return token; + } +} diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/config/SmsAutoConfiguration.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/config/SmsAutoConfiguration.java new file mode 100644 index 00000000..12e1760b --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/config/SmsAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * 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.alicloud.sms.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alicloud.context.sms.SmsConfigProperties; +import org.springframework.cloud.alicloud.sms.ISmsService; +import org.springframework.cloud.alicloud.sms.SmsInitializerEventListener; +import org.springframework.cloud.alicloud.sms.SmsServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; + +/** + * @author pbting + */ +@Configuration +@EnableConfigurationProperties +@ConditionalOnClass(value = SendSmsRequest.class) +@ConditionalOnProperty(value = "spring.cloud.alicloud.sms.enable", matchIfMissing = true) +public class SmsAutoConfiguration { + + @Bean + public SmsServiceImpl smsService(SmsConfigProperties smsConfigProperties) { + return new SmsServiceImpl(smsConfigProperties); + } + + @Bean + public SmsInitializerEventListener smsInitializePostListener( + SmsConfigProperties msConfigProperties, ISmsService smsService) { + return new SmsInitializerEventListener(msConfigProperties, smsService); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/EndpointManager.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/EndpointManager.java new file mode 100644 index 00000000..3b807d08 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/EndpointManager.java @@ -0,0 +1,106 @@ +/* + * 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.alicloud.sms.endpoint; + +import com.aliyuncs.dysmsapi.model.v20170525.SendBatchSmsRequest; +import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.locks.ReentrantLock; + +/** + * + */ +public final class EndpointManager { + + private final static int BACKLOG_SIZE = 20; + + private final static ReentrantLock SEND_REENTRANT_LOCK = new ReentrantLock(true); + private final static ReentrantLock SEND_BATCH_REENTRANT_LOCK = new ReentrantLock( + true); + + private final static LinkedBlockingQueue SEND_SMS_REQUESTS = new LinkedBlockingQueue( + BACKLOG_SIZE); + private final static LinkedBlockingQueue SEND_BATCH_SMS_REQUESTS = new LinkedBlockingQueue( + BACKLOG_SIZE); + private final static LinkedBlockingQueue RECEIVE_MESSAGE_ENTITIES = new LinkedBlockingQueue( + BACKLOG_SIZE); + + public static void addSendSmsRequest(SendSmsRequest sendSmsRequest) { + if (SEND_SMS_REQUESTS.offer(sendSmsRequest)) { + return; + } + try { + SEND_REENTRANT_LOCK.lock(); + SEND_SMS_REQUESTS.poll(); + SEND_SMS_REQUESTS.offer(sendSmsRequest); + } + finally { + SEND_REENTRANT_LOCK.unlock(); + } + } + + public static void addSendBatchSmsRequest(SendBatchSmsRequest sendBatchSmsRequest) { + if (SEND_BATCH_SMS_REQUESTS.offer(sendBatchSmsRequest)) { + return; + } + try { + SEND_BATCH_REENTRANT_LOCK.lock(); + SEND_BATCH_SMS_REQUESTS.poll(); + SEND_BATCH_SMS_REQUESTS.offer(sendBatchSmsRequest); + } + finally { + SEND_BATCH_REENTRANT_LOCK.unlock(); + } + } + + public static void addReceiveMessageEntity( + ReceiveMessageEntity receiveMessageEntity) { + if (RECEIVE_MESSAGE_ENTITIES.offer(receiveMessageEntity)) { + return; + } + RECEIVE_MESSAGE_ENTITIES.poll(); + RECEIVE_MESSAGE_ENTITIES.offer(receiveMessageEntity); + } + + public static Map getSmsEndpointMessage() { + List sendSmsRequests = new LinkedList<>(); + List sendBatchSmsRequests = new LinkedList<>(); + List receiveMessageEntities = new LinkedList<>(); + try { + SEND_REENTRANT_LOCK.lock(); + SEND_BATCH_REENTRANT_LOCK.lock(); + sendSmsRequests.addAll(SEND_SMS_REQUESTS); + sendBatchSmsRequests.addAll(SEND_BATCH_SMS_REQUESTS); + } + finally { + SEND_REENTRANT_LOCK.unlock(); + SEND_BATCH_REENTRANT_LOCK.unlock(); + } + receiveMessageEntities.addAll(RECEIVE_MESSAGE_ENTITIES); + + Map endpointMessages = new HashMap<>(); + endpointMessages.put("send-sms-request", sendSmsRequests); + endpointMessages.put("send-batch-sms-request", sendBatchSmsRequests); + endpointMessages.put("message-listener", receiveMessageEntities); + + return endpointMessages; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/ReceiveMessageEntity.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/ReceiveMessageEntity.java new file mode 100644 index 00000000..3e941c3c --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/ReceiveMessageEntity.java @@ -0,0 +1,60 @@ +/* + * 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.alicloud.sms.endpoint; + +import org.springframework.cloud.alicloud.sms.base.MessageListener; + +import java.io.Serializable; + +/** + * @author pbting + */ +public class ReceiveMessageEntity implements Serializable { + private String messageType; + private String queueName; + private MessageListener messageListener; + + public ReceiveMessageEntity(String messageType, String queueName, + MessageListener messageListener) { + this.messageType = messageType; + this.queueName = queueName; + this.messageListener = messageListener; + } + + public String getMessageType() { + return messageType; + } + + public void setMessageType(String messageType) { + this.messageType = messageType; + } + + public String getQueueName() { + return queueName; + } + + public void setQueueName(String queueName) { + this.queueName = queueName; + } + + public MessageListener getMessageListener() { + return messageListener; + } + + public void setMessageListener(MessageListener messageListener) { + this.messageListener = messageListener; + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpoint.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpoint.java new file mode 100644 index 00000000..d254e0eb --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpoint.java @@ -0,0 +1,31 @@ +/* + * 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.alicloud.sms.endpoint; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; + +import java.util.Map; + +@Endpoint(id = "sms-info") +public class SmsEndpoint { + + @ReadOperation + public Map invoke() { + + return EndpointManager.getSmsEndpointMessage(); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpointAutoConfiguration.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpointAutoConfiguration.java new file mode 100644 index 00000000..1db0ec25 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpointAutoConfiguration.java @@ -0,0 +1,31 @@ +/* + * 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.alicloud.sms.endpoint; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; + +@ConditionalOnWebApplication +@ConditionalOnClass(Endpoint.class) +public class SmsEndpointAutoConfiguration { + + @Bean + public SmsEndpoint smsEndpoint() { + return new SmsEndpoint(); + } +} \ No newline at end of file diff --git a/spring-cloud-alicloud-sms/src/main/resources/META-INF/spring.factories b/spring-cloud-alicloud-sms/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..875d39db --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.alicloud.sms.config.SmsAutoConfiguration,\ + org.springframework.cloud.alicloud.sms.endpoint.SmsEndpointAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-starter-alibaba/pom.xml b/spring-cloud-starter-alibaba/pom.xml index bacdefff..3c91f8d7 100644 --- a/spring-cloud-starter-alibaba/pom.xml +++ b/spring-cloud-starter-alibaba/pom.xml @@ -19,4 +19,26 @@ spring-cloud-starter-stream-rocketmq spring-cloud-starter-bus-rocketmq + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + \ No newline at end of file diff --git a/spring-cloud-starter-alicloud/pom.xml b/spring-cloud-starter-alicloud/pom.xml index 5427b6ec..3684464e 100644 --- a/spring-cloud-starter-alicloud/pom.xml +++ b/spring-cloud-starter-alicloud/pom.xml @@ -15,5 +15,28 @@ spring-cloud-starter-alicloud-acm spring-cloud-starter-alicloud-ans spring-cloud-starter-alicloud-schedulerx + spring-cloud-starter-alicloud-sms + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + org.codehaus.mojo + cobertura-maven-plugin + false + + true + + + + + \ No newline at end of file diff --git a/spring-cloud-starter-alicloud/spring-cloud-starter-alicloud-sms/pom.xml b/spring-cloud-starter-alicloud/spring-cloud-starter-alicloud-sms/pom.xml new file mode 100644 index 00000000..57a398f7 --- /dev/null +++ b/spring-cloud-starter-alicloud/spring-cloud-starter-alicloud-sms/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + + org.springframework.cloud + spring-cloud-starter-alicloud + 0.2.2.BUILD-SNAPSHOT + + spring-cloud-starter-alicloud-sms + Spring Cloud Starter Alibaba Cloud SMS + + + + org.springframework.cloud + spring-cloud-alicloud-sms + + + + 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 ded2be10..1321c84b 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 @@ -92,6 +92,9 @@ public class RocketMQMessageChannelBinder extends TransactionCheckListener.class)); } + if (errorChannel != null) { + messageHandler.setSendFailureChannel(errorChannel); + } return messageHandler; } else { @@ -107,7 +110,7 @@ public class RocketMQMessageChannelBinder extends throws Exception { if (group == null || "".equals(group)) { throw new RuntimeException( - "'group must be configured for channel + " + destination.getName()); + "'group must be configured for channel " + destination.getName()); } RocketMQInboundChannelAdapter rocketInboundChannelAdapter = new RocketMQInboundChannelAdapter( diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/exception/RocketMQSendFailureException.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/exception/RocketMQSendFailureException.java new file mode 100644 index 00000000..d8d7fc5d --- /dev/null +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/exception/RocketMQSendFailureException.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 + * + * 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.stream.binder.rocketmq.exception; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; + +/** + * An exception that is the payload of an {@code ErrorMessage} when occurs send failure. + * + * @author Jim + * @since 0.2.2 + */ +public class RocketMQSendFailureException extends MessagingException { + + private final org.apache.rocketmq.common.message.Message rocketmqMsg; + + public RocketMQSendFailureException(Message message, + org.apache.rocketmq.common.message.Message rocketmqMsg, Throwable cause) { + super(message, cause); + this.rocketmqMsg = rocketmqMsg; + } + + public org.apache.rocketmq.common.message.Message getRocketmqMsg() { + return rocketmqMsg; + } + + @Override + public String toString() { + return super.toString() + " [rocketmqMsg=" + this.rocketmqMsg + "]"; + } + +} diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQInboundChannelAdapter.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQInboundChannelAdapter.java index 7b70c067..dd4c67b1 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQInboundChannelAdapter.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQInboundChannelAdapter.java @@ -16,14 +16,6 @@ package org.springframework.cloud.stream.binder.rocketmq.integration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - import org.apache.commons.lang3.ClassUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; @@ -53,8 +45,17 @@ import org.springframework.retry.RetryCallback; import org.springframework.retry.RetryContext; import org.springframework.retry.RetryListener; import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + /** * @author Jim */ @@ -71,6 +72,10 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { private RecoveryCallback recoveryCallback; + private DefaultMQPushConsumer consumer; + + private CloudStreamMessageListener listener; + private final ExtendedConsumerProperties consumerProperties; private final String destination; @@ -90,6 +95,31 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { this.instrumentationManager = instrumentationManager; } + @Override + protected void onInit() { + if (consumerProperties == null + || !consumerProperties.getExtension().getEnabled()) { + return; + } + super.onInit(); + if (this.retryTemplate != null) { + Assert.state(getErrorChannel() == null, + "Cannot have an 'errorChannel' property when a 'RetryTemplate' is " + + "provided; use an 'ErrorMessageSendingRecoverer' in the 'recoveryCallback' property to " + + "send an error message when retries are exhausted"); + } + this.consumer = consumersManager.getOrCreateConsumer(group, destination, + consumerProperties); + + Boolean isOrderly = consumerProperties.getExtension().getOrderly(); + this.listener = isOrderly ? new CloudStreamMessageListenerOrderly() + : new CloudStreamMessageListenerConcurrently(); + + if (retryTemplate != null) { + this.retryTemplate.registerListener(this.listener); + } + } + @Override protected void doStart() { if (consumerProperties == null @@ -98,18 +128,6 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { } String tags = consumerProperties.getExtension().getTags(); - Boolean isOrderly = consumerProperties.getExtension().getOrderly(); - - DefaultMQPushConsumer consumer = consumersManager.getOrCreateConsumer(group, - destination, consumerProperties); - - final CloudStreamMessageListener listener = isOrderly - ? new CloudStreamMessageListenerOrderly() - : new CloudStreamMessageListenerConcurrently(); - - if (retryTemplate != null) { - retryTemplate.registerListener(listener); - } Set tagsSet = tags == null ? new HashSet<>() : Arrays.stream(tags.split("\\|\\|")).map(String::trim) @@ -122,11 +140,11 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { try { if (!StringUtils.isEmpty(consumerProperties.getExtension().getSql())) { - consumer.subscribe(destination, MessageSelector + this.consumer.subscribe(destination, MessageSelector .bySql(consumerProperties.getExtension().getSql())); } else { - consumer.subscribe(destination, String.join(" || ", tagsSet)); + this.consumer.subscribe(destination, String.join(" || ", tagsSet)); } Optional.ofNullable(consumerInstrumentation) .ifPresent(c -> c.markStartedSuccessfully()); @@ -139,7 +157,7 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { throw new RuntimeException("RocketMQ Consumer hasn't been subscribed.", e); } - consumer.registerMessageListener(listener); + this.consumer.registerMessageListener(this.listener); try { consumersManager.startConsumer(group); @@ -214,10 +232,8 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { RocketMQInboundChannelAdapter.this.destination) .markConsumedFailure(); }); - throw new RuntimeException( - "RocketMQ Message hasn't been processed successfully. Caused by ", - e); } + return null; } private Acknowledgement doSendMsgs(final List msgs, @@ -229,18 +245,24 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { logger.debug(retryInfo + "consuming msg:\n" + msg); logger.debug(retryInfo + "message body:\n" + new String(msg.getBody())); Acknowledgement acknowledgement = new Acknowledgement(); - Message toChannel = MessageBuilder.withPayload(msg.getBody()) - .setHeaders(new RocketMQMessageHeaderAccessor() - .withAcknowledgment(acknowledgement) - .withTags(msg.getTags()).withKeys(msg.getKeys()) - .withFlag(msg.getFlag()).withRocketMessage(msg)) - .build(); + Message toChannel = convertMessagingFromRocketMQMsg(msg, + acknowledgement); acknowledgements.add(acknowledgement); RocketMQInboundChannelAdapter.this.sendMessage(toChannel); }); return acknowledgements.get(0); } + private Message convertMessagingFromRocketMQMsg(MessageExt msg, + Acknowledgement acknowledgement) { + return MessageBuilder.withPayload(msg.getBody()) + .setHeaders(new RocketMQMessageHeaderAccessor() + .withAcknowledgment(acknowledgement).withTags(msg.getTags()) + .withKeys(msg.getKeys()).withFlag(msg.getFlag()) + .withRocketMessage(msg)) + .build(); + } + @Override public boolean open(RetryContext context, RetryCallback callback) { @@ -283,9 +305,16 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { public ConsumeConcurrentlyStatus consumeMessage(final List msgs, ConsumeConcurrentlyContext context) { Acknowledgement acknowledgement = consumeMessage(msgs); - context.setDelayLevelWhenNextConsume( - acknowledgement.getConsumeConcurrentlyDelayLevel()); - return acknowledgement.getConsumeConcurrentlyStatus(); + if (acknowledgement != null) { + context.setDelayLevelWhenNextConsume( + acknowledgement.getConsumeConcurrentlyDelayLevel()); + return acknowledgement.getConsumeConcurrentlyStatus(); + } + else { + context.setDelayLevelWhenNextConsume(consumerProperties.getExtension() + .getError().getDelayLevelWhenNextConsume()); + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } } } @@ -296,11 +325,17 @@ public class RocketMQInboundChannelAdapter extends MessageProducerSupport { public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { Acknowledgement acknowledgement = consumeMessage(msgs); - context.setSuspendCurrentQueueTimeMillis( - (acknowledgement.getConsumeOrderlySuspendCurrentQueueTimeMill())); - return acknowledgement.getConsumeOrderlyStatus(); + if (acknowledgement != null) { + context.setSuspendCurrentQueueTimeMillis( + (acknowledgement.getConsumeOrderlySuspendCurrentQueueTimeMill())); + return acknowledgement.getConsumeOrderlyStatus(); + } + else { + context.setSuspendCurrentQueueTimeMillis(consumerProperties.getExtension() + .getError().getSuspendCurrentQueueTimeMillis()); + return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; + } } - } } diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageHandler.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageHandler.java index ba8054ad..f2dd937c 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageHandler.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/integration/RocketMQMessageHandler.java @@ -16,10 +16,6 @@ package org.springframework.cloud.stream.binder.rocketmq.integration; -import java.time.Instant; -import java.util.Map; -import java.util.Optional; - import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; @@ -33,20 +29,32 @@ import org.apache.rocketmq.remoting.exception.RemotingException; import org.springframework.cloud.stream.binder.ExtendedProducerProperties; import org.springframework.cloud.stream.binder.rocketmq.RocketMQBinderConstants; import org.springframework.cloud.stream.binder.rocketmq.RocketMQMessageHeaderAccessor; +import org.springframework.cloud.stream.binder.rocketmq.exception.RocketMQSendFailureException; import org.springframework.cloud.stream.binder.rocketmq.metrics.InstrumentationManager; import org.springframework.cloud.stream.binder.rocketmq.metrics.ProducerInstrumentation; import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQBinderConfigurationProperties; import org.springframework.cloud.stream.binder.rocketmq.properties.RocketMQProducerProperties; import org.springframework.context.Lifecycle; import org.springframework.integration.handler.AbstractMessageHandler; +import org.springframework.integration.support.DefaultErrorMessageStrategy; +import org.springframework.integration.support.ErrorMessageStrategy; import org.springframework.integration.support.MutableMessage; +import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.util.Assert; + +import java.time.Instant; +import java.util.Map; +import java.util.Optional; /** * @author Jim */ public class RocketMQMessageHandler extends AbstractMessageHandler implements Lifecycle { + private ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy(); + private DefaultMQProducer producer; private ProducerInstrumentation producerInstrumentation; @@ -57,6 +65,8 @@ public class RocketMQMessageHandler extends AbstractMessageHandler implements Li private TransactionCheckListener transactionCheckListener; + private MessageChannel sendFailureChannel; + private final ExtendedProducerProperties producerProperties; private final String destination; @@ -88,6 +98,9 @@ public class RocketMQMessageHandler extends AbstractMessageHandler implements Li producer = new DefaultMQProducer(destination); } + producer.setVipChannelEnabled( + producerProperties.getExtension().getVipChannelEnabled()); + Optional.ofNullable(instrumentationManager).ifPresent(manager -> { producerInstrumentation = manager.getProducerInstrumentation(destination); manager.addHealthInstrumentation(producerInstrumentation); @@ -131,8 +144,8 @@ public class RocketMQMessageHandler extends AbstractMessageHandler implements Li @Override protected void handleMessageInternal(org.springframework.messaging.Message message) throws Exception { + Message toSend = null; try { - Message toSend; if (message.getPayload() instanceof byte[]) { toSend = new Message(destination, (byte[]) message.getPayload()); } @@ -166,7 +179,13 @@ public class RocketMQMessageHandler extends AbstractMessageHandler implements Li } if (!sendRes.getSendStatus().equals(SendStatus.SEND_OK)) { - throw new MQClientException("message hasn't been sent", null); + if (getSendFailureChannel() != null) { + this.getSendFailureChannel().send(message); + } + else { + throw new RocketMQSendFailureException(message, toSend, + new MQClientException("message hasn't been sent", null)); + } } if (message instanceof MutableMessage) { RocketMQMessageHeaderAccessor.putSendResult((MutableMessage) message, @@ -184,18 +203,60 @@ public class RocketMQMessageHandler extends AbstractMessageHandler implements Li .ifPresent(p -> p.markSentFailure()); logger.error( "RocketMQ Message hasn't been sent. Caused by " + e.getMessage()); - throw new MessagingException(e.getMessage(), e); + if (getSendFailureChannel() != null) { + getSendFailureChannel().send(this.errorMessageStrategy.buildErrorMessage( + new RocketMQSendFailureException(message, toSend, e), null)); + } + else { + throw new RocketMQSendFailureException(message, toSend, e); + } } } + /** + * Using in RocketMQ Transactional Mode. Set RocketMQ localTransactionExecuter in + * {@link DefaultMQProducer#sendMessageInTransaction}. + * @param localTransactionExecuter the executer running when produce msg. + */ public void setLocalTransactionExecuter( LocalTransactionExecuter localTransactionExecuter) { this.localTransactionExecuter = localTransactionExecuter; } + /** + * Using in RocketMQ Transactional Mode. Set RocketMQ transactionCheckListener in + * {@link TransactionMQProducer#setTransactionCheckListener}. + * @param transactionCheckListener the listener set in {@link TransactionMQProducer}. + */ public void setTransactionCheckListener( TransactionCheckListener transactionCheckListener) { this.transactionCheckListener = transactionCheckListener; } + + /** + * Set the failure channel. After a send failure, an {@link ErrorMessage} will be sent + * to this channel with a payload of a {@link RocketMQSendFailureException} with the + * failed message and cause. + * @param sendFailureChannel the failure channel. + * @since 0.2.2 + */ + public void setSendFailureChannel(MessageChannel sendFailureChannel) { + this.sendFailureChannel = sendFailureChannel; + } + + /** + * Set the error message strategy implementation to use when sending error messages + * after send failures. Cannot be null. + * @param errorMessageStrategy the implementation. + * @since 0.2.2 + */ + public void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) { + Assert.notNull(errorMessageStrategy, "'errorMessageStrategy' cannot be null"); + this.errorMessageStrategy = errorMessageStrategy; + } + + public MessageChannel getSendFailureChannel() { + return sendFailureChannel; + } } \ No newline at end of file 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 6343f153..8202fefb 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 @@ -52,6 +52,40 @@ public class RocketMQConsumerProperties { private Boolean enabled = true; + private Error error; + + public static class Error { + + /** + * Reconsume later timeMillis in ConsumeOrderlyContext. + */ + private Long suspendCurrentQueueTimeMillis = 1000L; + + /** + * Message consume retry strategy in ConsumeConcurrentlyContext. + * + * -1,no retry,put into DLQ directly 0,broker control retry frequency >0,client + * control retry frequency + */ + private Integer delayLevelWhenNextConsume = 0; + + public Long getSuspendCurrentQueueTimeMillis() { + return suspendCurrentQueueTimeMillis; + } + + public void setSuspendCurrentQueueTimeMillis(Long suspendCurrentQueueTimeMillis) { + this.suspendCurrentQueueTimeMillis = suspendCurrentQueueTimeMillis; + } + + public Integer getDelayLevelWhenNextConsume() { + return delayLevelWhenNextConsume; + } + + public void setDelayLevelWhenNextConsume(Integer delayLevelWhenNextConsume) { + this.delayLevelWhenNextConsume = delayLevelWhenNextConsume; + } + } + public String getTags() { return tags; } @@ -91,4 +125,12 @@ public class RocketMQConsumerProperties { public void setBroadcasting(Boolean broadcasting) { this.broadcasting = broadcasting; } + + public Error getError() { + return error; + } + + public void setError(Error error) { + this.error = error; + } } diff --git a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQProducerProperties.java b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQProducerProperties.java index 1a05ad50..b526d960 100644 --- a/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQProducerProperties.java +++ b/spring-cloud-stream-binder-rocketmq/src/main/java/org/springframework/cloud/stream/binder/rocketmq/properties/RocketMQProducerProperties.java @@ -45,6 +45,8 @@ public class RocketMQProducerProperties { */ private String transactionCheckListener; + private Boolean vipChannelEnabled = true; + public Boolean getEnabled() { return enabled; } @@ -84,4 +86,12 @@ public class RocketMQProducerProperties { public void setTransactionCheckListener(String transactionCheckListener) { this.transactionCheckListener = transactionCheckListener; } + + public Boolean getVipChannelEnabled() { + return vipChannelEnabled; + } + + public void setVipChannelEnabled(Boolean vipChannelEnabled) { + this.vipChannelEnabled = vipChannelEnabled; + } }