diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 13166321..e955b57b 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -26,6 +26,9 @@ 2.16.0 4.3.1 2.1.6 + 1.1.0 + 1.1.8 + 1.1.0 @@ -153,6 +156,18 @@ ${oss.version} + + + com.aliyun.mns + aliyun-sdk-mns + ${aliyun.sdk.mns} + + + + com.aliyun + aliyun-java-sdk-dysmsapi + ${aliyun.java.sdk.dysmsapi} + @@ -195,6 +210,11 @@ spring-cloud-alicloud-schedulerx ${project.version} + + org.springframework.cloud + spring-cloud-alicloud-sms + ${project.version} + org.springframework.cloud spring-cloud-alicloud-context @@ -248,6 +268,12 @@ ${project.version} + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + ${project.version} + + org.springframework.cloud spring-cloud-starter-stream-rocketmq diff --git a/spring-cloud-alibaba-examples/pom.xml b/spring-cloud-alibaba-examples/pom.xml index 3e7894dc..0eca5b82 100644 --- a/spring-cloud-alibaba-examples/pom.xml +++ b/spring-cloud-alibaba-examples/pom.xml @@ -28,6 +28,7 @@ ans-example/ans-provider-example acm-example/acm-local-example rocketmq-example + sms-example spring-cloud-bus-rocketmq-example schedulerx-example/schedulerx-simple-task-example 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..c10808ff --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/pom.xml @@ -0,0 +1,51 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba-examples + 0.1.2.BUILD-SNAPSHOT + + + 4.0.0 + + alibaba.com + sms-example + jar + + sms-example + Demo project for Spring Boot + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-alicloud-sms + + + com.google.code.gson + gson + 2.8.2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + 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..087ab0b0 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsApplication.java @@ -0,0 +1,13 @@ +package org.springframework.cloud.alibaba.cloud.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SmsApplication { + + public static void main(String[] args) { + + 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/SmsCode.java b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsCode.java new file mode 100644 index 00000000..49312616 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsCode.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * @author pbting + * @date 2019-01-09 2:12 PM + */ +public class SmsCode { + + private String code; + + public SmsCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } +} \ 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..4585a173 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsController.java @@ -0,0 +1,133 @@ +package org.springframework.cloud.alibaba.cloud.example; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.alicloud.sms.ISmsService; +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; +import com.google.gson.Gson; + +@RestController +public class SmsController { + + @Autowired + private ISmsService smsService; + + @Autowired + private SmsReportMessageListener smsReportMessageListener; + + /** + * 短信发送 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("[\"****\",\"****\",\"****\"]"); + // 必填:短信签名-支持不同的号码发送不同的短信签名 + request.setSignNameJson("[\"企业级分布式应用服务\",\"企业级分布式应用服务\",\"企业级分布式应用服务\"]"); + // 必填:短信模板-可在短信控制台中找到 + request.setTemplateCode("*****"); + // 必填:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 + // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败 + List smsCodes = new ArrayList<>(); + smsCodes.add(new SmsCode(code)); + smsCodes.add(new SmsCode(code)); + smsCodes.add(new SmsCode(code)); + request.setTemplateParamJson(new Gson().toJson(smsCodes)); + // 可选-上行短信扩展码(扩展码字段控制在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..4f63c15c --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsReportMessageListener.java @@ -0,0 +1,29 @@ +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..94afb88b --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/java/org/springframework/cloud/alibaba/cloud/example/SmsUpMessageListener.java @@ -0,0 +1,20 @@ +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..cc0931c9 --- /dev/null +++ b/spring-cloud-alibaba-examples/sms-example/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.application.name=sca-sms-example +server.port=9051 +# config management +#config sms +spring.cloud.alicloud.access-key=***** +spring.cloud.alicloud.secret-key=****** +spring.cloud.alicloud.sms.report-queue-name=***** +spring.cloud.alicloud.sms.up-queue-name=***** +#config endpoint +management.port=19391 +management.context-path=/test +management.security.enabled=false \ No newline at end of file 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 52e35309..144520fc 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.context.embedded.EmbeddedServletContainerInitializedEvent") @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 34bcb64c..96d6d264 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,42 +16,33 @@ package org.springframework.cloud.alicloud.ans; -import java.util.*; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.alicloud.context.ans.AnsProperties; -import org.springframework.cloud.client.DefaultServiceInstance; +import com.alibaba.ans.core.NamingService; +import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; +import org.springframework.cloud.alicloud.ans.registry.AnsRegistration; 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.*; /** * @author xiaolongzuo + * @author pbting */ public class AnsDiscoveryClient implements DiscoveryClient { public static final String DESCRIPTION = "Spring Cloud ANS Discovery Client"; - @Autowired - private AnsProperties ansProperties; + private AnsRegistration ansRegistration; + + public AnsDiscoveryClient(AnsRegistration ansRegistration) { + this.ansRegistration = ansRegistration; + } @Override public String description() { return DESCRIPTION; } - @Override - public ServiceInstance getLocalServiceInstance() { - String serviceId = ansProperties.getClientDomains(); - String host = ansProperties.getClientIp(); - int port = ansProperties.getClientPort(); - boolean secure = ansProperties.isSecure(); - Map metadata = ansProperties.getClientMetadata(); - return new DefaultServiceInstance(serviceId, host, port, secure, metadata); - } - @Override public List getInstances(String serviceId) { try { @@ -91,8 +82,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); @@ -100,4 +92,8 @@ public class AnsDiscoveryClient implements DiscoveryClient { return result; } + @Override + public ServiceInstance getLocalServiceInstance() { + return ansRegistration; + } } 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..99118791 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,23 +19,28 @@ 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.alicloud.ans.registry.AnsRegistration; 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 + * @author pbting */ @Configuration +@Conditional(MigrateOnConditionMissingClass.class) @ConditionalOnMissingBean(DiscoveryClient.class) @EnableConfigurationProperties @AutoConfigureBefore(SimpleDiscoveryClientAutoConfiguration.class) public class AnsDiscoveryClientAutoConfiguration { @Bean - public DiscoveryClient ansDiscoveryClient() { - return new AnsDiscoveryClient(); + public DiscoveryClient ansDiscoveryClient(AnsRegistration ansRegistration) { + return new AnsDiscoveryClient(ansRegistration); } } 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 77314748..7a35ef6f 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,25 +16,25 @@ package org.springframework.cloud.alicloud.ans.endpoint; +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.AbstractEndpoint; +import org.springframework.cloud.alicloud.context.ans.AnsProperties; + 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 org.springframework.boot.actuate.endpoint.AbstractEndpoint; -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; - /** * @author xiaolongzuo + * @author pbting */ public class AnsEndpoint extends AbstractEndpoint> { - private static final Logger LOGGER = LoggerFactory.getLogger(AnsEndpoint.class); + private static final Log log = LogFactory.getLog(AnsEndpoint.class); private AnsProperties ansProperties; @@ -49,7 +49,7 @@ public class AnsEndpoint extends AbstractEndpoint> { @Override 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<>(); @@ -64,7 +64,7 @@ public class AnsEndpoint extends AbstractEndpoint> { } } 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/endpoint/AnsEndpointAutoConfiguration.java b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpointAutoConfiguration.java index 62883e9a..033f1438 100644 --- a/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpointAutoConfiguration.java +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/endpoint/AnsEndpointAutoConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.alicloud.ans.endpoint; +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.cloud.alicloud.context.ans.AnsProperties; @@ -25,7 +26,7 @@ import org.springframework.context.annotation.Bean; * @author xiaolongzuo */ @ConditionalOnWebApplication -@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.AbstractEndpoint") +@ConditionalOnClass(AbstractEndpoint.class) public class AnsEndpointAutoConfiguration { @Bean 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..53d559a1 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpoint.java @@ -0,0 +1,34 @@ +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.AbstractEndpoint; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * @author pbting + */ +public class MigrateEndpoint + extends AbstractEndpoint>> { + + private static final Log log = LogFactory.getLog(MigrateEndpoint.class); + + public MigrateEndpoint() { + super("migrate"); + } + + /** + * @return ans endpoint + */ + @Override + 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..b3d1e2ef --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateEndpointAutoConfiguration.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +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; + +/** + * @author pbting + */ +@ConditionalOnWebApplication +@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint") +@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..4dc84ae3 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnCondition.java @@ -0,0 +1,48 @@ +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..e4c83284 --- /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(" matche result 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..2a93d842 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateOnConditionMissingClass.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 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(" matche result 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..06f673fb --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateProxyManager.java @@ -0,0 +1,102 @@ +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.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.ProxyFactory; + +import java.util.*; +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); + for (Iterator iterator = adviceList.iterator(); iterator.hasNext();) { + proxyFactory.addAdvice(iterator.next()); + } + return proxyFactory.getProxy(classLoader); + } + + static Object newServerListProxy(Object bean, ClassLoader classLoader, + IClientConfig clientConfig) { + List adviceLists = new LinkedList<>(); + adviceLists.add(new ServerListInvocationHandler(clientConfig)); + bean = springProxyFactory(bean, classLoader, adviceLists, + 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) { + // step 1: initializer a advice for after returning advice + final List adviceLists = new LinkedList<>(); + adviceLists.add(new MethodInterceptor() { + private final IClientConfig iclientConfig = clientConfig; + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + Object returnValue = methodInvocation.proceed(); + String methodName = methodInvocation.getMethod().getName(); + if (!"chooseServer".equals(methodName)) { + return returnValue; + } + + String serviceId = iclientConfig.getClientName(); + Server server = ServerListInvocationHandler + .checkAndGetServiceServer(serviceId, (Server) returnValue); + ServerListInvocationHandler.incrementCallService(serviceId, server); + return server; + } + }); + // step 2: new proxy instance by spring factory + bean = springProxyFactory(bean, classLoader, adviceLists, + 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..3ac48922 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRefreshEventListener.java @@ -0,0 +1,95 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.loadbalancer.ILoadBalancer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.Iterator; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author pbting + */ +@Component +public class MigrateRefreshEventListener implements ApplicationListener { + private final static Log log = LogFactory.getLog(MigrateRefreshEventListener.class); + + private final static String MIGRATE_SWITCH = "sca.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(new Runnable() { + @Override + public void run() { + try { + onApplicationEvent(null); + } + catch (Exception e) { + log.error( + "check the value of 'sca.migrate.ans.switch' in environment whether changed or not cause an Exeption", + e); + } + } + }, 1, 1, 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 + Set serviceIds = MigrateProxyManager.getServicesId(); + for (Iterator iterator = serviceIds.iterator(); iterator.hasNext();) { + namedContextFactory.getInstance(iterator.next(), 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..df246f18 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateRibbonBeanPostProcessor.java @@ -0,0 +1,62 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +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; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.ServerList; + +/** + * @author pbting + */ +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 Object postProcessBeforeInitialization(Object o, String s) + throws BeansException { + return o; + } + + @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..b6c709ca --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrateServiceRegistry.java @@ -0,0 +1,68 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +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; + +/** + * @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(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent event) { + String serverPort = event.getApplicationContext().getEnvironment() + .getProperty("server.port"); + if (null != serverPort && serverPort.trim().length() > 0) { + try { + this.ansRegistration.setPort(Integer.parseInt(serverPort)); + } + catch (NumberFormatException e) { + // nothing to do + } + } + log.info("[ Migrate ] change the port to " + serverPort); + + if (this.running.get()) { + return; + } + + if (this.ansRegistration.getPort() > 0) { + 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."); + } + else { + log.warn("the server port is smaller than zero. " + + this.ansRegistration.getPort()); + } + 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..8f632bf1 --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/MigrationAutoconfiguration.java @@ -0,0 +1,39 @@ +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; + +/** + * @author pbting + */ +@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..07eb04bb --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerListInvocationHandler.java @@ -0,0 +1,168 @@ +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.*; +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; + +/** + * @author pbting + */ +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 (source.size() > 0 && 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(); + for (Iterator iterator = ansServerList.iterator(); iterator + .hasNext();) { + AnsServer ansServer = iterator.next(); + if (server.getHostPort() + .equals(ansServer.getHealthService().toInetAddr())) { + // fix bug: mast be set the zone, update server list,will filter + // by: ZoneAffinityPredicate + ansServer.setZone(server.getZone()); + ansServer.setSchemea(server.getScheme()); + ansServer.setId(server.getId()); + ansServer.setReadyToServe(true); + serverIterator.remove(); + log.info("Source Server is remove " + server.getHostPort() + + ", and from ANS Server is override:" + + ansServer.toString()); + } + } + } + + for (Iterator iterator = ansServerList.iterator(); iterator + .hasNext();) { + source.add(iterator.next()); + } + log.info("[" + this.clientConfig.getClientName() + "] " + + "; loadbalancer server list override after:" + source); + // override + return source; + } + + private List filterAliveAnsServer(List sourceServerList) { + final List resultServerList = new LinkedList<>(); + for (Iterator iterator = sourceServerList.iterator(); iterator + .hasNext();) { + AnsServer ansServer = iterator.next(); + 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) { + log.warn(String.format("[%s] refers the server is null", server)); + } + server = (server == null ? SERVER_LIST_CONCURRENT_MAP.get(serviceId) + .getInitialListOfServers().get(0) : server); + return server; + } + + /** + * + * @param serviceId + * @param server + * @return + */ + static long 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); + return 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..4fbccfdf --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/migrate/ServerWrapper.java @@ -0,0 +1,38 @@ +package org.springframework.cloud.alicloud.ans.migrate; + +import com.netflix.loadbalancer.Server; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author pbting + */ +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 870c5fc8..ded9e15e 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,24 +16,27 @@ package org.springframework.cloud.alicloud.ans.registry; -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.boot.context.embedded.EmbeddedServletContainerInitializedEvent; import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.client.serviceregistry.ServiceRegistry; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * @author xiaolongzuo + * @author pbting */ +@Component public class AnsAutoServiceRegistration extends AbstractAutoServiceRegistration { - private static final Logger LOGGER = LoggerFactory - .getLogger(AnsAutoServiceRegistration.class); - @Autowired + private static final Log log = LogFactory.getLog(AnsAutoServiceRegistration.class); + private AnsRegistration registration; public AnsAutoServiceRegistration(ServiceRegistry serviceRegistry, @@ -59,13 +62,23 @@ public class AnsAutoServiceRegistration @Override protected AnsRegistration getManagementRegistration() { - return null; + return registration; + } + + @Override + public void start() { + // nothing to do + } + + @EventListener(EmbeddedServletContainerInitializedEvent.class) + public void doStart() { + super.start(); } @Override protected void register() { if (!this.registration.getAnsProperties().isRegisterEnabled()) { - LOGGER.debug("Registration disabled."); + log.debug("Registration disabled."); return; } if (this.registration.getPort() < 0) { @@ -83,16 +96,6 @@ public class AnsAutoServiceRegistration } - @Override - protected int getConfiguredPort() { - return this.getPort().get(); - } - - @Override - protected void setConfiguredPort(int port) { - this.getPort().set(port); - } - @Override protected Object getConfiguration() { return this.registration.getAnsProperties(); @@ -110,4 +113,13 @@ public class AnsAutoServiceRegistration return StringUtils.isEmpty(appName) ? super.getAppName() : appName; } + @Override + protected int getConfiguredPort() { + return registration.getPort(); + } + + @Override + protected void setConfiguredPort(int port) { + this.registration.setPort(port); + } } 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 a78414fd..e28bf477 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() { @@ -54,8 +54,9 @@ public class AnsRegistration implements Registration, ServiceInstance { if (null != managementPort) { Map metadata = ansProperties.getClientMetadata(); metadata.put(MANAGEMENT_PORT, managementPort.toString()); - String contextPath = env.getProperty("management.context-path"); - String address = env.getProperty("management.address"); + String contextPath = env + .getProperty("management.server.servlet.context-path"); + String address = env.getProperty("management.server.address"); if (!StringUtils.isEmpty(contextPath)) { metadata.put(MANAGEMENT_CONTEXT_PATH, contextPath); } @@ -63,6 +64,11 @@ public class AnsRegistration implements Registration, ServiceInstance { metadata.put(MANAGEMENT_ADDRESS, address); } } + + String serverPort = env.getProperty("server.port"); + if (null != serverPort) { + this.setPort(Integer.valueOf(serverPort)); + } } @Override 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..7b6d229b 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 @@ -17,7 +17,9 @@ package org.springframework.cloud.alicloud.ans.ribbon; 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; import com.netflix.client.config.IClientConfig; @@ -25,13 +27,15 @@ import com.netflix.loadbalancer.ServerList; /** * @author xiaolongzuo + * @author pbting */ @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..d874e2d9 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,14 +16,19 @@ package org.springframework.cloud.alicloud.ans.ribbon; -import java.util.Collections; +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; import com.alibaba.ans.shaded.com.taobao.vipserver.client.core.Host; import com.netflix.loadbalancer.Server; /** * @author xiaolongzuo + * @author pbting */ public class AnsServer extends Server { @@ -34,7 +39,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 +54,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 +105,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..99617ef0 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,16 +16,17 @@ 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 + * @author pbting */ public class AnsServerList extends AbstractServerList { @@ -60,10 +61,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(3)) { + 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..5bda207c --- /dev/null +++ b/spring-cloud-alicloud-ans/src/main/java/org/springframework/cloud/alicloud/ans/ribbon/MigrateRibbonCofiguration.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.alicloud.ans.ribbon; + +import org.springframework.cloud.alicloud.ans.migrate.MigrateRibbonBeanPostProcessor; +import org.springframework.cloud.alicloud.ans.migrate.MigrateOnConditionClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import com.netflix.client.config.IClientConfig; + +@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/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..d6489a25 --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigProperties.java @@ -0,0 +1,122 @@ +/* + * 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.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..af59b8ff --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsConfigRegistration.java @@ -0,0 +1,41 @@ +/* + * 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.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..82c304a8 --- /dev/null +++ b/spring-cloud-alicloud-context/src/main/java/org/springframework/cloud/alicloud/context/sms/SmsContextAutoConfiguration.java @@ -0,0 +1,45 @@ +/* + * 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.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..9e7a0291 --- /dev/null +++ b/spring-cloud-alicloud-sms/pom.xml @@ -0,0 +1,71 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba + 0.1.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 + + + org.springframework.boot + spring-boot-actuator + 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..8010e9fc --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/AbstractSmsService.java @@ -0,0 +1,62 @@ +/* + * 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<>(); + + /** + * + * @param accessKeyId + * @param accessKeySecret + * @return + */ + public IAcsClient getHangZhouRegionClientProfile(String accessKeyId, + String accessKeySecret) { + + String key = getKey("cn-hangzhou", accessKeyId, accessKeySecret); + IAcsClient acsClient = acsClientConcurrentHashMap.get(key); + if (acsClient == null) { + synchronized (this) { + acsClient = acsClientConcurrentHashMap.get(key); + if (acsClient == null) { + acsClient = new DefaultAcsClient(DefaultProfile + .getProfile("cn-hangzhou", accessKeyId, accessKeySecret)); + acsClientConcurrentHashMap.put(key, acsClient); + } + } + } + + return acsClient; + } + + 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/SmsInitializer.java b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializer.java new file mode 100644 index 00000000..4f6cca1f --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/SmsInitializer.java @@ -0,0 +1,103 @@ +/* + * 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.alicloud.context.sms.SmsConfigProperties; +import org.springframework.cloud.alicloud.sms.base.MessageListener; + +import com.aliyuncs.profile.DefaultProfile; + +/** + * @author pbting + */ +public class SmsInitializer implements BeanPostProcessor, SmartInitializingSingleton { + + private final static Log log = LogFactory.getLog(SmsInitializer.class); + + private SmsConfigProperties msConfigProperties; + private ISmsService smsService; + + public SmsInitializer(SmsConfigProperties msConfigProperties, + ISmsService smsService) { + this.msConfigProperties = msConfigProperties; + this.smsService = smsService; + } + + @Override + public void afterSingletonsInstantiated() { + // 整个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); + } + catch (Exception e) { + log.error("initializer the sms cause an exception", e); + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof MessageListener) { + initMessageListener((MessageListener) bean); + } + return bean; + } + + private void initMessageListener(MessageListener messageListener) { + if (SmsReportMessageListener.class.isInstance(messageListener)) { + if (msConfigProperties.getReportQueueName() != null + && msConfigProperties.getReportQueueName().trim().length() > 0) { + smsService.startSmsReportMessageListener( + (SmsReportMessageListener) messageListener); + return; + } + + 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); + return; + } + + throw new IllegalArgumentException("the SmsUp queue name for " + + messageListener.getClass().getCanonicalName() + " must be set."); + } + } +} \ 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..6fb88ea9 --- /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.SmsInitializer; +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 SmsInitializer smsInitializePostListener( + SmsConfigProperties msConfigProperties, ISmsService smsService) { + return new SmsInitializer(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..70d7d3c6 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/EndpointManager.java @@ -0,0 +1,107 @@ +/* + * 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; + +/** + * + * @author pbting + */ +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..3f722084 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpoint.java @@ -0,0 +1,33 @@ +/* + * 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.AbstractEndpoint; + +import java.util.Map; + +public class SmsEndpoint extends AbstractEndpoint> { + + public SmsEndpoint() { + super("sms_info"); + } + + @Override + 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..48cb05c9 --- /dev/null +++ b/spring-cloud-alicloud-sms/src/main/java/org/springframework/cloud/alicloud/sms/endpoint/SmsEndpointAutoConfiguration.java @@ -0,0 +1,30 @@ +/* + * 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.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; + +@ConditionalOnWebApplication +@ConditionalOnClass(name = "org.springframework.boot.actuate.endpoint.AbstractEndpoint") +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-alicloud/pom.xml b/spring-cloud-starter-alicloud/pom.xml index 7b3b9157..9b6b2f35 100644 --- a/spring-cloud-starter-alicloud/pom.xml +++ b/spring-cloud-starter-alicloud/pom.xml @@ -17,6 +17,7 @@ spring-cloud-starter-alicloud-acm spring-cloud-starter-alicloud-ans spring-cloud-starter-alicloud-schedulerx + spring-cloud-starter-alicloud-sms 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..d7b5c420 --- /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.1.2.BUILD-SNAPSHOT + + spring-cloud-starter-alicloud-sms + Spring Cloud Starter Alibaba Cloud SMS + + + + org.springframework.cloud + spring-cloud-alicloud-sms + + + +