1 changed files with 86 additions and 0 deletions
@ -0,0 +1,86 @@ |
|||||
|
# 设计:个人主页可选 EJS 整页渲染 |
||||
|
|
||||
|
**日期**:2026-04-21 |
||||
|
**状态**:已定稿(与产品对话一致) |
||||
|
|
||||
|
## 1. 背景与目标 |
||||
|
|
||||
|
当前公开个人根路径 `/@:publicSlug` 由 **Nuxt 页面** `app/pages/@[publicSlug]/index.vue` 与 **`layout: public`** 渲染,视觉与结构受站内组件体系约束。 |
||||
|
|
||||
|
**目标**: |
||||
|
|
||||
|
- 在 **同一 URL** `/@:publicSlug`(及与之同义的仅带尾部 `/` 的根路径)上,允许用户通过设置 **切换**: |
||||
|
- **Vue 默认主页**(现状),或 |
||||
|
- **服务端 EJS 输出的完整 HTML 文档**(整页不局限于站内壳层;模板内可链到 `/@slug/posts/...`、`timeline`、`about` 等现有 Vue 子路由)。 |
||||
|
- **`/@slug` 根路径以外**(如文章、时光机、关于页)**行为不变**,仍走现有 Vue 路由。 |
||||
|
- **模板能力(已确认)**:采用 **完整 EJS**(含任意 `<% %>` 服务端逻辑);等价于在服务器上以进程身份执行 **用户可控代码**,**信任模型与滥用责任由运营侧承担**;本版 **不做** 沙箱化必选(若日后对公众开放,需另立项「受限模板 / 沙箱进程」)。 |
||||
|
|
||||
|
## 2. 产品规则(已确认) |
||||
|
|
||||
|
| 维度 | 规则 | |
||||
|
|------|------| |
||||
|
| 切换范围 | **仅** 个人主页根路径 `/@:publicSlug`(路径上 **无** 下一级 segment,如 `/posts` 不算)。 | |
||||
|
| 子路由 | `/@:publicSlug/posts/...`、`timeline`、`about` 等 **始终** 为现有 Vue 页;主页模板中可 **链接** 至这些路径(产品叙事:**首页可完全自定义,子页仍是站内 App**)。 | |
||||
|
| 模式 | 每用户一个 **`homeRenderMode`**(命名实现时可微调,语义固定):`vue` \| `ejs`。`vue` 时行为与现网一致;`ejs` 时对根路径返回 **完整 `text/html`**。 | |
||||
|
| 编辑权限 | **仅** 对应 `publicSlug` 的 **已登录所有者** 可在控制台(如 `me` / 设置)编辑模板与切换模式。 | |
||||
|
| 数据真相 | 注入 EJS 的公开数据与 **`GET /api/public/profile/:publicSlug`** **语义一致**(同一可见性、同一字段含义),通过 **共享 server 层** 组装,避免两套逻辑漂移。 | |
||||
|
|
||||
|
## 3. 非目标(本版明确不做) |
||||
|
|
||||
|
- **不** 用 EJS 接管 `/@slug/**` 整棵子树;**不** 改为独立子域或独立路径作为主入口(除非你方后续改需求)。 |
||||
|
- **不** 将「完整 EJS」与「沙箱必选」同时作为本版交付条件;沙箱 / Liquid 等作为 **后续** 产品阶段评估。 |
||||
|
- **不** 以「仅客户端 `v-html` 注入整站」冒充 **整页 SSR HTML** 的主方案(可与方案 1 对比说明,但不替代主路径)。 |
||||
|
|
||||
|
## 4. 架构(推荐实现) |
||||
|
|
||||
|
### 4.1 方案选定:Nitro 短路与早返回 |
||||
|
|
||||
|
在 **Nitro** 侧(`server/middleware` 或等价、且顺序可控的钩子): |
||||
|
|
||||
|
1. 仅当请求路径 **匹配**「公开个人根路径」:`/^\/@(?<slug>[^/]+)\/?$/`(具体正则与大小写策略 **与路由参数约定一致**,需与 Nuxt `@[publicSlug]` 对齐)。 |
||||
|
2. 解析 `publicSlug`,查库得到用户与 **`homeRenderMode`**。 |
||||
|
3. 若为 **`ejs`**:拉取 **与公开 API 等价** 的 payload,执行 **`ejs.render`**,设置 **`Content-Type: text/html;charset=utf-8`**,**结束响应**(不进入 Nuxt 页面管线)。 |
||||
|
4. 若为 **`vue`**:**不写 body**,**放行** 至现有 `index.vue`。 |
||||
|
|
||||
|
**备选(不采用为主)**:独立路径或子域渲染子站(违背同 URL 切换);或仅用 Vue + `v-html`(违背整页 HTML / SEO 预期)。 |
||||
|
|
||||
|
### 4.2 数据与注入 |
||||
|
|
||||
|
- **单一来源**:抽取 **`getPublicProfilePayloadForHome(publicSlug)`**(名称可调整),供 **`GET /api/public/profile/:publicSlug`** 与 EJS 渲染 **共用**。 |
||||
|
- **`ejs.render(template, locals)`**:`locals` 至少包含: |
||||
|
- **`profile`**:与 API 响应体(成功时的业务 `data`)**结构一致** 的对象; |
||||
|
- **`site`**:站名、站点公开根 URL 等(来源与现有全局配置一致); |
||||
|
- **安全向 helper**:如 **`escapeHtml`**、日期展示格式化等(**鼓励** 在模板里用 helper,但 **不** 通过模板消灭 EJS 的任意代码能力——与产品选项 A 一致)。 |
||||
|
- **禁止项(工程习惯)**:不向 `locals` 注入开放 **`require`**、未约束文件路径等;**`ejs` 选项** 需审阅官方文档,避免 **`root` / `filename`** 等导致意外包含路径遍历;模板存储于 **DB 文本字段**,不设「从磁盘任意路径读模板」。 |
||||
|
|
||||
|
### 4.3 存储 |
||||
|
|
||||
|
- 用户表或等价扩展:保存 **`homeRenderMode`**、`homeEjsSource`(或拆表);**最大长度**、**版本号(可选)** 在实现层约束。 |
||||
|
- 保存时:**可选** 先做 **语法与编译试跑**(仅对所有者预览接口或保存路径),语法错误返回 **400**,错误信息 **不** 对匿名访客暴露内部栈。 |
||||
|
|
||||
|
## 5. 安全与滥用(在选项 A 下的最低工程线) |
||||
|
|
||||
|
以下内容 **不能** 替代「仅可信用户 / 条款 + 封禁」的产品决策,但作为 **最低限度**: |
||||
|
|
||||
|
- **单请求**:渲染 **超时**(防止死循环 / 极重计算拖死进程)。 |
||||
|
- **模板体积**:上传 **上限**。 |
||||
|
- **错误响应**:访客侧 **通用错误页或简短 500 文案**;调试信息仅在 **已鉴权预览** 中展示。 |
||||
|
- **可选**:运营 **一键将 `homeRenderMode` 置回 `vue`**(管理端或 CLI 文档化)。 |
||||
|
|
||||
|
## 6. 错误与兼容性 |
||||
|
|
||||
|
- **运行时错误**:记录服务端日志;对匿名访客返回 **安全** 的 HTML 错误页或 500。 |
||||
|
- **自动降级**(连续失败 N 次自动切回 `vue`):**本版默认不做**;若日后做,须在设置中明文提示并可关闭,避免主人困惑。 |
||||
|
|
||||
|
## 7. 测试 |
||||
|
|
||||
|
- **单元测试**:固定 `locals` 与短模板片段,断言 **`ejs.render`** 输出与 **逃逸** 行为。 |
||||
|
- **集成或手动清单**:同一 `publicSlug` 在 `vue` / `ejs` 下各访问一次根路径;确认 **`/posts` 等子路径** 仍为 Vue。 |
||||
|
|
||||
|
## 8. 依赖 |
||||
|
|
||||
|
- 新增 **`ejs`** 运行时依赖(版本以 lockfile 与审计为准);类型如有 **`@types/ejs`** 则开发依赖补齐。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**下一实现阶段**:在用户确认本 spec 无异议后,另见 `docs/superpowers/plans/` 下 **implementation plan**(`writing-plans` 产出)。 |
||||
Loading…
Reference in new issue