硅基俊宁.
Skills · 技能体系 ↗
← 返回文章

TaleFrame 架构实录:原生 Android 如何接上 Vercel 小说后台

俊宁 · 2026-11-01TaleFrame 架构实录:原生 Android 如何接上 Vercel 小说后台封面

做 TaleFrame 时,我明确了两个要求:客户端用原生 Android;小说更新不能依赖重新打包 App。前期只交付小说阅读,漫剧保留 Coming Soon。随之需要回答的,是一组很具体的工程问题:小说列表由谁维护,正文存在哪里,后台保存后读者何时能看到,以及内容增加后费用从哪里产生。

现在,读者可以在 App 中浏览书库、打开章节、保存书架与阅读进度;管理员可以在网页后台维护书籍和章节,再单独发布。本文按当前实现拆开这条链路,说明每份数据的归属,以及现有方案的适用边界。

一句话总结

TaleFrame 是原生 Android 客户端,加上部署在 Vercel 的 Next.js 管理后台与 API;小说列表和章节文本都存于 Neon Postgres,封面文件存于 Vercel Blob,阅读进度和下载缓存留在手机本地。

“接上 Vercel”描述的是服务端部署方式,不会把 Kotlin 与 Jetpack Compose 客户端变成跨端应用。这里已经实现的是小说内容在线更新;APK 自动升级、读者账号、支付和漫剧播放不在当前交付范围内。

客户端与服务端分别实现了什么

先把可用功能与预留能力分开。截至 2026 年 11 月 1 日,本次核对了客户端源码、服务端数据模型、生产数据库与公开目录接口。

部分已实现的职责当前边界
Android App书库展示、搜索与分类、章节阅读、字体与阅读主题、书架和本地进度原生 Kotlin、Jetpack Compose;当前为调试 APK,未以应用商店版本交付
在线内容同步启动及手动刷新目录、按需取章节、正文哈希校验、磁盘缓存内容更新,不是客户端程序升级;首次没有缓存时需要联网
管理后台单管理员登录、书目信息编辑、章节编辑与批量导入、发布、下架和回退没有多编辑者角色体系,也没有读者账号系统
服务端 API管理写接口、公开目录接口、按版本读取正文公开阅读内容,不具备付费章节授权能力
漫剧保留 Dramas 导航,显示 Coming Soon未接入视频上传、转码或播放链路

生产书库当前包含 92 本小说、4,105 章。公开目录也返回同样数量。此前的历史内容只做了一次性导入:日常编辑写入独立数据库,App 运行时不再读取原小说站点。

这里的“已实现”不等于完成所有发行验收。模拟器联调、公开 API 可读、生产库存在内容,分别证明不同范围;实体设备、不同地区网络与真实用户规模下的稳定性仍需单独验证。

三个后台入口,分别管理内容、数据库和部署

日常更新小说应该进入业务后台,而不是直接修改数据库表。

入口管理对象什么时候使用
TaleFrame Studio书目、章节草稿、发布版本新增小说、更新章节、发布或回退
Neon ConsolePostgres 数据库与 SQL查看表、排查数据问题、检查数据库使用情况
Vercel DashboardNext.js 部署、运行日志、环境变量、Blob发布服务端代码、排查请求、管理封面存储

Neon 是独立的数据库服务。即使通过 Vercel 的集成入口创建或连接数据库,数据管理仍由 Neon 承担。Vercel 的存储文档也区分了 Blob 等产品与外部数据库集成。Vercel Storage

后台部署成功,不意味着数据库内容被写入部署包。服务端通过环境变量取得连接信息,在处理请求时访问 Neon;数据库凭证和 Blob 写入凭证不会下发给 Android。

Neon 的账号、资源和费用由谁管理

TaleFrame 当前使用的是 Vercel-managed Neon 原生 Marketplace 集成。2026 年 11 月 1 日核查安装记录和资源连接时,确认 taleframe-db 归属当前 Vercel 团队,并连接到 taleframe-server。因此,数据库由 Neon 提供,资源的团队归属、成员权限和订阅管理则通过 Vercel 维护。

这次接入复用了已有集成:安装记录的创建日期为 2025 年 1 月 17 日(北京时间),早于本次 TaleFrame 开发。这个日期证明的是集成已存在,不能据此推断 TaleFrame 数据库也创建于当天,更不能说本次开发重新注册了一个 Neon 账号。

管理事项TaleFrame 当前的管理入口
数据库资源与项目连接Vercel 的 Storage,选择 taleframe-db
团队成员与协作权限Vercel 团队的 Members
Neon 套餐、资源配置与费用Vercel;该集成的计费由 Vercel 统一处理
数据表、SQL 与数据库操作从 Storage 中点击 Open in Neon,进入 Neon Console
小说列表与章节更新TaleFrame Studio;编辑完成后 Publish

这里还需要区分“已有集成”和“已有登录账号”。Neon 官方说明,这种集成既可能为新用户创建账号,也可能在已有用户的账号下增加 Vercel 管理的组织。本次核查没有还原最初的注册流程,因此无法确认当年是自动创建了登录身份,还是使用了原有 Neon 身份;可以确认的是当前资源采用 Vercel 管理模式。Neon 原生集成与计费说明

