feat(project): 实现临期逾期告警功能

- 新增告警记录表 rdms_due_alert_record 用于去重控制
- 添加告警相关常量类 DueAlertConstants 和对象类型枚举
- 在各数据访问层增加告警候选查询方法
- 实现告警候选服务类和站内信等级功能
- 添加临期逾期告警模板常量定义
- 扩展站内信发送接口支持消息等级
- 新增未读消息批量查询功能用于重复发送判定
This commit is contained in:
2026-06-13 15:00:36 +08:00
parent 635c18767e
commit 896ef0f127
41 changed files with 1650 additions and 18 deletions

View File

@@ -1,17 +1,26 @@
package com.njcn.rdms.module.system.api.notify;
import com.njcn.rdms.framework.common.enums.UserTypeEnum;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.api.notify.dto.NotifySingleSendReqDTO;
import com.njcn.rdms.module.system.api.notify.dto.NotifyUnreadMessageListReqDTO;
import com.njcn.rdms.module.system.api.notify.dto.NotifyUnreadMessageRespDTO;
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyMessageDO;
import com.njcn.rdms.module.system.service.notify.NotifyMessageService;
import com.njcn.rdms.module.system.service.notify.NotifySendService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
/**
* {@link NotifyMessageSendApi} 的实现:委托现有 {@link NotifySendService},不重写发送逻辑。
* 未读查询同理委托 {@link NotifyMessageService}userType 固定 ADMIN与发送口径一致
*
* @author hongawen
*/
@@ -22,12 +31,22 @@ public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
@Resource
private NotifySendService notifySendService;
@Resource
private NotifyMessageService notifyMessageService;
@Override
public CommonResult<Long> sendSingleNotify(NotifySingleSendReqDTO reqDTO) {
Long logId = notifySendService.sendSingleNotifyToAdmin(
reqDTO.getUserId(), reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
Long logId = notifySendService.sendSingleNotify(reqDTO.getUserId(), UserTypeEnum.ADMIN.getValue(),
reqDTO.getTemplateCode(), reqDTO.getTemplateParams(), reqDTO.getLevel());
return success(logId);
}
@Override
public CommonResult<List<NotifyUnreadMessageRespDTO>> getUnreadNotifyMessageListByTemplateCodes(
NotifyUnreadMessageListReqDTO reqDTO) {
List<NotifyMessageDO> list = notifyMessageService.getUnreadNotifyMessageListByTemplateCodes(
reqDTO.getUserIds(), UserTypeEnum.ADMIN.getValue(), reqDTO.getTemplateCodes());
return success(BeanUtils.toBean(list, NotifyUnreadMessageRespDTO.class));
}
}

View File

@@ -43,6 +43,9 @@ public class NotifyMessageRespVO {
@Schema(description = "阅读时间")
private LocalDateTime readTime;
@Schema(description = "消息等级1普通/2提醒/3警告/4严重", example = "1")
private Integer level;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@@ -93,5 +93,11 @@ public class NotifyMessageDO extends BaseDO {
* 阅读时间
*/
private LocalDateTime readTime;
/**
* 消息等级1普通/2提醒/3警告/4严重默认普通
*
* 值见 {@link com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants}
*/
private Integer level;
}

View File

@@ -70,4 +70,15 @@ public interface NotifyMessageMapper extends BaseMapperX<NotifyMessageDO> {
.eq(NotifyMessageDO::getUserType, userType));
}
/** 一批用户在指定模板下的未读消息调用方保证两个集合非空IN 空集会报错) */
default List<NotifyMessageDO> selectUnreadListByUserIdsAndTemplateCodes(Collection<Long> userIds,
Integer userType,
Collection<String> templateCodes) {
return selectList(new LambdaQueryWrapperX<NotifyMessageDO>()
.eq(NotifyMessageDO::getReadStatus, false)
.eq(NotifyMessageDO::getUserType, userType)
.in(NotifyMessageDO::getUserId, userIds)
.in(NotifyMessageDO::getTemplateCode, templateCodes));
}
}

View File

