Browse Source

feat: 优化登出功能,添加详细日志记录和性能监控

norm
hyh 2 months ago
parent
commit
3d2ab3a071
  1. 20
      src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommand.cs
  2. 242
      src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommandHandler.cs

20
src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommand.cs

@ -0,0 +1,20 @@
using MediatR;
using CellularManagement.Domain.Common;
namespace CellularManagement.Application.Features.Auth.Commands.Logout;
/// <summary>
/// 登出命令
/// </summary>
public class LogoutCommand : IRequest<OperationResult<bool>>
{
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
}

242
src/CellularManagement.Application/Features/Auth/Commands/Logout/LogoutCommandHandler.cs

@ -0,0 +1,242 @@
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;
namespace CellularManagement.Application.Features.Auth.Commands.Logout;
/// <summary>
/// 登出命令处理器
/// </summary>
public class LogoutCommandHandler : IRequestHandler<LogoutCommand, OperationResult<bool>>
{
private readonly IJwtProvider _jwtProvider;
private readonly ILogger<LogoutCommandHandler> _logger;
private readonly ILoginLogRepository _loginLogRepository;
private readonly IUnitOfWork _unitOfWork;
private static readonly Action<ILogger, string, Exception?> LogTokenValidationError =
LoggerMessage.Define<string>(LogLevel.Warning, new EventId(1, "TokenValidationError"), "令牌验证错误: {Message}");
private static readonly Action<ILogger, string, string, Exception?> LogTokenProcessingError =
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(2, "TokenProcessingError"), "处理令牌 {TokenType} 时发生错误: {Message}");
/// <summary>
/// 初始化处理器
/// </summary>
public LogoutCommandHandler(
IJwtProvider jwtProvider,
ILogger<LogoutCommandHandler> logger,
ILoginLogRepository loginLogRepository,
IUnitOfWork unitOfWork)
{
_jwtProvider = jwtProvider;
_logger = logger;
_loginLogRepository = loginLogRepository;
_unitOfWork = unitOfWork;
}
/// <summary>
/// 处理登出请求
/// </summary>
public async Task<OperationResult<bool>> 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<bool>.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<bool>.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<bool>.CreateSuccess(true);
}
catch (Exception ex)
{
sw.Stop();
_logger.LogError(ex, "[{CorrelationId}] 处理登出请求时发生异常,耗时: {ElapsedMs}ms",
correlationId, sw.ElapsedMilliseconds);
return OperationResult<bool>.CreateFailure("系统错误");
}
}
/// <summary>
/// 记录登出日志
/// </summary>
private async Task RecordLogoutLogAsync(string userId, string correlationId, CancellationToken cancellationToken)
{
try
{
var loginLog = new LoginLog
{
Id = Guid.NewGuid(),
UserId = userId,
LoginTime = DateTime.UtcNow,
IsSuccess = true
};
await _loginLogRepository.AddAsync(loginLog, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogDebug("[{CorrelationId}] 登出日志记录成功", correlationId);
}
catch (Exception ex)
{
_logger.LogError(ex, "[{CorrelationId}] 记录登出日志时发生异常", correlationId);
throw;
}
}
/// <summary>
/// 处理令牌撤销和黑名单
/// </summary>
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);
}
catch (Exception ex)
{
LogTokenProcessingError(_logger, tokenType, "处理令牌时发生异常", ex);
throw;
}
}
}
Loading…
Cancel
Save