核查时,集成返回的套餐为 Free(free_v3),作用范围是集成安装。但同一响应中的费用数据属于 2025 年 5 月,不能用它判断当前账期是否产生费用。实际支出应查看 Vercel 当前账期的用量和账单;Neon 的 Free 套餐也不代表 Vercel 函数、Blob 和流量等整套服务都永久免费。

小说列表、正文、封面与进度的存储归属

当前系统有三类持久化位置:Neon 数据库、Blob 对象存储、Android 应用私有存储。每一类都承担明确职责。

数据存储位置具体结构或用途
小说书目草稿Neon books.draftJSONB:书名、作者、简介、分类、封面 URL、排序等
当前发布指针Neon books.published_release指向读者当前可见的发布快照
编辑中的章节正文Neon chapters.bodyTEXT:以章节为单位保存;同表保存章号、标题和编辑版本
已发布正文版本Neon chapter_versions.bodyTEXT:由章节 ID 与正文 SHA-256 定位
发布时的书目与章节清单Neon releases.snapshotJSONB:书籍信息、章节标题、正文哈希和接口路径
全局目录版本Neon catalog_state.version发布、下架或回退后递增,供客户端检查变化
小说封面文件Vercel Blob图片本体;数据库保存其公开 URL
手机已下载的目录Android 私有文件目录 JSON,经校验后原子写入
手机已读正文与封面Android 私有文件downloaded-content 缓存,按 URL 派生文件名
书架、阅读位置、字号与主题Android SharedPreferences当前设备保存,不同步到服务端

所以,“小说列表存在哪”的完整答案有两层:管理员维护的书目在 books,读者读取的列表由当前发布指针关联 releases 后生成。 公开列表不会直接暴露编辑中的书目草稿。

“小说文本存在哪”同样需要区分草稿与发布态:编辑器读写 chapters.body,读者接口读取 chapter_versions.body。正文没有拆成 Blob 文本文件,也没有在当前网络版 APK 中打包整个书库。

一次章节修改,如何从后台到达 App

以修改一本书的第 12 章为例,内容经过以下过程。

保存只是更新草稿

管理员打开章节并保存,后台更新章节正文和编辑版本。读者此时仍然读取上一份已发布正文。

编辑接口使用 editVersion 检查提交是否过期。如果两个页面先后修改同一章,旧页面保存时会收到冲突,避免把较新的修改静默覆盖。当前模型以单管理员为主要使用场景,这个保护仍然有价值:一个人同时打开两个标签页,也可能发生冲突。

Publish 在事务内生成可读版本

发布逻辑读取该书已保存的章节,计算每章正文的 SHA-256。新的正文版本写入 chapter_versions;同一章节、相同哈希已经存在时,不重复插入。

随后创建发布快照,把书目信息与章节引用保存到 releases,更新 books.published_release,并递增目录版本。相关更新在同一数据库事务中提交。公开目录用一条 SQL 同时读取版本号和已发布书目,使两者来自同一个数据库快照。

发布清单里的章节引用可以概括为:

{
  "id": "稳定的章节 ID",
  "number": 12,
  "title": "章节标题",
  "sha256": "正文的 SHA-256",
  "asset": "/api/v1/chapters/{chapterId}/{sha256}"
}

这里的哈希标识正文版本,稳定 ID 标识章节。正文发生变化,下载路径随哈希改变;未改动的章节仍使用原路径。这让客户端可以复用没有变化的缓存。

App 检查目录,再按需读取正文

App 启动后先尝试加载磁盘目录,再请求 GET /api/v1/catalog。用户也可以手动点击 Refresh。已有版本会通过 If-None-Match 发给服务端;版本未变时返回 304,不再传输整份目录。

当目录变了,App 校验数据结构、章节引用与地址格式,成功后原子替换本地目录。更新失败时保留之前可用的内容,而不是先清空书库。

打开章节时,App 读取 GET /api/v1/chapters/{chapterId}/{sha256},核对正文 SHA-256,再写入缓存。之后相同版本优先使用本地副本。正文阅读没有经过 WebView,文本由 Compose 组件渲染。

当前目录包含所有已发布小说及其章节引用,不是仅含书名的轻量首页,也没有目录分页和增量补丁接口。这降低了首版实现复杂度,但书库继续扩大时,需要测量目录响应体与解析耗时,不能只关心正文占用。

两层缓存,承担不同的等待与离线行为

服务端公开目录和正文响应设置了 s-maxage=60,用于共享缓存;客户端磁盘缓存用于断网阅读和重复访问。二者不能混为一谈。

Vercel 对 s-maxage 的处理属于 CDN 缓存语义;代码中写了缓存头,只能证明配置意图,实际是否命中仍要查看响应与运行数据。Vercel Cache-Control

这也意味着发布不是实时推送。读者需要发生刷新请求,目录还可能处于共享缓存有效期内。当前没有消息推送或长连接通知。

