CF FIELDBOOK/实践

私有资料阅读站

电脑关机以后,读者仍能阅读

资料在自己的电脑上整理,却希望受邀读者随时能检索、阅读和下载。这时有两个要求同时存在:编辑者要保留原件、人工修改与分发选择的控制权;读者访问又不能依赖编辑者的电脑在线。

这份实践采用本机编辑、云端交付的分工。本机 Python、SQLite 与文件存储拥有完整资料;只有明确选出的内容才进入一份固定的发布快照。Cloudflare Workers 提供阅读入口,D1 保存目录、阅读单元与当前版本,private R2 保存被选中的原件和预览。后来加入 Workers AI 与 Vectorize,帮助读者用自己的说法找回资料。

编辑端和阅读端怎样连接#

组件 负责什么 为什么这样分
本机资料库 收录、编辑、保留来源、决定发布哪些条目 本机可见不自动意味着可以分发
发布工具 导出固定快照,写入并读回云端数据,最后切换当前版本 内容准备与读者可见分成两步
Worker 核验读者资格,检索并交付允许读取的内容 文件与接口共用读取规则
D1 当前发布版本、目录、正文单元、成员与下架状态 精确状态可以被查询和核对
private R2 原件、预览与其他被选中的对象 大文件无需跟着目录查询一起加载
Workers AI / Vectorize 生成查询向量,召回相近条目 找到候选以后,仍由阅读规则决定能否交付

Worker 通过 binding / 资源绑定使用分配给它的数据库和对象存储。绑定提供程序访问资源的能力;读者是否有资格查看某条资料,由应用判断。Workers bindings 官方说明解释了这种资源访问方式。

一次阅读大致经过:

读者请求 → 身份与资格检查 → D1 当前发布版本与下架检查
         → 目录或正文;需要文件时再读取 private R2
         → 交付前确认资格与可见性仍然有效

R2 没有开启 public bucket 入口。读者取得原件要经过 Worker,而不是拿到一个绕过权限的永久公开地址。R2 bucket 默认不公开,启用公开访问需要明确配置;这与 Worker 自己拥有 R2 binding 是两件事。R2 public buckets 官方说明。

最初的阅读入口使用 Access。应用依赖 Access 身份时,验证 JWT 的签名、issuer、audience 与有效期;看见一个身份请求头不够。后来系统显式改用应用自己的成员 Session,让网页账号与 Remote MCP 共用成员资格。两种模式分别配置,没有让任意一种身份通过就算登录的混合入口。Access JWT 验证文档解释平台身份的验证方法;成员 Session 与模式切换是这份应用自己的实现。

新内容为什么不能边上传边出现#

一份资料可能同时需要目录、正文、PDF 页图和原件。若上传了一半就把它交给读者,目录可能能搜到,打开却缺页;语义检索也可能还没收录新条目。

发布工具为此使用三个动作。这是应用的发布设计,不是 Cloudflare 自动替每个网站提供的流程:

  1. Stage:准备候选。 写入一份固定内容快照,核对它需要的目录行、文件与向量。此时读者继续看旧版本。
  2. Read-back:读回确认。 写入响应之后,重新读取需要交付的数据,核对身份、内容与数量;全部就绪,候选才标为可启用。
  3. Activate:切换版本。 只在当前指针仍是预期旧值、下架状态仍符合条件时切到新快照。其他发布者先改了指针,就停止重新核对。

这里的「发布版本」是一次选定的内容快照,不是任务例子中的执行代次。以下是合成状态,说明读者什么时候能看见新内容:

时刻 当前版本 新候选 读者看到什么
开始准备 G1 G2 尚未齐全 G1
文件完成,向量仍待确认 G1 G2 未就绪 G1
全部读回一致 G1 G2 可启用 G1
条件匹配,切换完成 G2 G2 已启用 G2

2026-09-30 和 10-02 的实际发布曾在向量读回时遇到超时或服务错误。工具停止而没有切换当前版本;再次处理同一份固定快照时,先核对已经存在的向量,只补真正缺失的部分。10-02 在更长但有限的确认窗口内完成整批读回后,再切换版本。失败期间旧版本继续服务。

Vectorize 的 upsert 是异步操作,返回 mutation identifier 后还需要时间才能查询到;同 ID 的 upsert 会替换旧向量。因此「响应成功」不能直接当作「搜索已经能读到」,也不适合在读回失败时反复覆盖整批数据。Vectorize API 官方说明。恢复时保留原快照和目标身份,能把一次中断接着做完;重新生成另一批内容则是新的发布。

下架状态独立于发布快照。切回旧版本时,已下架条目仍受读取限制;版本回退本身不能撤销已经下载到读者设备的副本,也不能替代重新核对分发资格。

