跳到主要内容

消息中心(通知)

消息中心是 Platform 的统一通知出口:负责多渠道消息的发送与记录。业务代码只需调用 IPlatformMessageService.SendAsync 并声明目标渠道(MessageType),底层 IMessageChannelSender 的各实现把消息投递到对应平台;每次发送都会持久化为一条 Message 记录,状态(成功 / 失败 / 跳过)与重试次数可查、可对失败项重试。

注意:本模块内部通过消息中心抽象对外提供通知能力,并没有独立名为 IExceptionNotifySender 的异常告警接口。异常 / 运维告警应自行组装 SendMessageReq 调用 SendAsync 实现。

业务能力

  • 统一发送:一条 SendMessageReq 可同时发往多个渠道(MessageType:站内 / 飞书 / 企业微信 / 公众号 / 短信)。未指定渠道时回落到配置里的 DefaultChannels
  • 渠道实现FeishuWebhookMessageChannelSender(飞书机器人 Webhook)、WeComWebhookMessageChannelSender(企业微信机器人 Webhook)、WeChatOfficialAccountMessageChannelSender(公众号模板消息)。
  • 记录与重试:每次发送落库 Message,可分页查询明细、对失败消息单条重试。
  • CmsKit 外发:可开启把 CmsKit 通知自动 fan-out 到外部渠道(按类型白名单过滤)。

概念与关系(策略模式)

  • IPlatformMessageServiceGetListAsync(MessageQuery) / GetAsync(Guid id) / SendAsync(SendMessageReq, ct) / RetryAsync(Guid id, ct) / SendIntegrationEventAsync(PlatformMessageIntegrationEvent, ct)
  • IMessageChannelSender(internal):每个渠道一个实现,按 MessageType 路由;MessageChannelSendResult 返回 Success / Failed / Skipped

为什么用 IMessageChannelSender 策略模式

飞书 Webhook、企微 Webhook、公众号模板消息三者在鉴权方式、请求报文、错误模型上完全不同。如果把这些差异堆在 PlatformMessageService 里,会得到一个巨大的 if/else 分支,且每加一个渠道都要改核心代码。

因此把"如何发到某个平台"收敛成一个内部接口 IMessageChannelSender

internal interface IMessageChannelSender
{
MessageType MessageType { get; }
Task<MessageChannelSendResult> SendAsync(MessageChannelSendContext context, CancellationToken ct = default);
}

PlatformMessageService 只负责稳定不变的部分——拆渠道、建 Message 记录、按 MessageType 找到对应 sender(channelSenders.FirstOrDefault(r => r.MessageType == entity.MessageType))、收口异常为 Failed、未注册渠道标记为 Skipped。新增渠道 = 加一个实现类 + 在 PlatformModuleStartup 注册一行,核心逻辑零改动。

重试与状态模型

发送不是"发了就走",而是先落库、再投递、回写状态,这让通知具备可观测与可恢复性:

  1. SendAsync 对每种 MessageTypeInsert 一条 MessageStatus = Pending),再调用 SendSingleAsync 投递,最后把 MessageChannelSendResult 的状态、提供方消息 Id、错误信息回写该行。
  2. 投递异常被 try/catch 吞掉并记为 Failed,不会让整个发送请求炸掉;未匹配到 sender 则记为 Skipped
  3. RetryAsync(id) 重新取该 Message,再次走 SendSingleAsync 并累加 RetryCount——失败项可以单条重试,无需全量重发。

这套"记录即真相"的设计意味着:即便外部平台瞬时抖动,你也能从 Message 表里筛出所有 Failed/ Skipped 精确补发。

MessageType 枚举取值:Site=0(站内)、FeiShuBot=1WeComBot=2WeChatOfficialAccount=3TextMessage=4(短信)。

配置(PlatformMessage 节)

绑定自配置节 PlatformMessage(常量 PlatformMessageOptions.SectionName):

业务含义
EnableCmsKitNotificationFanout是否把 CmsKit 通知外发到外部渠道
DefaultChannels默认外发渠道(MessageType 数组,未指定渠道时回落到此)
AllowedCmsKitNotificationTypesCmsKit 外发类型白名单,空表示不限制
Feishu:Enabled / WebhookUrl / Secret飞书机器人开关与签名参数
WeCom:Enabled / WebhookUrl企业微信机器人开关
WeChatOfficialAccount:Enabled / AppId / AppSecret / TemplateId / DefaultUrl公众号模板消息配置
{
"PlatformMessage": {
"EnableCmsKitNotificationFanout": true,
"DefaultChannels": [1, 2],
"AllowedCmsKitNotificationTypes": ["Comment", "Reply"],
"Feishu": {
"Enabled": true,
"WebhookUrl": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx",
"Secret": "your-secret"
},
"WeCom": {
"Enabled": true,
"WebhookUrl": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
},
"WeChatOfficialAccount": {
"Enabled": true,
"AppId": "your-app-id",
"AppSecret": "your-app-secret",
"TemplateId": "your-template-id",
"DefaultUrl": "https://example.com"
}
}
}

开发示例

private readonly IPlatformMessageService _messageService;

// 发送一条同时走飞书 + 企业微信的告警
var sent = await _messageService.SendAsync(new SendMessageReq
{
Title = "构建失败",
Content = "main 分支 CI 失败",
BizType = "CI",
MessageTypes = new List<MessageType> { MessageType.FeiShuBot, MessageType.WeComBot }
});

// 对失败消息重试
await _messageService.RetryAsync(sent[0].Id);

相关文档