客户端正文与封面共用约 100 MiB 的自动缓存预算,按文件最近访问时间淘汰。这个上限不是整台 App 的总存储上限:目录和阅读偏好另行保存。它也不等于“92 本书全部离线可读”,只有已下载且尚未被淘汰的内容可以离线读取。

已经打开的阅读页保留其书籍版本对象,目录刷新不会立即把正在阅读的章节切换到新版本。但服务端只保留有限历史,旧阅读页若之后再下载已清理的正文,仍可能遇到版本不可用,需要刷新书库。客户端缓存改善体验,并不创造无限期的历史访问承诺。

为什么当前把正文放进 Postgres

最初担心小说很大,因此专门核对了实际体积。2026 年 11 月 1 日,本次只读查询得到:

项目实测值口径
小说数92数据库书目数,均已有发布指针
章节数4,105章节草稿表行数
草稿正文22,833,120 字节,约 22.83 MBsum(octet_length(body)),UTF-8 字节数
数据库大小42,713,088 字节,约 42.71 MBpg_database_size,含其他表和索引;并非云账单计费量

这组数据支持的是一个有限判断:在当前书库规模下,正文并没有大到必须拆出另一套文本文件服务。 直接使用 Postgres,可以在一处处理草稿、正文版本、发布引用和回退,减少数据库与外部文件之间的一致性协调。

发布正文并非全库按哈希去重,唯一键是“章节 ID + 哈希”;不同章节即便正文相同,也不是同一条版本记录。系统保留每本书最近 5 份发布快照,并清理不再被这些快照引用的正文版本。因此,不能用“原始文本大小 × 发布次数”估算实际增长,也不能保证数据库大小永远保持当前数值。

封面采用另一条路径:后台验证图片,限制上传大小,缩放到最多 768 像素宽并转成 WebP,再上传 Blob。App 按公开 URL 读取图片。图片无需参与正文的数据库事务,独立放在对象存储中更直接。

目前的成本控制还包括按章下载、目录条件请求、封面压缩和历史正文清理。最终费用仍受数据库计算、函数执行、请求量、存储与分发流量影响;现有免费额度或套餐不能解释成永久零成本。关于平台选择与费用边界,可继续阅读围绕 Vercel,搭一套独立开发者的交付套件。

回退、下架与备份,各自能保证什么

回退会把当前发布指针切回仍保留的快照,同时增加目录版本。它不会覆盖编辑中的草稿,也不是“整库恢复到过去某一天”。

下架会清空该书的发布指针,之后公开目录不再列出它;正文接口回源时也检查书籍仍处于已发布状态。不过,共享缓存存在有效期,已经下载到手机里的内容也无法远程收回。当前公开 API 与公共封面 URL 的设计适合免费公开阅读,不能直接作为付费内容保护方案。

项目提供数据库导出和恢复脚本,备份包含草稿、发布记录和正文版本。备份里的封面是 URL,不包含 Blob 图片本体,因此数据库备份不能替代图片备份。恢复脚本拒绝向非空目标数据库覆盖写入;这些工具的存在,也不等于已经运行了定时备份服务。

管理员会话、登录限流与同源写请求校验负责保护后台写操作。读者权限是另一件事:未来如果增加账号、付费章节或跨设备进度,需要补充读者身份、授权和同步模型。

要不要用:我的判断框架

值得采用的场景,是类似 TaleFrame 的小型内容产品:原生客户端需要独立演进,后台以少量管理者编辑为主,正文体积可控,允许通过刷新获取新内容,并且内容本身可以公开阅读。此时,一套 Next.js 后台、一个托管 Postgres 与图片对象存储已经能够支持持续更新。

可以再等的能力,包括独立搜索服务、消息队列、多级内容分发和读者云端进度。是否增加它们,应看现有搜索、发布与阅读是否出现具体问题,而不是因为将来可能需要就预先部署。

重点关注的变化,是全量目录变大、数据库回源压力、真实地区网络可用性,以及付费内容或视频进入产品。这些变化会影响现有边界:目录可能需要拆分,正文分发可能需要调整,漫剧则需要重新测量视频存储与播放成本。小说的 22.83 MB 文本测量,不能用来推断视频业务费用。

当前这套架构的核心,是让编辑草稿、发布版本和本地阅读状态各自有明确归属。后续即使替换某个托管平台,这三个边界仍然值得保留。


本文依据 TaleFrame 当前实现与 2026 年 11 月 1 日的只读核验整理。相关实现入口包括服务端 schema.sql、lib/content.ts、公开与管理路由,以及客户端 Catalog.kt、ContentNetwork.kt、ReadingStore.kt。源码尚未公开,本文不提供不可访问的仓库链接;代码机制、线上读接口核验和发行状态已分别说明。

封面为 AI 生成的阅读场景插画,不是 App 界面截图。

本文使用 writting-skill 辅助写作,配图使用 better-imagegen 生成。项目已开源,欢迎在 GitHub 点个 Star。