Clone
45
Local-Development-and-Verification
ila edited this page 2026-08-29 21:42:49 +08:00
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.

本地开发与验证

本页区分“现在可执行的文档流程”和“产品代码落地后可执行的 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_USERNAME / CHORUS_SEED_USER_EMAIL / CHORUS_SEED_USER_PASSWORD 构造受控种子用户;Portal 只使用账号登录,值仅通过环境注入
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. 文档与工作区

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。

$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 文件。

$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

能力探测使用构造查询,不创建业务表:

$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 已提供首个迁移和幂等种子命令。只在明确的隔离库执行:

migrate -path migrations -database $env:CHORUS_MIGRATE_URL up

$env:CHORUS_SEED_USER_USERNAME = "mvp0-test"
$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,不满足就失败关闭而不修改数据。

$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 文件启动:

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 推荐通过受管启动脚本运行:

scripts\chorus-dev.bat start-mock

脚本默认读取仓库外的 D:\OPC\chorus-tools\chorus-test.env.ps1,也可把其他可信环境文件作为带引号的第二个参数传入。start-mock 只启动 mock 协议服务,不启动 worker,也不会放宽生产安全 Client 的 SSRF 规则。进程状态与日志保存在 %LOCALAPPDATA%\Chorus\dev。

前台调试仍可直接运行:

$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 本地开发优先使用仓库自带脚本:

scripts\chorus-dev.bat start
scripts\chorus-dev.bat status
scripts\chorus-dev.bat restart
scripts\chorus-dev.bat stop

start 会校验环境文件、portal/config/settings.yml、本地 MySQL TCP 连接和监听端口,构建临时可执行文件,以 --config 显式传入 YAML 后在后台启动 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。

需要在当前终端观察输出时,仍可运行:

go run ./portal --config portal/config/settings.yml

独立运行或 Windows 解压包可直接在 Portal settings 中设置端口:

server:
  listen_address: 127.0.0.1:8080

环境变量 CHORUS_LISTEN_ADDRESS 可覆盖该值;非法 host:port、空 host、端口 0 或超过 65535 会在 HTTP server 启动前被拒绝。

浏览器验证:

  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 和 CHORUS_PROVIDER_ALLOWED_PORTS
管理端生成异常 是否先迁移再导表,是否误把菜单脚本当业务 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 不能替代。
$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 与优雅退出验证:

$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 使用固定的合成幂等任务,重复运行不持续新增记录。
$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 账号、密码和地址只通过本机环境变量提供,不写入命令示例、仓库或测试报告:

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_USERNAME、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 数据并清理,不调用真实上游。运行:

$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。

完成修改前

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 的运行参数:

YAML 字段 环境覆盖变量 示例值与约束
worker.lease_seconds CHORUS_WORKER_LEASE_SECONDS 60;必须大于 HTTP 超时 + 5 秒
worker.poll_milliseconds CHORUS_WORKER_POLL_MILLISECONDS 250;正整数
provider.http_timeout_seconds CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS 45;正整数
provider.max_response_bytes CHORUS_PROVIDER_MAX_RESPONSE_BYTES 33554432;正整数
provider.allowed_ports CHORUS_PROVIDER_ALLOWED_PORTS 默认示例为 80/443;仅显式加入所需公开端口

CHORUS_TEST_DISABLE_WORKER=true 仅供 CHORUS_ENV=test 的 Playwright fixture 使用。测试 helper 位于 portal/web/e2e/fixture,会按构造用户归属写入受控状态和测试图片;不得打包部署,也不得用于开发或生产数据。mock 上游的自动测试通过依赖注入连接本地 fixture,不允许把回环地址加入生产 SSRF 白名单。

#24 管理端本地验证(2026-08-22)

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:

schema_version: 1
portal:
  host: 127.0.0.1
  port: 8080
  environment_file: ../../chorus-tools/chorus-test.env.ps1
  settings_file: ../portal/config/settings.yml
admin:
  settings_file: ../admin/config/settings.yml
admin_ui:
  host: 127.0.0.1
  port: 9528

