feat: add admin menu management crud
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user