1. 页面概览
1.1 是什么
指标管理(MetricsPage.vue,路由 /foundry/metrics)是 LightFoundry 的「指标语义层」管理界面,对标 Palantir Foundry 的 Metric 语义层能力。它把「业务指标」从物理 SQL 中解放出来:先定义实体(Entity),再在实体下声明维度(Dimension)与度量(Measure),最后把「度量 + 维度 + 过滤条件 + 口径」组合成一条指标(Metric)。查询时前端只提交指标名与参数,由后端 OOL 翻译引擎生成物理 SQL,并对用户做 RLS/CLS 安全注入后到真实数据源执行。
页面采用四个 Tab 组织全链路:指标目录(Catalog 卡片浏览口径)、实体模型(实体 + 维度/度量维护)、指标定义(指标 CRUD + 状态/版本)、指标查询(维度分组 + 过滤 + limit 结果表格)。四个 Tab 对应四套后端 API,全部走同一个 client.js axios 实例(baseURL /api/v1,Bearer aip_token)。
这是一个「深水区」页面:指标查询不是简单表格查询,而是聚合 + 维度分组 + 时间范围过滤 + 编辑态叠加的组合翻译流程。理解后端 QueryMetric 的七步翻译(取定义 → 合并筛选 → OOL 翻译 → 分页 → 编辑态叠加判定 → 执行 → 组装口径)是排查查询异常的关键。
1.2 核心价值
| 维度 | 说明 |
|---|---|
| 口径统一 | 指标定义一次、多处查询复用,formula 口径字段在目录/查询结果中完整回显,杜绝各报表各算各的 |
| 三层语义建模 | Entity(grain 键)→ Dimension/Measure(维度/聚合值)→ Metric(业务计算),与 dbt MetricFlow / OSI 四层拆分对齐 |
| 查询即翻译 | 前端只发指标名 + 参数,OOOL 翻译引擎负责生成参数化 SQL 并注入 RLS/CLS,前端不可绕过安全闸门 |
| 状态与版本 | 指标状态 draft → active → deprecated 全生命周期;每次编辑更新版本自动 +1,目录随状态联动 |
| 多类型派生 | simple / ratio / derived / cumulative 四类指标分派,ratio/derived 用分子分母两个度量拼装口径 |
1.3 一句话总结
2. 访问入口
2.1 路由与菜单
- 路由:
/foundry/metrics - 路由名称:
FoundryMetrics - 菜单位置:Foundry 左侧边栏「指标管理」(FoundryLayout 菜单「语义分析」区块)
- 源码文件:
action/web/src/views/MetricsPage.vue(约 1235 行,四 Tab 一体)
2.2 认证与权限
- 需要登录:是(路由
meta.requiresAuth = true) - 登录方式:AIP 统一登录,
localStorage.aip_token作为Authorization: Bearer <token>;token 失效时client.js响应拦截器会清理aip_token/aip_username并跳转/login - 页面本身无角色限制;但查询与检索结果受后端数据安全约束:
handleQueryMetric会把当前登录用户注入req.UserID,供 OOL 翻译做属性级只读判定(无读权限属性「禁止聚合」)与 RLS/CLS 只读注入 - 404 排错:访问
/foundry/metrics出现 404,先确认 Foundry 后端(18081)已启动、前端路由已注册(router/index.js第 227-231 行)
2.3 端口与 API 前缀
- 后端端口:18081(Foundry)
- API 前缀:
/api/v1(action/web/src/api/client.js) - 请求超时:30 秒(client.js 全局
timeout: 30000)
3. 界面布局
页面从上到下为:页头 → 操作结果提示条 → Tab 切换条 → 当前 Tab 内容区。
┌──────────────────────────────────────────────────────────────┐ │ ① 页头:标题「指标管理」 │ │ ② 操作结果提示条(alert 成功/失败,可「关闭」) │ │ ③ Tab 切换条:[指标目录] [实体模型] [指标定义] [指标查询] │ │ ④ 内容区(随 Tab 切换) │ │ ├─ 指标目录:指标卡片网格(grid auto-fill minmax 340px) │ │ ├─ 实体模型:左「实体列表」(300px) + 右「实体详情」双栏 │ │ │ ├─ 新建实体表单 / 实体详情(维度表 + 度量表 + 行内编辑) │ │ ├─ 指标定义:状态筛选 + 新建指标 + 创建/编辑表单 + 指标表格 │ │ └─ 指标查询:查询表单(指标/limit/offset/时间范围/维度/过滤) │ │ + 查询结果卡片(指标元信息 + 结果表格) │ └──────────────────────────────────────────────────────────────┘
各板块职责:
- ① 页头:仅标题「指标管理」,无副标题(
page-header仅 h2)。 - ② 操作结果提示条:全局唯一反馈通道,
showAlert(msg, type)驱动,type 为alert-error/alert-success/alert-info;带「关闭」按钮清空。 - ③ Tab 条:
tabs数组顺序即展示顺序:指标目录 / 实体模型 / 指标定义 / 指标查询;点击切换activeTab,四个 Tab 各自独立加载。 - ④ 指标目录:加载
catalog.metrics,每张卡片显示显示名/状态徽标/name/实体/v版本/口径 formula/度量标签/维度标签/描述;空列表显示「暂无指标,请先在"指标定义"中创建。」。 - ④ 实体模型:左栏实体列表可点击选中(
item-selected高亮);右栏为新建表单或实体详情(维度表格、度量表格 + 行内添加表单)。 - ④ 指标定义:顶部「状态筛选」下拉 + 「新建指标」按钮;下方创建/编辑表单与指标列表表格(ID/名称/显示名称/实体/度量/维度/状态/版本/操作)。
- ④ 指标查询:查询表单含指标下拉、limit/offset、时间维度名、开始/结束时间、维度多选、过滤条件;下方「查询结果」卡片展示指标元信息(名称/实体/聚合/状态/formula)与结果表格。
4. 交互元素详解
4.1 全局元素
| 位置 | 元素 | 含义 | 默认值/必填 | 操作效果 | 触发的后端调用 |
|---|---|---|---|---|---|
| 页顶 | 操作结果提示条 + 「关闭」按钮 | 反馈最近一次操作结果 | 无 | 点击清空 alert.message | 无 |
| 页顶 | Tab 条「指标目录/实体模型/指标定义/指标查询」 | 切换四块功能区 | 默认 指标目录 | 切换 activeTab,内容区重渲染 | 各 Tab 数据源在 onMounted 预加载 |
4.2 指标目录 Tab
| 位置 | 元素 | 含义 | 默认值/必填 | 操作效果 | 触发的后端调用 |
|---|---|---|---|---|---|
| 目录卡片 | 指标卡片 | 单条指标的口径卡片 | — | 只读展示 | 无 |
| 卡片头 | 状态徽标 | draft/active/deprecated 状态 | — | 样式随状态 | 无 |
display_name || name(标题)、name · 实体 <entity> · v<version>(元信息)、口径 formula(code)、度量/维度标签、description。4.3 实体模型 Tab
| 位置 | 元素 | 含义 | 默认值/必填 | 操作效果 | 触发的后端调用 |
|---|---|---|---|---|---|
| 左栏 | 实体列表项 | 实体(显示名 + name + 表 + 数据源 #) | — | 点击选中并加载详情 | GET /metrics/entities/:id |
| 左栏 | 「新建实体」/「收起」按钮 | 展开/收起新建实体表单 | — | 切换 showEntityForm | 无 |
| 新建表单 | 名称(api_name)* | 实体 api_name | 必填,如 customer_entity | 文本输入 | — |
| 新建表单 | 显示名称 | 中文显示名 | 可空,如 客户实体 | 文本输入 | — |
| 新建表单 | 对象类型 | 关联 ontology 对象类型下拉 | 可空(默认 disabled 占位「选择对象类型」) | 选项来自 GET /ontology/objects | — |
| 新建表单 | 数据源 ID * | 实体查询数据源 | 必填,默认 1 | 数字输入 | — |
| 新建表单 | 基础表 | 实体基础物理表 | 可空,如 customer | 文本输入 | — |
| 新建表单 | 主时间维度 | primary_time_dimension | 可空,如 order_date | 文本输入 | — |
| 新建表单 | 描述 | 实体描述 | 可空 | 文本域 | — |
| 新建表单 | 「创建」/「提交中...」 | 提交实体 | 前置校验名称/数据源非空 | 校验失败 alert;成功关闭表单并选中新实体 | POST /metrics/entities |
| 新建表单 | 「取消」 | 关闭表单 | — | showEntityForm = false | 无 |
| 详情-维度 | 维度表格 | name/display_name/source_property/time_grain/时间维度 | — | 只读 | 无 |
| 详情-维度 | 「+ 添加维度」按钮 | 展开行内维度表单 | — | showDimForm = true | 无 |
| 行内维度 | 维度名/显示名/来源属性/时间粒度下拉/时间维度勾选 | 维度字段 | 维度名必填;时间维度勾选则粒度必填 | 粒度选项 year/quarter/month/week/day | — |
| 行内维度 | 「保存」/「保存中...」+「取消」 | 提交/放弃维度 | — | 成功后刷新详情 | POST /metrics/entities/:id/dimensions |
| 详情-度量 | 度量表格 | name/display_name/expression/agg_type/type/source_property | — | 只读 | 无 |
| 详情-度量 | 「+ 添加度量」按钮 | 展开行内度量表单 | — | showMeasureForm = true | 无 |
| 行内度量 | 度量名/显示名/聚合下拉/类型下拉/来源属性/表达式/分子/分母 | 度量字段 | 度量名必填;ratio 类型须填分子分母 | 聚合选项 sum/avg/count/min/max;类型 simple/ratio/derived/cumulative;类型为 ratio 时多出分子分母两个输入框 | — |
| 行内度量 | 「保存」+「取消」 | 提交/放弃度量 | — | 成功后刷新详情 | POST /metrics/entities/:id/measures |
4.4 指标定义 Tab
| 位置 | 元素 | 含义 | 默认值/必填 | 操作效果 | 触发的后端调用 |
|---|---|---|---|---|---|
| 顶部 | 状态筛选下拉 | 按状态过滤指标列表 | 默认 全部;选项 全部/draft/active/deprecated | change 即刷新列表 | GET /metrics?status= |
| 顶部 | 「新建指标」/「收起表单」按钮 | 展开/收起创建表单 | — | 切换 showMetricForm | 无 |
| 表单 | 名称(api_name)* | 指标 api_name | 创建必填,编辑时 disabled(不可改) | 文本输入 | — |
| 表单 | 显示名称 | 指标显示名 | 可空,如 营收 | 文本输入 | — |
| 表单 | 状态 | 指标状态 | 默认 draft;选项 draft/active/deprecated | 下拉 | — |
| 表单 | 实体 * | 指标关联实体 | 必填 | 选择后触发 onMetricEntityChange() 加载该实体的度量/维度选项 | GET /metrics/entities/:id |
| 表单 | 度量(measure)* | 指标引用的度量 | 必填(选项来自实体) | 选择 | — |
| 表单 | 版本(创建时) | 初始版本号 | 默认 1,编辑时 disabled | 数字输入 | — |
| 表单 | 负责人 owner | 指标负责人 | 可空,如 zhangsan | 文本输入 | — |
| 表单 | 同义词 synonyms | 业务同义词 | 可空,逗号分隔,如 收入,销售额 | 文本输入 | — |
| 表单 | 描述 | 指标口径描述 | 可空 | 文本域 | — |
| 表单 | 维度多选 | 该指标允许的维度(勾选) | 选项来自实体维度名 | 未选实体时提示「请先选择实体」 | — |
| 表单 | 过滤条件(可选)+「+ 过滤条件」 | 默认筛选条件列表 | 无 | 每行 field/op/value,op 来自 = != > >= < <= IN NOT IN LIKE IS NULL IS NOT NULL | — |
| 表单 | 「删除」按钮(过滤条件行) | 移除一行 | — | removeRow(filters, i) | 无 |
| 表单 | 「保存」/「保存中...」+「取消」 | 提交/放弃 | 前置校验名称/实体/度量 | 创建走 POST、编辑走 PUT(版本自动 +1),成功刷新列表与目录 | POST /metrics / PUT /metrics/:id |
| 列表 | 指标表格 | ID/名称/显示名称/实体/度量(#id)/维度/状态/版本 | — | 只读 | 无 |
| 列表 | 「编辑」按钮 | 打开编辑表单 | — | 异步加载实体选项后恢复原选择 | GET /metrics/entities/:id(编辑表单内) |
| 列表 | 「删除」按钮 | 软删指标 | confirm 二次确认「确定删除指标"xx"吗?」 | 成功刷新列表与目录 | DELETE /metrics/:id |
4.5 指标查询 Tab
| 位置 | 元素 | 含义 | 默认值/必填 | 操作效果 | 触发的后端调用 |
|---|---|---|---|---|---|
| 查询表单 | 指标 * | 查询指标下拉 | 必填 | 选择后触发 onQueryMetricChange 加载维度选项 | GET /metrics/:id / GET /metrics/entities/:id(维度缺失时) |
| 查询表单 | limit | 返回行数上限 | 默认 100 | 数字输入 | — |
| 查询表单 | offset | 分页偏移 | 默认 0 | 数字输入 | — |
| 查询表单 | 时间维度名(可选) | time_dimension | 可空,如 order_date | 文本输入 | — |
| 查询表单 | 开始时间(可选) | time_start | 可空,如 2026-01-01 | 文本输入 | — |
| 查询表单 | 结束时间(可选) | time_end | 可空,如 2026-12-31 | 文本输入 | — |
| 查询表单 | 维度多选 | 分组维度 | 该指标维度名 | 勾选即入 dimensions | — |
| 查询表单 | 过滤条件(可选)+「+ 过滤条件」 | 查询过滤 | 无 | 同指标定义表单 | — |
| 查询表单 | 「查询」/「查询中...」 | 发起查询 | 未选指标时禁用 | 三时间项任一填写才附带 time_range;成功渲染结果卡片 | POST /metrics/query |
| 结果卡片 | 指标元信息 | 名称/实体/聚合/状态/formula | — | 只读 | — |
| 结果卡片 | 结果表格 | 列名 + 行数据 | — | 无结果时显示「无查询结果」 | — |
5. 后端关联
5.1 API 客户端
action/web/src/api/client.js:
- baseURL:
/api/v1(Vite 开发服务器代理到 Foundry 后端 18081) - timeout:
30000 - 请求拦截器:从
localStorage.getItem('aip_token')取 token,有则注入Authorization: Bearer <token> - 响应拦截器:401 时移除
aip_token/aip_username,路径非/login则跳转/login - 导出:
apiClient(默认导出),MetricsPage 通过import apiClient from '../api/client.js'使用,无独立 API 文件
5.2 端点表
| 方法 | 路径 | 请求体要点 | 超时 |
|---|---|---|---|
| GET | /metrics/catalog | 无 | 30s |
| GET | /metrics | query: status(draft/active/deprecated,可空) | 30s |
| POST | /metrics | {name, display_name, description, measure_id, entity_id, dimensions, filters, synonyms, owner, status, version} | 30s |
| GET | /metrics/:id | 无 | 30s |
| PUT | /metrics/:id | {display_name, description, dimensions, filters, synonyms, owner, status}(name/measure_id/entity_id 不可改) | 30s |
| DELETE | /metrics/:id | 无(软删:status=deprecated) | 30s |
| POST | /metrics/query | {name, dimensions, filters, time_range{start,end,time_dimension}, limit, offset, user_id} | 30s |
| GET | /metrics/entities | 无 | 30s |
| POST | /metrics/entities | {name, display_name, description, object_type_id, base_table, data_source_id, primary_time_dimension} | 30s |
| GET | /metrics/entities/:id | 无 | 30s |
| POST | /metrics/entities/:id/dimensions | {name, display_name, source_property, time_grain, is_time_dimension} | 30s |
| POST | /metrics/entities/:id/measures | {name, display_name, expression, agg_type, type, numerator, denominator, source_property} | 30s |
| GET | /ontology/objects | 无(对象类型下拉数据源) | 30s |
5.3 响应结构
统一成功包装 {code: 0, data: ...}(server 包 okData);失败包装 {code, error}(errorResponse,code 为错误码、status 对应 HTTP 状态码)。
指标目录 GET /metrics/catalog:
{
"code": 0,
"data": {
"metrics": [
{
"name": "revenue",
"display_name": "营收",
"entity": "sales_entity",
"entity_id": 1,
"metric_type": "simple",
"measures": ["revenue_measure"],
"dimensions": ["order_date", "region"],
"formula": "SUM(amount)",
"synonyms": "收入,销售额",
"data_source_id": 1,
"owner": "zhangsan",
"version": 2,
"status": "active"
}
]
}
}
实体详情 GET /metrics/entities/:id:
{
"code": 0,
"data": {
"entity": { "id": 1, "name": "sales_entity", "display_name": "销售实体", "object_type_id": 3, "base_table": "orders", "data_source_id": 1, "primary_time_dimension": "order_date" },
"dimensions": [ { "name": "order_date", "display_name": "下单日期", "source_property": "order_date", "time_grain": "day", "is_time_dimension": true } ],
"measures": [ { "name": "revenue_measure", "display_name": "营收度量", "expression": "SUM(amount)", "agg_type": "sum", "type": "simple" } ]
}
}
指标查询 POST /metrics/query:
{
"code": 0,
"data": {
"columns": ["order_date", "metric"],
"rows": [["2026-08-01", 12500.5]],
"metric": {
"name": "revenue",
"display_name": "营收",
"entity": "sales_entity",
"formula": "SUM(amount)",
"agg_type": "sum",
"type": "simple",
"metric_type": "simple",
"dimensions": ["order_date"],
"status": "active",
"edited_overlay": false,
"edit_overlay": ""
}
}
}
resultColumns 计算兼容 columns 两种形态:数组直接使用;对象则 Object.keys(columns) 取键名。5.4 关联模块表
| 后端包/文件 | 职责 |
|---|---|
products/foundry/server/semantic_handlers.go | 指标/实体/维度/度量/目录/查询 HTTP handler(含语义检索与对象查询) |
products/foundry/metric/models.go | 四层模型:MetricEntity / MetricDimension / MetricMeasure / MetricDefinition(表 metric_entities / metric_dimensions / metric_measures / foundry_metrics) |
products/foundry/metric/service.go | 实体/度量/指标 CRUD 与版本管理 |
products/foundry/metric/query.go | QueryMetric 七步翻译 + GetMetricCatalog |
products/foundry/metric/catalog.go | 指标目录组装(口径 formula 分派) |
products/foundry/semantic/ool.go | OOL 翻译(聚合翻译 + RLS/CLS 注入 + 属性级只读判定) |
products/foundry/semantic/edit_overlay.go | 编辑态聚合叠加(B1-5) |
products/foundry/ontology | 对象类型(GET /ontology/objects) |
platform/connector | 真实数据源连接器(runnerFor 经 provider 获取) |
5.5 关键机制
- 指标状态机:
draft → active → deprecated。DELETE 即软删(置 deprecated),查询时 deprecated 指标直接拒绝:「metric ... 已停用(deprecated),不可查询」。 - 版本机制:创建时可用
version指定初始版本(默认 1);PUT 更新后版本自动 +1(前端提示「指标更新成功(版本已自动 +1)」),目录卡片与列表展示v<version>。 - QueryMetric 翻译流程(深水区核心):① 按 api_name 取指标定义 + 度量 + 实体(deprecated 拒绝);② 合并「默认筛选(def.Filters)」+「请求筛选(req.Filters)」;③ 时间范围落到实体 primary_time_dimension(或请求指定)的 time_grain 时间维度,start→
>=、end→<=追加过滤;④ 分组维度校验:必须是实体维度且在该指标允许维度内,映射到source_property;⑤ OOL 翻译聚合表达式——simple 走agg(measure.expression),ratio/derived 拆分子分母两个聚合表达式按(num)/(den)拼装,cumulative 走SUM(...) OVER (ORDER BY 时间维度 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW);⑥ 追加LIMIT/OFFSET;⑦ 执行(真实数据源经注入 runner 或连接器)。 - 编辑态聚合叠加(B1-5):仅 simple 指标且注入了编辑态读取器、对象存在涉及度量/维度字段的未提交编辑时,回退行级查询(行上限
OverlayRowLimit)+ 内存聚合,保证聚合值与行级一致;超行上限返回未叠加值并标注edit_overlay="approximate";ratio/derived/cumulative 本版不叠加(edited_overlay=false诚实标注)。 - 安全注入:
handleQueryMetric注入当前用户到req.UserID,OOL 翻译做属性级只读判定(无读权限属性禁止聚合)与 RLS/CLS 只读注入;查询执行经参数化 SQL。 - 额度与分页:limit/offset 由前端传默认 100/0,后端仅
limit>0时拼接LIMIT ?、offset>0时拼接OFFSET ?。
6. 核心流程详解
6.1 从零到可查:指标建模主流程
- 建实体:进入「实体模型」Tab → 点「新建实体」→ 填写名称(api_name)、显示名称、对象类型(可选,来自
GET /ontology/objects)、数据源 ID、基础表、主时间维度、描述 → 点「创建」。成功后自动选中新实体并加载详情。 - 加维度:详情「维度」区点「+ 添加维度」→ 填维度名/显示名/来源属性/时间粒度;若勾选「时间维度」则粒度必填(year/quarter/month/week/day)→ 点「保存」。维度名是后续分组与多选的 key。
- 加度量:详情「度量」区点「+ 添加度量」→ 填度量名/显示名/聚合(sum/avg/count/min/max)/类型(simple/ratio/derived/cumulative)/来源属性/表达式(如
SUM(amount));类型为 ratio 时补充分子(numerator)与分母(denominator)→ 点「保存」。 - 建指标:切到「指标定义」Tab → 「新建指标」→ 填名称(api_name)、显示名、状态(默认 draft)、实体(必选,选中即加载该实体度量/维度选项)、度量(必选)、版本、owner、同义词(逗号分隔)、描述;勾选允许维度;按需追加过滤条件 → 「保存」。创建成功后列表与目录同时刷新。
- 发版:在表单把状态改为
active再保存(或创建时直接 active),目录卡片即展示 active 徽标。 - 查询:切到「指标查询」Tab → 选指标 → 勾选维度 → 填 limit/offset(默认 100/0)→ 可选时间维度名与开始/结束时间 → 「查询」→ 查看结果表格与口径 formula。
6.2 指标编辑与版本流
- 编辑入口:指标列表「编辑」按钮。
openMetricEdit填充表单后await onMetricEntityChange(true)异步加载实体选项,再恢复原 measure/dimensions 选择。 - 编辑可改字段:display_name、description、dimensions、filters、synonyms、owner、status;name、measure_id、entity_id、version 不可改(表单中 name/version disabled)。
- 保存走
PUT /metrics/:id,后端更新版本 +1,前端提示「指标更新成功(版本已自动 +1)」,随后刷新列表与目录。
6.3 指标删除(软删)流
指标列表「删除」→ confirm('确定删除指标"xx"吗?') → DELETE /metrics/:id → 后端置 status=deprecated → 刷新列表与目录。删除后指标仍可被目录展示(带 deprecated 徽标),但查询接口直接拒绝执行。
6.4 指标查询分支语义
- 无时间范围:
time_start/time_end/time_dimension三项全空时,请求体不附带time_range,查询不加时间过滤。 - 有时间范围:任一项填写即附带
time_range(空项补空串),后端把 start 转>=、end 转<=过滤到时间维度的source_property;time_dimension未填时回退实体的primary_time_dimension。若该时间维度名未定义或非时间维度,后端返回 400「时间维度 xx 未定义或非时间维度」。 - 分组维度:
dimensions勾选的维度名必须存在于实体且在该指标允许维度内,否则后端 400「维度 xx 未定义在 entity yy 中」/「维度 xx 不在指标 yy 的允许维度内」。 - 过滤条件:仅保留
field非空的行,映射为{field, op, value};op 支持= != > >= < <= IN NOT IN LIKE IS NULL IS NOT NULL,全部经语义层白名单校验防注入。 - 结果列兼容:
columns为数组直接渲染;为对象则取键名。行内 NULL 原样展示(前端对 query 结果未做 NULL 转换,与数据集预览页不同)。
6.5 指标类型分派
| 类型 | 口径 formula | 查询翻译 |
|---|---|---|
| simple | measure.Expression(如 SUM(amount)) | 单聚合表达式 OOL 翻译 |
| ratio | 分子表达式 / 分母表达式 | 两个聚合分别翻译后 (num)/(den) 拼装;优先定义层 numerator_measure_id/denominator_measure_id 引用,缺省回退 measure 层 numerator/denominator |
| derived | 两度量派生表达式 | 同 ratio 拼装;带 time_window(移动平均等)标 P2 拒绝;须定义分子/分母度量引用 |
| cumulative | cumulative(<表达式>) | 基础聚合须 sum/count,按时间维度分组后 SUM(...) OVER (ORDER BY time ...) 累计;trailing 窗口 P2 拒绝 |
7. 权限与安全
- 认证:AIP 统一登录(
aip_token)。路由requiresAuth拦截未登录访问;401 由client.js拦截器清理凭证并跳转登录页。 - 数据级安全(RLS/CLS/密级):查询执行前 OOL 翻译注入 RLS/CLS——行级可见性 + 列级可见性 + 对象级可见性在
semantic层统一判定;QueryMetricRequest.UserID透传当前登录用户,属性级只读判定「无读权限属性禁止聚合」。实体建模页的「数据源 ID」直连物理数据源,查询结果与安全判定均以数据源实际配置为准。 - 写操作防护:所有写操作(创建实体/维度/度量/指标、编辑、删除)均前端
busy置位防连点;删除指标、删除列表项(数据集等)均有confirm二次确认;指标删除为软删(deprecated),非物理删除。 - 翻译白名单防注入:指标查询的过滤字段必须为对象属性(语义层白名单),聚合表达式仅接受
sum/avg/count/min/max与合法字段,杜绝前端注入任意 SQL;最终执行为参数化 SQL。
8. 常见问题与排错
8.1 查询报「metric "xx" 已停用(deprecated),不可查询」
- 现象:指标查询 Tab 选了一个旧指标,点「查询」后 alert 报「查询失败:metric "xx" 已停用(deprecated),不可查询」。
- 原因:该指标曾在指标定义 Tab 被「删除」(软删 → deprecated),或状态被手动改成 deprecated;QueryMetric 对 deprecated 指标直接拒绝执行。
- 排查步骤:① 到「指标定义」Tab 把状态筛选改为
deprecated确认该指标状态;② 若需恢复,点「编辑」把状态改回active保存(版本 +1)后再查询;③ 若确实废弃,删除后从指标定义重建同名指标需注意name唯一索引,历史名称仍被占用时后端会返回重复键错误。
8.2 查询报「时间维度 "order_date" 未定义或非时间维度」
- 现象:查询表单填了时间维度名或开始/结束时间,提交后 400「时间维度 xx 未定义或非时间维度」。
- 原因:指标查询的时间过滤必须落在「实体维度表中
is_time_dimension=true的维度」的source_property上;若实体下根本没有声明时间维度、或时间维度名拼写与实体维度名不一致,即报此错。 - 排查步骤:① 到「实体模型」Tab 选中该指标对应的实体,检查「维度」表格里是否有一条
时间维度=是且时间粒度非空的维度;② 若没有,点「+ 添加维度」补一条时间维度(勾选「时间维度」+ 选粒度);③ 确认查询表单「时间维度名」填写的就是该维度name(不是 source_property);④ 什么都不填时间项也可查询(跳过时间过滤)。
8.3 查询结果只有一列「metric」或列结构奇怪
- 现象:结果表格只有聚合列、没有分组维度列,或列名与预期不一致。
- 原因:
dimensions分组维度未勾选(后端只聚合不分组),或columns为对象形态被Object.keys展开;另外指标定义里「维度多选」限制了该指标允许分组维度,超出会 400。 - 排查步骤:① 在「指标查询」勾选需要的维度;② 若该指标下「维度」提示「该指标暂无可用维度」,到「指标定义」编辑该指标,确认实体已含维度且维度已在指标维度多选中勾上;③ 确认选中维度名与实体维度名一致(大小写敏感)。
8.4 实体/指标保存时按钮一直「保存中...」或卡住
- 现象:点保存后按钮变「保存中...」长时间无响应,或后续操作失效。
- 原因:
busy置位后请求挂起(后端 18081 未启动/超时 30s)、或网络请求被浏览器拦截;finally会把busy复位,但若进程中断则残留。 - 排查步骤:① 确认 Foundry 后端(18081)在跑:
tasklist | grep或访问GET /api/v1/metrics/catalog;② 浏览器 DevTools Network 查看具体请求是否 pending 或 401/500;③ 401 时确认已用 AIP 账号登录(localStorage.aip_token存在且未过期);④ 刷新页面复位busy状态后重试。
8.5 指标目录为空但指标定义列表有数据
- 现象:「指标目录」Tab 显示「暂无指标」,但「指标定义」Tab 有指标列表。
- 原因:目录数据来自独立的
GET /metrics/catalog接口(组装时依赖实体/度量解析),若实体或度量被删除、或目录请求失败,catalog.metrics为空或加载失败被静默处理。 - 排查步骤:① 切到「指标定义」确认列表确实有数据;② 用 API 直查
GET /api/v1/metrics/catalog看返回是否正常(metrics数组长度);③ 若目录报错,到「实体模型」确认指标引用的实体/度量仍存在(metric catalog组装对缺失实体/度量会跳过该条目);④ 点页面任意刷新(切 Tab 重进)重载目录。
9. 已知缺陷与边界
| 缺陷/边界 | 说明 | 影响 |
|---|---|---|
| cumulative 基础聚合受限 | 仅支持 sum/count;avg/min/max 非可加聚合标 P2 | cumulative 指标查询报 400 |
| cumulative/derived 时间窗口 P2 | time_window(trailing 累计/移动平均)本版不支持 | 带窗口的指标查询报 400 |
| derived 仅两度量派生 | 须定义 numerator_measure_id/denominator_measure_id,更多表达式不支持 | 建模受限 |
| 编辑态叠加仅 simple | ratio/derived/cumulative 不叠加,edited_overlay=false 如实标注 | 聚合值可能与行级不一致(编辑态下) |
| 指标详情 O(n) 过滤 | handleGetMetric 无 GetByID,按 ListMetrics 全量过滤 | 指标量大时详情接口较慢 |
| 维度多选以字符串为 key | 指标维度多选用维度 name 字符串,重名实体维度会混淆 | 同名维度跨实体选择异常 |
| 过滤 op 为文本输入 | field/value 为自由文本,无类型校验 | 非法输入靠后端白名单拦截 |
| 无时间过滤不附带 time_range | 三项全空时请求体省略 time_range | 功能正常,但接口语义需文档化 |
| 删除为软删 | DELETE 指标置 deprecated,历史查询仍可见该状态 | 目录仍展示 deprecated 指标 |
注:查询结果 NULL 原样展示的问题已于 2026-09-06 修复(formatCell 将 null/undefined/纯空白统一显示为「NULL」,与数据集预览页文案一致)。