相对路径以 local-services.yml 所在目录为基准。Portal environment_file 保存数据库、Session 等敏感环境变量;settings_file 保存 server.listen_address、Provider 和 worker 运维参数。监听地址优先级为 CHORUS_LISTEN_ADDRESS 环境变量 > YAML server.listen_address > 默认 127.0.0.1:8080;统一启动脚本仍以 local-services.yml 的 Portal Host/Port 显式覆盖 YAML,确保三实例端口只有一个运行事实源。配置严格拒绝未知字段、非 loopback Host、无效/重复端口、缺失文件或错误扩展名。chorus-local-config 读取 Admin settings 时只投影 settings.application.host/port,不会输出其他配置内容。Admin Host/Port 不在统一配置重复保存,避免两个事实源漂移。

从仓库根目录执行:

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 常驻三个本地服务

本机已安装 D:\supervisor 时,从仓库根目录运行:

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 地址、环境文件和 settings 文件
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、账号创建或依赖安装。

#41 MVP-2 迁移与 API Key 核心验证

普通回归不执行破坏性迁移测试:

go test ./...
go vet ./...
go -C admin test ./...

真实迁移和仓储集成测试必须显式指定一个可丢弃且名称完全匹配的隔离库,并顺序运行,避免迁移 down 与仓储测试争用同一数据库:

$env:CHORUS_MIGRATION_TEST_DATABASE = "chorus_mvp2_test"
$env:CHORUS_RUN_MIGRATION_TESTS = "1"
$env:CHORUS_RUN_MYSQL_TESTS = "1"
go test ./migrations -count=1
go test ./internal/core/apikey -count=1

CHORUS_DSN 与 CHORUS_MIGRATE_URL 必须指向同一个明确创建的可丢弃库。测试会重置目标库结构,严禁指向 chorus、当前开发库、共享库或生产库。#41 已在本机 MySQL 8.4.8 独立库完成迁移 up/down/up、非空数据 down 拒绝、仓储创建/读取/改名/撤销和事务回滚验证。

#42 Portal API Key 验证

普通回归和前端产物构建:

go test ./...
go vet ./...
go -C admin test ./...
pnpm --dir portal/web build

涉及真实仓储与 Handler 的验证必须使用显式命名、可丢弃的 MySQL 8 隔离库,不得指向当前开发库、共享库或生产库。#42 已在独立库覆盖 CSRF、创建后仅一次返回完整 token、数据库 hash、刷新不回显、非法有效期、跨用户 404、停用用户 403、改名和幂等撤销。

浏览器 E2E 使用 portal/web/e2e/fixture 的合成用户和独立库,在 375、768、1024、1440 四种视口执行创建、一次展示、关闭后清除、刷新不可恢复、改名和撤销,并检查无外部请求、无页面横向溢出和至少 44px 的操作目标。测试截图只能在完整 token 已从 DOM 清除后生成。