@@ -25,10 +25,12 @@ public interface NotifyMessageService {
* @param template 模版信息
* @param templateContent 模版内容
* @param templateParams 模版参数
* @param level 消息等级null 视为普通),见 NotifyMessageLevelConstants
* @return 站内信编号
*/
Long createNotifyMessage(Long userId, Integer userType,
NotifyTemplateDO template, String templateContent, Map<String, Object> templateParams);
NotifyTemplateDO template, String templateContent,
Map<String, Object> templateParams, Integer level);
/**
* 获得站内信分页
@@ -75,6 +77,17 @@ public interface NotifyMessageService {
*/
Long getUnreadNotifyMessageCount(Long userId, Integer userType);
/**
* 查询一批用户在指定模板下的未读站内信(业务方"未读不重发"判定用)
*
* @param userIds 用户编号列表
* @param userType 用户类型
* @param templateCodes 模板编码列表
* @return 未读站内信列表(任一入参为空集时返回空列表)
*/
List<NotifyMessageDO> getUnreadNotifyMessageListByTemplateCodes(Collection<Long> userIds, Integer userType,
Collection<String> templateCodes);
/**
* 标记站内信为已读
*

View File

@@ -6,6 +6,7 @@ import com.njcn.rdms.module.system.controller.admin.notify.vo.message.NotifyMess
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyMessageDO;
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyTemplateDO;
import com.njcn.rdms.module.system.dal.mysql.notify.NotifyMessageMapper;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -28,11 +29,13 @@ public class NotifyMessageServiceImpl implements NotifyMessageService {
@Override
public Long createNotifyMessage(Long userId, Integer userType,
NotifyTemplateDO template, String templateContent, Map<String, Object> templateParams) {
NotifyTemplateDO template, String templateContent,
Map<String, Object> templateParams, Integer level) {
NotifyMessageDO message = new NotifyMessageDO().setUserId(userId).setUserType(userType)
.setTemplateId(template.getId()).setTemplateCode(template.getCode())
.setTemplateType(template.getType()).setTemplateNickname(template.getNickname())
.setTemplateContent(templateContent).setTemplateParams(templateParams).setReadStatus(false);
.setTemplateContent(templateContent).setTemplateParams(templateParams).setReadStatus(false)
.setLevel(level == null ? NotifyMessageLevelConstants.NORMAL : level);
notifyMessageMapper.insert(message);
return message.getId();
}
@@ -62,6 +65,16 @@ public class NotifyMessageServiceImpl implements NotifyMessageService {
return notifyMessageMapper.selectUnreadCountByUserIdAndUserType(userId, userType);
}
@Override
public List<NotifyMessageDO> getUnreadNotifyMessageListByTemplateCodes(Collection<Long> userIds, Integer userType,
Collection<String> templateCodes) {
// 空集守卫IN 空集合会生成非法 SQL
if (userIds == null || userIds.isEmpty() || templateCodes == null || templateCodes.isEmpty()) {
return List.of();
}
return notifyMessageMapper.selectUnreadListByUserIdsAndTemplateCodes(userIds, userType, templateCodes);
}
@Override
public int updateNotifyMessageRead(Collection<Long> ids, Long userId, Integer userType) {
return notifyMessageMapper.updateListRead(ids, userId, userType);

View File

@@ -1,5 +1,7 @@
package com.njcn.rdms.module.system.service.notify;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import java.util.List;
import java.util.Map;
@@ -36,7 +38,7 @@ public interface NotifySendService {
String templateCode, Map<String, Object> templateParams);
/**
* 发送单条站内信给用户
* 发送单条站内信给用户(默认普通等级)
*
* @param userId 用户编号
* @param userType 用户类型
@@ -44,8 +46,24 @@ public interface NotifySendService {
* @param templateParams 站内信模板参数
* @return 发送日志编号
*/
Long sendSingleNotify( Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams);
default Long sendSingleNotify(Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams) {
return sendSingleNotify(userId, userType, templateCode, templateParams,
NotifyMessageLevelConstants.NORMAL);
}
/**
* 发送单条带等级的站内信给用户
*
* @param userId 用户编号
* @param userType 用户类型
* @param templateCode 站内信模板编号
* @param templateParams 站内信模板参数
* @param level 消息等级,可为 null由落库层兜底为普通见 NotifyMessageLevelConstants
* @return 发送日志编号
*/
Long sendSingleNotify(Long userId, Integer userType,
String templateCode, Map<String, Object> templateParams, Integer level);
default void sendBatchNotify(List<String> mobiles, List<Long> userIds, Integer userType,
String templateCode, Map<String, Object> templateParams) {

View File

@@ -43,7 +43,8 @@ public class NotifySendServiceImpl implements NotifySendService {
}
@Override
public Long sendSingleNotify(Long userId, Integer userType, String templateCode, Map<String, Object> templateParams) {
public Long sendSingleNotify(Long userId, Integer userType, String templateCode,
Map<String, Object> templateParams, Integer level) {
// 校验模版
NotifyTemplateDO template = validateNotifyTemplate(templateCode);
if (Objects.equals(template.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
@@ -55,7 +56,7 @@ public class NotifySendServiceImpl implements NotifySendService {
// 发送站内信
String content = notifyTemplateService.formatNotifyTemplateContent(template.getContent(), templateParams);
return notifyMessageService.createNotifyMessage(userId, userType, template, content, templateParams);
return notifyMessageService.createNotifyMessage(userId, userType, template, content, templateParams, level);
}
@VisibleForTesting

View File

@@ -0,0 +1,10 @@
-- 站内信消息等级:通用消息表加 level 列1普通/2提醒/3警告/4严重默认普通
-- 设计见 docs/superpowers/specs/2026-06-13-告警消息等级-design.md
-- 幂等MySQL 不支持 ADD COLUMN IF NOT EXISTS用 information_schema 判断后 PREPARE 执行
-- 列定义/注释须与演示库补丁 docs/sql/patches/2026-06-13-告警消息等级-01.sql 块1 保持一致
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'system_notify_message' AND column_name = 'level');
SET @ddl = IF(@col_exists = 0,
'ALTER TABLE system_notify_message ADD COLUMN level TINYINT NOT NULL DEFAULT 1 COMMENT ''消息等级:1普通/2提醒/3警告/4严重'' AFTER read_time',
'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@@ -0,0 +1,54 @@
package com.njcn.rdms.module.system.service.notify;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyMessageDO;
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyTemplateDO;
import com.njcn.rdms.module.system.dal.mysql.notify.NotifyMessageMapper;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
/**
* 站内信落库单测:消息等级 level 正确落库(指定值 + null 兜底普通)。
*/
class NotifyMessageServiceImplTest extends BaseMockitoUnitTest {
@InjectMocks
private NotifyMessageServiceImpl notifyMessageService;
@Mock
private NotifyMessageMapper notifyMessageMapper;
private NotifyTemplateDO template() {
NotifyTemplateDO t = new NotifyTemplateDO();
t.setId(1L);
t.setCode("due_alert_overdue_owner");
t.setType(3);
t.setNickname("系统通知");
return t;
}
@Test
void createNotifyMessage_shouldPersistGivenLevel() {
notifyMessageService.createNotifyMessage(100L, 2, template(), "正文", null,
NotifyMessageLevelConstants.SEVERE);
ArgumentCaptor<NotifyMessageDO> captor = ArgumentCaptor.forClass(NotifyMessageDO.class);
verify(notifyMessageMapper).insert(captor.capture());
assertEquals(NotifyMessageLevelConstants.SEVERE, captor.getValue().getLevel());
}
@Test
void createNotifyMessage_nullLevel_shouldFallbackToNormal() {
notifyMessageService.createNotifyMessage(100L, 2, template(), "正文", null, null);
ArgumentCaptor<NotifyMessageDO> captor = ArgumentCaptor.forClass(NotifyMessageDO.class);
verify(notifyMessageMapper).insert(captor.capture());
assertEquals(NotifyMessageLevelConstants.NORMAL, captor.getValue().getLevel());
}
}