live-forum/server/webapi/LiveForum/LiveForum.WebApi/BackgroundServices/CommentReplyMessageConsumer.cs
2026-03-24 11:27:37 +08:00

140 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 评论回复消息后台消费者
/// 从 message:events:comment 队列消费评论回复事件
/// </summary>
public class CommentReplyMessageConsumer : BackgroundService
{
private readonly IDatabase _database;
private readonly IMessageEventHandler _eventHandler;
private readonly ILogger<CommentReplyMessageConsumer> _logger;
private const string QUEUE_NAME = "message:events:comment";
public CommentReplyMessageConsumer(
IDatabase database,
IMessageEventHandler eventHandler,
ILogger<CommentReplyMessageConsumer> 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<CommentReplyEvent>(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] 评论回复消息消费者已停止");
}
/// <summary>
/// 带重试的事件处理
/// </summary>
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);
}
}
}