优化短信通知,增加阿里云短信通知

This commit is contained in:
zhou-hao 2020-07-10 18:33:28 +08:00
parent 0fa4e606a5
commit 011f56a7ce
8 changed files with 261 additions and 177 deletions

View File

@ -17,6 +17,12 @@
<artifactId>hsweb-easy-orm-rdb</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-starter</artifactId>

View File

@ -0,0 +1,20 @@
package org.jetlinks.community.notify.sms;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetlinks.community.notify.Provider;
@Getter
@AllArgsConstructor
public enum SmsProvider implements Provider {
aliyunSms("阿里云短信服务")
;
private final String name;
@Override
public String getId() {
return name();
}
}

View File

@ -1,10 +1,10 @@
package org.jetlinks.community.notify.sms.provider;
package org.jetlinks.community.notify.sms;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.notify.*;
import org.jetlinks.core.Values;
import org.jetlinks.core.metadata.ConfigMetadata;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.template.Template;
import org.jetlinks.community.notify.template.TemplateManager;
import org.jetlinks.community.notify.template.TemplateProperties;
@ -54,7 +54,7 @@ public class TestSmsProvider extends AbstractNotifier<PlainTextSmsTemplate> impl
@Nonnull
@Override
public Mono<Void> send(@Nonnull PlainTextSmsTemplate template, @Nonnull Values context) {
return Mono.fromRunnable(() -> log.info("send sms [{}] message:{}", template.getSendTo(context.getAllValues()), template.getTextSms(context.getAllValues())));
return Mono.fromRunnable(() -> log.info("send sms {} message:{}", template.getSendTo(context.getAllValues()), template.getTextSms(context.getAllValues())));
}
@Nonnull
@ -71,7 +71,7 @@ public class TestSmsProvider extends AbstractNotifier<PlainTextSmsTemplate> impl
@Override
public String getNotifierId() {
return "test-sms-notify";
return "test-sms-sender";
}
@Override

View File

@ -0,0 +1,115 @@
package org.jetlinks.community.notify.sms.aliyun;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.logger.ReactiveLogger;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.sms.SmsProvider;
import org.jetlinks.core.Values;
import org.jetlinks.community.notify.template.TemplateManager;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Objects;
@Slf4j
public class AliyunSmsNotifier extends AbstractNotifier<AliyunSmsTemplate> {
private final IAcsClient client;
private final int connectTimeout = 1000;
private final int readTimeout = 5000;
@Getter
private String notifierId;
private String domain = "dysmsapi.aliyuncs.com";
private String regionId = "cn-hangzhou";
public AliyunSmsNotifier(NotifierProperties profile, TemplateManager templateManager) {
super(templateManager);
Map<String, Object> config = profile.getConfiguration();
DefaultProfile defaultProfile = DefaultProfile.getProfile(
this.regionId = (String) Objects.requireNonNull(config.get("regionId"), "regionId不能为空"),
(String) Objects.requireNonNull(config.get("accessKeyId"), "accessKeyId不能为空"),
(String) Objects.requireNonNull(config.get("secret"), "secret不能为空")
);
this.client = new DefaultAcsClient(defaultProfile);
this.domain = (String) config.getOrDefault("domain", domain);
this.notifierId = profile.getId();
}
public AliyunSmsNotifier(IClientProfile profile, TemplateManager templateManager) {
this(new DefaultAcsClient(profile), templateManager);
}
public AliyunSmsNotifier(IAcsClient client, TemplateManager templateManager) {
super(templateManager);
this.client = client;
}
@Override
@Nonnull
public NotifyType getType() {
return DefaultNotifyType.sms;
}
@Nonnull
@Override
public Provider getProvider() {
return SmsProvider.aliyunSms;
}
@Override
@Nonnull
public Mono<Void> send(@Nonnull AliyunSmsTemplate template, @Nonnull Values context) {
return Mono.<Void>defer(() -> {
try {
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain(domain);
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.setSysConnectTimeout(connectTimeout);
request.setSysReadTimeout(readTimeout);
request.putQueryParameter("RegionId", regionId);
request.putQueryParameter("PhoneNumbers", template.getPhoneNumber());
request.putQueryParameter("SignName", template.getSignName());
request.putQueryParameter("TemplateCode", template.getCode());
request.putQueryParameter("TemplateParam", template.createTtsParam(context.getAllValues()));
CommonResponse response = client.getCommonResponse(request);
log.info("发送短信通知完成 {}:{}", response.getHttpResponse().getStatus(), response.getData());
JSONObject json = JSON.parseObject(response.getData());
if (!"ok".equalsIgnoreCase(json.getString("Code"))) {
return Mono.error(new BusinessException(json.getString("Message"), json.getString("Code")));
}
} catch (Exception e) {
return Mono.error(e);
}
return Mono.empty();
}).doOnEach(ReactiveLogger.onError(err -> {
log.info("发送短信通知失败", err);
})).subscribeOn(Schedulers.elastic());
}
@Override
@Nonnull
public Mono<Void> close() {
return Mono.fromRunnable(client::shutdown);
}
}

