如何搭建企业微信机器人,用机器人发博文、动态
没服务器又想随手发博客,这篇记录我搭的企业微信机器人:给机器人发一条消息,几分钟后它就变成博客上的一篇文章,全程不碰后台。
整体是怎么跑起来的
先看整条链路。从你发消息到文章上线,中间一共四跳:
企业微信(收消息 + 被动回复) │ 解密后的 XML ▼EdgeOne 边缘函数(解密 / 验签 / 加密回包) │ /internal 转发(hybrid 模式) ▼Cloudflare Worker(攒草稿 → 拼 markdown → 写 GitHub) │ commit 一个 .md 文件 ▼GitHub(Actions 自动构建部署博客)最绕的是「被动回复」。企业微信要求你在收到回调后 5 秒内把回复写进响应体,而且必须是加密的 XML。真实的写博客动作(调 GitHub API、等部署)绝对不能在 5 秒里同步做完,否则超时企微就当你没回。
我的做法是:边缘函数收到消息,先解密、立刻拼一句「已记录」加密回包返回(满足 5 秒),再在后台把内容丢给 Worker 去写 GitHub。回不回消息和写不写文章是两条线,互不阻塞。
EdgeOne 边缘函数,http 响应返回之后,普通 fetch 会被运行时强制终止。 向后端 Cloudflare Worker 转发任务,必须使用 event.waitUntil(fetch(…)) 包裹转发逻辑。 如果不包,企微能收到 “已记录” 回复,但后续写 GitHub 逻辑直接不执行,很难排查。
踩坑实录
坑一:以为是源站被墙,其实是加密算法写错了
最早我全放在 Cloudflare 上,企业微信回调死活访问不通。当时直觉是「CF 源站在国内被掐了」,于是把入口迁到 EdgeOne(国内 CDN,理论上更稳)。
结果迁过来还是不通。最后一行行查,发现是我自己写的 AES 加解密有 bug——企微回调用的是 AES-256-CBC + PKCS7,密文前面还要拼 16 字节随机串和 4 字节消息长度,回包还要按它的格式再加密一遍。只要哪一步字节对不上,企微就直接当你非法请求,表现就是「回调不通」。
修好之后我才确认:Cloudflare 源站本来是通的,当初根本不用换入口。为了不依赖任何第三方库(CF / EO 的运行时都不一定让你装包),我直接用了纯 JS 实现,核心是拿 Web Crypto 的 AES-ECB 当 AES-256-CBC 的轮函数,自己拼 CBC 链和 PKCS7 填充:
// // Web Crypto 不直接提供 AES‑256‑CBC,基于原生 AES‑ECB 手动实现 CBC 模式,无第三方依赖async function aes256cbc(key32, iv16, data, decrypt) { const wkey = await crypto.subtle.importKey("raw", key32, "AES-ECB", false, ["encrypt", "decrypt"]); const pad = (b) => { const n = 32 - (b.length % 32); const p = new Uint8Array(n).fill(n); const o = new Uint8Array(b.length + n); o.set(b); o.set(p, b.length); return o; }; const unpad = (b) => b.subarray(0, b.length - b[b.length - 1]); let prev = iv16, out = new Uint8Array(decrypt ? data : pad(data)); const step = decrypt ? data.length / 16 : out.length / 16; for (let i = 0; i < step; i++) { const blk = out.subarray(i * 16, i * 16 + 16); const d = new Uint8Array(await crypto.subtle[decrypt ? "decrypt" : "encrypt"]({ name: "AES-ECB" }, wkey, blk)); for (let j = 0; j < 16; j++) d[j] ^= prev[j]; if (decrypt) prev = blk.slice(); else { prev = d.slice(); out.set(d, i * 16); } } return decrypt ? unpad(out) : out;}这段在 Cloudflare Worker 和 EdgeOne 边缘函数里各放了一份,逻辑完全一样,企微都能正确解开。也是因为它没有 npm 依赖,后面做纯 CF / 纯 EO 才能直接复制过去用。
坑二:回包里多写一个 AgentID,企微直接静默丢弃
这个坑最麻烦,因为它不报错。
被动回复的 XML 结构企微是有标准 schema 的,里面没有 AgentID 这个字段。我当时顺手把 agentid 也塞进了回包(想着反正带上呗),结果企微收下后什么都不回、什么都不报错,表现就是「收不到任何回复」。我对着日志看了半天,加密对了、验签对了、回包也返回 200 了,就是没动静。
最后发现是那个多余的 AgentID 字段让企微判定回包非法,整条静默丢掉。把回包 XML 里的 AgentID 删掉、重部署,机器人就能收到消息了。
// 回包内层 XML:千万别带 AgentIDconst xml = `<xml> <Encrypt><![CDATA[${encrypt}]]></Encrypt> <MsgSignature>${sig}</MsgSignature> <TimeStamp>${ts}</TimeStamp> <Nonce>${nonce}</Nonce></xml>`;机器人到底能干什么
指令集现在是这样的,发文字给它就会攒成一个草稿,指令控制行为:
| 指令 | 作用 |
|---|---|
/done 或 /发布 | 把当前草稿发布成文章或动态 |
/取消 /清空 /cancel | 丢弃草稿,下次从头写 |
/动态 | 切到动态模式(短内容,文件名用时间戳) |
/文章 | 切到文章模式(文件名用标题) |
/tags | 设置文章标签,如 /tags: 折腾, 教程 |
/状态 | 看看当前草稿攒了多少、什么模式 |
/预览 | 返回将要发布的 markdown 原文,不发 |
/修改 | 把草稿全文甩回给你,下次回复整体替换重算 |
/帮助 | 打印上面这一张表 |
文字里以「键: 值」开头的行会自动识别成文章属性,中英文键名都认(标题: / tags: 都行),剩下的当正文。几个常用的点:
- 文件名:动态固定
2026-08-14-020613.md;文章用 FILENAME_FORMAT 开关——title(默认)就是纯标题标题.md,date就是2026-08-14-标题.md。 - 自定义链接:写
slug: 05,发布后返回https://你的域名/posts/05;不设 slug 就退回文件名链接。 - 动态专属:还能写
location: 广西和pinned: true,直接进 frontmatter。 - 正文插链接:标准 Markdown
[文字](url)就行,链接行不会被误吞。 - 封面:明确写
封面:https://...才用;不写就image: api(随机封面),发的图只进正文不抢封面。
标记(@提及 / #话题标签)目前不可用,正文直接写文字即可。
三种部署模式
用环境变量 DEPLOY_MODE 切换角色。现在三种模式随时换:
-
hybrid(默认)
EdgeOne 当入口解密回包,真正的写博客交给 Cloudflare Worker 的
/internal。国内延迟最低,两套东西各司其职。 -
eo(纯 EdgeOne)
EdgeOne 自己解密、自己 dispatch、自己写 GitHub,草稿存 EdgeOne 的 KV(绑定名
DRAFTS),Worker 彻底退休。入口本来就在 EO、解密回包也已跑通,只补「写 GitHub + KV 存草稿」两步,是改动最小的一个。TipEdgeOne 的 KV 存储仅规则引擎形态边缘函数支持,普通 JS 边缘函数无法绑定 KV;切换为 eo 模式部署前先确认你的边缘函数类型。
代码已存档在仓库
edge-functions/wxbot.js(与图床反代index.js分立),部署时作为企微入口边缘函数发布即可。 -
cf(纯 Cloudflare)
反过来,回调直接打 Cloudflare Worker,Worker 自己解密 + 回包 + 写 GitHub。需要把 EO 那份纯 JS AES 加密回包逻辑搬进 Worker(已经搬了,且跑了加解密往返测试,企微能正确解开)。
切换就是改环境变量,业务逻辑同源、指令集完全一致:
# EdgeOne 侧(index.js 的环境变量)DEPLOY_MODE = "hybrid" # hybrid / eo
# Cloudflare 侧(wrangler.toml 的 [vars])DEPLOY_MODE = "hybrid" # hybrid / cf切到 cf 时,企业微信回调的入口域名得从 EdgeOne 换成 Cloudflare 的 Worker 域名——所以「换模式」不只是改个 env,还牵动域名指向,部署时要一起改。
纯 EO 那个 KV 绑定我在 EdgeOne 规则引擎形态里实测过能跑;万一你控制台那边 KV 绑定名不是 DRAFTS,回去补一下绑定就行,代码不用动。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!











