Files
chorus/docs/02-architecture-and-code-map.md
T

129 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- gitea-wiki-mirror:start -->
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
<!-- gitea-wiki-mirror:end -->
# 架构与代码地图
## 项目定位
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` 必须持久化。
- 输出文件先写临时文件并原子落位;图片必须有缩略图。原图、结果和缩略图访问均校验用户归属。