
HexStudio 是我给自己写的一套博客管理工具。起因很普通。Hexo 源码放在 GitHub,人在外面时想改个错字、发条说说,只能等回宿舍开电脑。手机浏览器能改文件,但 Hexo 的目录和 front matter 要手改,Stellar 标签、图床上传也没有现成入口。
之前用过一段时间的 Qexo,后台功能挺全,不过它和我的 Stellar 主题、说说和友链这套流程之间总隔着几层。后来干脆自己写一个 Android 客户端,再把需要常驻的任务放到 Cloudflare 上。
现在这套东西分三块:Android APK、Cloudflare Worker + D1、博客本身。这篇先讲三块怎么配合,再走一遍部署,最后列最近博客的改动。
三块拼图
| 部分 | 位置 | 负责什么 |
|---|---|---|
| Android | android/ | 登录 GitHub、选择 Hexo 仓库、编辑文章和页面、发说说、审友链、接收通知 |
| Worker | worker/ | 说说、友链、朋友圈数据的读写接口,定时抓取和存活检测 |
| 博客 | MyBlogSource | Hexo + Stellar,把 Worker 的 JSON 渲染成页面 |
APK 不碰 Cloudflare 的数据,除了说说和友链。文章和页面仍然走 GitHub Contents API,写完直接提交到 MyBlogSource,Cloudflare Pages 检测到新 commit 后自动构建。这样博客的正文一直留在 Git 里,换主题和迁移不用另外想办法,导出同样如此。
说说、友链申请和朋友圈文章放在 D1。它们更新频繁,走 Pages 构建太慢。Worker 读写 D1 基本是毫秒级,网站打开页面时直接取 JSON。
一次说说发布怎么走

打开「说说」页,顶部有一张输入卡片。点一下会在当前页面展开,文字多了就往下长,超过 7 行左右会淡入全屏编辑器。图片按钮直接打开相册,上传到图床后缩略图排在输入框下面,发布时和文字一起提交。
APK 调 POST /admin/talks,Worker 校验会话后写入 D1。网站说说页每隔一段时间或刷新时请求 /talks,按发布时间倒序渲染。导入历史 tgtalk 数据也是走同一个接口,按 source_id 去重。
文章编辑是另一条路。选仓库、读文件树,编辑 Markdown 后提交到 GitHub,每一步都可以在 APK 里完成。编辑器里带了 Stellar 标签面板,插入 {% note %}、{% folding %}、{% tabs %} 这类标签时会顺带补上闭合标签。预览用的是本地 marked + DOMPurify,避免每次改字都开一次网页。


几个具体取舍
仓库当数据库。 文章、页面和配置都留在 GitHub,APK 只做客户端。这样不用维护一台常驻服务器,也不会出现文章只存在某个数据库里的情况。代价是每次读仓库都要走 API,所以加了本地缓存:文件树 15 分钟、用户信息 24 小时,冷启动先显示缓存,再在后台刷新。
Worker + D1,不上传统后端。 说说、友链和通知的量很小,D1 的免费额度足够。Cron 每分钟处理一个友链源,朋友圈文章抓回来后写进 feed_articles,网站只读 /feeds。朋友站点没有 RSS 时,会依次尝试 Sitemap 和首页文章链接,再抓文章页的元数据。
朋友圈按真实发布时间排序。 这里踩过坑:早期爬虫解析不到日期就把抓取时间写进 published_at,结果十年前的文章一抓回来就排到最前面。现在的处理顺序是 article:published_time、JSON-LD、<time datetime>、页面可见日期,RSS 没日期时回查文章页。仍然拿不到就排到最后,页面显示「日期未知」,不再冒充今天发布。存量数据也重新抓了一遍。
友链每天体检。 Worker 每天请求一次友链首页,404、410、5xx、超时和 DNS 失败会被标成失效,按小时重试,恢复后自动移回。网站友链页分成「友链」和「无法访问的网站」两块,失效的站点灰度显示。APK 里也能看到失效角标和原因。

