Files
chorus/docs/02-architecture-and-code-map.md
T
2026-08-20 23:22:32 +08:00

9.7 KiB
Raw Blame History

generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Architecture-and-Code-Map wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Architecture-and-Code-Map.- wiki_revision: 525b833916552d1f2203ef462ea42f5e8a853270 synchronized_at: 2026-08-20T15:21:40Z

架构与代码地图

项目定位

chorus 是独立运行的 Go 生图生文服务:

  • 图生图:提示词 + 一张或多张原图 → 新图;
  • 文生文:提示词 → 文本。

portal 只负责接收、校验、持久化和展示;worker 才能调用上游。admin 配置 Provider/Model、路由池和查看记录。一个仓库包含两个二进制、一个纯领域核心和基础设施适配层:

chorus/
├── internal/core/              领域模型、接口、编排和状态规则;仅 GORM + 标准库
│   ├── model/
│   ├── provider/               协议接口与 chat/images/images_edits/gemini 领域语义
│   ├── router/                 候选、retryable 与选路规则
│   ├── generate/               prompt 合成和生成编排
│   ├── queue/                  租约、CAS、重试状态
│   ├── storage/                存储接口
│   └── crypto/                 密钥加密接口
├── internal/platform/          HTTP/SSRF、gobreaker、imaging、本地/S3 适配
├── portal/                     用户页面、HTMX 片段、JSON API、worker 组装
├── admin/                      固定 go-admin 后端,业务 app 在 app/chorus/
├── admin-ui/                   固定 go-admin-ui 导入代码与定制页
└── migrations/                 所有生产表、sys_* 基线和配置种子

固定来源和提交见 项目档案。D:\github\goadmin 只用于来源审查和首次导入,运行、CI 与部署不得依赖该路径。

MVP-0 的最小形态

目录 MVP-0 实现 暂缓
internal/core/model users、providers、provider_models、prompt_templates、generations、输入/输出 点数、路由池、审计
internal/core/provider chat、images_edits images、gemini
internal/core/router 唯一启用 ProviderModel 加权、熔断、故障转移
internal/core/queue SKIP LOCKED、租约 token/owner、CAS 完成、失败落错 心跳续租、多次跨 Provider 重试、清理
internal/platform SSRF 安全 HTTP、本地存储、缩略图 gobreaker、对象存储
portal 种子用户登录、桌面双栏/移动单列、提交、轮询、结果 公开注册、点数/API Key 导航、无限滚动、移动抽屉
admin/admin-ui 不接入;配置由可重复种子命令完成 MVP-1 schema-first CRUD 与定制页

MVP-0 第一张上传图自动作为 primary,其余为 reference;不提供角色编辑、备注和拖拽。默认中性模板已于 2026-08-20 确认,使用 Go text/template 且只开放 {{.UserPrompt}},具体内容见业务规则;模板作为数据管理,不硬编码。

#6 已落地的共享基线

  • go.mod 固定 Go 1.26.5;internal/config 只从环境读取配置,生产缺少 DSN、会话密钥、Provider 主密钥或存储目录时失败关闭且不输出值。
  • migrations/000001_mvp0_base 建立 users、providers、provider_models、prompt_templates、generations、generation_inputs、generation_outputs,生产启动不调用 AutoMigrate。
  • 迁移显式定义用户幂等唯一键、队列/历史索引、5 个外键和删除规则,以及状态、租约、错误、attempt 数组和输出内容检查约束。
  • internal/seed/defaults.json 保存已确认 Prompt 和 mock ProviderModel 数据;cmd/chorus-seed 在事务中幂等 upsert 构造用户、mock Provider/Model 和 Prompt,不保存或输出明文凭据。
  • portal 当前只是配置门禁骨架,尚未注册 HTTP 路由或 worker;后续工单不得把它当作已完成服务。

#7 已落地的核心契约

  • internal/core/model 的 7 个 GORM 模型按 #6 migration version 1 映射,schema 单测核对完整列集合;状态只允许 pending → running → succeeded|failed,终态不可回退。
  • internal/core/provider、queue、storage、crypto 和 model 仓储接口只表达 MVP-0 所需能力;core 不 import Gin、go-admin、gobreaker、imaging 或具体 HTTP/文件系统实现。
  • internal/core/generate 使用 Go text/template 解析数据模板,但只接受直接的 .UserPrompt 插值;空输入、非法语法、缺少/未知变量和额外模板构造均失败关闭。chat 保留用户原文,images_edits 渲染结果与已确认模板逐字一致。
  • 图片输入复制后按上传顺序写入 position,第一张为 primary、其余为 reference;调用方可直接把返回值和 rendered prompt 持久化。
  • internal/core/router 固定 HTTP 429/5xx、超时和连接错误为 retryable,400/401、内容策略拒绝及未知错误为不可重试;MVP-0 仍只调用唯一启用模型,不执行换家。
  • GORM 固定为用户指定 go-admin 提交使用的 v1.31.2;生产启动和 core 均没有 AutoMigrate。

