作为一名技术博主,最头疼的事情之一就是维护多个平台的内容同步。每次写完博客,都要手动复制粘贴到微信公众号,不仅繁琐,还容易出错。最近我决定用技术手段解决这个问题——构建一个完全自动化的博客同步流水线。
项目背景
我的博客基于 Hexo 部署在 GitHub Pages 上,而微信公众号是国内主要的写作平台之一。目标是:
- 在博客仓库 push 新文章
- 自动检测到新内容
- 自动发布到微信公众号
- 记录发布状态,避免重复
技术方案选型
触发方式
最初考虑使用 GitHub Actions 定时轮询,但这不够实时。最终选择了 GitHub Webhook 主动触发的方式:
- GitHub Push → Webhook → 本地服务
为什么不直接用 GitHub Actions 发布文章?因为微信公众号没有官方发布 API,只能通过浏览器自动化操作。
浏览器自动化
Playwright 是最佳选择:
- 官方维护,API 稳定
- 支持会话持久化,登录状态可保存
- 跨平台支持
存储方案
使用 SQLite (better-sqlite3):
- 轻量级,无需额外服务
- 记录发布状态,防止重复
- 存储同步日志,方便排查问题
架构设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| ┌─────────────────┐ │ Git Push 新文章 │ └────────┬────────┘ │ ▼ ┌─────────────────────────────┐ │ GitHub Webhook 通知 │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 本地服务接收并解析 │ │ - 获取变更的 Markdown 文件 │ │ - 检查是否已发布 │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ Markdown 转换 │ │ - 转为公众号富文本格式 │ │ - 处理图片和代码 │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 浏览器自动化发布 │ │ - 登录微信公众号 │ │ - 创建图文消息 │ │ - 自动发布 │ └────────┬────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 记录发布状态 │ │ - 成功/失败日志 │ │ - 文章映射记录 │ └─────────────────────────────┘
|
核心实现
1. Webhook 签名验证
1 2 3 4 5 6 7 8 9 10
| import crypto from 'crypto';
export function verifySignature(payload: string, signature: string, secret: string): boolean { const hmac = crypto.createHmac('sha256', secret); const digest = hmac.update(payload).digest('hex'); return crypto.timingSafeEqual( Buffer.from(`sha256=${digest}`), Buffer.from(signature) ); }
|
使用 timingSafeEqual 防止时序攻击。
2. Markdown 转公众号格式
1 2 3 4 5 6
| export function toWechatFormat(html: string): string { return html .replace(/<h1>/g, '<section style="font-size: 24px; font-weight: bold; margin: 20px 0;">') .replace(/<p>/g, '<section style="margin: 15px 0; line-height: 1.8;">') .replace(/<blockquote>/g, '<section style="border-left: 4px solid #ddd; padding-left: 15px; color: #666; margin: 15px 0;">'); }
|
微信公众号编辑器不支持标准 HTML,需要转换内联样式。
3. Playwright 自动发布
1 2 3 4 5 6 7 8 9 10 11 12 13
| async publish(title: string, content: string): Promise<PublishResult> { await this.page.click('.weui-desktop-menu__item:has-text("素材管理")'); await this.page.waitForLoadState('networkidle');
await this.page.click('text=新建图文消息');
return { success: true, url }; }
|
会话持久化是关键,首次扫码登录后保存状态,后续无需重复登录。
4. 调度器
1 2 3 4 5 6 7 8 9
| export function startScheduler(intervalMs: number = 60000): void { syncPendingPosts();
intervalId = setInterval(() => { syncPendingPosts().catch(err => { console.error('调度器执行错误:', err); }); }, intervalMs); }
|
定期检查待发布文章,确保不漏掉任何内容。
部署方案
由于需要浏览器环境,我选择在本地 NAS/电脑上运行服务:
1 2 3 4 5
| cd scripts/sync-service npm install cp .env.example .env
npm run dev
|
首次运行会打开浏览器,扫码登录公众号即可。
项目结构
1 2 3 4 5 6 7 8 9 10 11
| scripts/sync-service/ ├── src/ │ ├── webhook/ # Webhook 处理 │ ├── converter/ # Markdown 转换 │ ├── publishers/ # 发布器 │ ├── storage/ # 数据存储 │ ├── sync/ # 调度器 │ └── index.ts # 主入口 ├── data/ # 运行时数据 ├── package.json └── README.md
|
待优化点
- 图片处理:目前本地图片需要手动上传,后续可集成图床自动上传
- 多平台支持:架构支持扩展到掘金、语雀等平台
- 错误重试:添加失败重试机制
- 发布预览:生成预览链接供人工确认后再发布
总结
这个项目从设计到实现大约花了一下午时间,核心代码不到 1000 行。通过合理的架构设计和技术选型,用最少的代码实现了完整的功能。
完全自动化后,我只需要专注写作,推送代码,剩下的事情交给程序处理。这就是技术让生活更美好的一个缩影吧!
项目地址:blog-sync-service
相关文章: