Commit be1029d37fda74bd178bf88b4c6a849f165a6433

Authored by chenjunyu_1481036421
1 parent 4be1f8c7

feat:新增:1.阿里云电话通知。2.删除设备时同时删除该设备的告警

... ... @@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
11 11 import org.apache.commons.lang3.StringUtils;
12 12 import org.springframework.http.ResponseEntity;
13 13 import org.springframework.security.access.prepost.PreAuthorize;
  14 +import org.springframework.transaction.annotation.Transactional;
14 15 import org.springframework.validation.annotation.Validated;
15 16 import org.springframework.web.bind.annotation.*;
16 17 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
... ... @@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
39 40 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
40 41 import org.thingsboard.server.controller.BaseController;
41 42 import org.thingsboard.server.dao.yunteng.entities.TkUserCollectEntity;
  43 +import org.thingsboard.server.dao.yunteng.service.TkAlarmInfoService;
42 44 import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService;
43 45 import org.thingsboard.server.dao.yunteng.service.TkDeviceService;
44 46 import org.thingsboard.server.dao.yunteng.service.TkUserCollectService;
... ... @@ -63,6 +65,7 @@ public class TkDeviceController extends BaseController {
63 65 private final ObjectMapper objectMapper;
64 66 private final TkDeviceProfileService ytDeviceProfileService;
65 67 private final GatewayNotificationsService gatewayNotificationsService;
  68 + private final TkAlarmInfoService tkAlarmInfoService ;
66 69
67 70
68 71 @PostMapping
... ... @@ -314,6 +317,7 @@ public class TkDeviceController extends BaseController {
314 317
315 318 @DeleteMapping
316 319 @ApiOperation("删除")
  320 + @Transactional
317 321 @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:device:delete'})")
318 322 public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO)
319 323 throws ThingsboardException {
... ... @@ -322,6 +326,7 @@ public class TkDeviceController extends BaseController {
322 326 if(null !=tdIds){
323 327 for (String id : tdIds) {
324 328 deleteTbDevice(id);
  329 + tkAlarmInfoService.deleteByDeviceId(currentTenantId,id);
325 330 }
326 331 tkdeviceService.deleteDevices(currentTenantId, deleteDTO.getIds());
327 332 }
... ...
... ... @@ -132,6 +132,11 @@
132 132 <artifactId>alibaba-dingtalk-service-sdk</artifactId>
133 133 <version>2.0.0</version>
134 134 </dependency>
  135 + <dependency>
  136 + <groupId>com.aliyun</groupId>
  137 + <artifactId>dyvmsapi20170525</artifactId>
  138 + <version>2.1.4</version>
  139 + </dependency>
135 140 <!-- 腾讯云短信 -->
136 141 <dependency>
137 142 <groupId>com.tencentcloudapi</groupId>
... ...
... ... @@ -5,13 +5,15 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
5 5 import org.thingsboard.server.common.data.yunteng.config.dingtalk.DingTalkSmsProviderConfiguration;
6 6 import org.thingsboard.server.common.data.yunteng.config.message.ali.AliSmsProviderConfiguration;
7 7 import org.thingsboard.server.common.data.yunteng.config.message.tencent.TencentSmsProviderConfiguration;
  8 +import org.thingsboard.server.common.data.yunteng.config.voice.AliVoiceSmsProviderConfiguration;
8 9 import org.thingsboard.server.common.data.yunteng.enums.MessageProviderTypeEnum;
9 10
10 11 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
11 12 @JsonSubTypes({
12   - @JsonSubTypes.Type(value = AliSmsProviderConfiguration.class, name = "ALI_CLOUD"),
13   - @JsonSubTypes.Type(value = TencentSmsProviderConfiguration.class, name = "TENCENT_CLOUD"),
14   - @JsonSubTypes.Type(value = DingTalkSmsProviderConfiguration.class, name = "DING_TALK")
  13 + @JsonSubTypes.Type(value = AliSmsProviderConfiguration.class, name = "ALI_CLOUD"),
  14 + @JsonSubTypes.Type(value = TencentSmsProviderConfiguration.class, name = "TENCENT_CLOUD"),
  15 + @JsonSubTypes.Type(value = DingTalkSmsProviderConfiguration.class, name = "DING_TALK"),
  16 + @JsonSubTypes.Type(value = AliVoiceSmsProviderConfiguration.class, name = "ALI_VOICE")
15 17 })
16 18 public interface MessageProviderConfiguration {
17 19
... ...
... ... @@ -7,6 +7,8 @@ import org.thingsboard.server.common.data.yunteng.config.message.ali.AliSmsProvi
7 7 import org.thingsboard.server.common.data.yunteng.config.message.ali.AliSmsSender;
8 8 import org.thingsboard.server.common.data.yunteng.config.message.tencent.TencentSmsProviderConfiguration;
9 9 import org.thingsboard.server.common.data.yunteng.config.message.tencent.TencentSmsSender;
  10 +import org.thingsboard.server.common.data.yunteng.config.voice.AliVoiceSmsProviderConfiguration;
  11 +import org.thingsboard.server.common.data.yunteng.config.voice.AliVoiceSmsSender;
10 12
11 13 import java.util.HashMap;
12 14 import java.util.List;
... ... @@ -40,6 +42,9 @@ public class TkDefaultMessageSenderFactory implements MessageSenderFactory {
40 42 (DingTalkSmsProviderConfiguration) config);
41 43 smsSenderMap.put(key,dingTalkSmsSender);
42 44 return dingTalkSmsSender;
  45 + case ALI_VOICE:
  46 + return new AliVoiceSmsSender(
  47 + (AliVoiceSmsProviderConfiguration) config);
43 48 default:
44 49 throw new RuntimeException("Unknown SMS provider type " + config.getType());
45 50 }
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.voice;
  2 +
  3 +import lombok.Data;
  4 +import org.thingsboard.server.common.data.yunteng.config.message.MessageProviderConfiguration;
  5 +import org.thingsboard.server.common.data.yunteng.enums.MessageProviderTypeEnum;
  6 +
  7 +@Data
  8 +public class AliVoiceSmsProviderConfiguration implements MessageProviderConfiguration {
  9 +
  10 + /** 阿里云AccessKeyId */
  11 + private String accessKeyId;
  12 +
  13 + /** 阿里云AccessKeySecret */
  14 + private String accessKeySecret;
  15 +
  16 + @Override
  17 + public MessageProviderTypeEnum getType() {
  18 + return MessageProviderTypeEnum.ALI_VOICE;
  19 + }
  20 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.config.voice;
  2 +
  3 +
  4 +
  5 +import com.aliyun.dyvmsapi20170525.Client;
  6 +import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsRequest;
  7 +import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsResponse;
  8 +import com.aliyun.teaopenapi.models.Config;
  9 +import com.aliyun.teautil.models.RuntimeOptions;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.apache.commons.lang3.StringUtils;
  12 +import org.thingsboard.server.common.data.yunteng.config.message.AbstractMessageSender;
  13 +import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
  14 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  15 +import org.thingsboard.server.common.data.yunteng.enums.ResponseCodeEnum;
  16 +
  17 +import java.util.LinkedHashMap;
  18 +
  19 +@Slf4j
  20 +public class AliVoiceSmsSender extends AbstractMessageSender {
  21 +
  22 + /** 阿里云短信参数配置 */
  23 + private final AliVoiceSmsProviderConfiguration config;
  24 +
  25 + private Config profile;
  26 +
  27 + public AliVoiceSmsSender(AliVoiceSmsProviderConfiguration config) {
  28 + if (StringUtils.isEmpty(config.getAccessKeyId())
  29 + || StringUtils.isEmpty(config.getAccessKeyId())) {
  30 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  31 + }
  32 + this.config = config;
  33 + initProfile();
  34 + }
  35 +
  36 + /** 使用AK&SK初始化账号Client */
  37 + private void initProfile() {
  38 + String endpoint = "dyvmsapi.aliyuncs.com";
  39 + profile = new Config();
  40 + profile.setAccessKeyId(config.getAccessKeyId());
  41 + profile.setAccessKeySecret(config.getAccessKeySecret());
  42 + profile.setEndpoint(endpoint);
  43 + }
  44 +
  45 + @Override
  46 + public String sendSms(
  47 + String phone, String templateCode, LinkedHashMap<String, Object> param, String signName) {
  48 + validatePhoneNumber(phone);
  49 + if (null == param || param.isEmpty()) {
  50 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  51 + }
  52 + try {
  53 + Client client = new Client(profile);
  54 + SingleCallByTtsRequest singleCallByTtsRequest = new SingleCallByTtsRequest();
  55 + singleCallByTtsRequest.setCalledShowNumber(StringUtils.isEmpty(signName)?null:signName);
  56 + singleCallByTtsRequest.setCalledNumber(phone);
  57 + singleCallByTtsRequest.setTtsCode(templateCode);
  58 + String TtsCodeParam = "{\"deviceName\":\""+param.get("deviceName")+"\",\"severity\":\""+param.get("severity")+"\"}";
  59 + singleCallByTtsRequest.setTtsParam(TtsCodeParam);
  60 + singleCallByTtsRequest.setSpeed(-100);
  61 + RuntimeOptions runtimeOptions = new RuntimeOptions();
  62 + SingleCallByTtsResponse response = client.singleCallByTtsWithOptions(singleCallByTtsRequest,runtimeOptions);
  63 + return response.getBody().getCode().equalsIgnoreCase(ResponseCodeEnum.OK.name())
  64 + ? ResponseCodeEnum.SUCCESS.name()
  65 + : response.getBody().getMessage();
  66 + } catch (Exception e) {
  67 + e.printStackTrace();
  68 + return e.getMessage();
  69 + }
  70 + }
  71 +}
... ...
... ... @@ -4,5 +4,6 @@ package org.thingsboard.server.common.data.yunteng.enums;
4 4 public enum MessageProviderTypeEnum {
5 5 ALI_CLOUD,
6 6 TENCENT_CLOUD,
7   - DING_TALK
  7 + DING_TALK,
  8 + ALI_VOICE
8 9 }
... ...
... ... @@ -5,5 +5,6 @@ public enum MessageTypeEnum {
5 5 EMAIL_MESSAGE,
6 6 PHONE_MESSAGE,
7 7 DING_TALK_MESSAGE,
8   - WECHAT_MESSAGE
  8 + WECHAT_MESSAGE,
  9 + VOICE_MESSAGE
9 10 }
... ...
... ... @@ -122,4 +122,10 @@ public class TkAlarmInfoServiceImpl implements TkAlarmInfoService {
122 122 public List<SysDictDTO> alarmType(TenantId tenantId) {
123 123 return ReflectUtils.sourceToTarget(tkJpaAarmDao.alarmType(tenantId.getId()), SysDictDTO.class);
124 124 }
  125 +
  126 + @Override
  127 + public void deleteByDeviceId(String tenantId, String tbDeviceId) {
  128 + tkJpaAarmDao.deleteByDeviceId(tenantId,tbDeviceId);
  129 + }
  130 +
125 131 }
... ...
... ... @@ -66,13 +66,13 @@ public class TkMessageServiceImpl implements TkMessageService {
66 66 }
67 67 /** 消息模板是否可用 */
68 68 QueryWrapper<TkMessageTemplateEntity> messageTemplateQueryWrapper =
69   - new QueryWrapper<TkMessageTemplateEntity>();
  69 + new QueryWrapper<TkMessageTemplateEntity>();
70 70 messageTemplateQueryWrapper
71   - .lambda()
72   - .eq(TkMessageTemplateEntity::getId, templateId)
73   - .eq(TkMessageTemplateEntity::getStatus, AssetStatusEnum.ENABLE.ordinal());
  71 + .lambda()
  72 + .eq(TkMessageTemplateEntity::getId, templateId)
  73 + .eq(TkMessageTemplateEntity::getStatus, AssetStatusEnum.ENABLE.ordinal());
74 74 TkMessageTemplateEntity messageTemplate =
75   - messageTemplateMapper.selectOne(messageTemplateQueryWrapper);
  75 + messageTemplateMapper.selectOne(messageTemplateQueryWrapper);
76 76 if (null == messageTemplate) {
77 77 throw new TkDataValidationException(ErrorMessage.MESSAGE_TEMPLATE_NOT_EXISTS.getMessage());
78 78 }
... ... @@ -87,12 +87,12 @@ public class TkMessageServiceImpl implements TkMessageService {
87 87
88 88 boolean isMatch = true;
89 89 if (platForm.equals(MessageProviderTypeEnum.TENCENT_CLOUD.name())
90   - && (null == configJsonNode.get("appId")
  90 + && (null == configJsonNode.get("appId")
91 91 || null == configJsonNode.get("secretId")
92 92 || null == configJsonNode.get("secretKey"))) {
93 93 isMatch = false;
94 94 } else if (platForm.equals(MessageProviderTypeEnum.ALI_CLOUD.name())
95   - && (null == configJsonNode.get("accessKeyId")
  95 + && (null == configJsonNode.get("accessKeyId")
96 96 || null == configJsonNode.get("accessKeySecret"))) {
97 97 isMatch = false;
98 98 }
... ... @@ -102,6 +102,11 @@ public class TkMessageServiceImpl implements TkMessageService {
102 102 || null == configJsonNode.get("clientSecret"))) {
103 103 isMatch = false;
104 104 }
  105 + else if (platForm.equals(MessageProviderTypeEnum.ALI_VOICE.name())
  106 + && (null == configJsonNode.get("accessKeyId")
  107 + || null == configJsonNode.get("accessKeySecret"))) {
  108 + isMatch = false;
  109 + }
105 110 if (!isMatch) {
106 111 throw new TkDataValidationException(ErrorMessage.SMS_CONFIG_ERROR.getMessage());
107 112 }
... ... @@ -113,16 +118,16 @@ public class TkMessageServiceImpl implements TkMessageService {
113 118 keyList.add(messageConfig.getTenantId());
114 119 }
115 120 MessageProviderConfiguration smsProviderConfiguration =
116   - JacksonUtil.convertValue(configObjectNode, MessageProviderConfiguration.class);
  121 + JacksonUtil.convertValue(configObjectNode, MessageProviderConfiguration.class);
117 122 if (null != smsProviderConfiguration) {
118 123 MessageSender smsSender = tkDefaultSmsSenderFactory.createSmsSender(smsProviderConfiguration
119 124 ,keyList);
120 125 String result =
121   - smsSender.sendSms(
122   - phoneNumbers,
123   - messageTemplate.getTemplateCode(),
124   - templateParam,
125   - messageTemplate.getSignName());
  126 + smsSender.sendSms(
  127 + phoneNumbers,
  128 + messageTemplate.getTemplateCode(),
  129 + templateParam,
  130 + messageTemplate.getSignName());
126 131 // 记录短信日志
127 132 String status = ResponseCodeEnum.SUCCESS.name();
128 133 String remark = smsReqDTO.getRemark() == null ? "" : smsReqDTO.getRemark();
... ... @@ -151,34 +156,34 @@ public class TkMessageServiceImpl implements TkMessageService {
151 156 public boolean sendSmsCode(String phoneNumber, MsgTemplatePurposeEnum purpose) {
152 157 // 检查手机号码是否存在系统,以免乱发消息
153 158 List<SysUserEntity> users =
154   - userMapper.selectList(
155   - new QueryWrapper<SysUserEntity>()
156   - .lambda()
157   - .eq(SysUserEntity::getPhoneNumber, phoneNumber));
  159 + userMapper.selectList(
  160 + new QueryWrapper<SysUserEntity>()
  161 + .lambda()
  162 + .eq(SysUserEntity::getPhoneNumber, phoneNumber));
158 163 if (users.isEmpty()) {
159 164 throw new TkDataValidationException("电话号码未在系统注册,请联系你的管理员");
160 165 }
161 166 if (users.get(0).getAccountExpireTime() != null
162   - && users.get(0).getAccountExpireTime().isBefore(LocalDateTime.now())) {
  167 + && users.get(0).getAccountExpireTime().isBefore(LocalDateTime.now())) {
163 168 throw new TkDataValidationException(ErrorMessage.ACCOUNT_HAS_EXPIRED.getMessage());
164 169 }
165 170 // 获取是否有验证码存在,防止发送数量过多
166 171 String key =
167   - purpose.name()
168   - + DEFAULT_DELIMITER
169   - + MessageTypeEnum.PHONE_MESSAGE.name()
170   - + DEFAULT_DELIMITER
171   - + phoneNumber;
  172 + purpose.name()
  173 + + DEFAULT_DELIMITER
  174 + + MessageTypeEnum.PHONE_MESSAGE.name()
  175 + + DEFAULT_DELIMITER
  176 + + phoneNumber;
172 177 boolean canSend =
173   - cacheUtils
174   - .get(MOBILE_LOGIN_SMS_CODE, key)
175   - .map(
176   - o -> {
177   - CodeTTL codeTTL = (CodeTTL) o;
178   - return System.currentTimeMillis() - codeTTL.getSendTs()
179   - >= 60 * 1000; // 大于1分钟才允许下次再发送短信
180   - })
181   - .orElse(true);
  178 + cacheUtils
  179 + .get(MOBILE_LOGIN_SMS_CODE, key)
  180 + .map(
  181 + o -> {
  182 + CodeTTL codeTTL = (CodeTTL) o;
  183 + return System.currentTimeMillis() - codeTTL.getSendTs()
  184 + >= 60 * 1000; // 大于1分钟才允许下次再发送短信
  185 + })
  186 + .orElse(true);
182 187 if (!canSend) {
183 188 throw new TkDataValidationException(ErrorMessage.MESSAGE_SEND_TOO_FAST.getMessage());
184 189 }
... ... @@ -186,15 +191,15 @@ public class TkMessageServiceImpl implements TkMessageService {
186 191 /** 消息模板是否可用 */
187 192 SysUserEntity entity = users.get(0);
188 193 List<TkMessageTemplateEntity> messageTemplates =
189   - messageTemplateMapper.selectList(
190   - new QueryWrapper<TkMessageTemplateEntity>()
191   - .lambda()
192   - .eq(
193   - TkMessageTemplateEntity::getTenantId,
194   - entity.getLevel() == 3 ? entity.getTenantId() : EntityId.NULL_UUID.toString())
195   - .eq(TkMessageTemplateEntity::getStatus, 1)
196   - .eq(TkMessageTemplateEntity::getTemplatePurpose, purpose.name())
197   - .eq(TkMessageTemplateEntity::getMessageType, MessageTypeEnum.PHONE_MESSAGE.name()));
  194 + messageTemplateMapper.selectList(
  195 + new QueryWrapper<TkMessageTemplateEntity>()
  196 + .lambda()
  197 + .eq(
  198 + TkMessageTemplateEntity::getTenantId,
  199 + entity.getLevel() == 3 ? entity.getTenantId() : EntityId.NULL_UUID.toString())
  200 + .eq(TkMessageTemplateEntity::getStatus, 1)
  201 + .eq(TkMessageTemplateEntity::getTemplatePurpose, purpose.name())
  202 + .eq(TkMessageTemplateEntity::getMessageType, MessageTypeEnum.PHONE_MESSAGE.name()));
198 203 if (messageTemplates.isEmpty()) {
199 204 throw new TkDataValidationException(ErrorMessage.MESSAGE_TEMPLATE_NOT_EXISTS.getMessage());
200 205 }
... ...
... ... @@ -153,6 +153,14 @@ public class TkNoticeServiceImpl implements TkNoticeService {
153 153 organization,
154 154 contacts);
155 155 }
  156 + if (messageCode.contains(MessageTypeEnum.VOICE_MESSAGE.name())
  157 + && templatesMap.containsKey(MessageTypeEnum.VOICE_MESSAGE.name())) {
  158 + aLiVoice4Alarm(
  159 + alarmInfo,
  160 + templatesMap.get(MessageTypeEnum.VOICE_MESSAGE.name()),
  161 + organization,
  162 + contacts);
  163 + }
156 164 });
157 165 }
158 166
... ... @@ -289,6 +297,35 @@ public class TkNoticeServiceImpl implements TkNoticeService {
289 297 });
290 298 }
291 299
  300 + /**
  301 + * 电话通知设备告警信息
  302 + *
  303 + * @param alarmInfo 告警信息
  304 + * @param templateId 告警模板主键
  305 + * @param organization 设备所属组织
  306 + * @param contacts 设备告警联系人
  307 + */
  308 + private void aLiVoice4Alarm(
  309 + AlarmInfoDTO alarmInfo,
  310 + String templateId,
  311 + TkOrganizationEntity organization,
  312 + List<TkAlarmContactEntity> contacts) {
  313 + // TODO 电话通知
  314 + SmsReqDTO info = new SmsReqDTO();
  315 + info.setId(templateId);
  316 + info.setParams(setAliAlarmParams(alarmInfo, organization));
  317 + info.setTemplatePurpose(MsgTemplatePurposeEnum.FOR_ALARM_NOTICE.name());
  318 + contacts.stream()
  319 + .parallel()
  320 + .forEach(
  321 + item -> {
  322 + if (!item.getPhone().isEmpty()) {
  323 + info.setPhoneNumbers(item.getPhone());
  324 + smsService.sendSms(info);
  325 + }
  326 + });
  327 + }
  328 +
292 329 private LinkedHashMap<String, Object> setAliAlarmParams(
293 330 AlarmInfoDTO alarmInfo, TkOrganizationEntity organization) {
294 331 LinkedHashMap<String, Object> params = new LinkedHashMap<>();
... ...
... ... @@ -35,4 +35,5 @@ public interface TkAlarmMapper extends BaseMapper<TkAlarmEntity> {
35 35
36 36 List<SysDictEntity> alarmType(@Param("tenantId") UUID tenantId);
37 37 List<AggregationDTO> countAlarms(@Param("queryMap") Map<String, Object> queryMap);
  38 + void deleteByDeviceId(@Param("tenantId") String tenantId, @Param("tbDeviceId") String tbDeviceId);
38 39 }
... ...
... ... @@ -53,4 +53,6 @@ public interface TkAlarmInfoService {
53 53 * @return
54 54 */
55 55 List<SysDictDTO> alarmType(TenantId tenantId);
  56 +
  57 + void deleteByDeviceId(String tenantId, String tbDeviceId);
56 58 }
... ...
... ... @@ -119,4 +119,14 @@
119 119 </where>
120 120 GROUP BY status;
121 121 </select>
  122 +
  123 + <delete id="deleteByDeviceId">
  124 + delete from alarm where 1=1
  125 + <if test="tenantId !=null">
  126 + AND tenant_id= #{tenantId}::uuid
  127 + </if>
  128 + <if test="tbDeviceId !=null">
  129 + AND originator_id= #{tbDeviceId}::uuid
  130 + </if>
  131 + </delete>
122 132 </mapper>
... ...