Skill 方案导入1472 字·2026/11/14·被检索 0 次

Skill 方案 S2 · 站点重启与隧道切换

按外向内停、内向外起,写清四条停止顺序、按PID清端口、Prisma引擎DLL只在端口释放后的窗口生成,以及启动后的四层健康断言。约40次重启链路的0.2亿token可压到0.03亿,二次返工接近零。

挂靠在《二、重复操作与 Skill 方案》之下

版本
使用说明
v1reused-preview2026/11/14 11:16:43

Skill 方案 S2 · 站点重启与隧道切换

一句话:把「重启考研学习导航门户、词汇站和 natapp 隧道,按以下顺序严格执行」这条被复述了几十遍的指令,变成一条不会被顺序坑到的命令;约 40 次完整重启链路消耗的 0.2 亿 token,可压到 0.03 亿。

一、为什么需要它

扫描里命中「按以下顺序严格执行」这一模板的会话有 50 个,累计 0.31 亿 token;其中包含完整重启链路(停三个服务 + 清端口 + 起隧道 + 验健康)的约 40 次,合计 0.2 亿 token。

指标数值说明
模板会话50 个用户原话高度固定,只换服务名
完整重启链路约 40 次其余是单服务重启
累计 token0.2 亿平均每次 5 万
平均工具调用9–14 次停、查端口、起、再查、再看日志
一次成功比例约六成其余要二次返工

现状痛点不是"不会重启",而是顺序和副作用:

  1. 顺序错就白停一次。先停门户再停隧道,公网会在这几十秒里持续吐 502;先起隧道再起门户,Cloudflare 侧会把连接打到空端口上,日志里留下 connection refused,下一次巡检容易误判成隧道故障。
  2. 端口清理不彻底。Stop-ScheduledTask 只结束任务实例,launch.ps1 拉起的 node 子进程有时留成孤儿,继续占 3000/3001/3102,下一次启动直接 EADDRINUSE。
  3. Prisma 引擎 DLL 锁。笔记站的 query engine 落在 notes/generated/db3,服务一跑起来这个 DLL 就被独占,此时手工 prisma generate 必然 EPERM。这是本机最反复踩的坑,schema 从 generated/prisma 一路换到 db3 就是被它逼的。
  4. Start-Process 起不来。本机同时存在 HTTPS_PROXY 与 https_proxy,PowerShell 会抛「已添加项」,任何子进程都起不来;所以隧道只能由计划任务在 -Command 里直接 & cloudflared.exe。

二、触发词与边界

触发词不适用场景
「按以下顺序严格执行」「重启网站」「全量重启」只是看看状态 → S1 巡检
「重启笔记站」「重启词汇站」「重启隧道」刚改完代码要上线 → S3 构建发布
「端口被占了」「EADDRINUSE」要改任务定义/触发器 → 单独确认后手改
「停一下所有服务」要退公网入口 → rollback.ps1,不是重启
「重启后 502」数据库迁移 → 不在本 skill 范围

边界:只操作已存在的计划任务与端口,不新建任务、不改 XML。任务缺失时停下来报告,附带 Get-ScheduledTask 的实际输出,不要凭记忆重建。

三、输入与前置检查

任务名服务端口启动器
KaoyanPortalportal/server.js3000launch.ps1 -Name portal
KaoyanNextServer词汇站 Next3001launch.ps1 -Name vocab
KaoyanNotes笔记站 Next3102launch.ps1 -Name notes
KaoyanCLabC 语言实验室3103launch.ps1 -Name clab
KaoyanTunnelcloudflared—任务内直接托管

前置三查:

Get-ScheduledTask -TaskName "Kaoyan*" | Select-Object TaskName, State
Get-NetTCPConnection -LocalPort 3000,3001,3102,3103 -State Listen | Select-Object LocalPort, OwningProcess
Get-Process node, cloudflared -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, StartTime

若 KaoyanTunnel 不存在或为 Disabled,先停下;用 ops\cf\install-task.ps1 重装是独立动作,需要用户确认。

四、执行步骤

步骤 1 · 停,严格按「外向内」的顺序。 每一步之间留 800 ms:

# 1) 先断公网入口,避免用户看到 502
Stop-ScheduledTask -TaskName "KaoyanTunnel"
Get-Process cloudflared -ErrorAction SilentlyContinue | Stop-Process -Force

# 2) 再停门户,不再接新请求
Stop-ScheduledTask -TaskName "KaoyanPortal"

# 3) 最后停下游
Stop-ScheduledTask -TaskName "KaoyanNextServer"
Stop-ScheduledTask -TaskName "KaoyanNotes"
Stop-ScheduledTask -TaskName "KaoyanCLab"

Stop-ScheduledTask 不会带走 cloudflared,必须像上面第二行那样显式强杀,否则会出现两个连接器抢 20241 指标端口。

步骤 2 · 清端口,按 PID 杀而不是按名字杀。

foreach ($p in 3000,3001,3102,3103) {
  $c = Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
  if ($c) { Stop-Process -Id $c.OwningProcess -Force -ErrorAction SilentlyContinue }
}
Start-Sleep -Milliseconds 800

