checklist
工程经验与教训 · Checklist(通用版)
和
checklist.md的分工:
checklist.md—— 本项目专用,精炼、贴着云账本的具体实现,重装/加功能时照着勾- 本文件(通用版)—— 把同一批教训抽象成做同类自托管项目都能用的检查表,
覆盖更全,带 178 项交付前自检和 15 条事故档案这份清单来自云账本从零到「手机可用 + 公网可达」的全过程,
记录的是踩过的坑和验证有效的做法,不是理论。
每条都对应一次真实的故障或返工。适用场景:自己做一个小型自托管服务,并且要让它在公网上长期稳定地跑。
最后更新:2026-09-12
〇、一句话总纲
自启只负责「开机拉一次」;守护负责「挂了把它拉回来」。两者缺一不可。
「任何长期运行的东西,都必须有人负责把它拉起来」——
而「开机时启动一次」不等于「一直在运行」。
这次服务断掉的直接原因就是:cloudflared 有服务守护、花生壳有服务守护,
唯独云账本自己只靠一个开机快捷方式——挂了就再也没人管。
为什么这条排第一:代码写错最多功能不对,服务悄悄死了是整个系统不可用,
而且你往往几小时后才发现。这个项目在两个组件上先后吃过同一个亏。
一、长期运行 = 必须有守护
1.1 逐个清点,一个都不能漏
| 组件 | 本次的做法 | 挂了会自动重启吗 |
|---|---|---|
| 应用本体(云账本) | 计划任务看门狗,每 5 分钟检查端口 | ✅ |
| Cloudflare 隧道 | Windows 服务 cloudflared |
✅ |
| 花生壳客户端 | 厂商自带服务 | ✅ |
检查方法:把架构图上的每个常驻组件列出来,逐个问「它挂了谁知道?」
1.2 「启动文件夹」是不够的
Windows「启动」文件夹 = 登录时执行一次
↓
进程后来崩了 / 被结束了 / 被误杀了
↓
没有任何机制知道,也没有任何机制恢复
结论:启动文件夹只解决「开机」,「运行中挂掉」必须另有守护。
1.3 守护的两种形态
| 形态 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| Windows 服务 | 系统级托管,崩溃自动重启,不依赖登录 | 需要管理员;普通脚本不能直接当服务 | 有服务封装的程序(如 cloudflared) |
| 计划任务看门狗 | 普通权限即可;逻辑自己写,灵活 | 有一个检查间隔的窗口期 | 普通脚本/程序(如 node 应用) |
看门狗的正确写法(deploy/ensure-running.ps1 就是这么做的):
① 先探测端口是否在监听(短超时 TCP 连接,别用重量级命令)
② 在监听 → 直接退出,什么都不做
③ 不在监听 → 拉起进程
关键点:
1.4 手动启动的进程不可靠
踩过的坑:我(AI)用工具调用 Start-Process 起的进程,
是那次调用的子进程——会话清理时被连带杀掉。而且它不留下任何错误日志,
看起来就像「应用自己消失了」。
教训:
1.5 判断「崩溃」还是「被结束」
| 现象 | 结论 |
|---|---|
| 错误日志里有堆栈 | 崩溃 → 修 bug |
| 错误日志完全为空 + 日志戛然而止 | 被结束 → 查是谁杀的、怎么启动的 |
| Windows 事件日志有记录 | 系统层面问题(内存、权限) |
二、Windows 平台的编码与脚本坑(每次都会踩)
2.1 文件编码(最容易反复踩)
| 文件类型 | 要求 | 原因 |
|---|---|---|
.cmd / .bat |
纯 ASCII | cmd.exe 按系统 ANSI 代码页解析,中文会变乱码并报「不是内部或外部命令」 |
.vbs |
纯 ASCII | Windows Script Host 同样按 ANSI 读 |
.ps1 |
必须带 UTF-8 BOM | Windows PowerShell 5.1 无 BOM 时按 GBK 读,中文全乱 |
做法:中文全部放进 .ps1,.cmd 只做纯 ASCII 的转发壳。
@echo off
chcp 65001 >nul
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0do-something.ps1"
if errorlevel 1 pause
血的教训:编辑器/工具保存 .ps1 时经常会把 BOM 吃掉,
所以要用脚本加回 BOM 并纳入测试,而不是靠人记得。
2.2 不要用 PowerShell 改文本文件
踩过的坑:用 PowerShell 的字符串替换去改 README.md,
结果整个文件被按 GBK 重新编码,中文全乱、行也丢了,最后只能 git checkout 回滚。
2.3 cmd /c 的引号地狱
踩过的坑:路径里有括号
C:\Program Files (x86)\cloudflared\cloudflared.exe
^^^^^
cmd /c ""C:\Program Files (x86)\...\x.exe" args" 会因为这对括号把引号解析搞坏,
命令静默失败(没进程、没日志、没报错)。
2.4 PowerShell 的 $ErrorActionPreference = 'Stop' 陷阱
踩过的坑:脚本里设了 Stop,结果调用 cloudflared 时它往 stderr 写日志,
PowerShell 把外部程序的 stderr 当成终止性错误,脚本直接中断——
服务装了一半、配置复制了一半,后面的步骤全没跑。
function Invoke-Native($exe, [string[]]$cmdArgs) {
$prev = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try { & $exe @cmdArgs 2>&1 | ForEach-Object { Write-Host " $_" } }
finally { $ErrorActionPreference = $prev }
}
2.5 控制台里的「假乱码」
Get-Content 读 UTF-8 文件再 Write-Host,在某些终端组合下会显示成乱码,
但文件本身是好的。
2.6 需要管理员的脚本要自己会说话
2.7 找可执行文件要显式枚举路径,别只靠 PATH
# ❌ 只写 node:PATH 里可能是别的 node(沙箱运行时、捆绑运行时、旧版本)
# ✅ 按标准安装位置逐个探,找不到再退回 PATH
$candidates = @(
"$env:ProgramFiles\nodejs\node.exe",
"${env:ProgramFiles(x86)}\nodejs\node.exe",
"$env:LOCALAPPDATA\Programs\nodejs\node.exe"
)
$node = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
2.8 启动前先查占用,失败要清干净
三、内网穿透与隧道
3.1 先想清楚「谁主动连谁」
外面进不来(没有公网 IP)
→ 让里面的程序主动连出去,保持长连接
→ 服务器把请求顺着这条连接反向送回来
3.2 隧道方案对比(实测)
| 花生壳 | Cloudflare 命名隧道 | |
|---|---|---|
| 实测延迟 | 0.3~0.6 秒 🏆 | 1.0~1.2 秒 |
| 年成本 | ¥0.01(促销价,可能涨) | ¥10(自己的域名) |
| 域名归属 | 厂商分配 | 自己的 |
| 到期风险 | ⚠️ 按年续费,促销价不保证 | 隧道永久有效 |
| 访问控制 | 免费版几乎没有 | 免费版可配 IP 白名单 / Access |
结论:两个都留着最划算——快的当主力,自己域名的当备用,互为保险。
3.3 为什么国内源站用 Cloudflare 反而慢
Cloudflare 的优势是「全球任播、就近接入」,
但如果源站在国内、Cloudflare 在大陆没有节点,
流量会被调度到境外(实测到了 lax / sjc),一来一回跨境两次。
3.4 cloudflared service install 装出来的服务是残的
踩过的坑:装完服务一直是 Stopped,事件日志只有 Cloudflared service starting、
没有任何错误。查服务的 binPath 才发现:
BINARY_PATH_NAME : "C:\...\cloudflared.exe" ← 就这些,没有 tunnel run
而 cloudflared.exe 不带参数运行只会打印「请用 tunnel run 启动」然后退出。
官方文档第 13~14 步也要求手工改注册表:
HKLM\SYSTEM\CurrentControlSet\Services\cloudflared → ImagePath
改成: "<exe>" --config="<config.yml>" tunnel run
3.5 服务账号读不到你的用户目录
服务以 SYSTEM 运行,它的「家目录」是:
C:\Windows\System32\config\systemprofile
所以 ~/.cloudflared/config.yml 找不到。
3.6 隧道建不起来,先怀疑 DNS
临时隧道一直连不上时,别急着怪隧道服务:
四、前端与缓存(最隐蔽的一类问题)
4.1 Service Worker 会让你「改了代码不生效」
踩过的坑:后端明明正确,界面上就是不弹新加的确认框。
原因是 SW 对静态资源用 stale-while-revalidate:
caches.match(req).then((cached) => {
const network = fetch(req).then(...); // 后台悄悄更新缓存
return cached || network; // ← 但这次仍然返回旧缓存!
});
两个问题叠加:
| 问题 | 后果 |
|---|---|
stale-while-revalidate |
一次请求永远返回上次缓存的那份,新版本要第二次才生效 |
缓存名写死(VERSION = 'yzb-v1') |
不手动改就永远不失效,装成 PWA 更久 |
4.2 正确的做法
4.3 底部弹层是「单层」的
export function openSheet(opts) {
if (activeSheet) closeSheet(); // ← 会先关掉当前那个
}
踩过的坑:表单提交成功后 formSheet 会 closeSheet(),
而我在 onSubmit 里弹了一个新确认框——被这次 closeSheet 一起关掉了,一闪就没。
4.4 状态更新要集中成一处
踩过的坑:流水页切换月份时,model.ym 改了、列表也重新加载了,
但中间的月份标签一直不更新——因为 renderMonthBar() 只在页面首次渲染时调用过。
五、数据与账务一致性
5.1 分清「计划」和「实际」
预算层(计划) ──「生成到账本」──► 账本层(实际)
▲ │
└────────「对比」只读 ────────────────┘
5.2 关联与生成是两件事
踩过的坑:用户说「取消关联」,他其实想要「取消自动记账」。
而系统把两者绑在一起——有关联就会生成。
最终设计:
关联科目 + 资金账户 → 生成的前提
取消关联 → 自动撤回它已经生成的凭证(对称、可逆)
5.3 计划改了,派生数据会过期
踩过的坑:生成到账本之后,再改预算金额,已生成的凭证原封不动
(代码遇到 alreadyPosted 直接跳过)。想更新只能一个月一个月地撤销重来。
正确的同步语义:
| 情况 | 处理 |
|---|---|
| 计划里还有它、内容变了 | 删掉旧凭证 + 按新内容重新生成 |
| 计划里已经没它了 | 删掉旧凭证 |
| 没变 | 不动 |
| 已对账 | 跳过并如实报告(改了就跟银行账单对不上) |
| 没生成过的月份 | 不动(那是「生成」的职责,不能顺手多生成一堆) |
5.4 时点事件与逐月事件分开处理
5.5 别漏掉「期初」和「折旧」
踩过的坑 ①:预算里设了期初盈余 8 万,但账本里没有对应凭证
→ 预算的历史盈余和账本的净资产差整整 8 万。
踩过的坑 ②:预算按 -15%/年 折算房产市值,账本按原值记
→ 差额逐年累积,到 2040 年差 94 万。
5.6 时间边界要写对
踩过的坑:balanceOf 写的是「首期还款之前 = 原始本金」,
没有区分「还没放款」和「已放款但未到首期」。
结果贷款 9 月才发放,1~8 月的净资产却被算成 -96 万。
5.7 每次都要验会计恒等式
资产 − 负债 = 期初权益 + 收入 − 支出
5.8 差异必须能拆解到分
验证有效的做法:发现差 675,700 时,逐项拆解
预算里有、账本里没有的预估开销 +676,000
├─ 家庭开销 52 个月 × 5,000 = 260,000
├─ 爸爸医疗健康 40 个月 × 10,000 = 400,000
└─ 房租 4 个月 × 4,000 = 16,000
账本里有、预算里没有的手工记账 −300
└─ 一笔手机上记的「儿子手机」
────────────
675,700 ✅ 分毫不差
5.9 统计 SQL 别漏软删除
踩过的坑:写对账/汇总 SQL 时用
LEFT JOIN transactions t ON t.id = s.tx_id AND t.deleted = 0
看起来没问题,但软删除交易的 splits 仍然会被 SUM 进去
(LEFT JOIN 的条件在 ON 里,不匹配时 t.id 为 NULL,但 s.amount_cents 照样累加)。
正确写法:
COALESCE(SUM(CASE WHEN t.id IS NOT NULL THEN s.amount_cents ELSE 0 END), 0)
5.10 写入路径的六条纪律(记账类应用尤其重要)
这几条是「数据一旦写坏就很难发现」的防线,做任何有金额/有状态的应用都适用。
① 金额全程用「分」为整数,绝不用浮点
// ❌ 0.1 + 0.2 !== 0.3,在钱上是事故
// ✅ 全部以「分」存整数;展示时除 100;解析输入时四舍五入成整数
const amountCents = Math.round(parseFloat(input) * 100);
② 服务端强制借贷平衡,不信前端传过来的数
③ 写操作包事务
BEGIN IMMEDIATE; -- 立刻拿写锁,避免并发写冲突
... 多条 INSERT/UPDATE ...
COMMIT; -- 任何一步出错 → ROLLBACK
④ 幂等键:重试不能变成重复记账
⑤ 软删除 + 部分唯一索引(唯一约束要排除已删除行)
-- ❌ 唯一索引包含已删除行:删掉后重建同名/同 clientId 的记录会失败
CREATE UNIQUE INDEX uq_tx_client ON transactions(book_id, client_id);
-- ✅ 只对未删除的行生效
CREATE UNIQUE INDEX uq_tx_client ON transactions(book_id, client_id) WHERE deleted = 0;
⑥ 生成类操作要可撤销
5.11 计算引擎要能兜底,不能死循环
凡是「按公式逐期推算」的东西(贷款摊销、折旧、分期、配额),边界一定要写死:
六、安全清单
6.1 公网可达 = 内网信息一律不下发
踩过的坑:设置页显示「手机访问 http://192.168.44.4:51194」「数据目录 E:...」「数据库大小」。
更严重的是接口层就在下发,而且两个公开接口也在下发:
| 接口 | 是否需要登录 | 泄露了什么 |
|---|---|---|
/api/health |
❌ 公开 | 内网地址 |
/api/auth/status |
❌ 公开 | 内网地址 |
/api/system/info |
✅ 需登录 | 内网地址、数据目录、数据库完整路径、库大小、运行平台 |
| 登录页 | ❌ 未登录可见 | 把内网地址印在表单下面 |
6.2 隧道隐藏了什么、没隐藏什么
| 公网攻击者 | 同一局域网的人 | |
|---|---|---|
| 真实 IP | ✅ 隐藏 | — |
| 本地端口 | ✅ 隐藏 | ❌ 暴露 |
| 明文 HTTP 访问 | — | ❌ 明文可达 |
踩过的坑:应用监听 0.0.0.0,带着笔记本连任何 Wi-Fi,同网段的人就能明文访问:
curl http://192.168.44.4:51194/api/health # → 200,明文
6.3 TLS 在中间终止 —— 中间方能看到明文
手机 ══加密══► 中间服务器 ══加密══► 你的电脑
▲
在这里解密,登录密码也是明文
这是所有「中间人转发」架构的共性,不是某家的缺陷。
6.4 平台账号被盗 = 钓鱼(比密码弱更危险)
攻击者拿到你的隧道平台账号
→ 把域名映射改成指向他自己的服务器
→ 放一个一模一样的登录页
→ 你输密码 → 密码直接进他手里
你完全无法分辨——地址栏、证书、页面全都对。
6.5 其他必做
6.6 一个反直觉的点
自定义域名的证书会进公开的证书透明日志(CT log),
任何人都能搜到你的子域名。
6.7 TRUST_PROXY 两个方向都会出事
这个开关决定「要不要采信 X-Forwarded-For」,开和不开都有风险:
| 情况 | 后果 |
|---|---|
监听 0.0.0.0 但没开 TRUST_PROXY |
登录限流按直连来源算;攻击者可伪造 XFF 绕过限流。局域网同网段还能明文直连 |
| 有可信反代但没开 TRUST_PROXY | 限流把所有用户当成同一个来源(代理 IP),一个人触发就锁住所有人 |
| 没有可信反代却开了 TRUST_PROXY | 客户端可以随便伪造来源 IP,限流形同虚设 |
6.8 备份与恢复的纪律
备份:
恢复(这一步出事代价最大):
6.9 时区:日期类应用的隐形杀手
七、测试与排查方法
7.1 每个 bug 都要有测试守住,而且测试要真能捕获它
踩过的坑(断言写错):
| 错误断言 | 问题 |
|---|---|
created.some(x => x.sourceType === 'opening') |
created 里根本没有 sourceType 字段 |
summary.items.some(x => x.sourceType === ...) |
summary 是按月聚合,没有 sourceType |
assert.ok(r.json.dbSize > 0) |
字段已经删掉了,应该反过来断言不存在 |
扫描整页文本找 127.0.0.1 |
「登录设备」卡片正常显示会话 IP,误报 |
loanBalanceCents === 0 |
那是所有贷款的合计,测试库里有别的贷款 |
7.2 浏览器测试能发现「光看代码发现不了」的问题
本次靠浏览器测试抓到的:
-
弹层被表单收尾的
closeSheet()一起关掉(第五节 4.3) -
月份标签不更新(第四节 4.4)
-
新的硬性表单校验把既有流程挡住了
7.3 测试之间不要互相污染
踩过的坑:为了验证登录页,测试里清了 localStorage,
结果后续用例的登录态全坏,一片红。
7.4 用「增量」隔离共享状态
有效的做法:验证「某笔贷款在放款前的余额」时,
loanBalanceCents 是所有贷款的合计,测试库里还有别的贷款。
const delta = (m) => afterAt(m) - beforeAt(m); // 加这笔贷款前后的差
assert.equal(delta('2026-01'), 0);
7.5 排查顺序(省时间的)
① 换个客户端/通道试 → 判断是服务端还是链路
② 看进程在不在 → 判断是「没运行」还是「运行了但不对」
③ 看日志(应用日志 + 错误日志分开)→ 判断崩溃还是被结束
④ 看服务的实际命令行 / 配置 → 判断配置有没有生效
⑤ 直接打接口看响应 → 判断是后端还是前端
⑥ 看前端资源版本 / 缓存 → 判断是不是浏览器跑了旧代码
7.6 改完代码后必须验证
7.7 端到端测试至少要覆盖这些
不是「随便点两下」,而是一条能贯穿的链路 + 几个容易坏的角落:
7.8 排查口诀与应急卡(贴在运维手册最显眼处)
先换通道 → 看日志 → 看进程 → 重启电脑
不要一上来就重装。
几个高频判断:
| 现象 | 结论 |
|---|---|
| 应用日志有新记录 | 请求到了,问题在回程(隧道/网络) |
| 应用日志完全没动静 | 请求没到,应用或通道没起 |
| 错误日志为空 + 日志戛然而止 | 被结束,不是崩溃 |
| 地址栏有锁但页面空白 | 通道还在,后端没了(或映射到期) |
| 白屏 / 样式乱 | 浏览器缓存旧资源 → 清缓存 |
| 反复被守护拉起 | 应用有真 bug → 看错误日志,不是守护的问题 |
应急卡(手机打不开时按顺序):
① 换另一个网址试(两条通道互为备用)
② 看 data\server.log 有没有新记录
③ 看守护日志:data\watchdog.log —— 是不是刚被拉起过
④ 看映射/隧道是否到期
⑤ 重启电脑(所有守护会把服务重新拉起来,等 1 分钟再试)
八、协作与流程
8.1 先对齐语义,再动手
踩过的坑:用户说「家庭开销不应该关联」,
字面理解是「清空关联字段」,实际想要的是「不要自动记账」。
这两个在当时的系统里是绑在一起的。
8.2 大改动先给方案
8.3 每次提交说清楚「改了什么、为什么」
8.4 文档分三类,各管一段
| 文档 | 回答什么问题 | 给谁看 |
|---|---|---|
| README | 这是什么、怎么用 | 使用者 |
| 部署文档 | 当初怎么搭起来的(含踩坑记录) | 未来的自己 |
| 运维手册 | 坏了怎么查、东西在哪 | 出事时的自己 |
| 原理文档 | 为什么这样设计 | 想深挖的自己 |
| 本清单 | 下次做类似项目怎么避坑 | 下次的自己 |
8.5 待办要显性化
8.6 定期做一次「外部视角」的代码审查
自己写的东西看不出自己的问题。这个项目在一次外部走读里被点出三类问题,
都是自己反复看过却没发现的:
| 类型 | 实例 | 为什么会漏 |
|---|---|---|
| 同类函数不一致 | 对账模块里 5 个函数查会话都带 AND book_id = ?,唯独 setMarks 漏了 |
写的时候「照着上一个抄」,抄漏了没人提醒;单账本下没后果,更难发现 |
| 算了又不用 | const sign = naturalSign(type) 紧跟一句 void sign |
看着「有这行代码」,其实是无用功;void x 是给自己看的遮羞布 |
| 一行两句 | if (...) throw ...; sendJson(...) |
挤压在一起的语句提交时容易看漏 |
反过来也要注意:外部审查的结论要自己核实。
同一份审查把「测试失败」归因为「跨用例状态污染」,
实际是当时断言写在了错误的数据结构上——现象对、归因错。
收到结论先复现一遍,再决定改什么。
附:交付前自检清单(可直接勾)
守护与可用性
网络与访问
安全
数据一致性
备份与恢复
前端
脚本与编码
测试
运维准备
附:本次事故档案(供复盘)
| # | 现象 | 根因 | 教训 |
|---|---|---|---|
| 1 | 改了预算保存后没弹同步框 | Service Worker 返回旧缓存 + 版本号写死 | 代码类资源要网络优先;版本号要自动变 |
| 2 | jz.dlink.work 502,挂了 1 小时 |
cloudflared 进程静默退出,无人守护 | 常驻组件必须有守护 |
| 3 | 服务装上了但一直 Stopped | service install 写出的命令行不完整 |
装完必须查 binPath |
| 4 | 隧道 VBS 完全静默失败 | Program Files (x86) 的括号搞坏 cmd 引号 |
外部程序调用绕开 cmd |
| 5 | 服务安装脚本跑到一半中断 | $ErrorActionPreference='Stop' + 外部程序写 stderr |
调用外部程序时放宽 |
| 6 | 应用进程莫名消失,无错误日志 | 是我用临时会话启动的子进程,会话清理被连带杀掉 | 长期运行的进程要用系统级方式启动 |
| 7 | 净资产凭空为负 96 万 | 没区分「未放款」和「未到首期」 | 时间边界要全部列出来确认 |
| 8 | 预算与账本差 94 万 | 折旧只算在预算、没落账本 | 两边口径设计阶段就要对齐 |
| 9 | 差 675,700 查不出原因 | 拆解后发现是「未录入的预估开销」+「一笔手工记账」 | 差异必须拆解到分 |
| 10 | 汇总金额含已删除凭证 | LEFT JOIN 的 deleted = 0 写在 ON 里仍会累加 |
软删除表的聚合要显式 CASE |
| 11 | 切月份标签不更新 | renderMonthBar() 只在首次渲染调用 |
状态更新集中成一个函数 |
| 12 | 新弹窗一闪就没 | 单层弹层 + 表单收尾的 closeSheet | 用 afterClose 钩子 |
| 13 | 设置页泄露内网地址 | 接口层就在下发,两个公开接口也有 | 修在接口层,公开接口重点审 |
| 14 | README 被改乱码 | 用 PowerShell 做字符串替换,按 GBK 重写 | 文本文件别用 shell 改 |
| 15 | .cmd 报「不是内部或外部命令」 |
cmd.exe 按 ANSI 读不了 UTF-8 中文 | .cmd 纯 ASCII,中文进 .ps1 |
| 16 | 「取消生成 → 重新生成」一直失败 | 唯一索引没排除软删除行,旧记录仍占着 client_id |
唯一约束要问一句「删掉后还能重建吗」 |
| 17 | 自检误报「服务账号目录不存在」 | 普通权限读 System32\config 是「拒绝访问」,被当成「不存在」 |
权限不足 ≠ 资源缺失,读不到就别下结论 |
| 18 | 自检文件被写坏 | 又用 PowerShell 的 Set-Content 重写 .js,中文按 GBK 重编码 |
同一个坑踩了两次 → 已成硬规则 |
附:这份清单怎么用
| 场景 | 过哪些章节 |
|---|---|
| 新起一个自托管小服务 | 〇(守护)+ 二(平台坑)+ 六(安全)+ 5.10(写入纪律) |
| 要开放到公网之前 | 六(全部)+ 3.x(隧道)+ 交付前自检的安全段 |
| 跑了一段时间要体检 | 交付前自检(全部)+ 7.8(排查口诀) |
| 加新功能 | 五(一致性)+ 7.1~7.7(测试)+ 8.x(流程) |
| 换机器 / 从零恢复 | 六.8(备份恢复)+ 一(守护)+ 二(脚本编码) |
| 出了故障 | 7.8(口诀)→ 运维手册的故障对照表 |
最有价值的不是「做对了什么」,而是「为什么会做错」。
每条后面的「为什么」都对应一次真实的返工,别跳过。
浙公网安备 33010602011771号