admin/src/Oms.Application/Services/DataPerm/DataPermService.Template.cs
zzh 0a5bd75618 feat: 新增财务应收应付接口并补充 Swagger 接口名称
- 新增 Finance 模块应收应付分页查询及订单费用联查

- 为 DataPerm、Xhs 等缺失接口补充中文 summary 接口名称

- 优化微信公众号验签 Swagger 命名并补充路由集成测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 12:15:15 +08:00

410 lines
16 KiB
C#

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
{
/// <summary>
/// 分页查询数据权限模板
/// </summary>
[HttpGet("/rbac/data-perm/template/list")]
public Task<DataPermPagedResult<DataPermTemplateListItemDto>> 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);
/// <summary>
/// 查询数据权限模板详情
/// </summary>
[HttpGet("/rbac/data-perm/template/get")]
public Task<DataPermTemplateDetailDto> GetTemplateDetailAsync(
[FromQuery] string id,
CancellationToken cancellationToken = default) =>
Store.GetTemplateDetailAsync(ParseGuid(id, "id"), cancellationToken);
/// <summary>
/// 创建数据权限模板
/// </summary>
[HttpPost("/rbac/data-perm/template/create")]
public Task<DataPermTemplateDetailDto> CreateTemplateAsync(
[FromBody] DataPermTemplateSaveInput input,
CancellationToken cancellationToken = default)
{
ValidateTemplateInput(input);
return Store.CreateTemplateAsync(input, GetCurrentMapId(), cancellationToken);
}
/// <summary>
/// 更新数据权限模板
/// </summary>
[HttpPut("/rbac/data-perm/template/update")]
public Task<DataPermTemplateDetailDto> UpdateTemplateAsync(
[FromQuery] string id,
[FromBody] DataPermTemplateSaveInput input,
CancellationToken cancellationToken = default)
{
ValidateTemplateInput(input);
return Store.UpdateTemplateAsync(ParseGuid(id, "id"), input, GetCurrentMapId(), cancellationToken);
}
/// <summary>
/// 删除数据权限模板
/// </summary>
[HttpDelete("/rbac/data-perm/template/delete")]
public Task DeleteTemplateAsync([FromQuery] string id, CancellationToken cancellationToken = default) =>
Store.DeleteTemplateAsync(ParseGuid(id, "id"), cancellationToken);
/// <summary>
/// 应用数据权限模板
/// </summary>
[HttpPost("/rbac/data-perm/template/apply")]
public async Task<TemplateApplyResultDto> 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<OrgPermSaveItemDto> UnionOrg(IEnumerable<OrgPermItemDto> existing, List<OrgPermSaveItemDto> 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<BizPermSaveItemDto> UnionBiz(IEnumerable<BizPermItemDto> existing, List<BizPermSaveItemDto> 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<SecretPermSaveItemDto> UnionSecret(IEnumerable<SecretPermItemDto> existing, List<SecretPermSaveItemDto> 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<DataScopeSaveItemDto> MergeDataScopes(
IEnumerable<DataScopeItemDto> existing,
List<DataScopeSaveItemDto> 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<OrgPermSaveItemDto> 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<OrgPermSaveItemDto> OrgPerms { get; init; } = [];
public List<DataScopeSaveItemDto> DataScopes { get; init; } = [];
public List<BizPermSaveItemDto> BizPerms { get; init; } = [];
public List<SecretPermSaveItemDto> SecretPerms { get; init; } = [];
public List<FilterRuleSaveItemDto> FilterRules { get; init; } = [];
}
private static class TemplateBundleParser
{
public static TemplatePermBundle Parse(IEnumerable<DataPermTemplateItemDto> 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<string, object?> content) => new()
{
OrganizationId = GetString(content, "organizationId"),
OrgScope = GetInt(content, "orgScope", DataPermConstants.OrgScopeSelfAndChildren)
};
private static DataScopeSaveItemDto ParseDataScope(Dictionary<string, object?> content) => new()
{
DataScope = GetInt(content, "dataScope", DataPermConstants.DataScopePersonal),
DataResourceCode = GetOptionalString(content, "dataResourceCode")
};
private static BizPermSaveItemDto ParseBiz(Dictionary<string, object?> content) => new()
{
BizType = GetString(content, "bizType"),
BizId = GetString(content, "bizId"),
BizName = GetOptionalString(content, "bizName")
};
private static IEnumerable<SecretPermSaveItemDto> ParseSecret(Dictionary<string, object?> 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<string, object?> content)
{
var resourceCode = GetString(content, "dataResourceCode");
FilterContentDto filterContent;
if (content.TryGetValue("filterContent", out var raw) && raw is JsonElement element)
{
filterContent = element.Deserialize<FilterContentDto>() ?? new FilterContentDto();
}
else
{
filterContent = new FilterContentDto();
}
FilterAstValidator.ParseAndValidate(resourceCode, filterContent);
return new FilterRuleSaveItemDto
{
DataResourceCode = resourceCode,
FilterContent = filterContent
};
}
private static string GetString(Dictionary<string, object?> content, string key) =>
content.TryGetValue(key, out var raw) ? raw?.ToString()?.Trim() ?? string.Empty : string.Empty;
private static string? GetOptionalString(Dictionary<string, object?> content, string key) =>
content.TryGetValue(key, out var raw) ? raw?.ToString()?.Trim() : null;
private static int GetInt(Dictionary<string, object?> content, string key, int defaultValue) =>
content.TryGetValue(key, out var raw) && int.TryParse(raw?.ToString(), out var value) ? value : defaultValue;
}
}