版本 v1.3 · 生效 2026-09-08 · 本文档为开发流程的唯一事实源(CHANGELOG 只记产品变更)。 配套文档:环境与网络地图(L0/L1/L2 概念与内测入口)见
docs/ENVIRONMENTS.md;架构事实源(HTTPS 链路 / 环境路由 / 安全边界)见docs/ARCHITECTURE.md;环境与隐私铁律见docs/DEVELOPMENT.md; 部署细节见docs/DEPLOY.md(含 §4B 内测环境);桌面端发布/事故处置见docs/PUBLISHING.md;游戏测试与内测规范见docs/GAMES.md第六节。
① 提出要求(用户)
↓
② 方案确认(影响面大/有歧义时,先一句话方案再动手)
↓
③ 代码修改 + ④ 本地 DoD 全绿
↓
⑤ 版本对齐 + CHANGELOG + 本地 commit(不 push)
↓
⑥ 部署 L1 内测环境(同步代码/静态 → 内测库迁移 → 重启内测服务)+ L1 自动冒烟(可写 E2E,断言只写内测库)
↓(需要同学试用时:把内测网址发给受邀者)
⑦ 用户验收(默认在内测环境进行;「测试通过」前禁止部署生产)
├─ 通过 → ⑧ 部署 L2 生产 + 生产只读巡检(金丝雀账号,零写操作)
│ ↓
│ ⑨ git push + tag vX.Y.Z(稳定版)→ ⑩ Release 构建 → ⑪ 收尾(含公网验证)
└─ 不通过 → 回 ③/⑤ 继续本地修复,重走 ⑥⑦⑧,绝不推送
一句话:内测先行、验收后门 —— push/tag/Release 的唯一门票是用户明确说「测试通过」;生产库永不用于测试写入。
v1.1 及以前:本地测完直接部署生产,测试动作(测试账号、对局、兑换、清库实验)都发生在唯一的生产环境上, 真实用户可能看到半成品,测试数据混进真实账本。v1.2 起新增 L1 内测环境(独立数据库 qbao_beta、 独立 API 端口、独立静态目录、不缓存),所有测试/内测先在 L1 完成,验收通过后才上生产。
| 阶段 | 动作 | 产出 | 责任人 |
|---|---|---|---|
| ① 需求 | 用户提出要求 | 明确的问题/需求条目 | 用户 |
| ② 方案 | 影响面大或有歧义时先给一句话方案,确认后再动手 | 确认过的方案 | 双方 |
| ③ 修改 | 先定位根因(日志/复现证据)→ 修改 → 按需补测试 | 干净的代码 | Agent |
| ④ DoD | 见 §5 清单,全部通过才允许部署 | 通过清单 | Agent |
| ⑤ 版本+提交 | 三端版本对齐、CHANGELOG 条目、git commit(本地) |
本地提交(回滚点) | Agent |
| ⑥ 部署 L1 | 同步 server/app 到内测目录 → 内测库迁移 → 重启内测服务 → L1 冒烟(可写 E2E);按需分发内测网址 | 内测新版 + 冒烟报告 | Agent |
| ⑦ 用户验收 | 用户在内测网址测试(或按 §3 例外直接在生产验收),等待明确结论 | 验收结论 | 用户 |
| ⑧ 部署 L2 | 仅当 ⑦ 通过:同步到生产目录(先备份)→ 生产库迁移 → 重启生产服务 → 生产只读巡检(金丝雀) | 生产新版 + 巡检报告 | Agent |
| ⑨ 推送 | git push origin main + git tag vX.Y.Z + push tag(稳定版) |
远端提交与标签 | Agent |
| ⑩ Release | GitHub Actions「Release 桌面版构建」→ 校验发布资产 | 公开发布 | Agent |
| ⑪ 收尾 | 按 PUBLISHING.md 搬包入库(add)+ 公网逐字节验证;L1 保持与 L2 同步;local/log.md 记录 |
日志 | Agent |
| L0 本机 | L1 内测(默认验收点) | L2 生产(真实用户) | |
|---|---|---|---|
| 入口 | 本地端口(游戏静态 127.0.0.1:8124 等) | 内测域名(见 local/ENV.md) | 生产域名(IP 直连已随源站退役取消) |
| 数据库 | 无(单测假库) | qbao_beta(可随时重建) | qbao(禁测试写入) |
| API | — | 香港主机 :3100(qbao-api-beta) | 香港主机 :3000(qbao-api) |
| 静态 | 仓库源码 | qbao-beta 目录(不缓存) | 生产目录(静态 7 天缓存) |
| 允许动作 | 写码/单测/本地页面 | 注册/对局/兑换/领奖/清库/压测 | 只读巡检(金丝雀账号) |
| 部署工具 | — | scripts/stage.ps1 -Env beta | scripts/stage.ps1 -Env prod |
必须走 L1 再上 L2 的改动类型:数据库迁移、经济/积分规则、新游戏接入、游戏云存档逻辑、账号/鉴权、 任何产生写操作的端到端验证。例外:纯展示/文案类小改动可在 L1 冒烟后直接上生产验收; 紧急修复可压缩 ②③⑥ 的节奏(先 L1 快验再上 L2),但 ⑦→⑧ 与 ⑨ 的闸门不变。
| 态 | 版本号 | 构建方式 | 分发入口 | 强制更新 |
|---|---|---|---|---|
| 开发态 | 三端 = X.Y.Z | 无安装包产出 | 无 | — |
| 测试态 | 构建时覆盖 X.Y.Z-beta.N(npx electron-builder --win nsis --publish never --config.extraMetadata.version=X.Y.Z-beta.N,不改任何跟踪文件、不推 tag、不进 CHANGELOG) |
本地构建 | 服务器 beta 渠道(publish-installer add --channel beta);测试者设 updateChannel: beta |
永不(工具+服务端双重拒绝) |
| 稳定态 | X.Y.Z(三端对齐,验收后 bump) |
正式 tag → CI Release → GitHub 归档 | 服务器 stable 渠道(add --channel stable);普通用户默认自动更新 |
仅显式 promote --required,白名单:API 破坏性变更 / 安全漏洞 / 数据迁移 |
规则(工具强制,非约定):
add --channel stable 拒绝 prerelease 版本号;beta 渠道永不进入 stable;release.yml 对含 - 的 tag 整体跳过。retract(见 docs/PUBLISHING.md §4)。server/sql/ 提供幂等迁移脚本,编号递增(当前已到 017),本地验证可重复执行;迁移先内测库、验收后生产库。vMAJOR.MINOR.PATCH 三端对齐:app/server/desktop 的 package.json + package-lock.json 的 packages[""].version;一律 JSON parse/stringify 更新,禁止文本替换锁文件(v3.34.1 事故教训)。CHANGELOG.md 顶部追加条目;git add(仅本次改动文件)→ git commit(只本地提交,不 push,为部署提供回滚点)。local/ENV.md)。scripts/stage.ps1 -Env beta -Mode all(server 代码 + app 构建产物/静态游戏 → 内测目录;真实主机/密钥从 gitignored 的 local/stage.env.ps1 读取)。node scripts/run_migration.js(读内测 .env → qbao_beta)。systemctl restart qbao-api-beta;/health OK。scripts/qa/smoke-stage.ps1 -Env beta(可写 E2E:注册→登录→游戏成绩→弹珠兑换/对局;断言写入 qbao_beta 且生产库零变化)。失败 → 回 ③ 或 ⑥,不得进入 ⑦。.bak_时间戳 快照(沿用历史惯例)。/health OK。index.html 字节数/sha256 与本地一致;Caddy 如有新 route/location 需同步修改并备份。/api/v1/desktop/manifest、/download?file=(含 404/410)、/update/<channel>/latest.yml、/dl、Range 206 与统计计数。/api/v1/chat/files/*、/api/v1/issues/images/*)不在此列:它们由业务端点显式下发 private, max-age=31536000, immutable(见 server/src/lib/mediaCache.js),反向代理的按扩展名静态缓存规则必须排除 /api/*。上线前后各测一次响应头,确认 API 响应里只剩一个 Cache-Control。@name path *.png \ 换行 not path /api/* 不是「且」——not/path//api/* 会被当成三个扩展名模式塞进同一个 path 列表,条件恒假且 caddy validate 不报错。要用块式匹配器(@name { path …; not path /api/* })或 path *.png !/api/*;改完查 http://127.0.0.1:2019/config/ 的编译产物确认 "not" 是键而不是数组元素。git push origin main → git tag v3.x.x → git push origin v3.x.x(稳定版 tag 不含 -;测试版不推 tag)。- 自动跳过),核对发布资产齐全(三端版本一致)。local/log.md 追加 → 提醒用户强刷;L1 保持与 L2 同版本同步(下次开发直接基于最新)。server: npx vitest run 全绿(基线 312 例 / 49 文件,2026-09-17 复核;含鉴权媒体票据、迁移完整性 + 路由唯一性、优雅停机、客户端错误上报守卫、AI 出题任务全链路 e2e 与自检空转兜底、受保护媒体缓存头与两端点防回退、列表缩略图 ?w= 与票据时间桶稳定性、聊天媒体四道闸门(配额/水位/归属/回收)与下载侧次数+字节双预算)server: 数据库变更后跑 node scripts/run_migration.js --verify(打印未落库的迁移编号 / 仓库缺失的已记账版本)app: npx vitest run 全绿(基线 317 用例 / 31 文件,2026-09-17 复核);登录门禁:未登录整页登录门禁、匿名零写盘、匿名改动锁重建;图片上传前压缩的安全网(非图片/GIF/小图不压、压大即弃、异常回退原件);图片本地缓存键稳定性(票据变化不改键、缩略图与整图分键、无 IndexedDB 环境静默降级)scripts: node --test scripts/installer-lib.test.js 全绿(6 例)desktop: node --check main.js preload.js updater.js updater-util.js + node --test desktop/test 全绿(5 例)npx eslint .(app/server)0 error(2026-09-17 复核:server src 71 warning、app 153 warning,均为既有风格项;server/test 另有 6 个既有 no-undef 'test' 属历史用例,不在 src 门禁口径内)app: npx vite build 成功;dist/index.html 大小以字节核对;涉及 Vue 模板时 compiler-sfc 扫描无悬空绑定app/src/games/qa-gate)全绿;游戏 QA 清单见 docs/GAMES.md 第六节| 场景 | 操作 |
|---|---|
| L1 内测坏了 | 重放上一版本到内测目录 + 重启;或直接重建 qbao_beta(清库脚本,1 分钟内;不涉及任何真实用户) |
| L2 网页端回滚 | 服务器 .bak* 恢复,或重新部署上一验收版本的工作树构建;不影响 git |
| 未推送的本地提交 | git reset / git commit --amend 清理 |
| 已推送的缺陷版本 | revert 或新版本修复 + 新 tag;旧 tag 不动 |
| 稳定渠道坏版本(恶性 bug) | retract 熔断 + 用户自助下载旧版重装 + 修复版走 beta → 新稳定版 |
| beta 渠道坏版本 | 删除 beta 渠道该版本条目(文件人工清理) |
| manifest 损坏 | 恢复 downloads/manifest.json.bak |
| 生产事故 | 先应急部署修复恢复服务(压缩节奏),再走验收闸门(红线不变:仍须验收后才推送) |
local/ENV.md,部署脚本读 gitignored 配置)。local/log.md 记录变更;修订后以此文件为准。