Files
junhong_cmp_fiber/openspec/specs/login-menu-button-response/spec.md
2026-01-30 17:22:38 +08:00

210 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Purpose
本规范定义登录接口返回菜单树和按钮权限的需求。
登录接口将在响应中返回三个权限相关字段:
- `menus`: 菜单树(树形结构,用于渲染侧边栏)
- `buttons`: 按钮权限码列表(扁平数组,用于控制按钮显示)
- `permissions`: 所有权限码列表(扁平数组,保留向后兼容性)
这使得前端可以直接使用菜单树渲染侧边栏,无需二次处理,同时保持与现有系统的向后兼容性。
# Requirements
## Requirement: 登录响应包含菜单树和按钮权限
登录接口 SHALL 在响应中返回三个权限相关字段:
- `menus`: 菜单树(树形结构,用于渲染侧边栏)
- `buttons`: 按钮权限码列表(扁平数组,用于控制按钮显示)
- `permissions`: 所有权限码列表(扁平数组,保留向后兼容性)
适用端点:
- `POST /api/admin/login`(后台登录)
- `POST /api/h5/login`H5 端登录)
### Scenario: 普通用户登录成功
- **WHEN** 普通用户(非超级管理员)登录成功
- **THEN** 响应包含 `menus` 数组(包含用户有权限的菜单树)
- **THEN** 响应包含 `buttons` 数组(包含用户有权限的按钮权限码)
- **THEN** 响应包含 `permissions` 数组(包含所有权限码)
- **THEN** `menus` 数组为树形结构,每个节点包含 `id`, `perm_code`, `name`, `url`, `sort`, `children` 字段
### Scenario: 用户无任何权限
- **WHEN** 用户登录成功但未分配任何角色或权限
- **THEN** 响应包含空的 `menus` 数组 `[]`
- **THEN** 响应包含空的 `buttons` 数组 `[]`
- **THEN** 响应包含空的 `permissions` 数组 `[]`
## Requirement: 菜单权限构建树形结构
系统 SHALL 基于权限表的 `perm_type``parent_id` 字段构建菜单树:
- 只包含 `perm_type = 1`(菜单权限)的权限记录
- 根据 `parent_id` 字段构建父子关系
- 根节点为 `parent_id = NULL``parent_id = 0` 的权限
- 子节点追加到父节点的 `children` 数组中
### Scenario: 构建两级菜单树
- **WHEN** 用户有以下权限:
- ID=1, perm_code="user:menu", perm_type=1, parent_id=NULL用户管理
- ID=2, perm_code="user:list:menu", perm_type=1, parent_id=1用户列表
- **THEN** `menus` 数组包含 1 个根节点(用户管理)
- **THEN** 根节点的 `children` 数组包含 1 个子节点(用户列表)
### Scenario: 孤儿节点提升为根节点
- **WHEN** 用户有子菜单权限perm_code="user:list:menu", parent_id=1
- **WHEN** 用户没有父菜单权限ID=1 不在权限列表中)
- **THEN** 子菜单提升为根节点,出现在 `menus` 数组的顶层
- **THEN** 子菜单的 `children` 数组为空
## Requirement: 按钮权限提取扁平列表
系统 SHALL 提取所有 `perm_type = 2`(按钮权限)的权限码作为 `buttons` 数组:
- 只包含 `perm_code` 字段值
- 不构建树形结构
- 按原始顺序返回
### Scenario: 提取按钮权限码
- **WHEN** 用户有以下权限:
- perm_code="user:create", perm_type=2
- perm_code="user:update", perm_type=2
- perm_code="user:delete", perm_type=2
- **THEN** `buttons` 数组包含 `["user:create", "user:update", "user:delete"]`
## Requirement: 平台过滤
系统 SHALL 根据登录请求的 `device` 参数过滤权限的 `platform` 字段:
- `platform = "all"` 的权限对所有端口可见
- `platform = "web"` 的权限只在 `device = "web"` 时可见
- `platform = "h5"` 的权限只在 `device = "h5"` 时可见
- 未指定 `device` 参数时默认为 `"web"`
### Scenario: Web 后台登录过滤 H5 菜单
- **WHEN** 用户登录时 `device = "web"`
- **WHEN** 用户有以下权限:
- perm_code="dashboard:menu", perm_type=1, platform="all"
- perm_code="user:menu", perm_type=1, platform="web"
- perm_code="mobile:menu", perm_type=1, platform="h5"
- **THEN** `menus` 数组包含 "dashboard:menu" 和 "user:menu"
- **THEN** `menus` 数组不包含 "mobile:menu"H5 专属菜单被过滤)
### Scenario: H5 端登录过滤 Web 菜单
- **WHEN** 用户登录时 `device = "h5"`
- **WHEN** 用户有以下权限:
- perm_code="mobile:menu", perm_type=1, platform="h5"
- perm_code="user:menu", perm_type=1, platform="web"
- perm_code="common:menu", perm_type=1, platform="all"
- **THEN** `menus` 数组包含 "mobile:menu" 和 "common:menu"
- **THEN** `menus` 数组不包含 "user:menu"Web 专属菜单被过滤)
## Requirement: 超级管理员获取所有权限
系统 SHALL 为超级管理员(`user_type = 1`)返回所有菜单和按钮权限:
- 查询数据库中所有 `status = 1`(启用)的权限
- 仍然应用平台过滤(根据 `device` 参数)
- 不查询角色权限关联表
### Scenario: 超级管理员登录
- **WHEN** 超级管理员user_type=1登录
- **WHEN** 数据库包含 100 个启用的权限50 个菜单 + 50 个按钮)
- **WHEN** 登录时 `device = "web"`
- **THEN** `menus` 数组包含所有 `platform="all"``platform="web"` 的菜单权限
- **THEN** `buttons` 数组包含所有 `platform="all"``platform="web"` 的按钮权限
- **THEN** 不包含 `platform="h5"` 的权限
## Requirement: 菜单排序
菜单树 SHALL 根据权限表的 `sort` 字段排序:
- 同级菜单按 `sort` 字段升序排列
- 子菜单在其父节点的 `children` 数组中按 `sort` 排序
- 递归应用到所有层级
### Scenario: 菜单按 sort 字段排序
- **WHEN** 用户有以下权限:
- perm_code="order:menu", sort=3
- perm_code="user:menu", sort=1
- perm_code="dashboard:menu", sort=2
- **THEN** `menus` 数组的顺序为 `["user:menu", "dashboard:menu", "order:menu"]`
### Scenario: 子菜单按 sort 字段排序
- **WHEN** 父菜单 "user:menu" 有三个子菜单:
- "user:list:menu", sort=10
- "user:role:menu", sort=5
- "user:dept:menu", sort=8
- **THEN** 父菜单的 `children` 数组顺序为 `["user:role:menu", "user:dept:menu", "user:list:menu"]`
## Requirement: GetMe 接口不返回菜单
`GET /api/admin/me``GET /api/h5/me` 接口 SHALL NOT 返回 `menus``buttons` 字段:
- 只返回 `user``permissions` 字段(现有行为保持不变)
- 避免频繁查询和构建菜单树
### Scenario: 调用 GetMe 接口
- **WHEN** 已登录用户调用 `GET /api/admin/me`
- **THEN** 响应包含 `user` 对象
- **THEN** 响应包含 `permissions` 数组(权限码列表)
- **THEN** 响应不包含 `menus` 字段
- **THEN** 响应不包含 `buttons` 字段
## Requirement: MenuNode 数据结构
系统 SHALL 定义 `MenuNode` DTO 结构体,包含以下字段:
- `id` (uint): 权限 ID
- `perm_code` (string): 权限码(如 "user:menu"
- `name` (string): 菜单名称(如 "用户管理"
- `url` (string): 路由路径(如 "/users"
- `sort` (int): 排序值
- `children` ([]MenuNode): 子菜单数组(递归结构)
所有字段 MUST 包含 JSON 标签。
### Scenario: MenuNode 结构定义
- **WHEN** 定义 MenuNode 结构体
- **THEN** 包含 `id` 字段,类型为 `uint`JSON 标签为 `"id"`
- **THEN** 包含 `perm_code` 字段,类型为 `string`JSON 标签为 `"perm_code"`
- **THEN** 包含 `name` 字段,类型为 `string`JSON 标签为 `"name"`
- **THEN** 包含 `url` 字段,类型为 `string`JSON 标签为 `"url"`
- **THEN** 包含 `sort` 字段,类型为 `int`JSON 标签为 `"sort"`
- **THEN** 包含 `children` 字段,类型为 `[]MenuNode`JSON 标签为 `"children"`
## Requirement: 响应格式向后兼容
系统 SHALL 保留原有 `permissions` 字段,确保向后兼容:
- 登录响应同时包含 `permissions`, `menus`, `buttons` 三个字段
- 前端可以选择使用新字段或继续使用旧字段
- `permissions` 包含所有权限码(菜单 + 按钮)
### Scenario: 向后兼容性验证
- **WHEN** 用户登录成功
- **WHEN** 用户有 3 个菜单权限和 2 个按钮权限
- **THEN** 响应包含 `permissions` 数组,长度为 5
- **THEN** 响应包含 `menus` 数组(树形结构)
- **THEN** 响应包含 `buttons` 数组,长度为 2
- **THEN** 旧版前端仍可使用 `permissions` 字段正常工作
## Requirement: 性能要求
菜单树构建逻辑 MUST 满足以下性能要求:
- 时间复杂度为 O(n)n 为权限数量
- 登录响应时间增加 < 50ms在权限数量 < 100 的场景下)
- 不影响 GetMe 接口性能(未修改)
### Scenario: 性能基准测试
- **WHEN** 用户有 50 个权限30 个菜单 + 20 个按钮)
- **WHEN** 菜单最大层级为 3 级
- **THEN** 登录接口响应时间增加 < 50ms
- **THEN** 菜单树构建时间 < 10ms