From b3d611304b632942d3f30422b39601ebc38104c6 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 29 Jan 2026 13:48:31 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E8=B7=A8=E7=A7=9F=E6=88=B7=E5=BF=BD=E7=95=A5=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...atchIdentityUserOperationCommandHandler.cs | 4 +- .../ChangeIdentityUserStatusCommandHandler.cs | 2 +- .../DeleteIdentityUserCommandHandler.cs | 2 +- .../GetIdentityUserDetailQueryHandler.cs | 2 +- ...ResetAdminPasswordByTokenCommandHandler.cs | 4 +- .../RestoreIdentityUserCommandHandler.cs | 2 +- .../SearchIdentityUsersQueryHandler.cs | 2 +- .../Repositories/IIdentityUserRepository.cs | 32 ----- .../Persistence/EfIdentityUserRepository.cs | 115 ++++-------------- 9 files changed, 36 insertions(+), 129 deletions(-) diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs index 1ad6573..1d88f67 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/BatchIdentityUserOperationCommandHandler.cs @@ -57,7 +57,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( // 4. 查询目标用户集合 var includeDeleted = request.Operation == IdentityUserBatchOperation.Restore; - var users = await identityUserRepository.GetForUpdateByIdsAsync(tenantId, userIds, includeDeleted, false, cancellationToken); + var users = await identityUserRepository.GetForUpdateByIdsAsync(tenantId, userIds, includeDeleted, cancellationToken); var usersById = users.ToDictionary(user => user.Id, user => user, EqualityComparer.Default); // 5. 预计算租户管理员约束 @@ -79,7 +79,7 @@ public sealed class BatchIdentityUserOperationCommandHandler( IncludeDeleted = false, Page = 1, PageSize = 1 - }, false, cancellationToken)).Total; + }, cancellationToken)).Total; var remainingActiveAdmins = activeAdminCount; // 6. 执行批量操作 diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs index 41d5135..4028bb6 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ChangeIdentityUserStatusCommandHandler.cs @@ -137,7 +137,7 @@ public sealed class ChangeIdentityUserStatusCommandHandler( Page = 1, PageSize = 1 }; - var result = await identityUserRepository.SearchPagedAsync(filter, false, cancellationToken); + var result = await identityUserRepository.SearchPagedAsync(filter, cancellationToken); if (result.Total <= 1) { throw new BusinessException(ErrorCodes.Conflict, "至少保留一个管理员"); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs index e874644..644ecee 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteIdentityUserCommandHandler.cs @@ -111,7 +111,7 @@ public sealed class DeleteIdentityUserCommandHandler( Page = 1, PageSize = 1 }; - var result = await identityUserRepository.SearchPagedAsync(filter, false, cancellationToken); + var result = await identityUserRepository.SearchPagedAsync(filter, cancellationToken); if (result.Total <= 1) { throw new BusinessException(ErrorCodes.Conflict, "至少保留一个管理员"); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs index ef73439..07ec79f 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/GetIdentityUserDetailQueryHandler.cs @@ -30,7 +30,7 @@ public sealed class GetIdentityUserDetailQueryHandler( IdentityUser? user; if (request.IncludeDeleted) { - user = await identityUserRepository.GetForUpdateIncludingDeletedAsync(currentTenantId, request.UserId, false, cancellationToken); + user = await identityUserRepository.GetForUpdateIncludingDeletedAsync(currentTenantId, request.UserId, cancellationToken); } else { diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs index ad9bc80..cd7a0a5 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ResetAdminPasswordByTokenCommandHandler.cs @@ -47,8 +47,8 @@ public sealed class ResetAdminPasswordByTokenCommandHandler( throw new BusinessException(ErrorCodes.BadRequest, "重置链接无效或已过期"); } - // 3. 获取用户(可更新,忽略租户过滤器)并写入新密码哈希 - var user = await userRepository.GetForUpdateIgnoringTenantAsync(userId.Value, cancellationToken) + // 3. 获取用户(可更新,强制租户隔离)并写入新密码哈希 + var user = await userRepository.GetForUpdateAsync(userId.Value, cancellationToken) ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在"); user.PasswordHash = passwordHasher.HashPassword(user, password); diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs index 5d70955..54daabb 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/RestoreIdentityUserCommandHandler.cs @@ -36,7 +36,7 @@ public sealed class RestoreIdentityUserCommandHandler( } // 3. 查询用户实体(包含已删除) - var user = await identityUserRepository.GetForUpdateIncludingDeletedAsync(currentTenantId, request.UserId, false, cancellationToken); + var user = await identityUserRepository.GetForUpdateIncludingDeletedAsync(currentTenantId, request.UserId, cancellationToken); if (user == null) { return false; diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs index 95717cc..0ce96c6 100644 --- a/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/SearchIdentityUsersQueryHandler.cs @@ -52,7 +52,7 @@ public sealed class SearchIdentityUsersQueryHandler( }; // 4. 执行分页查询 - var (items, total) = await identityUserRepository.SearchPagedAsync(filter, false, cancellationToken); + var (items, total) = await identityUserRepository.SearchPagedAsync(filter, cancellationToken); if (items.Count == 0) { return new PagedResult(Array.Empty(), request.Page, request.PageSize, total); diff --git a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs index c354be7..fb45b38 100644 --- a/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Identity/Repositories/IIdentityUserRepository.cs @@ -8,15 +8,6 @@ namespace TakeoutSaaS.Domain.Identity.Repositories; /// public interface IIdentityUserRepository { - /// - /// 根据账号获取后台用户。 - /// - /// 为保证多租户隔离,优先使用带租户参数的重载方法。 - /// 账号。 - /// 取消标记。 - /// 后台用户或 null。 - Task FindByAccountAsync(string account, CancellationToken cancellationToken = default); - /// /// 根据租户与账号获取后台用户。 /// @@ -84,14 +75,6 @@ public interface IIdentityUserRepository /// 后台用户或 null。 Task FindByIdAsync(long userId, CancellationToken cancellationToken = default); - /// - /// 根据 ID 获取后台用户(忽略租户过滤器,仅用于只读查询)。 - /// - /// 用户 ID。 - /// 取消标记。 - /// 后台用户或 null。 - Task FindByIdIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default); - /// /// 根据 ID 获取后台用户(用于更新,返回可跟踪实体)。 /// @@ -100,27 +83,16 @@ public interface IIdentityUserRepository /// 后台用户或 null。 Task GetForUpdateAsync(long userId, CancellationToken cancellationToken = default); - /// - /// 根据 ID 获取后台用户(用于更新,忽略租户过滤器)。 - /// - /// 用于跨租户场景(如平台生成的重置密码链接)。 - /// 用户 ID。 - /// 取消标记。 - /// 后台用户或 null。 - Task GetForUpdateIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default); - /// /// 根据 ID 获取后台用户(用于更新,包含已删除数据)。 /// /// 租户 ID。 /// 用户 ID。 - /// 是否忽略租户过滤。 /// 取消标记。 /// 后台用户或 null。 Task GetForUpdateIncludingDeletedAsync( long tenantId, long userId, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default); /// @@ -136,12 +108,10 @@ public interface IIdentityUserRepository /// 分页查询后台用户列表。 /// /// 查询过滤条件。 - /// 是否忽略租户过滤器。 /// 取消标记。 /// 分页结果。 Task<(IReadOnlyList Items, int Total)> SearchPagedAsync( IdentityUserSearchFilter filter, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default); /// @@ -159,14 +129,12 @@ public interface IIdentityUserRepository /// 租户 ID。 /// 用户 ID 集合。 /// 是否包含已删除数据。 - /// 是否忽略租户过滤器。 /// 取消标记。 /// 后台用户列表。 Task> GetForUpdateByIdsAsync( long tenantId, IEnumerable userIds, bool includeDeleted, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default); /// diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfIdentityUserRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfIdentityUserRepository.cs index 8e78b18..c7718fa 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfIdentityUserRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/Identity/Persistence/EfIdentityUserRepository.cs @@ -9,15 +9,6 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence; /// public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIdentityUserRepository { - /// - /// 根据账号获取后台用户。 - /// - /// 账号。 - /// 取消标记。 - /// 后台用户或 null。 - public Task FindByAccountAsync(string account, CancellationToken cancellationToken = default) - => dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Account == account, cancellationToken); - /// /// 根据租户与账号获取后台用户。 /// @@ -143,19 +134,6 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde public Task FindByIdAsync(long userId, CancellationToken cancellationToken = default) => dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); - /// - /// 根据 ID 获取后台用户(忽略租户过滤器,仅用于只读查询)。 - /// - /// 用户 ID。 - /// 取消标记。 - /// 后台用户或 null。 - public Task FindByIdIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default) - => dbContext.IdentityUsers - .IgnoreQueryFilters() - .AsNoTracking() - .Where(x => x.DeletedAt == null) - .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); - /// /// 根据 ID 获取后台用户(用于更新,返回可跟踪实体)。 /// @@ -165,42 +143,23 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde public Task GetForUpdateAsync(long userId, CancellationToken cancellationToken = default) => dbContext.IdentityUsers.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); - /// - /// 根据 ID 获取后台用户(用于更新,忽略租户过滤器)。 - /// - /// 用于跨租户场景(如平台生成的重置密码链接)。 - /// 用户 ID。 - /// 取消标记。 - /// 后台用户或 null。 - public Task GetForUpdateIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default) - => dbContext.IdentityUsers - .IgnoreQueryFilters() - .Where(x => x.DeletedAt == null) - .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); - /// /// 根据 ID 获取后台用户(用于更新,包含已删除数据)。 /// /// 租户 ID。 /// 用户 ID。 - /// 是否忽略租户过滤。 /// 取消标记。 /// 后台用户或 null。 public Task GetForUpdateIncludingDeletedAsync( long tenantId, long userId, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default) { - // 1. 构建查询(包含已删除数据) - var query = dbContext.IdentityUsers.IgnoreQueryFilters(); - if (!ignoreTenantFilter) - { - query = query.Where(x => x.TenantId == tenantId); - } - - // 2. 返回实体 - return query.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); + // 1. 构建查询(包含已删除数据,但强制租户隔离) + return dbContext.IdentityUsers + .IgnoreQueryFilters() + .Where(x => x.TenantId == tenantId) + .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); } /// @@ -232,33 +191,28 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde /// 分页查询后台用户列表。 /// /// 查询过滤条件。 - /// 是否忽略租户过滤器。 /// 取消标记。 /// 分页结果。 public async Task<(IReadOnlyList Items, int Total)> SearchPagedAsync( IdentityUserSearchFilter filter, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default) { + if (!filter.TenantId.HasValue || filter.TenantId.Value <= 0) + { + throw new InvalidOperationException("TenantId 不能为空且必须大于 0"); + } + + var tenantId = filter.TenantId.Value; + // 1. 构建基础查询 var query = dbContext.IdentityUsers.AsNoTracking(); - if (ignoreTenantFilter || filter.IncludeDeleted) + if (filter.IncludeDeleted) { query = query.IgnoreQueryFilters(); } - // 2. 租户过滤 - if (!ignoreTenantFilter) - { - if (filter.TenantId.HasValue && filter.TenantId.Value != 0) - { - query = query.Where(x => x.TenantId == filter.TenantId.Value); - } - } - else if (filter.TenantId.HasValue && filter.TenantId.Value != 0) - { - query = query.Where(x => x.TenantId == filter.TenantId.Value); - } + // 2. 租户过滤(强制) + query = query.Where(x => x.TenantId == tenantId); if (!filter.IncludeDeleted) { @@ -288,22 +242,12 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde { var roleId = filter.RoleId.Value; var userRoles = dbContext.UserRoles.AsNoTracking(); - if (ignoreTenantFilter || filter.IncludeDeleted) + if (filter.IncludeDeleted) { userRoles = userRoles.IgnoreQueryFilters(); } - if (!ignoreTenantFilter) - { - if (filter.TenantId.HasValue && filter.TenantId.Value != 0) - { - userRoles = userRoles.Where(x => x.TenantId == filter.TenantId.Value); - } - } - else if (filter.TenantId.HasValue && filter.TenantId.Value != 0) - { - userRoles = userRoles.Where(x => x.TenantId == filter.TenantId.Value); - } + userRoles = userRoles.Where(x => x.TenantId == tenantId); if (!filter.IncludeDeleted) { @@ -373,11 +317,12 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde /// 用户 ID 集合。 /// 取消标记。 /// 后台用户列表。 - public Task> GetByIdsAsync(long tenantId, IEnumerable userIds, CancellationToken cancellationToken = default) - => dbContext.IdentityUsers.AsNoTracking() + public async Task> GetByIdsAsync(long tenantId, IEnumerable userIds, CancellationToken cancellationToken = default) + { + return await dbContext.IdentityUsers.AsNoTracking() .Where(x => x.TenantId == tenantId && userIds.Contains(x.Id)) - .ToListAsync(cancellationToken) - .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); + .ToListAsync(cancellationToken); + } /// /// 批量获取后台用户(可用于更新,支持包含已删除数据)。 @@ -385,33 +330,28 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde /// 租户 ID。 /// 用户 ID 集合。 /// 是否包含已删除数据。 - /// 是否忽略租户过滤器。 /// 取消标记。 /// 后台用户列表。 - public Task> GetForUpdateByIdsAsync( + public async Task> GetForUpdateByIdsAsync( long tenantId, IEnumerable userIds, bool includeDeleted, - bool ignoreTenantFilter = false, CancellationToken cancellationToken = default) { // 1. 构建基础查询 var ids = userIds.Distinct().ToArray(); if (ids.Length == 0) { - return Task.FromResult>(Array.Empty()); + return Array.Empty(); } var query = dbContext.IdentityUsers.Where(x => ids.Contains(x.Id)); - if (ignoreTenantFilter || includeDeleted) + if (includeDeleted) { query = query.IgnoreQueryFilters(); } - if (!ignoreTenantFilter) - { - query = query.Where(x => x.TenantId == tenantId); - } + query = query.Where(x => x.TenantId == tenantId); if (!includeDeleted) { @@ -419,8 +359,7 @@ public sealed class EfIdentityUserRepository(IdentityDbContext dbContext) : IIde } // 2. 返回列表 - return query.ToListAsync(cancellationToken) - .ContinueWith(t => (IReadOnlyList)t.Result, cancellationToken); + return await query.ToListAsync(cancellationToken); } ///