公开 Reader 的数据来源是 FreshRSS,但不是直接暴露 FreshRSS 的所有数据。中间有一层数据桥接,负责获取、筛选和转换。
数据源
FreshRSS 兼容 Google Reader API,这是一个被广泛支持的 RSS 阅读器 API 标准。通过这个 API 可以:
- 获取订阅源列表。
- 获取文章列表。
- 获取文章详情。
- 标记文章已读/未读。
API 配置
数据桥接使用 FreshRSS 的 /reader/app/api/query.php 端点:
const READER_QUERY_PATH = '/reader/app/api/query.php';
const READER_QUERY_PARAMETERS = new Set(['user', 't', 'f']);
查询参数:
user:用户名(不包含密码)。t:API token。f:固定值greader(Google Reader 兼容模式)。
安全验证
在使用 API 配置之前,进行严格的安全验证:
- HTTPS 强制:只接受 HTTPS 协议的 URL。
- 同源验证:API URL 的 origin 必须与站点 URL 一致。
- 参数验证:只接受预定义的参数名和数量。
- 凭据检查:URL 中不能包含用户名或密码。
- 路径验证:必须是指定的 API 路径。
任何验证失败都会抛出错误,阻止不安全的配置。
数据获取
使用 bounded fetch 获取 API 响应:
const response = await fetcher(source.sourceUrl, {
credentials: 'omit',
redirect: 'error',
headers: { Accept: 'application/json' },
signal: controller.signal,
});
关键安全措施:
- 不发送凭据:
credentials: 'omit'确保不发送 cookie。 - 不跟随重定向:
redirect: 'error'防止被重定向到其他地址。 - 超时控制:10 秒超时,防止长时间挂起。
- 大小限制:响应体不能超过 1MB,防止内存溢出。
数据转换
从 Google Reader API 获取的原始数据需要转换为站点使用的格式:
function adaptGReaderPayload(payload: unknown, now: Date): ReaderResult {
// 转换逻辑
}
转换过程:
- 解析 JSON 响应。
- 提取文章列表。
- 筛选适合公开的内容。
- 转换为标准化的 ReaderItem 格式。
隐私筛选
数据桥接的一个关键职责是确保只有适合公开的内容进入公开 Reader:
公开字段
- 标题
- 链接
- 来源名称
- 摘要
- 发布日期
不公开字段
- 订阅源 URL
- 阅读状态
- 分类和标签
- 账户信息
- 管理接口信息
构建时执行
数据获取在构建时执行,不是在客户端:
- 构建时获取:RSS 数据在
astro build时从 FreshRSS 获取。 - 静态输出:获取的数据被嵌入到静态 HTML 中。
- 客户端无感知:访客看到的是静态页面,不涉及 API 调用。
这意味着:
- FreshRSS 服务器只在构建时被访问,不是每个访客请求都会触发。
- 如果 FreshRSS 不可用,构建会失败,但已部署的站点不受影响。
- 访客的隐私不受影响,因为所有数据都是预渲染的。
错误处理
数据获取可能因为多种原因失败:
- FreshRSS 服务器不可用。
- API token 过期。
- 网络超时。
- 响应格式异常。
失败时的处理策略:
- 构建中断,不生成不完整的 Reader 页面。
- 已部署的上一个版本保持不变。
- 需要手动检查问题并重新构建。
本笔记的状态
这是一个 growing 状态的笔记。随着 Reader 功能的演进,会补充关于增量同步、缓存策略和错误恢复的细节。
评论