From 31af39258d69cb7f95bda80a442472382a688a88 Mon Sep 17 00:00:00 2001 From: zhouhao Date: Wed, 19 Feb 2020 15:03:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=9A=E7=9F=A5=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notify-component/notify-core/pom.xml | 41 ++++ .../community/notify/AbstractNotifier.java | 26 +++ .../notify/DefaultNotifierManager.java | 71 +++++++ .../community/notify/DefaultNotifyType.java | 24 +++ .../jetlinks/community/notify/Notifier.java | 76 +++++++ .../community/notify/NotifierManager.java | 39 ++++ .../community/notify/NotifierProperties.java | 59 ++++++ .../community/notify/NotifierProvider.java | 53 +++++ .../community/notify/NotifyConfigManager.java | 28 +++ .../jetlinks/community/notify/NotifyType.java | 15 ++ .../jetlinks/community/notify/Provider.java | 20 ++ .../notify/rule/NotifierRuleNode.java | 48 +++++ .../notify/rule/RuleNotifierProperties.java | 37 ++++ .../template/AbstractTemplateManager.java | 50 +++++ .../community/notify/template/Template.java | 15 ++ .../notify/template/TemplateManager.java | 57 +++++ .../notify/template/TemplateProperties.java | 19 ++ .../notify/template/TemplateProvider.java | 19 ++ .../notify-component/notify-dingtalk/pom.xml | 32 +++ .../dingtalk/DingTalkMessageTemplate.java | 86 ++++++++ .../notify/dingtalk/DingTalkNotifier.java | 120 +++++++++++ .../dingtalk/DingTalkNotifierProvider.java | 80 +++++++ .../notify/dingtalk/DingTalkProperties.java | 19 ++ .../notify/dingtalk/DingTalkProvider.java | 20 ++ .../notify/dingtalk/DingTalkNotifierTest.java | 39 ++++ .../notify-component/notify-email/pom.xml | 44 ++++ .../community/notify/email/EmailProvider.java | 20 ++ .../email/embedded/DefaultEmailNotifier.java | 198 ++++++++++++++++++ .../DefaultEmailNotifierProvider.java | 122 +++++++++++ .../embedded/DefaultEmailProperties.java | 49 +++++ .../notify/email/embedded/EmailTemplate.java | 30 +++ .../email/embedded/ParsedEmailTemplate.java | 29 +++ .../notify-component/notify-sms/pom.xml | 41 ++++ .../sms/provider/Hy2046SmsSenderProvider.java | 168 +++++++++++++++ .../sms/provider/PlainTextSmsTemplate.java | 40 ++++ .../notify/sms/provider/TestSmsProvider.java | 81 +++++++ .../notify-component/notify-voice/README.md | 46 ++++ .../notify-component/notify-voice/pom.xml | 27 +++ .../voice/VoiceNotifierConfiguration.java | 19 ++ .../community/notify/voice/VoiceProvider.java | 20 ++ .../voice/aliyun/AliyunNotifierProvider.java | 79 +++++++ .../voice/aliyun/AliyunVoiceNotifier.java | 109 ++++++++++ .../voice/aliyun/AliyunVoiceTemplate.java | 37 ++++ .../aliyun/AliyunVoiceNotifierTest.java | 67 ++++++ .../notify-component/notify-wechat/pom.xml | 28 +++ .../notify/wechat/WechatCorpProperties.java | 19 ++ .../notify/wechat/WechatMessageTemplate.java | 102 +++++++++ .../notify/wechat/WechatNotifierProvider.java | 77 +++++++ .../notify/wechat/WechatProvider.java | 20 ++ .../notify/wechat/WeixinCorpNotifier.java | 120 +++++++++++ .../notify/wechat/WeixinCorpNotifierTest.java | 38 ++++ jetlinks-components/notify-component/pom.xml | 25 +++ jetlinks-components/pom.xml | 1 + .../authentication-manager/.gitignore | 27 --- jetlinks-manager/device-manager/.gitignore | 27 --- .../DevicePropertyMeasurement.java | 9 +- jetlinks-manager/network-manager/.gitignore | 29 --- jetlinks-manager/notify-manager/README.md | 1 + jetlinks-manager/notify-manager/pom.xml | 125 +++++++++++ .../manager/entity/NotifyConfigEntity.java | 64 ++++++ .../manager/entity/NotifyTemplateEntity.java | 51 +++++ .../service/DefaultNotifyConfigManager.java | 25 +++ .../service/DefaultTemplateManager.java | 35 ++++ .../manager/service/NotifierCacheManager.java | 65 ++++++ .../manager/service/NotifyConfigService.java | 14 ++ .../service/NotifyTemplateService.java | 17 ++ .../manager/web/NotifierConfigController.java | 112 ++++++++++ .../manager/web/NotifierController.java | 69 ++++++ .../web/NotifierTemplateController.java | 56 +++++ jetlinks-manager/pom.xml | 1 + jetlinks-standalone/pom.xml | 6 + 71 files changed, 3394 insertions(+), 88 deletions(-) create mode 100644 jetlinks-components/notify-component/notify-core/pom.xml create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/AbstractNotifier.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifierManager.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifyType.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Notifier.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierManager.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProperties.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProvider.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyConfigManager.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyType.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Provider.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/NotifierRuleNode.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/RuleNotifierProperties.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/AbstractTemplateManager.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/Template.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateManager.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProperties.java create mode 100644 jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProvider.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/pom.xml create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkMessageTemplate.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifier.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierProvider.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProperties.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProvider.java create mode 100644 jetlinks-components/notify-component/notify-dingtalk/src/test/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierTest.java create mode 100644 jetlinks-components/notify-component/notify-email/pom.xml create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/EmailProvider.java create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifierProvider.java create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailProperties.java create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/EmailTemplate.java create mode 100644 jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/ParsedEmailTemplate.java create mode 100644 jetlinks-components/notify-component/notify-sms/pom.xml create mode 100644 jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/Hy2046SmsSenderProvider.java create mode 100644 jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/PlainTextSmsTemplate.java create mode 100644 jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/TestSmsProvider.java create mode 100644 jetlinks-components/notify-component/notify-voice/README.md create mode 100644 jetlinks-components/notify-component/notify-voice/pom.xml create mode 100644 jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceNotifierConfiguration.java create mode 100644 jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceProvider.java create mode 100644 jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunNotifierProvider.java create mode 100644 jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceNotifier.java create mode 100644 jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceTemplate.java create mode 100644 jetlinks-components/notify-component/notify-voice/src/test/java/org/jetlinks/community/notify/voice/supports/aliyun/AliyunVoiceNotifierTest.java create mode 100644 jetlinks-components/notify-component/notify-wechat/pom.xml create mode 100644 jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatCorpProperties.java create mode 100644 jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatMessageTemplate.java create mode 100644 jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatNotifierProvider.java create mode 100644 jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatProvider.java create mode 100644 jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifier.java create mode 100644 jetlinks-components/notify-component/notify-wechat/src/test/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifierTest.java create mode 100644 jetlinks-components/notify-component/pom.xml delete mode 100644 jetlinks-manager/authentication-manager/.gitignore delete mode 100644 jetlinks-manager/device-manager/.gitignore delete mode 100644 jetlinks-manager/network-manager/.gitignore create mode 100644 jetlinks-manager/notify-manager/README.md create mode 100644 jetlinks-manager/notify-manager/pom.xml create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyConfigEntity.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyTemplateEntity.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultNotifyConfigManager.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultTemplateManager.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifierCacheManager.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyConfigService.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyTemplateService.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierConfigController.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierController.java create mode 100644 jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierTemplateController.java diff --git a/jetlinks-components/notify-component/notify-core/pom.xml b/jetlinks-components/notify-component/notify-core/pom.xml new file mode 100644 index 00000000..1a2106f4 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/pom.xml @@ -0,0 +1,41 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + + 4.0.0 + + notify-core + + + org.hswebframework + hsweb-easy-orm-rdb + + + org.jetlinks + rule-engine-support + ${jetlinks.version} + + + org.hswebframework.web + hsweb-starter + ${hsweb.framework.version} + + + org.jetlinks + jetlinks-core + ${jetlinks.version} + + + ${project.groupId} + common-component + ${project.version} + + + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/AbstractNotifier.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/AbstractNotifier.java new file mode 100644 index 00000000..aafe38e4 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/AbstractNotifier.java @@ -0,0 +1,26 @@ +package org.jetlinks.community.notify; + +import lombok.AllArgsConstructor; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.template.Template; +import org.jetlinks.community.notify.template.TemplateManager; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +@AllArgsConstructor +public abstract class AbstractNotifier implements Notifier { + + private TemplateManager templateManager; + + @Override + @Nonnull + public Mono send(@Nonnull String templateId, @Nonnull Values context) { + return templateManager + .getTemplate(getType(), templateId) + .switchIfEmpty(Mono.error(new UnsupportedOperationException("模版不存在:" + templateId))) + .flatMap(tem -> send((T) tem, context)); + } + + +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifierManager.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifierManager.java new file mode 100644 index 00000000..c1228bee --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifierManager.java @@ -0,0 +1,71 @@ +package org.jetlinks.community.notify; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +@SuppressWarnings("all") +public class DefaultNotifierManager implements NotifierManager, BeanPostProcessor { + + private final Map> providers = new ConcurrentHashMap<>(); + + private Map notifiers = new ConcurrentHashMap<>(); + + private NotifyConfigManager configManager; + + public DefaultNotifierManager(NotifyConfigManager manager) { + this.configManager = manager; + } + + protected Mono getProperties(NotifyType notifyType, + String id) { + return configManager.getNotifyConfig(notifyType, id); + } + + public Mono reload(String id) { + // TODO: 2019/12/20 集群支持 + return Mono.justOrEmpty(notifiers.remove(id)) + .flatMap(Notifier::close); + } + + protected Mono createNotifier(NotifierProperties properties) { + return Mono.justOrEmpty(providers.get(properties.getType())) + .switchIfEmpty(Mono.error(new UnsupportedOperationException("不支持的通知类型:" + properties.getType()))) + .flatMap(map -> Mono.justOrEmpty(map.get(properties.getProvider()))) + .switchIfEmpty(Mono.error(new UnsupportedOperationException("不支持的服务商:" + properties.getProvider()))) + .flatMap(notifierProvider -> notifierProvider.createNotifier(properties)) + .flatMap(notifier -> Mono.justOrEmpty(notifiers.put(properties.getId(), notifier)) + .flatMap(Notifier::close)//如果存在旧的通知器则关掉之 + .onErrorContinue((err, obj) -> log.error(err.getMessage(), err))//忽略异常 + .thenReturn(notifier)); + } + + @Override + @Nonnull + public Mono getNotifier(@Nonnull NotifyType type, + @Nonnull String id) { + return Mono.justOrEmpty(notifiers.get(id)) + .switchIfEmpty(Mono.defer(() -> getProperties(type, id)).flatMap(this::createNotifier)); + } + + protected void registerProvider(NotifierProvider provider) { + providers.computeIfAbsent(provider.getType().getId(), ignore -> new ConcurrentHashMap<>()) + .put(provider.getProvider().getId(), provider); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof NotifierProvider) { + registerProvider(((NotifierProvider) bean)); + } + return bean; + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifyType.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifyType.java new file mode 100644 index 00000000..4af7b37c --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/DefaultNotifyType.java @@ -0,0 +1,24 @@ +package org.jetlinks.community.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum DefaultNotifyType implements NotifyType { + + sms("短信"), + email("邮件"), + voice("语音"), + dingTalk("钉钉"), + weixin("微信"), + + ; + + private String name; + + @Override + public String getId() { + return name(); + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Notifier.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Notifier.java new file mode 100644 index 00000000..36313437 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Notifier.java @@ -0,0 +1,76 @@ +package org.jetlinks.community.notify; + +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.template.Template; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.function.Consumer; + +/** + * 通知器,用于发送通知,如: 短信,邮件,语音,微信等s + * + * @author zhouhao + * @see NotifierManager + * @see NotifierProvider + * @since 1.0 + */ +public interface Notifier { + + /** + * 获取通知类型,如: 语音通知 + * + * @return 通知类型 + * @see DefaultNotifyType + */ + @Nonnull + NotifyType getType(); + + /** + * 获取通知服务提供商,如: aliyun 等 + * + * @return 通知服务提供商 + */ + @Nonnull + Provider getProvider(); + + /** + * 指定模版ID进行发送. + * 发送失败或者模版不存在将返回{@link Mono#error(Throwable)}. + * + * @param templateId 模版ID + * @param context 上下文 + * @return 异步发送结果 + * @see Mono#doOnError(Consumer) + * @see Mono#doOnSuccess(Consumer) + * @see Template + */ + @Nonnull + Mono send(@Nonnull String templateId, Values context); + + /** + * 指定模版{@link Template}并发送. + *

+ * 注意:不同等服务商使用的模版实现不同. + *

+ * 发送失败返回{@link Mono#error(Throwable)}. + * + * @param template 模版 + * @param context 上下文 + * @return 异步发送结果 + * @see Mono#doOnError(Consumer) + * @see Mono#doOnSuccess(Consumer) + * @see Template + */ + @Nonnull + Mono send(@Nonnull T template, @Nonnull Values context); + + /** + * 关闭通知器,以释放相关资源 + * + * @return 关闭结果 + */ + @Nonnull + Mono close(); + +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierManager.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierManager.java new file mode 100644 index 00000000..a14dae29 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierManager.java @@ -0,0 +1,39 @@ +package org.jetlinks.community.notify; + +import org.jetlinks.community.notify.template.Template; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +/** + * 通知管理器,用于获取获取通知器. + * + * @author zhouhao + * @see 1.0 + * @see Notifier + * @see DefaultNotifyType + * @see Template + * @see NotifyConfigManager + */ +public interface NotifierManager { + + /** + * 获取通知器 + * + * @param type 通知类型 {@link DefaultNotifyType} + * @param id 唯一标识 + * @param 模版类型 + * @return 异步获取结果 + * @see NotifierProvider + */ + @Nonnull + Mono> getNotifier(@Nonnull NotifyType type, @Nonnull String id); + + /** + * 重新加载通知管理器 + * + * @param id 通知管理器ID + * @return 加载结果 + */ + Mono reload(String id); +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProperties.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProperties.java new file mode 100644 index 00000000..03777874 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProperties.java @@ -0,0 +1,59 @@ +package org.jetlinks.community.notify; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Map; +import java.util.Optional; + +/** + * 通知配置属性 + * + * @author zhouhao + * @see NotifyConfigManager + * @since 1.0 + */ +@Getter +@Setter +public class NotifierProperties implements Serializable { + + private static final long serialVersionUID = -6849794470754667710L; + + /** + * 配置全局唯一标识 + */ + private String id; + + /** + * 通知类型标识 + * @see NotifyType + */ + private String type; + + /** + * 通知服务提供商标识,如: aliyun ... + */ + private String provider; + + /** + * 配置名称 + */ + private String name; + + /** + * 配置内容,不同的服务提供商,配置不同. + * @see NotifierProvider + */ + private Map configuration; + + public Optional getConfig(String key){ + return Optional.ofNullable(configuration) + .map(conf->conf.get(key)); + } + public Object getConfigOrNull(String key){ + return Optional.ofNullable(configuration) + .map(conf->conf.get(key)) + .orElse(null); + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProvider.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProvider.java new file mode 100644 index 00000000..842f013e --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifierProvider.java @@ -0,0 +1,53 @@ +package org.jetlinks.community.notify; + +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.community.notify.template.Template; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * 通知服务提供商 + * + * @author zhouhao + * @see org.jetlinks.community.notify.template.TemplateProvider + * @see NotifierManager + * @since 1.0 + */ +public interface NotifierProvider { + + /** + * 获取通知类型 + * + * @return 通知类型 + * @see DefaultNotifyType + */ + @Nonnull + NotifyType getType(); + + /** + * @return 服务商 + */ + @Nonnull + Provider getProvider(); + + /** + * 根据配置创建通知器 + * + * @param properties 通知配置 + * @return 创建结果 + */ + @Nonnull + Mono> createNotifier(@Nonnull NotifierProperties properties); + + /** + * 获取通知配置元数据,通过元数据可以知道此通知所需要的配置信息 + * + * @return 配置元数据 + */ + @Nullable + default ConfigMetadata getNotifierConfigMetadata() { + return null; + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyConfigManager.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyConfigManager.java new file mode 100644 index 00000000..c0a042c2 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyConfigManager.java @@ -0,0 +1,28 @@ +package org.jetlinks.community.notify; + +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +/** + * 通知配置管理器,用于统一管理通知配置 + * + * @author zhouhao + * @version 1.0 + * @since 1.0 + */ +public interface NotifyConfigManager { + + /** + * 根据类型和配置ID获取通知器配置 + *

+ * 如果配置不存在则返回{@link Mono#empty()},可通过{@link Mono#switchIfEmpty(Mono)}进行处理 + * + * @param notifyType 类型 {@link DefaultNotifyType} + * @param configId 配置ID + * @return 配置 + */ + @Nonnull + Mono getNotifyConfig(@Nonnull NotifyType notifyType, @Nonnull String configId); + +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyType.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyType.java new file mode 100644 index 00000000..63aeba80 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/NotifyType.java @@ -0,0 +1,15 @@ +package org.jetlinks.community.notify; + +/** + * 通知类型.通常使用枚举实现此接口 + * + * @author zhouhao + * @see DefaultNotifyType + * @since 1.0 + */ +public interface NotifyType { + + String getId(); + + String getName(); +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Provider.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Provider.java new file mode 100644 index 00000000..04fd02ea --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/Provider.java @@ -0,0 +1,20 @@ +package org.jetlinks.community.notify; + +/** + * 服务商标识,通常使用枚举实现此接口 + * + * @author zhouhao + * @since 1.0 + */ +public interface Provider { + /** + * @return 唯一标识 + */ + String getId(); + + /** + * @return 名称 + */ + String getName(); + +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/NotifierRuleNode.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/NotifierRuleNode.java new file mode 100644 index 00000000..0bd6fe43 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/NotifierRuleNode.java @@ -0,0 +1,48 @@ +package org.jetlinks.community.notify.rule; + +import lombok.AllArgsConstructor; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.NotifierManager; +import org.jetlinks.rule.engine.api.RuleData; +import org.jetlinks.rule.engine.api.RuleDataHelper; +import org.jetlinks.rule.engine.api.executor.ExecutionContext; +import org.jetlinks.rule.engine.executor.CommonExecutableRuleNodeFactoryStrategy; +import org.reactivestreams.Publisher; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.function.Function; + +@Component +@AllArgsConstructor +public class NotifierRuleNode extends CommonExecutableRuleNodeFactoryStrategy { + + private NotifierManager notifierManager; + + @Override + public String getSupportType() { + return "notifier"; + } + + @Override + public Function> createExecutor(ExecutionContext context, RuleNotifierProperties config) { + return rule -> notifierManager + .getNotifier(config.getNotifyType(), config.getNotifierId()) + .switchIfEmpty(Mono.fromRunnable(() -> { + context.logger().warn("通知器[{}-{}]不存在", config.getNodeType(), config.getNotifierId()); + })) + .flatMap(notifier -> notifier.send(config.getTemplateId(), Values.of(RuleDataHelper.toContextMap(rule)))) + .doOnError(err -> { + context.logger().error("发送[{}]通知[{}-{}]失败", + config.getNotifyType().getName(), + config.getNotifierId(), + config.getTemplateId(), err); + }) + .doOnSuccess(ignore -> { + context.logger().info("发送[{}]通知[{}-{}]完成", + config.getNotifyType().getName(), + config.getNotifierId(), + config.getTemplateId()); + }); + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/RuleNotifierProperties.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/RuleNotifierProperties.java new file mode 100644 index 00000000..295af470 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/rule/RuleNotifierProperties.java @@ -0,0 +1,37 @@ +package org.jetlinks.community.notify.rule; + +import lombok.Getter; +import lombok.Setter; +import org.jetlinks.community.notify.DefaultNotifyType; +import org.jetlinks.rule.engine.api.model.NodeType; +import org.jetlinks.rule.engine.executor.node.RuleNodeConfig; +import org.springframework.util.Assert; + +@Getter +@Setter +public class RuleNotifierProperties implements RuleNodeConfig { + + private DefaultNotifyType notifyType; + + private String notifierId; + + private String templateId; + + @Override + public NodeType getNodeType() { + return NodeType.PEEK; + } + + @Override + public void setNodeType(NodeType nodeType) { + + } + + @Override + public void validate() { + Assert.notNull(notifyType,"notifyType can not be null"); + Assert.hasText(notifierId,"notifierId can not be empty"); + Assert.hasText(templateId,"templateId can not be empty"); + + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/AbstractTemplateManager.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/AbstractTemplateManager.java new file mode 100644 index 00000000..3410879b --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/AbstractTemplateManager.java @@ -0,0 +1,50 @@ +package org.jetlinks.community.notify.template; + +import org.jetlinks.community.notify.NotifyType; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AbstractTemplateManager implements TemplateManager { + + protected Map> providers = new ConcurrentHashMap<>(); + + protected Map templates = new ConcurrentHashMap<>(); + + protected abstract Mono getProperties(NotifyType type, String id); + + protected void register(TemplateProvider provider) { + providers.computeIfAbsent(provider.getType().getId(), ignore -> new ConcurrentHashMap<>()) + .put(provider.getProvider().getId(), provider); + } + + @Override + @Nonnull + public Mono createTemplate(@Nonnull NotifyType type,@Nonnull TemplateProperties prop) { + return Mono.justOrEmpty(providers.get(type.getId())) + .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的通知类型:" + prop.getType()))) + .flatMap(map -> Mono.justOrEmpty(map.get(prop.getProvider())) + .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的服务商:" + prop.getProvider()))) + .flatMap(provider -> provider.createTemplate(prop))); + } + + @Nonnull + @Override + public Mono getTemplate(@Nonnull NotifyType type, @Nonnull String id) { + return Mono.justOrEmpty(templates.get(id)) + .switchIfEmpty(Mono.defer(() -> + getProperties(type, id) + .flatMap(prop -> this.createTemplate(type, prop)) + .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("通知类型不支持:" + type.getId()))) + )); + } + + @Override + @Nonnull + public Mono reload(String templateId) { + // TODO: 2019/12/20 集群支持 + return Mono.fromRunnable(() -> templates.remove(templateId)); + } +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/Template.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/Template.java new file mode 100644 index 00000000..b33eeca4 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/Template.java @@ -0,0 +1,15 @@ +package org.jetlinks.community.notify.template; + + +import java.io.Serializable; + +/** + * 通知模版,不同的服务商{@link org.jetlinks.community.notify.NotifierProvider},{@link TemplateProvider}需要实现不同的模版 + * + * @author zhouhao + * @version 1.0 + * @since 1.0 + */ +public interface Template extends Serializable { + +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateManager.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateManager.java new file mode 100644 index 00000000..20d85033 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateManager.java @@ -0,0 +1,57 @@ +package org.jetlinks.community.notify.template; + +import org.jetlinks.community.notify.NotifyType; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +/** + * 模版管理器,用于统一管理通知模版. 模版的转换由不同的通知服务商{@link TemplateProvider}去实现. + * + * @author zhouhao + * @see Template + * @see TemplateProvider + * @since 1.0 + */ +public interface TemplateManager { + + /** + * 根据通知类型和模版ID获取模版 + *

+ * 如果模版不存在将返回{@link Mono#empty()},可通过{@link Mono#switchIfEmpty(Mono)}进行处理. + *

+ * 如果通知类型或者通知服务商不支持,将会返回{@link Mono#error(Throwable)}. {@link UnsupportedOperationException} + *

+ * 请根据不同的通知类型处理对应的模版. + * + * @param type 通知类型 + * @param id 模版ID + * @return 模版 + */ + @Nonnull + Mono getTemplate(@Nonnull NotifyType type, @Nonnull String id); + + /** + * 根据通知类型和配置对象创建一个模版 + *

+ * 如果通知类型或者通知服务商不支持,将会返回{@link Mono#error(Throwable)}. {@link UnsupportedOperationException} + *

+ * 请根据不同的通知类型处理对应的模版. + * + * @param type 通知类型 + * @param properties 模版配置 + * @return 模版 + * @see TemplateProvider + */ + @Nonnull + Mono createTemplate(@Nonnull NotifyType type, @Nonnull TemplateProperties properties); + + /** + * 重新加载模版 + * + * @param templateId 模版ID + * @return 异步结果 + */ + @Nonnull + Mono reload(String templateId); +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProperties.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProperties.java new file mode 100644 index 00000000..a7f1293c --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProperties.java @@ -0,0 +1,19 @@ +package org.jetlinks.community.notify.template; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Map; + +@Getter +@Setter +public class TemplateProperties implements Serializable { + private static final long serialVersionUID = -6849794470754667710L; + + private String type; + + private String provider; + + private String template; +} diff --git a/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProvider.java b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProvider.java new file mode 100644 index 00000000..241cd903 --- /dev/null +++ b/jetlinks-components/notify-component/notify-core/src/main/java/org/jetlinks/community/notify/template/TemplateProvider.java @@ -0,0 +1,19 @@ +package org.jetlinks.community.notify.template; + +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.Provider; +import reactor.core.publisher.Mono; + +public interface TemplateProvider { + + NotifyType getType(); + + Provider getProvider(); + + Mono createTemplate(TemplateProperties properties); + + default ConfigMetadata getTemplateConfigMetadata() { + return null; + } +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/pom.xml b/jetlinks-components/notify-component/notify-dingtalk/pom.xml new file mode 100644 index 00000000..3d0590bc --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/pom.xml @@ -0,0 +1,32 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + + 4.0.0 + + notify-dingtalk + + + + + + org.jetlinks.community + notify-core + ${project.version} + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkMessageTemplate.java b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkMessageTemplate.java new file mode 100644 index 00000000..ebc5de9e --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkMessageTemplate.java @@ -0,0 +1,86 @@ +package org.jetlinks.community.notify.dingtalk; + +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.hswebframework.web.utils.ExpressionUtils; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.template.Template; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.validation.constraints.NotBlank; +import java.util.Collections; + +@Getter +@Setter +public class DingTalkMessageTemplate implements Template { + + /** + * 应用ID + */ + @NotBlank(message = "[agentId]不能为空") + private String agentId; + + private String userIdList; + + private String departmentIdList; + + private boolean toAllUser; + + @NotBlank(message = "[message]不能为空") + private String message; + + + @SneakyThrows + public BodyInserters.FormInserter createFormInserter(BodyInserters.FormInserter inserter, Values context) { + inserter.with("agent_id", this.getAgentId()) + .with("to_all_user", String.valueOf(toAllUser)) + .with("msg",this.createMessage(context)); + if (StringUtils.hasText(userIdList)) { + inserter.with("userid_list", this.createUserIdList(context)); + } + if (StringUtils.hasText(departmentIdList)) { + inserter.with("dept_id_list", this.createDepartmentIdList(context)); + } + return inserter; + + } + + public UriComponentsBuilder createUriParameter(UriComponentsBuilder builder, Values context){ + builder.queryParam("agent_id", this.getAgentId()) + .queryParam("to_all_user", String.valueOf(toAllUser)) + .queryParam("msg",this.createMessage(context)); + if (StringUtils.hasText(userIdList)) { + builder.queryParam("userid_list", this.createUserIdList(context)); + } + if (StringUtils.hasText(departmentIdList)) { + builder.queryParam("dept_id_list", this.createDepartmentIdList(context)); + } + return builder; + } + + public String createUserIdList(Values context) { + if (StringUtils.isEmpty(userIdList)) { + return userIdList; + } + return ExpressionUtils.analytical(userIdList, context.getAllValues(), "spel"); + } + + public String createDepartmentIdList(Values context) { + if (StringUtils.isEmpty(departmentIdList)) { + return departmentIdList; + } + return ExpressionUtils.analytical(departmentIdList, context.getAllValues(), "spel"); + } + + public String createMessage(Values context) { + JSONObject json = new JSONObject(); + json.put("msgtype", "text"); + json.put("text", Collections.singletonMap("content",ExpressionUtils.analytical(message, context.getAllValues(), "spel"))); + return json.toJSONString(); + } + +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifier.java b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifier.java new file mode 100644 index 00000000..c0885034 --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifier.java @@ -0,0 +1,120 @@ +package org.jetlinks.community.notify.dingtalk; + +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.exception.BusinessException; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.AbstractNotifier; +import org.jetlinks.community.notify.DefaultNotifyType; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.Provider; +import org.jetlinks.community.notify.template.TemplateManager; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.time.Duration; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicReference; + +@Slf4j +public class DingTalkNotifier extends AbstractNotifier { + + private AtomicReference accessToken = new AtomicReference<>(); + + private long refreshTokenTime; + + private long tokenTimeOut = Duration.ofSeconds(7000).toMillis(); + + private WebClient client; + + private static final String tokenApi = "https://oapi.dingtalk.com/gettoken"; + + private static final String notify = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"; + + private DingTalkProperties properties; + + public DingTalkNotifier(WebClient client, DingTalkProperties properties, TemplateManager templateManager) { + super(templateManager); + this.client = client; + this.properties = properties; + } + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.dingTalk; + } + + @Nonnull + @Override + public Provider getProvider() { + return DingTalkProvider.dingTalkMessage; + } + + @Nonnull + @Override + public Mono send(@Nonnull DingTalkMessageTemplate template, @Nonnull Values context) { + return getToken() + .flatMap(token -> + client.post() + .uri(notify) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(template.createFormInserter(BodyInserters.fromFormData("access_token", token),context)) + .exchange() + .flatMap(clientResponse -> clientResponse.bodyToMono(HashMap.class)) + .as(this::checkResult)) + .then(); + } + + private Mono checkResult(Mono msg) { + return msg.doOnNext(map -> { + String code = String.valueOf(map.get("errcode")); + if ("0".equals(code)) { + log.info("发送钉钉通知成功"); + } else { + log.warn("发送钉钉通知失败:{}", map); + throw new BusinessException("发送钉钉通知失败:" + map.get("errmsg"), code); + } + }); + } + + private Mono getToken() { + if (System.currentTimeMillis() - refreshTokenTime > tokenTimeOut || accessToken.get() == null) { + return requestToken(); + } + return Mono.just(accessToken.get()); + } + + private Mono requestToken() { + return client + .get() + .uri(UriComponentsBuilder.fromUriString(tokenApi) + .queryParam("appkey", properties.getAppKey()) + .queryParam("appsecret", properties.getAppSecret()) + .build().toUri()) + .exchange() + .flatMap(resp -> resp.bodyToMono(HashMap.class)) + .map(map -> { + if (map.containsKey("access_token")) { + return map.get("access_token"); + } + throw new BusinessException("获取Token失败:" + map.get("errmsg"), String.valueOf(map.get("errcode"))); + }) + .cast(String.class) + .doOnNext((r) -> { + refreshTokenTime = System.currentTimeMillis(); + accessToken.set(r); + }); + } + + @Nonnull + @Override + public Mono close() { + accessToken.set(null); + refreshTokenTime = 0; + return Mono.empty(); + } +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierProvider.java b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierProvider.java new file mode 100644 index 00000000..0cf71647 --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierProvider.java @@ -0,0 +1,80 @@ +package org.jetlinks.community.notify.dingtalk; + +import com.alibaba.fastjson.JSON; +import org.hswebframework.web.bean.FastBeanCopier; +import org.hswebframework.web.validator.ValidatorUtils; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.core.metadata.DefaultConfigMetadata; +import org.jetlinks.core.metadata.types.BooleanType; +import org.jetlinks.core.metadata.types.StringType; +import org.jetlinks.community.ConfigMetadataConstants; +import org.jetlinks.community.notify.*; +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 org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +@Component +public class DingTalkNotifierProvider implements NotifierProvider, TemplateProvider { + + private WebClient client = WebClient.create(); + + private final TemplateManager templateManager; + + public DingTalkNotifierProvider(TemplateManager templateManager) { + this.templateManager = templateManager; + } + + public static final DefaultConfigMetadata notifierConfig = new DefaultConfigMetadata("通知配置", "") + .add("appKey", "appKey", "", new StringType().expand(ConfigMetadataConstants.required.value(true))) + .add("appSecret", "appSecret", "", new StringType()); + + public static final DefaultConfigMetadata templateConfig = new DefaultConfigMetadata("模版配置", "") + .add("agentId", "应用ID", "", new StringType().expand(ConfigMetadataConstants.required.value(true))) + .add("userIdList", "收信人ID", "与部门ID不能同时为空", new StringType()) + .add("departmentIdList", "收信部门ID", "与收信人ID不能同时为空", new StringType()) + .add("toAllUser", "全部用户", "推送到全部用户", new BooleanType()) + .add("message", "内容", "最大不超过500字", new StringType().expand(ConfigMetadataConstants.maxLength.value(500L))); + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.dingTalk; + } + + @Nonnull + @Override + public Provider getProvider() { + return DingTalkProvider.dingTalkMessage; + } + + @Override + public Mono createTemplate(TemplateProperties properties) { + return Mono.fromSupplier(() -> { + return ValidatorUtils.tryValidate(JSON.parseObject(properties.getTemplate(), DingTalkMessageTemplate.class)); + }); + } + + @Nonnull + @Override + public Mono createNotifier(@Nonnull NotifierProperties properties) { + return Mono.defer(() -> { + DingTalkProperties dingTalkProperties = FastBeanCopier.copy(properties.getConfiguration(), new DingTalkProperties()); + return Mono.just(new DingTalkNotifier(client, ValidatorUtils.tryValidate(dingTalkProperties), templateManager)); + }); + } + + @Override + public ConfigMetadata getNotifierConfigMetadata() { + return notifierConfig; + } + + @Override + public ConfigMetadata getTemplateConfigMetadata() { + return templateConfig; + } +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProperties.java b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProperties.java new file mode 100644 index 00000000..df6fd840 --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProperties.java @@ -0,0 +1,19 @@ +package org.jetlinks.community.notify.dingtalk; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +public class DingTalkProperties { + + @NotBlank(message = "appKey不能为空") + private String appKey; + + @NotBlank(message = "appSecret不能为空") + private String appSecret; + + +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProvider.java b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProvider.java new file mode 100644 index 00000000..87adbe3b --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/main/java/org/jetlinks/community/notify/dingtalk/DingTalkProvider.java @@ -0,0 +1,20 @@ +package org.jetlinks.community.notify.dingtalk; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jetlinks.community.notify.Provider; + +@Getter +@AllArgsConstructor +public enum DingTalkProvider implements Provider { + dingTalkMessage("钉钉消息通知") + ; + + private String name; + + @Override + public String getId() { + return name(); + } + +} diff --git a/jetlinks-components/notify-component/notify-dingtalk/src/test/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierTest.java b/jetlinks-components/notify-component/notify-dingtalk/src/test/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierTest.java new file mode 100644 index 00000000..986de38b --- /dev/null +++ b/jetlinks-components/notify-component/notify-dingtalk/src/test/java/org/jetlinks/community/notify/dingtalk/DingTalkNotifierTest.java @@ -0,0 +1,39 @@ +package org.jetlinks.community.notify.dingtalk; + +import org.jetlinks.core.Values; +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.test.StepVerifier; + +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +class DingTalkNotifierTest { + + + @Test + void test(){ + DingTalkProperties properties=new DingTalkProperties(); + properties.setAppKey("dingd2rgqrqnbvgbvi9xZQ"); + properties.setAppSecret("rfSNLse4SI1CeAo6aV8cPRzig8HZACwRR6XGS_feOje-3M7rE68WjL9LKWTgko2R"); + + DingTalkMessageTemplate messageTemplate=new DingTalkMessageTemplate(); + + messageTemplate.setAgentId("335474263"); + messageTemplate.setMessage("test"+System.currentTimeMillis()); + messageTemplate.setUserIdList("0458215455697857"); + + DingTalkNotifier notifier=new DingTalkNotifier( + WebClient.builder().build(),properties,null + ); + + notifier.send(messageTemplate, Values.of(new HashMap<>())) + .as(StepVerifier::create) + .expectComplete() + .verify(); + + + } + +} \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-email/pom.xml b/jetlinks-components/notify-component/notify-email/pom.xml new file mode 100644 index 00000000..66d3852b --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/pom.xml @@ -0,0 +1,44 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + + 4.0.0 + + notify-email + + + org.jetlinks + rule-engine-support + ${jetlinks.version} + + + + org.jetlinks.community + notify-core + ${project.version} + + + org.springframework + spring-context-support + + + + javax.mail + mail + 1.4.7 + + + + org.jsoup + jsoup + 1.11.3 + + + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/EmailProvider.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/EmailProvider.java new file mode 100644 index 00000000..74f6c3d4 --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/EmailProvider.java @@ -0,0 +1,20 @@ +package org.jetlinks.community.notify.email; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jetlinks.community.notify.Provider; + +@Getter +@AllArgsConstructor +public enum EmailProvider implements Provider { + + embedded("默认") + ; + + private String name; + + @Override + public String getId() { + return name(); + } +} diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java new file mode 100644 index 00000000..b46ee1cf --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java @@ -0,0 +1,198 @@ +package org.jetlinks.community.notify.email.embedded; + +import com.alibaba.fastjson.JSONObject; +import io.vavr.control.Try; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.exception.BusinessException; +import org.hswebframework.web.id.IDGenerator; +import org.hswebframework.web.utils.ExpressionUtils; +import org.hswebframework.web.validator.ValidatorUtils; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.*; +import org.jetlinks.community.notify.email.EmailProvider; +import org.jetlinks.community.notify.template.TemplateManager; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.InputStreamSource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import javax.annotation.Nonnull; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeUtility; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * 使用javax.mail进行邮件发送 + * + * @author bsetfeng + * @author zhouhao + * @since 1.0 + **/ +@Slf4j +public class DefaultEmailNotifier extends AbstractNotifier { + + + @Getter + @Setter + private JavaMailSender javaMailSender; + + @Getter + @Setter + private String sender; + + public static Scheduler scheduler = Schedulers.newElastic("email-notifier"); + + public DefaultEmailNotifier(NotifierProperties properties, TemplateManager templateManager) { + super(templateManager); + + DefaultEmailProperties emailProperties = new JSONObject(properties.getConfiguration()) + .toJavaObject(DefaultEmailProperties.class); + ValidatorUtils.tryValidate(emailProperties); + + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(emailProperties.getHost()); + mailSender.setPort(emailProperties.getPort()); + mailSender.setUsername(emailProperties.getUsername()); + mailSender.setPassword(emailProperties.getPassword()); + mailSender.setJavaMailProperties(emailProperties.createJavaMailProperties()); + this.sender = emailProperties.getSender(); + this.javaMailSender = mailSender; + } + + @Nonnull + @Override + public Mono send(@Nonnull EmailTemplate template, @Nonnull Values context) { + return Mono.just(template) + .map(temp -> convert(temp, context.getAllValues())) + .flatMap(temp -> doSend(temp, template.getSendTo())); + } + + @Nonnull + @Override + public Mono close() { + return Mono.empty(); + } + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.email; + } + + @Nonnull + @Override + public Provider getProvider() { + return EmailProvider.embedded; + } + + protected Mono doSend(ParsedEmailTemplate template, List sendTo) { + return Mono + .fromCallable(() -> { + MimeMessage mimeMessage = this.javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8"); + + helper.setFrom(this.sender); + helper.setTo(sendTo.toArray(new String[0])); + helper.setSubject(template.getSubject()); + helper.setText(new String(template.getText().getBytes(), StandardCharsets.UTF_8), true); + + return Flux.fromIterable(template.getAttachments().entrySet()) + .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue()))) + .doOnNext(tp -> Try.run(() -> helper.addAttachment(MimeUtility.encodeText(tp.getT1()), tp.getT2())).get()) + .then( + Flux.fromIterable(template.getImages().entrySet()) + .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue()))) + .doOnNext(tp -> Try.run(() -> helper.addInline(tp.getT1(), tp.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE)).get()) + .then() + ).thenReturn(mimeMessage) + ; + + }) + .publishOn(scheduler) + .flatMap(Function.identity()) + .doOnNext(message -> this.javaMailSender.send(message)) + .then() + ; + } + + + protected Mono convertResource(String resource) { + if (resource.startsWith("http")) { + return WebClient.create() + .get() + .uri(resource) + .accept(MediaType.APPLICATION_OCTET_STREAM) + .exchange() + .flatMap(rep -> rep.bodyToMono(Resource.class)); + } else { + try { + return Mono.just(new InputStreamResource(new FileInputStream(resource))); + } catch (FileNotFoundException e) { + return Mono.error(e); + } + } + } + + + protected ParsedEmailTemplate convert(EmailTemplate template, Map context) { + String subject = template.getSubject(); + String text = template.getText(); + if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(text)) { + throw new BusinessException("模板内容错误,text 或者 subject 不能为空."); + } + String sendText = render(text, context); + List tempAttachments = template.getAttachments(); + Map attachments = new HashMap<>(); + if (tempAttachments != null) { + for (EmailTemplate.Attachment tempAttachment : tempAttachments) { + attachments.put(tempAttachment.getName(), render(tempAttachment.getLocation(), context)); + } + } + return ParsedEmailTemplate.builder() + .attachments(attachments) + .images(extractSendTextImage(sendText)) + .text(sendText) + .subject(render(subject, context)) + .build(); + } + + + private Map extractSendTextImage(String sendText) { + Map images = new HashMap<>(); + Document doc = Jsoup.parse(sendText); + for (Element src : doc.getElementsByTag("img")) { + String s = src.attr("src"); + if (s.startsWith("http")) { + continue; + } + String tempKey = IDGenerator.MD5.generate(); + src.attr("src", "cid:".concat(tempKey)); + images.put(tempKey, s); + } + return images; + } + + private String render(String str, Map context) { + return ExpressionUtils.analytical(str, context, "spel"); + } + +} diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifierProvider.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifierProvider.java new file mode 100644 index 00000000..c344f56d --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifierProvider.java @@ -0,0 +1,122 @@ +package org.jetlinks.community.notify.email.embedded; + +import com.alibaba.fastjson.JSON; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.core.metadata.DefaultConfigMetadata; +import org.jetlinks.core.metadata.SimplePropertyMetadata; +import org.jetlinks.core.metadata.types.*; +import org.jetlinks.community.notify.*; +import org.jetlinks.community.notify.email.EmailProvider; +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; + +import static org.jetlinks.community.ConfigMetadataConstants.*; + +@Component +public class DefaultEmailNotifierProvider implements NotifierProvider, TemplateProvider { + + private final TemplateManager templateManager; + + public DefaultEmailNotifierProvider(TemplateManager templateManager) { + this.templateManager = templateManager; + } + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.email; + } + + @Nonnull + @Override + public Provider getProvider() { + return EmailProvider.embedded; + } + + public static final DefaultConfigMetadata templateConfig; + + public static final DefaultConfigMetadata notifierConfig; + + static { + { + SimplePropertyMetadata name = new SimplePropertyMetadata(); + name.setId("name"); + name.setName("文件名"); + name.setValueType(new StringType()); + + SimplePropertyMetadata location = new SimplePropertyMetadata(); + location.setId("location"); + location.setName("文件地址"); + location.setValueType(new FileType() + .bodyType(FileType.BodyType.url) + .expand(allowInput.value(true))); + + templateConfig = new DefaultConfigMetadata("邮件模版", "") + .add("subject", "标题", "标题,可使用变量", new StringType().expand(maxLength.value(255L))) + .add("text", "内容", "", new StringType().expand(maxLength.value(5120L), isRichText.value(true))) + .add("sendTo", "收件人", "", new ArrayType().elementType(new StringType())) + .add("attachments", "附件列表", "", new ArrayType() + .elementType(new ObjectType() + .addPropertyMetadata(name) + .addPropertyMetadata(location))); + } + + { + SimplePropertyMetadata name = new SimplePropertyMetadata(); + name.setId("name"); + name.setName("配置名称"); + name.setValueType(new StringType()); + + SimplePropertyMetadata value = new SimplePropertyMetadata(); + value.setId("value"); + value.setName("配置值"); + value.setValueType(new StringType()); + + SimplePropertyMetadata description = new SimplePropertyMetadata(); + description.setId("description"); + description.setName("说明"); + description.setValueType(new StringType()); + + notifierConfig = new DefaultConfigMetadata("邮件配置", "") + .add("host", "服务器地址", "例如: pop3.qq.com", new StringType().expand(maxLength.value(255L))) + .add("port", "端口", "", new IntType().min(0).max(65536)) + .add("sender", "发件人", "默认和用户名相同", new StringType()) + .add("username", "用户名", "", new StringType()) + .add("password", "密码", "", new PasswordType()) + .add("properties", "其他配置", "", new ArrayType() + .elementType(new ObjectType() + .addPropertyMetadata(name) + .addPropertyMetadata(value) + .addPropertyMetadata(description))); + } + + + } + + @Override + public ConfigMetadata getNotifierConfigMetadata() { + return notifierConfig; + } + + @Override + public ConfigMetadata getTemplateConfigMetadata() { + return templateConfig; + } + + @Nonnull + @Override + public Mono createNotifier(@Nonnull NotifierProperties properties) { + return Mono.fromSupplier(() -> new DefaultEmailNotifier(properties, templateManager)); + } + + @Override + public Mono createTemplate(TemplateProperties properties) { + + return Mono.fromSupplier(() -> JSON.parseObject(properties.getTemplate(), EmailTemplate.class)); + } +} diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailProperties.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailProperties.java new file mode 100644 index 00000000..1dd9914e --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailProperties.java @@ -0,0 +1,49 @@ +package org.jetlinks.community.notify.email.embedded; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +@Getter +@Setter +public class DefaultEmailProperties { + private String host; + + private int port; + + private String username; + + private String password; + + private String sender; + + private List properties; + + @Getter + @Setter + public static class ConfigProperty { + + private String name; + + private String value; + + private String description; + } + + public Properties createJavaMailProperties() { + + Properties properties = new Properties(); + + if (this.properties != null) { + for (ConfigProperty property : this.properties) { + properties.put(property.getName(), property.getValue()); + } + } + + return properties; + } + +} diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/EmailTemplate.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/EmailTemplate.java new file mode 100644 index 00000000..f7789c07 --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/EmailTemplate.java @@ -0,0 +1,30 @@ +package org.jetlinks.community.notify.email.embedded; + +import lombok.Getter; +import lombok.Setter; +import org.jetlinks.community.notify.template.Template; + +import java.util.List; + +@Getter +@Setter +public class EmailTemplate implements Template { + + private String subject; + + private String text; + + private List attachments; + + private List sendTo; + + @Getter + @Setter + public static class Attachment { + + private String name; + + private String location; + + } +} diff --git a/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/ParsedEmailTemplate.java b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/ParsedEmailTemplate.java new file mode 100644 index 00000000..1b7fcf76 --- /dev/null +++ b/jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/ParsedEmailTemplate.java @@ -0,0 +1,29 @@ +package org.jetlinks.community.notify.email.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * @author bsetfeng + * @since 1.0 + **/ +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ParsedEmailTemplate { + + //附件 key:附件名称 value:附件uri + private Map attachments; + + //图片 key:text中图片占位符 value:图片uri + private Map images; + + private String subject; + + private String text; +} diff --git a/jetlinks-components/notify-component/notify-sms/pom.xml b/jetlinks-components/notify-component/notify-sms/pom.xml new file mode 100644 index 00000000..fee40261 --- /dev/null +++ b/jetlinks-components/notify-component/notify-sms/pom.xml @@ -0,0 +1,41 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + + 4.0.0 + + notify-sms + + + + org.hswebframework + hsweb-easy-orm-rdb + + + + org.hswebframework.web + hsweb-starter + ${hsweb.framework.version} + + + org.jetlinks + rule-engine-support + + + + org.springframework + spring-context-indexer + + + + org.jetlinks.community + notify-core + ${project.version} + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/Hy2046SmsSenderProvider.java b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/Hy2046SmsSenderProvider.java new file mode 100644 index 00000000..85049c6b --- /dev/null +++ b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/Hy2046SmsSenderProvider.java @@ -0,0 +1,168 @@ +package org.jetlinks.community.notify.sms.provider; + +import com.alibaba.fastjson.JSON; +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 createTemplate(TemplateProperties properties) { + return Mono.fromSupplier(() -> JSON.parseObject(properties.getTemplate(), PlainTextSmsTemplate.class)); + } + + @Nonnull + @Override + public Mono 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(userId, username, password)); + }); + } + + @Override + public String getId() { + return "hy2046"; + } + + @Override + public String getName() { + return "宏衍2046"; + } + + class Hy2046SmsSender extends AbstractNotifier { + + String userId; + String username; + String password; + + public Hy2046SmsSender(String userId, String username, String password) { + super(templateManager); + this.userId = userId; + 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 close() { + return Mono.empty(); + } + + + @Nonnull + @Override + public Mono 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 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(); + } + + } +} + diff --git a/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/PlainTextSmsTemplate.java b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/PlainTextSmsTemplate.java new file mode 100644 index 00000000..bd78c1dd --- /dev/null +++ b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/PlainTextSmsTemplate.java @@ -0,0 +1,40 @@ +package org.jetlinks.community.notify.sms.provider; + +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.web.utils.ExpressionUtils; +import org.jetlinks.core.metadata.DefaultConfigMetadata; +import org.jetlinks.core.metadata.types.ArrayType; +import org.jetlinks.core.metadata.types.StringType; +import org.jetlinks.community.ConfigMetadataConstants; +import org.jetlinks.community.notify.template.Template; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@Getter +@Setter +public class PlainTextSmsTemplate implements Template { + + public static final DefaultConfigMetadata templateConfig = new DefaultConfigMetadata("模版配置", "") + .add("text", "短信内容", "短信内容,支持使用变量:${ }", new StringType() + .expand(ConfigMetadataConstants.maxLength.value(512L))) + .add("sendTo", "收件人", "", new ArrayType().elementType(new StringType())); + + private String text; + + private List sendTo; + + public String getTextSms(Map context) { + return ExpressionUtils.analytical(text, context, "spel"); + } + + public String[] getSendTo(Map context) { + + return sendTo.stream() + .map(str -> ExpressionUtils.analytical(str, context, "spel")).toArray(String[]::new); + + } + +} diff --git a/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/TestSmsProvider.java b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/TestSmsProvider.java new file mode 100644 index 00000000..483d5662 --- /dev/null +++ b/jetlinks-components/notify-component/notify-sms/src/main/java/org/jetlinks/community/notify/sms/provider/TestSmsProvider.java @@ -0,0 +1,81 @@ +package org.jetlinks.community.notify.sms.provider; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +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; +import org.jetlinks.community.notify.template.TemplateProvider; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +@Slf4j +@Component +@Profile({"dev", "test"}) +public class TestSmsProvider extends AbstractNotifier implements NotifierProvider, TemplateProvider, Provider { + + public TestSmsProvider(TemplateManager templateManager) { + super(templateManager); + } + + @Override + @Nonnull + public NotifyType getType() { + return DefaultNotifyType.sms; + } + + @Override + @Nonnull + public Provider getProvider() { + return this; + } + + @Override + public Mono createTemplate(TemplateProperties properties) { + return Mono.fromSupplier(() -> JSON.parseObject(properties.getTemplate(), PlainTextSmsTemplate.class)); + } + + @Override + public ConfigMetadata getTemplateConfigMetadata() { + return PlainTextSmsTemplate.templateConfig; + } + + @Override + public ConfigMetadata getNotifierConfigMetadata() { + return null; + } + + @Nonnull + @Override + public Mono send(@Nonnull PlainTextSmsTemplate template, @Nonnull Values context) { + return Mono.fromRunnable(() -> log.info("send sms [{}] message:{}", template.getSendTo(context.getAllValues()), template.getTextSms(context.getAllValues()))); + } + + @Nonnull + @Override + public Mono close() { + return Mono.empty(); + } + + @Nonnull + @Override + public Mono createNotifier(@Nonnull NotifierProperties properties) { + return Mono.just(this); + } + + @Override + public String getId() { + return "test"; + } + + @Override + public String getName() { + return "测试"; + } +} diff --git a/jetlinks-components/notify-component/notify-voice/README.md b/jetlinks-components/notify-component/notify-voice/README.md new file mode 100644 index 00000000..c58d63e4 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/README.md @@ -0,0 +1,46 @@ +# 语音通知 + +用于发送电话语音通知. + + + +## API + +1. org.jetlinks.community.notify.voice.VoiceNotifierManager + +例子: +```java + +motifierManager.getNotifier(notifierId) //获取通知器 + .flatMap(notifier->notifier.send(templateId,contextMap))//发送 + .doOnError(err->log.error("发送失败",err)) //失败 + .subscribe(ignore->log.info("发送成功")); //成功 + +``` + +## 拓展自定义服务商 + +实现接口: `org.jetlinks.community.notify.voice.provider.VoiceNotifierProvider` + + +## 阿里云(`aliyun`) + +系统已实现阿里云语音通知`org.jetlinks.community.notify.voice.supports.aliyun.AliyunVoiceNotifierProvider` + +配置(`NotifierProperties.configuration`): + +| Key | 示例 | +| ---- | ---- | +| regionId | cn-hangzhou | +| accessKeyId | LTAI4Dj2oThQnZYMAwTSj1F8 | +| secret | FeZ2nQZKM635IsPDudZOaQ5aDHMFeT | + +模版配置(`TemplateProperties.template`) + +| Key | 示例 | +| ---- | ---- | +| ttsCode | TTS_123456 | +| calledShowNumbers | 02387673801 | +| calledNumber | 18502114022 | +| playTimes | 1 | + diff --git a/jetlinks-components/notify-component/notify-voice/pom.xml b/jetlinks-components/notify-component/notify-voice/pom.xml new file mode 100644 index 00000000..0da7bee2 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/pom.xml @@ -0,0 +1,27 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + notify-voice + + + + com.aliyun + aliyun-java-sdk-core + 4.1.0 + + + ${project.groupId} + notify-core + ${project.version} + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceNotifierConfiguration.java b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceNotifierConfiguration.java new file mode 100644 index 00000000..45eeb701 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceNotifierConfiguration.java @@ -0,0 +1,19 @@ +package org.jetlinks.community.notify.voice; + +import org.jetlinks.community.notify.template.TemplateManager; +import org.jetlinks.community.notify.voice.aliyun.AliyunNotifierProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class VoiceNotifierConfiguration { + + + @Bean + @ConditionalOnBean(TemplateManager.class) + public AliyunNotifierProvider aliyunNotifierProvider(TemplateManager templateManager) { + return new AliyunNotifierProvider(templateManager); + } + +} diff --git a/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceProvider.java b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceProvider.java new file mode 100644 index 00000000..51fed4c9 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/VoiceProvider.java @@ -0,0 +1,20 @@ +package org.jetlinks.community.notify.voice; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jetlinks.community.notify.Provider; + +@Getter +@AllArgsConstructor +public enum VoiceProvider implements Provider { + + aliyun("阿里云") + ; + + private String name; + + @Override + public String getId() { + return name(); + } +} diff --git a/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunNotifierProvider.java b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunNotifierProvider.java new file mode 100644 index 00000000..f5e93440 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunNotifierProvider.java @@ -0,0 +1,79 @@ +package org.jetlinks.community.notify.voice.aliyun; + +import com.alibaba.fastjson.JSON; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.validator.ValidatorUtils; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.core.metadata.DefaultConfigMetadata; +import org.jetlinks.core.metadata.types.IntType; +import org.jetlinks.core.metadata.types.StringType; +import org.jetlinks.community.notify.*; +import org.jetlinks.community.notify.template.TemplateManager; +import org.jetlinks.community.notify.template.TemplateProperties; +import org.jetlinks.community.notify.template.TemplateProvider; +import org.jetlinks.community.notify.voice.VoiceProvider; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +/** + * + * 阿里云语音通知服务 + * + * + * @author zhouhao + * @since 1.0 + */ +@Slf4j +@AllArgsConstructor +public class AliyunNotifierProvider implements NotifierProvider, TemplateProvider { + + private TemplateManager templateManager; + + @Nonnull + @Override + public Provider getProvider() { + return VoiceProvider.aliyun; + } + + public static final DefaultConfigMetadata templateConfig = new DefaultConfigMetadata("阿里云语音模版", + "https://help.aliyun.com/document_detail/114035.html?spm=a2c4g.11186623.6.561.3d1b3c2dGMXAmk") + .add("ttsCode", "模版ID", "ttsCode", new StringType()) + .add("calledShowNumbers", "被叫显号", "", new StringType()) + .add("CalledNumber", "被叫号码", "", new StringType()) + .add("PlayTimes", "播放次数", "", new IntType()); + + public static final DefaultConfigMetadata notifierConfig = new DefaultConfigMetadata("阿里云通知配置", + "https://help.aliyun.com/document_detail/114035.html?spm=a2c4g.11186623.6.561.3d1b3c2dGMXAmk") + .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 createTemplate(TemplateProperties properties) { + return Mono.fromCallable(() -> ValidatorUtils.tryValidate(JSON.parseObject(properties.getTemplate(), AliyunVoiceTemplate.class))); + } + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.voice; + } + + @Nonnull + @Override + public Mono createNotifier(@Nonnull NotifierProperties properties) { + return Mono.fromSupplier(() -> new AliyunVoiceNotifier(properties, templateManager)); + } +} diff --git a/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceNotifier.java b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceNotifier.java new file mode 100644 index 00000000..aabd5a86 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceNotifier.java @@ -0,0 +1,109 @@ +package org.jetlinks.community.notify.voice.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.extern.slf4j.Slf4j; +import org.hswebframework.web.exception.BusinessException; +import org.hswebframework.web.logger.ReactiveLogger; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.*; +import org.jetlinks.community.notify.template.TemplateManager; +import org.jetlinks.community.notify.voice.VoiceProvider; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.Objects; + +@Slf4j +public class AliyunVoiceNotifier extends AbstractNotifier { + + private IAcsClient client; + private String domain = "dyvmsapi.aliyuncs.com"; + private String regionId = "cn-hangzhou"; + private int connectTimeout = 1000; + private int readTimeout = 5000; + + public AliyunVoiceNotifier(NotifierProperties profile, TemplateManager templateManager) { + super(templateManager); + Map 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", "dyvmsapi.aliyuncs.com"); + } + + public AliyunVoiceNotifier(IClientProfile profile, TemplateManager templateManager) { + this(new DefaultAcsClient(profile), templateManager); + } + + public AliyunVoiceNotifier(IAcsClient client, TemplateManager templateManager) { + super(templateManager); + this.client = client; + } + + @Override + @Nonnull + public NotifyType getType() { + return DefaultNotifyType.voice; + } + + @Nonnull + @Override + public Provider getProvider() { + return VoiceProvider.aliyun; + } + + @Override + @Nonnull + public Mono send(@Nonnull AliyunVoiceTemplate template, @Nonnull Values context) { + + return Mono.defer(() -> { + try { + CommonRequest request = new CommonRequest(); + request.setMethod(MethodType.POST); + request.setDomain(domain); + request.setVersion("2017-05-25"); + request.setAction("SingleCallByTts"); + request.setConnectTimeout(connectTimeout); + request.setReadTimeout(readTimeout); + request.putQueryParameter("RegionId", regionId); + request.putQueryParameter("CalledShowNumber", template.getCalledShowNumbers()); + request.putQueryParameter("CalledNumber", template.getCalledNumber()); + request.putQueryParameter("TtsCode", template.getTtsCode()); + request.putQueryParameter("PlayTimes", String.valueOf(template.getPlayTimes())); + request.putQueryParameter("TtsParam", 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); + })); + } + + @Override + @Nonnull + public Mono close() { + return Mono.fromRunnable(client::shutdown); + } +} diff --git a/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceTemplate.java b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceTemplate.java new file mode 100644 index 00000000..894a6245 --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/main/java/org/jetlinks/community/notify/voice/aliyun/AliyunVoiceTemplate.java @@ -0,0 +1,37 @@ +package org.jetlinks.community.notify.voice.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; + +/** + * 阿里云通知模版 + * + * https://help.aliyun.com/document_detail/114035.html?spm=a2c4g.11186623.6.561.3d1b3c2dGMXAmk + */ +@Getter +@Setter +public class AliyunVoiceTemplate implements Template { + + @NotBlank(message = "[ttsCode]不能为空") + private String ttsCode; + + @NotBlank(message = "[calledShowNumbers]不能为空") + private String calledShowNumbers; + + @NotBlank(message = "[calledNumber]不能为空") + private String calledNumber; + + private int playTimes = 1; + + private Map ttsParam; + + public String createTtsParam(Map ctx){ + + return JSON.toJSONString(ctx); + } +} diff --git a/jetlinks-components/notify-component/notify-voice/src/test/java/org/jetlinks/community/notify/voice/supports/aliyun/AliyunVoiceNotifierTest.java b/jetlinks-components/notify-component/notify-voice/src/test/java/org/jetlinks/community/notify/voice/supports/aliyun/AliyunVoiceNotifierTest.java new file mode 100644 index 00000000..b4837c8f --- /dev/null +++ b/jetlinks-components/notify-component/notify-voice/src/test/java/org/jetlinks/community/notify/voice/supports/aliyun/AliyunVoiceNotifierTest.java @@ -0,0 +1,67 @@ +/* +package org.jetlinks.community.notify.voice.supports.aliyun; + +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.NotifierProperties; +import org.jetlinks.community.notify.NotifyType; +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.voice.aliyun.AliyunVoiceNotifier; +import org.jetlinks.community.notify.voice.aliyun.AliyunVoiceTemplate; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import javax.annotation.Nonnull; +import java.util.HashMap; + +class AliyunVoiceNotifierTest { + + @Test + void test() { + NotifierProperties properties = new NotifierProperties(); + properties.setId("test"); + properties.setName("test"); + properties.setProvider("aliyun"); + properties.setConfiguration(new HashMap() {{ + put("regionId", "cn-hangzhou"); + put("accessKeyId", "LTAI4Fj2oYhjnYYMAwTSj1F8"); + put("secret", "tea2nKEKM635IsPDud0OaZ5aIHM8eG"); + }}); + + AliyunVoiceNotifier notifier = new AliyunVoiceNotifier( + properties, new TemplateManager() { + @Nonnull + @Override + public Mono getTemplate(@Nonnull NotifyType type, @Nonnull String id) { + return Mono.empty(); + } + + @Nonnull + @Override + public Mono createTemplate(@Nonnull NotifyType type, @Nonnull TemplateProperties properties) { + return Mono.empty(); + } + + @Override + public Mono reload(String templateId) { + return Mono.empty(); + } + } + ); + AliyunVoiceTemplate template = new AliyunVoiceTemplate(); + template.setCalledShowNumbers("02566040637"); + template.setPlayTimes(1); + template.setTtsCode("TTS_176535661"); + template.setCalledNumber("18502314099"); + notifier.send(template, Values.of(new HashMap() { + { + put("busi","测试"); + put("address","测试"); + + } + })).as(StepVerifier::create) + .verifyComplete(); + } +}*/ diff --git a/jetlinks-components/notify-component/notify-wechat/pom.xml b/jetlinks-components/notify-component/notify-wechat/pom.xml new file mode 100644 index 00000000..b9e31e30 --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/pom.xml @@ -0,0 +1,28 @@ + + + + notify-component + org.jetlinks.community + 1.0-SNAPSHOT + + 4.0.0 + + notify-wechat + + + + + org.jetlinks.community + notify-core + ${project.version} + + + + org.springframework.boot + spring-boot-starter-webflux + + + + \ No newline at end of file diff --git a/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatCorpProperties.java b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatCorpProperties.java new file mode 100644 index 00000000..4b8931d0 --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatCorpProperties.java @@ -0,0 +1,19 @@ +package org.jetlinks.community.notify.wechat; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +@Getter +@Setter +public class WechatCorpProperties { + + @NotBlank(message = "corpId不能为空") + private String corpId; + + @NotBlank(message = "corpSecret不能为空") + private String corpSecret; + + +} diff --git a/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatMessageTemplate.java b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatMessageTemplate.java new file mode 100644 index 00000000..e0de3b1b --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatMessageTemplate.java @@ -0,0 +1,102 @@ +package org.jetlinks.community.notify.wechat; + +import com.alibaba.fastjson.JSONObject; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.hswebframework.web.utils.ExpressionUtils; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.template.Template; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.validation.constraints.NotBlank; +import java.util.Collections; + +@Getter +@Setter +public class WechatMessageTemplate implements Template { + + /** + * 应用ID + */ + @NotBlank(message = "[agentId]不能为空") + private String agentId; + + private String toUser; + + private String toParty; + + private String toTag; + + @NotBlank(message = "[message]不能为空") + private String message; + + + @SneakyThrows + public BodyInserters.FormInserter createFormInserter(BodyInserters.FormInserter inserter, Values context) { + inserter.with("agentid", this.getAgentId()) + .with("msgtype","text") + .with("text",this.createMessage(context)); + if (StringUtils.hasText(toUser)) { + inserter.with("touser", this.createUserIdList(context)); + } + if (StringUtils.hasText(toParty)) { + inserter.with("toparty", this.createDepartmentIdList(context)); + } + return inserter; + + } + + public String createJsonRequest(Values context){ + JSONObject json=new JSONObject(); + json.put("agentid",getAgentId()); + json.put("msgtype","text"); + json.put("text",Collections.singletonMap("content",ExpressionUtils.analytical(message, context.getAllValues(), "spel"))); + + if (StringUtils.hasText(toUser)) { + json.put("touser", this.createUserIdList(context)); + } + if (StringUtils.hasText(toParty)) { + json.put("toparty", this.createDepartmentIdList(context)); + } + + return json.toJSONString(); + } + + + public UriComponentsBuilder createUriParameter(UriComponentsBuilder builder, Values context){ + builder.queryParam("agentid", this.getAgentId()) + .queryParam("msgtype","text") + .queryParam("text",this.createMessage(context)); + if (StringUtils.hasText(toUser)) { + builder.queryParam("touser", this.createUserIdList(context)); + } + if (StringUtils.hasText(toParty)) { + builder.queryParam("toparty", this.createDepartmentIdList(context)); + } + return builder; + } + + public String createUserIdList(Values context) { + if (StringUtils.isEmpty(toUser)) { + return toUser; + } + return ExpressionUtils.analytical(toUser, context.getAllValues(), "spel"); + } + + public String createDepartmentIdList(Values context) { + if (StringUtils.isEmpty(toParty)) { + return toParty; + } + return ExpressionUtils.analytical(toParty, context.getAllValues(), "spel"); + } + + public String createMessage(Values context) { + JSONObject json = new JSONObject(); + json.put("content", ExpressionUtils.analytical(message, context.getAllValues(), "spel")); + return json.toJSONString(); + } + +} diff --git a/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatNotifierProvider.java b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatNotifierProvider.java new file mode 100644 index 00000000..2a246beb --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatNotifierProvider.java @@ -0,0 +1,77 @@ +package org.jetlinks.community.notify.wechat; + +import com.alibaba.fastjson.JSON; +import org.hswebframework.web.bean.FastBeanCopier; +import org.hswebframework.web.validator.ValidatorUtils; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.core.metadata.DefaultConfigMetadata; +import org.jetlinks.core.metadata.types.StringType; +import org.jetlinks.community.ConfigMetadataConstants; +import org.jetlinks.community.notify.*; +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 org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +@Component +public class WechatNotifierProvider implements NotifierProvider, TemplateProvider { + + private WebClient client = WebClient.create(); + + private final TemplateManager templateManager; + + public WechatNotifierProvider(TemplateManager templateManager) { + this.templateManager = templateManager; + } + + public static final DefaultConfigMetadata notifierConfig = new DefaultConfigMetadata("通知配置", "") + .add("corpId", "corpId", "", new StringType().expand(ConfigMetadataConstants.required.value(true))) + .add("corpSecret", "corpSecret", "", new StringType()); + + public static final DefaultConfigMetadata templateConfig = new DefaultConfigMetadata("模版配置", "") + .add("agentId", "应用ID", "", new StringType().expand(ConfigMetadataConstants.required.value(true))) + .add("toUser", "收信人ID", "与部门ID不能同时为空", new StringType()) + .add("toParty", "收信部门ID", "与收信人ID不能同时为空", new StringType()) + .add("toTag", "按标签推送", "", new StringType()) + .add("message", "内容", "最大不超过500字", new StringType().expand(ConfigMetadataConstants.maxLength.value(500L))); + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.weixin; + } + + @Nonnull + @Override + public Provider getProvider() { + return WechatProvider.corpMessage; + } + + @Override + public Mono createTemplate(TemplateProperties properties) { + return Mono.fromSupplier(() -> ValidatorUtils.tryValidate(JSON.parseObject(properties.getTemplate(), WechatMessageTemplate.class))); + } + + @Nonnull + @Override + public Mono createNotifier(@Nonnull NotifierProperties properties) { + return Mono.defer(() -> { + WechatCorpProperties wechatCorpProperties = FastBeanCopier.copy(properties.getConfiguration(), new WechatCorpProperties()); + return Mono.just(new WeixinCorpNotifier(client, ValidatorUtils.tryValidate(wechatCorpProperties), templateManager)); + }); + } + + @Override + public ConfigMetadata getNotifierConfigMetadata() { + return notifierConfig; + } + + @Override + public ConfigMetadata getTemplateConfigMetadata() { + return templateConfig; + } +} diff --git a/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatProvider.java b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatProvider.java new file mode 100644 index 00000000..612432dd --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WechatProvider.java @@ -0,0 +1,20 @@ +package org.jetlinks.community.notify.wechat; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jetlinks.community.notify.Provider; + +@Getter +@AllArgsConstructor +public enum WechatProvider implements Provider { + corpMessage("微信企业消息通知") + ; + + private String name; + + @Override + public String getId() { + return name(); + } + +} diff --git a/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifier.java b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifier.java new file mode 100644 index 00000000..6f90a186 --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/main/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifier.java @@ -0,0 +1,120 @@ +package org.jetlinks.community.notify.wechat; + +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.exception.BusinessException; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.AbstractNotifier; +import org.jetlinks.community.notify.DefaultNotifyType; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.Provider; +import org.jetlinks.community.notify.template.TemplateManager; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.time.Duration; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicReference; + +@Slf4j +public class WeixinCorpNotifier extends AbstractNotifier { + + private AtomicReference accessToken = new AtomicReference<>(); + + private long refreshTokenTime; + + private long tokenTimeOut = Duration.ofSeconds(7000).toMillis(); + + private WebClient client; + + private static final String tokenApi = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; + + private static final String notify = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; + + private WechatCorpProperties properties; + + public WeixinCorpNotifier(WebClient client, WechatCorpProperties properties, TemplateManager templateManager) { + super(templateManager); + this.client = client; + this.properties = properties; + } + + @Nonnull + @Override + public NotifyType getType() { + return DefaultNotifyType.weixin; + } + + @Nonnull + @Override + public Provider getProvider() { + return WechatProvider.corpMessage; + } + + @Nonnull + @Override + public Mono send(@Nonnull WechatMessageTemplate template, @Nonnull Values context) { + return getToken() + .flatMap(token -> + client.post() + .uri(UriComponentsBuilder.fromUriString(notify).queryParam("access_token",token).toUriString()) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(template.createJsonRequest(context))) + .exchange() + .flatMap(clientResponse -> clientResponse.bodyToMono(HashMap.class)) + .as(this::checkResult)) + .then(); + } + + private Mono checkResult(Mono msg) { + return msg.doOnNext(map -> { + String code = String.valueOf(map.get("errcode")); + if ("0".equals(code)) { + log.info("发送微信企业通知成功"); + } else { + log.warn("发送微信企业通知失败:{}", map); + throw new BusinessException("发送微信企业通知失败:" + map.get("errmsg"), code); + } + }); + } + + private Mono getToken() { + if (System.currentTimeMillis() - refreshTokenTime > tokenTimeOut || accessToken.get() == null) { + return requestToken(); + } + return Mono.just(accessToken.get()); + } + + private Mono requestToken() { + return client + .get() + .uri(UriComponentsBuilder.fromUriString(tokenApi) + .queryParam("corpid", properties.getCorpId()) + .queryParam("corpsecret", properties.getCorpSecret()) + .build().toUri()) + .exchange() + .flatMap(resp -> resp.bodyToMono(HashMap.class)) + .map(map -> { + if (map.containsKey("access_token")) { + return map.get("access_token"); + } + throw new BusinessException("获取Token失败:" + map.get("errmsg"), String.valueOf(map.get("errcode"))); + }) + .cast(String.class) + .doOnNext((r) -> { + refreshTokenTime = System.currentTimeMillis(); + accessToken.set(r); + }); + } + + @Nonnull + @Override + public Mono close() { + accessToken.set(null); + refreshTokenTime = 0; + return Mono.empty(); + } +} diff --git a/jetlinks-components/notify-component/notify-wechat/src/test/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifierTest.java b/jetlinks-components/notify-component/notify-wechat/src/test/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifierTest.java new file mode 100644 index 00000000..ed185b7d --- /dev/null +++ b/jetlinks-components/notify-component/notify-wechat/src/test/java/org/jetlinks/community/notify/wechat/WeixinCorpNotifierTest.java @@ -0,0 +1,38 @@ +package org.jetlinks.community.notify.wechat; + +import org.jetlinks.core.Values; +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.test.StepVerifier; + +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +class WeixinCorpNotifierTest { + + + @Test + void test(){ + WechatCorpProperties properties=new WechatCorpProperties(); + properties.setCorpId("wwd7e935e2867897122"); + properties.setCorpSecret("c0qeMSJK2pJee47Bg4kguBmd1JCBt2OFfsNFVGjc1i0"); + + WechatMessageTemplate messageTemplate=new WechatMessageTemplate(); + + messageTemplate.setAgentId("1000002"); + messageTemplate.setMessage("test"+System.currentTimeMillis()); + messageTemplate.setToUser("zhouhao"); + + WeixinCorpNotifier notifier=new WeixinCorpNotifier( + WebClient.builder().build(),properties,null + ); + + notifier.send(messageTemplate, Values.of(new HashMap<>())) + .as(StepVerifier::create) + .expectComplete() + .verify(); + + } + +} \ No newline at end of file diff --git a/jetlinks-components/notify-component/pom.xml b/jetlinks-components/notify-component/pom.xml new file mode 100644 index 00000000..8fdf418f --- /dev/null +++ b/jetlinks-components/notify-component/pom.xml @@ -0,0 +1,25 @@ + + + + jetlinks-components + org.jetlinks.community + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + notify-component + pom + + notify-core + notify-sms + notify-email + notify-wechat + notify-dingtalk + notify-voice + + + + \ No newline at end of file diff --git a/jetlinks-components/pom.xml b/jetlinks-components/pom.xml index 345d4b8d..eaff3fae 100644 --- a/jetlinks-components/pom.xml +++ b/jetlinks-components/pom.xml @@ -19,6 +19,7 @@ timeseries-component dashboard-component common-component + notify-component jetlinks-components diff --git a/jetlinks-manager/authentication-manager/.gitignore b/jetlinks-manager/authentication-manager/.gitignore deleted file mode 100644 index bc38c7ba..00000000 --- a/jetlinks-manager/authentication-manager/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -**/pom.xml.versionsBackup -**/target/ -**/out/ -*.class -# Mobile Tools for Java (J2ME) -.mtj.tmp/ -.idea/ -/nbproject -*.ipr -*.iws -*.iml - -# Package Files # -*.jar -*.war -*.ear -*.log -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -**/transaction-logs/ -!/.mvn/wrapper/maven-wrapper.jar -/data/ -*.db -/static/ -/upload -/ui/upload/ -docker/data \ No newline at end of file diff --git a/jetlinks-manager/device-manager/.gitignore b/jetlinks-manager/device-manager/.gitignore deleted file mode 100644 index bc38c7ba..00000000 --- a/jetlinks-manager/device-manager/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -**/pom.xml.versionsBackup -**/target/ -**/out/ -*.class -# Mobile Tools for Java (J2ME) -.mtj.tmp/ -.idea/ -/nbproject -*.ipr -*.iws -*.iml - -# Package Files # -*.jar -*.war -*.ear -*.log -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -**/transaction-logs/ -!/.mvn/wrapper/maven-wrapper.jar -/data/ -*.db -/static/ -/upload -/ui/upload/ -docker/data \ No newline at end of file diff --git a/jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/DevicePropertyMeasurement.java b/jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/DevicePropertyMeasurement.java index cf9314bf..019bd01b 100644 --- a/jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/DevicePropertyMeasurement.java +++ b/jetlinks-manager/device-manager/src/main/java/org/jetlinks/community/device/measurements/DevicePropertyMeasurement.java @@ -4,10 +4,7 @@ import org.hswebframework.web.api.crud.entity.QueryParamEntity; import org.jetlinks.core.message.property.ReadPropertyMessageReply; import org.jetlinks.core.message.property.ReportPropertyMessage; import org.jetlinks.core.message.property.WritePropertyMessageReply; -import org.jetlinks.core.metadata.ConfigMetadata; -import org.jetlinks.core.metadata.DataType; -import org.jetlinks.core.metadata.DefaultConfigMetadata; -import org.jetlinks.core.metadata.PropertyMetadata; +import org.jetlinks.core.metadata.*; import org.jetlinks.community.dashboard.*; import org.jetlinks.community.dashboard.supports.StaticMeasurement; import org.jetlinks.community.device.message.DeviceMessageUtils; @@ -43,8 +40,10 @@ class DevicePropertyMeasurement extends StaticMeasurement { Map createValue(Object value) { Map values = new HashMap<>(); + DataType type = metadata.getValueType(); + value = type instanceof Converter ? ((Converter) type).convert(value) : value; values.put("value", value); - values.put("formatValue", metadata.getValueType().format(value)); + values.put("formatValue", type.format(value)); return values; } diff --git a/jetlinks-manager/network-manager/.gitignore b/jetlinks-manager/network-manager/.gitignore deleted file mode 100644 index c08f8301..00000000 --- a/jetlinks-manager/network-manager/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -**/pom.xml.versionsBackup -**/target/ -**/out/ -*.class -# Mobile Tools for Java (J2ME) -.mtj.tmp/ -.idea/ -/nbproject -*.ipr -*.iws -*.iml - -# Package Files # -*.jar -*.war -*.ear -*.log -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -**/transaction-logs/ -!/.mvn/wrapper/maven-wrapper.jar -/data/ -*.db -/static/ -/upload -/ui/upload/ -docker/data -!ip2region.db -!device-simulator.jar \ No newline at end of file diff --git a/jetlinks-manager/notify-manager/README.md b/jetlinks-manager/notify-manager/README.md new file mode 100644 index 00000000..71baf352 --- /dev/null +++ b/jetlinks-manager/notify-manager/README.md @@ -0,0 +1 @@ +Notify Manager \ No newline at end of file diff --git a/jetlinks-manager/notify-manager/pom.xml b/jetlinks-manager/notify-manager/pom.xml new file mode 100644 index 00000000..919ad565 --- /dev/null +++ b/jetlinks-manager/notify-manager/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + + org.jetlinks.community + jetlinks-manager + 1.0-SNAPSHOT + ../pom.xml + + notify-manager + + + 4.0.0-SNAPSHOT + + + + + org.jetlinks.community + common-component + ${project.version} + + + + org.hswebframework.web + hsweb-authorization-api + ${hsweb.framework.version} + + + + org.jetlinks + jetlinks-supports + ${jetlinks.version} + + + + org.jetlinks + jetlinks-core + ${jetlinks.version} + + + + org.hswebframework.web + hsweb-starter + ${hsweb.framework.version} + + + + io.r2dbc + r2dbc-h2 + + + + org.hswebframework + hsweb-easy-orm-rdb + + + + org.jetlinks.community + notify-email + ${project.version} + + + + org.jetlinks.community + notify-voice + ${project.version} + + + + org.jetlinks.community + notify-sms + ${project.version} + + + + + + + aliyun-nexus + aliyun + http://maven.aliyun.com/nexus/content/groups/public/ + + + + hsweb-nexus + Nexus Release Repository + http://nexus.hsweb.me/content/groups/public/ + + true + always + + + + + spring.io + spring + https://repo.spring.io/milestone + + + + + + + releases + Nexus Release Repository + http://nexus.hsweb.me/content/repositories/releases/ + + + snapshots + Nexus Snapshot Repository + http://nexus.hsweb.me/content/repositories/snapshots/ + + + + + + aliyun-nexus + aliyun + http://maven.aliyun.com/nexus/content/groups/public/ + + + diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyConfigEntity.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyConfigEntity.java new file mode 100644 index 00000000..22efd8e3 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyConfigEntity.java @@ -0,0 +1,64 @@ +package org.jetlinks.community.notify.manager.entity; + +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType; +import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec; +import org.hswebframework.web.api.crud.entity.GenericEntity; +import org.hswebframework.web.crud.annotation.EnableEntityEvent; +import org.jetlinks.community.notify.NotifierProperties; + +import javax.persistence.Column; +import javax.persistence.Table; +import java.sql.JDBCType; +import java.util.HashMap; +import java.util.Map; + +@Table(name = "notify_config") +@Getter +@Setter +@EnableEntityEvent +public class NotifyConfigEntity extends GenericEntity { + + /** + * 配置名称 + */ + @Column + private String name; + + /** + * 通知类型 + */ + @Column + private String type; + + /** + * 服务提供商 + */ + @Column + private String provider; + + /** + * 描述 + */ + @Column + private String description; + + /** + * 配置详情 + */ + @Column + @JsonCodec + @ColumnType(jdbcType = JDBCType.CLOB) + private Map configuration; + + public NotifierProperties toProperties() { + NotifierProperties properties = new NotifierProperties(); + properties.setProvider(provider); + properties.setId(getId()); + properties.setType(type); + properties.setConfiguration(configuration == null ? new HashMap<>() : configuration); + properties.setName(name); + return properties; + } +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyTemplateEntity.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyTemplateEntity.java new file mode 100644 index 00000000..ec69383a --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/entity/NotifyTemplateEntity.java @@ -0,0 +1,51 @@ +package org.jetlinks.community.notify.manager.entity; + +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType; +import org.hswebframework.ezorm.rdb.mapping.annotation.Comment; +import org.hswebframework.web.api.crud.entity.GenericEntity; +import org.hswebframework.web.crud.annotation.EnableEntityEvent; +import org.jetlinks.community.notify.template.TemplateProperties; + +import javax.persistence.Column; +import javax.persistence.Table; +import java.sql.JDBCType; + +/** + * @author wangzheng + * @author zhouhao + * @since 1.0 + */ +@Setter +@Getter +@Table(name = "notify_template") +@EnableEntityEvent +public class NotifyTemplateEntity extends GenericEntity { + + @Column + @Comment("通知类型") + private String type; + + @Column + @Comment("通知服务商") + private String provider; + + @Column + @Comment("模板名称") + private String name; + + @Comment("模板内容") + @Column + @ColumnType(jdbcType = JDBCType.CLOB) + private String template; + + + public TemplateProperties toTemplateProperties() { + TemplateProperties properties = new TemplateProperties(); + properties.setProvider(provider); + properties.setType(type); + properties.setTemplate(template); + return properties; + } +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultNotifyConfigManager.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultNotifyConfigManager.java new file mode 100644 index 00000000..37b6158e --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultNotifyConfigManager.java @@ -0,0 +1,25 @@ +package org.jetlinks.community.notify.manager.service; + +import org.jetlinks.community.notify.NotifierProperties; +import org.jetlinks.community.notify.NotifyConfigManager; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.manager.entity.NotifyConfigEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; + +@Service +public class DefaultNotifyConfigManager implements NotifyConfigManager { + + @Autowired + private NotifyConfigService configService; + + @Nonnull + @Override + public Mono getNotifyConfig(@Nonnull NotifyType notifyType, @Nonnull String configId) { + return configService.findById(configId) + .map(NotifyConfigEntity::toProperties); + } +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultTemplateManager.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultTemplateManager.java new file mode 100644 index 00000000..eb573262 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/DefaultTemplateManager.java @@ -0,0 +1,35 @@ +package org.jetlinks.community.notify.manager.service; + +import lombok.extern.slf4j.Slf4j; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.manager.entity.NotifyTemplateEntity; +import org.jetlinks.community.notify.template.AbstractTemplateManager; +import org.jetlinks.community.notify.template.TemplateProperties; +import org.jetlinks.community.notify.template.TemplateProvider; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@Slf4j +public class DefaultTemplateManager extends AbstractTemplateManager implements BeanPostProcessor { + + @Autowired + private NotifyTemplateService templateService; + + @Override + protected Mono getProperties(NotifyType type, String id) { + return templateService.findById(Mono.just(id)) + .map(NotifyTemplateEntity::toTemplateProperties); + } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof TemplateProvider) { + register(((TemplateProvider) bean)); + } + return bean; + } + +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifierCacheManager.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifierCacheManager.java new file mode 100644 index 00000000..f498a125 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifierCacheManager.java @@ -0,0 +1,65 @@ +package org.jetlinks.community.notify.manager.service; + +import lombok.extern.slf4j.Slf4j; +import org.hswebframework.web.crud.events.EntityDeletedEvent; +import org.hswebframework.web.crud.events.EntityModifyEvent; +import org.jetlinks.community.notify.NotifierManager; +import org.jetlinks.community.notify.manager.entity.NotifyConfigEntity; +import org.jetlinks.community.notify.manager.entity.NotifyTemplateEntity; +import org.jetlinks.community.notify.template.TemplateManager; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import java.util.List; + +@Component +@Slf4j +public class NotifierCacheManager { + + private final TemplateManager templateManager; + + private final NotifierManager notifierManager; + + public NotifierCacheManager(TemplateManager templateManager, NotifierManager notifierManager) { + this.templateManager = templateManager; + this.notifierManager = notifierManager; + } + + @EventListener + public void handleTemplateModify(EntityModifyEvent event) { + reloadTemplate(event.getBefore()); + } + + @EventListener + public void handleTemplateDelete(EntityDeletedEvent event) { + reloadTemplate(event.getEntity()); + } + + @EventListener + public void handleConfigModify(EntityModifyEvent event) { + reloadConfig(event.getBefore()); + } + + @EventListener + public void handleConfigDelete(EntityDeletedEvent event) { + reloadConfig(event.getEntity()); + } + + protected void reloadConfig(List entities) { + Flux.fromIterable(entities) + .map(NotifyConfigEntity::getId) + .doOnNext(id -> log.info("clear notifier config [{}] cache", id)) + .flatMap(notifierManager::reload) + .subscribe(); + } + + protected void reloadTemplate(List entities) { + Flux.fromIterable(entities) + .map(NotifyTemplateEntity::getId) + .doOnNext(id -> log.info("clear template [{}] cache", id)) + .flatMap(templateManager::reload) + .subscribe(); + } + +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyConfigService.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyConfigService.java new file mode 100644 index 00000000..cb22601a --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyConfigService.java @@ -0,0 +1,14 @@ +package org.jetlinks.community.notify.manager.service; + +import org.hswebframework.web.crud.service.GenericReactiveCacheSupportCrudService; +import org.jetlinks.community.notify.manager.entity.NotifyConfigEntity; +import org.springframework.stereotype.Service; + +@Service +public class NotifyConfigService extends GenericReactiveCacheSupportCrudService { + + @Override + public String getCacheName() { + return "notify_config"; + } +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyTemplateService.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyTemplateService.java new file mode 100644 index 00000000..737f748d --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/service/NotifyTemplateService.java @@ -0,0 +1,17 @@ +package org.jetlinks.community.notify.manager.service; + +import org.hswebframework.web.crud.service.GenericReactiveCrudService; +import org.jetlinks.community.notify.manager.entity.NotifyTemplateEntity; +import org.springframework.stereotype.Service; + +/** + * @author wangzheng + * @see + * @since 1.0 + */ +@Service +public class NotifyTemplateService extends GenericReactiveCrudService { + + + +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierConfigController.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierConfigController.java new file mode 100644 index 00000000..ce155965 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierConfigController.java @@ -0,0 +1,112 @@ +package org.jetlinks.community.notify.manager.web; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.web.authorization.annotation.QueryAction; +import org.hswebframework.web.authorization.annotation.Resource; +import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.community.notify.NotifierProvider; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.manager.entity.NotifyConfigEntity; +import org.jetlinks.community.notify.manager.service.NotifyConfigService; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/notifier/config") +@Resource(id = "notifier", name = "通知管理") +public class NotifierConfigController implements ReactiveServiceCrudController { + + private final NotifyConfigService notifyConfigService; + + private final List providers; + + + public NotifierConfigController(NotifyConfigService notifyConfigService, + List providers) { + this.notifyConfigService = notifyConfigService; + this.providers = providers; + } + + + @Override + public NotifyConfigService getService() { + return notifyConfigService; + } + + @GetMapping("/{type}/{provider}/metadata") + @QueryAction + public Mono getAllTypes(@PathVariable String type, + @PathVariable String provider) { + return Flux.fromIterable(providers) + .filter(prov -> prov.getType().getId().equalsIgnoreCase(type) && prov.getProvider().getId().equalsIgnoreCase(provider)) + .flatMap(prov -> Mono.justOrEmpty(prov.getNotifierConfigMetadata())) + .next(); + } + + + @GetMapping("/types") + @QueryAction + public Flux getAllTypes() { + return Flux.fromIterable(providers) + .collect(Collectors.groupingBy(NotifierProvider::getType)) + .flatMapIterable(Map::entrySet) + .map(en -> { + NotifyTypeInfo typeInfo = new NotifyTypeInfo(); + typeInfo.setId(en.getKey().getId()); + typeInfo.setName(en.getKey().getName()); + typeInfo.setProviderInfos(en.getValue().stream().map(ProviderInfo::of).collect(Collectors.toList())); + return typeInfo; + }); + } + + /** + * 根据类型获取服务商信息 + * + * @param type 类型标识 {@link NotifyType#getId()} + * @return 服务商信息 + */ + @GetMapping("/type/{type}/providers") + @QueryAction + public Flux getTypeProviders(@PathVariable String type) { + return Flux.fromIterable(providers) + .filter(provider -> provider.getType().getId().equals(type)) + .map(ProviderInfo::of); + } + + @Getter + @Setter + @EqualsAndHashCode(of = "id") + public static class NotifyTypeInfo { + private String id; + + private String name; + + private List providerInfos; + + } + + @AllArgsConstructor + @Getter + public static class ProviderInfo { + private String type; + + private String id; + + private String name; + + public static ProviderInfo of(NotifierProvider provider) { + return new ProviderInfo(provider.getType().getId(), provider.getProvider().getId(), provider.getProvider().getName()); + } + + } + +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierController.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierController.java new file mode 100644 index 00000000..7095fe65 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierController.java @@ -0,0 +1,69 @@ +package org.jetlinks.community.notify.manager.web; + +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.web.authorization.annotation.Resource; +import org.hswebframework.web.authorization.annotation.ResourceAction; +import org.hswebframework.web.exception.NotFoundException; +import org.jetlinks.core.Values; +import org.jetlinks.community.notify.DefaultNotifyType; +import org.jetlinks.community.notify.NotifierManager; +import org.jetlinks.community.notify.NotifyType; +import org.jetlinks.community.notify.manager.entity.NotifyTemplateEntity; +import org.jetlinks.community.notify.template.TemplateManager; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@RestController +@RequestMapping("/notifier") +@Resource(id = "notifier", name = "通知管理") +public class NotifierController { + + + private final NotifierManager notifierManager; + + private final TemplateManager templateManager; + + public NotifierController(NotifierManager notifierManager, TemplateManager templateManager) { + this.notifierManager = notifierManager; + this.templateManager = templateManager; + } + + /** + * 指定通知器以及模版.发送通知. + * + * @param notifierId 通知器ID + * @param mono 发送请求 + * @return 发送结果 + */ + @PostMapping("/{notifierId}/_send") + @ResourceAction(id = "send", name = "发送通知") + public Mono sendNotify(@PathVariable String notifierId, + @RequestBody Mono mono) { + return mono.flatMap(tem -> { + NotifyType type = DefaultNotifyType.valueOf(tem.getTemplate().getType()); + return Mono.zip( + notifierManager.getNotifier(type, notifierId) + .switchIfEmpty(Mono.error(() -> new NotFoundException("通知器[" + notifierId + "]不存在"))), + templateManager.createTemplate(type, tem.getTemplate().toTemplateProperties()), + (notifier, template) -> notifier.send(template, Values.of(tem.getContext()))) + .flatMap(Function.identity()); + }); + } + + @Getter + @Setter + public static class SendNotifyRequest { + + @NotNull + private NotifyTemplateEntity template; + + private Map context = new HashMap<>(); + } + +} diff --git a/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierTemplateController.java b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierTemplateController.java new file mode 100644 index 00000000..93b234a9 --- /dev/null +++ b/jetlinks-manager/notify-manager/src/main/java/org/jetlinks/community/notify/manager/web/NotifierTemplateController.java @@ -0,0 +1,56 @@ +package org.jetlinks.community.notify.manager.web; + +import org.hswebframework.web.authorization.annotation.Authorize; +import org.hswebframework.web.authorization.annotation.QueryAction; +import org.hswebframework.web.authorization.annotation.Resource; +import org.hswebframework.web.crud.service.ReactiveCrudService; +import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController; +import org.jetlinks.core.metadata.ConfigMetadata; +import org.jetlinks.community.notify.manager.entity.NotifyTemplateEntity; +import org.jetlinks.community.notify.manager.service.NotifyTemplateService; +import org.jetlinks.community.notify.template.TemplateProvider; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +/** + * @author wangzheng + * @author zhouhao + * @since 1.0 + */ +@RestController +@RequestMapping("/notifier/template") +@Authorize +@Resource(id = "template", name = "通知模板") +public class NotifierTemplateController implements ReactiveServiceCrudController { + + private final NotifyTemplateService templateService; + + private final List providers; + + + public NotifierTemplateController(NotifyTemplateService templateService, List providers) { + this.templateService = templateService; + this.providers = providers; + } + + @Override + public ReactiveCrudService getService() { + return templateService; + } + + + + @GetMapping("/{type}/{provider}/config/metadata") + @QueryAction + public Mono getAllTypes(@PathVariable String type, + @PathVariable String provider) { + return Flux.fromIterable(providers) + .filter(prov -> prov.getType().getId().equalsIgnoreCase(type) && prov.getProvider().getId().equalsIgnoreCase(provider)) + .flatMap(prov -> Mono.justOrEmpty(prov.getTemplateConfigMetadata())) + .next(); + } + +} diff --git a/jetlinks-manager/pom.xml b/jetlinks-manager/pom.xml index 98d17481..6d736f1f 100644 --- a/jetlinks-manager/pom.xml +++ b/jetlinks-manager/pom.xml @@ -15,6 +15,7 @@ authentication-manager device-manager network-manager + notify-manager \ No newline at end of file diff --git a/jetlinks-standalone/pom.xml b/jetlinks-standalone/pom.xml index 54798d43..f1972834 100644 --- a/jetlinks-standalone/pom.xml +++ b/jetlinks-standalone/pom.xml @@ -123,6 +123,12 @@ ${project.version} + + ${project.groupId} + notify-manager + ${project.version} + + org.jetlinks jetlinks-supports