代码地图

想改什么 从哪里开始读 必要验证
页面、状态和响应式 portal/web/templates/、portal/web/static/ 浏览器 E2E + 已确认原型
提交校验/幂等 portal/handler/generate.go handler 测试、跨用户测试
HTMX 卡片轮询 portal/handler/card.go 终态停止、网络错误、认证失效
prompt 合成 internal/core/generate/prompt.go 默认模板、图片顺序与落库
上游协议 internal/core/provider/ mock 429/5xx/超时/连接/400/401/策略拒绝
SSRF/重定向/结果 URL internal/platform/http/ DNS、IPv4/IPv6、redirect、proxy 测试
选路与 retryable internal/core/router/ 四类红线用例
队列和陈旧 worker internal/core/queue/ MySQL 并发、租约过期、CAS
文件与缩略图 internal/platform/storage/ 临时文件、原子 rename、权限
表结构 migrations/ 最新 SQL 隔离 MySQL up/down
管理端 CRUD admin/app/chorus/、admin-ui/src/views/ 生成差异审查、生产路由检查

文件名是规划入口;实现工单调整后同步本页。

两条主要执行路径

用户提交生成(同步)

表单/HTMX/JSON 请求
  → 认证、CSRF(浏览器)、上传与业务校验
  → 保存输入文件
  → 事务写 generations(status=pending)、generation_inputs
  → 以 (user_id, idempotency_key) 保证幂等
  → 立即返回 loading 卡片或 202 JSON

同步链路不得调用上游,不校验点数或配额,也不得把文件系统真实路径返回给浏览器。HTMX 请求返回可替换的片段;JSON 请求返回稳定错误码与字段级错误,认证失效明确返回 401/登录引导,不能用空白片段吞错。

worker 执行生成(异步)

事务内 SKIP LOCKED 选 pending 或租约已过期的 running
  → 原子写 running、lease_owner、lease_token、lease_until,attempt_count + 1
  → 合成 rendered_prompt 并落库
  → 选 ProviderModel,使用安全 HTTP 适配器调用
  → 结果写临时文件,校验后原子 rename;图片生成缩略图
  → 用 id + lease_token + status=running 做 CAS
  → 成功写 outputs/status=succeeded;失败写 status=failed/error/attempts

状态对外始终是 pending → running → succeeded|failed。过期任务由新 worker 在一次原子更新中替换租约,不把状态退回 pending。旧 worker 的 token 已失效,最终 CAS 必须影响 0 行,不能覆盖新 worker 结果。租约时长必须大于上游请求超时、下载/存储和安全余量之和;具体值是部署前配置门禁。

attempts 与 attempt_count 在同一事务/原子更新中追加,错误内容先分类、脱敏和截断。worker 收到退出信号后停止取新任务,等待在途任务到设定期限;无法完成时让租约自然过期,不能无条件提交。

管理端数据库与代码生成

固定 go-admin-ui 的实际流程是:

编写并验证可逆 SQL
  → 在隔离开发库执行 up
  → /db/tables/page 读取现有表
  → /sys/tables/info 导入 sys_tables/sys_columns 元数据
  → 配置、预览、生成 Go/Vue CRUD
  → 人工审查路径、权限、字段和差异
  → 菜单/API 配置改写为可逆 SQL
  → 重新从空库执行全部 migrations 验证

go-admin 的 AutoMigrate 只能在隔离、可丢弃数据库中用于研究固定提交的初始化结构,不能嵌入应用启动或部署。go-admin-ui 的“生成迁移脚本”不是业务表 DDL;/gen/todb 会直接改菜单,/gen/toproject 会写源码。dev-tools 路由在固定提交中仅有 JWT 且绕过 Casbin,生产必须通过构建/路由注册检查确保不存在,不能只在菜单中隐藏。

不可破坏的边界

  • internal/core 只依赖 GORM 和标准库;Gin、go-admin、gobreaker、imaging 都在外层。
  • 管理员 sys_user 与终端用户 users 分表,不复用密码、Cookie、JWT 或 API Key。
  • 生成链路不读写点数:无扣费、退款、余额校验或每日配额。
  • 429/5xx/超时/连接错误 才换下一家;400/401/内容策略拒绝 立即返回。
  • 所有 Provider 请求、重定向和上游返回 URL 下载都必须经过 DNS 解析后、连接前的 DialContext 拦截;代理环境不能绕开检查。
  • 生产数据库只接受 migrations/,不执行 AutoMigrate。
  • rendered_prompt、attempts、失败 error_code/error_message 必须持久化。
  • 输出文件先写临时文件并原子落位;图片必须有缩略图。原图、结果和缩略图访问均校验用户归属。