Runbooks
三条链路,动的东西完全不同,风险也完全不同。 把它们当成一件事,是这个岗位最容易出的事故。
gameboxclient 工作区快照。
本页每条断言都在源码里当场核对过,含两个用 Node 实测验证过的行为。
⛔ 读之前先明确
在拿到「哪些环境可以安全操作」的明确答复之前,这三条 runbook 都只用来读,不要照着做。
三条里有两条会动线上:Runbook A 提交到 SVN 仓库, Runbook C 会把所有在线玩家踢下线并重启服务器。 它们都没有「撤销」按钮。
| A · 资源发布 | B · 客户端打包 | C · 服务器更新 | |
|---|---|---|---|
| 入口 | /expert/release |
命令行 npm run build:electron:* |
Dashboard 首页的「更新服务器」卡片 |
| 动了什么 | 本地隐藏工作副本 + 远端 SVN 仓库 | 只动你自己的机器(产出安装包) | 线上游戏服务器 |
| 影响谁 | 拉取该资源的人 | 没人,直到你把包发出去 | 所有在线玩家(会被踢下线) |
| 风险 | ⚠️ 中 | ✅ 低 | 🔴 高 |
| 入口 | 专家模式 → 发布(/expert/release) |
| 实际动了什么 |
不是本代码仓库。是
<盘符根>/.hidden_resource/<渠道> 下的 SVN
工作副本
|
| 需要的凭据 |
SVN 账号密码 —— .env 里为空,
只能靠登录下发
|
| 不可逆动作 |
svnCommit()。提交前的自动 add / delete
也已经改了工作副本(见下)
|
| 成功证据 | ⚠️ 不能只看界面提示 —— 见下方 100 个文件的坑 |
| 怎么停 |
提交前:关掉弹窗即可。提交后:只能
svn revert 或反向提交,需要有人有仓库权限
|
| 找谁 | SVN 仓库管理员(待确认是谁) |
这是最容易搞错的一点。workspace()
(src/api/workspace/index.ts:96-102)返回的是:
getHiddenResourcePath() + '/' + gameEnv.VITE_API_CHANNEL_SVN
而 getHiddenResourcePath()(:75-86)最后一行是:
path.join(path.parse(baseurl).root, '.hidden_resource')
path.parse(x).root 取的是盘符根目录 ——
Windows 上是 C:\,macOS 上是 /。 所以工作副本在
C:\.hidden_resource\<渠道>,
不在应用安装目录下,也不在你的代码仓库里。
⚠️ 两套工作区,别混
你的源码仓库(gameboxclient,当前有 9401
条 未提交改动)和运行时的 SVN 工作副本
(C:\.hidden_resource\…)是完全无关的两个东西。
git status 显示的那 9401
条跟发布内容毫无关系,
绝不能拿它推断「有什么要发」。要看发布内容,只能看发布页列出的文件列表。
点发布卡片后,complieproject_svn()
(src/utils/extend/svn.ts:161-176)做的第一件事是:
if (flag) {
await svnAddUnversionedFiles(localUrl, option) // 把未纳入版本的文件 svn add
await svnRemoveMissingFiles(localUrl, option) // 把本地已删的文件 svn delete
}
const target = await svnStatus(localUrl) // 然后才列状态给你看
也就是说,等弹窗出现、你看到那份文件列表时,
「加入版本控制」和「标记删除」这两件事已经做完了。
弹窗只决定要不要 commit。
❌ 别学
确认框之前就修改了状态。用户以为「我还没确认,所以什么都没发生」, 实际上工作副本已经被改了 ——
包括把一些你可能根本不想加进版本控制的临时文件
svn add 了进去。
规范做法是:先只读地算出「将要做什么」, 展示给用户确认,确认后再一次性执行 add / delete / commit。
实操影响:关掉弹窗不等于回到原状。
如果你只是想「看一眼有什么改动」,看完请检查工作副本状态, 必要时手动
svn revert。
svnCommit()(svn.ts:268-284):
const commitList = localFiles.map(...)
SVN.commands.commit(commitList.splice(0, 100), mergeOption, function (error) {
resolve(!error) // ← 只要这 100 个没报错,就返回成功
})
Array.splice(0, 100) 返回前 100 个元素,
剩下的留在 commitList 里 —— 而 commitList
之后再没被用过。我实测验证过:
// 250 个文件的情况
传给 commit 的: 100 个
剩在 commitList 里没人管的: 150 个
❌ 真 bug
改动超过 100 个文件时,只有前 100 个被提交,其余静默丢弃,
而且界面照样显示成功(resolve(!error)
只反映那 100 个的结果)。
后果:「我明明发布成功了,但资源没更新 / 只更新了一部分」—— 这是本项目最难查的一类事故,因为界面给的是成功。
动手前必做:看发布弹窗里列出的文件数量。 超过 100 就不要直接提交,先跟人确认怎么分批。
规范做法是分批循环提交直到列表为空,并把每批结果汇总; 或者至少在超过上限时明确报错而不是假装成功。
只要 Jenkins 上有任何构建在跑,发布就走不下去 ——
包括纯本地的 SVN 提交。看到「当前有打包任务在进行中」是设计如此,不是 bug。
范围确实是「任何」:getActiveBuilds() 遍历
JOB_TYPES 里配置的全部 job
(jenkins/monitor.ts:73、jenkins/config.ts:8)。
⚠️ 两个细节,别理解成「点不动」
① 卡片是能点的。模板里唯一的
:disabled 绑定是
userInfo?.user_type == 3(权限),
跟 Jenkins 状态无关
(views/Option/index.vue:250,278,304)。
闸门在 compileProject() 函数体内
(:90-110)—— 你点下去,它先去问一次 Jenkins,
然后才弹警告。所以是「点了才拦」,不是「点不动」。
② Jenkins 查不通时,闸门会静默打开。
getActiveBuilds() 整个循环包在 try 里,
catch 只 console.error 就
return activeBuilds
(jenkins/monitor.ts:99-103)——
网络断了或 Jenkins 挂了,返回空数组,
调用方会当成「没有构建在跑」而放行。
规范做法:查询失败 ≠ 检查通过, 这两种情况必须能区分开 —— 现在它们都表现为一个空数组。 具体怎么改见 写操作前必查 · 提案 6。
| 入口 | 命令行,没有界面入口 |
| 实际动了什么 | 只动你自己的机器,产出 release/ 下的安装包 |
| 需要的凭据 | 无(本地构建) |
| 不可逆动作 | 构建本身没有。但把包传到更新服务器之后, 所有客户端都会自动拉到 |
| 成功证据 | release/ 目录下有对应平台的安装包 |
| 怎么停 | 传出去之前随时可以停;传出去之后要撤只能发新版覆盖 |
| 找谁 | 管更新服务器的人(待确认 —— 见下方地址不一致问题) |
build:pro 不是打包| 命令 | 实际做什么 | 产出 |
|---|---|---|
npm run build:pro |
vite build --mode pro |
只是前端资源(dist/),
不是安装包
|
npm run build:electron:pro |
rm -rf release/ && vite build --mode pro &&
electron-builder
|
这才是安装包 |
npm run build:electron |
vue-tsc --noEmit && vite build && electron-builder
|
带类型检查,但 vite build
没带 --mode ——
走的是默认环境,和 :pro 不是同一份配置
|
npm run build:electron:winpro |
同 :pro 但不清 release/ |
Windows 包 |
在 macOS 上跑 build:electron:* 产出的是 macOS 包, 不是
Windows 安装包。要出 Windows 正式包必须在 Windows 环境。
| 配置在哪 | 指向 | |
|---|---|---|
| 打包时的 publish | electron-builder.json5:49-53 |
http://192.168.31.90:9009/latest.yml(内网 IP、HTTP)
|
| 客户端拉更新 | electron/main/tools/updater.ts:66-69 |
https://39hezi-518qn-…cos.ap-shanghai.myqcloud.com/Gamebox_Develop/future(COS、HTTPS)
|
⚠️ 必须问清楚才能发版
打出来的包该传到哪、客户端到底从哪拉,这两处配置各说各话。 可能的解释:内网那个是给内部测试用的,COS 才是正式渠道; 也可能是某一处已经过期没人改。
在问清楚之前不要发客户端版本 —— 传错地方等于没发,传对地方但配置过期则可能推给所有用户一个错的包。
而且没有缓冲:客户端会立刻下载。
updater.ts:65 那句
autoUpdater.autoDownload = false
在 30 行后被抵消了 ——
update-available 处理器里直接调了
autoUpdater.downloadUpdate()(:95),
中间没有任何用户确认:
// updater.ts:89-97
autoUpdater.on('update-available', (info) => {
Logger.info('检查到有更新,开始下载新版本')
autoUpdater.downloadUpdate() // ← 无条件,没人问过用户
sendUpdateMessage(message.updateAva) // ← 这只是通知渲染层,下载已经开始了
})
autoDownload = false 关掉的是 electron-updater
自己在检查后自动下载的行为,代码随后又手动触发了一次。
渲染层收到 updateAva 时下载已在进行,
它是个通知,不是一道闸门
(全仓库搜不到第二处调 downloadUpdate() 的地方)。
所以误发的后果是全量的:包一旦传到客户端真正拉取的那个地址, 所有在线客户端下次启动检查时就会把它拉下来。 更要在发版前把这两个地址问清楚。
🔴 这是全站风险最高的一个操作
它会把所有在线玩家踢下线,然后重启游戏服务器。 入口就在登录后的首页上。
📝 2026-08-14 订正
本节初版把两个不同入口混成了一个,并断言「没有二次确认」—— 三处都是错的。实际上两个入口都有确认框。 订正内容见下,出错经过记在 learning-records/0007。
| 入口 | 有两个,走的是不同组件、不同按钮 —— 见下方 C-1 |
| 实际动了什么 | 线上游戏服务器 |
| 不可逆动作 | 踢出所有在线玩家;重启服务进程。 且对你勾选的每一个区服都执行一遍 |
| 成功证据 | 任务面板四步全部变为已完成 |
| 怎么停 | 点下去就停不了了。四步是顺序 await, 中间失败会抛错中断,但已经踢掉的人不会自动回来 |
| 找谁 | 动之前找运营确认可以踢人的时间窗口 |
Dashboard 首页上有两张卡片都能通向「重启线上服务器」, 但走的是完全不同的组件。初版文档只写了一个,还写错了组件名。
①「更新服务器」卡片
(Dashboard/index.vue:1174-1180)→
showServerControlVisible = true
② 打开 ElDialog 标题「服务器管理」
(:1682-1685),内容是
ServeChooseView
(:1696)—— 不是
serverControl.vue
③ 里面三个按钮:「表回滚」「一键开服」
「打包构建」
(serveChooseView.vue:36-38)
④「打包构建」→ startChangeServer(2) →
ElMessageBox.confirm
(:125-135)→ Emits('serverClose', arr, 2)
⑤ → serverBuild(data, 2) →
method.ts:564 的
for (const item of data) → 对每个服调
startFun(item)(:582,没有 await)
①「游戏管理」区的「服务器管理」卡片
(Dashboard/index.vue:953-956)→
serverConrtolFunction()(method.ts:542)
② → serverControlRef.value.showCurrentVisible()
→ 打开 serverControl.vue
③ 四个按钮
openDia(1..4):修改服务器状态 / 下线所有玩家 /
打包服务程序 / 更新服务程序
④ 都只是转发给
EditStatusDialog.openModifyDiaglog(type)
(serverControl.vue:133-135)—— 在那个弹窗里勾选区服后,
才真正提交
入口一最终跑的是
startFun()(src/views/GameEditor/Dashboard/method.ts:593)
顺序执行,每步失败就 throw 中断:
① 服务器状态 —— changeServerStatus()
② 踢出所有人 ——
tickAllPeople() → /1000y/sj/serverKickAll
(method.ts:622-635)
③ 打包服务器 —— packServer() →
/1000y/sj/packServer,成功判据是返回里含
Finished: SUCCESS(method.ts:637-644)
④ 重启服务器
两个入口都有 ElMessageBox.confirm:
| 入口 | 位置 | 文案 |
|---|---|---|
| 「打包构建」 | serveChooseView.vue:125-135 |
打包构建会将选择的服修改为维护状态,并踢人下线,服务器打包构建。 |
| 「下线所有玩家」 | EditStatusDialog.vue:42 |
此操作会强制服务器内的所有玩家下线,请确认操作 |
| 「打包服务程序」 | EditStatusDialog.vue:118 |
此操作会编译打包新的游戏服务器程序,请确认操作 |
| 「更新服务程序」 | EditStatusDialog.vue:186 |
此操作将更新部署并启动新的服务器程序,请确认操作 |
文案统一定义在 src/views/Database/const.ts:59-61
的 WarningInfo。
serverControl.vue:55-62 那个 ElTooltip
是悬停提示,确认框在后面的弹窗里 ——
初版文档看到 tooltip 就下了「没有确认框」的结论,是只看了一层。
⚠️ 真正的风险在这里
确认框的文案里没有区服名字,也没有在线人数。 它只说「选择的服」,而你要动的是你刚才勾选的全部区服:
// method.ts:564-584
for (const item of data) {
if (index == 1) { await changeServerStatus(item.sId, 1) } // 一键开服
else if (index == 3) { excelBackByCopy(...) } // 表回滚
else { startFun(item) } // ← 注意:没有 await
}
勾错一个服,确认框不会帮你发现 —— 它长得跟只选一个服时一模一样。
另外注意 startFun(item)(:582)
前面没有 await,而
startFun 是 async 的。
所以勾了 N 个服,是 N 条「踢人 → 打包 → 重启」流程同时开跑,
不是排队一个一个来。出问题时它们的日志会交错在一起。
规范做法:不可逆且影响线上用户的操作, 确认框里要把目标对象名字逐个打出来 (「即将重启 XX 服、YY 服,当前在线 N 人,确认?」), 让用户有机会发现自己选错了。
在改代码之前,你能做的: 点确认之前,回头看一眼勾选列表, 而不是只看确认框那句话。
站点版本 dcc04aa · 2026-08-14
内容基线 gameboxclient @ 2026-08-12