feat: 用户管理后端与日志库迁移

This commit is contained in:
2025-12-27 06:23:03 +08:00
parent 0ff2794667
commit b2a90cf8af
57 changed files with 4117 additions and 33 deletions

View File

@@ -1,3 +1,4 @@
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Identity.Entities;
@@ -22,6 +23,41 @@ public sealed class IdentityUser : MultiTenantEntityBase
/// </summary>
public string PasswordHash { get; set; } = string.Empty;
/// <summary>
/// 手机号(租户内唯一)。
/// </summary>
public string? Phone { get; set; }
/// <summary>
/// 邮箱(租户内唯一)。
/// </summary>
public string? Email { get; set; }
/// <summary>
/// 账号状态。
/// </summary>
public IdentityUserStatus Status { get; set; } = IdentityUserStatus.Active;
/// <summary>
/// 登录失败次数。
/// </summary>
public int FailedLoginCount { get; set; }
/// <summary>
/// 锁定截止时间UTC
/// </summary>
public DateTime? LockedUntil { get; set; }
/// <summary>
/// 最近登录时间UTC
/// </summary>
public DateTime? LastLoginAt { get; set; }
/// <summary>
/// 是否强制修改密码。
/// </summary>
public bool MustChangePassword { get; set; }
/// <summary>
/// 所属商户(平台管理员为空)。
/// </summary>
@@ -31,4 +67,9 @@ public sealed class IdentityUser : MultiTenantEntityBase
/// 头像地址。
/// </summary>
public string? Avatar { get; set; }
/// <summary>
/// 并发控制字段。
/// </summary>
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}

View File

@@ -0,0 +1,22 @@
namespace TakeoutSaaS.Domain.Identity.Enums;
/// <summary>
/// 后台账号状态。
/// </summary>
public enum IdentityUserStatus
{
/// <summary>
/// 正常启用。
/// </summary>
Active = 1,
/// <summary>
/// 已禁用。
/// </summary>
Disabled = 2,
/// <summary>
/// 已锁定。
/// </summary>
Locked = 3
}

View File

