踩坑地图与规避手册
口径:19 个坑全部来自 11-11 至 11-14 的记忆日志与 15 篇对话档案,只写已被验证的修复,不写「可能有效」。每个坑四件事:症状、根因、一次性修复动作、预防检查项。「一次性」的判据是——修完之后同类症状不再出现,且不留下新的技术债。
一、怎么用这张图
顺序是:拿症状去匹配表里的「症状」列 → 用「根因」确认是不是同一个病 → 执行「一次性修复」→ 把「检查项」写进下一个会话的开场白。不要跳步。这套项目里最贵的几次返工,都是因为症状相似但根因不同,直接套用旧修法(例如把所有 404 都当成路由问题,而实际是 basePath 少了尾斜杠)。
二、环境类:端口、构建、计划任务、编码
| # | 症状 | 根因 | 一次性修复动作 | 预防检查项 |
|---|---|---|---|---|
| E1 | 服务启动报 EACCES,换端口就好 | Windows 保留端口段 3002–3101 | 笔记站固定 3102,不再往 3002 试 | 新服务先查保留段;端口写进交接包,不临时改 |
| E2 | 新进程日志停在 Ready in xxx ms,端口不监听 | 服务运行中执行 next build,覆盖了正在被读取的 .next | Stop-ScheduledTask → build → Start-ScheduledTask | 构建前后比对 .next/BUILD_ID 时间戳;build 前必须确认端口已释放 |
| E3 | 重启后页面报 ChunkLoadError,或任务 LastTaskResult=267014 | Start-ScheduledTask 对已运行任务不会真正重启,新旧 chunk 混用 | 先 Stop 再 Start;267014 时单独再 Start,等 15–20 秒 | Get-ScheduledTaskInfo 看 LastTaskResult:0x41301 才是运行中 |
| E4 | 计划任务 LastTaskResult=1,PowerShell 报「意外的标记 }」 | launch.ps1 被编辑器写回时丢了 UTF-8 BOM,PS 5.1 按 GBK 解析中文注释 | 恢复 BOM(前 3 字节 EF BB BF) | 改完 .ps1 用 Format-Hex 看头三字节;改脚本后必须实跑一次 |
| E5 | Start-ScheduledTask 返回成功,但 LastRunTime 不动、端口不监听 | 计划任务 S4U 主体失效(密码/策略变更后常见) | 导出 XML 备份,重注册为当前用户 -LogonType Interactive | 备份留在 ops/kaoyannotes.task.xml;启动失败先怀疑 S4U 再怀疑代码 |
三、工具链类:删除、Prisma、basePath、Playwright、转义
| # | 症状 | 根因 | 一次性修复动作 | 预防检查项 |
|---|---|---|---|---|
| T1 | rm -rf .next 被拒,提示 SAFE_DELETE_BULK_CONFIRM_REQUIRED | safe-delete 钩子对超过 50 项的批量删除强制确认(.next 有 399 项) | 改用 PowerShell Remove-Item .next -Recurse -Force | 删目录前先估算条目数;批量删除一律走 PowerShell |
| T2 | prisma generate 持续 EPERM,换目录才能过 | 3102 端口的 SYSTEM 级进程锁住引擎 DLL,普通权限 taskkill 拒绝访问 | 先停服务、杀端口进程,再 generate;输出目录固定为 generated/db | 读 prisma-generate.log 确认是锁还是语法;不要靠换目录绕过(曾迁四次:prisma→db→db2→db3) |
| T3 | 客户端 res.json() 抛 Unexpected token '<',或点导航 404 | basePath 下 fetch 少了尾斜杠被 308 到 HTML;裸 <a> 不带 /notes 前缀 | 统一走 lib/api.ts 的 API 常量并保留尾斜杠;站内跳转用 Next Link | 新增路由后手点一遍导航;接口地址必须能在源码里搜到唯一出处 |
| T4 | Playwright 截图/断言集体失效:找不到 chromium、networkidle 超时、点了没反应 | 插件要 chromium-1210 而本机只有 1243;页面有轮询永不 networkidle;未水合就点击 | executablePath 指向本机 Edge;改 domcontentloaded + 2.5s;点击前等元素可见且可交互 | 脚本头部固定这三条;直连 3102 一律匿名,权限用例必须走 127.0.0.1:3000 |
| T5 | Bash 里路径被吃掉、正则报 SyntaxError | 反斜杠在 JSON→JS→正则三层转义;C:\... 在 POSIX shell 里被当转义 | 工具参数一律绝对路径 + 正斜杠;匹配字面反斜杠用 split/join 或 String.fromCharCode(92) | 命令行出现反斜杠就停下来改写法;路径用引号包住 |
四、数据类:存量、版本、度量、索引
| # | 症状 | 根因 | 一次性修复动作 | 预防检查项 |
|---|---|---|---|---|
| F1 | 新参数上线后,用户说「实测没搞定」 | 参数只写进新生成的数据(bare 只进新围栏),存量 20 张表仍是旧围栏 | 改渲染默认值(bare !== false 即裸表),显式 bare=0 才画框;不回改历史正文 | 任何「新增开关」先问一句:存量数据是否也走这条路径 |
| F2 | 清理旧版本时误删当前版,或保留数比设定多一版 | 当前版可能不是最大版本号(切换过主版本) | 保留集 = 最近 N 版 ∪ 当前版(isPrimary / SheetDoc.version),当前版无条件保留 | 删版本前先读 isPrimary;批量清理先 dry-run 打印保留集 |
| F3 | 编辑器里不折行,页面上折行;列宽怎么调都不齐 | 两端字体栈、字号、padding 不同源,实测差 9%(34.1px vs 37.3px) | 抽 SHEET_FONT_FAMILY / SHEET_FONT_PX / FIT_PAD_X 常量共享,两端 CSS 对齐;存量跑 sheet-refit.ts | 同一内容在编辑器与页面各截一张同宽图比对;度量只允许一个来源 |
| F4 | 表格「引用数」永远是 0,但功能看着正常 | syncSheetRefs() 写了却没接到任何写入口 | 接到建笔记、存正文、切主版本、删笔记四处,再跑回填脚本 | 改完立刻读一次计数;新写的同步函数必须有调用点,否则就是死代码 |
五、协议与网络:节点、DNS、缓存、回源、上行
| # | 症状 | 根因 | 一次性修复动作 | 预防检查项 |
|---|---|---|---|---|
| N1 | HTTPS 隧道怎么都起不来,同节点 HTTP 正常 | 平台侧屏蔽了该节点的 443(同节点 80/8500/15682 全开,走外部代理也不通) | 在控制台把隧道切到另一节点(.184 → .183),重新下发 frpc 配置 | 先用 curl -m 5 -k https://<节点IP> 判 443 通不通,再查本机配置 |
| N2 | IPv4 DNS 改完了,域名还是解析到旧 IP | Windows 优先使用网卡 IPv6 DNS fe80::1(路由器 RA 下发),IPv4 改了不代表 IPv6 改了 | netsh interface ipv6 set dnsservers name="以太网" source=static address=2400:3200::1 | Get-DnsClientServerAddress 必须按 AddressFamily 分开看;改 DNS 两族一起改 |
| N3 | 权威 DNS 已是新 IP,本机仍访问旧站 | 运营商/路由器缓存未过期(TTL 603s,实测约 8 分钟) | curl --resolve 域名:443:新IP 强制验证;curl --doh-url 绕过本地缓存 | 判「是不是缓存」永远先做一次 --resolve;不要在缓存期内改配置 |
| N4 | 本机权限测试全过,公网行为相反(登录了仍提示未登录) | 直连 3102 一律匿名,身份头由门户 3000 注入;门户必须保留原始 Host 头 | 权限类问题一律在 127.0.0.1:3000 复现;门户反代不改写 Host | 交接包里写死「3102 只用于接口、3000 用于身份」;密钥文件改动后靠 mtime 热感知 |
| N5 | 上传大文件报 Unexpected token '<',接口日志无记录 | 公网上行约 98KB/s,12MB 单包要 127 秒,网关 524 返回 HTML 错误页 | 改协议:目录批量导入 + 图片每批 12 个串行 + readJson() 把 413/524/502 翻成人话 | 上传前先测上行;单包上限按「上行 × 60 秒」估算,不按文件大小拍脑袋 |
| N6 | 公网 http 下「复制」永远失败,https 正常 | navigator.clipboard 只在安全上下文存在,http 下是 undefined | 统一走 copyText():clipboard → 隐藏 textarea + execCommand → 手动复制兜底 | 站点存在 http 入口时,所有剪贴板调用必须降级;用 isSecureContext 判 |
六、三个最贵的坑,值得单独读
1. Prisma 输出目录:绕过的代价是四个目录
症状是 EPERM,根因是运行中的进程锁住 DLL。当时的处置是「换个输出目录继续干活」,四次迁移留下 generated/prisma、generated/db、generated/db2、generated/db3 四套路径,schema.prisma 的 output 与 lib/db.ts 的 import 每次都要同步改。正确顺序只有一句:先停服务、杀端口进程,再 generate。判断标准也很硬——如果修复动作需要改业务代码的引用路径,那它就不是修复,是搬家。
2. 443 与 IPv6:两层故障串联,最容易被误判成「我这边没问题」
第一层是平台屏蔽 443,本机配置、证书链、frpc 全部正确;第二层是 IPv6 DNS 缓存旧 IP,IPv4 改完仍不通。这一段跨了两天,中间全是无效分支:改代理名复现、TXT 验证、双隧道并存。值得记的不是修法,是止损线:当「自证清白」超过两轮,就应该停下来要一个外部可观测指标(curl --resolve 的返回码),而不是继续在本机找证据。
3. bare 存量:参数化只覆盖新数据,是最隐蔽的一类 bug
用户的原话是「升级后表格自动隐藏外框,实测没搞定」。查下来,代码没错——bare 只写进了升级那一刻新生成的围栏,而用户那篇笔记的 20 张表是在 bare 上线前 27 分钟升级的,正文里全是旧围栏。全站 bare 围栏 2 条、普通围栏 23 条。修法是改渲染默认值,而不是回改 20 篇正文。这类坑的共同结构是:功能按「新数据」设计,验收按「新数据」做,用户拿「老数据」验收。
七、这四类坑的共同结构
把 19 个坑横着看,只有三种失败模式。
第一种是在错误的层修问题:应用层改 z-index 修层叠上下文、换输出目录修文件锁、改代理名修平台 443。三种都属于「用可控的动作应对不可控的原因」,短期保进度,长期留债。
第二种是单侧验证:只测桌面不测手机、只测编辑器不测渲染、只测本机不测公网、只测新数据不测存量。第六节第 3 条就是典型。
第三种是把重试当修复:构建失败连跑五次、OAuth 连等两轮、Start-ScheduledTask 连点三次。重试只能掩盖不确定性,连续两次同错就该停下来读日志。
对应的纪律只有三条:改之前先问「这一层对不对」;验收必须两端各一次;同一症状出现第二次,就把它写成检查项,而不是第三次再修。