语义搜索只负责找到候选#

在这份实践中,embedding 输入来自读者能看见的标题、简介和检索提示,没有为了检索额外发送全部原件正文或私人来源记录。使用的模型是 @cf/baai/bge-m3,输出 1024 维向量;模型输入与输出方式见 Workers AI 模型页。

查询先取得原词匹配,同时用 Vectorize 找相近向量,再合并已有条目关联。每个候选都回到 D1,检查它属于当前发布版本、身份相符、没有被下架,并符合筛选条件。文件读取继续经过成员授权。Vectorize 提供按距离找近邻及 namespace 限定范围的能力;把这些候选交回 D1 核验,是应用自己的阅读合同。Vectorize 查询文档。

这解决了「记得意思,忘了标题」的一部分问题,代价是候选有数量上限,窄筛选可能把已召回的条目继续排除。页面因此区分精确匹配与语义候选,不把一页相近结果说成所有相关资料,也不把「没找到」解释为资料一定不存在。

接上 Remote MCP 后,登录还只是第一步#

后来阅读站增加只读 MCP 工具:搜索、描述条目、按版本与单元读取、查看关联、近期更新和本人收藏。工具复用同一份目录与下架规则,没有给模型一个直接访问数据库或管理资料的入口。

2026-09-22 曾出现一个具体失败:网页已经登录,但从外部客户端进入授权页仍返回 403。原因是应用只允许外站顶层导航到首页与登录页,遗漏了精确的授权入口。修复允许进入该页面,同时保留 consent POST 和资料 API 的同源要求;用户仍需明确授权客户端读取。

验收于是分为三个动作:网页登录证明用户身份;consent 证明用户把指定读取权限交给这个客户端;实际 tool call 才证明 token、工具和资料交付一起可用。MCP 的 HTTP 授权规范要求发现授权服务,并验证 token 确实签发给目标资源;网页 Cookie 并不能自动完成这条路径。MCP 2025-11-25 授权规范。

当时实际观察到了什么#

日期 实际观察范围 留下的限制
2026-09-16~18 正式入口登录、目录、代表性文档与原件读取;停止本机预览服务后仍能在云端阅读 浏览器下载触发与最终落盘核对不同;不是所有读者设备验收
2026-09-21 真正调用 Workers AI、Vectorize、D1;若干改述查询找回预期条目,正式浏览器显示结果和正文 证明代表性召回;没有建立全正文、全问题的检索评测
2026-09-22 从真实 Remote MCP 客户端完成授权,六个只读工具可调用,搜索到正文的路径成立 只覆盖该客户端;合法空结果不证明其他客户端兼容
2026-09-25~30 后续认证修改、部署读回与精确 HTTP 探测 后续部署没有全部重新完成六工具验收,也未覆盖所有成员的授权路径
2026-10-02 同一候选从中断继续,整批数据读回后切换;正式浏览器能看见新增条目 没有重验所有资料、成员、设备或失败路径

这些日期来自作者历史运行记录。本文在 2026-10-03 重构解释并查阅上面的公开文档,没有重新登录该系统或运行云端实验;官方机制与作者观察各自承担不同证据。

怎样在自己的环境验证#

先用不含私人资料的三个小条目:一份短文、一张预览、一条书签。给它们稳定 ID,并明确一个条目暂不允许发布。需要云端执行时,先确定自己的资源、调用预算和清理范围;本仓库没有这份系统的可部署源码。

按用户能观察到的结果逐步检查:

  1. 匿名请求无法取得目录、正文和原件;已获准读者能完整读取选中的样本。
  2. 在文件或向量尚未齐全时停止发布,读者仍看旧版本;恢复后不出现半份新资料。
  3. 在切换前让另一操作改变当前版本,旧预期值的切换应失败;读回真实指针后再决定下一步。
  4. 让一个语义候选处于下架状态,搜索和原件读取都不能把它交付。保留一个明确的精确查询,比较原词与语义结果。
  5. 关闭编辑端后,从另一设备重新请求目录与正文,确认阅读不依赖本机服务。
  6. 若接入 MCP,分别完成登录、明确授权和 search → describe → read;再撤销资格,确认后续读取被拒绝。

若需要多人直接在线编辑、严格实时同步,或只想分享一份公开文件,固定快照发布可能增加不必要的步骤。也可以继续使用已有服务器;需要文件与目录分工时,先读手册:保存内容,也保存它的来历。想先观察中断为什么不能直接重放,运行任务恢复离线例子;它检查本地状态机制,不实现本篇资料发布系统。

术语 / FIELD NOTES

在词表中继续阅读