Skip to content
bitzorcas
中EN

Concept

Menu 菜单目录与导航

源码校验 Menu 的全局菜单目录、九个 HTTP 用例、授权可见性、树形投影、缓存同步、种子和当前产品边界。

Last updated

Menu 是平台的全局导航目录 owner。它管理 SysModule 的结构与展示字段,并根据当前用户的角色授权生成平铺列表、菜单树和前端导航。它不决定目标 API 是否可访问,也不是 Feature 管理器或前端路由注册中心。

1. 当前产品面

220-sys_module.csv

SysModule 全局目录

MenuTreeBuilder

当前用户角色

MenuVisibilityResolver

Authorization 窄读端口

flat / tree / navigation

菜单写用例

本地 tag 失效 + 跨实例通知

当前代码提供三个项目:Contracts 仅含四种输出 DTO;Application 持有九个用例与树策略;Infrastructure 持有全局目录行、IEntitySet store、seed step 和缓存同步消费者。

2. 九个 HTTP 用例

方法与路径用例资源权限
POST /api/menusCreateMenucreate
PUT /api/menus/{id}UpdateMenuupdate
DELETE /api/menus/{id}DeleteMenudelete
PATCH /api/menus/{id}/sortSortMenuupdate
PATCH /api/menus/{id}/toggleToggleMenuupdate
GET /api/menus/{id}GetMenuDetailview
GET /api/menus/flatGetMenuFlatview
GET /api/menus/treeGetMenuTreeview
GET /api/menus/navigationGetNavigation仅认证,不实现 IAuthorizedRequest

GenerateEndpointAttribute.RequireAuthorization 默认为 true,因此 navigation 仍要求登录。它跳过的是 menus.menu.view 资源决策,不是认证。

3. 权限与治理

模块标记是 AppModule("Menu", Code="menus"),声明允许依赖 Authorization 和 Identity。当前权限目录只有四项:

menus.menu.view
menus.menu.create
menus.menu.update
menus.menu.delete

Menu 没有 Feature catalog。旧文档所说的“Feature-aware visibility”并无源码依据;当前可见性只由 admin 角色短路或 Authorization 返回的模块码决定。

4. 请求到导航的真实链路

MenuStoreCacheMenuTreeBuilderAuthorization readerVisibilityResolverGetNavigation.HandlerGenerated endpointClientMenuStoreCacheMenuTreeBuilderAuthorization readerVisibilityResolverGetNavigation.HandlerGenerated endpointClientGET /api/menus/navigation + bearer tokenauthenticated QueryGetNavigationAsync(currentUser)ResolveVisibleModuleCodesAsync()granted module codes for rolesget-or-create user-scoped entryload enabled global modules on missnested NavigationItem[]

管理员角色名按不区分大小写比较;只要角色集合包含 admin,解析器就返回 null,表示目录全量可见。其他用户无角色时得到空导航。

5. 正确消费示例

读取前端导航
// ① 该查询由生成端点要求认证,但不要求菜单管理权限。
var result = await mediator.Send(
new GetNavigation.Query(),
cancellationToken);
// ② 返回值已经按当前用户角色过滤并补齐可见节点的祖先。
if (result.IsFailure)
return result.Error;
// ③ 客户端只把它用于导航;目标 API 仍执行自己的权限策略。
return result.Value.Select(RenderNavigationNode).ToList();

导航 DTO 只有 Id、Name、Icon、LinkUrl 和 Children;Code、Scope、Enabled 与授权信息不会出现在导航响应中。

6. 全局目录而非租户目录

MenuModuleCatalogRecord 继承 EntityBase,表元数据没有 IsTenant=true。目录结构对所有租户共享;租户差异来自 Authorization 中的租户化角色—模块关系。

这意味着修改名称、链接、父级、排序或启停会影响所有租户。若产品需要租户自定义菜单,不能直接把当前全局行当租户行使用,需要显式覆盖模型和合并策略。

7. 种子基线

MenuModuleSeedStep 的顺序为 220,以 Code 为自然键。当前资产有九行根节点:Operations、PlatformBilling、Files、Notifications、Webhooks、Catalog、Tickets、Chat 和 Sandbox。

seed 会更新父级、展示、旧 MVC 字段、排序、说明、IsMenu、Enabled、Scope 与软删状态,但不会更新作为匹配键的 Code。

8. 当前保证

  • Code 有数据库唯一索引。
  • ParentId 有普通索引。
  • store 通过 IEntitySet<MenuModuleCatalogRecord> 保持 ORM 中立。
  • Authorization 关系只经 IAuthorizationAssignmentReader 窄端口读取。
  • 查询只加载 Enabled 且未软删的目录行。
  • flat、tree、navigation 都按用户与可见集合缓存。
  • 写用例在成功持久化后清理本地菜单 tag,并发送同步通知。
  • API Shell 未配置数据库时为 IMenuStore 提供 fail-closed unavailable adapter。

9. 当前不保证

  • 创建/更新不验证父节点存在、自引用或祖先环。
  • 更新不验证 Name、Code 非空,也不预查 Code 冲突。
  • 删除不是单事务级联,也不校验授权关系引用。
  • 排序只是写入整数,不重排同级节点。
  • 启停父节点不级联修改子节点状态。
  • 树递归没有环检测或最大深度。
  • 没有 Menu Feature、审计时间线、版本历史或租户覆盖。
  • 数据库写入与缓存广播之间没有 outbox 原子性。

10. 何时使用 Menu

适合放入 Menu 的是稳定的产品导航目录、模块入口与旧控制器动作兼容信息。仅用于某个页面内部的 tab、按钮或临时引导,不应被提升为全局 SysModule 行。

新增节点前必须先确定稳定 Code、父级、目标 URL、作用域、是否是菜单、默认启用状态以及哪些角色获得模块可见性。

11. 文档导航

12. 源码核查

Terminal window
# 核对九个用例和生成端点声明。
find src/Platform/Menu -path '*Commands*' -o -path '*Queries*' | sort
# 核对全局目录元数据、种子与授权窄端口。
rg -n "BitzTable|WhereColumns|IAuthorizationAssignmentReader" src/Platform/Menu -g '*.cs'
# 不应存在 Menu Feature catalog。
rg -n "FeatureCatalog|FeatureDefinition" src/Platform/Menu -g '*.cs'

返回平台模块目录 · Authorization 模块

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%