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: 5ecd4ee8ad3522e75cb8fc0f031238c3beaac85a synchronized_at: 2026-08-20T08:30:20Z # 架构与代码地图 ## 项目定位 chorus 是独立运行的 Go 生图生文服务: - **图生图**:提示词 + 一张或多张原图 → 新图; - **文生文**:提示词 → 文本。 portal 只负责接收、校验、持久化和展示;worker 才能调用上游。admin 配置 Provider/Model、路由池和查看记录。一个仓库包含两个二进制、一个纯领域核心和基础设施适配层: ```text 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_* 基线和配置种子 ``` 固定来源和提交见 [项目档案](Project-Profile.-)。`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}}`,具体内容见业务规则;模板作为数据管理,不硬编码。 ## 代码地图 | 想改什么 | 从哪里开始读 | 必要验证 | |---|---|---| | 页面、状态和响应式 | `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/` | 生成差异审查、生产路由检查 | 文件名是规划入口;实现工单调整后同步本页。 ## 两条主要执行路径 ### 用户提交生成(同步) ```text 表单/HTMX/JSON 请求 → 认证、CSRF(浏览器)、上传与业务校验 → 保存输入文件 → 事务写 generations(status=pending)、generation_inputs → 以 (user_id, idempotency_key) 保证幂等 → 立即返回 loading 卡片或 202 JSON ``` 同步链路不得调用上游,不校验点数或配额,也不得把文件系统真实路径返回给浏览器。HTMX 请求返回可替换的片段;JSON 请求返回稳定错误码与字段级错误,认证失效明确返回 401/登录引导,不能用空白片段吞错。 ### worker 执行生成(异步) ```text 事务内 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 的实际流程是: ```text 编写并验证可逆 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` 必须持久化。 - 输出文件先写临时文件并原子落位;图片必须有缩略图。原图、结果和缩略图访问均校验用户归属。