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

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Identity;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
@@ -50,14 +51,40 @@ public sealed class AdminAuthService(
var user = await userRepository.FindByAccountAsync(request.Account, cancellationToken)
?? throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
// 2. 验证密码(使用 ASP.NET Core Identity 的密码哈希器)
// 2. 校验账号状态
var now = DateTime.UtcNow;
if (user.Status == IdentityUserStatus.Disabled)
{
throw new BusinessException(ErrorCodes.Forbidden, "账号已被禁用,请联系管理员");
}
if (user.Status == IdentityUserStatus.Locked)
{
if (user.LockedUntil.HasValue && user.LockedUntil.Value <= now)
{
await ResetLockedUserAsync(user.Id, cancellationToken);
}
else
{
throw new BusinessException(ErrorCodes.Forbidden, "账号已被锁定,请稍后再试");
}
}
if (user.MustChangePassword)
{
throw new BusinessException(ErrorCodes.Forbidden, "账号需要重置密码,请通过重置链接设置新密码");
}
// 3. 验证密码(使用 ASP.NET Core Identity 的密码哈希器)
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
if (result == PasswordVerificationResult.Failed)
{
await IncreaseFailedLoginAsync(user.Id, now, cancellationToken);
throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
}
// 3. 构建用户档案并生成令牌
// 4. (空行后) 更新登录成功状态
await UpdateLoginSuccessAsync(user.Id, now, cancellationToken);
// 5. (空行后) 构建用户档案并生成令牌
var profile = await BuildProfileAsync(user, cancellationToken);
return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken);
}
@@ -273,6 +300,67 @@ public sealed class AdminAuthService(
};
}
private async Task ResetLockedUserAsync(long userId, CancellationToken cancellationToken)
{
// 1. 获取可更新实体
var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
if (tracked == null)
{
return;
}
// 2. 解除锁定并清空失败次数
tracked.Status = IdentityUserStatus.Active;
tracked.LockedUntil = null;
tracked.FailedLoginCount = 0;
// 3. 保存变更
await userRepository.SaveChangesAsync(cancellationToken);
}
private async Task IncreaseFailedLoginAsync(long userId, DateTime now, CancellationToken cancellationToken)
{
// 1. 获取可更新实体
var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
if (tracked == null)
{
return;
}
// 2. 累计失败次数并判断是否需要锁定
tracked.FailedLoginCount += 1;
if (tracked.FailedLoginCount >= 5)
{
tracked.Status = IdentityUserStatus.Locked;
tracked.LockedUntil = now.AddMinutes(15);
}
// 3. 保存变更
await userRepository.SaveChangesAsync(cancellationToken);
}
private async Task UpdateLoginSuccessAsync(long userId, DateTime now, CancellationToken cancellationToken)
{
// 1. 获取可更新实体
var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
if (tracked == null)
{
return;
}
// 2. 重置失败次数并刷新登录时间
tracked.FailedLoginCount = 0;
tracked.LockedUntil = null;
tracked.LastLoginAt = now;
if (tracked.Status == IdentityUserStatus.Locked)
{
tracked.Status = IdentityUserStatus.Active;
}
// 3. 保存变更
await userRepository.SaveChangesAsync(cancellationToken);
}
private static bool IsLikelyPhone(string value)
{
if (string.IsNullOrWhiteSpace(value))