View File

@ -0,0 +1,78 @@
package org.jetlinks.community.notify.sms.aliyun;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.validator.ValidatorUtils;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.sms.SmsProvider;
import org.jetlinks.core.metadata.ConfigMetadata;
import org.jetlinks.core.metadata.DefaultConfigMetadata;
import org.jetlinks.core.metadata.types.StringType;
import org.jetlinks.community.notify.template.TemplateManager;
import org.jetlinks.community.notify.template.TemplateProperties;
import org.jetlinks.community.notify.template.TemplateProvider;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
/**
* 阿里云短信通知服务
* </a>
*
* @author zhouhao
* @since 1.3
*/
@Component
@Slf4j
@AllArgsConstructor
public class AliyunSmsNotifierProvider implements NotifierProvider, TemplateProvider {
private final TemplateManager templateManager;
@Nonnull
@Override
public Provider getProvider() {
return SmsProvider.aliyunSms;
}
public static final DefaultConfigMetadata templateConfig = new DefaultConfigMetadata("阿里云短信模版",
"https://help.aliyun.com/document_detail/108086.html")
.add("signName", "签名", "", new StringType())
.add("code", "模版编码", "", new StringType())
.add("phoneNumber", "收信人", "", new StringType());
public static final DefaultConfigMetadata notifierConfig = new DefaultConfigMetadata("阿里云API配置"
,"https://help.aliyun.com/document_detail/101300.html")
.add("regionId", "regionId", "regionId", new StringType())
.add("accessKeyId", "accessKeyId", "", new StringType())
.add("secret", "secret", "", new StringType());
@Override
public ConfigMetadata getTemplateConfigMetadata() {
return templateConfig;
}
@Override
public ConfigMetadata getNotifierConfigMetadata() {
return notifierConfig;
}
@Override
public Mono<AliyunSmsTemplate> createTemplate(TemplateProperties properties) {
return Mono.fromCallable(() -> ValidatorUtils.tryValidate(JSON.parseObject(properties.getTemplate(), AliyunSmsTemplate.class)));
}
@Nonnull
@Override
public NotifyType getType() {
return DefaultNotifyType.sms;
}
@Nonnull
@Override
public Mono<AliyunSmsNotifier> createNotifier(@Nonnull NotifierProperties properties) {
return Mono.fromSupplier(() -> new AliyunSmsNotifier(properties, templateManager));
}
}

View File

@ -0,0 +1,37 @@
package org.jetlinks.community.notify.sms.aliyun;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import org.jetlinks.community.notify.template.Template;
import javax.validation.constraints.NotBlank;
import java.util.Map;
/**
* 阿里云短信模版
*
* @since 1.3
*/
@Getter
@Setter
public class AliyunSmsTemplate implements Template {
//签名名称
@NotBlank(message = "[signName]不能为空")
private String signName;
//模版编码
@NotBlank(message = "[code]不能为空")
private String code;
@NotBlank(message = "[phoneNumber]不能为空")
private String phoneNumber;
private Map<String, String> param;
public String createTtsParam(Map<String, Object> ctx) {
return JSON.toJSONString(ctx);
}
}