使用和验证 OpenAPI v1(#43)

完整契约保存在 portal/openapi/openapi.json。运行中的同版本接口在 /openapi/v1/openapi.json 返回该文件;该路径同样要求有效 API Key。不要把真实 Key 写入脚本、命令历史、工单或文档,可在受保护的当前终端临时设置并在完成后清除:

$env:CHORUS_API_KEY = "<本次创建且已妥善保存的 API Key>"
$headers = @{
  Authorization = "Bearer $env:CHORUS_API_KEY"
  "Idempotency-Key" = [guid]::NewGuid().ToString()
}
$body = @{ prompt = "生成一段简短说明" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:8080/openapi/v1/generations/text" `
  -Headers $headers -ContentType "application/json" -Body $body
$env:CHORUS_API_KEY = $null

首次相同请求返回 202;用完全相同的 Header 和正文重放返回 200 和同一 generation id。相同幂等键配不同请求返回 409。图片接口使用 multipart 的 prompt、metadata 和 files[<client_id>];metadata 必须包含 capability、role_rule、images,文件声明与 part 一一对应。详情中的文件 URL 已指向 /openapi/v1,下载仍需同一个 Bearer Header。

普通及契约回归:

go test ./portal/openapi ./portal/service ./portal/handler
go vet ./portal/openapi ./portal/service ./portal/handler
go test ./...

真实 Handler 集成测试必须使用显式命名、可丢弃的 MySQL 8 隔离库。#43 已在 chorus_mvp2_openapi_test 覆盖三条认证链隔离、无效/到期/撤销/停用 Key、JSON/multipart、跨渠道幂等与冲突、游标、跨用户详情和文件访问;测试结束已删除该库,未调用真实 Provider。

用户端账号登录(#50)

执行 000008_portal_username_login 后,历史用户账号为 user_<id>。Portal 登录 JSON 契约固定为 {"account":"<账号>","password":"<密码>"};旧 email 字段返回 400,把邮箱作为 account 返回统一的 401。需要易记账号时,在受保护环境中设置 CHORUS_SEED_USER_USERNAME 后重新执行 seed。生产和常驻启动脚本仍不会自动执行 migration 或 seed。

终端用户密码长度(#51)

当前受控用户的 seed/E2E 密码长度必须为 6 至 1024 个字符。5 个字符及以下会在哈希前被拒绝;6 个字符使用与长密码相同的版本化 bcrypt 编码。该策略不影响管理端账号,未来开放注册或外部用户前必须重新评估。

#44 三维限流本地验证

Portal/worker 新增六个正整数配置:

变量 含义
CHORUS_USER_RATE_LIMIT_CAPACITY 单个终端用户在窗口内可提交的生成请求数
CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS 终端用户固定窗口秒数
CHORUS_API_KEY_RATE_LIMIT_CAPACITY 单个 API Key 在窗口内可发起的已认证 OpenAPI 请求数
CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS API Key 固定窗口秒数
CHORUS_PROVIDER_RATE_LIMIT_CAPACITY 单个 Provider 在窗口内可开始的上游调用数
CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS Provider 固定窗口秒数

CHORUS_ENV=production 时六项必须显式提供且大于 0,缺失或非法会拒绝启动;开发环境默认分别为用户 60/60 秒、API Key 120/60 秒、Provider 60/60 秒。本机 Supervisor 仍从 config/local-services.yml 指向的受保护 Portal 环境文件读取,不把这些值写进仓库配置。

验证命令:

go test ./...
go vet ./...
go build ./...
go test -race -count=1 ./internal/platform/ratelimit ./portal/handler ./portal/worker ./internal/core/queue
go -C admin test ./...
python dev_scripts/harness.py check --strict

MySQL 集成测试需在受控测试库或确认无在途任务的本地开发库中设置 CHORUS_TEST_DSN,运行 go test -count=1 -run TestMySQL ./internal/core/queue ./portal/worker。测试使用 mock Provider,不消耗真实上游额度。限流为进程内固定窗口,重启 portal 会清空计数。

#45 API 安全审计与管理 API 验证

常规验证:

go test ./...
go vet ./...
go test -race -count=1 ./internal/core/apiaudit ./portal/service ./portal/handler
go -C admin test ./...
go -C admin test -race -count=1 ./app/chorus

MySQL 8 集成验证需要受控测试 DSN,且运行 Portal 集成前暂停常驻 worker:

$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
$env:CHORUS_MIGRATION_TEST_DATABASE = $env:CHORUS_MYSQL_DATABASE
go test -count=1 -run '^TestPortalAuthenticationSubmissionAndAuthorization$' ./portal/handler
go -C admin test -count=1 -run '^(TestAdminAPIKeyGovernanceMySQL|TestChorusAPIMySQLRejectsUnauthorizedAndRedactsCredentials)$' ./app/chorus

验证覆盖:生命周期事务审计、未知 Key 不写行、成功/拒绝/429 提交审计、summary 脱敏、last_used_at 节流、管理检索/详情、并发安全的幂等撤销、双审计和 JWT/Casbin 负向路径。管理 API 的真实 go-admin 鉴权失败沿用框架约定,可能返回 HTTP 200 且 JSON code=401;判断时必须同时检查响应 JSON,不能只看 HTTP 状态。

#46 管理端 API 密钥治理页面

管理员登录后,从“Chorus 运营 → API 密钥”进入治理页面。列表仅展示终端用户、密钥名称、固定前缀、状态和时间元数据,支持按用户编号/账号/密钥名称/前缀与状态筛选,并使用服务端分页。页面和浏览器缓存中都不会取得完整密钥、public_id 或 secret_hash,也不提供管理员创建用户密钥的入口。

“详情”读取单条元数据和最近 20 条脱敏安全事件;摘要只显示已审核的来源、类型、幂等结果和管理员编号。“撤销”必须经过二次确认,成功后立即刷新列表;重复请求保持幂等,但已撤销密钥不能恢复。没有权限、加载失败、空结果和加载中均有独立页面状态。

前端回归命令:

corepack pnpm --dir admin-ui lint
corepack pnpm --dir admin-ui exec vue-cli-service test:unit --runInBand
$env:NODE_OPTIONS = "--max-old-space-size=4096"
corepack pnpm --dir admin-ui build:prod
$env:NODE_OPTIONS = $null

浏览器验收需登录实际管理端,覆盖列表、筛选、详情、撤销确认的取消路径以及 375/768/1024/1440 四种视口;主页面不得横向溢出,表格在窄屏内自行滚动。真实撤销只对专门创建的合成测试密钥执行,不使用生产用户或真实凭据。

#47 MVP-2 跨模块集成验收(2026-08-25,验收通过)

本次只使用明确可丢弃的 chorus_test、构造数据和 mock Provider。运行破坏性迁移前先停止 chorus-user 与 chorus-admin-api;迁移测试会保留一组 MVP-1 合成夹具,因此进入队列测试前必须再执行一次明确的 down -all → up,避免该 pending generation 被后续测试认领。完成后重新安装 Supervisor 制品并重启两个后端实例。

已通过:

  • MySQL 8 migration up/down/up、MVP-1 数据演进、非空 MVP-2 down 拒绝、API Key 仓储、Portal/OpenAPI、队列 CAS/延后、mock worker 和 Admin 治理集成测试。
  • 根模块与 Admin 的 go test、go vet、go build,以及限流、API Key、审计、队列、Portal 和 Admin 定向 race 测试。
  • Portal 前端构建;Admin UI lint 无 error(保留 98 条基线 warning)和 14 suites / 49 tests;Admin UI --no-module 生产构建通过。
  • Portal Playwright 在 375/768/1024/1440 四视口为 9 passed、3 条按既有视口条件 skipped;API Key 创建一次显示、刷新不可恢复、改名、撤销和 fixture 重复清理通过。
  • DevHarness strict、43 项 Harness 单测和核心 Wiki 镜像检查。

已知限制:本机执行 Admin UI 默认双 bundle 构建时,legacy bundle 已成功,但 module bundle 因当时仅约 2.7 GB 可用内存而 OOM;释放并发并使用单 bundle vue-cli-service build --no-module 后成功。这是本机资源限制,不改写为默认双 bundle 完整通过。管理端 #46 的登录态浏览器 E2E 仍沿用该工单已记录的未执行限制。

发现并修复:#52 管理端幂等撤销响应时间精度;#53 Portal E2E 安全审计清理顺序。完整 Key、Prompt、Authorization、Cookie 和真实数据均未写入测试证据。 #47、#52 和 #53 已由用户于 2026-08-25 明确验收通过;上述未执行项继续作为已知验证限制保留。

管理端系统管理回归

常规回归从仓库根目录执行:

go test ./...
go vet ./...
go build ./...
go -C admin test ./...
go -C admin vet ./...
go -C admin build ./...
corepack pnpm --dir admin-ui lint
corepack pnpm --dir admin-ui test:unit -- --runInBand
$env:NODE_OPTIONS = "--max-old-space-size=4096"
corepack pnpm --dir admin-ui build:prod
$env:NODE_OPTIONS = $null
corepack pnpm --dir admin-ui exec playwright test tests/e2e/system-management.spec.ts --reporter=line

MySQL 保护与迁移测试只允许指向精确命名的可丢弃库 chorus_test:

$env:CHORUS_MIGRATION_TEST_DATABASE = "chorus_test"
$env:CHORUS_RUN_MIGRATION_TESTS = "1"
go test -count=1 -run '^TestMigrationsUpDownUpMySQL$' ./migrations
$env:CHORUS_RUN_ADMIN_TESTS = "1"
go -C admin test -count=1 -run '^TestAdminProtectionMySQL$' ./app/admin/service

两项测试都会在连接后再次核对数据库名;迁移测试会重置目标库。浏览器测试使用合成导航、账号和空列表响应,不需要真实登录凭据,也不调用 Provider。

Admin 生成媒体验证(#72)

定向验证:

go test ./internal/core/storage ./internal/platform/storage ./migrations
go -C admin test -count=1 ./app/chorus ./cmd
pnpm -C admin-ui lint
pnpm -C admin-ui test:unit --runInBand
pnpm -C admin-ui build:prod

migration 10 必须在精确命名的可丢弃 MySQL 8 测试库执行 up/down/up。测试应核对四个 API、菜单关联和 chorus_operator Casbin 规则,并确认 down 只移除本迁移登记的权限数据。浏览器验收使用现有管理员会话和已有记录,不调用 Provider;覆盖详情按需打开、多图、文本、空结果、对象缺失、错误状态和窄屏,检查媒体请求使用 Authorization header 且 URL 不包含 Token 或存储键。

管理员创建终端用户(#73)

  1. 先把数据库迁移到 version 11;生产仍禁止 AutoMigrate。
  2. 登录 Admin,进入“用户与访问 → 终端用户”,点击“新增终端用户”,填写账号、昵称、邮箱、初始密码和状态。
  3. 创建成功后用户立即出现在列表;active 用户可用账号和初始密码登录 Portal,disabled 用户不能登录。
  4. 行操作“重置密码”只接受并确认新密码;保存后旧密码失效。

最小验证命令:

go test ./migrations ./portal/auth ./portal/handler
go -C admin test ./app/chorus
corepack pnpm@9.15.1 --dir admin-ui exec jest tests/unit/chorus/users-account.spec.js --runInBand
corepack pnpm@9.15.1 --dir admin-ui build:prod

migration 的 up/down/up 必须只在明确指定的可丢弃 chorus_test 数据库执行;version 11 的 down 只撤销权限配置,不删除已经创建的用户。

Portal 自助注册配置与验证(#79–#83)

Portal YAML 可配置:

server:
  trusted_proxy_cidrs: []
registration:
  attempts: 5
  window_seconds: 900

对应环境覆盖项是 CHORUS_TRUSTED_PROXY_CIDRS、CHORUS_REGISTRATION_ATTEMPTS 和 CHORUS_REGISTRATION_WINDOW_SECONDS。生产环境必须显式提供注册限流配置;trusted_proxy_cidrs 只填写实际反向代理的地址段,直连部署保持空数组。策略启停不在 YAML 中配置,由管理员页面和数据库单例策略管理;迁移后默认关闭。

隔离 MySQL 的迁移和注册回归至少执行:

$env:CHORUS_RUN_MIGRATION_TESTS = "1"
$env:CHORUS_MIGRATION_TEST_DATABASE = "chorus_test"
go test ./migrations -run TestMigrationsUpDownUpMySQL -count=1 -v

$env:CHORUS_TEST_DSN = "<仅指向 chorus_test 的 DSN;设置 loc=UTC 和 MySQL session time_zone=+00:00>"
go test -p 1 ./internal/core/queue ./internal/core/router ./portal/handler ./portal/worker -count=1

浏览器注册验收使用 CHORUS_ENV=test、CHORUS_TEST_DISABLE_WORKER=true 和 fixture,覆盖 375×812、768×1024、1024×768、1440×900。fixture 负责临时开放策略、创建构造账号、清理账号并恢复关闭;不得用于开发或生产数据。Admin 侧执行 go -C admin test ./...、pnpm --dir admin-ui test:unit、pnpm --dir admin-ui build:prod,并验证注册策略开关的确认与更新状态。