refactor: 用户管理仅平台管理员
This commit is contained in:
@@ -57,10 +57,20 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
/// <returns>存在返回 true。</returns>
|
||||
public Task<bool> ExistsByAccountAsync(string account, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 标准化账号
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrWhiteSpace(account))
|
||||
{
|
||||
throw new ArgumentException("账号不能为空。", nameof(account));
|
||||
}
|
||||
|
||||
// 2. (空行后) 标准化账号
|
||||
var normalized = account.Trim();
|
||||
// 2. 查询是否存在
|
||||
return dbContext.IdentityUsers.AnyAsync(x => x.Account == normalized, cancellationToken);
|
||||
|
||||
// 3. (空行后) 查询是否存在(包含已删除数据)
|
||||
return dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.AnyAsync(x => x.Account == normalized, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +90,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.Account == normalized);
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Account == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
@@ -91,6 +101,47 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断账号是否存在(按 Portal 与租户范围精确匹配,可排除指定用户)。
|
||||
/// </summary>
|
||||
/// <param name="portal">Portal 类型。</param>
|
||||
/// <param name="tenantId">租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。</param>
|
||||
/// <param name="account">账号。</param>
|
||||
/// <param name="excludeUserId">排除的用户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>存在返回 true。</returns>
|
||||
public Task<bool> ExistsByAccountAsync(
|
||||
PortalType portal,
|
||||
long? tenantId,
|
||||
string account,
|
||||
long? excludeUserId = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrWhiteSpace(account))
|
||||
{
|
||||
throw new ArgumentException("账号不能为空。", nameof(account));
|
||||
}
|
||||
|
||||
// 2. (空行后) 校验 Portal 与 tenantId 组合
|
||||
ValidatePortalTenantId(portal, tenantId);
|
||||
|
||||
// 3. (空行后) 标准化账号并构建查询(包含已删除数据)
|
||||
var normalized = account.Trim();
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Portal == portal && x.TenantId == tenantId && x.Account == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Id != excludeUserId.Value);
|
||||
}
|
||||
|
||||
// 4. (空行后) 返回是否存在
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断手机号是否存在(租户内,可排除指定用户)。
|
||||
/// </summary>
|
||||
@@ -108,7 +159,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.Phone == normalized);
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Phone == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
@@ -119,6 +170,47 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断手机号是否存在(按 Portal 与租户范围精确匹配,可排除指定用户)。
|
||||
/// </summary>
|
||||
/// <param name="portal">Portal 类型。</param>
|
||||
/// <param name="tenantId">租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。</param>
|
||||
/// <param name="phone">手机号。</param>
|
||||
/// <param name="excludeUserId">排除的用户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>存在返回 true。</returns>
|
||||
public Task<bool> ExistsByPhoneAsync(
|
||||
PortalType portal,
|
||||
long? tenantId,
|
||||
string phone,
|
||||
long? excludeUserId = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrWhiteSpace(phone))
|
||||
{
|
||||
throw new ArgumentException("手机号不能为空。", nameof(phone));
|
||||
}
|
||||
|
||||
// 2. (空行后) 校验 Portal 与 tenantId 组合
|
||||
ValidatePortalTenantId(portal, tenantId);
|
||||
|
||||
// 3. (空行后) 标准化手机号并构建查询(包含已删除数据)
|
||||
var normalized = phone.Trim();
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Portal == portal && x.TenantId == tenantId && x.Phone == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Id != excludeUserId.Value);
|
||||
}
|
||||
|
||||
// 4. (空行后) 返回是否存在
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断邮箱是否存在(租户内,可排除指定用户)。
|
||||
/// </summary>
|
||||
@@ -136,7 +228,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.Email == normalized);
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && x.Email == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
@@ -147,6 +239,47 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断邮箱是否存在(按 Portal 与租户范围精确匹配,可排除指定用户)。
|
||||
/// </summary>
|
||||
/// <param name="portal">Portal 类型。</param>
|
||||
/// <param name="tenantId">租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。</param>
|
||||
/// <param name="email">邮箱。</param>
|
||||
/// <param name="excludeUserId">排除的用户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>存在返回 true。</returns>
|
||||
public Task<bool> ExistsByEmailAsync(
|
||||
PortalType portal,
|
||||
long? tenantId,
|
||||
string email,
|
||||
long? excludeUserId = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 参数校验
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
throw new ArgumentException("邮箱不能为空。", nameof(email));
|
||||
}
|
||||
|
||||
// 2. (空行后) 校验 Portal 与 tenantId 组合
|
||||
ValidatePortalTenantId(portal, tenantId);
|
||||
|
||||
// 3. (空行后) 标准化邮箱并构建查询(包含已删除数据)
|
||||
var normalized = email.Trim();
|
||||
var query = dbContext.IdentityUsers
|
||||
.IgnoreQueryFilters()
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Portal == portal && x.TenantId == tenantId && x.Email == normalized);
|
||||
|
||||
if (excludeUserId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Id != excludeUserId.Value);
|
||||
}
|
||||
|
||||
// 4. (空行后) 返回是否存在
|
||||
return query.AnyAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ID 获取后台用户。
|
||||
/// </summary>
|
||||
@@ -188,7 +321,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
// 1. 构建基础查询
|
||||
var query = dbContext.IdentityUsers
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId);
|
||||
|
||||
// 2. 关键字过滤
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
@@ -211,21 +344,20 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
IdentityUserSearchFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 构建基础查询
|
||||
var query = dbContext.IdentityUsers.AsNoTracking();
|
||||
// 1. 校验 Portal 与 tenantId 组合
|
||||
ValidatePortalTenantId(filter.Portal, filter.TenantId);
|
||||
|
||||
// 2. (空行后) 包含软删除数据时忽略全局过滤
|
||||
// 2. (空行后) 构建基础查询(按 Portal + TenantId 精确匹配)
|
||||
var query = dbContext.IdentityUsers
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Portal == filter.Portal && x.TenantId == filter.TenantId);
|
||||
|
||||
// 3. (空行后) 包含软删除数据时忽略全局过滤
|
||||
if (filter.IncludeDeleted)
|
||||
{
|
||||
query = query.IgnoreQueryFilters();
|
||||
}
|
||||
|
||||
// 3. (空行后) 可选租户过滤
|
||||
if (filter.TenantId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.TenantId == filter.TenantId.Value);
|
||||
}
|
||||
|
||||
// 4. (空行后) 关键字筛选
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
@@ -248,7 +380,9 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
if (filter.RoleId.HasValue)
|
||||
{
|
||||
var roleId = filter.RoleId.Value;
|
||||
var userRoles = dbContext.UserRoles.AsNoTracking();
|
||||
var userRoles = dbContext.UserRoles
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Portal == filter.Portal && x.TenantId == filter.TenantId);
|
||||
|
||||
// 6.1 包含软删除数据时忽略全局过滤
|
||||
if (filter.IncludeDeleted)
|
||||
@@ -256,13 +390,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
userRoles = userRoles.IgnoreQueryFilters();
|
||||
}
|
||||
|
||||
// 6.2 (空行后) 可选租户过滤
|
||||
if (filter.TenantId.HasValue)
|
||||
{
|
||||
userRoles = userRoles.Where(x => x.TenantId == filter.TenantId.Value);
|
||||
}
|
||||
|
||||
// 6.3 (空行后) 用户角色关联过滤
|
||||
// 6.2 (空行后) 用户角色关联过滤
|
||||
query = query.Where(user => userRoles.Any(role => role.UserId == user.Id && role.RoleId == roleId));
|
||||
}
|
||||
|
||||
@@ -338,7 +466,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
// 2. (空行后) 查询并返回列表
|
||||
return await dbContext.IdentityUsers
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && ids.Contains(x.Id))
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && ids.Contains(x.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -365,7 +493,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
|
||||
// 2. (空行后) 构建查询
|
||||
var query = dbContext.IdentityUsers
|
||||
.Where(x => x.TenantId == tenantId && ids.Contains(x.Id));
|
||||
.Where(x => x.Portal == PortalType.Tenant && x.TenantId == tenantId && ids.Contains(x.Id));
|
||||
|
||||
// 3. (空行后) 包含软删除数据时忽略全局过滤
|
||||
if (includeDeleted)
|
||||
@@ -377,6 +505,46 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量获取后台用户(可用于更新,按 Portal 与租户范围精确匹配,支持包含已删除数据)。
|
||||
/// </summary>
|
||||
/// <param name="portal">Portal 类型。</param>
|
||||
/// <param name="tenantId">租户 ID(Portal=Tenant 时必填;Portal=Admin 时必须为空)。</param>
|
||||
/// <param name="userIds">用户 ID 集合。</param>
|
||||
/// <param name="includeDeleted">是否包含已删除数据。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>后台用户列表。</returns>
|
||||
public async Task<IReadOnlyList<IdentityUser>> GetForUpdateByIdsAsync(
|
||||
PortalType portal,
|
||||
long? tenantId,
|
||||
IEnumerable<long> userIds,
|
||||
bool includeDeleted,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 去重并快速返回空集合
|
||||
var ids = userIds.Distinct().ToArray();
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
return Array.Empty<IdentityUser>();
|
||||
}
|
||||
|
||||
// 2. (空行后) 校验 Portal 与 tenantId 组合
|
||||
ValidatePortalTenantId(portal, tenantId);
|
||||
|
||||
// 3. (空行后) 构建查询
|
||||
var query = dbContext.IdentityUsers
|
||||
.Where(x => x.Portal == portal && x.TenantId == tenantId && ids.Contains(x.Id));
|
||||
|
||||
// 4. (空行后) 包含软删除数据时忽略全局过滤
|
||||
if (includeDeleted)
|
||||
{
|
||||
query = query.IgnoreQueryFilters();
|
||||
}
|
||||
|
||||
// 5. (空行后) 返回列表
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增后台用户。
|
||||
/// </summary>
|
||||
@@ -405,6 +573,25 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Portal 与 tenantId 参数校验。
|
||||
private static void ValidatePortalTenantId(PortalType portal, long? tenantId)
|
||||
{
|
||||
// 1. 按 Portal 规则校验 tenantId
|
||||
switch (portal)
|
||||
{
|
||||
case PortalType.Admin when tenantId is null:
|
||||
return;
|
||||
case PortalType.Admin:
|
||||
throw new ArgumentException("Portal=Admin 时 tenantId 必须为空。", nameof(tenantId));
|
||||
case PortalType.Tenant when tenantId.HasValue && tenantId.Value > 0:
|
||||
return;
|
||||
case PortalType.Tenant:
|
||||
throw new ArgumentException("Portal=Tenant 时必须指定 tenantId。", nameof(tenantId));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(portal), portal, "未知 Portal 类型。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 持久化仓储变更。
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user