using Microsoft.EntityFrameworkCore; using TakeoutSaaS.Domain.Identity.Entities; using TakeoutSaaS.Domain.Identity.Repositories; namespace TakeoutSaaS.Infrastructure.Identity.Persistence; /// /// EF Core 后台用户仓储实现。 /// 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); /// /// 判断账号是否存在。 /// /// 账号。 /// 取消标记。 /// 存在返回 true。 public Task ExistsByAccountAsync(string account, CancellationToken cancellationToken = default) { // 1. 标准化账号 var normalized = account.Trim(); // 2. 查询是否存在 return dbContext.IdentityUsers.AnyAsync(x => x.Account == normalized, cancellationToken); } /// /// 判断账号是否存在(租户内,可排除指定用户)。 /// /// 租户 ID。 /// 账号。 /// 排除的用户 ID。 /// 取消标记。 /// 存在返回 true。 public Task ExistsByAccountAsync(long tenantId, string account, long? excludeUserId = null, CancellationToken cancellationToken = default) { // 1. 标准化账号 var normalized = account.Trim(); // 2. 构建查询(包含已删除数据) var query = dbContext.IdentityUsers .IgnoreQueryFilters() .AsNoTracking() .Where(x => x.TenantId == tenantId && x.Account == normalized); if (excludeUserId.HasValue) { query = query.Where(x => x.Id != excludeUserId.Value); } // 3. 返回是否存在 return query.AnyAsync(cancellationToken); } /// /// 判断手机号是否存在(租户内,可排除指定用户)。 /// /// 租户 ID。 /// 手机号。 /// 排除的用户 ID。 /// 取消标记。 /// 存在返回 true。 public Task ExistsByPhoneAsync(long tenantId, string phone, long? excludeUserId = null, CancellationToken cancellationToken = default) { // 1. 标准化手机号 var normalized = phone.Trim(); // 2. 构建查询(包含已删除数据) var query = dbContext.IdentityUsers .IgnoreQueryFilters() .AsNoTracking() .Where(x => x.TenantId == tenantId && x.Phone == normalized); if (excludeUserId.HasValue) { query = query.Where(x => x.Id != excludeUserId.Value); } // 3. 返回是否存在 return query.AnyAsync(cancellationToken); } /// /// 判断邮箱是否存在(租户内,可排除指定用户)。 /// /// 租户 ID。 /// 邮箱。 /// 排除的用户 ID。 /// 取消标记。 /// 存在返回 true。 public Task ExistsByEmailAsync(long tenantId, string email, long? excludeUserId = null, CancellationToken cancellationToken = default) { // 1. 标准化邮箱 var normalized = email.Trim(); // 2. 构建查询(包含已删除数据) var query = dbContext.IdentityUsers .IgnoreQueryFilters() .AsNoTracking() .Where(x => x.TenantId == tenantId && x.Email == normalized); if (excludeUserId.HasValue) { query = query.Where(x => x.Id != excludeUserId.Value); } // 3. 返回是否存在 return query.AnyAsync(cancellationToken); } /// /// 根据 ID 获取后台用户。 /// /// 用户 ID。 /// 取消标记。 /// 后台用户或 null。 public Task FindByIdAsync(long userId, CancellationToken cancellationToken = default) => dbContext.IdentityUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); /// /// 根据 ID 获取后台用户(用于更新,返回可跟踪实体)。 /// /// 用户 ID。 /// 取消标记。 /// 后台用户或 null。 public Task GetForUpdateAsync(long userId, CancellationToken cancellationToken = default) => dbContext.IdentityUsers.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); /// /// 根据 ID 获取后台用户(用于更新,包含已删除数据)。 /// /// 用户 ID。 /// 取消标记。 /// 后台用户或 null。 public Task GetForUpdateIncludingDeletedAsync(long userId, CancellationToken cancellationToken = default) => dbContext.IdentityUsers .IgnoreQueryFilters() .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken); /// /// 按租户与关键字搜索后台用户(只读)。 /// /// 租户 ID。 /// 关键字(账号/名称)。 /// 取消标记。 /// 后台用户列表。 public async Task> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default) { // 1. 构建基础查询 var query = dbContext.IdentityUsers .AsNoTracking() .Where(x => x.TenantId == tenantId); // 2. 关键字过滤 if (!string.IsNullOrWhiteSpace(keyword)) { var normalized = keyword.Trim(); query = query.Where(x => x.Account.Contains(normalized) || x.DisplayName.Contains(normalized)); } // 3. 返回列表 return await query.ToListAsync(cancellationToken); } /// /// 分页查询后台用户列表。 /// /// 查询过滤条件。 /// 取消标记。 /// 分页结果。 public async Task<(IReadOnlyList Items, int Total)> SearchPagedAsync( IdentityUserSearchFilter filter, CancellationToken cancellationToken = default) { // 1. 构建基础查询 var query = dbContext.IdentityUsers.AsNoTracking(); // 2. (空行后) 包含软删除数据时忽略全局过滤 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)) { var normalized = filter.Keyword.Trim(); var likeValue = $"%{normalized}%"; query = query.Where(x => EF.Functions.ILike(x.Account, likeValue) || EF.Functions.ILike(x.DisplayName, likeValue) || (x.Phone != null && EF.Functions.ILike(x.Phone, likeValue)) || (x.Email != null && EF.Functions.ILike(x.Email, likeValue))); } // 5. (空行后) 状态过滤 if (filter.Status.HasValue) { query = query.Where(x => x.Status == filter.Status.Value); } // 6. (空行后) 角色过滤 if (filter.RoleId.HasValue) { var roleId = filter.RoleId.Value; var userRoles = dbContext.UserRoles.AsNoTracking(); // 6.1 包含软删除数据时忽略全局过滤 if (filter.IncludeDeleted) { userRoles = userRoles.IgnoreQueryFilters(); } // 6.2 (空行后) 可选租户过滤 if (filter.TenantId.HasValue) { userRoles = userRoles.Where(x => x.TenantId == filter.TenantId.Value); } // 6.3 (空行后) 用户角色关联过滤 query = query.Where(user => userRoles.Any(role => role.UserId == user.Id && role.RoleId == roleId)); } // 7. (空行后) 时间范围过滤 if (filter.CreatedAtFrom.HasValue) { query = query.Where(x => x.CreatedAt >= filter.CreatedAtFrom.Value); } if (filter.CreatedAtTo.HasValue) { query = query.Where(x => x.CreatedAt <= filter.CreatedAtTo.Value); } if (filter.LastLoginFrom.HasValue) { query = query.Where(x => x.LastLoginAt >= filter.LastLoginFrom.Value); } if (filter.LastLoginTo.HasValue) { query = query.Where(x => x.LastLoginAt <= filter.LastLoginTo.Value); } // 8. (空行后) 排序 var sorted = filter.SortBy?.ToLowerInvariant() switch { "account" => filter.SortDescending ? query.OrderByDescending(x => x.Account) : query.OrderBy(x => x.Account), "displayname" => filter.SortDescending ? query.OrderByDescending(x => x.DisplayName) : query.OrderBy(x => x.DisplayName), "status" => filter.SortDescending ? query.OrderByDescending(x => x.Status) : query.OrderBy(x => x.Status), "lastloginat" => filter.SortDescending ? query.OrderByDescending(x => x.LastLoginAt) : query.OrderBy(x => x.LastLoginAt), _ => filter.SortDescending ? query.OrderByDescending(x => x.CreatedAt) : query.OrderBy(x => x.CreatedAt) }; // 9. (空行后) 分页 var page = filter.Page <= 0 ? 1 : filter.Page; var pageSize = filter.PageSize <= 0 ? 20 : filter.PageSize; var total = await sorted.CountAsync(cancellationToken); var items = await sorted .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(cancellationToken); return (items, total); } /// /// 根据 ID 集合批量获取后台用户(只读)。 /// /// 租户 ID。 /// 用户 ID 集合。 /// 取消标记。 /// 后台用户列表。 public async Task> GetByIdsAsync(long tenantId, IEnumerable userIds, CancellationToken cancellationToken = default) { // 1. 去重并快速返回空集合 var ids = userIds.Distinct().ToArray(); if (ids.Length == 0) { return Array.Empty(); } // 2. (空行后) 查询并返回列表 return await dbContext.IdentityUsers .AsNoTracking() .Where(x => x.TenantId == tenantId && ids.Contains(x.Id)) .ToListAsync(cancellationToken); } /// /// 批量获取后台用户(可用于更新,支持包含已删除数据)。 /// /// 租户 ID。 /// 用户 ID 集合。 /// 是否包含已删除数据。 /// 取消标记。 /// 后台用户列表。 public async Task> GetForUpdateByIdsAsync( long tenantId, IEnumerable userIds, bool includeDeleted, CancellationToken cancellationToken = default) { // 1. 去重并快速返回空集合 var ids = userIds.Distinct().ToArray(); if (ids.Length == 0) { return Array.Empty(); } // 2. (空行后) 构建查询 var query = dbContext.IdentityUsers .Where(x => x.TenantId == tenantId && ids.Contains(x.Id)); // 3. (空行后) 包含软删除数据时忽略全局过滤 if (includeDeleted) { query = query.IgnoreQueryFilters(); } // 4. (空行后) 返回列表 return await query.ToListAsync(cancellationToken); } /// /// 新增后台用户。 /// /// 后台用户实体。 /// 取消标记。 /// 异步任务。 public Task AddAsync(IdentityUser user, CancellationToken cancellationToken = default) { // 1. 添加实体 dbContext.IdentityUsers.Add(user); // 2. 返回完成任务 return Task.CompletedTask; } /// /// 删除后台用户(软删除)。 /// /// 后台用户实体。 /// 取消标记。 /// 异步任务。 public Task RemoveAsync(IdentityUser user, CancellationToken cancellationToken = default) { // 1. 标记删除 dbContext.IdentityUsers.Remove(user); // 2. 返回完成任务 return Task.CompletedTask; } /// /// 持久化仓储变更。 /// /// 取消标记。 /// 保存任务。 public Task SaveChangesAsync(CancellationToken cancellationToken = default) => dbContext.SaveChangesAsync(cancellationToken); }