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

8.5 KiB
Raw Blame History

Purpose

本规范定义登录接口返回菜单树和按钮权限的需求。

登录接口将在响应中返回三个权限相关字段:

  • menus: 菜单树(树形结构,用于渲染侧边栏)
  • buttons: 按钮权限码列表(扁平数组,用于控制按钮显示)
  • permissions: 所有权限码列表(扁平数组,保留向后兼容性)

这使得前端可以直接使用菜单树渲染侧边栏,无需二次处理,同时保持与现有系统的向后兼容性。

Requirements

Requirement: 登录响应包含菜单树和按钮权限

登录接口 SHALL 在响应中返回三个权限相关字段:

  • menus: 菜单树(树形结构,用于渲染侧边栏)
  • buttons: 按钮权限码列表(扁平数组,用于控制按钮显示)
  • permissions: 所有权限码列表(扁平数组,保留向后兼容性)

适用端点:

  • POST /api/admin/login(后台登录)
  • POST /api/h5/loginH5 端登录)

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_typeparent_id 字段构建菜单树:

  • 只包含 perm_type = 1(菜单权限)的权限记录
  • 根据 parent_id 字段构建父子关系
  • 根节点为 parent_id = NULLparent_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/meGET /api/h5/me 接口 SHALL NOT 返回 menusbuttons 字段:

  • 只返回 userpermissions 字段(现有行为保持不变)
  • 避免频繁查询和构建菜单树

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 字段,类型为 uintJSON 标签为 "id"
  • THEN 包含 perm_code 字段,类型为 stringJSON 标签为 "perm_code"
  • THEN 包含 name 字段,类型为 stringJSON 标签为 "name"
  • THEN 包含 url 字段,类型为 stringJSON 标签为 "url"
  • THEN 包含 sort 字段,类型为 intJSON 标签为 "sort"
  • THEN 包含 children 字段,类型为 []MenuNodeJSON 标签为 "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