Nightingale 业务组(BusiGroup)API 指南:基于 Skill Gateway 的只读查询与 bgid/gid 溯源
【免费下载链接】nightingaleNightingale is to monitoring and alerting what Grafana is to visualization.项目地址: https://gitcode.com/GitHub_Trending/ni/nightingale
业务组(Business Group,简称 BG)是 Nightingale(n9e)中一切监控资源的归属与权限单元:告警规则、目标(targets)、仪表盘、静默、订阅、录屏规则等都必须归属于某个业务组,而用户对资源的访问权限也是按业务组授予的。本文围绕aiagent/skill/embedded/builtin/skill-creator/api/busi-groups.md这一份 Skill 内置 API 参考文档,完整讲解通过 Skill Gateway 查询业务组的三个只读接口、全部查询参数与响应字段,并结合仓库源码(models/busi_group.go、center/router/router_busi_group.go、models/user.go)深入解析其 RBAC 行为与底层实现。读完本文,你将能正确构造bgid/gid/gids参数去调用其它 n9e 只读接口,并能安全地在自己的 Skill 脚本中完成"按业务组发现与筛选资源"的流程。
一、业务组:n9e 的 RBAC 与资源归属单元
在 Nightingale 中,每个告警规则(alert rule)、目标(target)、仪表盘(board)、静默(mute)、订阅(subscribe)、录屏规则(recording rule)等资源都恰好归属于一个业务组,用户对这些资源的访问权限则通过团队(user-group / teams)按组授予。
这里有一个贯穿全篇、必须牢记的关键约定:业务组的id正是其它接口中所说的bgid、gid或gids。例如:
/busi-group/<id>/alert-rules路径中的<id>;/busi-groups/alert-rules、/targets、/alert-his-events/list等接口上的gids/bgid查询参数。
因此,本文介绍的三个接口是整个查询体系的第一站:先从这里发现"当前用户可见哪些业务组、它们的 id 是多少",再把拿到的 id 传给其它接口做资源过滤。
在源码层面,这一模型的落点是 models/busi_group.go 中的BusiGroup结构体(数据表busi_group)与 models/busi_group_member.go 中的BusiGroupMember结构体(数据表busi_group_member,保存"业务组-团队"的关联关系及perm_flag权限标志)。业务组与团队是多对多关系,一个业务组可以由多个团队共同管理,一个团队也可以管理多个业务组。
二、调用前提:Skill Gateway 协议速览
本文档描述的接口属于Skill Gateway 调用,是只读 GET 请求。正式动手前,先了解 n9e-api.md 规定的通用协议(写任何脚本前都应先读该索引文件):
- Socket 路径来自环境变量
N9E_SKILL_GATEWAY,通过它发送换行分隔的 JSON(newline-delimited JSON); - 路径必须包含
/api/n9e前缀,例如请求{"method":"GET","path":"/api/n9e/busi-groups",...}; query中的所有值必须是字符串,例如{"limit":"300","all":"true"},而不是数字或布尔值(这一点只针对querymap;POSTbody内使用原生 JSON 类型);- 响应信封为
{"ok":true,"status":200,"data":{"dat":<payload>,"err":""}}——一定要读取data["dat"];若err非空则说明 n9e API 报错; - 列表的两种形态:Pattern A 为
dat = {"list":[...],"total":N}(分页,用于高频事件/目标接口);Pattern B 为dat = [...]裸数组(无 total,一次性返回,用于配置对象列表)。本文的业务组接口属于Pattern B; - 路径不能凭空猜测:错误路径不会返回 404,n9e 会把 SPA 的
index.html(HTML 字符串)放进data,导致静默失败。脚本必须校验ok为 true、data是 dict 且data["err"]为空后再使用data["dat"]; - Deny-list:
/datasource*、/notify-channel*、/users、SSO/IdP 等携带密钥或涉及写操作的路径会被网关以ok:false拒绝,不要试图绕过(完整清单见 n9e-api.md 末尾的 Blocked 一节)。
三、三个核心端点一览
busi-groups.md定义了三个端点,覆盖了"列表 → 详情 → 标签"的完整发现链路:
| Path | 用途 | dat形态 |
|---|---|---|
/busi-groups | 当前用户可见的业务组(管理员:全部;其它用户:其团队所拥有的组) | Pattern B——BusiGroup裸数组 |
/busi-group/:id | 单个业务组,附带其所属团队(user_groups被填充),:id位于路径中 | 单个BusiGroup对象 |
/busi-groups/tags | 跨业务组去重后的目标标签集合 | 字符串裸数组 |
三条端点分别解决三个问题:/busi-groups用于枚举和发现 id;/busi-group/:id用于查看某个组的完整归属信息(谁在管理、读写权限如何);/busi-groups/tags用于"我要按标签筛选目标"场景下的标签字典获取。
四、/busi-groups:列表查询与查询参数
4.1 查询参数
| 参数 | 类型 | 必填 | 默认值 | 含义 |
|---|---|---|---|---|
query | string | 否 | "" | 对组name的不区分大小写子串匹配。(隐藏回退:对管理员,若按名字无匹配,则会把它当作目标ident重试一次,以找到该主机所属的业务组。) |
limit | int(字符串) | 否 | 300 | 返回的最大行数。 |
all | bool(字符串) | 否 | false | true= 列出系统中的每一个业务组(管理员无论此标志如何都始终看到全部);否则仅列出当前用户团队所拥有的组。 |
注意:通过网关传参时所有值都是字符串,例如{"limit":"300","all":"true"}。
4.2 可见性规则与隐藏的 ident 回退
/busi-groups的可见性并非"全量返回",其 RBAC 逻辑在 models/user.go 的User.BusiGroups方法中体现得淋漓尽致:
- 管理员(
u.IsAdmin())或传了all=true:直接按name like "%query%"查询全部业务组; - 普通用户:先通过
MyGroupIds拿到用户所属的团队 id,再经BusiGroupIds(见 models/busi_group_member.go)反查出这些团队有权限的业务组 id 集合,最后限定id in ?过滤——用户永远只能看到自己团队所拥有的组; - 隐藏的 ident 回退:当
query按名字查不到任何组时,代码会把query当作目标ident(主机标识)再查一次TargetGet(ctx, "ident=?", query),若命中则返回该主机所属的业务组;普通用户还会额外校验该主机的组是否与busiGroupIds有交集(t.MatchGroupId(busiGroupIds...))。源码注释戏称这是"一般不告诉别人的隐藏功能",但它确实是一个实用的逆向查找入口。
4.3 列表返回的注意事项
/busi-groups会填充update_by_nickname(计算字段),但不会填充user_groups(保持为空/null);- 当你需要"所属团队及其权限标志"时,请改用
/busi-group/:id。
update_by_nickname的填充由 models/user.go 的FillUpdateByNicknames泛型函数完成:它通过反射读取每个元素的UpdateBy字段,批量查用户名→昵称映射(UserNicknameMap),再写回UpdateByNickname字段,避免了对数据库的 N 次查询。路由层在 center/router/router_busi_group.go 的busiGroupGets中调用它。
五、/busi-group/:id:单个业务组与其所属团队
当需要获取某个业务组的完整归属信息时,使用/busi-group/:id,id必须放在路径中(例如/api/n9e/busi-group/2)。
该接口与列表接口的关键差异在于user_groups字段被填充。其实现对应 center/router/router_busi_group.go 的busiGroupGet:先通过BusiGroup中间件按路径参数拿到目标业务组,再调用bg.FillUserGroups(rt.Ctx)。而FillUserGroups的实现在 models/busi_group.go:
- 通过
BusiGroupMemberGetsByBusiGroupId查出该业务组的所有成员关系(busi_group_member表); - 对每条成员关系,用
UserGroupGetById取回完整的团队对象(UserGroup); - 组装成
UserGroupWithPermFlag(定义于 models/busi_group.go),其中PermFlag直接取自成员记录的perm_flag字段。
perm_flag只有两个取值:rw(读写)与ro(只读)。一个业务组通常至少要有一个rw的团队来承担管理职责——路由层busiGroupAdd(center/router/router_busi_group.go)在创建业务组时校验:members不能为空,且必须至少有一个团队是rw权限,否则直接报 400。同样,DelMembers(models/busi_group.go)在删除成员时会确保业务组至少保留一个团队,否则返回 "the business group must retain at least one team"。
六、/busi-groups/tags:跨组目标标签字典
GET /api/n9e/busi-groups/tags?gids=1,2,3- 返回从所选业务组的目标(targets)上收集到的去重标签字符串数组(
[]string),例如["env=prod","region=cn-east-1"]; - 接受可选的
gids查询参数(逗号分隔的业务组 id;为空表示当前用户的所有业务组)。
其实现链路在 center/router/router_busi_group.go 的busiGroupsGetTags:先用TargetIndentsGetByBgids(models/target_busi_group.go)根据业务组 id 集合查出所有目标 ident,再调用TargetGetTags(models/target.go)聚合并去重出标签。典型用法是:拿到标签字典后,与/targets接口配合,用目标标签做进一步的筛选与分组统计。
七、响应对象:BusiGroup全字段解析
/busi-groups返回裸数组(Pattern B),数组中的每个元素是一个BusiGroup对象;/busi-group/:id则直接返回单个对象。字段定义与源码BusiGroup结构体(models/busi_group.go)一一对应:
| 字段(json) | 类型 | 含义 |
|---|---|---|
id | int64 | 业务组 id——即其它接口中使用的bgid/gid/gids的值 |
name | string | 业务组显示名(全局唯一) |
label_enable | int | 1= 该业务组还会向目标的指标注入一个标签;0= 关闭 |
label_value | string | label_enable=1时注入的标签值(否则为空) |
create_at | int64 | 创建时间,unix 秒 |
create_by | string | 创建者用户名 |
update_at | int64 | 最后更新时间,unix 秒 |
update_by | string | 最后更新者用户名 |
update_by_nickname | string(计算字段) | 由update_by解析出的显示昵称 |
user_groups | array(计算字段) | 所属团队 + 权限标志。仅由/busi-group/:id填充;在/busi-groups列表中为空/null。每个元素形如{"user_group": <UserGroup>, "perm_flag": "ro"\|"rw"} |
关于user_groups中内嵌的UserGroup对象,它携带id、name、note、create_at、create_by、update_at、update_by、update_by_nickname,以及(在此处通常为空的)users/busi_groups字段。
7.1 关于 label_enable / label_value 的补充
label_enable与label_value是业务组上比较容易忽略但影响深远的字段。从 models/busi_group.go 的Update与BusiGroupAdd(models/busi_group.go)可以看出其约束:
name全局唯一,创建/更新时会做BusiGroupExists校验;- 当
label_enable=1时,label_value全局唯一——即不允许两个业务组注入相同的标签值,否则会报 "BusiGroup already exists"; - 当
label_enable=0时,label_value会被强制清空为""。
这意味着业务组具备"向归属目标的指标注入固定标签"的能力,可用于在查询指标时区分数据来源归属(源码中以bgLabelKey参数贯穿TargetGetTags等目标处理逻辑)。
八、完整请求 / 响应示例
8.1 列表:/busi-groups
请求:
{"method":"GET","path":"/api/n9e/busi-groups","query":{"limit":"300"}}响应(已裁剪):
{ "ok": true, "status": 200, "data": { "dat": [ { "id": 2, "name": "default-busi-group", "label_enable": 0, "label_value": "", "create_at": 1700000000, "create_by": "root", "update_at": 1700000000, "update_by": "root", "update_by_nickname": "Administrator", "user_groups": null } ], "err": "" } }8.2 详情:/busi-group/2(附带所属团队)
请求:
{"method":"GET","path":"/api/n9e/busi-group/2","query":{}}响应:
{ "ok": true, "status": 200, "data": { "dat": { "id": 2, "name": "default-busi-group", "label_enable": 0, "label_value": "", "create_at": 1700000000, "create_by": "root", "update_at": 1700000000, "update_by": "root", "update_by_nickname": "Administrator", "user_groups": [ {"user_group": {"id": 1, "name": "admins", "note": ""}, "perm_flag": "rw"} ] }, "err": "" } }对比两份响应可以直观看到差异:列表接口中user_groups为null,而详情接口中它被填充为一个包含{"user_group": {...}, "perm_flag": "rw"}的数组。示例中id=2、name=default-busi-group是安装初始化时自动创建的默认业务组,由名为admins的团队以rw(读写)权限管理。
九、脚本中的实战用法:响应校验与 id 溯源
9.1 响应校验模板
由于错误路径会静默返回 HTML,脚本中应始终先做三段式校验再取数(模板见 n9e-api.md 的 "Validate every response" 一节):
import json, os, socket def call(path, query=None): req = {"method": "GET", "path": "/api/n9e/" + path.lstrip("/"), "query": query or {}} s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(os.environ["N9E_SKILL_GATEWAY"]) s.sendall((json.dumps(req) + "\n").encode()) buf = b"" while True: chunk = s.recv(65536) if not chunk: break buf += chunk if b"\n" in buf: break resp = json.loads(buf.split(b"\n")[0].decode()) if not resp.get("ok") or not isinstance(resp.get("data"), dict): raise RuntimeError(f"gateway call failed: {str(resp)[:200]}") env = resp["data"] if env.get("err"): raise RuntimeError(f"n9e api error: {env['err']}") return env["dat"]9.2 典型流程:发现 → 溯源 → 过滤
一个典型的业务组驱动查询流程如下:
- 发现:
/busi-groups?limit=300拿到全部可见组的id/name映射; - 溯源:对某个不确定归属的资源(如告警事件返回的
bgid),可用/busi-group/<id>反查组名与管理团队; - 过滤:把选定的
gids(逗号分隔字符串)传给/busi-groups/alert-rules、/targets、/alert-his-events/list等资源端点,实现"只看某几个业务组"的查询; - 按标签分组:
/busi-groups/tags?gids=...获取标签字典后,再结合目标标签做汇总统计。
注意业务组相关接口的两种作用域惯用法(见 n9e-api.md 的 "Business-group scoping idiom"):跨组资源用/busi-groups/<resource>?gids=...(空gids= 当前用户 RBAC 允许的所有组),单组资源用/busi-group/<id>/<resource>(<id>必填于路径)。
9.3 权限边界:只读与 Deny-list
通过 Skill Gateway 调用时,业务组接口仅为只读 GET。网关会以发起对话的用户的身份执行请求,n9e 自身的路由中间件会做常规 RBAC 与业务组权限校验;而携带密钥或写操作的路径(如/datasource*、/notify-channel*、/users、SSO 相关等)会被网关以ok:false拒绝。业务组的创建、更新、成员管理等写操作不在网关能力范围内,应在 n9e Web 控制台的"业务组"管理界面完成。
十、延伸阅读
- 调用协议、响应信封、列表形态与 Deny-list 总览:n9e-api.md
- 业务组数据模型与增删改查、删除级联检查:models/busi_group.go、models/busi_group_member.go
- 业务组 HTTP 路由与参数解析(列表/详情/标签/成员管理):center/router/router_busi_group.go
- 业务组可见性的 RBAC 核心逻辑(admin/all/ident 回退):models/user.go
- 配套资源接口(按组过滤的实际应用场景):api/alert-rules.md、api/targets.md、api/alert-events.md、api/user-groups.md
- 编写调用这些接口的 Skill 脚本的完整规范:SKILL.md
【免费下载链接】nightingaleNightingale is to monitoring and alerting what Grafana is to visualization.项目地址: https://gitcode.com/GitHub_Trending/ni/nightingale
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考