using System.Text.Json; using Fake.Application; using Fake.DependencyInjection; using Fake.Rbac.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Oms.Application.Dtos.DataPerm; namespace Oms.Application.DataPerm; public class DataPermStore(FakeRbacDbContext dbContext, DataPermContextCache cache) : IScopedDependency { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; // EF Core 原始 SQL 参数不接受 DBNull,空值直接传 null private static object? SqlParam(object? value) => value; public async Task GetUserDataPermAsync(Guid userId, CancellationToken cancellationToken = default) { var user = await dbContext.Database.SqlQueryRaw(""" SELECT u.`Id` AS `UserId`, u.`Name` AS `UserName`, u.`OrganizationId` AS `OrganizationId`, o.`Name` AS `OrganizationName` FROM `user` u LEFT JOIN `organization` o ON o.`Id` = u.`OrganizationId` AND o.`IsDeleted` = 0 WHERE u.`Id` = {0} AND u.`IsDeleted` = 0 LIMIT 1 """, userId.ToString()).FirstOrDefaultAsync(cancellationToken) ?? throw new BusinessException("用户不存在"); var now = DateTime.UtcNow; var orgRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`OrganizationId` AS `OrganizationId`, o.`Name` AS `OrganizationName`, p.`OrgScope` AS `OrgScope`, p.`EffectiveTime` AS `EffectiveTime`, p.`ExpireTime` AS `ExpireTime`, p.`IsTemporary` AS `IsTemporary` FROM `user_org_perm` p LEFT JOIN `organization` o ON o.`Id` = p.`OrganizationId` AND o.`IsDeleted` = 0 WHERE p.`IsDeleted` = 0 AND p.`UserId` = {0} """, userId.ToString()).ToListAsync(cancellationToken); var scopeRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`DataScope` AS `DataScope`, p.`DataResourceCode` AS `DataResourceCode`, p.`ExpireTime` AS `ExpireTime` FROM `user_data_scope` p WHERE p.`IsDeleted` = 0 AND p.`UserId` = {0} """, userId.ToString()).ToListAsync(cancellationToken); var bizRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`BizType` AS `BizType`, p.`BizId` AS `BizId`, p.`BizName` AS `BizName`, p.`ExpireTime` AS `ExpireTime` FROM `user_biz_perm` p WHERE p.`IsDeleted` = 0 AND p.`UserId` = {0} """, userId.ToString()).ToListAsync(cancellationToken); var secretRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`SecretLevel` AS `SecretLevel`, p.`DataResourceCode` AS `DataResourceCode`, p.`ExpireTime` AS `ExpireTime` FROM `user_secret_perm` p WHERE p.`IsDeleted` = 0 AND p.`UserId` = {0} """, userId.ToString()).ToListAsync(cancellationToken); var filterRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`DataResourceCode` AS `DataResourceCode`, CAST(p.`FilterContent` AS CHAR) AS `FilterContentJson`, p.`ExpireTime` AS `ExpireTime` FROM `user_filter_rule` p WHERE p.`IsDeleted` = 0 AND p.`UserId` = {0} """, userId.ToString()).ToListAsync(cancellationToken); return new UserDataPermDto { UserId = user.UserId.ToString(), UserName = user.UserName, OrganizationId = user.OrganizationId?.ToString(), OrganizationName = user.OrganizationName, OrgPerms = orgRows.Select(x => new OrgPermItemDto { Id = x.Id.ToString(), OrganizationId = x.OrganizationId.ToString(), OrganizationName = x.OrganizationName, OrgScope = x.OrgScope, EffectiveTime = x.EffectiveTime, ExpireTime = x.ExpireTime, IsTemporary = x.IsTemporary != 0, IsExpired = IsExpired(now, x.EffectiveTime, x.ExpireTime) }).ToList(), DataScopes = scopeRows.Select(x => new DataScopeItemDto { Id = x.Id.ToString(), DataScope = x.DataScope, DataResourceCode = x.DataResourceCode, ExpireTime = x.ExpireTime, IsExpired = IsExpired(now, null, x.ExpireTime) }).ToList(), BizPerms = bizRows.Select(x => new BizPermItemDto { Id = x.Id.ToString(), BizType = x.BizType, BizId = x.BizId, BizName = x.BizName, ExpireTime = x.ExpireTime, IsExpired = IsExpired(now, null, x.ExpireTime) }).ToList(), SecretPerms = secretRows.Select(x => new SecretPermItemDto { Id = x.Id.ToString(), SecretLevel = x.SecretLevel, DataResourceCode = x.DataResourceCode, ExpireTime = x.ExpireTime, IsExpired = IsExpired(now, null, x.ExpireTime) }).ToList(), FilterRules = filterRows.Select(x => new FilterRuleItemDto { Id = x.Id.ToString(), DataResourceCode = x.DataResourceCode, FilterContent = FilterAstValidator.Deserialize(x.FilterContentJson), ExpireTime = x.ExpireTime, IsExpired = IsExpired(now, null, x.ExpireTime) }).ToList() }; } public async Task GetRoleDataPermAsync(Guid roleId, CancellationToken cancellationToken = default) { var role = await dbContext.Database.SqlQueryRaw(""" SELECT r.`Id` AS `RoleId`, r.`Name` AS `RoleName` FROM `role` r WHERE r.`Id` = {0} AND r.`IsDeleted` = 0 LIMIT 1 """, roleId.ToString()).FirstOrDefaultAsync(cancellationToken) ?? throw new BusinessException("角色不存在"); var orgRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`OrganizationId` AS `OrganizationId`, o.`Name` AS `OrganizationName`, p.`OrgScope` AS `OrgScope` FROM `role_org_perm` p LEFT JOIN `organization` o ON o.`Id` = p.`OrganizationId` AND o.`IsDeleted` = 0 WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {0} """, roleId.ToString()).ToListAsync(cancellationToken); var scopeRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`DataScope` AS `DataScope`, p.`DataResourceCode` AS `DataResourceCode` FROM `role_data_scope` p WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {0} """, roleId.ToString()).ToListAsync(cancellationToken); var bizRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`BizType` AS `BizType`, p.`BizId` AS `BizId`, p.`BizName` AS `BizName` FROM `role_biz_perm` p WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {0} """, roleId.ToString()).ToListAsync(cancellationToken); var secretRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`SecretLevel` AS `SecretLevel`, p.`DataResourceCode` AS `DataResourceCode` FROM `role_secret_perm` p WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {0} """, roleId.ToString()).ToListAsync(cancellationToken); var filterRows = await dbContext.Database.SqlQueryRaw(""" SELECT p.`Id` AS `Id`, p.`DataResourceCode` AS `DataResourceCode`, CAST(p.`FilterContent` AS CHAR) AS `FilterContentJson` FROM `role_filter_rule` p WHERE p.`IsDeleted` = 0 AND p.`RoleId` = {0} """, roleId.ToString()).ToListAsync(cancellationToken); return new RoleDataPermDto { RoleId = role.RoleId.ToString(), RoleName = role.RoleName, OrgPerms = orgRows.Select(x => new OrgPermItemDto { Id = x.Id.ToString(), OrganizationId = x.OrganizationId.ToString(), OrganizationName = x.OrganizationName, OrgScope = x.OrgScope }).ToList(), DataScopes = scopeRows.Select(x => new DataScopeItemDto { Id = x.Id.ToString(), DataScope = x.DataScope, DataResourceCode = x.DataResourceCode }).ToList(), BizPerms = bizRows.Select(x => new BizPermItemDto { Id = x.Id.ToString(), BizType = x.BizType, BizId = x.BizId, BizName = x.BizName }).ToList(), SecretPerms = secretRows.Select(x => new SecretPermItemDto { Id = x.Id.ToString(), SecretLevel = x.SecretLevel, DataResourceCode = x.DataResourceCode }).ToList(), FilterRules = filterRows.Select(x => new FilterRuleItemDto { Id = x.Id.ToString(), DataResourceCode = x.DataResourceCode, FilterContent = FilterAstValidator.Deserialize(x.FilterContentJson) }).ToList() }; } public async Task SaveUserDataPermAsync( Guid userId, SaveUserDataPermInput input, Guid operatorUserId, int operatorMapId, CancellationToken cancellationToken = default) { await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); var beforeJson = JsonSerializer.Serialize(await GetUserDataPermAsync(userId, cancellationToken), JsonOptions); var operatorId = operatorMapId; await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `user_org_perm` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `UserId` = {1} AND `IsDeleted` = 0", [operatorId, userId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `user_data_scope` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `UserId` = {1} AND `IsDeleted` = 0", [operatorId, userId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `user_biz_perm` SET `IsDeleted` = 1 WHERE `UserId` = {0} AND `IsDeleted` = 0", [userId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `user_secret_perm` SET `IsDeleted` = 1 WHERE `UserId` = {0} AND `IsDeleted` = 0", [userId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `user_filter_rule` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `UserId` = {1} AND `IsDeleted` = 0", [operatorId, userId.ToString()], cancellationToken); foreach (var item in input.OrgPerms) { var orgId = Guid.Parse(item.OrganizationId); await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `user_org_perm` (`Id`,`UserId`,`OrganizationId`,`OrgScope`,`EffectiveTime`,`ExpireTime`,`IsTemporary`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},{6},{7},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), userId.ToString(), orgId.ToString(), item.OrgScope, item.EffectiveTime ?? DateTime.UtcNow, SqlParam(item.ExpireTime), item.IsTemporary ? 1 : 0, operatorId ], cancellationToken); } foreach (var item in input.DataScopes) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `user_data_scope` (`Id`,`UserId`,`DataScope`,`DataResourceCode`,`ExpireTime`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), userId.ToString(), item.DataScope, SqlParam(string.IsNullOrWhiteSpace(item.DataResourceCode) ? null : item.DataResourceCode.Trim()), SqlParam(item.ExpireTime), operatorId ], cancellationToken); } foreach (var item in input.BizPerms) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `user_biz_perm` (`Id`,`UserId`,`BizType`,`BizId`,`BizName`,`ExpireTime`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},{6},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), userId.ToString(), item.BizType.Trim(), item.BizId.Trim(), SqlParam(item.BizName), SqlParam(item.ExpireTime), operatorId ], cancellationToken); } foreach (var item in input.SecretPerms) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `user_secret_perm` (`Id`,`UserId`,`SecretLevel`,`DataResourceCode`,`ExpireTime`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), userId.ToString(), item.SecretLevel, SqlParam(string.IsNullOrWhiteSpace(item.DataResourceCode) ? null : item.DataResourceCode.Trim()), SqlParam(item.ExpireTime), operatorId ], cancellationToken); } foreach (var item in input.FilterRules) { var validated = FilterAstValidator.ParseAndValidate(item.DataResourceCode, item.FilterContent); var json = FilterAstValidator.Serialize(validated); var hash = FilterAstValidator.ComputeHash(validated); await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `user_filter_rule` (`Id`,`UserId`,`DataResourceCode`,`FilterContent`,`FilterHash`,`ExpireTime`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},{6},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), userId.ToString(), item.DataResourceCode.Trim(), json, hash, SqlParam(item.ExpireTime), operatorId ], cancellationToken); } var afterJson = JsonSerializer.Serialize(await GetUserDataPermAsync(userId, cancellationToken), JsonOptions); await InsertChangeLogAsync("user", userId.ToString(), "update", beforeJson, afterJson, operatorUserId, operatorMapId, cancellationToken); await transaction.CommitAsync(cancellationToken); await cache.InvalidateUserAsync(userId, cancellationToken); } public async Task SaveRoleDataPermAsync( Guid roleId, SaveUserDataPermInput input, Guid operatorUserId, int operatorMapId, DataPermRepository repository, CancellationToken cancellationToken = default) { await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); var beforeJson = JsonSerializer.Serialize(await GetRoleDataPermAsync(roleId, cancellationToken), JsonOptions); var operatorId = operatorMapId; await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `role_org_perm` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `RoleId` = {1} AND `IsDeleted` = 0", [operatorId, roleId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `role_data_scope` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `RoleId` = {1} AND `IsDeleted` = 0", [operatorId, roleId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `role_biz_perm` SET `IsDeleted` = 1 WHERE `RoleId` = {0} AND `IsDeleted` = 0", [roleId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `role_secret_perm` SET `IsDeleted` = 1 WHERE `RoleId` = {0} AND `IsDeleted` = 0", [roleId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `role_filter_rule` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP(), `UpdateUserId` = {0} WHERE `RoleId` = {1} AND `IsDeleted` = 0", [operatorId, roleId.ToString()], cancellationToken); foreach (var item in input.OrgPerms) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `role_org_perm` (`Id`,`RoleId`,`OrganizationId`,`OrgScope`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},UTC_TIMESTAMP()) """, [Guid.NewGuid().ToString(), roleId.ToString(), item.OrganizationId, item.OrgScope, operatorId], cancellationToken); } foreach (var item in input.DataScopes) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `role_data_scope` (`Id`,`RoleId`,`DataScope`,`DataResourceCode`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), roleId.ToString(), item.DataScope, SqlParam(string.IsNullOrWhiteSpace(item.DataResourceCode) ? null : item.DataResourceCode.Trim()), operatorId ], cancellationToken); } foreach (var item in input.BizPerms) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `role_biz_perm` (`Id`,`RoleId`,`BizType`,`BizId`,`BizName`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},UTC_TIMESTAMP()) """, [Guid.NewGuid().ToString(), roleId.ToString(), item.BizType.Trim(), item.BizId.Trim(), SqlParam(item.BizName), operatorId], cancellationToken); } foreach (var item in input.SecretPerms) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `role_secret_perm` (`Id`,`RoleId`,`SecretLevel`,`DataResourceCode`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), roleId.ToString(), item.SecretLevel, SqlParam(string.IsNullOrWhiteSpace(item.DataResourceCode) ? null : item.DataResourceCode.Trim()), operatorId ], cancellationToken); } foreach (var item in input.FilterRules) { var validated = FilterAstValidator.ParseAndValidate(item.DataResourceCode, item.FilterContent); var json = FilterAstValidator.Serialize(validated); var hash = FilterAstValidator.ComputeHash(validated); await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `role_filter_rule` (`Id`,`RoleId`,`DataResourceCode`,`FilterContent`,`FilterHash`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},UTC_TIMESTAMP()) """, [Guid.NewGuid().ToString(), roleId.ToString(), item.DataResourceCode.Trim(), json, hash, operatorId], cancellationToken); } var afterJson = JsonSerializer.Serialize(await GetRoleDataPermAsync(roleId, cancellationToken), JsonOptions); await InsertChangeLogAsync("role", roleId.ToString(), "update", beforeJson, afterJson, operatorUserId, operatorMapId, cancellationToken); await transaction.CommitAsync(cancellationToken); var userIds = await repository.GetUserIdsByRoleAsync(roleId, cancellationToken); foreach (var userId in userIds) { await cache.InvalidateUserAsync(userId, cancellationToken); } } public async Task InsertChangeLogAsync( string targetType, string targetId, string action, string? beforeJson, string? afterJson, Guid operatorUserId, int operatorMapId, CancellationToken cancellationToken = default) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `data_perm_change_log` (`TargetType`,`TargetId`,`Action`,`BeforeJson`,`AfterJson`,`OperatorUserId`,`OperatorMapId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},{6},UTC_TIMESTAMP()) """, [targetType, targetId, action, SqlParam(beforeJson), SqlParam(afterJson), operatorUserId.ToString(), operatorMapId], cancellationToken); } public async Task> GetChangeLogsAsync( string? targetType, string? targetId, int page, int pageSize, CancellationToken cancellationToken = default) { var where = "WHERE 1=1"; var args = new List(); if (!string.IsNullOrWhiteSpace(targetType)) { where += " AND l.`TargetType` = {" + args.Count + "}"; args.Add(targetType.Trim()); } if (!string.IsNullOrWhiteSpace(targetId)) { where += " AND l.`TargetId` = {" + args.Count + "}"; args.Add(targetId.Trim()); } var countSql = $"SELECT COUNT(*) AS `Value` FROM `data_perm_change_log` l {where}"; var total = await dbContext.Database.SqlQueryRaw(countSql, args.ToArray()).FirstAsync(cancellationToken); var offset = Math.Max(page - 1, 0) * pageSize; var listSql = $""" SELECT l.`Id` AS `Id`, l.`TargetType` AS `TargetType`, l.`TargetId` AS `TargetId`, l.`Action` AS `Action`, l.`OperatorUserId` AS `OperatorUserId`, l.`OperatorMapId` AS `OperatorMapId`, l.`CreateTime` AS `CreateTime` FROM `data_perm_change_log` l {where} ORDER BY l.`CreateTime` DESC LIMIT {pageSize} OFFSET {offset} """; var rows = await dbContext.Database.SqlQueryRaw(listSql, args.ToArray()) .ToListAsync(cancellationToken); var items = rows.Select(x => new ChangeLogItemDto { Id = x.Id, TargetType = x.TargetType, TargetId = x.TargetId.ToString(), Action = x.Action, OperatorUserId = x.OperatorUserId?.ToString(), OperatorMapId = x.OperatorMapId, CreateTime = x.CreateTime }).ToList(); return new DataPermPagedResult { Total = total, Items = items }; } public async Task> GetFieldMasksAsync(string dataResourceCode, CancellationToken cancellationToken = default) { const string sql = """ SELECT m.`FieldName` AS `FieldName`, m.`SecretLevel` AS `SecretLevel`, m.`MaskType` AS `MaskType`, m.`MaskPattern` AS `MaskPattern` FROM `data_perm_field_mask` m WHERE m.`IsDeleted` = 0 AND m.`IsEnabled` = 1 AND m.`DataResourceCode` = {0} ORDER BY m.`FieldName` """; return await dbContext.Database.SqlQueryRaw(sql, dataResourceCode) .ToListAsync(cancellationToken); } public async Task SaveFieldMasksAsync(string dataResourceCode, List items, CancellationToken cancellationToken = default) { await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `data_perm_field_mask` SET `IsDeleted` = 1 WHERE `DataResourceCode` = {0} AND `IsDeleted` = 0", [dataResourceCode], cancellationToken); foreach (var item in items) { await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `data_perm_field_mask` (`Id`,`DataResourceCode`,`FieldName`,`SecretLevel`,`MaskType`,`MaskPattern`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},{5},UTC_TIMESTAMP()) """, [ Guid.NewGuid().ToString(), dataResourceCode, item.FieldName.Trim(), item.SecretLevel, string.IsNullOrWhiteSpace(item.MaskType) ? "partial" : item.MaskType.Trim(), SqlParam(item.MaskPattern) ], cancellationToken); } } public async Task> GetAccessLogsAsync( int page, int pageSize, CancellationToken cancellationToken = default) { const string countSql = "SELECT COUNT(*) AS `Value` FROM `data_perm_access_log`"; var total = await dbContext.Database.SqlQueryRaw(countSql).FirstAsync(cancellationToken); var offset = Math.Max(page - 1, 0) * pageSize; var listSql = $""" SELECT l.`Id` AS `Id`, l.`UserId` AS `UserId`, l.`DataResourceCode` AS `DataResourceCode`, l.`ApiPath` AS `ApiPath`, l.`RowCount` AS `RowCount`, l.`CreateTime` AS `CreateTime` FROM `data_perm_access_log` l ORDER BY l.`CreateTime` DESC LIMIT {pageSize} OFFSET {offset} """; var items = await dbContext.Database.SqlQueryRaw(listSql) .ToListAsync(cancellationToken); return new DataPermPagedResult { Total = total, Items = items.Select(x => new AccessLogItemDto { Id = x.Id, UserId = x.UserId.ToString(), DataResourceCode = x.DataResourceCode, ApiPath = x.ApiPath, RowCount = x.RowCount, CreateTime = x.CreateTime }).ToList() }; } public async Task> GetTemplateListAsync( string? keyword, int page, int pageSize, CancellationToken cancellationToken = default) { var where = "WHERE t.`IsDeleted` = 0"; var args = new List(); if (!string.IsNullOrWhiteSpace(keyword)) { where += " AND (t.`Name` LIKE {" + args.Count + "} OR t.`Code` LIKE {" + (args.Count + 1) + "})"; args.Add($"%{keyword.Trim()}%"); args.Add($"%{keyword.Trim()}%"); } #pragma warning disable EF1002 var total = await dbContext.Database.SqlQueryRaw( $"SELECT COUNT(*) AS `Value` FROM `data_perm_template` t {where}", args.ToArray()).FirstAsync(cancellationToken); #pragma warning restore EF1002 var offset = Math.Max(page - 1, 0) * pageSize; var sql = $""" SELECT t.`Id` AS `Id`, t.`Name` AS `Name`, t.`Code` AS `Code`, t.`Description` AS `Description`, t.`RoleCode` AS `RoleCode`, t.`IsBuiltIn` AS `IsBuiltIn`, (SELECT COUNT(*) FROM `data_perm_template_item` i WHERE i.`TemplateId` = t.`Id` AND i.`IsDeleted` = 0) AS `ItemCount` FROM `data_perm_template` t {where} ORDER BY t.`CreateTime` DESC LIMIT {pageSize} OFFSET {offset} """; var rows = await dbContext.Database.SqlQueryRaw(sql, args.ToArray()) .ToListAsync(cancellationToken); var items = rows.Select(x => new DataPermTemplateListItemDto { Id = x.Id.ToString(), Name = x.Name, Code = x.Code, Description = x.Description, RoleCode = x.RoleCode, IsBuiltIn = x.IsBuiltIn != 0, ItemCount = x.ItemCount }).ToList(); return new DataPermPagedResult { Total = total, Items = items }; } public async Task GetTemplateDetailAsync(Guid templateId, CancellationToken cancellationToken = default) { const string headerSql = """ SELECT t.`Id` AS `Id`, t.`Name` AS `Name`, t.`Code` AS `Code`, t.`Description` AS `Description`, t.`RoleCode` AS `RoleCode`, t.`IsBuiltIn` AS `IsBuiltIn` FROM `data_perm_template` t WHERE t.`Id` = {0} AND t.`IsDeleted` = 0 LIMIT 1 """; var headerRow = await dbContext.Database.SqlQueryRaw(headerSql, templateId.ToString()) .FirstOrDefaultAsync(cancellationToken) ?? throw new BusinessException("模板不存在"); const string itemSql = """ SELECT i.`Id` AS `Id`, i.`DimensionType` AS `DimensionType`, CAST(i.`ItemContent` AS CHAR) AS `ItemContentJson`, i.`SortOrder` AS `SortOrder` FROM `data_perm_template_item` i WHERE i.`TemplateId` = {0} AND i.`IsDeleted` = 0 ORDER BY i.`SortOrder`, i.`DimensionType` """; var itemRows = await dbContext.Database.SqlQueryRaw(itemSql, templateId.ToString()) .ToListAsync(cancellationToken); var header = new DataPermTemplateDetailDto { Id = headerRow.Id.ToString(), Name = headerRow.Name, Code = headerRow.Code, Description = headerRow.Description, RoleCode = headerRow.RoleCode, IsBuiltIn = headerRow.IsBuiltIn != 0, Items = itemRows.Select(x => new DataPermTemplateItemDto { Id = x.Id.ToString(), DimensionType = x.DimensionType, ItemContent = JsonSerializer.Deserialize>(x.ItemContentJson, JsonOptions) ?? [], SortOrder = x.SortOrder }).ToList() }; return header; } public async Task CreateTemplateAsync( DataPermTemplateSaveInput input, int operatorMapId, CancellationToken cancellationToken = default) { var id = Guid.NewGuid(); await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `data_perm_template` (`Id`,`Name`,`Code`,`Description`,`RoleCode`,`IsBuiltIn`,`CreateUserId`,`CreateTime`) VALUES ({0},{1},{2},{3},{4},0,{5},UTC_TIMESTAMP()) """, [id.ToString(), input.Name.Trim(), input.Code.Trim(), SqlParam(input.Description), SqlParam(input.RoleCode), operatorMapId], cancellationToken); await ReplaceTemplateItemsAsync(id, input.Items, cancellationToken); return await GetTemplateDetailAsync(id, cancellationToken); } public async Task UpdateTemplateAsync( Guid templateId, DataPermTemplateSaveInput input, int operatorMapId, CancellationToken cancellationToken = default) { var existing = await GetTemplateDetailAsync(templateId, cancellationToken); if (existing.IsBuiltIn && !string.Equals(existing.Code, input.Code.Trim(), StringComparison.OrdinalIgnoreCase)) { throw new BusinessException("内置模板不可修改编码"); } await dbContext.Database.ExecuteSqlRawAsync(""" UPDATE `data_perm_template` SET `Name` = {0}, `Description` = {1}, `RoleCode` = {2}, `UpdateUserId` = {3}, `UpdateTime` = UTC_TIMESTAMP() WHERE `Id` = {4} AND `IsDeleted` = 0 """, [input.Name.Trim(), SqlParam(input.Description), SqlParam(input.RoleCode), operatorMapId, templateId.ToString()], cancellationToken); await ReplaceTemplateItemsAsync(templateId, input.Items, cancellationToken); return await GetTemplateDetailAsync(templateId, cancellationToken); } public async Task DeleteTemplateAsync(Guid templateId, CancellationToken cancellationToken = default) { var existing = await GetTemplateDetailAsync(templateId, cancellationToken); if (existing.IsBuiltIn) { throw new BusinessException("内置模板不可删除"); } await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `data_perm_template` SET `IsDeleted` = 1, `UpdateTime` = UTC_TIMESTAMP() WHERE `Id` = {0}", [templateId.ToString()], cancellationToken); await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `data_perm_template_item` SET `IsDeleted` = 1 WHERE `TemplateId` = {0}", [templateId.ToString()], cancellationToken); } private async Task ReplaceTemplateItemsAsync( Guid templateId, List items, CancellationToken cancellationToken) { await dbContext.Database.ExecuteSqlRawAsync( "UPDATE `data_perm_template_item` SET `IsDeleted` = 1 WHERE `TemplateId` = {0}", [templateId.ToString()], cancellationToken); foreach (var item in items.OrderBy(x => x.SortOrder)) { var json = JsonSerializer.Serialize(item.ItemContent, JsonOptions); await dbContext.Database.ExecuteSqlRawAsync(""" INSERT INTO `data_perm_template_item` (`Id`,`TemplateId`,`DimensionType`,`ItemContent`,`SortOrder`) VALUES ({0},{1},{2},{3},{4}) """, [Guid.NewGuid().ToString(), templateId.ToString(), item.DimensionType.Trim(), json, item.SortOrder], cancellationToken); } } private static bool IsExpired(DateTime now, DateTime? effectiveTime, DateTime? expireTime) => (effectiveTime.HasValue && effectiveTime.Value > now) || (expireTime.HasValue && expireTime.Value <= now); private sealed class UserHeaderRow { public Guid UserId { get; set; } public string? UserName { get; set; } public Guid? OrganizationId { get; set; } public string? OrganizationName { get; set; } } private sealed class UserOrgPermRow { public Guid Id { get; set; } public Guid OrganizationId { get; set; } public string? OrganizationName { get; set; } public int OrgScope { get; set; } public DateTime? EffectiveTime { get; set; } public DateTime? ExpireTime { get; set; } public int IsTemporary { get; set; } } private sealed class UserDataScopeRow { public Guid Id { get; set; } public int DataScope { get; set; } public string? DataResourceCode { get; set; } public DateTime? ExpireTime { get; set; } } private sealed class UserBizPermRow { public Guid Id { get; set; } public string BizType { get; set; } = string.Empty; public string BizId { get; set; } = string.Empty; public string? BizName { get; set; } public DateTime? ExpireTime { get; set; } } private sealed class UserSecretPermRow { public Guid Id { get; set; } public int SecretLevel { get; set; } public string? DataResourceCode { get; set; } public DateTime? ExpireTime { get; set; } } private sealed class UserFilterRuleRow { public Guid Id { get; set; } public string DataResourceCode { get; set; } = string.Empty; public string FilterContentJson { get; set; } = string.Empty; public DateTime? ExpireTime { get; set; } } private sealed class RoleHeaderRow { public Guid RoleId { get; set; } public string? RoleName { get; set; } } private sealed class RoleOrgPermRow { public Guid Id { get; set; } public Guid OrganizationId { get; set; } public string? OrganizationName { get; set; } public int OrgScope { get; set; } } private sealed class RoleDataScopeRow { public Guid Id { get; set; } public int DataScope { get; set; } public string? DataResourceCode { get; set; } } private sealed class RoleBizPermRow { public Guid Id { get; set; } public string BizType { get; set; } = string.Empty; public string BizId { get; set; } = string.Empty; public string? BizName { get; set; } } private sealed class RoleSecretPermRow { public Guid Id { get; set; } public int SecretLevel { get; set; } public string? DataResourceCode { get; set; } } private sealed class RoleFilterRuleRow { public Guid Id { get; set; } public string DataResourceCode { get; set; } = string.Empty; public string FilterContentJson { get; set; } = string.Empty; } private sealed class ChangeLogRow { public long Id { get; set; } public string TargetType { get; set; } = string.Empty; public Guid TargetId { get; set; } public string Action { get; set; } = string.Empty; public Guid? OperatorUserId { get; set; } public int? OperatorMapId { get; set; } public DateTime CreateTime { get; set; } } private sealed class AccessLogRow { public long Id { get; set; } public Guid UserId { get; set; } public string DataResourceCode { get; set; } = string.Empty; public string ApiPath { get; set; } = string.Empty; public int? RowCount { get; set; } public DateTime CreateTime { get; set; } } private sealed class TemplateListRow { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public string Code { get; set; } = string.Empty; public string? Description { get; set; } public string? RoleCode { get; set; } public int IsBuiltIn { get; set; } public int ItemCount { get; set; } } private sealed class TemplateHeaderRow { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public string Code { get; set; } = string.Empty; public string? Description { get; set; } public string? RoleCode { get; set; } public int IsBuiltIn { get; set; } } private sealed class TemplateItemRow { public Guid Id { get; set; } public string DimensionType { get; set; } = string.Empty; public string ItemContentJson { get; set; } = string.Empty; public int SortOrder { get; set; } } }