Nightingale 订阅规则 HTTP API 实战指南:面向外部 A2A Agent 与 curl 调用的完整接口手册
【免费下载链接】nightingaleNightingale is to monitoring and alerting what Grafana is to visualization.项目地址: https://gitcode.com/GitHub_Trending/ni/nightingale
本文基于
aiagent/skill/embedded/builtin/alert-subscribe-copilot/http-api.md编写,并辅以仓库源码(路由注册、处理器实现、模型定义、内存缓存)进行纵深展开。
订阅规则(Alert Subscribe)是 Nightingale 告警通知阶段的关键机制:按条件筛选告警事件后,克隆一份副本并按订阅配置改写,再走一遍通知链路,从而实现跨团队抄送、告警升级(持续 N 分钟未处理后通知负责人)等典型场景。Nightingale 为这类订阅规则提供了一套完整的 HTTP REST API,专门供站外 A2A Agent 集成或为用户生成可执行的 curl 命令使用。读完本文,你将掌握全部 7 个端点的请求/响应形态、Bearer Token 鉴权与两级权限模型、Tryrun 逐门校验机制,以及绕过 API 直改数据库的兜底方案。
订阅规则与这套 HTTP API 的定位
在动手调接口之前,先明确这套 API 的服务对象。仓库中aiagent/skill/embedded/builtin/alert-subscribe-copilot/SKILL.md明确写道:
You are the in-app AI assistant for n9e, running inside the n9e process and already authenticated as the current user.Operate directly via the built-in tools — do not log in, do not call HTTP APIs, and do not use http_fetch against your own endpoints(the HTTP flow in
http-api.mdis for external A2A agents).
也就是说,Nightingale 内部 AI 助手(in-app assistant)禁止调用本套 HTTP 端点,它应使用内置 Function Calling 工具(如create_alert_subscribe、update_alert_subscribe,见 aiagent/tools/subscribe.go)。本套 HTTP API 是为外部 A2A Agent 和手工 curl 调用而设计的公开能力。
订阅本身发生在通知阶段(alert/dispatch/dispatch.go中的handleSubs):原始事件照常走它自己的通知路径,每个匹配的订阅会克隆一份事件副本、按订阅配置改写后再跑一遍通知链路。订阅是叠加式的——它不拦截、不替换原始通知。理解这一点,是正确使用增删改查接口的前提。
接口总览:七个端点一张表
所有端点均挂在/api/n9e前缀下,路由定义集中在 center/router/router.go。完整清单如下(与原始文档保持一致):
| 操作 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 列表(跨业务组) | GET | /api/n9e/busi-groups/alert-subscribes | 返回当前用户可见业务组下的订阅 |
| 列表(单业务组) | GET | /api/n9e/busi-group/:id/alert-subscribes | 指定业务组:id下的订阅 |
| 详情 | GET | /api/n9e/alert-subscribe/:sid | 按订阅 ID 获取完整配置 |
| 创建 | POST | /api/n9e/busi-group/:id/alert-subscribes | Body 为单个AlertSubscribeJSON 对象;group_id取自 URL |
| 更新 | PUT | /api/n9e/busi-group/:id/alert-subscribes | Body 为数组[{...}](与创建相反);按显式字段列表更新,但仍建议先 GET 详情、改完整个对象再 PUT |
| 删除 | DELETE | /api/n9e/busi-group/:id/alert-subscribes | Body:{"ids":[1,2,3]} |
| Tryrun | POST | /api/n9e/alert-subscribe/alert-subscribes-tryrun | Body:{"event_id":<历史事件ID>,"config":{...订阅草稿...}};逐门校验匹配条件,新版本(notify_version=1)还会真实发送通知规则测试——编辑后先 Tryrun 再保存 |
从路由源码可以看到每个端点的完整中间件链,例如:
pages.GET("/busi-groups/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.alertSubscribeGetsByGids) pages.POST("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.bgrw(), rt.alertSubscribeAdd) pages.PUT("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/put"), rt.bgrw(), rt.alertSubscribePut) pages.DELETE("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/del"), rt.bgrw(), rt.alertSubscribeDel) pages.POST("/alert-subscribe/alert-subscribes-tryrun", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.alertSubscribeTryRun)请求统一要求Authorization: Bearer <token>头,token 为用户令牌(用户登录后生成)。
认证与鉴权:Bearer Token 与两级权限模型
所有端点都要求Authorization: Bearer <token>。在此基础上,权限是两级叠加的:
菜单级权限(
rt.perm(...)):对应/alert-subscribes、/alert-subscribes/add、/alert-subscribes/put、/alert-subscribes/del四个操作码。实现见 center/router/router_mw.go 的perm中间件,内部调用me.CheckPerm校验。其中:- 查询类接口只要求读权限
/alert-subscribes; - 创建要求
/alert-subscribes/add; - 更新要求
/alert-subscribes/put; - 删除要求
/alert-subscribes/del; - Tryrun 挂在
/alert-subscribes/add下(与创建同级)。
- 查询类接口只要求读权限
业务组级权限(
rt.bgro()/rt.bgrw()):对 URL 中的业务组:id做数据级校验。bgro(只读)调用CanDoBusiGroup检查是否可见;bgrw(读写)调用CanDoBusiGroup(..., "rw")检查是否具备读写权限,不满足直接返回 403forbidden。实现见 center/router/router_mw.go。
因此,创建/更新/删除订阅都需要「业务组读写权限(bgrw)+ 对应/alert-subscribes/*菜单权限」双重满足;纯列表查询只需要业务组可见(bgro)+ 读菜单权限。跨业务组列表接口alertSubscribeGetsByGids对非管理员会回退到MyBusiGroupIds只列出自己所属业务组的数据(见 center/router/router_alert_subscribe.go)。
查询类接口:列表与详情
跨业务组列表
curl -H "Authorization: Bearer <token>" \ "http://<n9e-host>/api/n9e/busi-groups/alert-subscribes"- 可选查询参数
gids(逗号分隔的业务组 ID 列表),用于精确限定范围;不带时对非管理员自动收敛到其可见业务组。 - 返回当前用户可见业务组下的全部订阅(服务端不做分页,由前端自行搜索分页,见
alertSubscribeGetsByGids的注释 "Return all, front-end search and paging")。
单业务组列表与详情
# 列出业务组 2 下的订阅 curl -H "Authorization: Bearer <token>" \ "http://<n9e-host>/api/n9e/busi-group/2/alert-subscribes" # 获取订阅 123 的完整详情 curl -H "Authorization: Bearer <token>" \ "http://<n9e-host>/api/n9e/alert-subscribe/123"单业务组列表与详情处理器(alertSubscribeGets/alertSubscribeGet)在返回前会做一整套填充(见 center/router/router_alert_subscribe.go):
FillUserGroups:把user_group_ids解析为完整用户组对象;FillRuleNames:把rule_ids解析为告警规则名,规则缺失时标记"Error: AlertRule not found";FillDatasourceIds与DB2FE:把数据库中的 JSON 序列化串还原为前端/API 形态的数组字段。
返回结构中的序列化字段
理解响应结构的关键在于模型 models/alert_subscribe.go 的字段定义——数据库行里存的是 JSON 字符串,API 返回的是 JSON 数组。DB2FE(DB→前端/API 形态)负责这层转换:
datasource_ids:DB 中是 JSON 字符串,API 中是[]int64;severities:DB 中是 JSON 字符串,API 中是[]int;webhooks:DB 中是 JSON 字符串,API 中是[]string;extra_config:DB 中是 JSON 字符串,API 中是任意 JSON 对象;tags/busi_groups:本身就是ormx.JSONArr;rule_ids/notify_rule_ids:使用gorm:"serializer:json"直接序列化存储。
一个典型详情响应(片段)形如:
{ "id": 123, "name": "跨团队抄送-支付核心链路", "group_id": 2, "disabled": 0, "prod": "", "cate": "", "datasource_ids": [1, 2], "rule_ids": [45, 67], "severities": [1, 2, 3], "for_duration": 600, "tags": [{"key": "team", "func": "==", "value": "pay"}], "busi_groups": [{"key": "groups", "func": "==", "value": "支付中心"}], "notify_version": 1, "notify_rule_ids": [9], "note": "支付核心链路告警升级" }创建:POST 单对象
创建接口的 Body 是单个AlertSubscribeJSON 对象(不是数组),group_id从 URL 路径取,不读 Body。对应处理器alertSubscribeAdd(center/router/router_alert_subscribe.go)的关键行为:
- 从 URL 取
:id作为GroupId,<= 0直接 400group_id invalid; - 从登录态注入
CreateBy/UpdateBy; - 调用
AlertSubscribe.Add落库。
curl -X POST -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ "http://<n9e-host>/api/n9e/busi-group/2/alert-subscribes" \ -d '{ "name": "跨团队抄送-支付核心链路", "disabled": 0, "prod": "", "cate": "", "datasource_ids": [1, 2], "cluster": "0", "rule_ids": [45, 67], "severities": [1, 2, 3], "for_duration": 600, "tags": [{"key": "team", "func": "==", "value": "pay"}], "busi_groups": [{"key": "groups", "func": "==", "value": "支付中心"}], "notify_version": 1, "notify_rule_ids": [9], "note": "支付核心链路告警升级" }'落库前的校验逻辑集中在AlertSubscribe.Verify()(models/alert_subscribe.go),外部 Agent 组装请求时应遵循这些规则:
severities必填,新旧版本都校验,缺省报severities is required;[1,2,3]表示全部严重级别;- 新版本(
notify_version=1)要求notify_rule_ids非空,否则报no notify rules selected;且该校验会清空旧版改写字段(redefine_severity/redefine_channels/webhooks/user_group_ids/new_channels等); - 旧版本(
notify_version=0,默认)要求:若指定了user_group_ids,则new_channels(新告警通知渠道)必须指定,否则可能因告警规则未配置通知渠道而导致订阅通知发不出去;同时NotifyRuleIds会被强制置空; datasource_ids为空数组会自动规范化为[0](FE 表示"全部数据源"的哨兵值),Verify中的IsAllDatasource分支处理该归一化。
更新:PUT 数组与显式字段列表
更新接口与创建相反:Body 必须是数组[{...}](可批量更新多条)。对应处理器alertSubscribePut(center/router/router_alert_subscribe.go)的语义要点:
- 按显式字段列表更新:
Update只更新服务端白名单列——name, disabled, prod, cate, datasource_ids, cluster, rule_id, rule_ids, tags, redefine_severity, new_severity, redefine_channels, new_channels, user_group_ids, update_at, update_by, webhooks, for_duration, redefine_webhooks, severities, extra_config, busi_groups, note, notify_rule_ids, notify_version; rule_id被强制清零:源码注释说明,"批量订阅告警规则功能上线后改用rule_ids而非rule_id;更新时置rule_id=0,防止遗留的旧字段引发错误订阅";- 服务端自动覆盖
UpdateBy/UpdateAt。
# 先 GET 详情拿到完整对象,修改后再 PUT(推荐做法) curl -X PUT -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ "http://<n9e-host>/api/n9e/busi-group/2/alert-subscribes" \ -d '[{ "id": 123, "name": "跨团队抄送-支付核心链路", "disabled": 0, "prod": "", "cate": "", "datasource_ids": [1, 2], "cluster": "0", "rule_ids": [45, 67], "severities": [1, 2, 3], "for_duration": 1200, "tags": [{"key": "team", "func": "==", "value": "pay"}], "busi_groups": [{"key": "groups", "func": "==", "value": "支付中心"}], "notify_version": 1, "notify_rule_ids": [9] }]'之所以强烈建议"先 GET 详情 → 改完整个对象 → 再 PUT":由于tags/busi_groups/webhooks/extra_config/notify_rule_ids等是序列化字段,且数组字段在更新时是整体替换语义,若只传部分字段,未触及的序列化字段会被空值清掉。这也是站内内置工具update_alert_subscribe采用"DB2FE 后再合并 patch、整行替换式UpdateFull"的原因(见 aiagent/tools/subscribe.go 中updateAlertSubscribe的注释:merge 底座必须是 FE 形态,否则未修改的序列化字段会被空 FE 值清掉)。
删除:ids 数组
curl -X DELETE -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ "http://<n9e-host>/api/n9e/busi-group/2/alert-subscribes" \ -d '{"ids": [123, 124, 125]}'处理器alertSubscribeDel将 Body 绑定到idsForm({"ids":[...]})后调用AlertSubscribeDel按主键批量删除(center/router/router_alert_subscribe.go、models/alert_subscribe.go)。
Tryrun:先验证后保存
POST /api/n9e/alert-subscribe/alert-subscribes-tryrun是本套 API 中最具实战价值的端点——编辑订阅后、正式保存前,先用一个历史事件试跑,逐门校验匹配条件。请求体为:
{ "event_id": 123456, "config": { "...订阅草稿..." } }其中event_id必须是历史告警事件 ID,config是待验证的订阅草稿(与创建接口的单个对象同构)。处理器alertSubscribeTryRun(center/router/router_alert_subscribe.go)的执行顺序就是引擎实际的匹配顺序:
curl -X POST -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ "http://<n9e-host>/api/n9e/alert-subscribe/alert-subscribes-tryrun" \ -d '{ "event_id": 123456, "config": { "name": "草稿-支付链路升级", "datasource_ids": [1, 2], "rule_ids": [45, 67], "severities": [1, 2], "tags": [{"key": "team", "func": "==", "value": "pay"}], "busi_groups": [{"key": "groups", "func": "==", "value": "支付中心"}], "notify_version": 1, "notify_rule_ids": [9] } }'逐门校验顺序
- 配置自检:先执行
Verify(),配置不合法(如缺severities)直接报错; - 事件存在性:按
event_id查历史事件,不存在返回 404event not found; - 数据源门:
MatchCluster比对事件DatasourceId与datasource_ids,不匹配报event datasource not match; - 告警规则门:
rule_ids非空时要求事件RuleId在列表中,否则报event rule id not match; - 标签门:
MatchTags比对事件标签与tags,不匹配报event tags not match; - 业务组名门:
MatchGroupsName比对事件业务组名与busi_groups(注意这里匹配的是事件所属业务组的名称),不匹配报event group name not match; - 严重级别门:
severities含 0 表示通配,否则要求事件 Severity 命中列表,不匹配报event severity not match。
任一扇门失败即返回对应错误信息,这正是"订阅没生效"排查时最有用的反馈。
新版本的真实通知测试
如果草稿是新版本(notify_version=1)且notify_rule_ids非空,Tryrun 会进一步真实执行通知规则的发信测试:逐个加载notify_rule_ids指向的通知规则,对其每一个NotifyConfig调用SendNotifyChannelMessage真正发送测试通知。全部成功返回event match subscribe and notification test ok;通知规则不存在返回 404,发信失败返回notify rule send error: ...。
如果草稿是旧版本(notify_version=0),则走ModifyEvent逻辑:校验new_channels是否选择了渠道、user_group_ids对应的用户是否为所选渠道配置了 token(如钉钉/企业微信/飞书等,见 center/router/router_alert_subscribe.go 对非默认渠道的跳过逻辑),返回event match subscribe and notify settings ok。
所以工作流应该是:编辑草稿 → Tryrun 验证(必要时结合返回的错误逐门修正)→ 确认所有门通过后再 PUT 正式保存。
生效与一致性:约 9 秒缓存轮询
调用写接口落库后,订阅不会立刻生效——Nightingale 采用内存缓存 + 轮询同步机制。缓存实现在 memsto/alert_subscribe_cache.go:
loopSyncAlertSubscribes每9000ms(9 秒)轮询一次,通过AlertSubscribeStatistics的total/last_updated判断数据是否变化,变了才全量重载(见StatChanged与Set的配合);- 重载时
Disabled == 1的订阅被直接过滤(不进入内存表),因此置disabled:1等于立刻失能,置回disabled:0后最多 9 秒恢复; - 缓存按
rule_ids建索引:rule_ids为空时归入 key0(表示订阅所有规则的事件),CompatibleWithOldRuleId保证老数据rule_id字段也能兼容(见 memsto/alert_subscribe_cache.go)。
这意味着:任何通过 API 或直接改库的变更,最迟约 9 秒后生效,无需重启服务。
兜底方案:直接修改数据库
当 API 无法覆盖(例如批量导入、脚本修复、极端场景下的紧急处置)时,可以直改数据库。约束如下(与原始文档一致):
- 数据表名为
alert_subscribe(模型TableName()见 models/alert_subscribe.go); - 以下列为JSON / 序列化字段,修改时必须按 JSON 格式写入,不能写裸字符串:
tags(ormx.JSONArr,标签过滤数组)busi_groups(ormx.JSONArr,业务组名过滤数组)webhooks(回调 URL 的 JSON 字符串)extra_config(扩展配置 JSON 字符串)notify_rule_ids/rule_ids(serializer:json)datasource_ids/severities(DB 中为 JSON 字符串,API 中为数组,二者由FE2DB/DB2FE互转,见 models/alert_subscribe.go);
- 直改数据库后无需重启:内存缓存约 9 秒后自动重载(轮询周期见上节);
- 修改前务必备份数据——序列化字段格式错误会导致该条订阅在缓存同步时被跳过(
syncAlertSubscribes中Parse/DB2FE失败仅记 warning 并continue,见 memsto/alert_subscribe_cache.go),从而静默失效。
边界说明:站内助手与外部 Agent 的分工
最后重申这套 API 的使用边界,避免误用:
| 场景 | 正确做法 |
|---|---|
| Nightingale 站内 AI 助手(in-app assistant) | 使用内置 FC 工具(create_alert_subscribe/update_alert_subscribe/list_alert_subscribes/get_alert_subscribe_detail),不调用 HTTP API、不登录、不对自身端点发起 http_fetch |
| 外部 A2A Agent / 自动化脚本 | 使用本文的 HTTP API,携带Authorization: Bearer <token> |
| 给用户提供可复制执行的命令 | 按本文 curl 模板提供,仅在用户明确要求 curl 时输出,不要替用户执行 |
外部 Agent 若要完整操作订阅,建议的调用序列是:GET /busi-groups/alert-subscribes(或GET /alert-subscribe/:sid)获取现状 → 组装/修改对象 →POST /alert-subscribe/alert-subscribes-tryrun逐门验证 → 通过后再POST创建或PUT更新 → 等待约 9 秒缓存刷新后生效。这套"先验证、后保存、再确认生效"的闭环,能把订阅误配造成的告警漏发/错发风险降到最低。
参考资料(仓库内)
- 接口规范原文:aiagent/skill/embedded/builtin/alert-subscribe-copilot/http-api.md
- 路由注册与中间件链:center/router/router.go、权限中间件 center/router/router_mw.go
- 处理器实现(含 Tryrun 逐门校验):center/router/router_alert_subscribe.go
- 数据模型与序列化字段定义:models/alert_subscribe.go
- 内存缓存与 9 秒轮询:memsto/alert_subscribe_cache.go
- 站内内置工具实现(外部 Agent 可对照参考字段形状):aiagent/tools/subscribe.go
【免费下载链接】nightingaleNightingale is to monitoring and alerting what Grafana is to visualization.项目地址: https://gitcode.com/GitHub_Trending/ni/nightingale
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考