using Fake.Application; using Oms.Application.DataPerm; using System.Security.Claims; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.DataPerm; using Oms.Application.Shared; using System.Text.Json; namespace Oms.Application.Services.DataPerm; public partial class DataPermService { /// /// 分页查询数据权限模板 /// [HttpGet("/rbac/data-perm/template/list")] public Task> GetTemplateListAsync( [FromQuery] string? keyword, [FromQuery] int page = 1, [FromQuery] int pageSize = 30, CancellationToken cancellationToken = default) => Store.GetTemplateListAsync( keyword, OmsDocHelper.NormalizePage(page), OmsDocHelper.NormalizePageSize(pageSize), cancellationToken); /// /// 查询数据权限模板详情 /// [HttpGet("/rbac/data-perm/template/get")] public Task GetTemplateDetailAsync( [FromQuery] string id, CancellationToken cancellationToken = default) => Store.GetTemplateDetailAsync(ParseGuid(id, "id"), cancellationToken); /// /// 创建数据权限模板 /// [HttpPost("/rbac/data-perm/template/create")] public Task CreateTemplateAsync( [FromBody] DataPermTemplateSaveInput input, CancellationToken cancellationToken = default) { ValidateTemplateInput(input); return Store.CreateTemplateAsync(input, GetCurrentMapId(), cancellationToken); } /// /// 更新数据权限模板 /// [HttpPut("/rbac/data-perm/template/update")] public Task UpdateTemplateAsync( [FromQuery] string id, [FromBody] DataPermTemplateSaveInput input, CancellationToken cancellationToken = default) { ValidateTemplateInput(input); return Store.UpdateTemplateAsync(ParseGuid(id, "id"), input, GetCurrentMapId(), cancellationToken); } /// /// 删除数据权限模板 /// [HttpDelete("/rbac/data-perm/template/delete")] public Task DeleteTemplateAsync([FromQuery] string id, CancellationToken cancellationToken = default) => Store.DeleteTemplateAsync(ParseGuid(id, "id"), cancellationToken); /// /// 应用数据权限模板 /// [HttpPost("/rbac/data-perm/template/apply")] public async Task ApplyTemplateAsync( [FromBody] TemplateApplyInput input, CancellationToken cancellationToken = default) { var template = await Store.GetTemplateDetailAsync(ParseGuid(input.TemplateId, "templateId"), cancellationToken); var bundle = TemplateBundleParser.Parse(template.Items); var result = new TemplateApplyResultDto(); foreach (var targetIdRaw in input.TargetIds.Distinct(StringComparer.OrdinalIgnoreCase)) { try { if (string.Equals(input.TargetType, "role", StringComparison.OrdinalIgnoreCase)) { var roleId = ParseGuid(targetIdRaw, "targetId"); var current = await Store.GetRoleDataPermAsync(roleId, cancellationToken); var merged = MergeRoleBundle(current, bundle, input); await Store.SaveRoleDataPermAsync(roleId, merged, GetCurrentUserGuid(), GetCurrentMapId(), Repository, cancellationToken); } else { var userId = ParseGuid(targetIdRaw, "targetId"); if (await Repository.IsPureCustomerUserAsync(userId, cancellationToken)) { throw new BusinessException("客户账号不支持数据权限配置"); } var current = await Store.GetUserDataPermAsync(userId, cancellationToken); var merged = MergeUserBundle(current, bundle, input); DataPermSaveValidator.Validate(merged, Repository); await DataPermSaveValidator.ValidateOrganizationsExistAsync(merged, Repository, cancellationToken); await Store.SaveUserDataPermAsync(userId, merged, GetCurrentUserGuid(), GetCurrentMapId(), cancellationToken); } result.SuccessCount++; } catch (Exception ex) { result.Failures.Add(new TemplateApplyFailureDto { TargetId = targetIdRaw, Reason = ex is BusinessException business ? business.Message : ex.Message }); } } return result; } private static void ValidateTemplateInput(DataPermTemplateSaveInput input) { if (string.IsNullOrWhiteSpace(input.Name) || string.IsNullOrWhiteSpace(input.Code)) { throw new BusinessException("模板名称与编码不能为空"); } } private static SaveUserDataPermInput MergeUserBundle( UserDataPermDto current, TemplatePermBundle bundle, TemplateApplyInput input) { var replace = string.Equals(input.Mode, "replace", StringComparison.OrdinalIgnoreCase); var effectiveTime = input.Temporary?.Enabled == true ? input.Temporary.EffectiveTime : null; var expireTime = input.Temporary?.Enabled == true ? input.Temporary.ExpireTime : null; var isTemporary = input.Temporary?.Enabled == true; var orgPerms = replace ? bundle.OrgPerms : UnionOrg(current.OrgPerms, bundle.OrgPerms); var dataScopes = replace ? bundle.DataScopes : MergeDataScopes(current.DataScopes, bundle.DataScopes); var bizPerms = replace ? bundle.BizPerms : UnionBiz(current.BizPerms, bundle.BizPerms); var secretPerms = replace ? bundle.SecretPerms : UnionSecret(current.SecretPerms, bundle.SecretPerms); var filterRules = replace ? bundle.FilterRules : current.FilterRules.Select(ToFilterSave).Concat(bundle.FilterRules).ToList(); ApplyTemporary(orgPerms, effectiveTime, expireTime, isTemporary); return new SaveUserDataPermInput { OrgPerms = orgPerms, DataScopes = dataScopes, BizPerms = bizPerms, SecretPerms = secretPerms, FilterRules = filterRules }; } private static SaveUserDataPermInput MergeRoleBundle( RoleDataPermDto current, TemplatePermBundle bundle, TemplateApplyInput input) { var replace = string.Equals(input.Mode, "replace", StringComparison.OrdinalIgnoreCase); return new SaveUserDataPermInput { OrgPerms = replace ? bundle.OrgPerms : UnionOrg(current.OrgPerms, bundle.OrgPerms), DataScopes = replace ? bundle.DataScopes : MergeDataScopes(current.DataScopes, bundle.DataScopes), BizPerms = replace ? bundle.BizPerms : UnionBiz(current.BizPerms, bundle.BizPerms), SecretPerms = replace ? bundle.SecretPerms : UnionSecret(current.SecretPerms, bundle.SecretPerms), FilterRules = replace ? bundle.FilterRules : current.FilterRules.Select(ToFilterSave).Concat(bundle.FilterRules).ToList() }; } private static List UnionOrg(IEnumerable existing, List incoming) { var map = existing.ToDictionary(x => x.OrganizationId, ToOrgSave, StringComparer.OrdinalIgnoreCase); foreach (var item in incoming) { map[item.OrganizationId] = item; } return map.Values.ToList(); } private static List UnionBiz(IEnumerable existing, List incoming) { var map = existing.ToDictionary(x => $"{x.BizType}:{x.BizId}", ToBizSave, StringComparer.OrdinalIgnoreCase); foreach (var item in incoming) { map[$"{item.BizType}:{item.BizId}"] = item; } return map.Values.ToList(); } private static List UnionSecret(IEnumerable existing, List incoming) { var set = existing.Select(x => (x.SecretLevel, x.DataResourceCode ?? string.Empty)).ToHashSet(); var result = existing.Select(ToSecretSave).ToList(); foreach (var item in incoming) { var key = (item.SecretLevel, item.DataResourceCode ?? string.Empty); if (set.Add(key)) { result.Add(item); } } return result; } private static List MergeDataScopes( IEnumerable existing, List incoming) { var map = existing.ToDictionary(x => x.DataResourceCode ?? string.Empty, ToScopeSave, StringComparer.OrdinalIgnoreCase); foreach (var item in incoming) { var key = item.DataResourceCode ?? string.Empty; if (!map.TryGetValue(key, out var current) || item.DataScope > current.DataScope) { map[key] = item; } } return map.Values.ToList(); } private static void ApplyTemporary( List orgPerms, DateTime? effectiveTime, DateTime? expireTime, bool isTemporary) { if (!isTemporary) { return; } foreach (var item in orgPerms) { item.EffectiveTime = effectiveTime ?? item.EffectiveTime; item.ExpireTime = expireTime ?? item.ExpireTime; item.IsTemporary = true; } } private static OrgPermSaveItemDto ToOrgSave(OrgPermItemDto item) => new() { OrganizationId = item.OrganizationId, OrgScope = item.OrgScope, EffectiveTime = item.EffectiveTime, ExpireTime = item.ExpireTime, IsTemporary = item.IsTemporary }; private static DataScopeSaveItemDto ToScopeSave(DataScopeItemDto item) => new() { DataScope = item.DataScope, DataResourceCode = item.DataResourceCode, ExpireTime = item.ExpireTime }; private static BizPermSaveItemDto ToBizSave(BizPermItemDto item) => new() { BizType = item.BizType, BizId = item.BizId, BizName = item.BizName, ExpireTime = item.ExpireTime }; private static SecretPermSaveItemDto ToSecretSave(SecretPermItemDto item) => new() { SecretLevel = item.SecretLevel, DataResourceCode = item.DataResourceCode, ExpireTime = item.ExpireTime }; private static FilterRuleSaveItemDto ToFilterSave(FilterRuleItemDto item) => new() { DataResourceCode = item.DataResourceCode, FilterContent = item.FilterContent, ExpireTime = item.ExpireTime }; private sealed class TemplatePermBundle { public List OrgPerms { get; init; } = []; public List DataScopes { get; init; } = []; public List BizPerms { get; init; } = []; public List SecretPerms { get; init; } = []; public List FilterRules { get; init; } = []; } private static class TemplateBundleParser { public static TemplatePermBundle Parse(IEnumerable items) { var bundle = new TemplatePermBundle(); foreach (var item in items) { switch (item.DimensionType.ToLowerInvariant()) { case "org": bundle.OrgPerms.Add(ParseOrg(item.ItemContent)); break; case "data_scope": bundle.DataScopes.Add(ParseDataScope(item.ItemContent)); break; case "biz": bundle.BizPerms.Add(ParseBiz(item.ItemContent)); break; case "secret": bundle.SecretPerms.AddRange(ParseSecret(item.ItemContent)); break; case "filter": bundle.FilterRules.Add(ParseFilter(item.ItemContent)); break; } } return bundle; } private static OrgPermSaveItemDto ParseOrg(Dictionary content) => new() { OrganizationId = GetString(content, "organizationId"), OrgScope = GetInt(content, "orgScope", DataPermConstants.OrgScopeSelfAndChildren) }; private static DataScopeSaveItemDto ParseDataScope(Dictionary content) => new() { DataScope = GetInt(content, "dataScope", DataPermConstants.DataScopePersonal), DataResourceCode = GetOptionalString(content, "dataResourceCode") }; private static BizPermSaveItemDto ParseBiz(Dictionary content) => new() { BizType = GetString(content, "bizType"), BizId = GetString(content, "bizId"), BizName = GetOptionalString(content, "bizName") }; private static IEnumerable ParseSecret(Dictionary content) { if (content.TryGetValue("secretLevels", out var raw) && raw is JsonElement element && element.ValueKind == JsonValueKind.Array) { return element.EnumerateArray().Select(x => new SecretPermSaveItemDto { SecretLevel = x.GetInt32(), DataResourceCode = GetOptionalString(content, "dataResourceCode") }); } return [ new SecretPermSaveItemDto { SecretLevel = GetInt(content, "secretLevel", 1), DataResourceCode = GetOptionalString(content, "dataResourceCode") } ]; } private static FilterRuleSaveItemDto ParseFilter(Dictionary content) { var resourceCode = GetString(content, "dataResourceCode"); FilterContentDto filterContent; if (content.TryGetValue("filterContent", out var raw) && raw is JsonElement element) { filterContent = element.Deserialize() ?? new FilterContentDto(); } else { filterContent = new FilterContentDto(); } FilterAstValidator.ParseAndValidate(resourceCode, filterContent); return new FilterRuleSaveItemDto { DataResourceCode = resourceCode, FilterContent = filterContent }; } private static string GetString(Dictionary content, string key) => content.TryGetValue(key, out var raw) ? raw?.ToString()?.Trim() ?? string.Empty : string.Empty; private static string? GetOptionalString(Dictionary content, string key) => content.TryGetValue(key, out var raw) ? raw?.ToString()?.Trim() : null; private static int GetInt(Dictionary content, string key, int defaultValue) => content.TryGetValue(key, out var raw) && int.TryParse(raw?.ToString(), out var value) ? value : defaultValue; } }