2026-06-11 17:01:24 +08:00
|
|
|
|
using RabbitMQ.Client;
|
|
|
|
|
|
using RabbitMQ.Client.Events;
|
2026-07-02 16:05:13 +08:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2026-07-10 10:44:00 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2026-06-11 17:01:24 +08:00
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Text.Encodings.Web;
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Infrastructure.RabbitMQ
|
|
|
|
|
|
{
|
|
|
|
|
|
public class RabbitMQService : IRabbitMQService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IRabbitMQConnection _connection;
|
2026-07-02 16:05:13 +08:00
|
|
|
|
private readonly IConfiguration _configuration;
|
2026-07-10 10:44:00 +08:00
|
|
|
|
private readonly ILogger<RabbitMQService> _logger;
|
2026-06-11 17:01:24 +08:00
|
|
|
|
private readonly JsonSerializerOptions options = new JsonSerializerOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
|
|
|
|
};
|
2026-07-10 10:44:00 +08:00
|
|
|
|
public RabbitMQService(IRabbitMQConnection connection, IConfiguration configuration, ILogger<RabbitMQService> logger)
|
2026-06-11 17:01:24 +08:00
|
|
|
|
{
|
|
|
|
|
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
2026-07-02 16:05:13 +08:00
|
|
|
|
_configuration = configuration;
|
2026-07-10 10:44:00 +08:00
|
|
|
|
_logger = logger;
|
2026-06-11 17:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<bool> SendAsync(RabbitMQSendParam param, CancellationToken cancellationToken = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using var channel = await _connection.CreateChannel();
|
|
|
|
|
|
|
2026-06-22 16:56:05 +08:00
|
|
|
|
// 声明 Exchange(持久化)
|
|
|
|
|
|
await channel.ExchangeDeclareAsync(exchange: param.Exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
|
2026-06-11 17:01:24 +08:00
|
|
|
|
|
|
|
|
|
|
// 声明队列(持久化)
|
2026-06-22 16:56:05 +08:00
|
|
|
|
await channel.QueueDeclareAsync(queue: param.Queue, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定队列到 Exchange
|
|
|
|
|
|
await channel.QueueBindAsync(queue: param.Queue, exchange: param.Exchange, routingKey: param.RoutingKey, arguments: null);
|
|
|
|
|
|
|
2026-06-11 17:01:24 +08:00
|
|
|
|
// 清空队列
|
2026-06-22 16:56:05 +08:00
|
|
|
|
if (param.Purge) await channel.QueuePurgeAsync(param.Queue);
|
2026-06-11 17:01:24 +08:00
|
|
|
|
// 消息序列化
|
|
|
|
|
|
var mesjson = JsonSerializer.Serialize(param.Data, options);
|
|
|
|
|
|
|
|
|
|
|
|
var body = Encoding.UTF8.GetBytes(mesjson);
|
|
|
|
|
|
var properties = new BasicProperties
|
|
|
|
|
|
{
|
|
|
|
|
|
Persistent = true // 设置消息持久化
|
|
|
|
|
|
};
|
|
|
|
|
|
await channel.BasicPublishAsync(param.Exchange, param.RoutingKey, false, properties, body, cancellationToken);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException ex)
|
|
|
|
|
|
{
|
2026-07-10 10:44:00 +08:00
|
|
|
|
_logger.LogWarning(ex, "RabbitMQ消息发送已取消,Exchange: {Exchange}, Queue: {Queue}, RoutingKey: {RoutingKey}",
|
|
|
|
|
|
param.Exchange, param.Queue, param.RoutingKey);
|
2026-06-11 17:01:24 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-07-10 10:44:00 +08:00
|
|
|
|
_logger.LogError(ex, "RabbitMQ消息发送失败,Exchange: {Exchange}, Queue: {Queue}, RoutingKey: {RoutingKey}",
|
|
|
|
|
|
param.Exchange, param.Queue, param.RoutingKey);
|
2026-06-11 17:01:24 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<bool> SendBatchAsync(IEnumerable<RabbitMQSendParam> @params, CancellationToken cancellationToken = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
IChannel channel = null;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
channel = await _connection.CreateChannel();
|
|
|
|
|
|
|
|
|
|
|
|
// 开启事务
|
|
|
|
|
|
await channel.TxSelectAsync();
|
|
|
|
|
|
|
|
|
|
|
|
var properties = new BasicProperties
|
|
|
|
|
|
{
|
|
|
|
|
|
Persistent = true // 设置消息持久化
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 批量发送消息到不同的 routingKey
|
|
|
|
|
|
var declaredExchanges = new HashSet<string>();
|
|
|
|
|
|
foreach (var param in @params)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 声明 Exchange(持久化)
|
|
|
|
|
|
if (declaredExchanges.Add(param.Exchange))
|
|
|
|
|
|
{
|
|
|
|
|
|
await channel.ExchangeDeclareAsync(exchange: param.Exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 声明队列(持久化)
|
|
|
|
|
|
await channel.QueueDeclareAsync(queue: param.Queue, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定队列到 Exchange
|
|
|
|
|
|
await channel.QueueBindAsync(queue: param.Queue, exchange: param.Exchange, routingKey: param.RoutingKey, arguments: null);
|
|
|
|
|
|
|
|
|
|
|
|
// 清空队列
|
|
|
|
|
|
if (param.Purge) await channel.QueuePurgeAsync(param.Queue);
|
|
|
|
|
|
|
|
|
|
|
|
// 消息序列化
|
|
|
|
|
|
var mesjson = JsonSerializer.Serialize(param.Data, options);
|
|
|
|
|
|
var body = Encoding.UTF8.GetBytes(mesjson);
|
|
|
|
|
|
|
|
|
|
|
|
// 发布消息
|
|
|
|
|
|
await channel.BasicPublishAsync(param.Exchange, param.RoutingKey, false, properties, body, cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提交事务 - 确保所有消息都发送成功
|
|
|
|
|
|
await channel.TxCommitAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2026-07-10 10:44:00 +08:00
|
|
|
|
_logger.LogError(ex, "RabbitMQ批量消息发送失败");
|
2026-06-11 17:01:24 +08:00
|
|
|
|
// 回滚事务
|
|
|
|
|
|
try { await channel?.TxRollbackAsync(); } catch { /* 忽略回滚异常 */ }
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
if (channel != null && !channel.IsClosed)
|
|
|
|
|
|
{
|
|
|
|
|
|
await channel.CloseAsync();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 16:56:05 +08:00
|
|
|
|
public async Task ReceiveAsync(string exchange, string queueName, string routingKey, Func<IChannel, BasicDeliverEventArgs, Task> callback, CancellationToken cancellationToken = default)
|
2026-06-11 17:01:24 +08:00
|
|
|
|
{
|
|
|
|
|
|
var channel = await _connection.CreateChannel();
|
2026-07-02 16:05:13 +08:00
|
|
|
|
var prefetchCount = _configuration.GetValue<ushort>("RabbitMq:PrefetchCount");
|
|
|
|
|
|
if (prefetchCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
prefetchCount = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await channel.BasicQosAsync(0, prefetchCount, false, cancellationToken);
|
2026-06-22 16:56:05 +08:00
|
|
|
|
|
|
|
|
|
|
// 声明 Exchange(持久化)
|
|
|
|
|
|
await channel.ExchangeDeclareAsync(exchange: exchange, type: "direct", durable: true, autoDelete: false, arguments: null);
|
|
|
|
|
|
|
|
|
|
|
|
// 声明队列(持久化)
|
2026-06-11 17:01:24 +08:00
|
|
|
|
await channel.QueueDeclareAsync(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
|
|
|
|
|
|
|
2026-06-22 16:56:05 +08:00
|
|
|
|
// 绑定队列到 Exchange
|
|
|
|
|
|
await channel.QueueBindAsync(queue: queueName, exchange: exchange, routingKey: routingKey, arguments: null);
|
|
|
|
|
|
|
2026-06-11 17:01:24 +08:00
|
|
|
|
var consumer = new AsyncEventingBasicConsumer(channel);
|
|
|
|
|
|
consumer.ReceivedAsync += async (model, ea) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
//var body = ea.Body.ToArray();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 直接传递 model 和 body 给 callback,不需要转换
|
|
|
|
|
|
await callback(channel, ea);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
//await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
await channel.BasicConsumeAsync(queue: queueName, autoAck: false, consumer: consumer, cancellationToken: cancellationToken);
|
|
|
|
|
|
// Prevent the method from returning immediately
|
|
|
|
|
|
await Task.Delay(-1, cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|