Files
TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/Identity/Handlers/MenuMapper.cs
MSuMshk 5a26f82628 refactor: 将 Permission 和 MenuDefinition 改为系统级实体
- Permission 和 MenuDefinition 改为继承 AuditableEntityBase(移除 TenantId)
- 添加 PortalType 枚举区分平台端/租户端
- Repository 使用 IgnoreQueryFilters() 查询系统级数据
- 更新所有相关 Handler 和 DTO,移除 TenantId 引用
- 与 AdminApi 保持一致的设计

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:49:27 +08:00

130 lines
4.3 KiB
C#

using System.Text.Json;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
namespace TakeoutSaaS.Application.Identity.Handlers;
/// <summary>
/// 菜单映射辅助。
/// </summary>
internal static class MenuMapper
{
/// <summary>
/// 将菜单实体映射为 DTO。
/// </summary>
/// <param name="entity">菜单实体。</param>
/// <returns>菜单定义 DTO。</returns>
public static MenuDefinitionDto ToDto(MenuDefinition entity)
{
// 1. 解析权限字段
var requiredPermissions = SplitCodes(entity.RequiredPermissions);
var metaPermissions = SplitCodes(entity.MetaPermissions);
var metaRoles = SplitCodes(entity.MetaRoles);
// 2. 解析按钮权限
var authList = string.IsNullOrWhiteSpace(entity.AuthListJson)
? []
: JsonSerializer.Deserialize<List<MenuAuthItemDto>>(entity.AuthListJson) ?? [];
// 3. 输出 DTO
return new MenuDefinitionDto
{
Id = entity.Id,
ParentId = entity.ParentId,
Name = entity.Name,
Path = entity.Path,
Component = entity.Component,
Title = entity.Title,
Icon = entity.Icon,
IsIframe = entity.IsIframe,
Link = entity.Link,
KeepAlive = entity.KeepAlive,
SortOrder = entity.SortOrder,
RequiredPermissions = requiredPermissions,
MetaPermissions = metaPermissions,
MetaRoles = metaRoles,
AuthList = authList
};
}
/// <summary>
/// 将 DTO 字段填充到实体。
/// </summary>
/// <param name="entity">菜单实体。</param>
/// <param name="dto">菜单 DTO。</param>
public static void FillEntity(MenuDefinition entity, MenuDefinitionDto dto)
{
// 1. 赋值基础字段
entity.ParentId = dto.ParentId;
entity.Name = dto.Name;
entity.Path = dto.Path;
entity.Component = dto.Component;
entity.Title = dto.Title;
entity.Icon = dto.Icon;
entity.IsIframe = dto.IsIframe;
entity.Link = dto.Link;
entity.KeepAlive = dto.KeepAlive;
entity.SortOrder = dto.SortOrder;
// 2. 权限与按钮
entity.RequiredPermissions = JoinCodes(dto.RequiredPermissions);
entity.MetaPermissions = JoinCodes(dto.MetaPermissions);
entity.MetaRoles = JoinCodes(dto.MetaRoles);
entity.AuthListJson = dto.AuthList.Count == 0
? null
: JsonSerializer.Serialize(dto.AuthList);
}
/// <summary>
/// 构建或更新菜单实体并返回 DTO。
/// </summary>
/// <param name="existing">已存在的菜单实体。</param>
/// <param name="portal">门户类型。</param>
/// <param name="payload">菜单 DTO 载荷。</param>
/// <returns>菜单定义 DTO。</returns>
public static MenuDefinitionDto FromCommand(MenuDefinition? existing, PortalType portal, MenuDefinitionDto payload)
{
// 1. 构造实体
var entity = existing ?? new MenuDefinition
{
Portal = portal,
CreatedAt = DateTime.UtcNow
};
// 2. 填充字段
FillEntity(entity, payload);
// 3. 返回 DTO 映射
return ToDto(entity);
}
/// <summary>
/// 将权限/角色集合合并为存储字符串。
/// </summary>
/// <param name="codes">编码集合。</param>
/// <returns>逗号分隔字符串。</returns>
public static string JoinCodes(IEnumerable<string> codes)
{
return string.Join(',', codes.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x.Trim()).Distinct(StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// 将逗号分隔编码拆分为集合。
/// </summary>
/// <param name="codes">编码字符串。</param>
/// <returns>编码数组。</returns>
public static string[] SplitCodes(string? codes)
{
if (string.IsNullOrWhiteSpace(codes))
{
return Array.Empty<string>();
}
return codes
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
}