## 前言
上周折腾了一个事:用 Hermes Agent 自动往微信公众号发文章。
听起来很简单对吧?调个 API 就完事了。但实际上踩了不少坑,记录一下给同样想搞的人参考。
先说结论:**能跑通,但没认证的订阅号只能推到草稿箱,不能直接发布。**
---
## 你需要准备什么
- 一个微信公众号(订阅号或服务号都行)
- 公众号的 AppID 和 AppSecret
- 一台服务器(或者本地电脑也行)
- Hermes Agent 已经装好
AppID 和 AppSecret 在 mp.weixin.qq.com → 设置与开发 → 基本配置 里找。
**注意:** 一定要把服务器 IP 加到白名单里,不然调 API 会报 `IP white list` 错误。
---
## 整体流程
说白了就三步:
1. 用 AppID + AppSecret 换 access_token
2. 把文章做成草稿(调 draft/add 接口)
3. 发布(调 freepublish/submit 接口)
但每一步都有坑。
---
## 第一步:获取 access_token
这个最简单,但也最容易出问题。
```java
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
+ "&appid=" + appId + "&secret=" + appSecret;
```
返回的 JSON 里有 `access_token`,有效期 2 小时。
**踩坑记录:**
- AppSecret 输错了会报 `invalid credential`
- IP 没加白名单会报 `IP white list`
- access_token 有调用频率限制,别每次都重新获取,缓存起来用
---
## 第二步:创建草稿
这一步是核心。微信的草稿接口是 `/cgi-bin/draft/add`。
请求体大概长这样:
```json
{
"articles": [{
"title": "文章标题",
"author": "作者",
"digest": "摘要",
"content": "HTML格式的正文",
"thumb_media_id": "封面图的media_id"
}]
}
```
**坑来了:**
### 坑1:content 必须是 HTML
不能直接丢 Markdown 过去,微信不认。得先转换成 HTML。
我用的是语义化标签,不加内联样式:
```html
一级标题
正文内容
引用内容
代码块
```
微信编辑器会自动识别这些标签,比写一堆 `style="..."` 靠谱多了。
### 坑2:thumb_media_id 必须有
没有封面图?微信不让你过。得先上传一张图片拿到 media_id。
上传接口是 `/cgi-bin/material/add_material?type=image`,注意是永久素材接口,不是临时的。
我一开始用的临时素材接口,返回的 media_id 不能用,白折腾了半小时。
### 坑3:content 里的 $ 符号
如果你的文章里有 `$` 符号(比如代码里的变量),Java 的 `String.replaceAll` 会把它当成正则引用符,直接报 `Illegal group reference` 错误。
解决办法:用 `Matcher.quoteReplacement()` 包一下。
```java
// 错误写法
String safe = content.replaceAll("$", "\\$");
// 正确写法
String safe = content.replaceAll("$", Matcher.quoteReplacement("\\$"));
```
这个坑我踩了两次才记住。
---
## 第三步:发布
草稿创建成功后,会返回一个 `media_id`,用这个调发布接口:
```
POST https://api.weixin.qq.com/cgi-bin/freepublish/submit
```
但是——
**未认证的订阅号调这个接口会返回 48001(api unauthorized)。**
也就是说,你的公众号得认证过(300 块/年),或者你是服务号,才能用这个接口。
没认证怎么办?
只能创建草稿,然后自己去 mp.weixin.qq.com 后台手动点「发布」。
说实话也就多一步,能接受。
---
## 我的最终方案
因为我的号没认证,所以最终方案是:
1. Hermes 生成文章内容
2. 调 API 推到公众号草稿箱
3. 我去后台点一下发布
整个过程大概 10 秒钟,比手动复制粘贴快多了。
---
## 完整代码片段
核心逻辑大概 50 行,贴一下关键部分:
```java
// 1. 获取 access_token
String token = getAccessToken(appId, appSecret);
// 2. 上传封面图
String thumbMediaId = uploadThumb(token, coverImageUrl);
// 3. 转换 Markdown → HTML
String htmlContent = markdownToHtml(article.getContent());
// 4. 创建草稿
String draftMediaId = createDraft(token, title, author, digest, htmlContent, thumbMediaId);
// 5. 发布(如果账号支持)
// publishDraft(token, draftMediaId);
```
完整的 Controller 我放在下面,可以直接抄:
```java
@PostMapping("/publish/{id}")
public Object publish(@PathVariable Long id,
@RequestBody Map
body) {
String appId = body.get("appId");
String appSecret = body.get("appSecret");
// 获取文章
Article article = articleService.getById(id);
// 获取token
String token = getAccessToken(appId, appSecret);
if (token == null) return Map.of("error", "获取token失败");
// 上传封面图
String thumbId = uploadDefaultThumb(token);
// 创建草稿
String mediaId = createDraft(token, article, thumbId);
if (mediaId == null) return Map.of("error", "创建草稿失败");
return Map.of("success", true,
"message", "已推送到草稿箱,请去后台发布");
}
```
---
## 常见错误码
| 错误码 | 含义 | 解决办法 |
|--------|------|----------|
| 40001 | access_token 无效 | 重新获取,检查 AppID/AppSecret |
| 40002 | 不合法的凭证 | 同上 |
| 40164 | IP 不在白名单 | 去后台加 IP 白名单 |
| 48001 | 接口未授权 | 公众号没认证,只能推草稿 |
| 45009 | 接口调用超限 | 降低调用频率,缓存 token |
---
## 总结
折腾了一天,总结几个教训:
1. **先搞清楚你的号是什么类型**,能不能用发布接口
2. **封面图必须有**,不能跳过
3. **$ 符号在 Java 里是雷**,处理 Markdown 时一定要注意
4. **access_token 要缓存**,2 小时内重复获取会报错
5. **未认证订阅号只能推草稿**,别白费力气调发布接口
希望这篇能帮到同样在折腾的人。
---
*如果觉得有用,欢迎关注公众号「情韵博客」,后续会分享更多 AI 实战经验。*