Menu 已具备清晰的 owner、生成端点、权限目录、双 ORM store 和跨实例失效骨架;但树完整性、变更原子性、审计和运维恢复仍未达到商业 GA。
1. 安全模型
管理端点必须同时认证并通过 create/update/delete/view 权限;navigation 只要求认证,然后按角色模块关系过滤。
2. 全局影响面
SysModule 不是租户表。任何管理写入都会改变全平台目录并清理全局菜单缓存 tag。角色授权关系虽然按租户解析,但结构、显示名、链接和启停是共享的。
因此 Menu 管理权限应被视为平台级高风险权限,不能等同普通租户后台配置权。
3. 显示过滤的安全边界
// ① 导航接口只决定是否把入口展示给当前用户。var navigation = await menus.GetNavigationAsync(currentUser, cancellationToken);
// ② 目标命令仍声明自己的资源与动作。public sealed record ApproveInvoice(string InvoiceId) : ICommand<Result>, IAuthorizedRequest{ public ResourceDescriptor Resource { get; } = new("billing", "invoice"); public AuthorizationAction Action { get; } = AuthorizationAction.Approve;}
// ③ 即使客户端手工调用 URL,授权管线也会再次决策。不要把 LinkUrl 当成可信策略输入,也不要根据节点是否存在决定后端授权。
4. admin 短路风险
角色名 admin 直接获得全部启用目录可见性。这是应用层约定,不是显式 permission grant。角色命名、外部 IdP 映射或租户自定义角色可能意外触发它。
GA 方案应选择:使用平台级不可伪造声明、显式超级管理员 capability,或仍经 Authorization policy 决策;同时固定大小写、租户与审计语义。
5. 输入与链接安全
当前没有校验 LinkUrl 的 scheme、host、相对路径、开放重定向或 javascript: 等危险值,也没有校验 Icon。若前端直接渲染这些字段,必须由 UI 做严格编码和允许列表;更稳妥的是服务端在写入时强制相对内部路由。
Name/Description 也应按文本渲染,不能当 HTML 注入。
6. 已有自动化证据
| 测试 | 当前证明 |
|---|---|
MenuQueryHandlerTests | detail 命中与 not-found |
MenuInfrastructureArchitectureTests | ORM 中立、查询走 store、fail-closed adapter |
AuthorizationMenuPersistenceOwnershipArchitectureTests | owner 行、窄端口、生成 model/seed manifest、迁移所有权 |
IdentitySeedDataTests | 菜单/权限种子引用与资产规则 |
FeatureDefinitionSeedStepTests | Feature 资产关联可解析 |
PortRepositoryParityTests | SqlSugar/EF Core store 行为一致 |
ApiShellHostBuildSmokeTests | 无数据库时 unavailable store |
这些证据不覆盖树算法和写用例业务规则。
7. 必补测试金字塔
优先补可重复、快速的算法测试,再用真实数据库和两实例环境固定基础设施语义。
8. 运行诊断
遇到“用户看不到菜单”时按顺序检查:
- 请求是否通过认证;
ICurrentUser.User.Roles是否为预期角色名;- Authorization reader 是否返回匹配的模块 Code;
- SysModule Code 是否完全对应;
- 目标行与所有祖先是否 Enabled/未软删;
- 所有祖先是否在根可达路径;
- navigation 路径上的节点是否都 IsMenu=true;
- 当前实例是否持有旧缓存。
9. 缓存诊断示例
# 查同步消费者是否收到事件及资源 ID。rg -n "Menu cache invalidated|Failed to invalidate menu cache" ./logs
# 源码核对 tag、TTL 和事件资源。rg -n "MenuCacheTags|CachePolicy.Medium|LocalResourceTypes.Cache" src/Platform/Menu -g '*.cs'
# 数据库侧需同时检查 Code、ParentId、Enabled、IsMenu、IsDeleted。当前消费者错误会被抑制;仅看到消息已消费不等于缓存失效成功。
10. 数据完整性巡检
生产巡检至少报告:空/重复 Code、空 Name、孤儿 ParentId、自引用、环、过深树、不可达启用节点、非菜单父节点下的菜单子节点、重复同级 OrderSort、危险 LinkUrl、授权关系引用缺失 Code。
巡检应只报告并提供修复计划,不应未经审批自动改写全局目录。
11. 发布前清单
- 备份 SysModule 与角色模块权限关系;
- 对比 seed 资产和数据库差异;
- 验证九个端点的 401/403/成功矩阵;
- 在每个支持 ORM 上运行 parity;
- 用两个 API 实例验证广播失效;
- 模拟 notifier 不可用与缓存异常;
- 验证深树、环、孤儿的失败方式;
- 验证链接只进入允许的内部路由;
- 准备 Code 迁移与回滚脚本;
- 发布后抽样普通用户、无角色用户和超级管理员。
12. 商业 GA 缺口
按优先级应补:
- 树写入 validator 与读侧环/深度保护;
- 事务化重父级、级联删除与确定性排序;
- Code 不可变或显式迁移工作流;
- URL/Name/Icon/Scope 完整校验;
- 数据库写与缓存事件的 outbox/重试;
- 事件 ResourceId 过滤和失败投递;
- 乐观并发与影响行数检查;
- 平台级管理审计、审批和回滚;
- 租户覆盖/本地化产品决策;
- consumer contract、双实例与故障注入门禁。
具体任务已写入 Architecture-Hub 的 Menu GA backlog,本文只陈述当前实现和验收方向。
13. 当前上线判断
在可信平台管理员、受控 seed、浅且无环目录、允许最多 15 分钟缓存兜底的内部场景,可有限使用。若对外提供租户自助配置、强审计、零陈旧或复杂树编辑,当前实现不应宣称 GA。
14. 全局扫描
# 当前测试证据。rg -n "Menu" tests -g '*.cs'
# 环、事务、outbox、审计实现缺口;预期 Menu 源码无关键命中。rg -n "visited|MaxDepth|UnitOfWork|Outbox|Audit" src/Platform/Menu -g '*.cs'
# 所有生成端点和授权请求。rg -n "GenerateEndpoint|IAuthorizedRequest" src/Platform/Menu -g '*.cs'