using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using MediatR; using X1.Domain.Entities; using X1.Domain.Repositories; using X1.Domain.Services; using System.Threading.Tasks; using System.Threading; using X1.Domain.Common; using Microsoft.AspNetCore.Http; using System.Security.Claims; using System; using System.Linq; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using X1.Application.Features.Auth.Models; using X1.Domain.Entities.Logging; using X1.Domain.Repositories.Identity; using X1.Domain.Repositories.Base; using X1.Domain.Options; using Microsoft.Extensions.Options; namespace X1.Application.Features.Auth.Commands; /// /// 登录命令处理器基类 /// public abstract class BaseLoginCommandHandler : IRequestHandler> where TCommand : IRequest> where TResponse : class { protected readonly UserManager _userManager; protected readonly IJwtProvider _jwtProvider; protected readonly ILogger _logger; protected readonly IUserRoleRepository _userRoleRepository; protected readonly IRolePermissionRepository _rolePermissionRepository; protected readonly IUnitOfWork _unitOfWork; protected readonly ILoginLogRepository _loginLogRepository; protected readonly IHttpContextAccessor _httpContextAccessor; protected readonly ISessionManagementService _sessionManagementService; protected readonly JwtOptions _jwtOptions; private const int MaxRetryAttempts = 3; /// /// 初始化处理器 /// protected BaseLoginCommandHandler( UserManager userManager, IJwtProvider jwtProvider, ILogger logger, IUserRoleRepository userRoleRepository, IRolePermissionRepository rolePermissionRepository, IUnitOfWork unitOfWork, ILoginLogRepository loginLogRepository, IHttpContextAccessor httpContextAccessor, ISessionManagementService sessionManagementService, IOptions jwtOptions) { _userManager = userManager; _jwtProvider = jwtProvider; _logger = logger; _userRoleRepository = userRoleRepository; _rolePermissionRepository = rolePermissionRepository; _unitOfWork = unitOfWork; _loginLogRepository = loginLogRepository; _httpContextAccessor = httpContextAccessor; _sessionManagementService = sessionManagementService; _jwtOptions = jwtOptions.Value; } /// /// 处理登录请求 /// public abstract Task> Handle(TCommand request, CancellationToken cancellationToken); /// /// 查找用户 /// protected abstract Task FindUserAsync(TCommand request); /// /// 获取用户标识 /// protected abstract string GetUserIdentifier(TCommand request); /// /// 获取密码 /// protected abstract string GetPassword(TCommand request); /// /// 验证凭据 /// protected virtual async Task ValidateCredentialsAsync(TCommand request, AppUser user) { return await _userManager.CheckPasswordAsync(user, GetPassword(request)); } /// /// 处理登录逻辑 /// protected async Task> HandleLoginAsync(TCommand request, CancellationToken cancellationToken) { try { var httpContext = _httpContextAccessor.HttpContext; var ipAddress = httpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown"; var userAgent = httpContext?.Request.Headers["User-Agent"].ToString() ?? "Unknown"; // 检查IP是否被限制 if (await _loginLogRepository.IsIpRestrictedAsync(ipAddress, cancellationToken)) { _logger.LogWarning("IP {IpAddress} 已被限制登录", ipAddress); return OperationResult.CreateFailure("登录尝试次数过多,请稍后再试"); } // 解析设备信息 var parser = UAParser.Parser.GetDefault(); var clientInfo = parser.Parse(userAgent); // 查找用户 var user = await FindUserAsync(request); var userIdentifier = GetUserIdentifier(request); // 创建登录日志 var loginLog = LoginLog.Create( userId: user?.Id ?? "Unknown", ipAddress: ipAddress, userAgent: userAgent, isSuccess: false, loginType: "Password", loginSource: "Web", browser: clientInfo.UA.ToString(), operatingSystem: clientInfo.OS.ToString()); if (user == null) { loginLog.UpdateFailureReason("用户不存在"); await _loginLogRepository.RecordLoginAsync(loginLog, cancellationToken); _logger.LogWarning("用户 {UserIdentifier} 不存在", userIdentifier); return OperationResult.CreateFailure("用户不存在"); } // 检查用户是否已删除 if (user.IsDeleted) { loginLog.UpdateFailureReason("用户已被删除"); await _loginLogRepository.RecordLoginAsync(loginLog, cancellationToken); _logger.LogWarning("用户 {UserIdentifier} 已被删除", userIdentifier); return OperationResult.CreateFailure("用户已被删除"); } // 检查用户是否已禁用 if (!user.IsActive) { loginLog.UpdateFailureReason("用户已被禁用"); await _loginLogRepository.RecordLoginAsync(loginLog, cancellationToken); _logger.LogWarning("用户 {UserIdentifier} 已被禁用", userIdentifier); return OperationResult.CreateFailure("用户已被禁用"); } // 验证凭据 var isValidCredentials = await ValidateCredentialsAsync(request, user); if (!isValidCredentials) { loginLog.UpdateFailureReason("验证失败"); await _loginLogRepository.RecordLoginAsync(loginLog, cancellationToken); _logger.LogWarning("用户 {UserIdentifier} 验证失败", userIdentifier); return OperationResult.CreateFailure("验证失败"); } // 更新最后登录时间(带并发控制) var retryCount = 0; var updateSuccess = false; while (!updateSuccess && retryCount < MaxRetryAttempts) { try { await _unitOfWork.ExecuteTransactionAsync(async () => { user.LastLoginTime = DateTime.UtcNow; var result = await _userManager.UpdateAsync(user); if (!result.Succeeded) { var errors = result.Errors.Select(e => e.Description).ToList(); _logger.LogWarning("更新用户最后登录时间失败: {Errors}", string.Join(", ", errors)); throw new InvalidOperationException(string.Join(", ", errors)); } }, cancellationToken: cancellationToken); updateSuccess = true; } catch (DbUpdateConcurrencyException) { retryCount++; _logger.LogWarning("用户 {UserIdentifier} 更新时发生并发冲突,重试次数: {RetryCount}", userIdentifier, retryCount); if (retryCount >= MaxRetryAttempts) { _logger.LogError("用户 {UserIdentifier} 更新失败,超过最大重试次数", userIdentifier); return OperationResult.CreateFailure("系统繁忙,请稍后重试"); } // 重新获取最新数据 user = await _userManager.FindByIdAsync(user.Id); if (user == null) { return OperationResult.CreateFailure("用户不存在"); } } } // 获取用户角色 var roles = await _userRoleRepository.GetUserRolesAsync(user.Id, cancellationToken); // 创建用户声明 var claims = new List { new(ClaimTypes.NameIdentifier, user.Id), new(ClaimTypes.Name, user.UserName!), new(ClaimTypes.Email, user.Email!) }; // 添加最后登录时间声明(如果存在) if (user.LastLoginTime.HasValue) { claims.Add(new Claim("LastLoginTime", user.LastLoginTime.Value.ToUniversalTime().ToString("o"))); } // 添加角色声明 claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); // 获取所有角色的权限 var permissionCodes = new HashSet(); if (roles.Any()) { var allRolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(roles, cancellationToken); foreach (var rolePermission in allRolePermissions) { if (rolePermission.Permission != null) { permissionCodes.Add(rolePermission.Permission.Code); } } } // 生成会话ID var sessionId = Guid.NewGuid().ToString(); // 添加会话ID到Claims(在生成令牌之前)- 使用ClaimTypes.UserData claims.Add(new Claim(ClaimTypes.UserData, sessionId)); // 生成访问令牌 var accessToken = _jwtProvider.GenerateAccessToken(claims); // 生成刷新令牌 var refreshToken = _jwtProvider.GenerateRefreshToken(claims); // 获取令牌过期时间 var expiresAt = _jwtProvider.GetTokenExpiration(accessToken); // 创建设备信息 var deviceInfo = $"{clientInfo.OS.Family} {clientInfo.OS.Major}.{clientInfo.OS.Minor} - {clientInfo.UA.Family} {clientInfo.UA.Major}.{clientInfo.UA.Minor}"; // 创建用户会话(踢出其他登录) var sessionExpiry = TimeSpan.FromMinutes(_jwtOptions.ExpiryMinutes); var sessionCreated = await _sessionManagementService.CreateSessionAsync( user.Id, sessionId, accessToken, deviceInfo, sessionExpiry); if (!sessionCreated) { _logger.LogError("创建用户会话失败: {UserId}", user.Id); return OperationResult.CreateFailure("创建会话失败,请稍后重试"); } // 创建用户信息 var userInfo = new UserInfo( user.Id, user.UserName!, user.RealName, user.Email!, user.PhoneNumber, roles.ToList().AsReadOnly(), permissionCodes.ToList().AsReadOnly()); // 记录成功的登录日志 loginLog = LoginLog.Create( userId: user.Id, ipAddress: ipAddress, userAgent: userAgent, isSuccess: true, loginType: "Password", loginSource: "Web", browser: clientInfo.UA.ToString(), operatingSystem: clientInfo.OS.ToString()); await _loginLogRepository.RecordLoginAsync(loginLog, cancellationToken); await _unitOfWork.SaveChangesAsync(cancellationToken); _logger.LogInformation("用户 {UserIdentifier} 认证成功", userIdentifier); // 返回认证结果 var response = (TResponse)Activator.CreateInstance( typeof(TResponse), accessToken, refreshToken, expiresAt, userInfo)!; return OperationResult.CreateSuccess(response); } catch (Exception ex) { _logger.LogError(ex, "用户认证失败"); return OperationResult.CreateFailure("认证失败,请稍后重试"); } } }