1. 页面概览
1.1 是什么
代码仓库(CodeRepo)是 LightFoundry 的 SQL 模板/宏复用能力(V5 Stage 6,B5-2),对应 CodeRepoPage.vue。它以三张表(fcr_repos / fcr_files / fcr_revisions)承载三类仓库(snippet 片段 / template 模板 / macro 宏),提供仓库管理、文件树 + 编辑器、版本历史与宏管理四块能力。P0 范围刻意不做 git 协议,采用「同 (repo_id, path) 新版本覆盖」的简化版本日志:文件内容更新时旧内容写入 fcr_revisions 归档、文件版本 +1,最新内容始终在 fcr_files。
页面的特色在「宏」:宏仓库(repo_type=macro)中每个文件即一个宏(path=宏名),SQL 中出现 {{macro:name}} 时在 SQL 工作台 Execute 前由后端自动展开(宏表优先,未命中原样传,最多迭代 maxMacroDepth=5 层防循环引用)。本页提供「宏展开调试」验证展开结果。
1.2 核心价值
| 能力 | 说明 |
|---|---|
| 三类仓库 | snippet(代码片段)/ template(SQL 模板)/ macro(宏定义),名称全局唯一 |
| 文件版本覆盖 | 保存同 path 新版本覆盖(version+1),旧内容自动归档进版本历史;内容未变仅更新元信息不产生新版本 |
| 版本历史与恢复 | 按 version 倒序列出归档版本,可查看内容、一键恢复(当前内容作为新版本归档) |
| 宏自动展开 | SQL 中 {{macro:name}} 在 Execute 前展开;宏表优先、未命中原样传、最多 5 层迭代 |
| 宏展开调试 | 本页直接 POST /coderepo/macros/resolve 验证宏引用结果 |
| 五种文件语言 | sql / python / js / yaml / go,默认 sql |
1.3 一句话总结
代码仓库页是 Foundry 的轻量代码资产库——仓库管类型、文件管版本、宏管复用,{{macro:name}} 一句引用即可把通用 SQL 片段复用到任意工作台查询。
2. 访问入口
2.1 路由与菜单
- 路由路径:
/foundry/coderepo,路由名FoundryCodeRepo,meta.title为「Foundry 代码仓库」,meta.requiresAuth: true(定义于action/web/src/router/index.js第 376-381 行)。 - 菜单入口:Foundry 侧边栏(
action/web/src/views/FoundryLayout.vue第 112 行)菜单项「代码仓库」。 - 源码文件:
action/web/src/views/CodeRepoPage.vue。
2.2 认证与权限
- 路由级
requiresAuth: true:未登录被拦截。 - API 级认证:经
action/web/src/api/client.js注入aip_token;401 跳/login。 - 权限要求:本页接口挂在 Foundry
protected组(RegisterCodeRepoRoutes由接线挂载),任意已登录用户可管理仓库/文件;user_id由鉴权中间件注入作为操作者(owner/updated_by),未登录上下文时回退system或仓库 owner。 - 404 排错:若仓库列表空且报 404,确认后端 18081 已启动、
protected组已挂RegisterCodeRepoRoutes(coderepo/rest.go);Vite 代理/api指向 Foundry。
2.3 端口与 API 前缀
端口:18081(Foundry)。API 前缀:/api(baseURL /api/v1),如 GET /api/v1/coderepo/repos、POST /api/v1/coderepo/macros/resolve。
3. 界面布局
代码仓库
└─ 提示条(alert,右上角「关闭」)
└─ 卡片「仓库列表」(共 N 个)
├─ 按钮「+ 新建仓库」/「收起」
├─ 新建/编辑表单(名称 * / 类型 * / 描述 + 保存/取消)
├─ 搜索行:搜索仓库(名称/描述/类型)+「共 N 个,命中 M 个」
├─ 表格:名称 / 类型 / 描述 / 文件数(按需载入)/ 操作(编辑/删除)
├─ 本地分页:上一页 / 第 x / y 页(共 N 条)/ 下一页(仅命中数 > 每页 10 条时显示)
└─ 诚实提示:仓库列表接口无搜索/分页参数、文件数按需查询
└─ [选中仓库] 三栏网格(340px 文件树 + 编辑器 + 版本历史)
├─ 左:{{name}} · 文件(N 个)+ 「+ 新建文件」+ 文件列表(语言/路径/版本 + 编辑/历史/删除)
├─ 中:编辑器(路径 disabled / 语言下拉 / 内容 textarea + 保存(vN+1))
└─ 右:版本历史(N 条归档 + 查看/恢复 + 归档预览)
└─ 卡片「宏管理」
├─ 左:宏清单(当前 macro 仓库的宏文件)
└─ 右:宏展开调试(textarea + 「展开预览」+ 展开结果/错误)
各板块职责:
- 仓库列表:全量仓库表;新建/编辑复用同一表单(编辑时带 id,保存走 PUT);删除级联文件与版本历史。
- 文件树:选中仓库后加载文件;新建文件走
POST /coderepo/repos/:id/files;行内编辑/历史/删除。 - 编辑器:当前文件内容编辑;语言可切换;保存提示「保存(vN+1)」;「关闭」清空编辑态。
- 版本历史:文件归档版本列表;「查看」在下方预览,「恢复」把归档内容写回为最新版本。
- 宏管理:当前仓库若为 macro 类型,文件即宏清单;右侧调试区验证
{{macro:name}}展开。
4. 交互元素详解
4.1 仓库列表与表单
| 元素 | 含义 | 必填/默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|
| 按钮「+ 新建仓库」/「收起」 | 展开/收起仓库表单 | — | 展开时重置表单(emptyRepoForm,类型默认 snippet) | — |
| 输入「名称 *」 | 仓库名称,全局唯一 | 必填 | placeholder「如 销售 SQL 模板」 | POST /coderepo/repos |
| 下拉「类型 *」 | 仓库类型 | 必填,默认 snippet | snippet 片段 / template 模板 / macro 宏 | 同上 |
| 输入「描述」 | 用途说明 | 选填 | 随 payload 提交 | 同上 |
| 按钮「保存」(busy「提交中...」) | 提交新建/更新 | — | 新建走 POST 并自动选中新仓库;编辑走 PUT;成功后刷新列表 | POST /coderepo/repos、PUT /coderepo/repos/:id |
| 按钮「取消」 | 收起表单 | — | 仅关闭表单 | — |
| 行内「编辑」 | 填表进入编辑态 | — | 复用表单,repoForm.id 有值 → PUT | — |
| 行内「删除」 | 删除仓库 | — | confirm「确定删除仓库"xxx"吗?其全部文件与版本历史将一并删除。」;若删除当前仓库同时清空文件/编辑器态 | DELETE /coderepo/repos/:id |
| 输入「搜索仓库」 | 名称/描述/类型纯前端过滤 | — | 输入即过滤 repos,并显示「共 N 个,命中 M 个」;无匹配显示「没有匹配的仓库。」 | 无(前端过滤) |
| 行内「载入」(文件数列) | 按需查询该仓库文件数 | — | 点后请求该仓库文件列表并回填数量(加载中「查询中...」);选中该仓库后自动已知 | GET /coderepo/repos/:id/files |
| 「上一页」/「下一页」 | 仓库列表本地分页(每页 10 条) | — | setRepoPage 切片 filteredRepos | 无(纯前端切片) |
仓库列表列:名称(link 样式点击选中)、类型(snippet 蓝 / template 绿 / macro 橙 tag)、描述、文件数、操作。空态「暂无仓库(后端可能未就绪)。」。
文件数按需加载(消除首屏 N+1):fetchRepos 只请求 GET /coderepo/repos,不再对每个仓库追加一次 GET /coderepo/repos/:id/files。文件数单元格逻辑:选中仓库(selectRepo → loadFiles)已取回其文件列表,直接显示文件数(免额外请求);其余仓库显示「载入」链接按钮,点 loadFileCount(r) 才按需请求该仓库文件列表并回填数量,加载中显示「查询中...」;后端无批量文件计数端点(见 5.5),故无法一次取回全部计数;按需加载后首屏只有 1 个请求。
搜索与分页(渲染层):fetchRepos 取回全量后,filteredRepos 按名称/描述/类型做纯前端关键词过滤(大小写不敏感),pagedRepos 按固定每页 10 条切片;分页条仅在命中数 > 10 时显示。搜索/分页均无后端调用(GET /coderepo/repos 不接受查询参数,见 5.2)。
4.2 文件树与编辑器
| 元素 | 含义 | 必填/默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|
| 按钮「+ 新建文件」 | 显示文件路径表单 | — | emptyFileForm(语言默认 sql) | — |
| 输入「文件路径 *」 | 仓库内唯一路径 | 必填 | placeholder「如 monthly_report.sql」;同 (repo_id,path) 已存在返回 DUPLICATE_ENTRY | POST /coderepo/repos/:id/files |
| 文件行 | 语言 tag + path + v{version} | — | 点击或「编辑」打开到编辑器 | — |
| 「历史」 | 打开版本历史 | — | 加载该文件归档 | GET /coderepo/files/:id/revisions |
| 「删除」 | 删除文件 | — | confirm「确定删除文件"xxx"吗?其版本历史将一并删除。」 | DELETE /coderepo/files/:id |
| 输入「路径 path」 | 当前文件路径 | 编辑时禁用 | 只读展示 | — |
| 下拉「语言 language」 | 文件语言 | 默认 sql | sql / python / js / yaml / go | PUT /coderepo/files/:id |
| 文本域「内容 content(保存时新版本覆盖,旧内容进版本历史)」 | 文件内容 | — | placeholder「SQL 模板 / 宏内容 / 代码片段...」 | 同上 |
| 按钮「保存(vN+1)」 | 提交保存 | — | 保存后 alert「已保存,新版本 v{version}」并刷新文件树 | PUT /coderepo/files/:id |
| 按钮「关闭」 | 退出编辑 | — | 清空 currentFile 与表单 | — |
4.3 版本历史与宏管理
| 元素 | 含义 | 操作效果 | 后端调用 |
|---|---|---|---|
| 版本历史列表 | 归档版本(version 倒序) | 每行显示 v{n} + 内容前 40 字 + 操作者/时间 | GET /coderepo/files/:id/revisions |
| 「查看」 | 预览归档内容 | 下方 sample-box 展示 | — |
| 「恢复」 | 恢复为归档内容 | confirm「确定将文件恢复到 v{n} 吗?(当前内容将作为新版本归档)」;恢复后当前内容以新版本归档 | PUT /coderepo/files/:id |
| 宏清单 | 当前 macro 仓库的宏文件 | 文件路径即宏名,显示 macro tag + path + 内容前 60 字 | — |
| 「宏展开调试」区 | 输入 SQL 含 {{macro:name}} | 点「展开预览」POST 解析,结果/错误展示 | POST /coderepo/macros/resolve |
| 按钮「展开预览」/「展开中...」 | 触发宏展开 | 成功显示「展开结果:」,失败红字错误 | POST /coderepo/macros/resolve |
5. 后端关联
5.1 API 客户端
本页使用 action/web/src/api/client.js(baseURL: '/api/v1'、timeout: 30000、aip_token Bearer 注入、401 跳登录),无自定义导出函数。
5.2 端点表
| 方法 | 路径(前缀 /api/v1) | 请求体 | 用途 |
|---|---|---|---|
| GET | /coderepo/repos | —(无搜索/分页参数) | 仓库列表(created_at 倒序,全量返回) |
| POST | /coderepo/repos | {name, description, repo_type} | 创建仓库(name 唯一,类型合法) |
| GET | /coderepo/repos/:id | — | 仓库详情(含文件列表) |
| PUT | /coderepo/repos/:id | {name?, description?, repo_type?} | 更新仓库(指针语义) |
| DELETE | /coderepo/repos/:id | — | 删除仓库(级联文件+版本历史) |
| GET | /coderepo/repos/:id/files | — | 仓库文件列表(path 升序) |
| POST | /coderepo/repos/:id/files | {path, content, language} | 添加文件(同 repo+path 重复报 DUPLICATE_ENTRY) |
| GET | /coderepo/files/:id | — | 文件详情 |
| PUT | /coderepo/files/:id | {content?, language?} | 更新文件(content 变化 version+1 并归档旧版) |
| DELETE | /coderepo/files/:id | — | 删除文件(级联版本历史) |
| GET | /coderepo/files/:id/revisions | — | 版本历史(version 倒序) |
| POST | /coderepo/macros/resolve | {sql} | 宏展开调试,返回 {sql} |
5.3 响应结构
仓库列表 GET /api/v1/coderepo/repos:
{ "code": 0, "data": { "repos": [
{ "id": "uuid", "name": "销售 SQL 模板", "description": "月度报表", "repo_type": "template",
"owner": "u1", "created_at": "..." }
], "total": 1 } }
文件列表 GET /api/v1/coderepo/repos/:id/files:
{ "code": 0, "data": { "files": [
{ "id": "uuid", "repo_id": "uuid", "path": "monthly_report.sql",
"content": "SELECT ...", "language": "sql", "version": 3,
"updated_by": "u1", "updated_at": "..." }
], "total": 1 } }
宏展开 POST /api/v1/coderepo/macros/resolve 请求与响应:
{ "sql": "SELECT {{macro:amount}} FROM orders" }
→ { "code": 0, "data": { "sql": "SELECT COALESCE(amount,0) FROM orders" } }
5.4 关联模块表
| 后端包/文件 | 职责 |
|---|---|
foundry/coderepo/service.go | Repo/File CRUD、版本归档、ResolveMacros 宏展开 |
foundry/coderepo/models.go | fcr_repos / fcr_files / fcr_revisions 模型与常量 |
foundry/coderepo/rest.go | 路由注册与 handler(maxJSONBodyBytes 1MB) |
foundry/query | SQL 工作台 Execute 前由接线挂 ResolveMacros(本包不触碰 query 内部) |
5.5 关键机制
- 版本覆盖归档:
UpdateFile在content != nil && *content != f.Content时,事务内先写fcr_revisions(归档版本号 = 当前 Version,连同旧 updated_by/updated_at),再version+1覆盖fcr_files;content 未变时仅更新元信息(不产生新版本)。删除文件/仓库级联清理归档。 - 宏解析规则(
ResolveMacros):- 引用格式
{{macro:name}},name 支持字母/数字/下划线/点/横线([A-Za-z0-9_.\-]+,与 workflow 变量字符集对齐); - 宏表优先:宏仓库(
repo_type=macro)中按path=name命中取内容替换; - 未命中原样传(不报错、不剥离);
- 迭代展开:宏内容可再引用宏,最多
maxMacroDepth=5层防循环/自引用死循环; - 命中缓存 + 未命中缓存(
cache/missedmap)避免重复查库;单轮无替换即提前终止。
- 引用格式
- 挂载点:接线 agent 在 SQL 工作台/
query.Execute前调用sql = svc.ResolveMacros(ctx, sql)。 - 创建文件与版本更新分离:新建文件必须走
POST /coderepo/repos/:id/files(同 path 重复即报错);更新走PUT /coderepo/files/:id产生新版本。 - owner 语义:
CreateRepo/CreateFile的 owner 取currentUserID,缺省回退system/ 仓库 owner;updated_by随保存记录。 - 列表无搜索/分页、无批量计数端点:
ListRepos(ctx)无任何参数,直接Order("created_at DESC").Find全量(foundry/coderepo/service.go:86-92),handler 也不解析 query(foundry/coderepo/rest.go:62-71)——搜索/分页只能在渲染层做;文件计数只能按仓库逐个GET /coderepo/repos/:id/files(rest.go:148-157),后端没有一次返回多仓文件数的聚合端点,故原「fetchRepos逐仓 N+1」改为按需加载。
6. 核心流程详解
6.1 主流程:定义宏并在 SQL 中复用
- 建宏仓库:点「+ 新建仓库」,名称填如「公共宏」,类型选
macro 宏,保存。 - 加宏文件:进入仓库(自动加载文件树),点「+ 新建文件」,路径填
amount(= 宏名),内容填如COALESCE(amount,0),语言 sql,创建。文件创建成功提示「文件创建成功 v1」。 - 调试展开:在「宏展开调试」输入
SELECT {{macro:amount}} FROM orders,点「展开预览」,得到SELECT COALESCE(amount,0) FROM orders。 - 工作台复用:在 SQL 工作台执行含
{{macro:amount}}的 SQL,Execute 前自动展开。 - 迭代更新:编辑器修改宏内容并「保存(v2)」,旧内容进版本历史;右侧历史可查看 v1 并可「恢复」。
6.2 版本历史管理
- 文件树点「历史」→
GET /coderepo/files/:id/revisions按版本倒序列出归档。 - 「查看」预览某版本内容;「恢复」将该版本内容写回(当前内容先归档为新版本)。
- 未覆盖保存过的文件无历史(「暂无历史版本(尚未覆盖保存过)。」)。
6.3 状态与终态语义
文件版本为线性递增(1,2,3...),最新版本常驻 fcr_files,历史版本在 fcr_revisions;恢复操作不覆盖原版本号而是「当前内容归档为 vN+1、目标内容成为 vN+2」。宏展开为同步请求(30 秒 timeout),macroBusy 期间按钮禁用;展开失败红字显示 error 并保留输入。
7. 权限与安全
- 认证:aip_token JWT;401 跳登录。
- 数据级安全:protected 组鉴权,任意已登录用户可管理仓库/文件;owner/updated_by 记录操作者身份供审计;无对象级 RLS。
- 写操作防护:删除仓库/文件/恢复版本均有 confirm 二次确认;保存中按钮禁用;
decodeJSON限制 body 1MB(宏内容/大文件保存受此约束);错误仅透传 error 文本。
8. 常见问题与排错
8.1 创建文件报「文件已存在」
现象:新建文件返回 DUPLICATE_ENTRY「文件已存在: 仓库/路径」。原因:同 (repo_id, path) 已存在;文件版本覆盖走「编辑保存」(PUT)而非新建。排查步骤:1) 确认该路径已存在于文件树;2) 若要更新内容,在文件树点「编辑」打开到编辑器后「保存(vN+1)」;3) 若确认要新文件,改用不同的 path。
8.2 保存文件后版本不变
现象:编辑后保存,alert 提示新版本但 v{version} 未 +1。原因:UpdateFile 仅在 content 实际变化时归档+升版;只改语言或内容未变时仅更新元信息。排查步骤:1) 确认内容确实发生了变化;2) 只改语言不升版属预期;3) 用「历史」确认归档是否新增。
8.3 宏展开后 {{macro:name}} 原样保留
现象:点「展开预览」结果中宏引用未替换。原因:宏名未命中任何 macro 仓库文件(未命中原样传是设计语义);或宏仓库类型不是 macro。排查步骤:1) 确认存在 repo_type=macro 的仓库;2) 确认该仓库内有 path=宏名的文件(文件路径即宏名,注意大小写与空格);3) 确认引用语法为 {{macro:name}}(name 仅允许字母/数字/下划线/点/横线);4) 展开调试输入与宏内容逐一比对。
8.4 宏展开死循环或异常中断
现象:宏 A 引用宏 B、宏 B 又引用宏 A 时,展开结果停留在第 5 层不再替换。原因:maxMacroDepth=5 的迭代上限触发(防循环引用的兜底,不做错误上报)。排查步骤:1) 确认这是预期的安全机制而非错误;2) 检查宏定义是否存在循环引用(宏内容里引用了自身或互相引用);3) 修正宏内容避免循环;4) 若业务确实需要更深嵌套,需后端调整 maxMacroDepth 常量。
8.5 仓库删除后仍在页面
现象:删除仓库后列表仍显示或编辑器仍显示旧文件。原因:删除的是非当前仓库时前端只刷新列表;删除当前仓库时前端已清空 currentRepo/files/currentFile。排查步骤:1) 删除当前仓库后前端应清空选中态——若未清空,刷新页面确认;2) 直接调用 GET /api/v1/coderepo/repos 验证后端已删;3) 若为缓存,重新加载页面。
9. 已知缺陷与边界
| 项目 | 说明 |
|---|---|
| 不做 git 协议 | P0 范围刻意简化:无分支/合并/diff/多人协作,仅线性版本覆盖 |
| 版本恢复是「新版本」 | 恢复归档内容会生成新版本号,不覆盖既有历史 |
| 宏未命中原样传 | 拼写错误不报错,仅靠调试工具发现 |
| maxMacroDepth=5 | 循环引用在 5 层后静默截断,无显式告警 |
| 文件计数无批量端点 | 后端无一次返回多仓文件数的聚合端点(coderepo/service.go:86-92、rest.go:148-157),前端已改为按需加载(选中仓库自动载入或点「载入」);未载入的仓库文件数显示「载入」链接而非数量 |
| 列表无搜索/分页参数 | GET /coderepo/repos 无查询参数(coderepo/service.go:86-92、rest.go:62-71),仓库搜索/分页为前端渲染层实现,仍一次全量拉取;文件树仍全量渲染(未加分页) |
| 内容字节比较 | content 完全相等时不产生新版本(仅元信息更新) |
| body 1MB 上限 | decodeJSON 限制,超大文件/宏内容保存会被拒绝 |