using LiveForum.IService.Messages;
using LiveForum.Model.Events;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
using System;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace LiveForum.WebApi.BackgroundServices
{
///
/// 评论回复消息后台消费者
/// 从 message:events:comment 队列消费评论回复事件
///
public class CommentReplyMessageConsumer : BackgroundService
{
private readonly IDatabase _database;
private readonly IMessageEventHandler _eventHandler;
private readonly ILogger _logger;
private const string QUEUE_NAME = "message:events:comment";
public CommentReplyMessageConsumer(
IDatabase database,
IMessageEventHandler eventHandler,
ILogger logger)
{
_database = database;
_eventHandler = eventHandler;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("[CommentConsumer] 评论回复消息消费者已启动");
while (!stoppingToken.IsCancellationRequested)
{
try
{
// 从Redis List左侧阻塞弹出(BLPOP,超时5秒)
var result = await _database.ListLeftPopAsync(QUEUE_NAME);
if (result.HasValue)
{
var json = result.ToString();
// 反序列化事件
var evt = JsonSerializer.Deserialize(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
if (evt != null)
{
_logger.LogInformation(
"[CommentConsumer] 开始处理评论回复事件: EventId={EventId}, CommentId={CommentId}, IsDirectComment={IsDirectComment}",
evt.EventId, evt.CommentId, evt.IsDirectComment);
// 处理事件(带重试)
await ProcessWithRetryAsync(evt, stoppingToken);
}
}
else
{
// 队列为空,等待1秒再查询
await Task.Delay(1000, stoppingToken);
}
}
catch (OperationCanceledException)
{
// 正常停止
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "[CommentConsumer] 消费评论回复事件时发生异常");
// 发生异常后等待5秒再继续
await Task.Delay(5000, stoppingToken);
}
}
_logger.LogInformation("[CommentConsumer] 评论回复消息消费者已停止");
}
///
/// 带重试的事件处理
///
private async Task ProcessWithRetryAsync(CommentReplyEvent evt, CancellationToken stoppingToken)
{
const int maxRetries = 3;
var retryCount = 0;
while (retryCount < maxRetries && !stoppingToken.IsCancellationRequested)
{
try
{
await _eventHandler.HandleCommentReplyEventAsync(evt);
_logger.LogInformation(
"[CommentConsumer] 评论回复事件处理成功: EventId={EventId}",
evt.EventId);
return; // 成功,退出重试循环
}
catch (Exception ex)
{
retryCount++;
if (retryCount >= maxRetries)
{
_logger.LogError(ex,
"[CommentConsumer] 评论回复事件处理失败(已重试{RetryCount}次): EventId={EventId}",
retryCount, evt.EventId);
// TODO: 可以将失败的事件写入死信队列或日志表
return;
}
_logger.LogWarning(ex,
"[CommentConsumer] 评论回复事件处理失败,准备重试({RetryCount}/{MaxRetries}): EventId={EventId}",
retryCount, maxRetries, evt.EventId);
// 延迟后重试(指数退避)
await Task.Delay(1000 * retryCount, stoppingToken);
}
}
}
public override Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("[CommentConsumer] 正在停止评论回复消息消费者...");
return base.StopAsync(cancellationToken);
}
}
}