杀完必须复查一遍,端口仍被占就再杀一次;残留的常驻 node 可能是 SYSTEM 权限,普通权限杀不掉,此时只能报错并提示需要提权,不要假装成功。

步骤 3 · Prisma generate 只能在这个窗口做。 端口已清、服务未起,是唯一能安全生成的时间点:

$N = "C:\Users\cag\.workbuddy\binaries\node\versions\22.22.2-6\node.exe"
& $N "C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\notes\node_modules\prisma\build\index.js" generate `
  --schema "C:\Users\cag\WorkBuddy\2026-10-11-06-00-16\notes\prisma\schema.prisma"

ops/launch.ps1 在 -Name notes 分支里已经内置了这一步(在杀端口之后、启动之前),所以日常重启不需要手工跑;这里列出来是为了排障——如果 prisma-generate.log 里有 EPERM,说明顺序被打乱了,服务当时还在跑。

步骤 4 · 起,严格按「内向外」反序。

Start-ScheduledTask -TaskName "KaoyanNextServer"
Start-ScheduledTask -TaskName "KaoyanNotes"
Start-ScheduledTask -TaskName "KaoyanCLab"
Start-Sleep -Seconds 12   # Next 冷启动约 8-12 秒,太早起门户会反代失败
Start-ScheduledTask -TaskName "KaoyanPortal"
Start-Sleep -Seconds 3
Start-ScheduledTask -TaskName "KaoyanTunnel"

0x800710E0 = 有卡死实例,先 Stop-ScheduledTask 再 Start,脚本里作为固定分支处理,不要重试同一条命令。

步骤 5 · 健康断言,逐层放行。

(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:3000/healthz -TimeoutSec 8).Content
(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:3001/vocab/ -TimeoutSec 8).StatusCode
(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:3102/notes/ -TimeoutSec 8).StatusCode
(Invoke-WebRequest -UseBasicParsing https://cag.quest/ -TimeoutSec 15).StatusCode

/healthz 返回 {"portal":"ok","vocab":"ok","notes":"ok","clab":"ok"} 才算门户层通过——它内部会分别探三个上游,比逐个 curl 更快也更准。

步骤 6 · 隧道切换(仅 natapp 模式需要)。 读 mode.txt:tunnel 走 Cloudflare 固定域名,无需换域名;非 tunnel 则读 natapp.log 尾部 256 KB 里最后一次 Tunnel established at,再与 domain-state.json 比对,变了才落 outbox/mail-*.json。日志已数 MB,整文件读取会拖慢整个流程。

五、输出与验收

{
  "stopped": ["KaoyanTunnel", "KaoyanPortal", "KaoyanNextServer", "KaoyanNotes"],
  "portsFreed": [3000, 3001, 3102, 3103],
  "prismaGenerate": "ok",
  "started": ["KaoyanNextServer", "KaoyanNotes", "KaoyanCLab", "KaoyanPortal", "KaoyanTunnel"],
  "health": { "portal": "ok", "vocab": "ok", "notes": "ok", "clab": "ok", "public": 200 },
  "elapsedSec": 38
}

三条硬断言:

  1. 四个端口必须全部出现在 portsFreed 里,任一端口仍被占用即失败退出,不许带病启动。
  2. /healthz 的四个字段全为 ok,且公网 https://cag.quest/ 返回 200;本地全绿但公网非 200 视为隧道层失败。
  3. 进程数守恒:重启前后 node 进程数与站点数一致(门户 1 + 词汇站 1 + 笔记站 1 + 实验室 1),多出来就是孤儿进程。

六、失败处理与回滚

现象根因动作
EADDRINUSE旧进程没杀干净回步骤 2,按 PID 强杀后重跑
prisma generate EPERM服务还在跑,DLL 被独占停掉 3102 对应进程,再 generate
Start-Process 报「已添加项」同时存在 HTTPS_PROXY 与 https_proxy不要用 Start-Process,走计划任务
门户起但 /healthz 里 vocab=down上游没起够 12 秒等 10 秒重探,不要立刻重启
公网 502 而本地全绿隧道连到了旧端口/旧进程强杀 cloudflared 后单独重启 KaoyanTunnel
任务启动被拒 0x800710E0卡死实例Stop-ScheduledTask 后再 Start

回滚:重启本身是幂等的,重复执行不会叠加进程,所以"回滚"只有一种——停止本次改动。若刚才是全量重启且用户要求恢复原状,按同样顺序再重启一次即可;若涉及隧道模式变更,用 ops\cf\rollback.ps1 -Check 先试运行再执行。

七、收益测算

项目现状改造后
单次 token5 万(9–14 次工具往返 + 日志阅读)0.8 万(一条命令 + 读 JSON)
次数约 40 次约 40 次
累计0.2 亿0.03 亿
单次耗时3–8 分钟40–60 秒
二次返工约四成接近零(顺序与 Prisma 窗口写死)

省下的主要是返工:现状每次失败都要重新走一遍"停—查—起—查",一次返工就是 5 万 token 再来一遍。节省 0.17 亿,约 85%。