admin/src/Oms.Application/DataPerm/DataPermStore.cs
zzh e0216e9692 feat: 销售产品贸易条款与查价分组展示,并修复用户资料 SQL 空值写入
查价返回 DDP/DDU 与产品分组;用户更新微信二维码时避免 EF 使用 DBNull。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 11:48:36 +08:00

977 lines
39 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
}
}