1. 页面概览
1.1 是什么
数据打标(Markings)是 LightFoundry 数据治理体系中的「密级标签」管理页面,对应 V5 Stage 5 的 B7-4 功能点。它以三张打标表(fg_markings / fg_marking_bindings / fus_user_markings)为数据底座,让管理员可以为 PII / PHI / CONFIDENTIAL 等敏感数据建立全局唯一的打标定义,把打标绑定到对象类型、本体属性、数据集与指标四类目标上,再通过用户授权矩阵把打标授予具体用户,最终在语义查询链路中形成「属性级第二道列级闸门」:未持有某打标的用户查询时,该打标绑定的属性列以 NULL 呈现(复用 query.go 既有属性级安全路径的 NULL AS col 处理)。
本页面本身只负责打标管理、绑定树与授权矩阵三类管理操作;真正的数据可见性生效位置在查询前置(由接线 agent 将本包 MarkingChecker 接口注入 semantic 查询服务)。因此本页的每一项改动——新建打标、绑定属性、勾选授权——都会立即影响后续对象语义查询返回的列集。
1.2 核心价值
| 能力 | 说明 |
|---|---|
| 打标定义 CRUD | 名称全局唯一(如 PII / PHI / CONFIDENTIAL),类别如 privacy / security / regulatory,描述自由填写 |
| 四类目标绑定 | object_type / property / dataset / metric 四种目标类型,property 绑定按 api_name 匹配,作用于所有含同名属性的对象类型 |
| 用户授权矩阵 | 用户 × Marking 勾选即授权/撤销,矩阵由 /markings/user-markings 全量授权记录渲染 |
| CLS 叠加闸门 | 与 ApplyCLS(B3-5.2)叠加生效、取更严格者(MergeHiddenColumns 并集合并);任一来源隐藏该列则最终隐藏 |
| admin 语义 | 全部操作要求 admin 角色,后端经 rbac.IsUserAdmin 强制校验,前端菜单只对管理员可用 |
1.3 一句话总结
数据打标页是把「谁可以看哪一列」从 ApplyCLS 的列级策略中再抽出一层密级标签门禁的管理界面——打标、绑定、授权三步到位,未授权用户查询绑定列即得 NULL。
2. 访问入口
2.1 路由与菜单
- 路由路径:
/foundry/markings,路由名FoundryMarkings,meta.title为「数据打标」,meta.requiresAuth: true(定义于action/web/src/router/index.js第 358-363 行,Foundry 子路由内)。 - 菜单入口:Foundry 侧边栏(
action/web/src/views/FoundryLayout.vue第 108 行)菜单项「数据打标」,路径与路由一致,点击进入本页。 - 源码文件:
action/web/src/views/MarkingPage.vue。
2.2 认证与权限
- 路由级
requiresAuth: true:未登录访问时,路由守卫会拦截并引导到登录页。 - API 级认证:所有请求经
action/web/src/api/client.js统一携带aip_token(localStorage 中的 JWT,Authorization: Bearer <token>);任一接口返回 401 时响应拦截器清空aip_token/aip_username并跳转/login。 - 角色限制:本页全部 API 为 admin 组语义,后端
marking_handlers.go的requireAdmin会校验当前用户是否具备 admin 角色(经rbac.IsUserAdmin判定;RBAC 未注入时退化为 JWT roles 含 admin 或 username 为 admin 的兜底)。非 admin 调用任何/markings接口都会收到 403 +{"error":"需要 admin 权限"}。 - 404 排错:若路由后页面空白或控制台报接口 404,先确认后端 Foundry 服务(端口 18081)已启动且
protected组已挂载 markings 路由(RegisterMarkingRoutes(protected, NewMarkingHandler(...))在server.go第 1142 行);前端通过 Vite 代理/api分流到 18081,若代理指向错误后端(如 18080 AIP)也会 404。
2.3 端口与 API 前缀
端口:18081(Foundry 后端独立端口)。API 前缀:/api(前端 client.js baseURL 为 /api/v1,Vite 代理把 /api 分流到 18081)。本页所有接口均挂在 /api/v1 下,如 GET /api/v1/markings。
3. 界面布局
数据打标 Markings
└─ 提示条(alert,右上角「关闭」)
└─ Tab 切换:Marking 管理 | 绑定树 | 用户授权矩阵
├─ Tab1 Marking 管理
│ ├─ 卡片「Marking 管理」:+ 新建 Marking / 收起表单
│ ├─ 创建/编辑表单(名称 name * / 类别 category / 描述 description + 提交按钮)
│ └─ 列表表格(名称/类别/描述/绑定数/创建时间/操作)
├─ Tab2 绑定树
│ ├─ 选择 Marking 下拉 + 刷新按钮
│ ├─ 「已绑定的目标」表格(目标类型/目标 ID/绑定时间/操作)
│ └─ 「新增绑定」区(target_type 下拉 + target_id 输入 + 绑定按钮 + 说明块)
└─ Tab3 用户授权矩阵
├─ 用户 × Marking 勾选表格
└─ 说明块(admin 语义 + 撤销立即生效)
各板块职责:
- 提示条:页面所有操作结果(成功/失败)统一在此展示,
alert-info信息态、alert-error错误态、alert-success成功态,点「关闭」清空。 - Tab 切换:下划线式 tab(与 Foundry 其它页一致),切换不触发数据重载,数据在
onMounted一次性拉齐。 - Tab1 列表:展示所有打标与各自绑定数(绑定数来自
GET /markings/bindings全量绑定统计),行内提供「编辑」「删除」。 - Tab2 绑定树:选定打标后查看其绑定目标,可新增 object_type / property / dataset / metric 四类绑定或逐条解除。
- Tab3 授权矩阵:行=用户,列=打标,勾选即授权,取消勾选即撤销,勾选状态来自
/markings/user-markings全量授权记录。
4. 交互元素详解
4.1 Tab 切换
| 元素 | 位置 | 含义 | 默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|---|
| Tab「Marking 管理」 | 页面顶部 | 打标 CRUD 视图 | 激活 | 切换到 Tab1 | 无(数据已预加载) |
| Tab「绑定树」 | 页面顶部 | 绑定管理视图 | 未激活 | 切换到 Tab2 | 无 |
| Tab「用户授权矩阵」 | 页面顶部 | 授权视图 | 未激活 | 切换到 Tab3 | 无 |
4.2 Tab1 Marking 管理
| 元素 | 含义 | 必填/默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|
| 按钮「+ 新建 Marking」/「收起表单」 | 展开/收起创建表单 | — | 展开时清空表单并重置编辑态 | 无 |
| 输入框「名称 name *」 | 打标名称,placeholder「如 PII / PHI / CONFIDENTIAL」 | 必填 | 保存时 trim 后提交 | POST /markings、PUT /markings/:id |
| 输入框「类别 category」 | 打标类别,placeholder「如 privacy / security / regulatory」 | 选填 | 同上 | 同上 |
| 输入框「描述 description」 | 说明文字,placeholder「打标说明」 | 选填 | 同上 | 同上 |
| 按钮「创建 Marking」/「保存修改」 | 提交新建/更新(busy 时显示「提交中...」并禁用) | — | 成功后 alert 提示、重置表单并 reloadAll 刷新 | POST /markings、PUT /markings/:id |
| 按钮「取消」 | 放弃本次编辑 | — | 重置表单并收起 | 无 |
| 行内按钮「编辑」 | 将行数据填入表单进入编辑态 | — | 表单标题切换为「保存修改」 | 无 |
| 行内按钮「删除」 | 删除打标 | — | confirm('确定删除 Marking"xxx"吗?(绑定与用户授权将一并清除)') 通过后删除 | DELETE /markings/:id |
表格列:名称、类别(status-badge 徽章,无类别显示 -)、描述、绑定数(来自绑定统计,bindingsByMarking)、创建时间(取前 10 位,即日期)、操作。空列表显示「暂无 Marking,请点击"新建 Marking"添加。」,加载中显示「加载中...」。
4.3 Tab2 绑定树
| 元素 | 含义 | 必填/默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|
| 下拉「选择 Marking」 | 选定要管理的打标 | — | change 时 loadBindings 拉取该打标绑定 | GET /markings/:id/bindings |
| 按钮「刷新」 | 刷新打标/绑定/授权 | — | reloadAll | GET /markings、/markings/bindings、/markings/user-markings |
| 下拉「目标类型 target_type」 | 绑定目标类型 | 必填,默认 property | 切换时清空 target_id 与属性列表 | 无 |
| 「目标 ID target_id」 | 目标标识 | 必填 | object_type 为输入框+对象 api_name datalist;property 为属性下拉(对象名.属性名(显示名));dataset/metric 为自由输入 | POST /markings/:id/bindings |
| 按钮「绑定」 | 提交绑定 | 需已选 Marking 且 target_id 非空 | 成功后清空 target_id、刷新绑定 | POST /markings/:id/bindings |
| 行内按钮「解除」 | 解除单条绑定 | — | confirm 通过后删除绑定记录 | DELETE /markings/:id/bindings/:bindingId |
目标类型四种取值:object_type(对象类型) 绑定对象 api_name;property(本体属性) 按属性 api_name 匹配、作用于所有含同名属性的对象类型;dataset(数据集) 绑定数据集 rid / id;metric(指标) 绑定指标名称。数据集与指标为信息性记录,后端不做存在性校验(可先行绑定);对象类型与属性会校验存在性(validateOntologyTarget 查 ontology 主表,不存在报 NOT_FOUND)。绑定列表每行以目标类型徽章着色(object_type 蓝、property 紫、dataset 橙、metric 绿),property 行附带提示「(按属性名作用于各对象类型同名列)」。
4.4 Tab3 用户授权矩阵
| 元素 | 含义 | 操作效果 | 后端调用 |
|---|---|---|---|
| 勾选框(用户 × Marking) | 勾选=授予该用户持有对应 Marking;取消=撤销 | 勾选时 POST 授权记录;取消时按已有授权记录 id DELETE | POST /markings/user-markings、DELETE /markings/user-markings/:id |
| 提示「勾选 = 授予该用户持有对应 Marking;取消勾选 = 撤销。持有 Marking 的用户才能看到其绑定属性列。」 | 操作语义说明 | — | 无 |
| 说明块「授权为 admin 语义(后端强制校验 admin 角色);撤销后用户立即失去对应绑定属性列的可见性。」 | 安全语义说明 | — | 无 |
矩阵数据流:GET /markings/users 拉全部用户(行),GET /markings/user-markings 拉全部授权记录(勾选状态 held() 判定 user_id+marking_id 是否命中),markings 为列头。每次勾选/取消后重新拉授权记录并 alert 提示「已授权 xxx 持有 xxx」或「已撤销 xxx 的 xxx」。
5. 后端关联
5.1 API 客户端
本页使用 action/web/src/api/client.js(默认导出 apiClient):baseURL: '/api/v1',timeout: 30000(30 秒);请求拦截器从 localStorage.getItem('aip_token') 读取 JWT 注入 Authorization: Bearer <token>;响应拦截器 401 时清 token、跳 /login;其余错误透传。本页未自定义导出函数,全部直接 apiClient.get/post/put/delete 调用。
5.2 端点表
| 方法 | 路径(前缀 /api/v1) | 请求体 | 用途 |
|---|---|---|---|
| GET | /markings | — | 列出全部打标 |
| POST | /markings | {name, category, description} | 创建打标(name 唯一) |
| GET | /markings/:id | — | 打标详情 |
| PUT | /markings/:id | {name, category, description} | 更新打标 |
| DELETE | /markings/:id | — | 删除打标(事务内级联清绑定与授权) |
| GET | /markings/bindings | — | 全部绑定(Tab1 绑定数统计) |
| GET | /markings/:id/bindings | — | 某打标的绑定列表 |
| POST | /markings/:id/bindings | {target_type, target_id} | 绑定目标 |
| DELETE | /markings/:id/bindings/:bindingId | — | 解除绑定 |
| GET | /markings/user-markings | 可选 query user_id / marking_id | 用户打标授权列表 |
| POST | /markings/user-markings | {user_id, marking_id} | 授权用户持有打标 |
| DELETE | /markings/user-markings/:id | — | 撤销授权 |
| GET | /markings/users | — | 用户列表(授权矩阵行) |
| GET | /ontology/objects | — | 对象类型列表(datalist 候选) |
| GET | /ontology/objects/:id | — | 对象类型详情(含 properties,属性下拉) |
/markings/user-markings 是静态前缀,Gin 路由静态节点优先于 /markings/:id 参数路由,两者不会冲突。5.3 响应结构
打标列表 GET /api/v1/markings:
{ "code": 0, "data": [
{ "id": "uuid", "name": "PII", "category": "privacy", "description": "个人身份信息", "created_at": "2026-08-30T10:00:00Z" }
] }
授权列表 GET /api/v1/markings/user-markings(含打标名与用户名):
{ "code": 0, "data": [
{ "id": "uuid", "user_id": "u1", "username": "zhangsan", "marking_id": "m1", "marking_name": "PII", "created_at": "..." }
] }
绑定视图 GET /api/v1/markings/:id/bindings:
{ "code": 0, "data": [
{ "id": "uuid", "marking_id": "m1", "target_type": "property", "target_id": "email", "created_at": "..." }
] }
错误响应统一 {"code": "<错误码>", "error": "<详情>"},如 {"code":"DUPLICATE_ENTRY","error":"打标 \"PII\" 已存在"}、{"code":"NOT_FOUND","error":"打标 \"xxx\" 不存在"}、{"code":"AUTHORIZATION_ERROR","error":"需要 admin 权限"}。
5.4 关联模块表
| 后端包/文件 | 职责 |
|---|---|
foundry/governance/marking.go | MarkingService 业务实现:CRUD、绑定、授权、MarkingChecker 接口、MergeHiddenColumns |
foundry/governance/models_marking.go | 三张打标表模型(fg_markings / fg_marking_bindings / fus_user_markings) |
foundry/server/marking_handlers.go | HTTP handler + requireAdmin admin 语义 + 路由注册(RegisterMarkingRoutes) |
foundry/server/server.go(第 1141-1142 行) | protected 组挂载 markings 全套路由 |
platform/auth(ApplyCLS) | B3-5.2 既有列级策略,marking 作为第二道闸门与之叠加 |
foundry/semantic/query.go | 属性级安全路径(NULL AS col),MarkingChecker 注入点 |
5.5 关键机制
- CLS 联动(第二道列级闸门):属性绑定 marking 后,用户在对象语义查询时,
ColumnsHiddenByMarking按fg_marking_bindingsjoinontology_properties(target_id=属性名 + object_type_id 限定)定位绑定,再以用户持有集差集判定:未持有该 marking(或属性绑定多个 marking 时缺任一)即隐藏该列。与 ApplyCLS 的隐藏集经MergeHiddenColumns取并集(取更严格者)。 - admin 语义:所有
/markings端点先过requireAdmin,不满足返回 403。 - 删除事务:
DeleteMarking在同一事务内先删绑定、再删授权、最后删打标,避免孤儿数据。 - 性能:
enrichUserMarkings用去重 id + IN 批量查询建 map,避免授权矩阵 N+1。 - 组合唯一:
(marking_id, target_type, target_id)绑定唯一、(user_id, marking_id)授权唯一,重复操作返回 DUPLICATE_ENTRY。
6. 核心流程详解
6.1 完整操作流:从打标到列隐藏
- 新建打标:进入 Tab1,点「+ 新建 Marking」,填名称(必填)与类别/描述,点「创建 Marking」。后端校验 name 非空且全局唯一,成功后前端提示「Marking 创建成功 id=xxx」并刷新列表。
- 绑定属性:切到 Tab2「绑定树」,下拉选择刚创建的打标,目标类型选
property,在属性下拉中选中目标对象类型的属性(如customer.email),点「绑定」。前端提示「绑定成功」。 - 授权用户:切到 Tab3「用户授权矩阵」,在对应用户行的该打标列打勾,前端提示「已授权 xxx 持有 PII」。
- 验证效果:未持有该打标的用户(或在矩阵中取消勾选)执行对象语义查询时,
email列显示为 NULL;持有用户正常可见。 - 维护:行内「编辑」修改打标元信息;「删除」级联清空其全部绑定与授权;「解除」逐条移除绑定。
6.2 分支流程
- 重名创建:名称与既有打标重复 → 后端返回
DUPLICATE_ENTRY「打标 "xxx" 已存在」,前端提示「保存失败:...」。 - 绑定不存在目标:object_type/property 的目标在 ontology 中不存在 → 返回 NOT_FOUND「对象类型 "xxx" 不存在」或「属性 "xxx" 不存在」;dataset/metric 不做存在性校验。
- 删除被绑定的打标:confirm 文案明确提示「绑定与用户授权将一并清除」;若删除的是当前绑定树下选中的打标,前端同时清空选中状态与绑定列表。
- 非 admin 操作:任何写操作在权限上被 403 拦截,前端提示「...失败:需要 admin 权限」。
6.3 状态机/任务终态语义
本页无异步任务与轮询,所有操作同步完成;「生效」语义即写库即生效——授权撤销后用户下一次查询立即失去对应绑定列可见性,无需刷新缓存(MarkingChecker 每次查询实时查库)。
7. 权限与安全
- 认证:JWT(aip_token)经 client.js 拦截器注入;401 自动跳登录。
- 数据级安全:
- admin 角色门禁:本页全部 API requireAdmin(rbac.IsUserAdmin,兜底 roles 含 admin / username 为 admin)。
- 列级安全:属性绑定 marking 后形成 CLS 第二道闸门,未持有用户查询该列得 NULL;与 ApplyCLS 取更严格者。
- 行级安全(RLS)不在此页管理,属对象查询链路其它模块。
- 写操作防护:删除/解除/撤销均有浏览器
confirm二次确认;操作中按钮禁用防重复提交;错误信息仅透传后端 error,不暴露内部栈。
8. 常见问题与排错
8.1 非 admin 用户访问返回 403「需要 admin 权限」
现象:页面能打开,但所有请求(或写操作)返回 403,alert 提示「需要 admin 权限」。原因:后端 requireAdmin 校验当前 JWT 用户非 admin 角色。排查步骤:1) 确认登录账号角色含 admin(用户管理页查看角色);2) 若 RBAC 服务已注入,检查 rbac.IsUserAdmin 判定逻辑;3) 用 admin 账号重新登录后再试。
8.2 属性绑定后查询列仍可见(CLS 未生效)
现象:Tab2 已绑定 property 并授予了部分用户,但未授权用户查询时该列仍返回数据。原因:MarkingChecker 未注入 semantic 查询前置,或对象查询走了不经该闸门的路径。排查步骤:1) 确认 server.go 组装层已将 NewMarkingService(s.db) 断言为 MarkingChecker 注入 semantic;2) 确认查询走的是对象语义查询接口;3) 用无授权用户执行查询,抓包看返回列是否含该属性(NULL 化是「列仍在但值为 NULL」);4) 检查是否与 ApplyCLS 其它隐藏规则叠加产生冲突。
8.3 绑定目标下拉为空
现象:Tab2 的 property 目标类型下拉无任何选项,或 object_type datalist 无候选。原因:onMounted 的 fetchObjectTypes / fetchAllProps 失败(后端 ontology 路由未挂载或 /api 代理 404),或对象类型本身为空。排查步骤:1) 浏览器控制台查看 GET /api/v1/ontology/objects 是否 200;2) 确认 Foundry 后端(18081)已启动且 ontology 表有数据;3) 确认 GET /ontology/objects/:id 返回含 properties 数组。
8.4 用户授权矩阵没有用户行
现象:Tab3 显示「需要先创建 Marking 并存在用户。」原因:markings 为空,或 /markings/users 返回空(RBAC 未注入时该接口直接返回 500「RBAC 服务未注入,无法列出用户」)。排查步骤:1) 确认已创建至少一个打标;2) 调用 GET /api/v1/markings/users 看响应;3) 若 500,检查 server 是否注入了 rbacService(ListMarkingUsers 依赖 RBAC 的 ListUsers)。
9. 已知缺陷与边界
| 项目 | 说明 |
|---|---|
| dataset/metric 绑定不校验存在性 | 后端按信息性记录处理,可先行绑定,目标 id 错误不会报错 |
| 属性绑定按 api_name 全局匹配 | property 绑定作用于所有含同名属性的对象类型,无法仅限定某一对象类型 |
| 撤销生效需经对象语义查询 | 未经 MarkingChecker 注入的查询路径不受本页闸门约束 |
| 授权矩阵全量渲染 | 用户/打标数量大时矩阵单元格数为 用户×打标,页面无分页 |
| RBAC 未注入退化 | /markings/users 必须依赖 RBAC.ListUsers,未注入即 500 |
| 前端无搜索/分页 | Tab1 列表全量展示,打标数量多时需滚动 |