View File

@ -1,172 +0,0 @@
package org.jetlinks.community.notify.sms.provider;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.jetlinks.core.Values;
import org.jetlinks.core.metadata.ConfigMetadata;
import org.jetlinks.core.metadata.DefaultConfigMetadata;
import org.jetlinks.core.metadata.types.PasswordType;
import org.jetlinks.core.metadata.types.StringType;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.template.Template;
import org.jetlinks.community.notify.template.TemplateManager;
import org.jetlinks.community.notify.template.TemplateProperties;
import org.jetlinks.community.notify.template.TemplateProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
@Component
@Slf4j
@Profile({"dev","test"})
public class Hy2046SmsSenderProvider implements NotifierProvider, TemplateProvider, Provider {
private WebClient webClient = WebClient.builder()
.baseUrl("http://sms10692.com/v2sms.aspx")
.build();
@Autowired
private TemplateManager templateManager;
@Nonnull
@Override
public NotifyType getType() {
return DefaultNotifyType.sms;
}
@Nonnull
@Override
public Provider getProvider() {
return this;
}
static DefaultConfigMetadata notifierConfig = new DefaultConfigMetadata("宏衍2046短信配置", "")
.add("userId", "userId", "用户ID", new StringType())
.add("username", "用户名", "用户名", new StringType())
.add("password", "密码", "密码", new PasswordType());
@Override
public ConfigMetadata getNotifierConfigMetadata() {
return notifierConfig;
}
@Override
public ConfigMetadata getTemplateConfigMetadata() {
return PlainTextSmsTemplate.templateConfig;
}
@Override
public Mono<? extends Template> createTemplate(TemplateProperties properties) {
return Mono.fromSupplier(() -> JSON.parseObject(properties.getTemplate(), PlainTextSmsTemplate.class));
}
@Nonnull
@Override
public Mono<Hy2046SmsSender> createNotifier(@Nonnull NotifierProperties properties) {
return Mono.defer(() -> {
String userId = (String) properties.getConfigOrNull("userId");
String username = (String) properties.getConfigOrNull("username");
String password = (String) properties.getConfigOrNull("password");
Assert.hasText(userId, "短信配置错误,缺少userId");
Assert.hasText(username, "短信配置错误,缺少username");
Assert.hasText(password, "短信配置错误,缺少password");
return Mono.just(new Hy2046SmsSender(properties.getId(),userId, username, password));
});
}
@Override
public String getId() {
return "hy2046";
}
@Override
public String getName() {
return "宏衍2046";
}
class Hy2046SmsSender extends AbstractNotifier<PlainTextSmsTemplate> {
String userId;
String username;
String password;
@Getter
private final String notifierId;
public Hy2046SmsSender(String id,String userId, String username, String password) {
super(templateManager);
this.userId = userId;
this.notifierId = id;
this.username = username;
this.password = password;
}
@Nonnull
@Override
public NotifyType getType() {
return DefaultNotifyType.sms;
}
@Nonnull
@Override
public Provider getProvider() {
return Hy2046SmsSenderProvider.this;
}
@Nonnull
@Override
public Mono<Void> close() {
return Mono.empty();
}
@Nonnull
@Override
public Mono<Void> send(@Nonnull PlainTextSmsTemplate template, @Nonnull Values context) {
return Mono.defer(() -> {
String ts = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
String sign = DigestUtils.md5Hex(username.concat(password).concat(ts));
String[] sendTo = template.getSendTo(context.getAllValues());
String mobile = String.join(",", sendTo);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("userid", userId);
formData.add("timestamp", ts);
formData.add("sign", sign);
formData.add("mobile", mobile);
formData.add("content", template.getTextSms(context.getAllValues()));
formData.add("action", "send");
formData.add("rt", "json");
return webClient.post()
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(Map.class)
.map(map -> {
if (Integer.valueOf(sendTo.length).equals(map.get("SuccessCounts"))) {
return true;
}
throw new RuntimeException("发送短信失败:" + map.get("Message"));
});
}).then();
}
}
}