给写字台接入谷歌 Blogger:OAuth 授权、文章互导、分发渠道

xiezitai · 2026-10-08 22:31 · 7 次阅读

给写字台接入谷歌 Blogger:OAuth 授权、文章互导、分发渠道

写字台原本已经能关联 WordPress 站点、博客园账号,还有另一台写字台(既做导入源、又做分发目标)。这次把谷歌 Blogger 也接了进来,做法和前三者差别不小——Blogger 没有「用户名 + 密码 + 接口地址」这种可以填在表单里的东西,拿授权走的是 Google 官方 OAuth 2.0,正文也不再是 Markdown。下面把这次的设计与踩坑记一遍。

一、关联:走 OAuth 2.0 授权码模式

后台新增一个「Blogger」面板,点「关联 Google 账号」会开一个弹窗去 Google 的同意页,而不是把整个后台跳走——授权完用户还得回到面板继续操作。

流程是标准的授权码模式,但有几处是必须做对的:

① 刷新令牌只能在首次授权时拿到。 参数里必须同时带 access_type=offline 与 prompt=consent,否则 Google 只在第一次给刷新令牌,之后只给访问令牌。一旦丢了刷新令牌,用户就得重新授权一次。拿到之后落库,之后每次调用前按访问令牌是否过期决定要不要续期。

② 公开回调不能是「裸」的。 回调地址 /google/auth/redirect 必须免鉴权(Google 的浏览器不会带你的 JWT),所以它天然是公开端点。攻击面在于:任何人构造一个带 code 的回调请求,都可能把某个 Google 账号绑到你的站点上,或者拿它当跳板。

处理办法是给 state 做 HMAC 签名:发起授权时生成一个 payload(含 redirect_uri 与 10 分钟后的过期时间),用站点自己的密钥签名,拼进 state;回调时先验签、再查过期,才继续用 code 换令牌。这样没有密钥就伪造不出合法 state,也顺带解决了「回调地址必须与发起时完全一致」的问题——直接从 payload 里取回原值,不用猜。

顺带一提,安全配置里只放行 /google/** 这一条公开路径,其它后台接口照旧走 JWT。

③ 凭据只从环境变量读。 客户端 ID 与客户端密钥属于敏感信息:不落库、不入仓库、后台接口也不回显(列表里只回一个「是否已配置」的布尔值)。这样即使有人拿到数据库导出、或者翻了后台接口,也拿不到客户端密钥。授权换来的刷新令牌与访问令牌确实要落库(否则每次重启都得重新授权),但序列化时标成只写,接口一律不回传。

④ 一个账号下可能是多个博客。 Blogger 的层级是「Google 账号 → 博客 → 文章」,所以授权一次要把该账号下的所有博客都列出来并关联,而不是只关联一个。同一条记录按博客 ID 唯一,重复授权只刷新令牌,不会一条博客长出两份。

⑤ 回调地址要能被推导出来。 生产上站点可能同时挂在裸域和 www 下,而 Google 要求回调地址与登记值逐字一致。所以回调地址不写死,而是按发起授权那一刻的请求推导(优先读反代传来的真实协议与域名头),这样多域名各自都能对上自己登记的那一行。

二、导入:Blogger API v3

浏览远端文章用 blogs/{id}/posts,翻页靠 pageToken。几处和普通 CMS 不一样的地方:

文章 ID 是 19 位长数字串。 这个长度超过 32 位整数,也踩得到 64 位整数在 JS 侧的精度边界(安全整数只有 16 位)。所以从实体字段到接口返回,全程按字符串处理,绝不先转数字再转回来——否则会静默丢掉末尾几位,更新时打到一篇不存在的文章上。

正文是 HTML,我们的库是 Markdown。 Blogger 的编辑器直接存 HTML,而写字台整站存 Markdown 原文。导入时用内置的转换器把 HTML 还原成 Markdown(标题、列表、加粗、链接、图片、引用、代码块),导出分发时再反向渲染回 HTML。这个方向性很关键:Blogger 不认 Markdown,把 Markdown 原文发过去只会被当纯文本渲染。

媒体要分「谁家的图」。 需求是:来自来源站点自身的文件下载落盘,第三方的一律保留外链。判断依据有两条,缺一不可:

  1. 主机要属于 Blogger 自家(*.blogspot.com、*.googleusercontent.com、*.ggpht.com,另外留了一个环境变量可以追加自己用的域名);
  2. 正文里的链接要带媒体后缀(图片、视频、压缩包这类)才下载——否则一个「站内普通文章链接」也会被当成附件抓下来。

第三方图床的图(比如你自己的 CDN)保持外链,避免把别人的带宽问题搬进自己的磁盘。

slug 三级兜底。 远端文章的 URL 末段是最理想的 slug;没有就拿标题转写;标题也是纯符号转不出东西时,退到 blogger-<文章ID>,保证一定有个稳定且唯一的地址。

整站导入要给进度。 整站导入是「拉列表 → 逐篇拉正文 → 逐篇落盘媒体」,几十上百篇跑下来是要时间的。做法是:

三、分发渠道新增谷歌 Blogger

文章列表的行内「分发」弹窗里多了一个「谷歌 Blogger」分组,和 WordPress / 博客园 / 写字台并列,可多选。

四、踩过的坑

现象 真因 处理
更新文章时打到「不存在的文章」 19 位 ID 先被转成数字,末尾精度丢失 实体到接口全程用字符串形态
老渠道的分发结果报空指针 新加的字符串 ID 字段在老渠道上是 null,直接调了它 取值时判空再回退到数字形态
服务起不来,报方法不可见 内部工具方法当初写成包私有 改成公开并补注释
测试里睡线程编译不过 忘了处理中断异常 包 try/catch 并恢复中断标志
断言时好时坏 断言依赖了目标列表的返回顺序 先定位到目标再断言,不靠下标
断言「包含某字样」永远不过 HTML 里的字段名是对象键,没有引号包裹 断言放宽到子串,不再纠结引号

五、配置与验证

要启用这块能力,需要自备一个 Google OAuth 2.0 客户端,并把回调地址登记好:

GOOGLE-XIEZITAI-CLIENTID=xxxxxxxx.apps.googleusercontent.com
GOOGLE-XIEZITAI-CLIENT_SECRET=GOCSPX-xxxxxxxx

上面只是占位符写法,真实值只放本地 .env(已被 git 忽略)。应用侧只从环境变量读,不落库、后台不回显、不入仓库。怀疑泄露时去 Google 控制台重置密钥即可。

另外留了两个可选旋钮:一个用来追加「算 Blogger 自家主机」的域名,一个用来设定单个落盘文件的大小上限(超出降级为外链并给警告)。

验证方式:

小结

接入一个「只有 OAuth、正文还是 HTML」的外部平台,真正花时间的不是调接口,而是这几件容易被忽略的事:令牌怎么安全地拿、公开回调怎么防洪泛、跨渠道的格式怎么双向不失真、长数字 ID 怎么不丢精度、哪些媒体该落盘。把这几点在类型和边界上定死,剩下的就是体力活。

评论

还没有评论,登录后来说两句。