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.
 
 
 
 
 

7.3 KiB

设计:个人主页可选 EJS 整页渲染

日期:2026-04-21
状态:已定稿(修订:与根路径解耦)

修订说明:不再在 /@:publicSlug 上根据设置切换整页;根路径 始终 为现有 Vue 主页。个性化整站(EJS) 使用 单独公开路由,避免与 Nuxt 根路由抢请求、也避免访客/站长对「同一 URL 两种形态」困惑。

1. 背景与目标

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

目标

  • /@:publicSlug不覆盖、不劫持,行为与现网一致,始终 渲染 Vue 默认主页。
  • 新增 独立公开路由,在满足设置时由服务端输出 完整 HTML(EJS),实现「不限于本站壳」的个性化整页;模板内可链到 /@slug/posts/...timelineabout 等现有 Vue 子路由。
  • 路由形态(已选):在 @ 命名空间下增加 固定子路径,规范路径为:
    • /@:publicSlug/custom
    • 实现层使用常量(如 PUBLIC_EJS_HOME_SEGMENT = 'custom'),若需改名仅改一处;不得与已有动态段(如 postsabout)冲突。
  • 模板能力(已确认):采用 完整 EJS(含任意 <% %> 服务端逻辑);等价于在服务器上以进程身份执行 用户可控代码信任模型与滥用责任由运营侧承担;本版 不做 沙箱化必选(若日后对公众开放,需另立项「受限模板 / 沙箱进程」)。

2. 产品规则(已确认)

维度 规则
根路径 /@:publicSlug Vue;存在「根路径切 EJS」模式。
个性化页 /@:publicSlug/custom(及同义尾部 /)在启用时由 EJS → text/html 响应;未启用时对访客返回 404(不推荐 301 到根路径,以免外站链到 custom 时期望稳定语义时产生循环或歧义;若产品坚持跳根,可另议)。
子路由 /@:publicSlug/posts/...timelineabout始终 Vue;custom 与上述 segment 并列 接管子树。
启用与编辑 每用户 customHomeEnabled(命名实现可微调)与 homeEjsSource 所有者在控制台开启并编辑模板;关闭后 /custom 404
数据真相 注入 EJS 的公开数据与 GET /api/public/profile/:publicSlug 语义一致(同一可见性、同一字段含义),通过 共享 server 层 组装,避免两套逻辑漂移。

3. 非目标(本版明确不做)

  • 根据设置 覆盖 /@:publicSlug 的响应体或路由解析顺序。
  • 用 EJS 接管 /@slug/**custom 之外的整棵子树;custom 改为通配符子站。
  • 将「完整 EJS」与「沙箱必选」同时作为本版交付条件;沙箱 / Liquid 等作为 后续 产品阶段评估。
  • 以「仅客户端 v-html 注入整站」冒充 整页 SSR HTML 的主方案(可与方案对比说明,但不替代主路径)。

4. 架构(推荐实现)

4.1 方案选定:Nitro 对 /@[slug]/custom 短路

Nitro 侧(server/middleware 或等价、且顺序可控的钩子):

  1. 仅当请求路径 匹配「个性化页」:/^\/@(?<slug>[^/]+)\/custom\/?$/(与 PUBLIC_EJS_HOME_SEGMENT 一致;大小写策略与 publicSlug 查库 约定对齐)。
  2. 解析 publicSlug,查库:用户存在customHomeEnabled 为真 且 homeEjsSource 非空(具体条件实现可收紧)。
  3. 满足:拉取 与公开 API 等价 的 payload,执行 ejs.renderContent-Type: text/html;charset=utf-8结束响应
  4. 不满足不写 body 替代 404——由后续 Nitro/Nuxt 处理;若工程上尚无对应 Vue 占位页,应 显式 404(避免落到无关页面)。推荐:专用 nitro 路由中间件内 对「匹配 /custom 但未启用」直接 404

与根路径的关系:访问 /@slug 永不进入 上述 EJS 分支,仅进入现有 index.vue

4.2 数据与注入

  • 单一来源:抽取 getPublicProfilePayloadForHome(publicSlug)(名称可调整),供 GET /api/public/profile/:publicSlug 与 EJS 渲染 共用
  • ejs.render(template, locals)locals 至少包含:
    • profile:与 API 响应体(成功时的业务 data结构一致 的对象;
    • site:站名、站点公开根 URL 等(来源与现有全局配置一致);
    • URL 辅助:如 profileRootPath = /@{slug}customHomePath = /@{slug}/custom,便于模板内链到站内子路径;
    • 安全向 helper:如 escapeHtml、日期展示格式化等(鼓励 在模板里用 helper,但 通过模板消灭 EJS 的任意代码能力——与产品选项 A 一致)。
  • 禁止项(工程习惯):不向 locals 注入开放 require、未约束文件路径等;ejs 选项 需审阅官方文档,避免 root / filename 等导致意外包含路径遍历;模板存储于 DB 文本字段,不设「从磁盘任意路径读模板」。

4.3 存储

  • 用户表或等价扩展:保存 customHomeEnabledhomeEjsSource(或拆表);最大长度版本号(可选) 在实现层约束。
  • 保存时:可选 先做 语法与编译试跑(仅对所有者预览接口或保存路径),语法错误返回 400,错误信息 对匿名访客暴露内部栈。

4.4 可选:站内发现

  • 若产品需要在 Vue 根主页展示「个性化站点」入口:仅在 customHomeEnabled 时显示链到 /@slug/custom本 spec 不强制(YAGNI,实现计划可单列为小任务)。

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

以下内容 不能 替代「仅可信用户 / 条款 + 封禁」的产品决策,但作为 最低限度

  • 单请求:渲染 超时(防止死循环 / 极重计算拖死进程)。
  • 模板体积上限
  • 错误响应:访客侧 通用错误页或简短 500 文案;调试信息仅在 已鉴权预览 中展示。
  • 可选:运营 关闭 customHomeEnabled(管理端或 CLI 文档化),使 /custom 404

6. 错误与兼容性

  • 运行时错误:记录服务端日志;对匿名访客返回 安全 的 HTML 错误页或 500。
  • 自动降级(连续失败自动关闭 customHomeEnabled):本版默认不做;若日后做,须在设置中明文提示并可关闭。

7. 测试

  • 单元测试:固定 locals 与短模板片段,断言 ejs.render 输出与 逃逸 行为。
  • 集成或手动清单
    • /@slug:始终为 Vue;
    • /@slug/custom:启用 → HTML;关闭 → 404
    • /@slug/posts/... 等仍为 Vue。

8. 依赖

  • 新增 ejs 运行时依赖(版本以 lockfile 与审计为准);类型如有 @types/ejs 则开发依赖补齐。

下一实现阶段:在用户确认本 spec 无异议后,另见 docs/superpowers/plans/implementation planwriting-plans 产出)。