You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.9 KiB
6.9 KiB
设计:个人主页可选 EJS 整页渲染(多页可扩展)
日期:2026-04-21
状态:已定稿(修订:根路径解耦 + 多页自建)
修订说明
/@:publicSlug不被 EJS 覆盖;始终 Vue 默认主页。- 个性化整页使用 独立路由族,不与 Nuxt 根路由抢请求。
- 不止一条:支持 多条 EJS 页面;主页所有者在控制台 自行添加 / 编辑 / 禁用 / 删除(具体交互由实现计划细化)。不再假设全局仅有一个
custom字符串模板字段。
1. 背景与目标
当前公开个人根路径 /@:publicSlug 由 Nuxt 页面 app/pages/@[publicSlug]/index.vue 与 layout: public 渲染,视觉与结构受站内组件体系约束。
目标:
/@:publicSlug:不覆盖、不劫持,始终 Vue。- 在
@命名空间下增加 固定前缀 + 动态页键,由服务端在用户启用时输出 完整 HTML(EJS),条数 多条,键由用户 添加时指定(在约束内)。 - 模板内可链到
/@slug/posts/...、timeline、about等现有 Vue 子路由。 - 模板能力(已确认):完整 EJS(含任意
<% %>);等价于在服务器上以进程身份执行 用户可控代码,信任模型与滥用责任由运营侧承担;本版 不做 沙箱化必选。
2. 产品规则(已确认)
| 维度 | 规则 |
|---|---|
| 根路径 | /@:publicSlug 仅 Vue。 |
| 个性化页 URL | /@:publicSlug/<PAGES_PREFIX>/<pageKey>(及同义尾部 /)。<PAGES_PREFIX> 为 全站常量(建议 p,短且易记;实现层 PUBLIC_EJS_PAGES_PREFIX),不得与用户内容的第一段自由混淆——访客能一眼看出是「自定义页族」。<pageKey> 为 单段 路径(见下条)。 |
pageKey |
由用户在添加页面时设定;约束:长度、字符集(建议 [a-z0-9-] 小写 storage、展示可折叠)、保留字 拒绝(含与站内 一级 路径同名者,如 posts、timeline、about、reading、以及前缀自身 p 若会产生歧义时由实现明确——至少禁止 pageKey 为空、.、..)。同一 publicSlug 下 pageKey 唯一。 |
| 条数上限 | 每用户可配置页面数上限(具体数字实现定,如 20~50),写入控制台与错误提示;本 spec 不定死数字。 |
| 命中条件 | 用户 publicSlug 有效;存在一行 pageKey 匹配 且 启用 且 ejsSource 非空;否则对该 URL 404。 |
| 子路由 | /@:publicSlug/posts/...、timeline、about 等 始终 Vue;EJS 仅命中 /<PAGES_PREFIX>/:pageKey 双段结构,不 向下吞更深路径(若未来要「子路径」,另开设计)。 |
| 编辑权限 | 仅 对应用户可在控制台 增删改 各页的 pageKey(或仅创建时可写、创建后只读——产品二选一,默认 创建后可改 key 易破坏外链,建议 创建后 key 只读 或 改 key 视为新页 + 旧 URL 301,实现计划定夺)。 |
| 数据真相 | 注入 EJS 的公开数据与 GET /api/public/profile/:publicSlug 语义一致,通过 共享 server 层 组装;并额外注入 当前页 元信息(见 4.2)。 |
3. 非目标(本版明确不做)
- 不 覆盖
/@:publicSlug。 - 不 用 EJS 接管除
/<PAGES_PREFIX>/:pageKey以外的/@slug/**。 - 不 在本版要求 沙箱;不将「受限模板」作为交付前提。
- 不 用仅客户端
v-html冒充 整页 SSR HTML 主路径。
4. 架构(推荐实现)
4.1 Nitro 短路:匹配 /@…/p/:pageKey
在 Nitro 侧(server/middleware 或顺序明确的钩子 / 专用 route):
- 路径匹配
^\/@(?<publicSlug>[^/]+)\/<PAGES_PREFIX>\/(?<pageKey>[^/]+)\/?$(<PAGES_PREFIX>与常量一致)。 publicSlug解析用户;按(userId, pageKey)查 自定义页表。- 命中且启用:组装
locals,ejs.render,text/html,结束响应。 - 未命中:显式 404(不要落到无关 Vue 页)。
与根路径:/@slug 永不进入本分支。
4.2 数据与注入
- profile 单一来源:
getPublicProfilePayloadForHome(publicSlug)(名称可调整)与GET /api/public/profile/:publicSlug共用。 ejs.render(template, locals)建议包含:profile:与公开 APIdata同构;site:站名、公开根 URL;page:{ key, path },path为当前页规范路径(含前缀),便于模板内自链;- URL 辅助:
profileRootPath、buildSitePath('posts')** 等(与现有路由约定一致); - 安全向 helper:
escapeHtml、日期格式化等(与选项 A 一致,不消灭任意 EJS 能力)。
- 禁止项:不向
locals注入无界require;ejs选项 避免引入磁盘任意读;模板仅存 DB,文件名不传真实 FSroot。
4.3 存储
- 独立表(推荐):
user_ejs_pages(名可微调):user_id、page_key、ejs_source、enabled、created_at、updated_at;唯一约束(user_id, page_key)。 - 不 再依赖用户表上单字段
homeEjsSource作为唯一真相(若历史上存在,迁移为表或弃用)。 - 单页模板体积、全站每用户页数 限流在服务层强制。
4.4 可选:站内发现
- Vue 根主页可列出「自定义页」入口(只展示
enabled的页);本 spec 不强制定 UI。
5. 安全与滥用(选项 A 下的最低工程线)
- 单请求 渲染 超时、单模板体积 上限、每用户页数 上限。
- 访客 500/错误页 不泄露栈;预览/保存 可对主人返回语法错误。
- 运营可 禁用某页 或 封禁用户 使对应 URL 404。
6. 错误与兼容性
- 运行时错误:日志 + 访客安全页。
- 不 做「自动删除页」类降级,除非产品后续明确要求并文案提示。
7. 测试
- 单元测试:
ejs.render+ 固定locals;pageKey与保留字校验。 - 集成 / 手测:
- 同一用户两条不同
pageKey均可 200; - 禁用或删除后 404;
/@slug、/@slug/posts/...仍为 Vue。
- 同一用户两条不同
8. 依赖
ejs;如有@types/ejs则 dev。
9. 与早期「仅 /custom」草案的关系
- 早期单一路径
/@slug/custom视为 仅pageKey = 'custom'的特殊情况;现以 通用多页模型 为准。若产品仍需 固定别名(例如默认生成一页welcome),可在实现时 种子一行,不再单独写死单一路由处理器(除非兼容迁移需要)。
下一实现阶段:见 docs/superpowers/plans/ 下 implementation plan(writing-plans 产出)。