1. 页面概览
1.1 是什么
「版本历史」页面是 LightFoundry 本体工作台(Foundry)中负责对象类型版本管理的页面。Foundry 的本体对象(如 customer、order、product)并不是"改一次就立刻生效"的扁平定义,而是遵循一条 draft(草稿)→ review(评审中)→ merged(已合入) 的版本状态机演进。每次正式修改都会生成一个新的版本记录,页面以垂直时间线的形式展示某个对象类型的全部历史版本,并在每个版本旁按状态渲染对应的操作按钮。
本页面的源码位于 action/web/src/views/VersionHistoryPage.vue,共约 380 行,属于典型的"单页 + 列表 + 弹窗"结构:顶部一个「选择对象类型」下拉框,选中后下方渲染该对象的版本时间线;时间线每条目按版本状态(draft / review / merged)显示不同的可用操作;所有需要展示的后端分析结果(合并前影响分析、合并后影响、回滚影响)统一在「影响分析弹窗」中呈现。
页面顶部注释块概括了三件事:选择对象 → 版本时间线;对 draft 执行 review(提交评审)、对 review 版本合入前调用 /impact 接口做影响分析;对历史 merged 版本执行 rollback(回滚)。这决定了本页面是 Foundry 版本治理的"操作台":它不负责创建/编辑对象定义(那是「本体工作台」与草稿编辑入口的事),而是负责推动版本状态流转、审批把关、合入与回滚。
1.2 核心价值
| 能力 | 说明 | 对应操作 |
|---|---|---|
| 版本时间线 | 一眼看清对象类型从诞生到现在的全部版本,含版本号、状态、基线版本、创建人与时间 | 选择对象后自动加载 |
| 评审流转 | 草稿提交评审,推动 draft → review | 「提交评审」 |
| 审批闭环 | review 且配置了审批策略时,可通过/驳回,驳回自动退回草稿(P0-5) | 「通过」/「驳回」 |
| 合入前影响分析 | 合入 review 版本前先调 /impact 展示对象引用清单,降低误合入风险 | 点击「合入」自动触发 |
| 历史回滚 | 从任意历史 merged 版本重建主表并生成新版本,实现"后悔药" | 「回滚到此版本」 |
| 审计可追溯 | 每个版本记录 created_by、created_at,合入/回滚返回影响报告 | 时间线元信息 |
1.3 一句话总结
2. 访问入口
2.1 路由与菜单
| 项目 | 值 |
|---|---|
| 路由 path | /foundry/versions |
| 路由 name | FoundryVersions |
| 侧边栏入口 | FoundryLayout 侧边栏「版本历史」(action/web/src/views/FoundryLayout.vue 菜单项) |
| 父路由 | /foundry(组件 FoundryLayout,meta.title: 'Foundry') |
| 前端源码 | action/web/src/views/VersionHistoryPage.vue |
| 路由注册文件 | action/web/src/router/index.js(约 209-213 行) |
访问方式:登录后在 Foundry 左侧菜单点击「版本历史」,或直接访问 /foundry/versions。路由注册于 Foundry 布局的子路由下,因此必须先进入 /foundry 布局,URL 才能被正确匹配;直接访问完整路径同样有效。
2.2 认证与权限
- 路由
meta.requiresAuth: true,未登录访问会被路由守卫拦截跳转登录页。 - API 认证使用
aip_token(localStorage),由action/web/src/api/client.js请求拦截器自动附加Authorization: Bearer <aip_token>。 - 页面本身不区分角色,但每个后端操作(评审/合入/审批/回滚)都会以当前登录用户身份写入版本记录的
created_by或审批记录的approver_id,属于审计级权限而非功能级权限;审批环节另有自批拦截与票数门禁(见第 7 章)。 - 若 token 过期或失效,客户端拦截器会清理
aip_token/aip_username并跳转/login;遇到"页面白屏/跳登录"时可先检查浏览器 localStorage 中aip_token是否存在。
2.3 端口与 API 前缀
- Foundry 后端端口:18081;前端开发服务器(Vite)通过
/api前缀把请求代理到该后端。 - API 基础前缀:
/api/v1(见action/web/src/api/client.js中baseURL: '/api/v1')。 - 本页所有请求都落在
/api/v1/ontology/...下,属于 Foundry 本体域。
3. 界面布局
本页布局为纵向卡片流,文字框图如下:
┌─────────────────────────────────────────────────────┐
│ 版本历史 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ [alert 提示条(出现时展示,右上角"关闭")] │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 选择对象类型 [下拉框:对象名(v版本 · 状态)] │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─ 状态卡片(三选一)───────────────────────────────┐ │
│ │ A. 加载中...(loading=true) │ │
│ │ B. 该对象暂无版本记录。(versions 为空) │ │
│ │ C. 版本时间线(timeline) │ │
│ │ ● v3 [merged] base v2 · user · 2026-08-30 │ │
│ │ [回滚到此版本] │ │
│ │ ● v2 [review] base v1 · user · 2026-08-30 │ │
│ │ [合入] [通过] [驳回] │ │
│ │ ● v1 [draft] base - · system · 2026-08-30 │ │
│ │ [提交评审] │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─ 影响分析弹窗(impactModal.visible 时全屏蒙层)───┐ │
│ │ 标题 + 关闭 │ <pre> 影响分析 JSON </pre> │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
| 板块 | 职责 |
|---|---|
| 页面标题区 | 固定显示「版本历史」,说明当前所在页面 |
| Alert 提示条 | 展示所有异步操作的成败信息,按类型着色(alert-info / alert-success / alert-error),右上角「关闭」按钮可手动清除 |
| 选择对象类型卡片 | 唯一的入口表单:下拉列出全部对象类型,选项文案为「对象名(v版本号 · 状态)」,选中即触发版本加载 |
| 状态卡片 | 根据 loading 与 versions 长度三选一渲染:加载中占位 / 空态提示 / 版本时间线 |
| 版本时间线 | 每个版本一条时间线条目:左侧彩色圆点(dot-draft/dot-review/dot-merged),右侧版本信息与操作按钮区;渲染层分页(每页 10/20/50 条 + 上一页/下一页) |
| 渲染层分页条 | 时间线下方:每页条数下拉(10/20/50 条)+「上一页 / 第 P / 总 T 页 / 下一页」;顶部常驻提示说明「版本列表接口无服务端分页、一次全量拉取」 |
| 影响分析弹窗 | 全屏遮罩 + 居中卡片,pre 区块按 JSON 格式化展示后端返回的影响分析报告,右上角「关闭」或点击遮罩空白处关闭 |
4. 交互元素详解
4.1 选择对象类型下拉框
| 项目 | 说明 |
|---|---|
| 位置 | 页面顶部第一个卡片内 |
| 含义 | 选择要查看版本历史的对象类型 |
| 必填/默认 | 必选;初始为占位项「请选择」(option :value="null" disabled) |
| 选项文案 | {{ item.object_type.name }}(v{{ item.object_type.version }} · {{ item.object_type.status }}) |
| 操作效果 | @change="handleSelectObject":立即加载该对象的版本列表(loading=true 期间显示"加载中...") |
| 触发的后端调用 | GET /ontology/objects/:id/versions |
下拉选项数据来自 GET /ontology/objects,取响应的 data.data 数组,每个元素含 object_type 对象(含 id、name、version、status)。
4.2 提交评审(draft 专属)
| 项目 | 说明 |
|---|---|
| 可见条件 | 版本状态为 draft(v-if="v.status === 'draft'") |
| 含义 | 把草稿版本提交为评审中状态 |
| 操作效果 | 成功后 alert 显示「版本 v{n} 已提交评审」(alert-success),并刷新版本列表 |
| 触发的后端调用 | POST /ontology/drafts/:vid/review(vid 为该版本的内部 id) |
| 状态变化 | draft → review;若草稿内容包含 requires_approval 动作,后端会在该版本上记录 approval_policy,前端据此显示「通过」/「驳回」按钮 |
| 附加行为 | 操作期间 busy=true,所有按钮置灰防止连点 |
4.3 合入(review 专属)
| 项目 | 说明 |
|---|---|
| 可见条件 | 版本状态为 review |
| 含义 | 把评审中版本合入为主表最新版本 |
| 操作效果 | 点击后先调用 /impact 做合并前影响分析并弹出,然后调用 merge 接口,合入成功后再次弹出「合入结果(影响分析)」并刷新列表 |
| 触发的后端调用 | POST /ontology/objects/:id/impact(预检)→ POST /ontology/drafts/:vid/merge |
| 状态变化 | review → merged;若配置了审批策略且 approve 票数不足,merge 会被后端拒绝(错误码 APPROVAL_REQUIRED,HTTP 409) |
approve 审批记录、状态保持 review;只有「合入」才真正把版本变为 merged 并重建主表。4.4 通过 / 驳回(review 且带审批策略时显示)
| 项目 | 说明 |
|---|---|
| 可见条件 | v.status === 'review' && v.approval_policy(审批闭环,P0-5) |
| 含义 | 对评审中版本投通过/驳回票,用于满足审批策略门禁 |
| 前置交互 | 两个按钮都会先用 window.prompt('审批意见(可选)', '') 收集意见(可为空),再 confirm 二次确认(「通过」提示"确定通过版本 v{n} 的审批吗?";「驳回」提示"确定驳回版本 v{n} 吗?将退回草稿状态。") |
| 操作效果 | 通过:alert「版本 v{n} 已通过审批」;驳回:alert「版本 v{n} 已驳回,退回草稿」;随后刷新版本列表 |
| 触发的后端调用 | POST /ontology/objects/:id/versions/:vid/approve(body {comment})/POST /ontology/objects/:id/versions/:vid/reject(body {comment}) |
| 状态变化 | 通过:状态保持 review(直至合入);驳回:状态退回 draft |
approval_policy.self_approval=false 且审批人(当前登录用户)正是版本创建人,返回错误码 APPROVAL_DENIED(HTTP 409),提示"self-approval not allowed by approval policy"。4.5 回滚到此版本(merged 专属)
| 项目 | 说明 |
|---|---|
| 可见条件 | 版本状态为 merged |
| 含义 | 以该历史版本为基线重建主表,并生成一个新版本 |
| 前置交互 | confirm('确定回滚到 v{n} 吗?将重建主表并生成新版本。') 二次确认 |
| 操作效果 | 成功后 alert「回滚成功」,并弹出「回滚结果(影响分析)」报告 |
| 触发的后端调用 | POST /ontology/objects/:id/rollback(body {target_version: v.version}) |
| 约束 | 后端要求 target_version > 0,且目标版本必须是 merged 状态,否则返回 INVALID_VERSION_STATE(HTTP 409) |
4.6 影响分析弹窗与 Alert 提示条
| 元素 | 说明 |
|---|---|
| 影响分析弹窗 | impactModal = {visible, title, content};合入预检标题为「合并前影响分析(对象引用清单)」,合入结果标题为「合入结果(影响分析)」,回滚标题为「回滚结果(影响分析)」;内容为 JSON.stringify(data, null, 2) 格式化的报告;「关闭」按钮或点击遮罩(@click.self)均可关闭 |
| Alert 提示条 | alert = {message, type};showAlert(msg, type) 统一封装;成功用 alert-success、失败用 alert-error、信息用 alert-info;右上角「关闭」把 message 置空 |
5. 后端关联
5.1 API 客户端
| 项目 | 值 |
|---|---|
| 客户端文件 | action/web/src/api/client.js |
| baseURL | /api/v1(Vite 代理到 Foundry 后端 18081) |
| 超时 | 30000ms |
| 请求拦截器 | 从 localStorage 读 aip_token,存在则附加 Authorization: Bearer <aip_token> |
| 响应拦截器 | HTTP 401 时清空 aip_token/aip_username,若非登录页则 window.location.href = '/login' |
| 导出 | export default apiClient(本页 import apiClient from '../api/client.js') |
5.2 端点表
| 方法 | 路径 | 请求体 | 说明 |
|---|---|---|---|
| GET | /ontology/objects | — | 列对象类型(支持 ?status= 过滤) |
| GET | /ontology/objects/:id/versions | — | 版本历史列表(版本号降序) |
| POST | /ontology/drafts/:vid/review | — | draft → review 状态流转 |
| POST | /ontology/objects/:id/impact | — | 对象引用影响分析(合并前预检) |
| POST | /ontology/drafts/:vid/merge | — | review → merged,返回影响分析报告 |
| POST | /ontology/objects/:id/versions/:vid/approve | {approver_name?, comment?} | 审批通过,写 approve 审批记录,状态保持 review |
| POST | /ontology/objects/:id/versions/:vid/reject | {approver_name?, comment?} | 审批驳回,写 reject 记录,状态退回 draft |
| POST | /ontology/objects/:id/rollback | {target_version: int} | 从历史 merged 版本重建主表 |
注::id 支持数字内部 id,也支持 api_name / rid(object-type:<name> 前缀会被剥离后按 name 查询),见服务端 resolveObjectID。
5.3 响应结构
所有成功响应统一封装为 {code: 0, data: ...}(okData);失败响应为 {code, error, ...}。
版本历史列表(GET /ontology/objects/:id/versions)的 data 为数组,元素为:
{
"id": 7,
"version": 3,
"status": "merged",
"base_version": 2,
"created_by": "admin",
"created_at": "2026-08-30T10:00:00Z",
"approval_policy": { "min_approvals": 1, "self_approval": false }
}
影响分析报告(POST /ontology/objects/:id/impact、merge、rollback 的 data.impact)结构:
{
"object_type_id": 2,
"object_type_name": "order",
"impacts": [
{ "type": "link", "id": "5", "name": "customer_orders", "detail": "link \"customer_orders\" (1 → 2)source 引用" },
{ "type": "action", "id": "9", "name": "update_order_status", "detail": "action \"update_order_status\" belongs to object type" },
{ "type": "edit", "id": "2", "name": "order", "detail": "1 条进行中的编辑记录(ontology_edits)" }
]
}
影响项 type 取值:link / action / pipeline / lineage / metric / report / edit;detail 中会出现"source 引用"、"target 引用"、"表未就绪"等后缀说明。
5.4 关联模块表
| 后端包/文件 | 职责 |
|---|---|
foundry/server/ontology_handlers.go | handleListVersions、handleSubmitReview、handleApproveVersion、handleRejectVersion、handleMergeDraft、handleRollback、handleAnalyzeImpact |
foundry/ontology/version.go | 版本状态机核心(CreateDraft/SubmitReview/Approve/Reject/Merge/Rollback/ListVersions)、ApprovalPolicy、错误码 INVALID_VERSION_STATE/APPROVAL_REQUIRED/APPROVAL_DENIED |
foundry/ontology/impact.go | AnalyzeImpact 影响分析(扫描链接/动作/管道/血缘/指标/编辑六类引用) |
foundry/ontology/repository.go | 版本快照、合并、回滚到版本的持久层(RollbackToVersion) |
foundry/server/server.go | 路由注册(/ontology/... 全部在 protected 组) |
5.5 关键机制
版本状态机(draft → review → merged)
| 转移 | 触发接口 | 条件与语义 |
|---|---|---|
| draft → review | POST /ontology/drafts/:vid/review | 仅 draft 可转移;记录 approval_policy(快照中有 requires_approval 动作时需过审) |
| review → merged | POST /ontology/drafts/:vid/merge | 仅 review 可合入;需先过审批门禁;冲突判定为"聚合真冲突" |
| review → draft | POST .../versions/:vid/reject | 仅 review 可驳回,写 reject 审批记录 |
| review → review | POST .../versions/:vid/approve | 写 approve 审批记录,状态保持 review |
| merged →(新 merged) | POST .../rollback | 从历史 merged 快照重建主表,生成新版本 |
聚合真冲突判定
merge 时聚合 (base_version, latest] 区间所有 merged 版本的 change_diff,与草稿 diff 按 (kind, api_name) 求交,op ∈ {modify, delete} 即冲突;落后但无真冲突时允许"非冲突自动合并"(不再"落后即拒绝")。
审批门禁
approve 数 ≥ min_approvals 才允许 merge(不足返回 APPROVAL_REQUIRED);self_approval=false 时禁止版本创建人给自己批准(返回 APPROVAL_DENIED)。
回滚语义
target_version 必须为正整数且目标版本为 merged;回滚重建主表并生成一个新版本,返回影响分析报告,便于回滚后评估影响面。
6. 核心流程详解
6.1 主流程:查看版本时间线
- 页面挂载时执行
fetchObjects(),请求GET /ontology/objects,把返回的对象类型列表填充进下拉框。 - 用户在「选择对象类型」下拉中选择一个对象,触发
handleSelectObject():loading=true,渲染"加载中..."卡片;- 请求
GET /ontology/objects/:id/versions; - 成功把
data.data写入versions,loading=false,渲染时间线;失败 alert「加载版本失败:<错误信息>」。切换对象时页码复位为第 1 页。 - 版本数组为一次全量返回(后端无 limit/offset、无 total),时间线之上按
pageSize(10/20/50,默认 20)做渲染层分页:pagedVersions = versions.slice((page-1)*pageSize, page*pageSize),合入/回滚刷新后页码自动钳制回合法范围。
- 若该对象没有任何版本记录,显示"该对象暂无版本记录。"。
时间线每条目渲染:版本号 v{n}(加粗)、状态徽标 status-badge(按状态着色)、元信息 base v{base_version || '-'} · {created_by || 'system'} · {formatTime(created_at)}。formatTime 把 ISO 时间串的 T 替换为空格并截取前 19 位。
6.2 分支流程:评审与合入
- 提交评审:draft 版本点击「提交评审」→
POST /ontology/drafts/:vid/review→ 成功 alert「版本 v{n} 已提交评审」→refreshVersions()重新拉取列表 → 该版本变为 review 状态;若带审批策略,时间线上出现「通过」「驳回」。 - 审批通过:「通过」→ prompt 意见 → confirm →
POST /ontology/objects/:id/versions/:vid/approve(body{comment})→ alert「版本 v{n} 已通过审批」→ 刷新;状态仍为 review。 - 审批驳回:「驳回」→ prompt 意见 → confirm →
POST .../reject→ alert「版本 v{n} 已驳回,退回草稿」→ 刷新;状态退回 draft。 - 合入:「合入」→ 先
POST /ontology/objects/:id/impact弹出「合并前影响分析(对象引用清单)」→ 再POST /ontology/drafts/:vid/merge→ 成功弹出「合入结果(影响分析)」并 alert「版本 v{n} 已合入」→ 刷新。合入失败(如审批票数不足 409)alert「合入失败:<错误信息>」。
6.3 分支流程:回滚
- merged 版本点击「回滚到此版本」→ confirm「确定回滚到 v{n} 吗?将重建主表并生成新版本。」
POST /ontology/objects/:id/rollback,body{target_version: v.version}。- 成功 alert「回滚成功」并弹出「回滚结果(影响分析)」;随后刷新版本列表,可在时间线顶部看到新生成的 merged 版本。
6.4 状态机终态语义
| 终态 | 含义 | 可执行操作 |
|---|---|---|
| draft | 草稿,可继续编辑(本页不可编辑) | 提交评审 |
| review | 评审中,等待审批/合入 | 合入;带审批策略时通过/驳回 |
| merged | 已合入主表,成为主表权威定义 | 回滚到此版本 |
busy=true 全局置灰按钮,防止并发重复提交;操作完成后统一 refreshVersions() 保证时间线与后端一致。7. 权限与安全
- 认证:全部请求依赖
aip_tokenBearer 认证;后端在protected路由组挂载/ontology/...。 - 身份注入:后端
currentUserID/currentUsername从 gin 上下文取当前登录用户,注入到版本记录的created_by、审批记录的approver_id、merge/rollback 的requestedBy;上下文缺失时user_id回退为"anonymous"。 - 审批安全(P0-5):
- 自批拦截:
approval_policy.self_approval=false时版本创建人不能给自己的版本批准; - 票数门禁:
min_approvals不足时 merge 被APPROVAL_REQUIRED拒绝; - 状态机守卫:所有非法状态转移(draft 直接 merge、merged 再 merge、回滚非 merged 目标等)返回
INVALID_VERSION_STATE(HTTP 409),不产生副作用。
- 自批拦截:
- 写操作防护:评审/合入/审批/回滚均为
POST(非幂等语义),前端以busy置灰 +confirm二次确认防护;回滚前强制确认文案提示"将重建主表"。 - 审计:每次状态流转以当前用户身份落库(版本记录的
created_by、审批记录decision/approver),形成可追溯链。
8. 常见问题与排错
问题 1:下拉框空白,加载对象列表失败
现象:进入页面后「选择对象类型」下拉没有选项,顶部 alert 显示"加载对象列表失败:..."。
原因:GET /ontology/objects 请求失败,常见于后端未启动、token 失效或网络代理未配置。
排查步骤:
- 确认 Foundry 后端(18081)已启动:
curl http://localhost:18081/api/v1/ontology/objects观察响应; - 打开浏览器 DevTools Network,检查该请求的状态码与响应体
error字段; - 若返回 401,重新登录获取
aip_token(localStorage)后刷新页面; - 确认 Vite 代理配置中
/api前缀正确转发到 18081。
问题 2:点击「合入」报 409,提示审批票数不足
现象:版本处于 review 状态且带 approval_policy,点击「合入」后 alert 显示"合入失败:approvals ... APPROVAL_REQUIRED"。
原因:版本配置了审批策略(如 min_approvals: 1),但当前 approve 审批记录数不足;或该版本由本人提交且 self_approval=false,本人"通过"被自批拦截(APPROVAL_DENIED)。
排查步骤:
- 先点击「通过」补齐审批记录(非创建人账号);
- 若「通过」时报
APPROVAL_DENIED,换一个非创建人账号登录操作; - 再回到本页点击「合入」。
问题 3:点击「回滚到此版本」后报错或时间线没变化
现象:confirm 后 alert 显示"回滚失败:...",或回滚后版本列表没有出现新版本。
原因:target_version 非法(≤0)、目标版本非 merged、或后端重建主表失败。
排查步骤:
- 确认回滚的版本状态徽标是 merged(绿色),draft/review 版本不可回滚;
- 检查 alert 中的具体错误码:
INVALID_VERSION_STATE表示状态非法,需核对目标版本; - 查看后端日志确认主表重建 SQL 是否执行成功;
- 刷新页面重新拉取版本列表确认最新状态。
问题 4:影响分析弹窗无法关闭
现象:弹窗一直显示,点「关闭」无反应。
原因:本弹窗的关闭按钮与遮罩点击均依赖 impactModal.visible = false;若连续触发(如合入预检弹窗弹出后未关又点了别的按钮),状态可能被覆盖。
排查步骤:
- 先关闭当前弹窗,观察是否还有第二层弹窗;
- 检查浏览器控制台是否有 Vue 渲染错误;
- 刷新页面即可复位(弹窗为纯前端状态,无后端副作用)。
9. 已知缺陷与边界
| 缺陷/边界 | 说明 |
|---|---|
| 分页为渲染层(后端无服务端分页) | GET /ontology/objects/:id/versions 无 limit/offset 参数(ontology_handlers.go:189-201 → ontology/repository.go:501-512 全量 Find),响应无 total。已补渲染层分页(10/20/50 条每页 + 上/下一页 + 常驻诚实提示),仍一次全量拉取;服务端真分页需后端补分页契约 |
| 审批人员体系未接入 | ApprovalPolicy.required_reviewers 为骨架字段,人员体系后续接入;当前仅按 min_approvals 计数 |
| 无草稿编辑入口 | 本页只负责状态流转;创建/编辑草稿需通过其他入口(如对象编辑相关页面) |
| 回滚不可再回滚 | 回滚生成的是新版本,若想回到回滚前状态需再对更早版本回滚 |
| 状态机严格 | 跨状态操作一律 409,无强制覆盖能力(有意识的安全设计) |
注:影响分析弹窗互相覆盖问题已于 2026-09-06 修复(合入改为先弹影响分析、经用户确认后才真正执行 merge;合入结果以只读弹窗在确认后另屏展示,不再与确认弹窗互相覆盖)。