跳到内容

Notification Starter

受众:开发者 摘要:统一通知 Facade。log / memory / database / mail / sms / websocket / webhook / wechat 八大通道;后台站内信 + 发送日志 + 失败重试 + 通知偏好 + 跳转 contract。

何时使用

场景建议
站内信(落库 + 后台运维)Notification.send("database", ...)
实时在线推送(在线用户)Notification.send("websocket", ...)
邮件(统一发送日志)Notification.send("mail", ...)
短信(统一发送日志、重试)Notification.send("sms", ...)
IM 机器人(钉钉 / 飞书 / Slack / Telegram / 自托管)Notification.send("webhook", ...)
微信公众号模板消息 / 客服消息、小程序订阅消息Notification.send("wechat", ...)
后台 / 调试log / memory
业务连续消息流(私聊、群聊、客服)用 Conversation API,用 Notification
直接短信(不入发送日志)直接 Sms.send(...),看 sms.md

业务代码只调 Notification Facade,直接绑定具体通道实现。

快速开始

java
Notification.send("database", NotificationMessage.builder()
        .subject("通知")
        .content("你的任务已经处理完成")
        .recipient(NotificationRecipient.user(1L))
        .build());

多通道:

java
List<NotificationSendResult> results = Notification.send(
    List.of("database", "websocket"),
    NotificationMessage.builder()
        .subject("订单状态变更")
        .content("订单已支付")
        .recipient(NotificationRecipient.user(1L))
        .build());

多通道发送会逐个通道尝试并返回每通道结果;某通道失败阻断后续。单通道失败仍抛异常。

Facade API

java
// 单通道
Notification.send(String channel, NotificationMessage message);

// 多通道
Notification.send(List<String> channels, NotificationMessage message);

NotificationMessage 关键字段:

