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 路由与菜单

2.2 认证与权限

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 语义 + 撤销立即生效)

各板块职责:

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
按钮「刷新」刷新打标/绑定/授权reloadAllGET /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 DELETEPOST /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.goMarkingService 业务实现:CRUD、绑定、授权、MarkingChecker 接口、MergeHiddenColumns
foundry/governance/models_marking.go三张打标表模型(fg_markings / fg_marking_bindings / fus_user_markings)
foundry/server/marking_handlers.goHTTP 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 关键机制

6. 核心流程详解

6.1 完整操作流:从打标到列隐藏

  1. 新建打标:进入 Tab1,点「+ 新建 Marking」,填名称(必填)与类别/描述,点「创建 Marking」。后端校验 name 非空且全局唯一,成功后前端提示「Marking 创建成功 id=xxx」并刷新列表。
  2. 绑定属性:切到 Tab2「绑定树」,下拉选择刚创建的打标,目标类型选 property,在属性下拉中选中目标对象类型的属性(如 customer.email),点「绑定」。前端提示「绑定成功」。
  3. 授权用户:切到 Tab3「用户授权矩阵」,在对应用户行的该打标列打勾,前端提示「已授权 xxx 持有 PII」。
  4. 验证效果:未持有该打标的用户(或在矩阵中取消勾选)执行对象语义查询时,email 列显示为 NULL;持有用户正常可见。
  5. 维护:行内「编辑」修改打标元信息;「删除」级联清空其全部绑定与授权;「解除」逐条移除绑定。

6.2 分支流程

6.3 状态机/任务终态语义

本页无异步任务与轮询,所有操作同步完成;「生效」语义即写库即生效——授权撤销后用户下一次查询立即失去对应绑定列可见性,无需刷新缓存(MarkingChecker 每次查询实时查库)。

7. 权限与安全

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 无候选。原因:onMountedfetchObjectTypes / 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 是否注入了 rbacServiceListMarkingUsers 依赖 RBAC 的 ListUsers)。

9. 已知缺陷与边界

项目说明
dataset/metric 绑定不校验存在性后端按信息性记录处理,可先行绑定,目标 id 错误不会报错
属性绑定按 api_name 全局匹配property 绑定作用于所有含同名属性的对象类型,无法仅限定某一对象类型
撤销生效需经对象语义查询未经 MarkingChecker 注入的查询路径不受本页闸门约束
授权矩阵全量渲染用户/打标数量大时矩阵单元格数为 用户×打标,页面无分页
RBAC 未注入退化/markings/users 必须依赖 RBAC.ListUsers,未注入即 500
前端无搜索/分页Tab1 列表全量展示,打标数量多时需滚动