feat: add admin menu management crud

This commit is contained in:
2025-12-05 21:16:07 +08:00
parent 02e33de5c8
commit a1499fc1a1
22 changed files with 1747 additions and 2 deletions

View File

@@ -19,6 +19,7 @@ public sealed class AdminAuthService(
IRoleRepository roleRepository,
IPermissionRepository permissionRepository,
IRolePermissionRepository rolePermissionRepository,
IMenuRepository menuRepository,
IPasswordHasher<IdentityUser> passwordHasher,
IJwtTokenService jwtTokenService,
IRefreshTokenStore refreshTokenStore,
@@ -29,6 +30,7 @@ public sealed class AdminAuthService(
private readonly IRoleRepository _roleRepository = roleRepository;
private readonly IPermissionRepository _permissionRepository = permissionRepository;
private readonly IRolePermissionRepository _rolePermissionRepository = rolePermissionRepository;
private readonly IMenuRepository _menuRepository = menuRepository;
/// <summary>
/// 管理后台登录:验证账号密码并生成令牌。
@@ -108,8 +110,12 @@ public sealed class AdminAuthService(
{
// 1. 读取档案以获取权限
var profile = await GetProfileAsync(userId, cancellationToken);
// 2. 生成菜单树
var menu = AdminMenuProvider.BuildMenuTree(profile.Permissions);
// 2. 读取菜单定义
var tenantId = _tenantProvider.GetCurrentTenantId();
var definitions = await _menuRepository.GetByTenantAsync(tenantId, cancellationToken);
// 3. 生成菜单树
var menu = BuildMenuTree(definitions, profile.Permissions);
return menu;
}
@@ -208,6 +214,103 @@ public sealed class AdminAuthService(
};
}
private static IReadOnlyList<MenuNodeDto> BuildMenuTree(
IReadOnlyList<Domain.Identity.Entities.MenuDefinition> definitions,
IReadOnlyList<string> permissions)
{
// 1. 权限集合
var permissionSet = new HashSet<string>(permissions ?? [], StringComparer.OrdinalIgnoreCase);
// 2. 父子分组
var parentLookup = definitions
.GroupBy(x => x.ParentId)
.ToDictionary(g => g.Key, g => g.OrderBy(d => d.SortOrder).ToList());
// 3. 递归构造并过滤
IReadOnlyList<MenuNodeDto> BuildChildren(long parentId)
{
if (!parentLookup.TryGetValue(parentId, out var children))
{
return [];
}
// 1. 收集可见节点
var result = new List<MenuNodeDto>();
foreach (var def in children)
{
// 1.1 构造节点
var node = MapMenuNode(def);
// 1.2 递归子节点
var sub = BuildChildren(def.Id);
// 1.3 可见性
var required = node.RequiredPermissions ?? [];
var visible = required.Length == 0 || required.Any(permissionSet.Contains);
// 1.4 收集
if (visible || sub.Count > 0)
{
result.Add(node with { Children = sub });
}
}
// 2. 返回本层节点
return result;
}
// 4. 返回根节点
return BuildChildren(0);
}
private static MenuNodeDto MapMenuNode(Domain.Identity.Entities.MenuDefinition definition)
{
// 1. 解析权限
var requiredPermissions = SplitCodes(definition.RequiredPermissions);
var metaPermissions = SplitCodes(definition.MetaPermissions);
var metaRoles = SplitCodes(definition.MetaRoles);
// 2. 解析按钮权限
var authList = string.IsNullOrWhiteSpace(definition.AuthListJson)
? []
: System.Text.Json.JsonSerializer.Deserialize<List<MenuAuthItemDto>>(definition.AuthListJson) ?? [];
// 3. 构造节点
return new MenuNodeDto
{
Name = definition.Name,
Path = definition.Path,
Component = definition.Component,
Meta = new MenuMetaDto
{
Title = definition.Title,
Icon = definition.Icon,
KeepAlive = definition.KeepAlive,
IsIframe = definition.IsIframe,
Link = definition.Link,
Roles = metaRoles,
Permissions = metaPermissions,
AuthList = authList
},
Children = [],
RequiredPermissions = requiredPermissions
};
}
private static string[] SplitCodes(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return Array.Empty<string>();
}
// 1. 拆分去重
return value
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
private async Task<string[]> ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken)
{
var relations = await _userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);