@@ -1,4 +1,5 @@
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
namespace TakeoutSaaS.Domain.Identity.Repositories;
@@ -23,6 +24,48 @@ public interface IIdentityUserRepository
/// <returns>存在返回 true。</returns>
Task<bool> ExistsByAccountAsync(string account, CancellationToken cancellationToken = default);
/// <summary>
/// 判断账号是否存在(租户内,可排除指定用户)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="account">账号。</param>
/// <param name="excludeUserId">排除的用户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>存在返回 true。</returns>
Task<bool> ExistsByAccountAsync(
long tenantId,
string account,
long? excludeUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 判断手机号是否存在(租户内,可排除指定用户)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="phone">手机号。</param>
/// <param name="excludeUserId">排除的用户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>存在返回 true。</returns>
Task<bool> ExistsByPhoneAsync(
long tenantId,
string phone,
long? excludeUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 判断邮箱是否存在(租户内,可排除指定用户)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="email">邮箱。</param>
/// <param name="excludeUserId">排除的用户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>存在返回 true。</returns>
Task<bool> ExistsByEmailAsync(
long tenantId,
string email,
long? excludeUserId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 根据 ID 获取后台用户。
/// </summary>
@@ -31,6 +74,14 @@ public interface IIdentityUserRepository
/// <returns>后台用户或 null。</returns>
Task<IdentityUser?> FindByIdAsync(long userId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据 ID 获取后台用户(忽略租户过滤器,仅用于只读查询)。
/// </summary>
/// <param name="userId">用户 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>后台用户或 null。</returns>
Task<IdentityUser?> FindByIdIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据 ID 获取后台用户(用于更新,返回可跟踪实体)。
/// </summary>
@@ -48,6 +99,20 @@ public interface IIdentityUserRepository
/// <returns>后台用户或 null。</returns>
Task<IdentityUser?> GetForUpdateIgnoringTenantAsync(long userId, CancellationToken cancellationToken = default);
/// <summary>
/// 根据 ID 获取后台用户(用于更新,包含已删除数据)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="userId">用户 ID。</param>
/// <param name="ignoreTenantFilter">是否忽略租户过滤。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>后台用户或 null。</returns>
Task<IdentityUser?> GetForUpdateIncludingDeletedAsync(
long tenantId,
long userId,
bool ignoreTenantFilter = false,
CancellationToken cancellationToken = default);
/// <summary>
/// 按租户与关键字查询后台用户列表(仅读)。
/// </summary>
@@ -57,6 +122,18 @@ public interface IIdentityUserRepository
/// <returns>后台用户列表。</returns>
Task<IReadOnlyList<IdentityUser>> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default);
/// <summary>
/// 分页查询后台用户列表。
/// </summary>
/// <param name="filter">查询过滤条件。</param>
/// <param name="ignoreTenantFilter">是否忽略租户过滤器。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>分页结果。</returns>
Task<(IReadOnlyList<IdentityUser> Items, int Total)> SearchPagedAsync(
IdentityUserSearchFilter filter,
bool ignoreTenantFilter = false,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取指定租户、用户集合对应的用户(只读)。
/// </summary>
@@ -66,6 +143,22 @@ public interface IIdentityUserRepository
/// <returns>后台用户列表。</returns>
Task<IReadOnlyList<IdentityUser>> GetByIdsAsync(long tenantId, IEnumerable<long> userIds, CancellationToken cancellationToken = default);
/// <summary>
/// 批量获取后台用户(可用于更新,支持包含已删除数据)。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="userIds">用户 ID 集合。</param>
/// <param name="includeDeleted">是否包含已删除数据。</param>
/// <param name="ignoreTenantFilter">是否忽略租户过滤器。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>后台用户列表。</returns>
Task<IReadOnlyList<IdentityUser>> GetForUpdateByIdsAsync(
long tenantId,
IEnumerable<long> userIds,
bool includeDeleted,
bool ignoreTenantFilter = false,
CancellationToken cancellationToken = default);
/// <summary>
/// 新增后台用户。
/// </summary>
@@ -74,6 +167,14 @@ public interface IIdentityUserRepository
/// <returns>异步操作任务。</returns>
Task AddAsync(IdentityUser user, CancellationToken cancellationToken = default);
/// <summary>
/// 删除后台用户(软删除)。
/// </summary>
/// <param name="user">后台用户实体。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步操作任务。</returns>
Task RemoveAsync(IdentityUser user, CancellationToken cancellationToken = default);
/// <summary>
/// 持久化仓储变更。
/// </summary>
@@ -81,3 +182,74 @@ public interface IIdentityUserRepository
/// <returns>异步操作任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
/// <summary>
/// 后台用户查询过滤条件。
/// </summary>
public sealed record IdentityUserSearchFilter
{
/// <summary>
/// 租户 ID。
/// </summary>
public long? TenantId { get; init; }
/// <summary>
/// 关键字(账号/姓名/手机号/邮箱)。
/// </summary>
public string? Keyword { get; init; }
/// <summary>
/// 用户状态。
/// </summary>
public IdentityUserStatus? Status { get; init; }
/// <summary>
/// 角色 ID。
/// </summary>
public long? RoleId { get; init; }
/// <summary>
/// 创建开始时间UTC
/// </summary>
public DateTime? CreatedAtFrom { get; init; }
/// <summary>
/// 创建结束时间UTC
/// </summary>
public DateTime? CreatedAtTo { get; init; }
/// <summary>
/// 最近登录开始时间UTC
/// </summary>
public DateTime? LastLoginFrom { get; init; }
/// <summary>
/// 最近登录结束时间UTC
/// </summary>
public DateTime? LastLoginTo { get; init; }
/// <summary>
/// 是否包含已删除数据。
/// </summary>
public bool IncludeDeleted { get; init; }
/// <summary>
/// 页码(从 1 开始)。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20;
/// <summary>
/// 排序字段。
/// </summary>
public string? SortBy { get; init; }
/// <summary>
/// 是否降序。
/// </summary>
public bool SortDescending { get; init; } = true;
}

View File

@@ -35,6 +35,15 @@ public interface IUserRoleRepository
/// <returns>异步操作任务。</returns>
Task ReplaceUserRolesAsync(long tenantId, long userId, IEnumerable<long> roleIds, CancellationToken cancellationToken = default);
/// <summary>
/// 统计指定角色下的用户数量。
/// </summary>
/// <param name="tenantId">租户 ID。</param>
/// <param name="roleId">角色 ID。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>用户数量。</returns>
Task<int> CountUsersByRoleAsync(long tenantId, long roleId, CancellationToken cancellationToken = default);
/// <summary>
/// 提交持久化变更。
/// </summary>

View File

@@ -0,0 +1,24 @@
using TakeoutSaaS.Domain.Tenants.Entities;
namespace TakeoutSaaS.Domain.Tenants.Repositories;
/// <summary>
/// 运营操作日志仓储。
/// </summary>
public interface IOperationLogRepository
{
/// <summary>
/// 新增操作日志。
/// </summary>
/// <param name="log">操作日志。</param>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task AddAsync(OperationLog log, CancellationToken cancellationToken = default);
/// <summary>
/// 保存仓储变更。
/// </summary>
/// <param name="cancellationToken">取消标记。</param>
/// <returns>异步任务。</returns>
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}