feat: 用户管理后端与日志库迁移
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user