字段说明
subject标题
content正文(可走 i18n key)
template模板编码(template
variable(k, v) / variables(map)模板变量
recipient(...)接收人(可多次)
attribute(k, v)通道扩展属性(如 webhook.provider

NotificationRecipient 类型:

类型构造
用户user(userId)
邮箱email(email)
手机mobile(mobile)
WebSocket 用户websocket(userId)
WebSocket sessionwebsocketSession(sessionId)
WebSocket 分组websocketGroup(group)
WebSocket 访客websocketGuest()
微信 openidNotificationRecipient.of("wechat-openid", openid)
主题topic(topic)

配置项

yaml
spring:
  open:
    notification:
      enabled: true
      default-channel: log
      channels:
        log:
          enabled: true
        memory:
          enabled: true
          capacity: 1000
        websocket:
          enabled: true
        mail:
          enabled: true
        sms:
          enabled: true
        database:
          enabled: true
        webhook:
          enabled: false
          default-provider: telegram
          message-type: text
          signing-enabled: true
          providers:
            telegram:
              endpoint: ""
            generic:
              endpoint: ""
        wechat:
          enabled: false
      template:
        validate-before-send: true
        fail-on-missing-variables: false
配置默认值说明
enabledtrue启用开关
default-channellog默认通道
template.validate-before-sendtrue发送前模板变量校验
template.fail-on-missing-variablesfalse缺失变量是否阻断发送
channels.<x>.enabled按通道启停通道
channels.webhook.default-providertelegram默认机器人
channels.webhook.signing-enabledtrue签名
channels.wechat.enabledfalse启用微信通知通道

Provider

Channel

Channel说明
log默认,写日志
memory测试,内存保留
database站内信,写 notification_message
websocket在线用户实时推送
mail委托 Mail starter
sms委托 SMS starter
webhook机器人 / 第三方推送
wechat委托 微信生态 core,发送公众号模板 / 客服消息和小程序订阅消息

扩展:实现 NotificationChannel SPI Bean 即可新增通道。

WeChat 通道

wechat 通道由 spring-open-core-wechat 提供,默认关闭,启用后复用 微信生态 中的 WechatOfficialServiceWechatMiniappService;不自研微信协议,也不在 Notification 模块保存微信密钥。

yaml
spring:
  open:
    notification:
      channels:
        wechat:
          enabled: true

消息类型:

wechat.message-type能力底层服务
official-template公众号模板消息WechatOfficialService#sendTemplateMessage
official-kefu-text公众号客服文本消息WechatOfficialService#sendTextKefuMessage
miniapp-subscribe小程序订阅消息WechatMiniappService#sendSubscribeMessage

常用 attribute:

Attribute必填说明
wechat.account-code微信账号 code,对应 spring.open.wechatwechat_account 中的账号
wechat.message-type上表中的消息类型
wechat.to-user微信 openid;未传时读取第一个 wechat-openid 接收人
wechat.template-id模板 / 订阅消息必填微信模板 ID;未传时回退 NotificationMessage.template
wechat.url公众号模板消息跳转 URL
wechat.client-message-id公众号模板消息 client msg id,未传时回退消息 ID
wechat.kf-account指定公众号客服账号
wechat.page小程序订阅消息跳转页面
wechat.miniprogram-state小程序状态,如 developer / trial / formal
wechat.lang小程序订阅消息语言;未传时使用消息 locale

variables 会作为微信模板 / 订阅消息 data 值传入,值会转成字符串。微信发送结果仍写入 wechat_message_log 出站日志;Notification 发送日志记录通道级成败,便于统一重试和审计。

公众号模板消息:

java
Notification.send("wechat", NotificationMessage.builder()
        .template("tpl_xxx")
        .recipient(NotificationRecipient.of("wechat-openid", "openid_xxx"))
        .variable("first", "订单已支付")
        .variable("keyword1", "SO202606080001")
        .attribute("wechat.account-code", "official-main")
        .attribute("wechat.message-type", "official-template")
        .attribute("wechat.url", "https://example.com/orders/SO202606080001")
        .build());

小程序订阅消息:

java
Notification.send("wechat", NotificationMessage.builder()
        .template("subscribe_tpl_xxx")
        .recipient(NotificationRecipient.of("wechat-openid", "openid_xxx"))
        .variable("thing1", "订单已发货")
        .attribute("wechat.account-code", "mini-main")
        .attribute("wechat.message-type", "miniapp-subscribe")
        .attribute("wechat.page", "pages/order/detail?id=1001")
        .attribute("wechat.miniprogram-state", "formal")
        .build());

公众号客服文本:

java
Notification.send("wechat", NotificationMessage.builder()
        .content("您好,客服已收到您的请求。")
        .recipient(NotificationRecipient.of("wechat-openid", "openid_xxx"))
        .attribute("wechat.account-code", "official-main")
        .attribute("wechat.message-type", "official-kefu-text")
        .build());

后台发送元数据接口 GET /api/v1/notifications/send-metadata 会返回 wechat 通道、微信消息类型和字段清单;wechat.to-user 按敏感字段展示,避免在前端表单和日志中扩散 openid。

Webhook Provider

Provider成功判断
genericHTTP 状态码
dingtalkHTTP 成功 + errcode/code == 0,支持 query 签名
wecomHTTP 成功 + errcode/code == 0
feishu / larkHTTP 成功 + code/errcode == 0,支持 payload 签名
slackHTTP 成功 + 空响应或 ok
discordHTTP 成功(204 No Content
barkHTTP 成功 + code=200
gotifyHTTP 成功
ntfyHTTP 成功
push-plusHTTP 成功 + code=200
server-chanHTTP 成功 + code=0
telegramHTTP 成功 + ok=true
teamsHTTP 成功
mattermostHTTP 成功 + 空响应或 ok

签名密钥 / endpoint token / 请求头写入失败原因。失败结果记录 provider / 第三方业务 code / 第三方消息 / 框架 hint。

Provider 单次覆盖

通过消息属性单次覆盖:

java
Notification.send("webhook", NotificationMessage.builder()
        .subject("部署通知")
        .content("生产环境发布完成")
        .attribute("webhook.provider", "dingtalk")
        .attribute("webhook.endpoint", "https://example.com/webhook")
        .build());

常见 attribute:webhook.provider / webhook.endpoint / webhook.message-type / webhook.secret / webhook.payload(完整覆盖)。

Provider 特定属性:

Provider常用属性
Barkwebhook.bark.device-key / group / level / sound / url / icon
Gotifywebhook.gotify.priority / extras
ntfywebhook.ntfy.topic / priority / tags / click / attach / filename / actions / markdown
PushPluswebhook.push-plus.token / topic / template / channel / option / to / callback-url
ServerChanwebhook.server-chan.channel / open-id
Telegramwebhook.telegram.chat-id / parse-modeMarkdown / MarkdownV2 / HTML

Webhook 配置示例:

yaml
spring:
  open:
    notification:
      channels:
        webhook:
          enabled: true
          default-provider: feishu
          message-type: markdown
          providers:
            dingtalk:
              endpoint: https://oapi.dingtalk.com/robot/send?access_token=your-token
              secret: ${SPRING_OPEN_NOTIFICATION_DINGTALK_SECRET:}
            feishu:
              endpoint: https://open.feishu.cn/open-apis/bot/v2/hook/your-token
              secret: ${SPRING_OPEN_NOTIFICATION_FEISHU_SECRET:}
            wecom:
              endpoint: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your-key
            slack:
              endpoint: https://hooks.slack.com/services/your/webhook/url
            telegram:
              endpoint: https://api.telegram.org/bot<bot-token>/sendMessage

后台数据库配置可覆盖同名 key,优先级高于本地文件。敏感 endpoint 和 secret 建议放后台并标记敏感。

数据库配置常用 key

text
spring.open.notification.channels.webhook.enabled
spring.open.notification.channels.webhook.default-provider
spring.open.notification.channels.webhook.message-type
spring.open.notification.channels.webhook.signing-enabled
spring.open.notification.channels.webhook.providers.<provider>.enabled
spring.open.notification.channels.webhook.providers.<provider>.endpoint
spring.open.notification.channels.webhook.providers.<provider>.secret
spring.open.notification.channels.webhook.providers.<provider>.message-type
spring.open.notification.channels.wechat.enabled

模板

java
Notification.send("database", NotificationMessage.builder()
        .template("order.paid")
        .variable("orderNo", "SO202605190001")
        .recipient(NotificationRecipient.user(1L))
        .build());

默认检查缺失变量但不阻断。严格模式 template.fail-on-missing-variables: true

code + channel + locale 匹配(详见 template.md)。通道 channel 优先,回退 all 通道模板。

业务事件集成

业务模块发布领域事件,应用装配层调 Notification Facade,避免业务模块直接绑定通知实现。

模块事件默认通道配置
Mall订单状态、售后状态、虚拟交付库存预警、售后待审核规则告警、订单待履约规则告警databasespring.open.mall.notifications / Notification 规则
Blog文章发布规则告警databaseNotification 规则
CMS内容审核驳回规则告警database / mailNotification 规则
Forum主题回复、点赞、收藏、回复点赞、举报处理进度databasespring.open.forum.notifications
Forum主题收到新回复规则告警databaseNotification 规则
Forum主题收到点赞规则告警databaseNotification 规则
Forum回复收到点赞规则告警databaseNotification 规则
Forum主题收到收藏规则告警databaseNotification 规则
Forum举报处理结果规则告警databaseNotification 规则
Short Link风险提示访问规则告警database / mailNotification 规则
Live Code分流兜底使用规则告警database / mailNotification 规则
Install安装完成 / 执行失败规则告警database / mailNotification 规则

Forum 联动只在应用装配层完成,Forum 模块本身只发布事件。关闭:

yaml
spring:
  open:
    forum:
      notifications:
        enabled: false

Forum 主题新回复、主题点赞、主题收藏、回复点赞和举报处理结果有两条装配链路:普通互动通知默认发给内容作者或举报人,受 spring.open.forum.notifications 控制;Notification 规则分别使用 forum.reply.created / forum.topic.liked / forum.topic.favorited / forum.reply.liked / forum.report.status-changed 事件编码,可在 /api/v1/notification-rules 配置运营接收人、通道、去重和静默窗口,未配置接收人时静默跳过。举报处理结果规则只处理非 pending 状态,避免和待审核提醒重复。

通知规则 Contract

Notification starter 只负责把消息发到指定通道;“什么事件达到阈值后该通知谁、是否去重、是否静默、是否升级”由 Notification 核心模块的通知规则 contract 承接。

可复制的最小用法:

java
NotificationRule rule = new NotificationRule(
        "open-platform.balance-low",
        true,
        3,
        Duration.ofMinutes(5),
        Duration.ofMinutes(10),
        Duration.ofMinutes(30),
        List.of("database"),
        List.of(1L),
        new NotificationRuleEscalation(
                true,
                Duration.ofHours(1),
                Duration.ofHours(2),
                List.of("mail", "webhook"),
                List.of(9L)));

NotificationRuleEvent event = new NotificationRuleEvent(
        "open-platform.balance-low",
        "app:1001",
        Instant.now(),
        3,
        Duration.ofMinutes(5),
        lastSentAt,
        lastEscalatedAt,
        Map.of("appId", 1001L));

NotificationRuleDecision decision = ruleResolver.resolve(rule, event);
if (decision.shouldSend()) {
    dispatchService.send(new SendNotificationCommand(
            decision.channels(),
            decision.recipientUserIds(),
            "open-platform.balance-low",
            "余额不足",
            "应用余额不足,请及时充值。",
            "zh-CN",
            event.dedupeKey(),
            Map.of("appId", 1001L),
            Map.of("dedupeKey", decision.dedupeKey())), dataScope);
}

解析语义:

能力说明
阈值thresholdCount 约束统计窗口内最小事件次数,thresholdWindow 用于校验调用方传入的统计窗口
去重dedupeWindow 内同一 dedupeKey 不重复发送
静默silenceWindow 内同一 dedupeKey 不再打扰接收人
升级escalation.after 到期后追加升级通道和升级接收人,escalation.dedupeWindow 防止升级通知刷屏
通道channels 为空默认 database,重复通道会去重并归一小写
接收人recipientUserIds 和升级接收人会去重;具体数据范围仍由发送 Service 校验

规则配置持久化由 spring-open-core-notification 承接,表名为 notification_rule。后台 API 前缀为 /api/v1/notification-rules,权限码为 notification:rule:view|create|update|delete

API说明
GET /api/v1/notification-rules分页查询规则配置,支持按启用状态和关键字筛选
GET /api/v1/notification-rules/templates查询内置规则模板,返回推荐编码、阈值、通道和升级策略
GET /api/v1/notification-rules/ops-summary查询规则总数、启用 / 停用、升级策略完整性、接收人配置、内置模板总体覆盖、按模板分类覆盖、待配置模板编码和升级缺口规则编码
POST /api/v1/notification-rules/export按当前筛选条件创建通知规则配置 CSV 导出异步任务,成功后通过任务产物下载接口获取文件
GET /api/v1/notification-rules/{id}查询规则详情
POST /api/v1/notification-rules创建规则配置
PUT /api/v1/notification-rules/{id}更新规则配置
POST /api/v1/notification-rules/{id}/enable启用规则
POST /api/v1/notification-rules/{id}/disable停用规则
DELETE /api/v1/notification-rules/{id}删除规则
DELETE /api/v1/notification-rules批量删除规则

规则配置字段会归一化为运行时 NotificationRule:通道默认 database,通道和接收人去重,阈值最小为 1,窗口秒数小于等于 0 时视为未配置。模板 API 只返回推荐配置,不创建或修改规则;运营摘要 API 只读聚合现有规则和内置模板,不修改规则状态。导出 API 会复用 Core Async Task 底座,创建 notification-rule.export 任务并写入 private CSV 产物,下载入口沿用异步任务的 artifact/download-url。升级策略完整性按“启用规则且启用升级”统计,存在升级通道或升级接收人任一配置即视为完整,否则返回 escalationIncompleteRuleCodes 供运维定位。Admin 后台 /#/notification-rules 已接入该 contract,支持规则分页筛选、新增 / 修改、启用 / 停用、删除 / 批量删除、通道 / 接收人配置、升级策略配置、从内置模板填充表单、顶部运营摘要、模板分类覆盖预览和升级缺口提示。当前 contract 已服务 Open Platform 余额不足、套餐耗尽、Key 异常、Provider 故障、限流、Queue 失败任务、Outbox 投递失败、补偿命令失败、Web3 扫描 / 结算失败、Scheduler 执行失败、异步任务失败、内容安全待复核和复核拒绝、Install 安装完成 / 执行失败、Mall 虚拟交付低库存和缺货、Mall 售后待审核、Mall 订单待履约、Blog 文章发布、CMS 内容审核驳回、Forum 主题新回复、Short Link 风险提示访问、Live Code 分流兜底使用等告警规则决策;更多业务事件源、更复杂升级策略和深度告警运营看板按后续功能簇推进。

后台接口

API说明
GET /api/v1/notifications通知管理分页
GET /api/v1/notifications/inbox当前用户收件箱
GET /api/v1/notifications/inbox/unread-count未读数
GET /api/v1/notifications/preferences当前用户通知偏好
PUT /api/v1/notifications/preferences保存通知偏好
GET /api/v1/notifications/send-metadata后台发送页面元数据
POST /api/v1/notifications/send后台发送
POST /api/v1/notifications/template-preview发送前模板预览
PUT /api/v1/notifications/inbox/{id}/read标记已读
PUT /api/v1/notifications/inbox/{id}/unread标记未读
PUT /api/v1/notifications/inbox/read-batch批量已读
PUT /api/v1/notifications/inbox/unread-batch批量未读
PUT /api/v1/notifications/inbox/read-all全部已读
DELETE /api/v1/notifications/inbox/{id}删除自己的站内信
DELETE /api/v1/notifications/inbox批量删除自己的站内信
GET /api/v1/notification-send-logs发送日志;支持 retryableOnly=true 只看失败且可人工重试的日志
GET /api/v1/notification-send-logs/summary发送统计(总 / 成功 / 失败 / 可重试 / 已重试 / 近 24h 失败)
POST /api/v1/notification-send-logs/{id}/retry失败重试
POST /api/v1/notification-send-logs/retry-batch批量失败重试
GET /api/v1/notification-message-attachments站内信附件
GET /api/v1/notification-read-receipts阅读回执

后台发送示例

http
POST /api/v1/notifications/send
json
{
  "channels": ["database"],
  "recipientUserIds": [1, 2],
  "template": "welcome",
  "variables": { "username": "admin" }
}

channels 为空默认 databaserecipientUserIds 去重并按数据范围校验。模板、subject、content、variables 复用渲染和发送前校验。

批量重试

json
{ "ids": [1, 2, 3] }

先校验所有日志都存在、失败、载荷可恢复,再逐条重试,避免部分发送后才发现某条不可重试。

发送日志列表和统计接口都支持 retryableOnly=true。当前 Notification 发送日志的“可重试”语义与人工重试 contract 一致:失败日志才进入可重试候选;成功日志不会出现在该筛选结果中。

短信发送运维

方式是否进 Notification 发送日志
Sms.send(...)
Notification.send("sms", ...)

需要后台查看 / 失败重试时用 Notification sms 通道:

http
GET /api/v1/notification-send-logs?channel=sms

POST /api/v1/auth/verify/sms-codes 验证码接口走 Notification sms 通道。

通知偏好

iam_user_preferencenotification 分组保存。当前默认支持:

  • 通道开关:站内信 / 邮件 / 短信 / WebSocket
  • 业务分类开关:系统 / 账号 / 待办 / 聊天 / 博客 / 论坛 / 电商 / 营销

NotificationPreferenceResolver 同时判断通道和业务分类。已接入:

  • 站内信实时推送尊重 websocketEnabled:关闭后不再推送 notification.* WebSocket 事件
  • 用户关系通知同时尊重 databaseEnabled + accountEnabled:关闭后好友申请、通过 / 拒绝、关注提醒不写收件箱
  • 会话新消息尊重 databaseEnabled + chatEnabled + 成员 muted:免打扰仍进会话未读,但不写收件箱
  • database 通道按消息属性 module 自动匹配业务分类(forumforumEnabledmallmallEnabled

其他业务优先走 Notification Facade。确实需要直接写库时必须显式复用同一偏好解析器,并提供可观测日志、重试入口或明确降级策略,在业务监听器静默吞异常。

通知跳转 Contract

站内信 attributes 携带稳定跳转目标。后端解析后写入 VO,并在 notification.* WebSocket 事件返回,三端自行解析原始 JSON。

字段说明
module业务模块(system / forum / mall
event / eventType事件编码(reply.created / order.status-changed
sourceType / sourceId触发来源对象
targetType / targetId点击后查看的目标对象
targetRoute语义化前端路由(/forum/topics/10
targetUserId用户关系类目标用户
dedupeKey业务幂等键

内置联动:

  • 用户关系:好友申请、通过 / 拒绝、关注 → 对方资料
  • Conversation:会话新消息 → /chat/conversations/{id},带 conversationType / messagePreview / 发送者信息
  • Forum:回复、点赞、收藏 → 主题;举报处理 → 举报记录
  • Mall:订单状态 → 订单;售后 → 售后单

targetRoute 是跨端语义路由,强制等于前端框架实际文件路径。App / PC / Admin 按自己路由表映射;REST 收件箱详情仍是事实来源。

站内信标题正文本地化

接口同时返回原始 subject / contentdisplaySubject / displayContent(当前语言)。显式 i18n key(messages.notification.demo.subject{messages.notification.demo.subject})自动解析,无翻译时回退原文。

站内信附件

只保存元数据和 Storage 身份,保存二进制内容,依赖 file_record 关联:

json
{
  "messageId": 1,
  "disk": "local",
  "path": "notifications/manual.pdf",
  "name": "manual.pdf",
  "contentType": "application/pdf",
  "size": 1024
}

返回时按当前 Storage 配置解析 disk + path 访问 URL。外部地址可直接传 url 兜底。

会话消息边界

用途选 Notification 还是 Conversation
通知 / 站内信 / 邮件 / 短信 / 机器人Notification
用户连续消息流(私聊、群聊、客服)Conversation(conversation_*
实时加速WebSocket,作历史消息来源

会话 API 见 view-app.md / view-pc.md 中的 Conversation 用户端契约。会话开关:

yaml
spring:
  open:
    conversation:
      enabled: true

扩展点与源码骨架

Notification starter 的核心链路保持“业务 Facade → Manager 编排 → Channel SPI 发送 → Recorder 观测”:

类型源码说明
自动配置NotificationAutoConfiguration / NotificationMailAutoConfiguration / NotificationSmsAutoConfiguration / NotificationWebSocketAutoConfiguration注册默认 log / memory / webhook 通道、可选 mail / sms / websocket 集成、Manager 和发送记录器
FacadeNotification面向业务代码的短名静态入口,只转发到 NotificationManager
ManagerNotificationManager / DefaultNotificationManager负责默认通道、多通道发送、模板渲染、失败语义和发送记录
消息 contractNotificationMessage承载标题、正文、模板、接收人、变量、locale 和通道扩展属性
事件载荷NotificationEventPayload把业务领域事件归一成通知属性,不决定具体通道
通道 SPINotificationChannel新增通道时实现该接口,并注册为 Spring Bean
记录 SPINotificationSendRecorder写入内存、数据库、审计或后续重试观测
配置解析NotificationConfigResolver / ConfigManagerNotificationRuntimeConfigSource读取本地配置和后台 ConfigManager 运行时覆盖配置

发送语义:

  • 单通道发送失败会抛出异常,适合关键通知。
  • 多通道发送按传入顺序逐个尝试,单个通道失败只返回失败结果,不阻断后续通道。
  • 配置 Template starter 后,Manager 会按 code + channel + locale 渲染;未配置模板或模板编码为空时直接使用 subject / content
  • attributes 是通道私有信息、跳转目标、业务事件和发送诊断的扩展区,可能进入发送记录,不要放密钥、token 或明文密码。
  • 业务模块优先发布领域事件,由应用装配层转换为 NotificationMessage 并选择通道,避免业务模块直接绑定某个通知实现。

开发约定

  • 通知内容长期保存用 database 通道
  • 实时在线推送用 websocket,离线消息仍落库
  • 第三方机器人密钥放环境变量或后台配置,写入代码
  • 各通道发送记录统一走 NotificationSendRecorder 扩展点
  • 业务模块发领域事件,应用装配层调 Facade
  • 直接写库时必须显式复用偏好解析器,静默吞异常
  • 跨端跳转用 targetRoute 语义路由,写死前端路径

验证命令

bash
./mvnw -pl spring-open-starters/spring-open-starter-notification -am test -DskipITs
./mvnw -pl spring-open-core/spring-open-core-notification -am -Dtest='*Notification*Test' -Dsurefire.failIfNoSpecifiedTests=false test -DskipITs
./mvnw -pl spring-open-application -am test -DskipITs -DskipFrontend

相关

Released under the Apache License 2.0.