1869 字
9 分钟
给博客修一条到公众号的发布管线,中途撞见另一个自己在改同一个仓库

晚上收拾完一篇新文章,我给 Claude 甩了一句话:集成微信公众号自动 post 管线。

就这七个字。然后我去忙别的了。

这活儿听起来简单:markdown 转个 HTML,调个 API,发出去完事。但凡这么想的人,大概率没读过微信公众号的开发者文档。

Claude 先去查了一圈现状。结论不算友好:公众号的正文只认内联 styleclass 和外部 CSS 一律被剥掉;外链 <a> 在正文里点不动;图片必须先传到微信自己的域名,写死的外链 URL 会被过滤掉。市面上现成的工具,doocs/md 只管转换排版,没有发布接口;wenyan-cli 倒是能一键发草稿箱,但那是另一套工具链。反正要写自己的 reconcile 逻辑(博客文章和公众号草稿箱要能对得上账),转换器也就顺手自己写了:markdown-it 接管解析,代码块用 shiki 逐个 token 上色成内联 style,外链降级成「文字 + 上标序号」、文末列一节参考链接,图片先留个占位符等传完图再回填真实地址。

写转换器的时候,Claude 顺带查文档查出了一颗雷:2025 年 7 月起,微信官方回收了个人主体和未认证账号的草稿箱、发布、素材这三个接口的权限。官方文档原话摆在那儿,白纸黑字。我的公众号是个人主体没错,管线眼看着还没上线就可能已经作废——但「个人主体」和「个人认证」不是一回事,具体我这个号算不算在回收范围里,光看文档看不出来,只能拿真凭据试一次。

于是管线设计成了两级:能调 API 就调,调不了就退化成生成一份内联样式全齐的 HTML,浏览器打开全选复制,手动粘贴进公众号编辑器——排版不丢,就是手要动一下。这条退路后来一次也没用上,但当时不知道,先垫好总没错。

配好 AppID 和 AppSecret 一测,第一下就撞上 40164:调用方 IP 不在白名单。这个好办,去后台加一下 IP。加完再测,draft/add 直接成了,草稿箱里真出现了一篇文章。我是个人认证账号,2025 年那波回收没伤到我这儿。悬了半天的心放下了。

本来故事到这儿该收尾了。但我瞄了眼刚建好的那份草稿,回了一句:这个发布者应该是 PHM。

PHM 是我自建的那个服务平台(烤肉流水线那篇提过),一台云主机,FastAPI 起服务、systemd 管进程、Caddy 管路由,push 到 main 自动部署。让 PHM 当发布者的道理很直接:本机直连的话,AppSecret 和调用凭证都躺在我的 Mac 上,家里 IP 一变就得去公众号后台重新加白名单——这活儿以后大概率是自动跑的,谁去盯着家里 IP 变没变?PHM 的服务器 IP 是稳定的,凭证也只用在服务器上,本机自始至终不碰真正的密钥。

架构一改,活儿就不是「调个 API」这么轻了:得在 PHM 那边起一个新服务,四个转发接口(传图、传素材、建草稿、发布),拿平台自带的鉴权 SDK 挡住除我之外的所有请求,本机这边把直连的代码抽成一层接口,让「本机直连」和「经服务器中继」共用一套上层逻辑。

写到一半,我去瞄了眼多 agent 状态面板——顺手养成的习惯,家里几个仓库经常同时开着好几个 Claude 会话。这一眼撞出个提示:

!! COLLISION [2] /Users/lishuyu/Codes/api
- 1a628cbb ... cwd=.../services/wechatservice
- 3fea634b ... (no summary yet)

同一个仓库,另一个会话也在跑。它在忙 jobtracker 的事,跟我这次的目录没有交集,但「没有交集」是我读了它的状态才敢下的判断,不是巧合保佑的。我留了句话过去:我正在新增 services/wechatservice/ 并准备推主干,别碰这个目录和端口。多 agent 协作最容易崩的地方从来不是逻辑写错,是两个人(或者两个 agent)同时以为自己是仓库里唯一的写手。

服务写完,部署到 PHM 上,跑通了一次完整链路:转换、传图、传封面、建草稿——这次草稿是经服务器转发建的,不再直连微信。本机的配置从「AppID/AppSecret」换成了「服务器地址 + 一个只认自己人的令牌」,微信那头看到的调用方永远是服务器的固定 IP。

我以为这就完工了。结果我又问了三个问题:现在 push 会自动触发草稿吗?能改文章吗?能删文章吗?

第一个问题问出了个设计漏洞:管线当时挂在本机的自动提交脚本上——文章存到本地、脚本 5 分钟防抖后推送、推送成功再顺手调一次同步。这套逻辑只在这台 Mac 上跑得动。真要交给别人改文章,或者哪天从另一台机器 push,同步压根不会触发。这不是我要的 SSOT(唯一真相源)。改法也不复杂:把同步逻辑挪进 GitHub Actions,任何一次碰到文章目录的 push,不管从哪儿推的,CI 都会跑一遍——本机脚本反倒该把这段逻辑摘干净,只管提交推送,不该再管公众号的事。

第二、第三个问题合起来是一条硬规矩:只有草稿能被自动改、自动删,发布过的文章永远不碰,改也不行。 这条我没打算留任何后门。公众号确实有更新草稿、删除草稿的接口,但发布之后的文章要么没有编辑接口,要么改动次数和字数被死死限制着——反正这道口子留给人,不留给自动化。落到代码上:state 里记一份内容哈希,文章改了、哈希对不上、状态还停在「草稿」,就调接口原地更新同一份草稿;文章删了或者被打回 draft: true,对应草稿跟着删掉;但凡状态翻到「已发布」,就此变成终态,代码路径上连删除接口的调用都没写——不是权限没给,是我压根没实现那个功能,想改也无从改起。

收尾那阵,本机的自动提交脚本又推了一篇新文章。没等我伸手,GitHub Actions 自己跑完了同步,提交记录里多了一行 chore: wechat sync state [skip ci]。管线在我没看着的时候,自己把活干了。

这次没有惊天动地的教训,就是几个朴素的判断反复应验:能自动化的东西,先想清楚谁该拿到那把钥匙——本机手快,但服务器才该是长期握着密钥的那个;共享的仓库里,先看一眼有没有别人在场,比撞上了再道歉便宜得多;至于「删除」和「发布」这种不可逆的动作,自动化该做的是把门关上,而不是加一道锁再指望自己不手滑。

草稿箱里现在躺着几篇新文章,等我有空点一下发布。剩下的,都交给管线了。

给博客修一条到公众号的发布管线,中途撞见另一个自己在改同一个仓库
https://blog.lishuyu.app/posts/公众号发布管线上线记/
作者
猫猫魔女
发布于
2026-07-02
许可协议
CC BY-NC-SA 4.0