feat: add notification send service for M8-F03

NotificationSendService dispatches events through configured channels (in-app todo, email placeholder, WeChat placeholder). Supports event type routing and role-based delivery.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-27 08:37:16 +08:00
parent 2609ea3f79
commit 1492e91431
@@ -0,0 +1,73 @@
package cn.craftlabs.platform.api.service;
import cn.craftlabs.platform.api.persistence.todo.PlatformNotificationConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* M8-F03: 通知发送服务。
* 根据通知配置,将事件通过已启用的通道送达目标角色。
* 当前实现:站内待办(in-app todo)已接入;邮件/企微为 logging 占位。
*/
@Service
public class NotificationSendService {
private static final Logger log = LoggerFactory.getLogger(NotificationSendService.class);
private final TodoService todoService;
private final cn.craftlabs.platform.api.persistence.todo.PlatformNotificationConfigMapper configMapper;
public NotificationSendService(TodoService todoService,
cn.craftlabs.platform.api.persistence.todo.PlatformNotificationConfigMapper configMapper) {
this.todoService = todoService;
this.configMapper = configMapper;
}
/**
* 发送通知:根据事件类型查询配置,向目标角色发送。
*
* @param eventType 事件类型(如 CALLBACK_PENDING, SN_EXPIRING
* @param title 通知标题
* @param sourceId 关联业务 ID
* @param sourceType 关联业务类型
*/
@Transactional
public void send(String eventType, String title, Long sourceId, String sourceType) {
var configs = configMapper.selectList(
com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformNotificationConfig.class)
.eq(PlatformNotificationConfig::getEventType, eventType));
for (PlatformNotificationConfig cfg : configs) {
String roleCode = cfg.getRoleCode();
// 站内待办
if (Boolean.TRUE.equals(cfg.getChannelInApp())) {
todoService.createTodo(eventType, title, sourceId, sourceType, "MEDIUM", roleCode);
log.info("In-app todo created for role={} event={} sourceId={}", roleCode, eventType, sourceId);
}
// 邮件(占位)
if (Boolean.TRUE.equals(cfg.getChannelEmail())) {
log.info("[EMAIL PLACEHOLDER] would send email for role={} event={} title={}", roleCode, eventType, title);
}
// 企业微信(占位)
if (Boolean.TRUE.equals(cfg.getChannelWecom())) {
log.info("[WECOM PLACEHOLDER] would send WeChat Work message for role={} event={} title={}", roleCode, eventType, title);
}
}
}
/**
* 查询某个事件类型是否有已启用的通知配置。
*/
public boolean hasActiveConfig(String eventType) {
return configMapper.selectCount(
com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformNotificationConfig.class)
.eq(PlatformNotificationConfig::getEventType, eventType)) > 0;
}
}