-- ============================================================================ -- 1.0.431:恢复 UAT 租户 A 的 UAT_FUNCTION_USER 角色的 sysOrg:list 按钮权限 -- -- 症状: -- UATAdminA(Id=838257187360837,AccountType=777 普通账号,非超管)打开任意 S0 -- 页面时弹两个「403 禁止访问,没有权限」。 -- -- 链路(CODE VERIFIED): -- Web/src/views/aidop/s0/sales/ProductDesignCycleList.vue → loadOptions() -- → loadOrgList('201') / loadOrgList('501')(s0SalesApi.ts:765) -- → 两次 GET /api/sysOrg/list -- JwtHandler.CheckAuthorizeAsync(Admin.NET.Web.Core/Handlers/JwtHandler.cs:85) -- 把路径映射为按钮权限名 sysOrg:list;非超管必须命中自有按钮权限, -- 未命中时仅当该权限「未登记为按钮权限」才兜底放行。 -- sysOrg:list 已登记(SysMenu Id=1300200030201,Type=3 Btn), -- 而该角色未授权 → 授权失败 → AdminResultProvider.cs:84 返回 -- 「403 禁止访问,没有权限」。两次请求 → 两个弹窗。 -- -- 为什么 1.0.386 补过又丢了(DB VERIFIED): -- 1.0.386 于 2026-08-24 13:21 Success(affected_rows=4),同时给 A/B 两个租户的 -- 同名角色补了权限,Id 依次为 …807686(租户 A)与 …807687(租户 B)。 -- 当前 …807687 仍在,…807686 已不存在;且租户 A 该角色现有 290 条 SysRoleMenu -- 的 Id 为一整段连续块(843881101070405~843881101086842), -- 是 SysRoleMenuService.GrantRoleMenu(Role/SysRoleMenuService.cs:73) -- 「先 DELETE 该角色全部 SysRoleMenu,再按 UI 勾选项全量 INSERT」的签名。 -- → 管理员在「角色管理 → 授权菜单」重存该角色、未勾选机构管理, -- 把 migration 单独补的这条冲掉了。 -- -- 本脚本口径:精确到单租户单角色单菜单,不按 RoleCode 跨租户批量补。 -- TenantId = 838257186181189(租户 A) -- RoleId = 838258976469061(租户 A 的 UAT_FUNCTION_USER) -- MenuId = 1300200030201(sysOrg:list,Type=3 Btn) -- -- 明确不做: -- - 不按 RoleCode 给所有租户补权限(1.0.386 的口径,已证明会掩盖单租户丢失) -- - 不改租户 B 的 RoleId=838259499909189(仅作对照) -- - 不改其他任何角色 -- - 不改 SysMenu、不改 SysTenantMenu -- - 不放开 sysOrg:add / sysOrg:update / sysOrg:delete -- - 无 DDL、无 DELETE、无业务数据写入 -- -- 已知连带影响(不可避免,且与对照租户 B 现状一致): -- SysMenuService.GetLoginMenuTree 在过滤 Type != Btn 之后,会对角色菜单集合做 -- ExpandMenuIdsWithAncestorsAsync(SysMenuService.cs:65/441)祖先补全。 -- 补入 Btn 1300200030201 后,其祖先「机构管理」(1300200030101, Type=2) 会被补进 -- 菜单树;租户 A 的 SysTenantMenu 允许该菜单,故 UATAdminA 侧边栏会新增 -- 「系统管理 / 机构管理」入口(只读可用,add/update/delete 仍会 403)。 -- 租户 B 的同名角色已是同一形态,本脚本使 A 与 B 对齐。 -- -- ID 策略:沿用 UpdateScripts 既有写法(1.0.386 / 1.0.406) -- SET @base := (SELECT IFNULL(MAX(Id),0) FROM SysRoleMenu); INSERT ... @base + 1 -- -- 幂等:INSERT ... SELECT ... FROM DUAL WHERE <全部前置条件> AND NOT EXISTS(...)。 -- 重复执行 affected rows = 0,不会产生第二条记录。 -- -- 环境容错:以上 Id 在不存在该 UAT 夹具的库上均查不到 → 整脚本为空操作, -- verify 同步短路为真(见 1.0.431.verify.sql)。 -- -- ⚠️ 缓存:GetOwnBtnPermList 结果按 sys_user_button: 缓存 7 天 -- (SysMenuService.cs:355,CacheConst.KeyUserButton),当前 CacheType=Memory -- (Admin.NET.Application/Configuration/Cache.json:7)为进程内缓存, -- SQL 写库不会使其失效。AutoVersionUpdate 在应用启动时执行本脚本, -- 该进程缓存本就为空,故随发布重启天然生效;若在已运行实例上手工执行, -- 需另行清缓存(见报告「缓存处理」)。 -- ============================================================================ SET @orglist_tenant := 838257186181189; SET @orglist_role := (SELECT r.Id FROM SysRole r WHERE r.Id = 838258976469061 AND r.TenantId = 838257186181189 AND r.Code = 'UAT_FUNCTION_USER' AND r.Status = 1 LIMIT 1); SET @orglist_menu := (SELECT m.Id FROM SysMenu m WHERE m.Id = 1300200030201 AND m.Permission = 'sysOrg:list' AND m.Type = 3 AND m.Status = 1 LIMIT 1); SET @orglist_rm_base := (SELECT IFNULL(MAX(Id), 0) FROM SysRoleMenu); INSERT INTO SysRoleMenu (Id, RoleId, MenuId) SELECT @orglist_rm_base + 1, @orglist_role, @orglist_menu FROM DUAL WHERE @orglist_role IS NOT NULL AND @orglist_menu IS NOT NULL AND EXISTS ( SELECT 1 FROM SysTenantMenu t WHERE t.TenantId = @orglist_tenant AND t.MenuId = @orglist_menu ) AND NOT EXISTS ( SELECT 1 FROM SysRoleMenu x WHERE x.RoleId = @orglist_role AND x.MenuId = @orglist_menu );