fix: 优化IoT告警配置支持动态选择邮件、短信、站内信的模板,不再依赖templateCode。

This commit is contained in:
panda
2026-05-22 14:52:02 +08:00
parent a08526d855
commit 3f1d86efff
16 changed files with 175 additions and 8 deletions
@@ -37,6 +37,15 @@ public class IotAlertConfigRespVO {
@Schema(description = "接收的类型数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
private List<Integer> receiveTypes;
@Schema(description = "短信模板编号", example = "iot_alert_sms")
private String smsTemplateCode;
@Schema(description = "邮件模板编号", example = "iot_alert_mail")
private String mailTemplateCode;
@Schema(description = "站内信模板编号", example = "iot_alert_notify")
private String notifyTemplateCode;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@@ -44,4 +44,12 @@ public class IotAlertConfigSaveReqVO {
@NotEmpty(message = "接收的类型数组不能为空")
private List<Integer> receiveTypes;
@Schema(description = "短信模板编号", example = "iot_alert_sms")
private String smsTemplateCode;
@Schema(description = "邮件模板编号", example = "iot_alert_mail")
private String mailTemplateCode;
@Schema(description = "站内信模板编号", example = "iot_alert_notify")
private String notifyTemplateCode;
}
@@ -81,4 +81,22 @@ public class IotAlertConfigDO extends BaseDO {
@TableField(typeHandler = IntegerListTypeHandler.class)
private List<Integer> receiveTypes;
/**
* 短信模板编号
*
* 关联 SmsTemplateDO 的 code 属性
*/
private String smsTemplateCode;
/**
* 邮件模板编号
*
* 关联 MailTemplateDO 的 code 属性
*/
private String mailTemplateCode;
/**
* 站内信模板编号
*
* 关联 NotifyTemplateDO 的 code 属性
*/
private String notifyTemplateCode;
}
@@ -98,6 +98,9 @@ public interface ErrorCodeConstants {
// ========== IoT 告警配置 1-050-013-000 ==========
ErrorCode ALERT_CONFIG_NOT_EXISTS = new ErrorCode(1_050_013_000, "IoT 告警配置不存在");
ErrorCode ALERT_CONFIG_SMS_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_001, "已选择短信接收方式,短信模板不能为空");
ErrorCode ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_002, "已选择邮件接收方式,邮件模板不能为空");
ErrorCode ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED = new ErrorCode(1_050_013_003, "已选择站内信接收方式,站内信模板不能为空");
// ========== IoT 告警记录 1-050-014-000 ==========
ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
@@ -1,11 +1,14 @@
package cn.iocoder.yudao.module.iot.service.alert;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.alert.vo.config.IotAlertConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.alert.IotAlertConfigDO;
import cn.iocoder.yudao.module.iot.dal.mysql.alert.IotAlertConfigMapper;
import cn.iocoder.yudao.module.iot.enums.alert.IotAlertReceiveTypeEnum;
import cn.iocoder.yudao.module.iot.service.rule.scene.IotSceneRuleService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
@@ -16,7 +19,10 @@ import org.springframework.validation.annotation.Validated;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOT_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.ALERT_CONFIG_SMS_TEMPLATE_REQUIRED;
/**
* IoT 告警配置 Service 实现类
@@ -42,6 +48,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
validateReceiveTemplates(createReqVO);
// 插入
IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class);
@@ -56,6 +63,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
validateReceiveTemplates(updateReqVO);
// 更新
IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class);
@@ -76,6 +84,24 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
}
}
private void validateReceiveTemplates(IotAlertConfigSaveReqVO reqVO) {
if (CollUtil.isEmpty(reqVO.getReceiveTypes())) {
return;
}
if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.SMS.getType())
&& StrUtil.isBlank(reqVO.getSmsTemplateCode())) {
throw exception(ALERT_CONFIG_SMS_TEMPLATE_REQUIRED);
}
if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.MAIL.getType())
&& StrUtil.isBlank(reqVO.getMailTemplateCode())) {
throw exception(ALERT_CONFIG_MAIL_TEMPLATE_REQUIRED);
}
if (reqVO.getReceiveTypes().contains(IotAlertReceiveTypeEnum.NOTIFY.getType())
&& StrUtil.isBlank(reqVO.getNotifyTemplateCode())) {
throw exception(ALERT_CONFIG_NOTIFY_TEMPLATE_REQUIRED);
}
}
@Override
public IotAlertConfigDO getAlertConfig(Long id) {
return alertConfigMapper.selectById(id);
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.service.rule.scene.action;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -79,30 +80,38 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction {
}
Map<String, Object> templateParams = buildTemplateParams(config, deviceMessage, device);
config.getReceiveUserIds().forEach(userId ->
config.getReceiveTypes().forEach(receiveType -> sendAlertMessageToUser(userId, receiveType, templateParams)));
config.getReceiveTypes().forEach(receiveType ->
sendAlertMessageToUser(userId, receiveType, config, templateParams)));
}
/**
* 按指定接收方式,给单个用户发送告警消息
*/
private void sendAlertMessageToUser(Long userId, Integer receiveType, Map<String, Object> templateParams) {
private void sendAlertMessageToUser(Long userId, Integer receiveType, IotAlertConfigDO config,
Map<String, Object> templateParams) {
IotAlertReceiveTypeEnum typeEnum = IotAlertReceiveTypeEnum.of(receiveType);
if (typeEnum == null) {
return;
}
String templateCode = resolveTemplateCode(config, typeEnum);
if (StrUtil.isBlank(templateCode)) {//为了兼容老的结构
templateCode=typeEnum.getTemplateCode();
log.warn("[sendAlertMessageToUser][配置({}) 用户({}) 接收方式({}) 未配置模板,使用默认模板({})]",
config.getId(), userId, typeEnum,templateCode);
}
try {
switch (typeEnum) {
case SMS:
smsSendApi.sendSingleSmsToAdmin(new SmsSendSingleToUserReqDTO().setUserId(userId)
.setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams));
.setTemplateCode(templateCode).setTemplateParams(templateParams));
break;
case MAIL:
mailSendApi.sendSingleMailToAdmin(new MailSendSingleToUserReqDTO().setUserId(userId)
.setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams));
.setTemplateCode(templateCode).setTemplateParams(templateParams));
break;
case NOTIFY:
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO().setUserId(userId)
.setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams));
.setTemplateCode(templateCode).setTemplateParams(templateParams));
break;
}
} catch (Exception ex) {
@@ -111,6 +120,15 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction {
}
}
private String resolveTemplateCode(IotAlertConfigDO config, IotAlertReceiveTypeEnum typeEnum) {
String templateCode = switch (typeEnum) {
case SMS -> config.getSmsTemplateCode();
case MAIL -> config.getMailTemplateCode();
case NOTIFY -> config.getNotifyTemplateCode();
};
return StrUtil.blankToDefault(templateCode, typeEnum.getTemplateCode());
}
private Map<String, Object> buildTemplateParams(IotAlertConfigDO config,
@Nullable IotDeviceMessage deviceMessage,
@Nullable IotDeviceDO device) {
@@ -106,6 +106,9 @@ public class IotAlertTriggerSceneRuleActionTest extends BaseMockitoUnitTest {
IotAlertReceiveTypeEnum.SMS.getType(),
IotAlertReceiveTypeEnum.MAIL.getType(),
IotAlertReceiveTypeEnum.NOTIFY.getType()));
c.setSmsTemplateCode("custom_sms");
c.setMailTemplateCode("custom_mail");
c.setNotifyTemplateCode("custom_notify");
});
IotDeviceDO device = randomPojo(IotDeviceDO.class);
@@ -130,13 +133,13 @@ public class IotAlertTriggerSceneRuleActionTest extends BaseMockitoUnitTest {
ArgumentCaptor<SmsSendSingleToUserReqDTO> smsCaptor = ArgumentCaptor.forClass(SmsSendSingleToUserReqDTO.class);
verify(smsSendApi, times(1)).sendSingleSmsToAdmin(smsCaptor.capture());
assertEquals(userId, smsCaptor.getValue().getUserId());
assertEquals(IotAlertReceiveTypeEnum.SMS.getTemplateCode(), smsCaptor.getValue().getTemplateCode());
assertEquals("custom_sms", smsCaptor.getValue().getTemplateCode());
ArgumentCaptor<MailSendSingleToUserReqDTO> mailCaptor = ArgumentCaptor.forClass(MailSendSingleToUserReqDTO.class);
verify(mailSendApi, times(1)).sendSingleMailToAdmin(mailCaptor.capture());
assertEquals(IotAlertReceiveTypeEnum.MAIL.getTemplateCode(), mailCaptor.getValue().getTemplateCode());
assertEquals("custom_mail", mailCaptor.getValue().getTemplateCode());
ArgumentCaptor<NotifySendSingleToUserReqDTO> notifyCaptor = ArgumentCaptor.forClass(NotifySendSingleToUserReqDTO.class);
verify(notifyMessageSendApi, times(1)).sendSingleMessageToAdmin(notifyCaptor.capture());
assertEquals(IotAlertReceiveTypeEnum.NOTIFY.getTemplateCode(), notifyCaptor.getValue().getTemplateCode());
assertEquals("custom_notify", notifyCaptor.getValue().getTemplateCode());
}
@Test
@@ -13,4 +13,7 @@ public class MailTemplateSimpleRespVO {
@Schema(description = "模版名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "哒哒哒")
private String name;
@Schema(description = "模版编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "mail_001")
private String code;
}
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.Notify
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateRespVO;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateSendReqVO;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateSimpleRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
import cn.iocoder.yudao.module.system.service.notify.NotifySendService;
import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
@@ -86,6 +87,13 @@ public class NotifyTemplateController {
return success(BeanUtils.toBean(pageResult, NotifyTemplateRespVO.class));
}
@GetMapping({"/list-all-simple", "/simple-list"})
@Operation(summary = "获得站内信模版精简列表")
public CommonResult<List<NotifyTemplateSimpleRespVO>> getSimpleTemplateList() {
List<NotifyTemplateDO> list = notifyTemplateService.getNotifyTemplateList();
return success(BeanUtils.toBean(list, NotifyTemplateSimpleRespVO.class));
}
@PostMapping("/send-notify")
@Operation(summary = "发送站内信")
@PreAuthorize("@ss.hasPermission('system:notify-template:send-notify')")
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.system.controller.admin.notify.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 站内信模版的精简 Response VO")
@Data
public class NotifyTemplateSimpleRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "模版名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统通知")
private String name;
@Schema(description = "模版编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "notify_001")
private String code;
}
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTempla
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateRespVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSendReqVO;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSimpleRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
import cn.iocoder.yudao.module.system.service.sms.SmsTemplateService;
@@ -88,6 +89,13 @@ public class SmsTemplateController {
return success(BeanUtils.toBean(pageResult, SmsTemplateRespVO.class));
}
@GetMapping({"/list-all-simple", "/simple-list"})
@Operation(summary = "获得短信模板精简列表")
public CommonResult<List<SmsTemplateSimpleRespVO>> getSimpleTemplateList() {
List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList();
return success(BeanUtils.toBean(list, SmsTemplateSimpleRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出短信模板 Excel")
@PreAuthorize("@ss.hasPermission('system:sms-template:export')")
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.system.controller.admin.sms.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 短信模板的精简 Response VO")
@Data
public class SmsTemplateSimpleRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "验证码")
private String name;
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "sms_001")
private String code;
}
@@ -69,6 +69,13 @@ public interface NotifyTemplateService {
*/
PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO);
/**
* 获得启用状态的站内信模版列表
*
* @return 站内信模版列表
*/
List<NotifyTemplateDO> getNotifyTemplateList();
/**
* 格式化站内信内容
*
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.notify;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
@@ -115,6 +116,11 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
return notifyTemplateMapper.selectPage(pageReqVO);
}
@Override
public List<NotifyTemplateDO> getNotifyTemplateList() {
return notifyTemplateMapper.selectList(NotifyTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
}
@VisibleForTesting
void validateNotifyTemplateCodeDuplicate(Long id, String code) {
NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);
@@ -70,6 +70,13 @@ public interface SmsTemplateService {
*/
PageResult<SmsTemplateDO> getSmsTemplatePage(SmsTemplatePageReqVO pageReqVO);
/**
* 获得启用状态的短信模板列表
*
* @return 短信模板列表
*/
List<SmsTemplateDO> getSmsTemplateList();
/**
* 获得指定短信渠道下的短信模板数量
*
@@ -130,6 +130,11 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
return smsTemplateMapper.selectPage(pageReqVO);
}
@Override
public List<SmsTemplateDO> getSmsTemplateList() {
return smsTemplateMapper.selectList(SmsTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
}
@Override
public Long getSmsTemplateCountByChannelId(Long channelId) {
return smsTemplateMapper.selectCountByChannelId(channelId);