Просмотр исходного кода

fix(aidop): preserve manual role menu revocations

YY968XX 18 часов назад
Родитель
Сommit
f66d527d5f

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.425</AssemblyVersion>
-    <FileVersion>1.0.425</FileVersion>
-    <Version>1.0.425</Version>
+    <AssemblyVersion>1.0.426</AssemblyVersion>
+    <FileVersion>1.0.426</FileVersion>
+    <Version>1.0.426</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 55 - 0
server/Plugins/Admin.NET.Plugin.AiDOP.Tests/Infrastructure/AidopMenuLinkSyncContractTests.cs

@@ -0,0 +1,55 @@
+using Xunit;
+
+namespace Admin.NET.Plugin.AiDOP.Tests.Infrastructure;
+
+public class AidopMenuLinkSyncContractTests
+{
+    [Fact]
+    public void StartupSync_MaintainsTenantMenus_ButNeverReconcilesRoleMenus()
+    {
+        var source = File.ReadAllText(FindSourceFile());
+        var ensureLinked = ExtractMethod(source, "public static void EnsureLinked(IServiceProvider services)");
+
+        Assert.Contains("SysTenantMenu", ensureLinked);
+        Assert.DoesNotContain("SysRoleMenu", ensureLinked);
+    }
+
+    private static string ExtractMethod(string source, string signature)
+    {
+        var signatureIndex = source.IndexOf(signature, StringComparison.Ordinal);
+        Assert.True(signatureIndex >= 0, $"Method not found: {signature}");
+
+        var bodyStart = source.IndexOf('{', signatureIndex);
+        Assert.True(bodyStart >= 0, $"Method body not found: {signature}");
+
+        var depth = 0;
+        for (var i = bodyStart; i < source.Length; i++)
+        {
+            if (source[i] == '{') depth++;
+            if (source[i] != '}') continue;
+            depth--;
+            if (depth == 0) return source[signatureIndex..(i + 1)];
+        }
+
+        throw new InvalidOperationException($"Method body is incomplete: {signature}");
+    }
+
+    private static string FindSourceFile()
+    {
+        var directory = new DirectoryInfo(AppContext.BaseDirectory);
+        while (directory != null)
+        {
+            var candidate = Path.Combine(
+                directory.FullName,
+                "server",
+                "Plugins",
+                "Admin.NET.Plugin.AiDOP",
+                "Infrastructure",
+                "AidopMenuLinkSync.cs");
+            if (File.Exists(candidate)) return candidate;
+            directory = directory.Parent;
+        }
+
+        throw new FileNotFoundException("AidopMenuLinkSync.cs not found from test output path.");
+    }
+}

+ 3 - 47
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs

@@ -7,13 +7,12 @@ using System.Diagnostics;
 namespace Admin.NET.Plugin.AiDOP.Infrastructure;
 
 /// <summary>
-/// 将 Ai-DOP 种子菜单 Id 补写到 <c>sys_tenant_menu</c> / <c>sys_role_menu</c>。
-/// 解决:<c>SysTenantMenuSeedData</c> 带 IgnoreUpdateSeed,库已存在时新增菜单不会自动进租户/角色;多租户下仅补默认租户会导致其它租户侧栏与菜单管理不可见。
+/// 将 Ai-DOP 种子菜单 Id 补写到 <c>sys_tenant_menu</c>。
+/// 解决:<c>SysTenantMenuSeedData</c> 带 IgnoreUpdateSeed,库已存在时新增菜单不会自动进入租户可用范围;多租户下仅补默认租户会导致其它租户菜单管理不可见。
+/// 角色菜单以角色管理保存结果为准;普通启动不得根据缺失项补写 <c>sys_role_menu</c>。
 /// </summary>
 public static class AidopMenuLinkSync
 {
-    /// <summary>与框架种子中首条「系统管理员」角色 Id 一致。</summary>
-    private const long SysAdminRoleId = 1300000000101L;
     private const long LegacyMaterialSubstitutionMenuId = 1329003000005L;
     private const long DeprecatedMaterialSubstitutionMenuId = 1329002000004L;
     private const long DeprecatedS8DashboardChildMenuId = 1329008000001L;
@@ -94,49 +93,6 @@ public static class AidopMenuLinkSync
 
         // 抑制清单登记之前就已存在的授权行,在这里清掉:让本方法从「覆盖运维意图」变成「强制执行运维意图」,重启即自愈。
         EnforceTenantMenuSuppressions(db, suppressedPairs);
-
-        // 已为任一 Ai-DOP 种子菜单授权的角色,补全新增子菜单;并始终包含默认租户系统管理员角色。
-        var roleIdsWithAnyAidop = db.Queryable<SysRoleMenu>()
-            .Where(rm => seedMenuIds.Contains(rm.MenuId))
-            .Select(rm => rm.RoleId)
-            .ToList()
-            .Distinct()
-            .ToHashSet();
-        roleIdsWithAnyAidop.Add(SysAdminRoleId);
-
-        var roleMenuPairs = db.Queryable<SysRoleMenu>()
-            .Where(rm => roleIdsWithAnyAidop.Contains(rm.RoleId) && seedMenuIds.Contains(rm.MenuId))
-            .Select(rm => new { rm.RoleId, rm.MenuId })
-            .ToList()
-            .Select(x => (x.RoleId, x.MenuId))
-            .ToHashSet();
-
-        // SysRoleMenu 主键须全局唯一。使用 menu.Id + (roleId % 1300000000000) 时,不同 (MenuId, RoleId) 会算出相同 Id(如 1322000000208),
-        // 批量插入失败,新菜单无法进入 sys_role_menu,侧栏不刷新。与 SysTenantMenu 一致,用固定哈希避免碰撞。
-        var roleRows = new List<SysRoleMenu>();
-        var pendingRoleMenuIds = new HashSet<long>();
-        foreach (var roleId in roleIdsWithAnyAidop)
-        {
-            foreach (var menu in seedMenus)
-            {
-                if (!existingMenuIds.Contains(menu.Id))
-                    continue;
-                if (roleMenuPairs.Contains((roleId, menu.Id)))
-                    continue;
-                var rmId = CommonUtil.GetFixedHashCode($"{roleId}:{menu.Id}", 1300000000000);
-                while (!pendingRoleMenuIds.Add(rmId))
-                    rmId++;
-                roleRows.Add(new SysRoleMenu
-                {
-                    Id = rmId,
-                    RoleId = roleId,
-                    MenuId = menu.Id
-                });
-            }
-        }
-
-        if (roleRows.Count > 0)
-            db.Insertable(roleRows).ExecuteCommand();
     }
 
     /// <summary>