更新检查。 APK 启动后会静默读取仓库根目录的 update.json,12 小时内只请求一次。设置页也能手动检查。发现新版本时弹窗给出更新说明和 APK 地址,安装仍然交给系统浏览器或包安装器。
从零部署
下面这套步骤对应公开仓库 Bytelv/HexStudio,域名和数据库名换成自己的即可。
1. Worker 和 D1
先在 Cloudflare 建一个 D1 数据库,把 ID 填到 worker/wrangler.toml。name、route、zone_name 按自己的域名改,Cron 保持 * * * * *。
1 | cd worker |
schema.sql 负责首次建表。已经有数据的环境不要重复执行,改用 migrations/ 里的迁移文件,例如友链存活检测这一版:
1 | npx wrangler d1 execute lvbyte-studio-db --remote --file=./migrations/20261031_friend_health.sql |
部署后至少确认三个公开接口能返回数据:
1 | curl https://studio.example.com/health |
IP_SALT 用于友链申请限流的 IP 哈希。生产环境不要用代码里的默认值,重新生成一个随机字符串。
2. GitHub OAuth App
在 GitHub 的 Developer settings 里新建 OAuth App,勾上 Enable Device Flow。Authorization callback URL 对 Device Flow 不是必需的,填博客首页即可。拿到 Client ID 后填到 android/app/build.gradle.kts 的 GITHUB_CLIENT_ID,或者登录页的高级设置里临时覆盖。
Worker 的 wrangler.toml 里有一项 ADMIN_GITHUB_LOGIN,只有这个 GitHub 账号能把 GitHub Token 换成 Worker 会话。换人使用时记得一起改。
3. 构建 APK
本机需要 JDK 17 和 Android SDK 35。local.properties 写 SDK 路径,release 签名放在 android/keystore.properties,这个文件和 .jks 都已经加进 .gitignore。
1 | storeFile=hexstudio-release.jks |
1 | cd android |
产物在 android/app/build/outputs/apk/release/。release 和 debug 的 applicationId 不同,正式版是 top.lvbyte.hexstudio,debug 带 .debug 后缀,两个包可以共存,但数据不互通。
发布更新时改 versionCode 和 versionName,把新 APK 放到 release/HexStudio-<版本>.apk,同步改根目录 update.json:
1 | { |
versionCode 必须递增,否则 Android 不认这个更新。签名文件也要留好,换签名后旧版本无法覆盖安装。
4. 博客接入
博客这边主要是几个页面和一段侧边栏脚本。友链页用 Stellar 的 friends 标签,通过 alive 参数分成两组:
1 | {% friends api:https://studio.example.com/friends?alive=1 %} |
朋友圈页用 timeline:
1 | {% timeline type:fcircle api:https://studio.example.com/feeds %} |
说说页和首页侧边栏都直接读 /talks。侧边栏在 source/_data/widgets.yml 里配置一个 Markdown 组件,放一个带 data-api 的容器,再用 Hexo injector 引入 widget-talks.js 和样式。这样换掉旧的 tgtalk 接口时不用改主题本身。
Cloudflare Pages 连接 MyBlogSource,构建命令是 npx hexo generate,输出目录 public。仓库是私有的也能连,Pages 会在每次 push 后自动构建。
最近博客改了什么

- 友链数据从旧接口迁到 Worker,新增自建申请表单,提交后会写一条通知,APK 轮询提醒。
- 说说页上线,网站用普通时间线。APK 里的编辑器和说说页改成了 NENO 风格,图片可以在内联框直接上传。
- 首页右侧「近期说说」改为读取 Worker,旧的
tgtalk.4123010.xyz链接已经移除。 - 朋友圈页面接入新的
/feeds,RSS 抓不到时回退到 Sitemap 和 HTML 爬取。文章日期按真实发布时间修正,抓不到日期的排到最后。 - 友链增加每日存活检测,失效站点进入「无法访问的网站」分组,恢复后自动回来。
- APK 增加检查更新、仓库缓存和说说内联编辑,正式版 1.2.2 约 2.2MB。
还不完善的地方
朋友圈爬虫仍然会碰到反爬站点、403/526 错误,以及纯 JS 渲染的页面。APK 的友链编辑里可以手动填 RSS 地址,填了之后会优先走订阅。
文章日期依赖页面元数据。有些站点把所有文章都标成同一天,或者页面里根本没有日期,这种情况只能按未知处理。好在不会再把它排到最新。
Android 端目前没有自动化 UI 测试。构建、单元测试和 lint 都能跑,但布局和输入手感还是得装到手机上看。我的开发环境没开成模拟器,这一步一直靠真机。
部署和维护时不要把图床密码、keystore 和会话 Token 写进仓库。管理账号也尽量只留一个,Worker 的 ADMIN_GITHUB_LOGIN 和 OAuth App 权限不要图省事放开。
项目地址
源码在 GitHub:Bytelv/HexStudio。

