Files
chorus/docs/04-local-development-and-verification.md
T

337 lines
19 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: Local-Development-and-Verification
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Local-Development-and-Verification.-
wiki_revision: 6975232fead63ed60c480fb4bf7c0e9188a430d1
synchronized_at: 2026-08-21T15:02:26Z
<!-- gitea-wiki-mirror:end -->
# 本地开发与验证
本页区分“现在可执行的文档流程”和“产品代码落地后可执行的 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_MASTER_KEY` / 对应 key ring 配置 | Provider Key AES-GCM 解密与轮换;管理端启动也必须提供 |
| `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 会话,必须与主密钥分离 |
| `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 与数据库连接;示例文件不得填入真实值。当前终端安全注入 `CHORUS_MASTER_KEY` 后启动:
```powershell
go -C admin run . server --config "<受保护 settings.yml 路径>"
```
该命令只启动 go-admin 管理 API,不执行 migration、seed 或 AutoMigrate,也不注册代码生成、Swagger、WebSocket 或静态文件路由。管理 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 白名单。