diff --git a/docs/superpowers/specs/2026-04-25-post-tags-and-list-filter-design.md b/docs/superpowers/specs/2026-04-25-post-tags-and-list-filter-design.md new file mode 100644 index 0000000..6285590 --- /dev/null +++ b/docs/superpowers/specs/2026-04-25-post-tags-and-list-filter-design.md @@ -0,0 +1,269 @@ +# 文章标签与列表筛选设计 + +日期:2026-04-25 +状态:已评审(待实现计划) +作者:Codex(与用户协作确认) + +## 1. 背景与目标 + +当前系统已有文章能力与多个文章列表页面,但标签能力未形成完整闭环: + +- 文章创建/编辑页未提供稳定的标签编辑体验。 +- 各文章列表页缺乏统一标签筛选能力。 +- 筛选状态无法通过 URL 持久化,导致刷新、分享、回退体验不完整。 + +本设计目标是为文章系统增加“标签 + 统一筛选”能力,并覆盖所有文章列表页面,满足以下要求: + +- 标签体系:混合模式(可选已有标签 + 允许新建标签)。 +- 筛选方式:支持多标签 OR/AND 切换。 +- 筛选执行:后端查询筛选,前端通过 URL 参数持久化。 +- 范围:覆盖“各个文章列表”页面,而非仅单页。 + +## 2. 范围定义 + +### 2.1 In Scope + +- 文章新增/编辑支持标签输入与保存。 +- 文章详情接口返回标准化标签列表用于回填。 +- 各文章列表接口支持按标签筛选(含分页联动)。 +- 各文章列表页面提供统一筛选交互(多选标签 + OR/AND)。 +- URL 参数承载筛选状态,支持刷新/分享/前进后退。 +- 数据模型升级为标准化标签关系模型,并提供迁移回填策略。 + +### 2.2 Out of Scope + +- 独立“标签管理后台”(全量管理、批量操作)。 +- 标签重命名、合并、别名管理工具。 +- 标签统计分析与推荐系统。 + +## 3. 关键决策与取舍 + +### 3.1 方案选择 + +已确认采用“标准化关系模型”方案: + +- 新增 `tags` + `post_tags` 多对多关系。 +- `posts.tagsJson` 暂保留作兼容过渡,不作为长期主模型。 + +原因: + +- 能稳定支撑 OR/AND、分页总数一致、标签池聚合等中长期需求。 +- 优于继续在 `tagsJson` 上做字符串查询的性能与可维护性。 + +### 3.2 筛选语义 + +- `tagMode=or`:命中任一所选标签即返回。 +- `tagMode=and`:必须同时命中全部所选标签才返回。 + +默认值为 `or`,非法值自动回退到 `or`。 + +## 4. 数据模型设计 + +## 4.1 新增表 + +- `tags` + - `id`(PK) + - `userId`(所属用户) + - `name`(展示名) + - `slug`(规范化匹配键) + - `createdAt` + - `updatedAt` + +- `post_tags` + - `postId` + - `tagId` + - `createdAt` + +## 4.2 约束与索引 + +- `tags`:唯一约束 `(userId, slug)`,防止同一用户重复标签。 +- `post_tags`:唯一约束 `(postId, tagId)`,防止重复关联。 +- 索引: + - `post_tags(postId)` + - `post_tags(tagId)` + - 视实现需要可补充复合索引以优化 AND 查询。 + +## 4.3 兼容策略 + +- 过渡期保留 `posts.tagsJson`: + - 写入以新关系表为主。 + - 可选同步回写 `tagsJson` 供旧逻辑读取。 +- 新接口返回 `tags: string[]`,逐步替代 `tagsJson` 在前端的使用。 +- 稳定后再评估移除 `tagsJson` 的窗口与步骤。 + +## 5. 标签规范 + +## 5.1 输入规则 + +- 允许用户从“已有标签池”选择。 +- 允许直接输入新标签并即时创建。 +- 单篇文章可设置多个标签。 + +## 5.2 归一化规则 + +写入前统一归一化: + +- 去除首尾空白。 +- 合并中间连续空白为单空格。 +- 生成匹配用 `slug`(用于唯一约束与筛选匹配)。 +- 展示名保留用户输入风格(在不违反校验的前提下)。 + +## 5.3 校验规则 + +- 空标签忽略。 +- 单标签长度上限:32 个字符。 +- 单篇文章标签数量上限:10 个。 +- 标签字符集:中文、英文字母、数字、空格、短横线(`-`)、下划线(`_`)。 + +## 6. API 设计 + +## 6.1 写接口 + +- `POST /api/me/posts` +- `PUT /api/me/posts/:id` + +请求体增加: + +- `tags: string[]` + +服务端流程: + +1. 归一化与校验 `tags`。 +2. 对标签执行用户维度 upsert(`tags` 表)。 +3. 在事务内重建文章标签关系(`post_tags`)。 +4. 返回标准化 `tags` 结果。 + +## 6.2 详情接口 + +- `GET /api/me/posts/:id` + +返回补充: + +- `tags: string[]`(用于编辑页回填) + +## 6.3 列表接口(统一筛选) + +- 我的文章: + - `GET /api/me/posts?page=...&tags=tag-a,tag-b&tagMode=or|and` +- 公开文章: + - `GET /api/public/profile/:publicSlug/posts?page=...&tags=...&tagMode=...` + +返回补充: + +- `availableTags: string[]`(当前主体可用标签池) +- `selectedTags: string[]`(本次生效筛选) +- `tagMode: "or" | "and"`(本次生效模式) + +注意: + +- `total` 与 `items` 必须共用完全一致的 where 条件,防止分页错位。 + +## 7. 查询逻辑 + +## 7.1 OR 查询 + +返回包含任一所选标签的文章。 + +## 7.2 AND 查询 + +返回同时包含全部所选标签的文章。推荐通过“按文章分组 + 命中标签数等于筛选标签数”实现。 + +## 7.3 空筛选 + +- `tags` 为空时不加标签过滤。 +- 仅 `tagMode` 存在但无 `tags` 时,视同无筛选。 + +## 8. 前端交互设计 + +## 8.1 编辑页(新建/编辑文章) + +- 增加标签输入区域: + - 可选择已有标签。 + - 可输入并创建新标签。 +- 保存时提交 `tags: string[]`。 +- 加载详情时回填标签。 + +## 8.2 各文章列表页 + +统一新增: + +- 标签多选控件。 +- OR/AND 模式切换控件。 +- 清空筛选按钮。 +- 当前筛选条件展示(可逐个移除)。 + +## 8.3 URL 状态管理 + +通过 query 持久化: + +- `tags`: 逗号分隔的标签 slug 或约定编码值。 +- `tagMode`: `or` / `and`。 +- 与 `page` 联动:筛选变化时可重置到第 1 页。 + +交互保证: + +- 刷新后恢复筛选状态。 +- 链接分享后可复现同筛选视图。 +- 浏览器前进/后退可恢复历史筛选状态。 + +## 8.4 空态文案 + +区分两种空态: + +- 真空:暂无任何文章。 +- 筛选空:存在文章,但当前标签筛选无结果。 + +## 9. 错误处理与一致性 + +- 非法 `tagMode`:回退 `or`。 +- 非法 `tags`:返回 400(明确错误提示)。 +- 写入采用事务,任一步失败全回滚。 +- 接口返回的 `selectedTags/tagMode` 应是服务端最终生效值,前端直接据此回显。 + +## 10. 测试策略 + +## 10.1 后端单元/服务测试 + +- 标签归一化。 +- 标签 upsert 去重(同用户维度)。 +- 文章标签关系重建。 +- OR/AND 筛选结果正确性。 + +## 10.2 API 测试 + +- query 参数解析(含非法输入)。 +- 分页 + 筛选一致性(`items/total/page`)。 +- 空筛选与边界筛选行为。 + +## 10.3 前端测试 + +- URL 参数回写与恢复。 +- 切换 OR/AND 的行为与请求参数。 +- 清空筛选与空态分支。 +- 翻页后保持筛选条件。 + +## 11. 迁移与发布策略 + +1. 新增 migration:创建 `tags`、`post_tags`、索引与约束。 +2. 增加回填脚本:从 `posts.tagsJson` 导入新关系表。 +3. 上线后双读/双写过渡(按实现方案定具体比例和时长)。 +4. 观察稳定性后,制定 `tagsJson` 下线计划。 + +## 12. 实施拆分建议(供计划阶段使用) + +- Step 1:数据库迁移与模型定义。 +- Step 2:服务层标签写入/读取与筛选查询。 +- Step 3:`/api/me/posts*` 接口改造。 +- Step 4:`/api/public/profile/:publicSlug/posts` 接口改造。 +- Step 5:新建/编辑页标签输入。 +- Step 6:各文章列表页筛选 UI + URL 同步。 +- Step 7:测试补齐与回归。 + +--- + +本设计已满足当前确认项: + +- 覆盖各文章列表页面。 +- 标签模式为“混合(可选已有 + 可新建)”。 +- 筛选支持 OR/AND 切换。 +- 后端查询筛选并通过 URL 持久化。