查价返回 DDP/DDU 与产品分组;用户更新微信二维码时避免 EF 使用 DBNull。 Co-authored-by: Cursor <cursoragent@cursor.com>
977 lines
39 KiB
C#
977 lines
39 KiB
C#
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<UserDataPermDto> GetUserDataPermAsync(Guid userId, CancellationToken cancellationToken = default)
|
||
{
|
||
var user = await dbContext.Database.SqlQueryRaw<UserHeaderRow>("""
|
||
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<UserOrgPermRow>("""
|
||
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<UserDataScopeRow>("""
|
||
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<UserBizPermRow>("""
|
||
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<UserSecretPermRow>("""
|
||
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<UserFilterRuleRow>("""
|
||
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<RoleDataPermDto> GetRoleDataPermAsync(Guid roleId, CancellationToken cancellationToken = default)
|
||
{
|
||
var role = await dbContext.Database.SqlQueryRaw<RoleHeaderRow>("""
|
||
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<RoleOrgPermRow>("""
|
||
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<RoleDataScopeRow>("""
|
||
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<RoleBizPermRow>("""
|
||
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<RoleSecretPermRow>("""
|
||
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<RoleFilterRuleRow>("""
|
||
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<DataPermPagedResult<ChangeLogItemDto>> GetChangeLogsAsync(
|
||
string? targetType,
|
||
string? targetId,
|
||
int page,
|
||
int pageSize,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var where = "WHERE 1=1";
|
||
var args = new List<object>();
|
||
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<long>(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<ChangeLogRow>(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<ChangeLogItemDto> { Total = total, Items = items };
|
||
}
|
||
|
||
public async Task<List<FieldMaskItemDto>> 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<FieldMaskItemDto>(sql, dataResourceCode)
|
||
.ToListAsync(cancellationToken);
|
||
}
|
||
|
||
public async Task SaveFieldMasksAsync(string dataResourceCode, List<FieldMaskItemDto> 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<DataPermPagedResult<AccessLogItemDto>> 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<long>(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<AccessLogRow>(listSql)
|
||
.ToListAsync(cancellationToken);
|
||
return new DataPermPagedResult<AccessLogItemDto>
|
||
{
|
||
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<DataPermPagedResult<DataPermTemplateListItemDto>> GetTemplateListAsync(
|
||
string? keyword,
|
||
int page,
|
||
int pageSize,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var where = "WHERE t.`IsDeleted` = 0";
|
||
var args = new List<object>();
|
||
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<long>(
|
||
$"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<TemplateListRow>(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<DataPermTemplateListItemDto> { Total = total, Items = items };
|
||
}
|
||
|
||
public async Task<DataPermTemplateDetailDto> 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<TemplateHeaderRow>(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<TemplateItemRow>(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<Dictionary<string, object?>>(x.ItemContentJson, JsonOptions) ?? [],
|
||
SortOrder = x.SortOrder
|
||
}).ToList()
|
||
};
|
||
|
||
return header;
|
||
}
|
||
|
||
public async Task<DataPermTemplateDetailDto> 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<DataPermTemplateDetailDto> 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<DataPermTemplateItemDto> 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; }
|
||
}
|
||
}
|