140 lines
5.1 KiB
C#
140 lines
5.1 KiB
C#
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:custom 队列消费自定义消息事件
|
||
/// </summary>
|
||
public class CustomMessageConsumer : BackgroundService
|
||
{
|
||
private readonly IDatabase _database;
|
||
private readonly IMessageEventHandler _eventHandler;
|
||
private readonly ILogger<CustomMessageConsumer> _logger;
|
||
private const string QUEUE_NAME = "message:events:custom";
|
||
|
||
public CustomMessageConsumer(
|
||
IDatabase database,
|
||
IMessageEventHandler eventHandler,
|
||
ILogger<CustomMessageConsumer> logger)
|
||
{
|
||
_database = database;
|
||
_eventHandler = eventHandler;
|
||
_logger = logger;
|
||
}
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
_logger.LogInformation("[CustomConsumer] 自定义消息消费者已启动");
|
||
|
||
while (!stoppingToken.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
// 从Redis List左侧弹出
|
||
var result = await _database.ListLeftPopAsync(QUEUE_NAME);
|
||
|
||
if (result.HasValue)
|
||
{
|
||
var json = result.ToString();
|
||
|
||
// 反序列化事件
|
||
var evt = JsonSerializer.Deserialize<CustomMessageEvent>(json, new JsonSerializerOptions
|
||
{
|
||
PropertyNameCaseInsensitive = true
|
||
});
|
||
|
||
if (evt != null)
|
||
{
|
||
_logger.LogInformation(
|
||
"[CustomConsumer] 开始处理自定义消息事件: EventId={EventId}, MessageType={MessageType}",
|
||
evt.EventId, evt.MessageType);
|
||
|
||
// 处理事件(带重试)
|
||
await ProcessWithRetryAsync(evt, stoppingToken);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 队列为空,等待1秒再查询
|
||
await Task.Delay(1000, stoppingToken);
|
||
}
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
// 正常停止
|
||
break;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "[CustomConsumer] 消费自定义消息事件时发生异常");
|
||
|
||
// 发生异常后等待5秒再继续
|
||
await Task.Delay(5000, stoppingToken);
|
||
}
|
||
}
|
||
|
||
_logger.LogInformation("[CustomConsumer] 自定义消息消费者已停止");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带重试的事件处理
|
||
/// </summary>
|
||
private async Task ProcessWithRetryAsync(CustomMessageEvent evt, CancellationToken stoppingToken)
|
||
{
|
||
const int maxRetries = 3;
|
||
var retryCount = 0;
|
||
|
||
while (retryCount < maxRetries && !stoppingToken.IsCancellationRequested)
|
||
{
|
||
try
|
||
{
|
||
await _eventHandler.HandleCustomMessageEventAsync(evt);
|
||
|
||
_logger.LogInformation(
|
||
"[CustomConsumer] 自定义消息事件处理成功: EventId={EventId}",
|
||
evt.EventId);
|
||
|
||
return; // 成功,退出重试循环
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
retryCount++;
|
||
|
||
if (retryCount >= maxRetries)
|
||
{
|
||
_logger.LogError(ex,
|
||
"[CustomConsumer] 自定义消息事件处理失败(已重试{RetryCount}次): EventId={EventId}",
|
||
retryCount, evt.EventId);
|
||
|
||
// TODO: 可以将失败的事件写入死信队列或日志表
|
||
return;
|
||
}
|
||
|
||
_logger.LogWarning(ex,
|
||
"[CustomConsumer] 自定义消息事件处理失败,准备重试({RetryCount}/{MaxRetries}): EventId={EventId}",
|
||
retryCount, maxRetries, evt.EventId);
|
||
|
||
// 延迟后重试(指数退避)
|
||
await Task.Delay(1000 * retryCount, stoppingToken);
|
||
}
|
||
}
|
||
}
|
||
|
||
public override Task StopAsync(CancellationToken cancellationToken)
|
||
{
|
||
_logger.LogInformation("[CustomConsumer] 正在停止自定义消息消费者...");
|
||
return base.StopAsync(cancellationToken);
|
||
}
|
||
}
|
||
}
|
||
|