修复 get-effective-by-user 展开下级组织时报 Unknown column c.PId。 Co-authored-by: Cursor <cursoragent@cursor.com>
533 lines
18 KiB
C#
533 lines
18 KiB
C#
using Fake.DependencyInjection;
|
|
using Fake.Rbac.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Oms.Application.Dtos.DataPerm;
|
|
|
|
namespace Oms.Application.DataPerm;
|
|
|
|
public class DataPermRepository(FakeRbacDbContext dbContext) : IScopedDependency
|
|
{
|
|
private FakeRbacDbContext Db => dbContext;
|
|
|
|
public async Task<RbacUserSnapshot?> GetUserSnapshotAsync(Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT
|
|
u.`Id` AS `Id`,
|
|
u.`userid` AS `MapId`,
|
|
u.`Account` AS `Account`,
|
|
u.`Name` AS `Name`,
|
|
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
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<RbacUserSnapshot>(sql, userId.ToString())
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> IsPureCustomerUserAsync(Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT COUNT(*) AS `Value`
|
|
FROM `user_role` ur
|
|
INNER JOIN `role` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0
|
|
WHERE ur.`UserId` = {0}
|
|
AND r.`Code` <> 'Customer'
|
|
AND r.`Code` NOT LIKE 'CustomerSub_%'
|
|
""";
|
|
|
|
var nonCustomerRoleCount = await Db.Database.SqlQueryRaw<int>(sql, userId.ToString())
|
|
.FirstAsync(cancellationToken);
|
|
if (nonCustomerRoleCount > 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const string hasRoleSql = """
|
|
SELECT COUNT(*) AS `Value`
|
|
FROM `user_role` ur
|
|
WHERE ur.`UserId` = {0}
|
|
""";
|
|
|
|
var roleCount = await Db.Database.SqlQueryRaw<int>(hasRoleSql, userId.ToString())
|
|
.FirstAsync(cancellationToken);
|
|
return roleCount > 0;
|
|
}
|
|
|
|
public async Task<bool> IsAdminUserAsync(Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT COUNT(*) AS `Value`
|
|
FROM `user_role` ur
|
|
INNER JOIN `role` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0
|
|
WHERE ur.`UserId` = {0}
|
|
AND r.`Code` = 'Admin'
|
|
""";
|
|
|
|
var count = await Db.Database.SqlQueryRaw<int>(sql, userId.ToString()).FirstAsync(cancellationToken);
|
|
return count > 0;
|
|
}
|
|
|
|
public async Task<List<Guid>> GetUserRoleIdsAsync(Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT ur.`RoleId` AS `Value`
|
|
FROM `user_role` ur
|
|
WHERE ur.`UserId` = {0}
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<Guid>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<Guid>> GetUserIdsByRoleAsync(Guid roleId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT ur.`UserId` AS `Value`
|
|
FROM `user_role` ur
|
|
WHERE ur.`RoleId` = {0}
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<Guid>(sql, roleId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<OrgPermRow>> GetRoleOrgPermsAsync(IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = roleIds.Distinct().ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT
|
|
p.`OrganizationId` AS `OrganizationId`,
|
|
p.`OrgScope` AS `OrgScope`,
|
|
NULL AS `EffectiveTime`,
|
|
NULL AS `ExpireTime`
|
|
FROM `role_org_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`RoleId` IN ({inClause})
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<OrgPermRow>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<OrgPermRow>> GetUserOrgPermsAsync(Guid userId, bool includeExpired, CancellationToken cancellationToken = default)
|
|
{
|
|
var sql = includeExpired
|
|
? """
|
|
SELECT
|
|
p.`OrganizationId` AS `OrganizationId`,
|
|
p.`OrgScope` AS `OrgScope`,
|
|
p.`EffectiveTime` AS `EffectiveTime`,
|
|
p.`ExpireTime` AS `ExpireTime`
|
|
FROM `user_org_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`UserId` = {0}
|
|
"""
|
|
: """
|
|
SELECT
|
|
p.`OrganizationId` AS `OrganizationId`,
|
|
p.`OrgScope` AS `OrgScope`,
|
|
p.`EffectiveTime` AS `EffectiveTime`,
|
|
p.`ExpireTime` AS `ExpireTime`
|
|
FROM `user_org_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`UserId` = {0}
|
|
AND p.`EffectiveTime` <= UTC_TIMESTAMP()
|
|
AND (p.`ExpireTime` IS NULL OR p.`ExpireTime` > UTC_TIMESTAMP())
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<OrgPermRow>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<DataScopeRow>> GetRoleDataScopesAsync(IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = roleIds.Distinct().ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT
|
|
p.`DataScope` AS `DataScope`,
|
|
p.`DataResourceCode` AS `DataResourceCode`,
|
|
NULL AS `ExpireTime`
|
|
FROM `role_data_scope` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`RoleId` IN ({inClause})
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<DataScopeRow>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<DataScopeRow>> GetUserDataScopesAsync(Guid userId, bool includeExpired, CancellationToken cancellationToken = default)
|
|
{
|
|
var sql = includeExpired
|
|
? """
|
|
SELECT
|
|
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}
|
|
"""
|
|
: """
|
|
SELECT
|
|
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}
|
|
AND p.`EffectiveTime` <= UTC_TIMESTAMP()
|
|
AND (p.`ExpireTime` IS NULL OR p.`ExpireTime` > UTC_TIMESTAMP())
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<DataScopeRow>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<BizPermRow>> GetRoleBizPermsAsync(IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = roleIds.Distinct().ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT
|
|
p.`BizType` AS `BizType`,
|
|
p.`BizId` AS `BizId`,
|
|
NULL AS `ExpireTime`
|
|
FROM `role_biz_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`RoleId` IN ({inClause})
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<BizPermRow>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<BizPermRow>> GetUserBizPermsAsync(Guid userId, bool includeExpired, CancellationToken cancellationToken = default)
|
|
{
|
|
var sql = includeExpired
|
|
? """
|
|
SELECT
|
|
p.`BizType` AS `BizType`,
|
|
p.`BizId` AS `BizId`,
|
|
p.`ExpireTime` AS `ExpireTime`
|
|
FROM `user_biz_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`UserId` = {0}
|
|
"""
|
|
: """
|
|
SELECT
|
|
p.`BizType` AS `BizType`,
|
|
p.`BizId` AS `BizId`,
|
|
p.`ExpireTime` AS `ExpireTime`
|
|
FROM `user_biz_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`UserId` = {0}
|
|
AND p.`EffectiveTime` <= UTC_TIMESTAMP()
|
|
AND (p.`ExpireTime` IS NULL OR p.`ExpireTime` > UTC_TIMESTAMP())
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<BizPermRow>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<SecretPermRow>> GetRoleSecretPermsAsync(IEnumerable<Guid> roleIds, CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = roleIds.Distinct().ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT
|
|
p.`SecretLevel` AS `SecretLevel`,
|
|
p.`DataResourceCode` AS `DataResourceCode`,
|
|
NULL AS `ExpireTime`
|
|
FROM `role_secret_perm` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`RoleId` IN ({inClause})
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<SecretPermRow>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<SecretPermRow>> GetUserSecretPermsAsync(Guid userId, bool includeExpired, CancellationToken cancellationToken = default)
|
|
{
|
|
var sql = includeExpired
|
|
? """
|
|
SELECT
|
|
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}
|
|
"""
|
|
: """
|
|
SELECT
|
|
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}
|
|
AND p.`EffectiveTime` <= UTC_TIMESTAMP()
|
|
AND (p.`ExpireTime` IS NULL OR p.`ExpireTime` > UTC_TIMESTAMP())
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<SecretPermRow>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<FilterRuleRow>> GetRoleFilterRulesAsync(
|
|
IEnumerable<Guid> roleIds,
|
|
string dataResourceCode,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = roleIds.Distinct().ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT
|
|
p.`DataResourceCode` AS `DataResourceCode`,
|
|
CAST(p.`FilterContent` AS CHAR) AS `FilterContentJson`,
|
|
NULL AS `ExpireTime`
|
|
FROM `role_filter_rule` p
|
|
WHERE p.`IsDeleted` = 0
|
|
AND p.`RoleId` IN ({inClause})
|
|
AND p.`DataResourceCode` = '{dataResourceCode.Replace("'", "''")}'
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<FilterRuleRow>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<List<FilterRuleRow>> GetUserFilterRulesAsync(
|
|
Guid userId,
|
|
string dataResourceCode,
|
|
bool includeExpired,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var resource = dataResourceCode.Replace("'", "''");
|
|
var baseSql = """
|
|
SELECT
|
|
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}
|
|
AND p.`DataResourceCode` =
|
|
""" + $"'{resource}'";
|
|
|
|
var sql = includeExpired
|
|
? baseSql
|
|
: baseSql + """
|
|
|
|
AND p.`EffectiveTime` <= UTC_TIMESTAMP()
|
|
AND (p.`ExpireTime` IS NULL OR p.`ExpireTime` > UTC_TIMESTAMP())
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<FilterRuleRow>(sql, userId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<HashSet<Guid>> ExpandOrganizationIdsAsync(
|
|
IEnumerable<(Guid OrganizationId, int OrgScope)> orgEntries,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var result = new HashSet<Guid>();
|
|
foreach (var (organizationId, orgScope) in orgEntries)
|
|
{
|
|
if (organizationId == Guid.Empty)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (orgScope == DataPermConstants.OrgScopeSelfOnly)
|
|
{
|
|
result.Add(organizationId);
|
|
continue;
|
|
}
|
|
|
|
var descendants = await GetOrganizationSubtreeIdsAsync(organizationId, cancellationToken);
|
|
foreach (var id in descendants)
|
|
{
|
|
result.Add(id);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<List<Guid>> GetOrganizationSubtreeIdsAsync(Guid rootOrganizationId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
WITH RECURSIVE org_tree AS (
|
|
SELECT o.`Id`
|
|
FROM `organization` o
|
|
WHERE o.`Id` = {0} AND o.`IsDeleted` = 0
|
|
UNION ALL
|
|
SELECT c.`Id`
|
|
FROM `organization` c
|
|
INNER JOIN org_tree t ON c.`ParentId` = t.`Id`
|
|
WHERE c.`IsDeleted` = 0
|
|
)
|
|
SELECT `Id` AS `Value` FROM org_tree
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<Guid>(sql, rootOrganizationId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<Dictionary<Guid, string>> GetOrganizationNamesAsync(
|
|
IEnumerable<Guid> organizationIds,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var ids = organizationIds.Distinct().Where(x => x != Guid.Empty).ToList();
|
|
if (ids.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", ids.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT o.`Id` AS `Id`, o.`Name` AS `Name`
|
|
FROM `organization` o
|
|
WHERE o.`IsDeleted` = 0
|
|
AND o.`Id` IN ({inClause})
|
|
""";
|
|
|
|
var rows = await Db.Database.SqlQueryRaw<OrganizationNameRow>(sql).ToListAsync(cancellationToken);
|
|
return rows.ToDictionary(x => x.Id, x => x.Name ?? x.Id.ToString());
|
|
}
|
|
|
|
public async Task<bool> OrganizationExistsAndEnabledAsync(Guid organizationId, CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT COUNT(*) AS `Value`
|
|
FROM `organization` o
|
|
WHERE o.`Id` = {0}
|
|
AND o.`IsDeleted` = 0
|
|
""";
|
|
|
|
var count = await Db.Database.SqlQueryRaw<int>(sql, organizationId.ToString()).FirstAsync(cancellationToken);
|
|
return count > 0;
|
|
}
|
|
|
|
public async Task<List<int>> ResolveOwnerMapIdsAsync(
|
|
Guid organizationId,
|
|
int dataScope,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (organizationId == Guid.Empty)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
if (dataScope == DataPermConstants.DataScopeTeam)
|
|
{
|
|
const string sql = """
|
|
SELECT u.`userid` AS `Value`
|
|
FROM `user` u
|
|
WHERE u.`IsDeleted` = 0
|
|
AND u.`OrganizationId` = {0}
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<int>(sql, organizationId.ToString())
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
if (dataScope == DataPermConstants.DataScopeDepartment)
|
|
{
|
|
var orgIds = await GetOrganizationSubtreeIdsAsync(organizationId, cancellationToken);
|
|
if (orgIds.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var inClause = string.Join(",", orgIds.Select(x => $"'{x}'"));
|
|
var sql = $"""
|
|
SELECT u.`userid` AS `Value`
|
|
FROM `user` u
|
|
WHERE u.`IsDeleted` = 0
|
|
AND u.`OrganizationId` IN ({inClause})
|
|
""";
|
|
|
|
return await Db.Database.SqlQueryRaw<int>(sql).ToListAsync(cancellationToken);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public async Task<List<DataResourceMetaDto>> GetDataResourcesAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
const string sql = """
|
|
SELECT
|
|
r.`Code` AS `Code`,
|
|
r.`Name` AS `Name`,
|
|
r.`MainTable` AS `MainTable`
|
|
FROM `data_resource` r
|
|
WHERE r.`IsDeleted` = 0
|
|
AND r.`IsEnabled` = 1
|
|
ORDER BY r.`SortOrder`, r.`Code`
|
|
""";
|
|
|
|
var rows = await Db.Database.SqlQueryRaw<DataResourceRow>(sql).ToListAsync(cancellationToken);
|
|
return rows.Select(row => new DataResourceMetaDto
|
|
{
|
|
Code = row.Code,
|
|
Name = row.Name,
|
|
MainTable = row.MainTable,
|
|
FilterableFields = DataPermConstants.FilterableFieldsByResource.TryGetValue(row.Code, out var fields)
|
|
? fields.ToList()
|
|
: []
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task InvalidateUserContextCacheKeysAsync(Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
// Cache invalidation handled by DataPermContextCache; repository placeholder for future DB-side hooks.
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private sealed class OrganizationNameRow
|
|
{
|
|
public Guid Id { get; set; }
|
|
|
|
public string? Name { get; set; }
|
|
}
|
|
|
|
private sealed class DataResourceRow
|
|
{
|
|
public string Code { get; set; } = string.Empty;
|
|
|
|
public string Name { get; set; } = string.Empty;
|
|
|
|
public string? MainTable { get; set; }
|
|
}
|
|
}
|