docs: 记录 Supervisor 三实例运行方式 (#39)

ila
2026-08-24 10:14:55 +08:00
parent 7bdb325936
commit db92190a43
2 changed files with 354 additions and 512 deletions
-376
@@ -1,376 +0,0 @@
# 本地开发与验证
本页区分“现在可执行的文档流程”和“产品代码落地后可执行的 Go/前端/数据库流程”。代码不存在或本机版本不满足时必须如实记录,不把预期当作通过。
## 环境要求
| 项 | 目标要求 | 检查命令 |
|---|---|---|
| Git | 近期版本 | `git --version` |
| Python | Python 3 标准库 | `python --version` |
| Go | **1.26.5** | `go version` |
| MySQL | 8.0,支持 SKIP LOCKED | `mysql --version` |
| golang-migrate | 可执行文件 | `migrate -version` |
| Node | >=22,用于重建 portal/web 与 admin-ui 静态资源 | `node --version` |
| pnpm | 9.15.1,由 packageManager/Corepack 固定 | `pnpm --version` |
| Tailwind | 4.3.3,由 portal/web 锁文件固定 | `pnpm --dir portal/web exec tailwindcss --help` |
2026-08-20 已完成 MVP-0 环境基线:便携 Go 1.26.5、golang-migrate v4.19.1 和隔离 MySQL 8.4.8 已验证。Chorus 专用实例监听 `127.0.0.1:3308`,使用独立数据目录和测试账号;既有 `3307` 实例与 MySQL 5.7 不受影响。Node 22.22.1 可用;admin-ui 后续仍必须按 `packageManager` 使用 pnpm 9.15.1,不能直接采用全局 pnpm 11。
固定来源:
- `D:\github\goadmin\go-admin` @ `f06540883b41d03782bb6b2c4150f298f328c6b6`
- `D:\github\goadmin\go-admin-ui` @ `67d393d713877572fab0b897296a4c1d525fc81d`
- `D:\github\goadmin\go-admin-doc` @ `424855aacf6905f3fde860c3331385cb25529a0d`
只从这些固定提交审查/导入,不直接在其工作区开发 chorus,也不让 chorus 构建依赖绝对路径。
主要环境变量:
| 变量 | 用途 |
|---|---|
| `CHORUS_DSN` | Go 应用、种子和管理员 bootstrap 命令使用的 GORM/MySQL DSN |
| `CHORUS_ADMIN_USERNAME` | 管理员 bootstrap 的目标账号;部署配置设为 `admin` |
| `CHORUS_ADMIN_PASSWORD` | 管理员 bootstrap 密码;只在当前受保护进程环境中提供 |
| `CHORUS_ADMIN_RESET_PASSWORD` | 仅设为 `1` 时允许 bootstrap 重置已有管理员密码 |
| `CHORUS_MIGRATE_URL` | golang-migrate 专用 MySQL URL |
| `CHORUS_SEED_USER_EMAIL` / `CHORUS_SEED_USER_PASSWORD` | 构造种子用户;仅通过环境注入 |
| `CHORUS_SEED_PROVIDER_BASE_URL` | mock Provider 基础 URL;不得指向生产 |
| `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES` | 默认不设置或 `0`,管理端拒绝连通性探测;只有人工授权真实探测时才设为 `1` |
| `CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS` | 探测已授权时的正整数冷却;未授权时不需要 |
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` / `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 探测已授权时的正整数安全 HTTP 限制 |
| `CHORUS_SESSION_KEY` | portal 会话密钥;不得与数据库、JWT 或其他凭据复用 |
| `CHORUS_STORAGE_ROOT` | 受保护生成物目录 |
| `CHORUS_ENV` | `development/test/production`,生产强制安全开关 |
| `CHORUS_SESSION_TTL_MINUTES` | 会话有效分钟数;生产必填 |
| `CHORUS_LOGIN_ATTEMPTS` / `CHORUS_LOGIN_WINDOW_SECONDS` | 单远端地址登录窗口限制;生产必填 |
| `CHORUS_MAX_PROMPT_BYTES` | prompt UTF-8 字节上限;生产必填 |
| `CHORUS_MAX_IMAGES` | 单任务图片数量上限;生产必填 |
| `CHORUS_MAX_IMAGE_BYTES` / `CHORUS_MAX_UPLOAD_BYTES` | 单图/整次 multipart 字节上限;生产必填 |
| `CHORUS_MAX_IMAGE_PIXELS` | 单图解码像素上限;生产必填 |
| `CHORUS_HISTORY_LIMIT` | 单次历史查询上限;生产必填 |
| `GITEA_TOKEN` | Wiki/工单操作,不影响产品服务 |
## 第一次运行
### 1. 文档与工作区
```powershell
git status --short --branch
python dev_scripts/harness.py check --strict
python -m unittest discover -s tests -v
$env:GITEA_URL = "https://git.ilapage.cn"
python dev_scripts/harness.py sync --check
```
### 2. 进入固定工具环境并连接隔离数据库
工具和凭据都位于仓库外。以下占位路径由维护者替换为自己的本机工具目录;环境文件不得提交,也不得把展开后的 DSN 输出到终端、日志、工单或 Wiki。
```powershell
$tools = "<本机工具目录>"
$env:PATH = "$tools\go1.26.5\bin;$tools\migrate-v4.19.1;$env:PATH"
. "$tools\chorus-test.env.ps1"
go version
migrate -version
mysql --version
```
当前已验证变量包括 `CHORUS_MYSQL_HOST`、`CHORUS_MYSQL_PORT`、`CHORUS_MYSQL_DATABASE`、`CHORUS_MYSQL_USER`、`CHORUS_MYSQL_PASSWORD`、`CHORUS_DB_DSN` 和 `CHORUS_MIGRATE_URL`。应用使用 GORM DSN,迁移工具使用单独验证过的 MySQL URL,不在两种格式之间临时拼接。
专用实例的可复制启动和停止方式如下;`my.ini` 必须绑定 `127.0.0.1`,并指定独立端口、数据目录、日志和 PID 文件。
```powershell
$mysqlHome = "<MySQL 8 安装目录>"
$instance = "<Chorus MySQL 实例目录>"
Start-Process -FilePath "$mysqlHome\bin\mysqld.exe" `
-ArgumentList "--defaults-file=$instance\my.ini", "--console" `
-WindowStyle Hidden
$env:MYSQL_PWD = $env:CHORUS_MYSQL_ROOT_PASSWORD
& "$mysqlHome\bin\mysqladmin.exe" --protocol=tcp `
--host=127.0.0.1 --port=3308 --user=root shutdown
$env:MYSQL_PWD = $null
```
能力探测使用构造查询,不创建业务表:
```powershell
$env:MYSQL_PWD = $env:CHORUS_MYSQL_PASSWORD
mysql --protocol=tcp --host=$env:CHORUS_MYSQL_HOST `
--port=$env:CHORUS_MYSQL_PORT --user=$env:CHORUS_MYSQL_USER `
--database=$env:CHORUS_MYSQL_DATABASE `
--execute="START TRANSACTION; SELECT id FROM (SELECT 1 AS id) AS capability_probe FOR UPDATE SKIP LOCKED; ROLLBACK;"
$env:MYSQL_PWD = $null
```
#6 已提供首个迁移和幂等种子命令。只在明确的隔离库执行:
```powershell
migrate -path migrations -database $env:CHORUS_MIGRATE_URL up
$env:CHORUS_SEED_USER_EMAIL = "mvp0-test@chorus.invalid"
$env:CHORUS_SEED_USER_PASSWORD = "<本次构造密码>"
$env:CHORUS_SEED_PROVIDER_BASE_URL = "<mock 上游 URL>"
go run ./cmd/chorus-seed
go run ./cmd/chorus-seed
migrate -path migrations -database $env:CHORUS_MIGRATE_URL down -all
migrate -path migrations -database $env:CHORUS_MIGRATE_URL up
```
两次种子执行后应仍是一个构造用户、一个 mock Provider、两个 ProviderModel 和两个 Prompt。`down -all` 会删除隔离库业务数据,只能用于明确可丢弃的测试库。`chorus_codegen` 只在 MVP-1 的 go-admin 结构研究/代码生成工单中另行创建,不提前扩大当前账号权限。
### 管理员 bootstrap(#28)
`go run ./cmd/chorus-admin-bootstrap` 只在 #20 的迁移及 `chorus_operator` 角色种子已完成后运行。它不执行迁移、AutoMigrate、seed,也不接受命令行参数。账号只创建一次;发现同名账号时会检查账号启用、未软删除且角色仍为 `chorus_operator`,不满足就失败关闭而不修改数据。
```powershell
$env:CHORUS_ADMIN_USERNAME = "admin"
# 仅在当前受保护终端设置 CHORUS_ADMIN_PASSWORD;不得写入脚本、仓库、日志或文档。
go run ./cmd/chorus-admin-bootstrap
```
第二次运行不会改变密码。只有经人工确认需要重置时,才在同一受保护终端临时设定 `CHORUS_ADMIN_RESET_PASSWORD=1` 后运行;完成后移除该变量。命令只输出创建、保持或显式重置的结果,不回显 DSN、密码或 bcrypt hash。
集成测试使用运行时生成的构造账号与密码,并要求显式的 `CHORUS_RUN_ADMIN_BOOTSTRAP_TESTS=1` 和指向隔离库的 `CHORUS_ADMIN_BOOTSTRAP_TEST_DSN`。测试结束会删除构造账号;不得把该变量指向开发共享库或生产库。
### 启动管理端后端(#23)
先完成 migrations 和管理员 bootstrap。把 `admin/config/settings.example.yml` 复制到仓库外的受保护位置,并在其中设置独立 JWT secret 与数据库连接;示例文件不得填入真实值。确认数据库已执行全部 migrations,并使用受保护的 settings 文件启动:
```powershell
go -C admin run . server --config "<受保护 settings.yml 路径>"
```
该命令只启动 go-admin 管理 API,不执行 migration、seed 或 AutoMigrate,也不注册验证码、代码生成、Swagger、WebSocket 或静态文件路由。`POST /api/v1/login` 在 `prod` 与 `dev` 都只接收必填的 `username`、`password`,不需要验证码字段。管理 API 使用 `Authorization: Bearer <JWT>`;不要在 URL 或 Cookie 放置 token。连通性探测默认返回 403 且不出站。只有经过人工批准的单次真实检查,才在本次受保护进程设置 `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES=1`,并同时提供正数 `CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS`、`CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` 和 `CHORUS_PROVIDER_MAX_RESPONSE_BYTES`。
### 3. go-admin schema-first 生成(MVP-1)
1. 先在 `chorus_codegen` 执行同一套版本化 SQL。
2. 启动仅限本机的 admin/admin-ui 开发实例。
3. 从数据库表列表导入业务表元数据,配置并预览生成结果。
4. 生成 Go/Vue 文件后检查输出路径、字段类型、敏感字段、权限和重复代码。
5. 不直接采用“生成迁移脚本”或 `/gen/todb` 的副作用;把菜单/API 配置整理成带 down 的 SQL。
6. 清空隔离库,从 migrations 重放并重新生成/构建。
7. 生产构建验证 dev-tools 路由不存在;仅隐藏菜单不算关闭。
go-admin AutoMigrate 不进入以上标准流程。如确需对照固定提交初始化结构,人工在额外可丢弃库运行一次,导出结构差异后销毁;禁止连接共享/生产库,也禁止把该命令放入应用启动、容器入口或部署脚本。
### 4. 配置 mock 上游与种子用户(MVP-0)
MVP-0 不开放注册。使用可重复、不会在日志输出密钥的种子命令建立测试用户、默认 prompt template、Provider 和 ProviderModel。默认先连接 mock HTTP 上游,覆盖文本、图片、429、5xx、超时、连接错误、400、401 和内容策略拒绝。
真实 Provider 的连通性检查只能由操作者明确触发一次低成本请求,并核对审计与冷却;不能作为自动测试或反复调试方式。
本地 mock 协议服务可单独启动,用于 curl 或协议调试。Windows 推荐通过受管启动脚本运行:
```bat
scripts\chorus-dev.bat start-mock
```
脚本默认读取仓库外的 `D:\OPC\chorus-tools\chorus-test.env.ps1`,也可把其他可信环境文件作为带引号的第二个参数传入。`start-mock` 只启动 mock 协议服务,不启动 worker,也不会放宽生产安全 Client 的 SSRF 规则。进程状态与日志保存在 `%LOCALAPPDATA%\Chorus\dev`。
前台调试仍可直接运行:
```powershell
$env:CHORUS_MOCK_ADDR = "127.0.0.1:18080"
go run ./cmd/chorus-mock-provider
```
生产安全 Client 始终拒绝回环/私网 Provider URL。自动化端到端测试使用构造公共域名解析和受控 `DialContext` 把请求送到进程内 mock,不应为了让 portal 直连本机 mock 而放宽 SSRF 规则。
种子密码从 #11 起保存为 `bcrypt:v1:<hash>`。已有无版本构造哈希不会被登录兼容;使用同一构造邮箱和本次选择的密码重新运行 `go run ./cmd/chorus-seed`,会更新密码哈希而不新增用户。开发/测试未提供 session key 时进程随机生成临时 key,重启后会话失效;生产必须显式提供至少 32 字节且与 Provider 主密钥分离的 key。
### 5. 启动 portal(MVP-0)
Windows 本地开发优先使用仓库自带脚本:
```bat
scripts\chorus-dev.bat start
scripts\chorus-dev.bat status
scripts\chorus-dev.bat restart
scripts\chorus-dev.bat stop
```
`start` 会校验环境文件、本地 MySQL TCP 连接和监听端口,构建临时可执行文件,在后台启动 portal,并检查 `/login`。重复启动不会创建第二个 portal;`restart` 只重启 portal;`stop` 只停止由脚本记录且可执行文件路径匹配的 portal 和 mock 进程。脚本不会执行迁移、GORM AutoMigrate、seed 或创建测试账号。
脚本默认读取仓库外的 `D:\OPC\chorus-tools\chorus-test.env.ps1`;也可把其他可信环境文件作为带引号的第二个参数传入,例如 `scripts\chorus-dev.bat start "D:\tools\chorus local.env.ps1"`。日志、PID 状态和临时可执行文件位于 `%LOCALAPPDATA%\Chorus\dev`。
需要在当前终端观察输出时,仍可运行:
```powershell
go run ./portal
```
浏览器验证:
1. 用种子用户登录,不应出现公开注册链接;
2. 桌面双栏、375px 移动单列均可完成文本和图片提交;
3. 第一张图自动 primary,其余 reference,MVP-0 没有角色编辑/拖拽;
4. 提交后立即显示 queued/running,终态停止轮询;
5. 文本、图片、失败、网络轮询错误和认证过期都有明确界面;
6. 数据库中 `rendered_prompt`、`attempts`、latency 或失败 error 字段齐全;
7. 输出通过鉴权访问,不能猜 URL 读取其他用户文件。
## 常用调试方式
| 症状 | 先看 |
|---|---|
| pending 不动 | worker 是否启动、取任务索引和数据库版本 |
| running 租约过期 | lease_owner/token/until、上游超时、陈旧 worker CAS |
| 成功但内容不对 | rendered_prompt 与 prompt template 版本 |
| 400/401 尝试多家 | retryable 实现错误 |
| 文件存在但页面 403/404 | 用户归属、原子落位、缩略图记录 |
| 上游连不上 | SSRF 日志、DNS/IPv6、redirect、proxy 和 base_url |
| 管理端生成异常 | 是否先迁移再导表,是否误把菜单脚本当业务 DDL |
| 样式丢失 | portal Tailwind 产物或 admin-ui pnpm/Vue CLI 构建 |
不要在日志、SQL、工单或 Wiki 中贴 API Key、Cookie、真实用户输入和生产文件。
## 测试策略
- 迁移:空 MySQL 8 隔离库逐个/全量 up-down-up,验证索引、外键、唯一约束和种子幂等。
- 队列:并发认领、租约过期重新认领、旧 token 最终写 0 行、优雅退出、attempt 原子性。从 #9 起该项必须连接迁移后的隔离 MySQL 8,mock SQL 不能替代。
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./internal/core/queue/...
go test -race -count=1 ./internal/core/queue/...
$env:CHORUS_TEST_DSN = $null
```
Provider/worker 的 mock 协议、真实 MySQL 8 attempt/output 与优雅退出验证:
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./internal/core/provider/... ./internal/platform/http/... ./internal/platform/mockprovider/... ./portal/worker/...
go test -race -count=1 ./...
$env:CHORUS_TEST_DSN = $null
```
- Provider:全部 retryable 类别使用 mock,不消耗真实额度。
- Portal handler:真实 MySQL 8 覆盖登录/退出、session/CSRF 轮换、用户作用域幂等、pending 提交、上传边界、任务与文件跨用户授权、终态和 HTMX 401;Go 集成测试结束后清理本次构造数据。浏览器 E2E 使用固定的合成幂等任务,重复运行不持续新增记录。
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./portal/handler/... ./portal/service/... ./portal/session/... ./portal/auth/...
$env:CHORUS_TEST_DSN = $null
```
portal 静态资源和浏览器 E2E 使用固定锁文件。E2E 账号、密码和地址只通过本机环境变量提供,不写入命令示例、仓库或测试报告:
```powershell
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
$env:CHORUS_E2E_BASE_URL = "http://127.0.0.1:8080"
# 在当前进程安全设置 CHORUS_E2E_EMAIL 与 CHORUS_E2E_PASSWORD
corepack pnpm --dir portal/web test:e2e
```
- SSRF:IPv4/IPv6 私网、DNS rebinding、redirect 链、环境代理、结果 URL。
- 安全:密码/session/CSRF、登录节流、跨用户任务与文件、错误脱敏、密钥轮换。
- 浏览器:HTMX 动态状态、终态停止、认证过期、网络错误;375/768/1024/1440 无主区域横向滚动,并检查键盘、44px 触控目标和 reduced-motion。
- 供应链:Go/Node 依赖锁定、漏洞和许可证检查按实现工单确定命令。
### 管理端后端集成测试(#23)
管理端测试只允许使用精确名称为 `chorus_test` 的可丢弃 MySQL 数据库;测试写入随机后缀的合成 Provider/Model/Route 数据并清理,不调用真实上游。运行:
```powershell
$env:CHORUS_RUN_ADMIN_TESTS = "1"
go -C admin test -count=1 ./...
$env:CHORUS_RUN_ADMIN_TESTS = $null
```
覆盖明文凭据版本切换、列表脱敏、单条读取禁止缓存、普通更新不清除凭据、路由发布版本冲突、JWT/Casbin 路由边界、生产模式账号密码登录(无验证码)、禁用探测不出站,以及探测冷却只预留一次。根模块的 migration up/down/up 回归仍需在可丢弃库执行:`go test -count=1 ./migrations -run TestMVP1MigrationsUpDownUpMySQL`。
## 完成修改前
```powershell
git status --short --branch
go build ./...
go vet ./...
go test ./...
go -C admin build .
go -C admin test ./...
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
corepack pnpm --dir portal/web test:e2e
pnpm --dir admin-ui install --frozen-lockfile
pnpm --dir admin-ui build:prod
python dev_scripts/harness.py check --strict
python -m unittest discover -s tests -v
python dev_scripts/harness.py sync --check
git diff --check
```
只运行受当前范围影响且实际存在的产品命令;不存在或因工具版本不能执行的项写入工单。涉及迁移、安全、队列、权限或 UI 时,还必须执行上面的专项验证并记录结果。
## MVP-0 集成验收记录(2026-08-21)
本次 #13 使用专用可丢弃库验证,不能把示例中的 down 或 fixture 指向现有开发库、共享库或生产库。结论如下:
- MySQL 8.4.8:全量 `up → seed 两次 → down -all → up → seed 两次` 通过;种子稳定为 1 用户、1 Provider、2 ProviderModel、2 Prompt,down 后业务表为 0。
- Go:`go build ./...`、`go vet ./...`、`go test -race -count=1 -p=1 ./...` 通过;MySQL 测试覆盖幂等、跨用户、租约恢复、旧 token CAS、attempts、文本/图片 mock 成功和鉴权文件。
- 浏览器:`pnpm --dir portal/web test:e2e` 在 375/768/1024/1440 通过;1024 额外覆盖 pending、running、文本成功、图片成功、失败终态及终态移除轮询属性。
- 进程:真实内嵌 worker 从隔离库认领 6 条 pending 并形成终态;回环 Provider 被 SSRF 在连接前拒绝;两次前台 `Ctrl+C` 均干净退出。
内嵌 worker 的运行参数:
| 变量 | 非生产默认 | 生产要求 |
|---|---:|---|
| `CHORUS_WORKER_LEASE_SECONDS` | 60 | 必须显式设置,且大于 HTTP 超时 + 5 秒 |
| `CHORUS_WORKER_POLL_MILLISECONDS` | 250 | 必须显式设置 |
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` | 45 | 必须显式设置 |
| `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 33554432 | 必须显式设置 |
`CHORUS_TEST_DISABLE_WORKER=true` 仅供 `CHORUS_ENV=test` 的 Playwright fixture 使用。测试 helper 位于 `portal/web/e2e/fixture`,会按构造用户归属写入受控状态和测试图片;不得打包部署,也不得用于开发或生产数据。mock 上游的自动测试通过依赖注入连接本地 fixture,不允许把回环地址加入生产 SSRF 白名单。
## #24 管理端本地验证(2026-08-22)
```powershell
go -C admin run . server --config admin/config/settings.yml
$env:VUE_APP_BASE_API = "http://127.0.0.1:8090"
pnpm --dir admin-ui dev
```
管理端不会创建数据库、运行 migration、seed 或 AutoMigrate。首次本地运行先人工创建空库并执行 `migrate -path migrations ... up`,再通过 `chorus-admin-bootstrap` 建立管理员。已验证纯账号密码登录、动态 Chorus 菜单和 Provider 列表 API;Casbin 使用 `sys_casbin_rule`。前端固定基线默认端口为 9527,端口占用时 Vue CLI 会选择下一可用端口。
## MVP-2 设计中的配置门禁(#36,尚未实现)
生产实现将增加三组显式正整数配置,分别控制用户提交、单 API Key 总请求和单 Provider 上游 attempt 的容量与窗口。生产环境缺少任何一项都必须启动失败;开发/测试 fixture 显式给值,不把测试值冒充生产阈值。具体环境变量名在实现工单中按现有 `CHORUS_*` 命名固定,并同步本页和部署页。
当前部署边界仍为单 portal 进程内嵌 worker。MVP-2 限流状态在进程内共享,重启会重置窗口;在共享限流存储设计和验证完成前,不得把多个 portal 实例当作受支持部署。Provider 限流与延后队列只允许用 mock 上游验证。
## 统一启动 Portal、Admin API 和 Admin UI(#38)
默认配置文件为 `config/local-services.yml`:
```yaml
schema_version: 1
portal:
host: 127.0.0.1
port: 8080
environment_file: ../../chorus-tools/chorus-test.env.ps1
admin:
settings_file: ../admin/config/settings.yml
admin_ui:
host: 127.0.0.1
port: 9528
```
相对路径以 `local-services.yml` 所在目录为基准。配置严格拒绝未知字段、非 loopback Host、无效/重复端口、缺失文件或错误扩展名。`chorus-local-config` 读取 Admin settings 时只投影 `settings.application.host/port`,不会输出其他配置内容。Admin Host/Port 不在统一配置重复保存,避免两个事实源漂移。
从仓库根目录执行:
```bat
scripts\start-all.bat
scripts\status-all.bat
scripts\stop-all.bat
```
也可把另一份不含凭据的统一配置作为第一个参数,例如 `scripts\start-all.bat "D:\tools\chorus-services.yml"`。`start-all` 的顺序为 Portal → Admin API → Admin UI;Admin 被编译到 `%LOCALAPPDATA%\Chorus\all\bin`,Admin UI 复用已安装的锁定依赖。缺少 `admin-ui/node_modules` 时脚本停止并提示人工执行锁文件安装,不自动联网安装。
状态和日志位于 `%LOCALAPPDATA%\Chorus\all`,Portal 继续使用 `%LOCALAPPDATA%\Chorus\dev`。`status-all` 区分 `managed running`、`stopped` 与 `unmanaged listener`。`stop-all` 只停止 PID 与可执行路径同时匹配的受管进程,不停止端口上的旧进程;若配置端口已被其他项目或手工进程占用,脚本会拒绝接管;修改统一配置选择空闲端口,或由该进程的所有者自行停止。默认使用 9528,避免与本机已有 9527 服务冲突。统一脚本不执行 migration、AutoMigrate、seed、账号创建或密码重置。
生产仍不得运行 Vue 开发服务器。Admin UI 构建为静态文件,由反向代理与 Portal/Admin API 组合为单一外部 Host。
+354 -136
@@ -1,178 +1,396 @@
# 项目档案
# 本地开发与验证
本页记录不经常变化、所有维护者都需要知道的信息。Wiki 初始化完成后,Wiki 的 `Project-Profile` 是事实来源,本文件是只读镜像。
本页区分“现在可执行的文档流程”和“产品代码落地后可执行的 Go/前端/数据库流程”。代码不存在或本机版本不满足时必须如实记录,不把预期当作通过。
## 文档状态
## 环境要求
线上 Gitea Wiki 已于 2026-08-20 初始化,15 个核心页面均已创建并回读确认。本页事实来源是 Wiki 的 `Project-Profile`,仓库中的 `docs/00-project-profile.md` 是只读镜像。长期文档的修改顺序固定为:修改 Wiki → 读取确认 → 导出 `docs/` → 校验差异 → 提交镜像。不要直接编辑带 `generated: true` 头的本地文件。
| 项 | 目标要求 | 检查命令 |
|---|---|---|
| Git | 近期版本 | `git --version` |
| Python | Python 3 标准库 | `python --version` |
| Go | **1.26.5** | `go version` |
| MySQL | 8.0,支持 SKIP LOCKED | `mysql --version` |
| golang-migrate | 可执行文件 | `migrate -version` |
| Node | >=22,用于重建 portal/web 与 admin-ui 静态资源 | `node --version` |
| pnpm | 9.15.1,由 packageManager/Corepack 固定 | `pnpm --version` |
| Tailwind | 4.3.3,由 portal/web 锁文件固定 | `pnpm --dir portal/web exec tailwindcss --help` |
## 基本信息
2026-08-20 已完成 MVP-0 环境基线:便携 Go 1.26.5、golang-migrate v4.19.1 和隔离 MySQL 8.4.8 已验证。Chorus 专用实例监听 `127.0.0.1:3308`,使用独立数据目录和测试账号;既有 `3307` 实例与 MySQL 5.7 不受影响。Node 22.22.1 可用;admin-ui 后续仍必须按 `packageManager` 使用 pnpm 9.15.1,不能直接采用全局 pnpm 11。
| 项目 | 内容 |
固定来源:
- `D:\github\goadmin\go-admin` @ `f06540883b41d03782bb6b2c4150f298f328c6b6`
- `D:\github\goadmin\go-admin-ui` @ `67d393d713877572fab0b897296a4c1d525fc81d`
- `D:\github\goadmin\go-admin-doc` @ `424855aacf6905f3fde860c3331385cb25529a0d`
只从这些固定提交审查/导入,不直接在其工作区开发 chorus,也不让 chorus 构建依赖绝对路径。
主要环境变量:
| 变量 | 用途 |
|---|---|
| 项目名称 | chorus |
| 一句话目标 | 提供独立于 cmhub 的 Go 生图生文服务:用户提交提示词与原图得到新图或文本,运营在管理端配置上游并查看记录 |
| 主要使用者 | 终端用户(Web 端生成)、运营与管理员(管理端)、Claude/Codex Agent、接手简单维护的初级程序员 |
| Gitea 地址 | https://git.ilapage.cn |
| 仓库 | `OPC/chorus` |
| 默认分支 | `main` |
| 主要维护者 | `ila` |
| 需求来源 | Obsidian 笔记《cmgen · 从 cmhub 抽取生图生文服务 — 需求与 Go 技术方案》(2026-08-20);项目代号由 `cmgen` 改为 `chorus` |
| 文档适用范围 | 默认分支当前版本 |
| `CHORUS_DSN` | Go 应用、种子和管理员 bootstrap 命令使用的 GORM/MySQL DSN |
| `CHORUS_ADMIN_USERNAME` | 管理员 bootstrap 的目标账号;部署配置设为 `admin` |
| `CHORUS_ADMIN_PASSWORD` | 管理员 bootstrap 密码;只在当前受保护进程环境中提供 |
| `CHORUS_ADMIN_RESET_PASSWORD` | 仅设为 `1` 时允许 bootstrap 重置已有管理员密码 |
| `CHORUS_MIGRATE_URL` | golang-migrate 专用 MySQL URL |
| `CHORUS_SEED_USER_EMAIL` / `CHORUS_SEED_USER_PASSWORD` | 构造种子用户;仅通过环境注入 |
| `CHORUS_SEED_PROVIDER_BASE_URL` | mock Provider 基础 URL;不得指向生产 |
| `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES` | 默认不设置或 `0`,管理端拒绝连通性探测;只有人工授权真实探测时才设为 `1` |
| `CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS` | 探测已授权时的正整数冷却;未授权时不需要 |
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` / `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 探测已授权时的正整数安全 HTTP 限制 |
| `CHORUS_SESSION_KEY` | portal 会话密钥;不得与数据库、JWT 或其他凭据复用 |
| `CHORUS_STORAGE_ROOT` | 受保护生成物目录 |
| `CHORUS_ENV` | `development/test/production`,生产强制安全开关 |
| `CHORUS_SESSION_TTL_MINUTES` | 会话有效分钟数;生产必填 |
| `CHORUS_LOGIN_ATTEMPTS` / `CHORUS_LOGIN_WINDOW_SECONDS` | 单远端地址登录窗口限制;生产必填 |
| `CHORUS_MAX_PROMPT_BYTES` | prompt UTF-8 字节上限;生产必填 |
| `CHORUS_MAX_IMAGES` | 单任务图片数量上限;生产必填 |
| `CHORUS_MAX_IMAGE_BYTES` / `CHORUS_MAX_UPLOAD_BYTES` | 单图/整次 multipart 字节上限;生产必填 |
| `CHORUS_MAX_IMAGE_PIXELS` | 单图解码像素上限;生产必填 |
| `CHORUS_HISTORY_LIMIT` | 单次历史查询上限;生产必填 |
| `GITEA_TOKEN` | Wiki/工单操作,不影响产品服务 |
## DevHarness 来源与基线
## 第一次运行
| 项目 | 内容 |
### 1. 文档与工作区
```powershell
git status --short --branch
python dev_scripts/harness.py check --strict
python -m unittest discover -s tests -v
$env:GITEA_URL = "https://git.ilapage.cn"
python dev_scripts/harness.py sync --check
```
### 2. 进入固定工具环境并连接隔离数据库
工具和凭据都位于仓库外。以下占位路径由维护者替换为自己的本机工具目录;环境文件不得提交,也不得把展开后的 DSN 输出到终端、日志、工单或 Wiki。
```powershell
$tools = "<本机工具目录>"
$env:PATH = "$tools\go1.26.5\bin;$tools\migrate-v4.19.1;$env:PATH"
. "$tools\chorus-test.env.ps1"
go version
migrate -version
mysql --version
```
当前已验证变量包括 `CHORUS_MYSQL_HOST`、`CHORUS_MYSQL_PORT`、`CHORUS_MYSQL_DATABASE`、`CHORUS_MYSQL_USER`、`CHORUS_MYSQL_PASSWORD`、`CHORUS_DB_DSN` 和 `CHORUS_MIGRATE_URL`。应用使用 GORM DSN,迁移工具使用单独验证过的 MySQL URL,不在两种格式之间临时拼接。
专用实例的可复制启动和停止方式如下;`my.ini` 必须绑定 `127.0.0.1`,并指定独立端口、数据目录、日志和 PID 文件。
```powershell
$mysqlHome = "<MySQL 8 安装目录>"
$instance = "<Chorus MySQL 实例目录>"
Start-Process -FilePath "$mysqlHome\bin\mysqld.exe" `
-ArgumentList "--defaults-file=$instance\my.ini", "--console" `
-WindowStyle Hidden
$env:MYSQL_PWD = $env:CHORUS_MYSQL_ROOT_PASSWORD
& "$mysqlHome\bin\mysqladmin.exe" --protocol=tcp `
--host=127.0.0.1 --port=3308 --user=root shutdown
$env:MYSQL_PWD = $null
```
能力探测使用构造查询,不创建业务表:
```powershell
$env:MYSQL_PWD = $env:CHORUS_MYSQL_PASSWORD
mysql --protocol=tcp --host=$env:CHORUS_MYSQL_HOST `
--port=$env:CHORUS_MYSQL_PORT --user=$env:CHORUS_MYSQL_USER `
--database=$env:CHORUS_MYSQL_DATABASE `
--execute="START TRANSACTION; SELECT id FROM (SELECT 1 AS id) AS capability_probe FOR UPDATE SKIP LOCKED; ROLLBACK;"
$env:MYSQL_PWD = $null
```
#6 已提供首个迁移和幂等种子命令。只在明确的隔离库执行:
```powershell
migrate -path migrations -database $env:CHORUS_MIGRATE_URL up
$env:CHORUS_SEED_USER_EMAIL = "mvp0-test@chorus.invalid"
$env:CHORUS_SEED_USER_PASSWORD = "<本次构造密码>"
$env:CHORUS_SEED_PROVIDER_BASE_URL = "<mock 上游 URL>"
go run ./cmd/chorus-seed
go run ./cmd/chorus-seed
migrate -path migrations -database $env:CHORUS_MIGRATE_URL down -all
migrate -path migrations -database $env:CHORUS_MIGRATE_URL up
```
两次种子执行后应仍是一个构造用户、一个 mock Provider、两个 ProviderModel 和两个 Prompt。`down -all` 会删除隔离库业务数据,只能用于明确可丢弃的测试库。`chorus_codegen` 只在 MVP-1 的 go-admin 结构研究/代码生成工单中另行创建,不提前扩大当前账号权限。
### 管理员 bootstrap(#28)
`go run ./cmd/chorus-admin-bootstrap` 只在 #20 的迁移及 `chorus_operator` 角色种子已完成后运行。它不执行迁移、AutoMigrate、seed,也不接受命令行参数。账号只创建一次;发现同名账号时会检查账号启用、未软删除且角色仍为 `chorus_operator`,不满足就失败关闭而不修改数据。
```powershell
$env:CHORUS_ADMIN_USERNAME = "admin"
# 仅在当前受保护终端设置 CHORUS_ADMIN_PASSWORD;不得写入脚本、仓库、日志或文档。
go run ./cmd/chorus-admin-bootstrap
```
第二次运行不会改变密码。只有经人工确认需要重置时,才在同一受保护终端临时设定 `CHORUS_ADMIN_RESET_PASSWORD=1` 后运行;完成后移除该变量。命令只输出创建、保持或显式重置的结果,不回显 DSN、密码或 bcrypt hash。
集成测试使用运行时生成的构造账号与密码,并要求显式的 `CHORUS_RUN_ADMIN_BOOTSTRAP_TESTS=1` 和指向隔离库的 `CHORUS_ADMIN_BOOTSTRAP_TEST_DSN`。测试结束会删除构造账号;不得把该变量指向开发共享库或生产库。
### 启动管理端后端(#23)
先完成 migrations 和管理员 bootstrap。把 `admin/config/settings.example.yml` 复制到仓库外的受保护位置,并在其中设置独立 JWT secret 与数据库连接;示例文件不得填入真实值。确认数据库已执行全部 migrations,并使用受保护的 settings 文件启动:
```powershell
go -C admin run . server --config "<受保护 settings.yml 路径>"
```
该命令只启动 go-admin 管理 API,不执行 migration、seed 或 AutoMigrate,也不注册验证码、代码生成、Swagger、WebSocket 或静态文件路由。`POST /api/v1/login` 在 `prod` 与 `dev` 都只接收必填的 `username`、`password`,不需要验证码字段。管理 API 使用 `Authorization: Bearer <JWT>`;不要在 URL 或 Cookie 放置 token。连通性探测默认返回 403 且不出站。只有经过人工批准的单次真实检查,才在本次受保护进程设置 `CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES=1`,并同时提供正数 `CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS`、`CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` 和 `CHORUS_PROVIDER_MAX_RESPONSE_BYTES`。
### 3. go-admin schema-first 生成(MVP-1)
1. 先在 `chorus_codegen` 执行同一套版本化 SQL。
2. 启动仅限本机的 admin/admin-ui 开发实例。
3. 从数据库表列表导入业务表元数据,配置并预览生成结果。
4. 生成 Go/Vue 文件后检查输出路径、字段类型、敏感字段、权限和重复代码。
5. 不直接采用“生成迁移脚本”或 `/gen/todb` 的副作用;把菜单/API 配置整理成带 down 的 SQL。
6. 清空隔离库,从 migrations 重放并重新生成/构建。
7. 生产构建验证 dev-tools 路由不存在;仅隐藏菜单不算关闭。
go-admin AutoMigrate 不进入以上标准流程。如确需对照固定提交初始化结构,人工在额外可丢弃库运行一次,导出结构差异后销毁;禁止连接共享/生产库,也禁止把该命令放入应用启动、容器入口或部署脚本。
### 4. 配置 mock 上游与种子用户(MVP-0)
MVP-0 不开放注册。使用可重复、不会在日志输出密钥的种子命令建立测试用户、默认 prompt template、Provider 和 ProviderModel。默认先连接 mock HTTP 上游,覆盖文本、图片、429、5xx、超时、连接错误、400、401 和内容策略拒绝。
真实 Provider 的连通性检查只能由操作者明确触发一次低成本请求,并核对审计与冷却;不能作为自动测试或反复调试方式。
本地 mock 协议服务可单独启动,用于 curl 或协议调试。Windows 推荐通过受管启动脚本运行:
```bat
scripts\chorus-dev.bat start-mock
```
脚本默认读取仓库外的 `D:\OPC\chorus-tools\chorus-test.env.ps1`,也可把其他可信环境文件作为带引号的第二个参数传入。`start-mock` 只启动 mock 协议服务,不启动 worker,也不会放宽生产安全 Client 的 SSRF 规则。进程状态与日志保存在 `%LOCALAPPDATA%\Chorus\dev`。
前台调试仍可直接运行:
```powershell
$env:CHORUS_MOCK_ADDR = "127.0.0.1:18080"
go run ./cmd/chorus-mock-provider
```
生产安全 Client 始终拒绝回环/私网 Provider URL。自动化端到端测试使用构造公共域名解析和受控 `DialContext` 把请求送到进程内 mock,不应为了让 portal 直连本机 mock 而放宽 SSRF 规则。
种子密码从 #11 起保存为 `bcrypt:v1:<hash>`。已有无版本构造哈希不会被登录兼容;使用同一构造邮箱和本次选择的密码重新运行 `go run ./cmd/chorus-seed`,会更新密码哈希而不新增用户。开发/测试未提供 session key 时进程随机生成临时 key,重启后会话失效;生产必须显式提供至少 32 字节且与 Provider 主密钥分离的 key。
### 5. 启动 portal(MVP-0)
Windows 本地开发优先使用仓库自带脚本:
```bat
scripts\chorus-dev.bat start
scripts\chorus-dev.bat status
scripts\chorus-dev.bat restart
scripts\chorus-dev.bat stop
```
`start` 会校验环境文件、本地 MySQL TCP 连接和监听端口,构建临时可执行文件,在后台启动 portal,并检查 `/login`。重复启动不会创建第二个 portal;`restart` 只重启 portal;`stop` 只停止由脚本记录且可执行文件路径匹配的 portal 和 mock 进程。脚本不会执行迁移、GORM AutoMigrate、seed 或创建测试账号。
脚本默认读取仓库外的 `D:\OPC\chorus-tools\chorus-test.env.ps1`;也可把其他可信环境文件作为带引号的第二个参数传入,例如 `scripts\chorus-dev.bat start "D:\tools\chorus local.env.ps1"`。日志、PID 状态和临时可执行文件位于 `%LOCALAPPDATA%\Chorus\dev`。
需要在当前终端观察输出时,仍可运行:
```powershell
go run ./portal
```
浏览器验证:
1. 用种子用户登录,不应出现公开注册链接;
2. 桌面双栏、375px 移动单列均可完成文本和图片提交;
3. 第一张图自动 primary,其余 reference,MVP-0 没有角色编辑/拖拽;
4. 提交后立即显示 queued/running,终态停止轮询;
5. 文本、图片、失败、网络轮询错误和认证过期都有明确界面;
6. 数据库中 `rendered_prompt`、`attempts`、latency 或失败 error 字段齐全;
7. 输出通过鉴权访问,不能猜 URL 读取其他用户文件。
## 常用调试方式
| 症状 | 先看 |
|---|---|
| DevHarness 来源仓库 | `https://git.ilapage.cn/OPC/dev_harness` |
| 当前基线提交 | `3696663781c569c57f47bb26e3b5b6369180fdaa` |
| 最后接入或升级日期 | 2026-08-20 |
| 项目适配说明 | 完整保留 Harness 规则、工单模板、Wiki 镜像与结构检查工具;`docs/00`、`02`–`06`、`09` 和 `docs/README.md` 改写为 chorus 内容,`docs/01`、`07`、`08`、`delivery/`、`templates/` 沿用上游文本 |
| pending 不动 | worker 是否启动、取任务索引和数据库版本 |
| running 租约过期 | lease_owner/token/until、上游超时、陈旧 worker CAS |
| 成功但内容不对 | rendered_prompt 与 prompt template 版本 |
| 400/401 尝试多家 | retryable 实现错误 |
| 文件存在但页面 403/404 | 用户归属、原子落位、缩略图记录 |
| 上游连不上 | SSRF 日志、DNS/IPv6、redirect、proxy 和 base_url |
| 管理端生成异常 | 是否先迁移再导表,是否误把菜单脚本当业务 DDL |
| 样式丢失 | portal Tailwind 产物或 admin-ui pnpm/Vue CLI 构建 |
## 子项目与交付单元
不要在日志、SQL、工单或 Wiki 中贴 API Key、Cookie、真实用户输入和生产文件。
| 子项目 / 交付单元 | 职责 | 技术栈与版本 | 构建与测试 | 发布方式 | 共享边界 |
|---|---|---|---|---|---|
| `internal/core` 核心域库 | 领域模型、provider 协议抽象、生成编排、队列状态、加密接口 | Go 1.26.5、GORM、标准库 | `go test ./internal/core/...` | 不单独发布 | **不得 import Gin、go-admin、gobreaker 或 imaging** |
| `internal/platform` 基础设施适配 | 出站 HTTP/SSRF、熔断、图片处理、存储等接口实现 | 标准库、sony/gobreaker、disintegration/imaging | `go test ./internal/platform/...` | 随服务二进制发布 | 通过接口注入 core,不反向污染领域层 |
| `portal` 用户端二进制 | 会话、页面/HTMX 片段、JSON API,MVP 阶段内嵌 worker | Gin、html/template、HTMX、Alpine、Tailwind | `pnpm --dir portal/web build`、`go test ./portal/...`、浏览器 E2E | 单二进制 | 依赖 core 与 platform;提交链路不得调用上游 |
| `admin` 管理端后端 | Provider、模型、Prompt、路由池、生成记录与终端用户只读查询 | 固定提交的 go-admin(Gin、GORM、Casbin、JWT) | `go -C admin test ./...` | 独立二进制 | 嵌套 Go module;依赖 core;不得复制生成逻辑;项目不包含点数、余额或配额 |
| `admin-ui` 管理端前端 | go-admin-ui CRUD 与少量定制页 | Vue 3.5.41、Element Plus 2.14.4、Vue CLI 5.0.9 | `pnpm install --frozen-lockfile`、`pnpm build:prod` | 静态产物 | 代码生成器只在隔离开发环境使用 |
| `migrations` 数据库迁移 | 业务表、`sys_*` 基线和菜单/API 配置的全部生产演进 | golang-migrate SQL | up/down 隔离库验证 | 随版本发布 | **生产数据库结构的唯一事实来源** |
## 测试策略
管理员 `sys_user` 与终端用户 `users` 分表。MVP-0 只要求 core、platform、portal 和必要迁移可运行;admin/admin-ui 在 MVP-1 接入,但生产所需 `sys_*` 初始结构和配置仍必须先转成版本化 SQL。任何构建、CI 或部署都不得依赖 `D:\github\goadmin` 的绝对路径。
- 迁移:空 MySQL 8 隔离库逐个/全量 up-down-up,验证索引、外键、唯一约束和种子幂等。
- 队列:并发认领、租约过期重新认领、旧 token 最终写 0 行、优雅退出、attempt 原子性。从 #9 起该项必须连接迁移后的隔离 MySQL 8,mock SQL 不能替代。
#23 已把固定 `go-admin` 的所需后端源导入 `admin/` 这个嵌套 Go module,并把内部 import 改为仓库模块路径。运行入口是 `admin/cmd/server.go`;生产仅保留 `chorus-admin server --config <受保护配置>`,不导入原项目的 `cmd/migrate`、代码生成、Swagger、WebSocket 或静态文件路由。该入口只连接已经迁移的数据库,不执行 AutoMigrate、迁移或 seed。
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./internal/core/queue/...
go test -race -count=1 ./internal/core/queue/...
$env:CHORUS_TEST_DSN = $null
```
## 技术栈与运行环境
Provider/worker 的 mock 协议、真实 MySQL 8 attempt/output 与优雅退出验证:
### 固定来源基线
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./internal/core/provider/... ./internal/platform/http/... ./internal/platform/mockprovider/... ./portal/worker/...
go test -race -count=1 ./...
$env:CHORUS_TEST_DSN = $null
```
| 项目 | 本地审查来源 | 固定提交 | 关键约束 |
|---|---|---|---|
| go-admin | `D:\github\goadmin\go-admin` | `f06540883b41d03782bb6b2c4150f298f328c6b6` | `go.mod` 要求 Go 1.26.5 |
| go-admin-ui | `D:\github\goadmin\go-admin-ui` | `67d393d713877572fab0b897296a4c1d525fc81d` | Vue 3.5.41、Element Plus 2.14.4、Vue CLI 5.0.9、Node >=22、`packageManager=pnpm@9.15.1` |
| go-admin-doc | `D:\github\goadmin\go-admin-doc` | `424855aacf6905f3fde860c3331385cb25529a0d` | 只作固定版本说明参考 |
- Provider:全部 retryable 类别使用 mock,不消耗真实额度。
- Portal handler:真实 MySQL 8 覆盖登录/退出、session/CSRF 轮换、用户作用域幂等、pending 提交、上传边界、任务与文件跨用户授权、终态和 HTMX 401;Go 集成测试结束后清理本次构造数据。浏览器 E2E 使用固定的合成幂等任务,重复运行不持续新增记录。
这些路径用于审查和导入来源,不是运行依赖。首次接入必须把需要的脚手架/前端代码纳入 chorus 仓库或把 Go 依赖锁定到可复现版本,并在实现工单记录来源提交、导入范围和本地修改。
```powershell
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
go test -v -count=1 ./portal/handler/... ./portal/service/... ./portal/session/... ./portal/auth/...
$env:CHORUS_TEST_DSN = $null
```
### 运行栈
portal 静态资源和浏览器 E2E 使用固定锁文件。E2E 账号、密码和地址只通过本机环境变量提供,不写入命令示例、仓库或测试报告:
| 部分 | 技术 | 说明 |
|---|---|---|
| Go | 1.26.5 | 以固定 go-admin 的 `go.mod` 为最低统一版本,不再使用原文档的 Go 1.22+ |
| HTTP | Gin | portal 与 admin 共用生态,但 core 不依赖 Gin |
| ORM 与迁移 | GORM + golang-migrate | GORM 负责运行期持久化;生产禁止 AutoMigrate |
| 数据库 | MySQL 8.0+;当前开发验收为 8.4.8 | 单库;队列依赖 `SELECT … FOR UPDATE SKIP LOCKED` |
| 队列 | MySQL 租约 | 不引入 Redis/MQ;租约 token + CAS 防止陈旧 worker 提交 |
| 管理端 | 固定 go-admin + go-admin-ui | schema-first 生成 CRUD;生产关闭 dev-tools |
| 用户端会话 | alexedwards/scs | Cookie 会话,不复用管理员 JWT |
| 平台适配 | gobreaker、imaging | 只能位于 `internal/platform` |
| 限流 | ulule/limiter | MVP-2 用户维度令牌桶 |
| 上游调用 | `net/http` 适配器 | 支持多图、`extra_body`、URL/Base64 差异,并实施 SSRF 钩子 |
| 用户端 UI | html/template + HTMX 2.x + Alpine 3.x + Tailwind 4.x | portal 运行时不需要 Node;重建 portal/web 与 admin-ui 静态资源需要 Node/pnpm |
| 开发环境 | Windows + PowerShell + Git;本机隔离 MySQL 8 | Harness 使用 Python 3 标准库;Chorus 专用实例使用回环地址和独立端口/数据目录 |
```powershell
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
$env:CHORUS_E2E_BASE_URL = "http://127.0.0.1:8080"
# 在当前进程安全设置 CHORUS_E2E_EMAIL 与 CHORUS_E2E_PASSWORD
corepack pnpm --dir portal/web test:e2e
```
### 已验证的 MVP-0 本机基线(2026-08-20)
- SSRF:IPv4/IPv6 私网、DNS rebinding、redirect 链、环境代理、结果 URL。
- 安全:密码/session/CSRF、登录节流、跨用户任务与文件、错误脱敏、密钥轮换。
- 浏览器:HTMX 动态状态、终态停止、认证过期、网络错误;375/768/1024/1440 无主区域横向滚动,并检查键盘、44px 触控目标和 reduced-motion。
- 供应链:Go/Node 依赖锁定、漏洞和许可证检查按实现工单确定命令。
- Go 使用官方 Windows amd64 压缩包固定为 1.26.5;SHA-256 为 `97e6b2a833b6d89f9ff17d25419ac0a7e3b482a044e9ab18cdef834bd834fd38`。便携工具目录加入当前开发 Shell 的 `PATH`,不替换机器已有 Go。
- `golang-migrate` 固定为 v4.19.1;Windows amd64 发布资产 SHA-256 为 `d2537dfd991787c1e458965c4f49098c5a72f943bfc9d975c573a9c245f7ba2e`。
- 开发数据库复用本机 MySQL 8.4.8 二进制,但使用 Chorus 专用数据目录、`127.0.0.1:3308` 和独立账号/库;现有 `3307` 实例和 MySQL 5.7 服务均不修改。
- 本机凭据由仓库外、仅当前账号可读的 PowerShell 环境文件注入;Wiki、工单、Git 和日志只记录变量名,不记录密码或完整 DSN。
- 已验证实例停止/重启、最小权限连接以及 `SELECT ... FOR UPDATE SKIP LOCKED`。这只证明开发环境可用,不代表生产部署已经验证。
### AutoMigrate 与代码生成结论
### 管理端后端集成测试(#23)
- go-admin 固定提交的 `cmd/migrate` 和模板中确有 AutoMigrate;它适合帮助理解脚手架初始化结构,但不是可审查、可回退的生产迁移。
- 如确需研究该初始化结果,只能由人工在**隔离、可丢弃、无生产数据**的开发数据库中单独运行,并把得到的结构差异整理为 `migrations/*.up.sql` 与 `*.down.sql`。
- go-admin-ui 的生成器先从数据库导入已有表到 `sys_tables/sys_columns`,再生成 Go/Vue 文件;因此标准流程是先执行版本化 SQL,再导入和生成。
- 生成器的“生成迁移脚本”主要写菜单、权限与 API 配置的 Go 迁移代码,并不是业务表 DDL,也没有本项目要求的可逆性和幂等证据;这些配置必须人工转换为可逆 SQL。
- dev-tools 路由包含写文件和写菜单的能力,固定提交中只有 JWT 保护且被 Casbin 排除,生产构建不得注册、代理或暴露这些路由。
管理端测试只允许使用精确名称为 `chorus_test` 的可丢弃 MySQL 数据库;测试写入随机后缀的合成 Provider/Model/Route 数据并清理,不调用真实上游。运行:
## 阅读入口
```powershell
$env:CHORUS_RUN_ADMIN_TESTS = "1"
go -C admin test -count=1 ./...
$env:CHORUS_RUN_ADMIN_TESTS = $null
```
- 新人入口:[Home](Home)。
- 产品需求与 MVP 分期:[产品需求总览](Product-Requirements-Overview.-)。
- 代码入口:[架构与代码地图](Architecture-and-Code-Map.-)。
- 业务边界:[业务规则与术语](Business-Rules-and-Glossary.-)。
- 运行验证:[本地开发与验证](Local-Development-and-Verification.-)。
- 简单维护:[常见修改指南](Common-Changes.-)。
- 错误定位:[故障排查](Troubleshooting)。
覆盖明文凭据版本切换、列表脱敏、单条读取禁止缓存、普通更新不清除凭据、路由发布版本冲突、JWT/Casbin 路由边界、生产模式账号密码登录(无验证码)、禁用探测不出站,以及探测冷却只预留一次。根模块的 migration up/down/up 回归仍需在可丢弃库执行:`go test -count=1 ./migrations -run TestMVP1MigrationsUpDownUpMySQL`。
## 常用命令
## 完成修改前
所有命令默认从仓库根目录执行。Windows 本地开发优先使用 #14 提供的受管启动脚本;脚本只封装已有构建与运行入口,不执行迁移、AutoMigrate 或 seed。
```powershell
git status --short --branch
go build ./...
go vet ./...
go test ./...
go -C admin build .
go -C admin test ./...
corepack pnpm --dir portal/web install --frozen-lockfile
corepack pnpm --dir portal/web build
corepack pnpm --dir portal/web test:e2e
pnpm --dir admin-ui install --frozen-lockfile
pnpm --dir admin-ui build:prod
python dev_scripts/harness.py check --strict
python -m unittest discover -s tests -v
python dev_scripts/harness.py sync --check
git diff --check
```
| 用途 | 命令 | 预期结果 |
|---|---|---|
| 查看工作区 | `git status --short --branch` | 显示分支且没有无关修改 |
| 检查文档结构 | `python dev_scripts/harness.py check --strict` | 输出“DevHarness 检查通过” |
| 运行 Harness 测试 | `python -m unittest discover -s tests -v` | 所有测试通过 |
| 检查核心 Wiki 镜像 | `python dev_scripts/harness.py sync --check` | Wiki 初始化后输出核心镜像一致 |
| 编译 | `go build ./...` | 无错误 |
| 单元测试 | `go test ./...` | 全部通过 |
| 静态检查 | `go vet ./...` | 无输出 |
| 管理端编译与测试 | `go -C admin build .`、`go -C admin test ./...` | 独立 admin module 无错误 |
| 数据库迁移 | `migrate -path migrations -database "$CHORUS_MIGRATE_URL" up` | 迁移版本前进且无错误 |
| 幂等种子 | `go run ./cmd/chorus-seed` | 只输出完成状态,不输出凭据 |
| 启动用户端 | `scripts\chorus-dev.bat start` | 后台启动并检查 `/login`,不执行迁移或 seed |
| 查看本地进程 | `scripts\chorus-dev.bat status` | 显示受管 portal 和 mock 的状态、PID 与日志位置 |
| 停止本地进程 | `scripts\chorus-dev.bat stop` | 只停止脚本记录且路径匹配的进程 |
只运行受当前范围影响且实际存在的产品命令;不存在或因工具版本不能执行的项写入工单。涉及迁移、安全、队列、权限或 UI 时,还必须执行上面的专项验证并记录结果。
## MVP-0 集成验收记录(2026-08-21)
## 目录边界
本次 #13 使用专用可丢弃库验证,不能把示例中的 down 或 fixture 指向现有开发库、共享库或生产库。结论如下:
| 目录 | 职责 | 不应放入 |
|---|---|---|
| `internal/core/` | 领域模型、协议接口、编排与状态规则 | Gin、go-admin、gobreaker、imaging、HTTP handler |
| `internal/platform/` | HTTP/SSRF、熔断、图片、存储等适配器 | 页面、管理端 CRUD、业务状态机复制 |
| `portal/` | 用户端 Gin、模板、静态资源、worker 组装 | 直接实现 provider 选路和上游协议 |
| `admin/` | go-admin 后端及 `app/chorus/` | 绕过 core 的生成实现、生产代码生成路由 |
| `admin-ui/` | 固定 go-admin-ui 导入代码与定制页 | 运行时依赖 `D:\github\goadmin` |
| `migrations/` | 全部生产表结构与配置种子 SQL | 测试数据、AutoMigrate、不可逆一次性修数 |
| `docs/` | 核心长期文档的 Wiki 只读镜像 | 人工直接维护的最终事实 |
| `docs/task/` | 人工按需导出的任务归档快照 | 讨论过程和默认自动导出 |
| `prototypes/` | 按工单/版本保存 HTML 审核快照 | 凭据、个人信息、生产数据 |
| `scripts/` | Chorus 本地开发与运行辅助脚本 | DevHarness 工具、凭据和生产数据 |
| `dev_scripts/` | DevHarness 工具 | 产品业务脚本 |
| `tests/` | Harness 测试;Go 测试与代码同目录 | 生产数据 |
- MySQL 8.4.8:全量 `up → seed 两次 → down -all → up → seed 两次` 通过;种子稳定为 1 用户、1 Provider、2 ProviderModel、2 Prompt,down 后业务表为 0。
- Go:`go build ./...`、`go vet ./...`、`go test -race -count=1 -p=1 ./...` 通过;MySQL 测试覆盖幂等、跨用户、租约恢复、旧 token CAS、attempts、文本/图片 mock 成功和鉴权文件。
- 浏览器:`pnpm --dir portal/web test:e2e` 在 375/768/1024/1440 通过;1024 额外覆盖 pending、running、文本成功、图片成功、失败终态及终态移除轮询属性。
- 进程:真实内嵌 worker 从隔离库认领 6 条 pending 并形成终态;回环 Provider 被 SSRF 在连接前拒绝;两次前台 `Ctrl+C` 均干净退出。
## 环境、配置与凭据
内嵌 worker 的运行参数:
- 核心 Wiki 同步配置是 `wiki-docs.json`;Gitea 地址可用 `GITEA_URL` 覆盖。
- Gitea PAT 仅从进程环境、MCP 安全配置或本机未跟踪的环境文件加载,不写入仓库、工单或 Wiki。
- 数据库连接串由 `CHORUS_DSN` 提供。
- Provider `api_key` 按用户已接受的风险决策明文保存在 `provider_credentials.api_key`;列表和普通详情只返回是否已配置,只有受 JWT/Casbin 保护的单 Provider 凭据接口可显式回显,且响应设置 `Cache-Control: no-store` 与 `Pragma: no-cache`。
- 会话签名/加密密钥仍由受保护配置提供;生产 Cookie 必须启用 `Secure`、`HttpOnly` 和合适的 `SameSite`。Provider 明文密钥不得进入代码、日志、审计、错误、工单、Wiki、原型或截图。
- 生成物默认落受保护的本地目录,不能直接把真实路径暴露为公开静态 URL;访问先校验用户归属。storage 从第一天是接口,预留 S3/OSS。
- 测试只用构造数据与 mock 上游;真实连通性检查必须是管理员明确触发的单次低成本动作,并有审计和冷却。
- 上传数量、单文件/总大小、允许 MIME、像素上限、上游超时、租约时长、保留期和限流阈值均为配置。未确认生产值前保持部署门禁,不由 Agent 臆造。
| 变量 | 非生产默认 | 生产要求 |
|---|---:|---|
| `CHORUS_WORKER_LEASE_SECONDS` | 60 | 必须显式设置,且大于 HTTP 超时 + 5 秒 |
| `CHORUS_WORKER_POLL_MILLISECONDS` | 250 | 必须显式设置 |
| `CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS` | 45 | 必须显式设置 |
| `CHORUS_PROVIDER_MAX_RESPONSE_BYTES` | 33554432 | 必须显式设置 |
## 项目专用验收要求
`CHORUS_TEST_DISABLE_WORKER=true` 仅供 `CHORUS_ENV=test` 的 Playwright fixture 使用。测试 helper 位于 `portal/web/e2e/fixture`,会按构造用户归属写入受控状态和测试图片;不得打包部署,也不得用于开发或生产数据。mock 上游的自动测试通过依赖注入连接本地 fixture,不允许把回环地址加入生产 SSRF 白名单。
- 长期核心文档必须先更新 Wiki,再导出本地镜像;Wiki 初始化未完成前,文档修改直接在 `docs/` 进行并在工单说明。
- `python dev_scripts/harness.py check --strict` 必须通过。
- MVP-0 落地后:`go build ./...`、`go vet ./...`、`go test ./...` 必须通过。
- 涉及 `retryable` 判定、SSRF 校验、密钥加解密和迁移的修改必须有针对性单元测试。
- 未执行或无法覆盖的验证必须记录到工单。
## MVP-0 完成状态(2026-08-21)
## #24 管理端本地验证(2026-08-22)
- #5~#12 和 #14 已由用户验收;#13 集成验收执行中发现 #10 的内嵌 worker 未接入 `portal/main.go`,已用缺陷 #15 恢复既有确认行为。
- 当前候选版本已在本机 MySQL 8.4.8 隔离库完成空库 `up/down/up`、重复种子、Go build/vet/race、真实 portal 进程认领、Playwright 四视口和终态验证。
- 用户于 2026-08-21 明确确认 #4 验收通过;MVP-0 的全部单元任务、独立集成验收和父工单均已完成并归档。Epic #3 保持开启,后续 MVP 必须另行确认。
- 验证只使用构造用户、构造图片、mock 上游与测试 fixture;没有连接生产/共享库、调用真实 Provider 额度或发布生产。
## Provider 凭据风险决策与 #24 已交付实现(2026-08-22)
```powershell
go -C admin run . server --config admin/config/settings.yml
$env:VUE_APP_BASE_API = "http://127.0.0.1:8090"
pnpm --dir admin-ui dev
```
用户已在 #30 明确接受 Provider API Key 明文落库及单条主动查看的风险。#24 已交付实现移除了运行时 `CHORUS_MASTER_KEY` 依赖,迁移 000005 在存在旧加密凭据时停止,禁止猜测或丢弃;列表和普通详情不返回完整密钥,单条读取禁止缓存。管理端使用固定 go-admin/go-admin-ui 基线,Casbin 显式读取迁移管理的 `sys_casbin_rule` 并关闭 adapter AutoMigrate。该实现已通过 MySQL 8、Go、前端构建和真实账号密码/API 联调,并于 2026-08-22 通过用户验收。
管理端不会创建数据库、运行 migration、seed 或 AutoMigrate。首次本地运行先人工创建空库并执行 `migrate -path migrations ... up`,再通过 `chorus-admin-bootstrap` 建立管理员。已验证纯账号密码登录、动态 Chorus 菜单和 Provider 列表 API;Casbin 使用 `sys_casbin_rule`。前端固定基线默认端口为 9527,端口占用时 Vue CLI 会选择下一可用端口。
## #38 统一本地服务启动入口(2026-08-24,待验收)
## MVP-2 设计中的配置门禁(#36,尚未实现)
Windows 本地开发可使用 `config/local-services.yml` 与 `scripts/start-all.bat`、`status-all.bat`、`stop-all.bat` 统一管理 Portal、Admin API 和 Admin UI。统一配置只保存 loopback Host、端口和受保护配置文件路径,不保存或复制 DSN、JWT secret、密码、Token 或 Provider Key。
生产实现将增加三组显式正整数配置,分别控制用户提交、单 API Key 总请求和单 Provider 上游 attempt 的容量与窗口。生产环境缺少任何一项都必须启动失败;开发/测试 fixture 显式给值,不把测试值冒充生产阈值。具体环境变量名在实现工单中按现有 `CHORUS_*` 命名固定,并同步本页和部署页。
Portal 的敏感配置仍来自 `portal.environment_file` 指向的受保护 PowerShell 环境文件,统一配置只覆盖 `CHORUS_LISTEN_ADDRESS`;Admin API 的 Host/Port 和敏感值仍以 `admin.settings_file` 指向的 settings.yml 为唯一事实源;Admin UI Host/Port 来自统一配置,API 基址在启动时由 Admin 地址派生。脚本不会运行 migration、AutoMigrate、seed、管理员 bootstrap 或依赖安装。
当前部署边界仍为单 portal 进程内嵌 worker。MVP-2 限流状态在进程内共享,重启会重置窗口;在共享限流存储设计和验证完成前,不得把多个 portal 实例当作受支持部署。Provider 限流与延后队列只允许用 mock 上游验证。
统一脚本只管理状态文件记录且可执行路径匹配的进程。端口被未受管进程占用时显示 `unmanaged listener` 并拒绝接管;启动中途失败只回滚本次新启动的组件。现有 `scripts/chorus-dev.bat` 继续用于单独管理 Portal 和 mock。
## 统一启动 Portal、Admin API 和 Admin UI(#38)
默认配置文件为 `config/local-services.yml`:
```yaml
schema_version: 1
portal:
host: 127.0.0.1
port: 8080
environment_file: ../../chorus-tools/chorus-test.env.ps1
admin:
settings_file: ../admin/config/settings.yml
admin_ui:
host: 127.0.0.1
port: 9528
```
相对路径以 `local-services.yml` 所在目录为基准。配置严格拒绝未知字段、非 loopback Host、无效/重复端口、缺失文件或错误扩展名。`chorus-local-config` 读取 Admin settings 时只投影 `settings.application.host/port`,不会输出其他配置内容。Admin Host/Port 不在统一配置重复保存,避免两个事实源漂移。
从仓库根目录执行:
```bat
scripts\start-all.bat
scripts\status-all.bat
scripts\stop-all.bat
```
也可把另一份不含凭据的统一配置作为第一个参数,例如 `scripts\start-all.bat "D:\tools\chorus-services.yml"`。`start-all` 的顺序为 Portal → Admin API → Admin UI;Admin 被编译到 `%LOCALAPPDATA%\Chorus\all\bin`,Admin UI 复用已安装的锁定依赖。缺少 `admin-ui/node_modules` 时脚本停止并提示人工执行锁文件安装,不自动联网安装。
状态和日志位于 `%LOCALAPPDATA%\Chorus\all`,Portal 继续使用 `%LOCALAPPDATA%\Chorus\dev`。`status-all` 区分 `managed running`、`stopped` 与 `unmanaged listener`。`stop-all` 只停止 PID 与可执行路径同时匹配的受管进程,不停止端口上的旧进程;若配置端口已被其他项目或手工进程占用,脚本会拒绝接管;修改统一配置选择空闲端口,或由该进程的所有者自行停止。默认使用 9528,避免与本机已有 9527 服务冲突。统一脚本不执行 migration、AutoMigrate、seed、账号创建或密码重置。
生产仍不得运行 Vue 开发服务器。Admin UI 构建为静态文件,由反向代理与 Portal/Admin API 组合为单一外部 Host。
<!-- supervisor-39 -->
## #39 本机 Supervisor 三实例(2026-08-24,待验收)
## 使用 Supervisor 常驻三个本地服务
本机 `D:\supervisor` 通过 `scripts/install-supervisor.bat` 安装并托管三个独立实例:`chorus-user`、`chorus-admin-api`、`chorus-admin-ui`。Portal 和 Admin UI 地址来自 `config/local-services.yml`;Admin API 地址与运行配置来自 `admin/config/settings.yml`。安装器只把构建产物和不含凭据的 program 配置写入 Supervisor 目录,Portal 启动时才在进程内读取受保护环境文件。
本机已安装 `D:\supervisor` 时,从仓库根目录运行:
三个实例均以前台子进程运行,启用自动重启、独立日志和进程组停止。更新代码或统一配置后重新运行安装器,再执行 Supervisor `reload`。此入口只用于当前 Windows 本地环境,不执行 migration、AutoMigrate、seed 或账号创建。
```bat
scripts\install-supervisor.bat
D:\supervisor\supervisord.exe ctl /c D:\supervisor\supervisord.conf reload
D:\supervisor\supervisord.exe ctl /c D:\supervisor\supervisord.conf status
```
安装器构建 Portal、Admin API 和配置解析器到 `D:\supervisor\chorus\bin`,生成 `D:\supervisor\programs\chorus.conf`,并确保主配置包含 `programs/*.conf`。三个实例及配置来源如下:
| Supervisor 实例 | 组件 | 地址来源 |
|---|---|---|
| `chorus-user` | Portal 与内嵌 worker | `config/local-services.yml` 的 `portal` |
| `chorus-admin-api` | Admin API | `admin/config/settings.yml` 的 `settings.application` |
| `chorus-admin-ui` | Vue 开发服务 | `config/local-services.yml` 的 `admin_ui`;API 基址由 Admin 地址派生 |
单个实例可使用 `ctl ... stop <名称>`、`start <名称>` 或 `restart <名称>` 管理。日志位于 `D:\supervisor\logs\chorus-*.log`。更新代码、端口或配置路径后必须重新运行安装器并 `reload`;安装器不复制敏感配置,也不执行迁移、seed、账号创建或依赖安装。