9.1 KiB
Person Panel:多用户个人数据中心与 RSS 收件箱 — 设计说明
日期:2026-04-18
状态:已评审(待实现计划)
范围:在现有 Nuxt 4 + Nitro + SQLite + Drizzle + @nuxt/ui 仓库上扩展,不引入分布式队列/Redis(单实例)。
1. 背景与目标
1.1 目标
构建 多用户个人数据中心:每位用户独立管理 个人资料/生平叙事、Markdown 文章、时光机(时间线)、RSS 阅读收件箱;支持 条目级可见性(公开 / 私密 / 仅链接);实例级 管理员 负责账号生命周期(邀请制:管理员创建账号)。定时任务在 单实例进程内 周期性拉取 RSS。
1.2 非目标(第一期不做)
- 空间协作:多账号共编同一主页(owner/editor 等)不做。
- RSS 订阅源列表对外公开:订阅源仅本人可见。
- 通用第三方 API 插件平台:先做 RSS/Atom(及可解析的变体);扩展点可在代码层预留接口,不产品化。
- 分布式任务队列 / 多实例 SQLite 水平扩展:不纳入本期;若未来需要,再评估「外部 HTTPS 触发同步」或独立 worker。
1.3 已确认需求摘要
| 维度 | 选择 |
|---|---|
| 用户模型 | 多用户,数据按 userId 隔离 |
| 注册 | 邀请制:管理员创建账号 |
| 可见性策略 | 混合:部分可对公网展示 |
| 可见性粒度 | 条目级(每篇文章、每条时间线事件、每条 RSS 条目可单独设置) |
| RSS 源列表 | 永远仅本人可见 |
| RSS 新条目默认 | 私密 |
| 角色 | 实例级:admin(开号/禁用)与 user |
| 内容主线 | 自写 Markdown 为主;RSS 为阅读收件箱,可与时间线等合并展示(UI 层) |
| 定时任务 | 进程内调度(A) |
| 部署 | 单实例 |
1.4 架构方案(选定)
单仓 Nuxt/Nitro 单体 + 领域分包 + SQLite:在现有仓库内划分 auth/admin、profile、posts、timeline、rss、jobs;定时器在 Nitro ready 钩子注册;抓取逻辑异步分批、每源超时,避免阻塞事件循环。
2. 功能模块
- Auth:延续 Cookie 会话;
users表扩展role、status、publicSlug等。 - Admin:创建用户、禁用/启用;首个管理员通过 bootstrap 环境变量 创建(见 §5.2)。
- Profile:简介、头像、生平(Markdown)、社交链接等;展示受条目/字段可见性约束(与 §3 一致)。
- Posts:Markdown 文章;
slug在单用户内唯一;元数据含标题、摘要、封面、标签、发布时间。 - Timeline:时间线事件表;可与 Posts / RSS 在 UI 上合并展示;第一期以 独立事件表 + 可选手动关联 为主,自动聚合为二期。
- RSS:订阅源、抓取状态、条目存储、去重;每用户独立。
- Jobs:进程内调度,周期性同步到期订阅源;启动后短延迟补跑一次。
3. 数据模型与可见性
3.1 多租户隔离
所有业务表含 userId。me/* API 必须从会话解析 userId,禁止由客户端指定任意 userId 访问他人数据。admin/* 在显式校验 role=admin 后操作账号维度字段。
3.2 表(概念)
| 域 | 表 | 要点 |
|---|---|---|
| 账号 | users(扩展) |
role: admin | user;status: active | disabled;publicSlug 唯一(公开主页) |
| 文章 | posts |
Markdown 正文、元数据、visibility、userId;slug 用户内唯一 |
| 时光机 | timeline_events |
日期/时间段、标题、正文、visibility、userId |
| RSS | rss_feeds |
feedUrl、userId、拉取状态(lastFetchedAt、lastError 等)、可选 pollIntervalMinutes |
| RSS | rss_items |
feedId、userId(冗余便于查询)、guid、canonicalUrl、标题、摘要、正文片段、publishedAt;默认 visibility=private |
去重:优先 guid,否则规范化后的 canonicalUrl。
3.3 可见性枚举(统一)
private:仅所有者可读(第一期建议:管理员默认不读取用户私密正文,仅管理账号;若需审计再单列策略)。unlisted:凭 不可猜测链接 访问;不出现在公开聚合列表;不进站点公共 feed(若未来有)。public:出现在该用户 公开主页 的聚合列表中。
仅链接 URL(U1,已定稿):采用 用户可读 publicSlug + 短随机 shareToken 的路径形态(例如 /p/:publicSlug/t/:shareToken),避免仅依赖可枚举 id。
3.4 公开主页 URL(实现定稿)
采用 /@:publicSlug 作为公开主页路径(若实现时发现与路由冲突,可退化为 /u/:publicSlug,但仓库内应 只保留一种 canonical 形式)。
3.5 禁用用户后的公开行为(实现定稿)
用户 status=disabled 时,其 公开主页返回 404;已登录会话在后续请求中拒绝。不在第一期做「禁用后仍展示历史公开快照」。
4. API 与路由分层
server/api/public/*:未登录可访问;仅聚合visibility=public的内容。unlisted通过专用详情路由(§3.3)。server/api/me/*:登录用户读写自有数据。server/api/admin/*:仅admin;用户 CRUD、可选实例级健康统计(注意隐私与脱敏)。
中间件:公开白名单 + 会话保护;admin 额外校验 role。
5. 管理员与账号流程
5.1 创建与禁用
- 管理员创建用户:
username、初始密码、可选email;role=user,status=active。 - 禁用:
status=disabled,使会话失效策略与中间件一致(见 §3.5)。
5.2 首个管理员(Bootstrap)
通过一次性环境变量创建,例如:
BOOTSTRAP_ADMIN_USERNAMEBOOTSTRAP_ADMIN_PASSWORD
在 迁移/启动脚本 中若检测到不存在任何 admin 用户则创建;成功后运维侧应 移除或清空 上述变量。具体执行点(seed vs 启动插件)在实现计划中敲定,但必须保证 不可重复无意提权。
5.3 自助注册
关闭或隐藏面向公众的注册;与现有 register 路由的兼容策略在实现计划中明确(例如仅 dev 开放或完全移除)。
6. Nuxt UI 信息架构
- 登录:Nuxt UI 表单组件;无自助注册入口(或仅管理员可见)。
- 站内(登录后):侧栏 — 概览、文章、时光机、RSS、资料、设置;
admin额外「用户管理」。 - RSS:左侧订阅源(本人)、右侧条目流;支持 手动触发同步;条目上切换可见性。
- 公开站:独立简洁布局,以阅读为主。
7. 定时任务(进程内)
- Nitro
ready注册间隔任务(如每 N 分钟扫描到期rss_feeds)。 - 启动补跑:短延迟触发一轮到期同步。
- 并发:小并发池 + 每源超时;失败写入
lastError;可选对连续失败源做退避。 - 手动同步:
me/rss/sync类接口,仍受 SSRF 与超时约束。
8. 安全基线
- RSS SSRF:仅允许
http/https;限制重定向;解析 IP 时阻断私网段(实现细节遵循所选 HTTP 客户端能力);响应体大小上限。 - 管理员接口:内存级速率限制或反向代理层限制(实现计划二选一)。
- Cookie:延续
HttpOnly、SameSite等与现有cookie服务一致的做法。
9. 错误处理与观测
- API 错误:统一 JSON 错误体(沿用现有
handler/响应工具);区分 401/403/404/422/429/500。 - RSS 抓取错误:写入
rss_feeds.lastError(及时间);用户可在 UI 查看 自己的 源状态;不向访客泄露内部堆栈。 - 日志:抓取失败、SSRF 拒绝、管理员操作可记审计日志(级别与脱敏在实现计划中定)。
10. 测试策略
- 单元测试:RSS URL 校验、去重键生成、可见性判定纯函数。
- 集成测试:
me与public路由在 多用户数据并存 下无串租;admin仅管理员可访问。 - 手动清单:禁用用户 404、unlisted 链接不可枚举进列表、新 RSS 条目默认私密。
11. 配置项(建议)
| 变量 | 含义 |
|---|---|
RSS_SYNC_INTERVAL_MINUTES |
全局默认轮询间隔(可被单源覆盖) |
RSS_FETCH_TIMEOUT_MS |
单源超时 |
RSS_MAX_CONCURRENT_FEEDS |
同步并发上限 |
RSS_MAX_RESPONSE_BYTES |
响应体上限 |
BOOTSTRAP_ADMIN_* |
仅首次创建管理员(§5.2) |
12. 自检记录(spec review)
- 占位符:无 TBD;bootstrap 执行点标为「实现计划敲定」属合理边界。
- 一致性:多用户隔离、条目级可见性、RSS 默认私密、单实例进程内任务彼此一致。
- 范围:第一期不引入队列/多实例;协作与 OPML 等未承诺。
- 歧义:公开路径已定
/@:publicSlug;禁用用户公开 404;unlisted 为 U1。
13. 后续步骤
实现前在单独会话使用 writing-plans 技能生成实现计划;本设计文档经作者与维护者确认后再进入开发。