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