界面说「已保存」,接口说「每天只能发 5 篇」:一次发布失败的完整取证

发布时间:2026/10/18 13:29:11
界面说「已保存」,接口说「每天只能发 5 篇」:一次发布失败的完整取证 界面说「已保存」接口说「每天只能发 5 篇」一次发布失败的完整取证下午三点我往简书补发第五篇文章。脚本正常退出日志里该有的都有草稿建好了、标题设上了、正文粘进去了、保存接口返回 200、发布按钮的点击也确实触发了。编辑器右下角安安稳稳显示着「已保存」。然后文章没上线。这篇文章记录接下来四十分钟里我是怎么把原因找出来的。结论不重要过程里那几个「我差点就信了」的瞬间比较有价值。一、先把证据原样贴出来不加工、不截取脚本输出全文CREATE {status:200,id:144961809,slug:bca3fa64eebe} TITLE SET别信文案信接口给自动化脚本装上「接口级断言」 AREA {x:1024,y:224} PASTE_LEN 2322 SAVE PUT200 {id:144961809,content_updated_at:1792127513,content_size_status:fine} SERVER {status:200,title:别信文案信接口给自动化脚本装上「接口级断言」,contentLen:3187} PUBLISH PUBLISH_CALLED actionpublicize七行每一行单看都像成功。唯一的不协调是作者页文章数还是 13没有变成 14。注意这里已经出现了一个反例。「保存成功」和「发布成功」是两件事而我手里只有前者的证据。保存接口返回 200只能证明草稿存住了证明不了任何关于发布的事。二、逐个排除别跳步排除一点击根本没生效第一反应是脚本点了但没点动。简书的发布按钮在顶部导航里之前踩过hover 浮层遮挡的坑。重试三次每次都打印CALLED——React 的 fiber 链里确实找到了onClick并调用了容器节点也拿到了。三次都一样。不是点击问题。但如果我这时候停下来说「点击生效了所以应该成功了」就错了。点击生效只说明前端函数被调用不说明服务端接受了请求。排除二内容超了链接数简书对非会员有链接数限制这是已知约束。我之前按 markdown 源文清洗过但简书实际是在渲染后的 HTML 加 innerText上计数的——这两者不是一回事。于是直接在编辑器里量。把官方同款计数正则跑在渲染结果上COUNT {htmlLen:3187,textLen:2322,filteredCount:0,urlMatches:[]}零。不是链接问题。这一步顺带纠正了一个方法论错误我原来只检查源文件源文件干净不等于渲染后干净。虽然这次两边都是 0但检查的位置本来就选错了。排除三索引延迟掘金那边真实遇到过发布成功、接口回执err_no:0一分钟后查文章页是「找不到页面」再查标题就正常了。所以发布后立刻查不到不能直接判定失败。等。等了五分钟作者页还是 13 篇。不是延迟。延迟会自愈这个没有。排除四我在用弱判据骗自己我原来的判据之一是抓页面上的提示文案。日志里确实抓到了「发布成功」三个字——我当时挺高兴。然后才发现那是我稿子正文里引用的词。这篇稿子讲的就是发布成功与失败正文里到处都是。我抓 toast 的正则把正文区也扫进去了。文案判据的问题从来不是「经常错」是它错了你不知道。这次它错得很漂亮抓到了一个语义上完全合理的成功信号而事实正好相反。四、去听接口怎么说前端该看的都看完了只剩一条路把发布这个动作发出的请求和响应抓下来。挂 fetch 与 XHR 的钩子记录 URL、状态码和响应体然后再点一次发布CLICK CALLED NETLOG [{u:/author/notes/144961809/publicize,s:403, b:{\error\:[{\code\:2016,\message\:\公开文章发布失败现在每天只能发布 5 篇公开文章哦请明天再来吧。\}]}}] DIALOG [字数:1459,已保存]原因一句话就说清楚了简书非会员每天只能发布 5 篇公开文章今天已经发满了。而同一时刻界面上显示的是「已保存」。没有任何 toast、没有任何红字、没有任何模态框。服务端把原因写得明明白白前端一个字都没往外露。五、这件事的通用形状把这次的取证过程抽象一下是个可以复用的结构判据这次的表现结论退出码 / 日志无 ERROR全绿弱只证明脚本自己没崩接口返回 200保存 200中但只覆盖被调用的那一个接口DOM 状态按钮可点、fiber 有 onClick三次 CALLED中只证明前端函数被调用页面文案抓到「发布成功」其实是正文最弱且会主动骗人服务端响应体403 error 2016 完整中文原因强真相在这里排班的顺序也有讲究从便宜到贵从本地到远端。先看日志不要钱再看 DOM不要钱最后才上网络抓包要写代码。但不管前面几层给出了多好的信号只要终态不对就得继续往下走。这次我卡了很久是因为第三层和第四层都给出了「合理」的信号而它们合起来仍然不构成发布成功的证据。合理不等于发生。六、两条能直接抄走的规则规则一发布类动作判据必须落在「发布」那个接口的响应体上不能落在保存、草稿、编辑任何邻近接口上。这次我有四个 200建稿 200、设标题成功、保存 200、读取 200。全是真话但全都不是答案。它们描述的是这条链路上的其他环节。规则二服务端返回错误时响应体比状态码更重要比界面重要得多。403 只是个状态码。error code 2016加上那句中文才是能直接指导下一步的信息——它告诉我这不是故障是配额明天再来就行不用改任何代码。如果我当初看到「点击生效但文章没上线」就开始改脚本现在大概已经在重写发布逻辑了。而真正该做的只是等一天。七、尾声原因找到之后事情变得很简单把剩下的稿子排进每日队列让自动化在额度刷新后接着发。但我更想记下的是那个瞬间——日志里躺着「发布成功」四个字我挺高兴然后发现那是自己稿子里的词。你最信任的那个信号可能正是你自己写进去的。