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

设计:个人主页可选 EJS 整页渲染(多页可扩展)

日期:2026-04-21
状态:已定稿(修订:根路径解耦 + 多页自建)

修订说明

  1. /@:publicSlug 不被 EJS 覆盖;始终 Vue 默认主页。
  2. 个性化整页使用 独立路由族与 Nuxt 根路由抢请求。
  3. 不止一条:支持 多条 EJS 页面;主页所有者在控制台 自行添加 / 编辑 / 禁用 / 删除(具体交互由实现计划细化)。不再假设全局仅有一个 custom 字符串模板字段。

1. 背景与目标

当前公开个人根路径 /@:publicSlugNuxt 页面 app/pages/@[publicSlug]/index.vuelayout: public 渲染,视觉与结构受站内组件体系约束。

目标

  • /@:publicSlug不覆盖、不劫持始终 Vue。
  • @ 命名空间下增加 固定前缀 + 动态页键,由服务端在用户启用时输出 完整 HTML(EJS),条数 多条,键由用户 添加时指定(在约束内)。
  • 模板内可链到 /@slug/posts/...timelineabout 等现有 Vue 子路由。
  • 模板能力(已确认)完整 EJS(含任意 <% %>);等价于在服务器上以进程身份执行 用户可控代码信任模型与滥用责任由运营侧承担;本版 不做 沙箱化必选。

2. 产品规则(已确认)

维度 规则
根路径 /@:publicSlug Vue。
个性化页 URL /@:publicSlug/<PAGES_PREFIX>/<pageKey>(及同义尾部 /)。<PAGES_PREFIX>全站常量(建议 p,短且易记;实现层 PUBLIC_EJS_PAGES_PREFIX),不得与用户内容的第一段自由混淆——访客能一眼看出是「自定义页族」。<pageKey>单段 路径(见下条)。
pageKey 由用户在添加页面时设定;约束:长度、字符集(建议 [a-z0-9-] 小写 storage、展示可折叠)、保留字 拒绝(含与站内 一级 路径同名者,如 poststimelineaboutreading以及前缀自身 p 若会产生歧义时由实现明确——至少禁止 pageKey 为空、...)。同一 publicSlugpageKey 唯一
条数上限 每用户可配置页面数上限(具体数字实现定,如 20~50),写入控制台与错误提示;本 spec 不定死数字。
命中条件 用户 publicSlug 有效;存在一行 pageKey 匹配启用ejsSource 非空;否则对该 URL 404
子路由 /@:publicSlug/posts/...timelineabout始终 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):

  1. 路径匹配 ^\/@(?<publicSlug>[^/]+)\/<PAGES_PREFIX>\/(?<pageKey>[^/]+)\/?$<PAGES_PREFIX> 与常量一致)。
  2. publicSlug 解析用户;按 (userId, pageKey)自定义页表
  3. 命中且启用:组装 localsejs.rendertext/html,结束响应。
  4. 未命中显式 404(不要落到无关 Vue 页)。

与根路径/@slug 永不进入本分支。

4.2 数据与注入

  • profile 单一来源getPublicProfilePayloadForHome(publicSlug)(名称可调整)与 GET /api/public/profile/:publicSlug 共用。
  • ejs.render(template, locals) 建议包含:
    • profile:与公开 API data 同构;
    • site:站名、公开根 URL;
    • page{ key, path }path 为当前页规范路径(含前缀),便于模板内自链;
    • URL 辅助profileRootPathbuildSitePath('posts')** 等(与现有路由约定一致);
    • 安全向 helperescapeHtml、日期格式化等(与选项 A 一致,不消灭任意 EJS 能力)。
  • 禁止项:不向 locals 注入无界 requireejs 选项 避免引入磁盘任意读;模板仅存 DB,文件名不传真实 FS root

4.3 存储

  • 独立表(推荐):user_ejs_pages(名可微调):user_idpage_keyejs_sourceenabledcreated_atupdated_at唯一约束 (user_id, page_key)
  • 再依赖用户表上单字段 homeEjsSource 作为唯一真相(若历史上存在,迁移为表或弃用)。
  • 单页模板体积全站每用户页数 限流在服务层强制。

4.4 可选:站内发现

  • Vue 根主页可列出「自定义页」入口(只展示 enabled 的页);本 spec 不强制定 UI

5. 安全与滥用(选项 A 下的最低工程线)

  • 单请求 渲染 超时单模板体积 上限、每用户页数 上限。
  • 访客 500/错误页 不泄露栈;预览/保存 可对主人返回语法错误。
  • 运营可 禁用某页封禁用户 使对应 URL 404

6. 错误与兼容性

  • 运行时错误:日志 + 访客安全页。
  • 做「自动删除页」类降级,除非产品后续明确要求并文案提示。

7. 测试

  • 单元测试ejs.render + 固定 localspageKey 与保留字校验。
  • 集成 / 手测
    • 同一用户两条不同 pageKey 均可 200;
    • 禁用或删除后 404
    • /@slug/@slug/posts/... 仍为 Vue。

8. 依赖

  • ejs;如有 @types/ejs 则 dev。

9. 与早期「仅 /custom」草案的关系

  • 早期单一路径 /@slug/custom 视为 pageKey = 'custom' 的特殊情况;现以 通用多页模型 为准。若产品仍需 固定别名(例如默认生成一页 welcome),可在实现时 种子一行再单独写死单一路由处理器(除非兼容迁移需要)。

下一实现阶段:见 docs/superpowers/plans/implementation planwriting-plans 产出)。