RoleAccessPermissionMenuMapping.cs 4.23 KB
using FoodLabeling.Application.Contracts.Constants;

namespace FoodLabeling.Application.Helpers;

/// <summary>
/// 角色表单勾选项(manage_labels 等)与 Menu.Router / PermissionCode 的映射。
/// </summary>
public static class RoleAccessPermissionMenuMapping
{
    /// <summary>
    /// UI 权限码 → 可绑定的菜单 Router(与库中 Menu.Router 一致,含父级目录)。
    /// </summary>
    private static readonly IReadOnlyDictionary<string, string[]> RoleCodeToRouters =
        new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
        {
            [RoleAccessPermissionCodes.ManageLabels] =
            [
                "/labeling",
                "/labels",
                "/label-categories",
                "/label-types",
                "/label-templates"
            ],
            [RoleAccessPermissionCodes.ManagePeople] = ["/account-management"],
            [RoleAccessPermissionCodes.EditSettings] = ["/menu-management", "/multiple-options"],
            [RoleAccessPermissionCodes.ViewReports] = ["/reports"],
            // 当前 Menu 种子暂无独立 Products / Batches 页面,保留空映射(不阻断其它权限绑定)
            [RoleAccessPermissionCodes.ManageProducts] = [],
            [RoleAccessPermissionCodes.ApproveBatches] = []
        };

    /// <summary>
    /// 将入参权限码展开为可与菜单匹配的 token(含 menu.xxx 与 /router)。
    /// </summary>
    public static HashSet<string> ExpandMatchTokens(IEnumerable<string> inputCodes)
    {
        var tokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var raw in inputCodes)
        {
            if (string.IsNullOrWhiteSpace(raw))
            {
                continue;
            }

            var code = raw.Trim();
            tokens.Add(code);

            if (!RoleAccessPermissionCodes.IsKnown(code))
            {
                continue;
            }

            if (!RoleCodeToRouters.TryGetValue(code, out var routers))
            {
                continue;
            }

            foreach (var router in routers)
            {
                AddRouterTokens(tokens, router);
            }
        }

        return tokens;
    }

    /// <summary>
    /// 判断菜单是否命中任一 token(PermissionCode 或 Router)。
    /// </summary>
    public static bool MenuMatchesAnyToken(string? permissionCode, string? router, IEnumerable<string> tokens)
    {
        var effective = RbacAccessPermissionHelper.GetEffectivePermissionCode(permissionCode, router);
        var routerNorm = NormalizeRouterPath(router);

        foreach (var token in tokens)
        {
            if (string.IsNullOrWhiteSpace(token))
            {
                continue;
            }

            var t = token.Trim();
            if (effective is not null &&
                string.Equals(effective, t, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            if (routerNorm is null)
            {
                continue;
            }

            if (t.StartsWith("menu.", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var tokenPath = t.StartsWith('/') ? NormalizeRouterPath(t) : NormalizeRouterPath("/" + t);
            if (tokenPath is not null &&
                string.Equals(routerNorm, tokenPath, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
        }

        return false;
    }

    private static void AddRouterTokens(HashSet<string> tokens, string router)
    {
        var path = NormalizeRouterPath(router);
        if (string.IsNullOrEmpty(path))
        {
            return;
        }

        tokens.Add(path);
        var derived = RbacAccessPermissionHelper.DerivePermissionCodeFromRouter(path);
        if (!string.IsNullOrWhiteSpace(derived))
        {
            tokens.Add(derived);
        }
    }

    private static string? NormalizeRouterPath(string? router)
    {
        var r = router?.Trim();
        if (string.IsNullOrEmpty(r))
        {
            return null;
        }

        r = r.TrimEnd('/');
        if (!r.StartsWith('/'))
        {
            r = "/" + r;
        }

        return r.ToLowerInvariant();
    }
}