## ADDED 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