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
7.3 KiB
设计:个人主页可选 EJS 整页渲染
日期:2026-04-21
状态:已定稿(修订:与根路径解耦)
修订说明:不再在 /@:publicSlug 上根据设置切换整页;根路径 始终 为现有 Vue 主页。个性化整站(EJS) 使用 单独公开路由,避免与 Nuxt 根路由抢请求、也避免访客/站长对「同一 URL 两种形态」困惑。
1. 背景与目标
当前公开个人根路径 /@:publicSlug 由 Nuxt 页面 app/pages/@[publicSlug]/index.vue 与 layout: public 渲染,视觉与结构受站内组件体系约束。
目标:
/@:publicSlug:不覆盖、不劫持,行为与现网一致,始终 渲染 Vue 默认主页。- 新增 独立公开路由,在满足设置时由服务端输出 完整 HTML(EJS),实现「不限于本站壳」的个性化整页;模板内可链到
/@slug/posts/...、timeline、about等现有 Vue 子路由。 - 路由形态(已选):在
@命名空间下增加 固定子路径,规范路径为:/@:publicSlug/custom- 实现层使用常量(如
PUBLIC_EJS_HOME_SEGMENT = 'custom'),若需改名仅改一处;不得与已有动态段(如posts、about)冲突。
- 模板能力(已确认):采用 完整 EJS(含任意
<% %>服务端逻辑);等价于在服务器上以进程身份执行 用户可控代码,信任模型与滥用责任由运营侧承担;本版 不做 沙箱化必选(若日后对公众开放,需另立项「受限模板 / 沙箱进程」)。
2. 产品规则(已确认)
| 维度 | 规则 |
|---|---|
| 根路径 | /@:publicSlug 仅 Vue;不存在「根路径切 EJS」模式。 |
| 个性化页 | 仅 /@:publicSlug/custom(及同义尾部 /)在启用时由 EJS → text/html 响应;未启用时对访客返回 404(不推荐 301 到根路径,以免外站链到 custom 时期望稳定语义时产生循环或歧义;若产品坚持跳根,可另议)。 |
| 子路由 | /@:publicSlug/posts/...、timeline、about 等 始终 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 或等价、且顺序可控的钩子):
- 仅当请求路径 匹配「个性化页」:
/^\/@(?<slug>[^/]+)\/custom\/?$/(与PUBLIC_EJS_HOME_SEGMENT一致;大小写策略与publicSlug查库 约定对齐)。 - 解析
publicSlug,查库:用户存在 且customHomeEnabled为真 且homeEjsSource非空(具体条件实现可收紧)。 - 满足:拉取 与公开 API 等价 的 payload,执行
ejs.render,Content-Type: text/html;charset=utf-8,结束响应。 - 不满足:不写 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 存储
- 用户表或等价扩展:保存
customHomeEnabled、homeEjsSource(或拆表);最大长度、版本号(可选) 在实现层约束。 - 保存时:可选 先做 语法与编译试跑(仅对所有者预览接口或保存路径),语法错误返回 400,错误信息 不 对匿名访客暴露内部栈。
4.4 可选:站内发现
- 若产品需要在 Vue 根主页展示「个性化站点」入口:仅在
customHomeEnabled时显示链到/@slug/custom;本 spec 不强制(YAGNI,实现计划可单列为小任务)。
5. 安全与滥用(在选项 A 下的最低工程线)
以下内容 不能 替代「仅可信用户 / 条款 + 封禁」的产品决策,但作为 最低限度:
- 单请求:渲染 超时(防止死循环 / 极重计算拖死进程)。
- 模板体积:上限。
- 错误响应:访客侧 通用错误页或简短 500 文案;调试信息仅在 已鉴权预览 中展示。
- 可选:运营 关闭
customHomeEnabled(管理端或 CLI 文档化),使/custom404。
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 plan(writing-plans 产出)。