using MediatR; using Microsoft.Extensions.Logging; using CellularManagement.Domain.Common; using CellularManagement.Domain.Services; using CellularManagement.Domain.Repositories; using CellularManagement.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System.Security.Claims; using System.Collections.Generic; using System.Diagnostics; using CellularManagement.Domain.Entities.Logging; using Microsoft.AspNetCore.Http; using CellularManagement.Domain.Repositories.Base; using CellularManagement.Domain.Repositories.Identity; namespace CellularManagement.Application.Features.Auth.Commands.Logout; /// /// 登出命令处理器 /// public class LogoutCommandHandler : IRequestHandler> { private readonly IJwtProvider _jwtProvider; private readonly ILogger _logger; private readonly ILoginLogRepository _loginLogRepository; private readonly IUnitOfWork _unitOfWork; private readonly IHttpContextAccessor _httpContextAccessor; private static readonly Action LogTokenValidationError = LoggerMessage.Define(LogLevel.Warning, new EventId(1, "TokenValidationError"), "令牌验证错误: {Message}"); private static readonly Action LogTokenProcessingError = LoggerMessage.Define(LogLevel.Error, new EventId(2, "TokenProcessingError"), "处理令牌 {TokenType} 时发生错误: {Message}"); /// /// 初始化处理器 /// public LogoutCommandHandler( IJwtProvider jwtProvider, ILogger logger, ILoginLogRepository loginLogRepository, IUnitOfWork unitOfWork, IHttpContextAccessor httpContextAccessor) { _jwtProvider = jwtProvider; _logger = logger; _loginLogRepository = loginLogRepository; _unitOfWork = unitOfWork; _httpContextAccessor = httpContextAccessor; } /// /// 处理登出请求 /// public async Task> Handle(LogoutCommand request, CancellationToken cancellationToken) { var sw = Stopwatch.StartNew(); var correlationId = Guid.NewGuid().ToString(); _logger.LogInformation("[{CorrelationId}] 开始处理登出请求", correlationId); try { // 快速验证令牌存在性 if (string.IsNullOrEmpty(request.AccessToken) || string.IsNullOrEmpty(request.RefreshToken)) { _logger.LogWarning("[{CorrelationId}] 令牌为空", correlationId); return OperationResult.CreateFailure("无效的请求"); } _logger.LogDebug("[{CorrelationId}] 开始验证令牌", correlationId); string? userId = null; bool isAccessTokenValid = false; bool isRefreshTokenValid = false; var tokenValidationSw = Stopwatch.StartNew(); try { // 验证访问令牌 _logger.LogDebug("[{CorrelationId}] 开始验证访问令牌", correlationId); var accessTokenClaims = _jwtProvider.GetClaimsFromToken(request.AccessToken); if (accessTokenClaims.Any()) { var tokenType = accessTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value; _logger.LogDebug("[{CorrelationId}] 访问令牌类型: {TokenType}", correlationId, tokenType); if (tokenType == "access_token") { userId = accessTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; isAccessTokenValid = !string.IsNullOrEmpty(userId); _logger.LogDebug("[{CorrelationId}] 访问令牌验证结果: {IsValid}, 用户ID: {UserId}", correlationId, isAccessTokenValid, userId); } else { _logger.LogWarning("[{CorrelationId}] 访问令牌类型不匹配: {TokenType}", correlationId, tokenType); } } } catch (Exception ex) { LogTokenValidationError(_logger, "访问令牌验证失败", ex); } try { // 验证刷新令牌 _logger.LogDebug("[{CorrelationId}] 开始验证刷新令牌", correlationId); var refreshTokenClaims = _jwtProvider.GetClaimsFromToken(request.RefreshToken); if (refreshTokenClaims.Any()) { var tokenType = refreshTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value; _logger.LogDebug("[{CorrelationId}] 刷新令牌类型: {TokenType}", correlationId, tokenType); if (tokenType == "refresh_token") { var refreshTokenUserId = refreshTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; isRefreshTokenValid = !string.IsNullOrEmpty(refreshTokenUserId) && (string.IsNullOrEmpty(userId) || userId == refreshTokenUserId); if (isRefreshTokenValid && string.IsNullOrEmpty(userId)) { userId = refreshTokenUserId; } _logger.LogDebug("[{CorrelationId}] 刷新令牌验证结果: {IsValid}, 用户ID: {UserId}", correlationId, isRefreshTokenValid, refreshTokenUserId); } else { _logger.LogWarning("[{CorrelationId}] 刷新令牌类型不匹配: {TokenType}", correlationId, tokenType); } } } catch (Exception ex) { LogTokenValidationError(_logger, "刷新令牌验证失败", ex); } tokenValidationSw.Stop(); _logger.LogInformation("[{CorrelationId}] 令牌验证完成,耗时: {ElapsedMs}ms", correlationId, tokenValidationSw.ElapsedMilliseconds); // 如果两个令牌都无效,直接返回成功 if (!isAccessTokenValid && !isRefreshTokenValid) { _logger.LogInformation("[{CorrelationId}] 所有令牌无效,直接返回成功", correlationId); return OperationResult.CreateSuccess(true); } // 记录登出日志 if (!string.IsNullOrEmpty(userId)) { _logger.LogDebug("[{CorrelationId}] 开始记录登出日志,用户ID: {UserId}", correlationId, userId); var logSw = Stopwatch.StartNew(); await RecordLogoutLogAsync(userId, correlationId, cancellationToken); logSw.Stop(); _logger.LogInformation("[{CorrelationId}] 登出日志记录完成,耗时: {ElapsedMs}ms", correlationId, logSw.ElapsedMilliseconds); } // 异步处理令牌撤销和黑名单 _ = Task.Run(async () => { var tokenProcessingSw = Stopwatch.StartNew(); try { if (isAccessTokenValid) { _logger.LogDebug("[{CorrelationId}] 开始处理访问令牌撤销和黑名单", correlationId); await ProcessTokenAsync(request.AccessToken, "访问令牌", correlationId); } if (isRefreshTokenValid) { _logger.LogDebug("[{CorrelationId}] 开始处理刷新令牌撤销和黑名单", correlationId); await ProcessTokenAsync(request.RefreshToken, "刷新令牌", correlationId); } } catch (Exception ex) { LogTokenProcessingError(_logger, "令牌处理", "处理令牌撤销和黑名单时发生异常", ex); } finally { tokenProcessingSw.Stop(); _logger.LogInformation("[{CorrelationId}] 令牌处理完成,耗时: {ElapsedMs}ms", correlationId, tokenProcessingSw.ElapsedMilliseconds); } }); sw.Stop(); _logger.LogInformation("[{CorrelationId}] 登出请求处理完成,总耗时: {ElapsedMs}ms", correlationId, sw.ElapsedMilliseconds); return OperationResult.CreateSuccess(true); } catch (Exception ex) { sw.Stop(); _logger.LogError(ex, "[{CorrelationId}] 处理登出请求时发生异常,耗时: {ElapsedMs}ms", correlationId, sw.ElapsedMilliseconds); return OperationResult.CreateFailure("系统错误"); } } /// /// 记录登出日志 /// private async Task RecordLogoutLogAsync(string userId, string correlationId, CancellationToken cancellationToken) { try { var httpContext = _httpContextAccessor.HttpContext; var ipAddress = httpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown"; var userAgent = httpContext?.Request.Headers["User-Agent"].ToString() ?? "Unknown"; // 解析设备信息 var parser = UAParser.Parser.GetDefault(); var clientInfo = parser.Parse(userAgent); var loginLog = LoginLog.Create( userId: userId, ipAddress: ipAddress, userAgent: userAgent, isSuccess: true, loginType: "Logout", loginSource: "Web", browser: clientInfo.UA.ToString(), operatingSystem: clientInfo.OS.ToString()); await _loginLogRepository.AddAsync(loginLog, cancellationToken); await _unitOfWork.SaveChangesAsync(cancellationToken); _logger.LogDebug("[{CorrelationId}] 登出日志记录成功", correlationId); } catch (Exception ex) { _logger.LogError(ex, "[{CorrelationId}] 记录登出日志时发生异常", correlationId); throw; } } /// /// 处理令牌撤销和黑名单 /// private async Task ProcessTokenAsync(string token, string tokenType, string correlationId) { try { var sw = Stopwatch.StartNew(); _jwtProvider.RevokeToken(token); _jwtProvider.AddToBlacklist(token); sw.Stop(); _logger.LogDebug("[{CorrelationId}] {TokenType}处理完成,耗时: {ElapsedMs}ms", correlationId, tokenType, sw.ElapsedMilliseconds); await Task.CompletedTask; } catch (Exception ex) { LogTokenProcessingError(_logger, tokenType, "处理令牌时发生异常", ex); throw; } } }