Browse Source
Define a unified design for mixed-mode post tags and backend URL-persistent filtering across all post list pages, including OR/AND semantics, data model migration, API contracts, and rollout/testing strategy. Made-with: Cursormain
1 changed files with 269 additions and 0 deletions
@ -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 持久化。 |
||||
Loading…
Reference in new issue