重构消息队列模块,新增NATS和RabbitMQ连接实现,移除旧版消息队列代码
This commit is contained in:
@@ -10,61 +10,129 @@ import (
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
// rabbitMQMessageClient RabbitMQ 实现
|
||||
type rabbitMQMessageClient struct {
|
||||
clientType messageClientType
|
||||
type RabbitMQPublishMsgConfig struct {
|
||||
QueueName string
|
||||
Durable bool
|
||||
DelayTime int
|
||||
Data any
|
||||
}
|
||||
|
||||
// StreamGroup 创建消费组(支持单个或批量)
|
||||
func (q *rabbitMQMessageClient) streamGroup(ctx context.Context, configs ...interface{}) error {
|
||||
if len(configs) == 0 {
|
||||
return fmt.Errorf("配置不能为空")
|
||||
}
|
||||
for _, config := range configs {
|
||||
cfg, ok := config.(*RabbitMQConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
if err := q.setupQueue(ctx, channel, cfg, cfg.DelayMessage); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
type RabbitMQSubscribeMsgConfig struct {
|
||||
QueueName string
|
||||
Durable bool
|
||||
DelayTime int
|
||||
ConsumerName string
|
||||
AutoAck bool
|
||||
PrefetchCount int
|
||||
HandleFunc func(ctx context.Context, message map[string]interface{}) error
|
||||
}
|
||||
|
||||
// Publish 发布消息(支持单个或批量)
|
||||
func (q *rabbitMQMessageClient) publish(ctx context.Context, config interface{}, data interface{}) error {
|
||||
cfg, ok := config.(*RabbitMQConfig)
|
||||
func (*RabbitMQPublishMsgConfig) GetPublishMsgType() {
|
||||
|
||||
}
|
||||
|
||||
func (*RabbitMQSubscribeMsgConfig) GetSubscribeMsgType() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 注册 RabbitMQ 插件,必须使用 RegisterPlugin 确保连接检测
|
||||
//registerPlugin(MessageRabbitMQ, func() messageUtil {
|
||||
// return &rabbitMQ{}
|
||||
//})
|
||||
}
|
||||
|
||||
type rabbitMQ struct{}
|
||||
|
||||
// Ping 检测 RabbitMQ 连接状态
|
||||
func (c *rabbitMQ) ping(ctx context.Context) bool {
|
||||
return rabbitmqPing()
|
||||
}
|
||||
|
||||
// Reconnect 重连 RabbitMQ
|
||||
func (c *rabbitMQ) reconnect(ctx context.Context) error {
|
||||
return rabbitmqReconnect(ctx)
|
||||
}
|
||||
|
||||
// Close 关闭 RabbitMQ 连接
|
||||
func (c *rabbitMQ) close(ctx context.Context) error {
|
||||
return rabbitmqClose(ctx)
|
||||
}
|
||||
|
||||
// Publish 发布消息
|
||||
func (c *rabbitMQ) Publish(ctx context.Context, msgConfig messagePublishConfig) error {
|
||||
cfg, ok := msgConfig.(*RabbitMQPublishMsgConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
if err := q.publishMessage(ctx, cfg, "work", data, 0); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ RabbitMQ 发布消息失败: err=%v", err)
|
||||
return err
|
||||
if g.IsEmpty(cfg.QueueName) {
|
||||
return fmt.Errorf("队列名称不能为空")
|
||||
}
|
||||
return nil
|
||||
if cfg.Data == nil {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
return c.publishMessageInternal(ctx, cfg.QueueName, cfg.Durable, cfg.DelayTime, cfg.Data)
|
||||
}
|
||||
|
||||
// PublishDelayed 发布延迟消息
|
||||
func (q *rabbitMQMessageClient) publishDelayed(ctx context.Context, config interface{}, data interface{}, delaySeconds int) error {
|
||||
cfg, ok := config.(*RabbitMQConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
if err := q.publishMessage(ctx, cfg, "delayed", data, delaySeconds); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ RabbitMQ 发布延迟消息失败: err=%v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// publishMessage 发布消息内部实现
|
||||
func (c *rabbitMQ) publishMessageInternal(ctx context.Context, queueName string, durable bool, delayTime int, data interface{}) error {
|
||||
delayMsg := delayTime > 0
|
||||
|
||||
func (q *rabbitMQMessageClient) publishMessage(ctx context.Context, cfg *RabbitMQConfig, mode string, data interface{}, delaySeconds int) error {
|
||||
// 1. 决定 Exchange 类型
|
||||
exchangeType := "fanout"
|
||||
exchangeName := queueName
|
||||
routingKey := queueName
|
||||
args := amqp.Table{}
|
||||
if delayMsg {
|
||||
exchangeType = "x-delayed-message"
|
||||
exchangeName = queueName + ".delayed"
|
||||
args["x-delayed-type"] = "fanout" // 底层用 topic
|
||||
}
|
||||
|
||||
// 2. 声明 Exchange(只声明一次)
|
||||
if err := channel.ExchangeDeclare(
|
||||
queueName, // exchange 交换机名称
|
||||
exchangeType,
|
||||
durable,
|
||||
false, // autoDelete
|
||||
false, // internal
|
||||
false, // noWait
|
||||
args,
|
||||
); err != nil {
|
||||
return fmt.Errorf("声明 Exchange 失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 声明队列
|
||||
if _, err := channel.QueueDeclare(
|
||||
queueName,
|
||||
durable,
|
||||
false, // autoDelete
|
||||
false, // exclusive
|
||||
false, // noWait
|
||||
nil, // args
|
||||
); err != nil {
|
||||
return fmt.Errorf("声明队列失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 绑定队列
|
||||
if err := channel.QueueBind(
|
||||
queueName,
|
||||
routingKey, // routingKey 路由键
|
||||
exchangeName, // exchange 交换机名称
|
||||
false, // noWait
|
||||
nil, // args
|
||||
); err != nil {
|
||||
return fmt.Errorf("绑定队列失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 序列化数据
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化数据失败: %w", err)
|
||||
}
|
||||
// 6. 发布消息
|
||||
deliveryMode := amqp.Transient
|
||||
if cfg.Durable {
|
||||
if durable {
|
||||
deliveryMode = amqp.Persistent
|
||||
}
|
||||
publishing := amqp.Publishing{
|
||||
@@ -73,15 +141,15 @@ func (q *rabbitMQMessageClient) publishMessage(ctx context.Context, cfg *RabbitM
|
||||
DeliveryMode: deliveryMode,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if delaySeconds > 0 {
|
||||
if delayMsg {
|
||||
duration := time.Duration(delayTime) * time.Minute
|
||||
publishing.Headers = amqp.Table{
|
||||
"x-delay": delaySeconds * 1000, // 延时时间(毫秒)
|
||||
"x-delay": duration, // 延迟时间(毫秒)
|
||||
}
|
||||
}
|
||||
exchange, routingKey := q.parseExchangeAndRoutingKey(ctx, mode, cfg)
|
||||
err = channel.PublishWithContext(
|
||||
ctx,
|
||||
exchange,
|
||||
exchangeName,
|
||||
routingKey,
|
||||
false, false,
|
||||
publishing,
|
||||
@@ -89,106 +157,44 @@ func (q *rabbitMQMessageClient) publishMessage(ctx context.Context, cfg *RabbitM
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *rabbitMQMessageClient) parseExchangeAndRoutingKey(_ context.Context, mode string, cfg *RabbitMQConfig) (exchange, routingKey string) {
|
||||
switch mode {
|
||||
case "work", "":
|
||||
exchange = "" // 默认交换机
|
||||
routingKey = cfg.Name // 队列名
|
||||
case "event", "topic":
|
||||
exchange = cfg.Exchange
|
||||
routingKey = cfg.Topic
|
||||
case "broadcast":
|
||||
exchange = cfg.Exchange
|
||||
routingKey = "" // fanout忽略路由键
|
||||
case "delayed":
|
||||
exchange = cfg.Exchange + ".delayed"
|
||||
routingKey = cfg.Topic
|
||||
default:
|
||||
exchange = ""
|
||||
routingKey = cfg.Name
|
||||
// Subscribe 订阅消息
|
||||
func (c *rabbitMQ) Subscribe(ctx context.Context, msgConfig messageSubscribeConfig) error {
|
||||
cfg, ok := msgConfig.(*RabbitMQSubscribeMsgConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
return exchange, routingKey
|
||||
if g.IsEmpty(cfg.QueueName) {
|
||||
return fmt.Errorf("队列名称不能为空")
|
||||
}
|
||||
if g.IsEmpty(cfg.ConsumerName) {
|
||||
return fmt.Errorf("消费者名称不能为空")
|
||||
}
|
||||
if g.IsEmpty(cfg.PrefetchCount) {
|
||||
cfg.PrefetchCount = 1
|
||||
}
|
||||
if g.IsEmpty(cfg.HandleFunc) {
|
||||
return fmt.Errorf("必须提供处理函数")
|
||||
}
|
||||
return c.createSubscribeInternal(ctx, cfg.QueueName, cfg.ConsumerName, cfg.PrefetchCount, cfg.AutoAck, cfg.HandleFunc)
|
||||
}
|
||||
|
||||
// setupQueue 统一的队列设置方法(声明 Exchange、队列、绑定、延迟 Exchange)
|
||||
func (q *rabbitMQMessageClient) setupQueue(ctx context.Context, ch *amqp.Channel, cfg *RabbitMQConfig, delayMessage bool) error {
|
||||
exchange, routingKey := q.parseExchangeAndRoutingKey(ctx, cfg.Mode, cfg)
|
||||
// createSubscribe 内部订阅消息
|
||||
func (c *rabbitMQ) createSubscribeInternal(ctx context.Context, queueName, consumerName string, prefetchCount int, autoAck bool, handler func(ctx context.Context, message map[string]interface{}) error) error {
|
||||
g.Log().Infof(ctx, "🔔 RabbitMQ 开始订阅: queueName=%s, consumerName=%s", queueName, consumerName)
|
||||
|
||||
// 声明 Exchange
|
||||
if err := ch.ExchangeDeclare(exchange, "topic", cfg.Durable, false, false, false, nil); err != nil {
|
||||
return fmt.Errorf("声明 Exchange 失败: %w", err)
|
||||
}
|
||||
|
||||
// 声明队列
|
||||
if _, err := ch.QueueDeclare(cfg.Queue, cfg.Durable, false, false, false, nil); err != nil {
|
||||
return fmt.Errorf("声明队列失败: %w", err)
|
||||
}
|
||||
|
||||
// 绑定队列
|
||||
if err := ch.QueueBind(cfg.Queue, routingKey, exchange, false, nil); err != nil {
|
||||
return fmt.Errorf("绑定队列失败: %w", err)
|
||||
}
|
||||
|
||||
// 声明延迟 Exchange(如果需要)
|
||||
if delayMessage {
|
||||
if err := ch.ExchangeDeclare(exchange, "x-delayed-message", true, false, false, false, amqp.Table{"x-delayed-type": "direct"}); err != nil {
|
||||
return fmt.Errorf("声明延迟 Exchange 失败: %w", err)
|
||||
}
|
||||
if err := ch.QueueBind(cfg.Name, routingKey, exchange, false, nil); err != nil {
|
||||
return fmt.Errorf("绑定延迟队列失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscribe 订阅消息(支持单个或批量)
|
||||
func (q *rabbitMQMessageClient) subscribe(ctx context.Context, configs ...interface{}) error {
|
||||
if len(configs) == 0 {
|
||||
return fmt.Errorf("配置不能为空")
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
cfg, ok := config.(*RabbitMQConfig)
|
||||
if !ok {
|
||||
return fmt.Errorf("无效的 RabbitMQ 配置类型")
|
||||
}
|
||||
handler := cfg.HandleFunc
|
||||
if handler == nil {
|
||||
return fmt.Errorf("必须提供处理函数")
|
||||
}
|
||||
if err := q.createSubscribe(ctx, cfg, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// subscribe 内部单个订阅消息
|
||||
func (q *rabbitMQMessageClient) createSubscribe(ctx context.Context, cfg *RabbitMQConfig, handler func(ctx context.Context, message map[string]interface{}) error) error {
|
||||
g.Log().Infof(ctx, "🔔 RabbitMQ 开始订阅: exchange=%s, queue=%s", cfg.Exchange, cfg.Queue)
|
||||
|
||||
// 设置 Qos (预取数量),控制每次推送的消息数量
|
||||
// prefetchCount: 未 ACK 消息的最大数量
|
||||
// prefetchSize: 未 ACK 消息的总大小(0 表示不限制)
|
||||
// global: false 表示仅应用于当前消费者
|
||||
prefetchCount := cfg.PrefetchCount
|
||||
if prefetchCount <= 0 {
|
||||
prefetchCount = 10 // 默认值为 10
|
||||
}
|
||||
if err := channel.Qos(prefetchCount, 0, false); err != nil {
|
||||
return fmt.Errorf("设置 Qos 失败: %w", err)
|
||||
}
|
||||
g.Log().Infof(ctx, "📊 设置 Prefetch Count: %d", prefetchCount)
|
||||
|
||||
msg, err := channel.Consume(
|
||||
cfg.Queue, // queue
|
||||
cfg.Queue, // consumer
|
||||
cfg.AutoAck, // auto-ack (根据配置决定)
|
||||
false, // exclusive
|
||||
false, // no-local
|
||||
false, // no-wait
|
||||
nil, // args
|
||||
queueName, // queue
|
||||
consumerName, // consumer
|
||||
autoAck, // auto-ack (根据配置决定)
|
||||
false, // exclusive
|
||||
false, // no-local
|
||||
false, // no-wait
|
||||
nil, // args
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("注册消费者失败: %w", err)
|
||||
@@ -207,7 +213,7 @@ func (q *rabbitMQMessageClient) createSubscribe(ctx context.Context, cfg *Rabbit
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
g.Log().Infof(ctx, "🔕 RabbitMQ 消费者停止: queue=%s", cfg.Queue)
|
||||
g.Log().Infof(ctx, "🔕 RabbitMQ 消费者停止: queueName=%s, consumerName=%s", queueName, consumerName)
|
||||
return
|
||||
case msg, ok := <-msg:
|
||||
if !ok {
|
||||
@@ -226,11 +232,11 @@ func (q *rabbitMQMessageClient) createSubscribe(ctx context.Context, cfg *Rabbit
|
||||
}
|
||||
}()
|
||||
|
||||
if err := q.handleMessageWithRetry(ctx, m, handler, cfg.MaxRetry); err != nil {
|
||||
if err := c.handleMessageWithRetryInternal(ctx, m, handler, autoAck); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ 消息处理失败(重试次数耗尽): %v", err)
|
||||
|
||||
// 仅在手动 ACK 模式下拒绝消息
|
||||
if !cfg.AutoAck {
|
||||
if !autoAck {
|
||||
// 拒绝消息不再重新入队(避免死循环)
|
||||
m.Nack(false, false)
|
||||
}
|
||||
@@ -238,7 +244,7 @@ func (q *rabbitMQMessageClient) createSubscribe(ctx context.Context, cfg *Rabbit
|
||||
}
|
||||
|
||||
// 仅在手动 ACK 模式下确认消息
|
||||
if cfg.AutoAck {
|
||||
if autoAck {
|
||||
if err := m.Ack(false); err != nil {
|
||||
g.Log().Errorf(ctx, "❌ ACK 消息失败: %v", err)
|
||||
}
|
||||
@@ -252,7 +258,7 @@ func (q *rabbitMQMessageClient) createSubscribe(ctx context.Context, cfg *Rabbit
|
||||
}
|
||||
|
||||
// handleMessageWithRetry 处理消息(支持重试)
|
||||
func (q *rabbitMQMessageClient) handleMessageWithRetry(ctx context.Context, msg amqp.Delivery, handler func(ctx context.Context, message map[string]interface{}) error, maxRetry int) error {
|
||||
func (c *rabbitMQ) handleMessageWithRetryInternal(ctx context.Context, msg amqp.Delivery, handler func(ctx context.Context, message map[string]interface{}) error, autoAck bool) error {
|
||||
var data map[string]interface{}
|
||||
|
||||
if err := json.Unmarshal(msg.Body, &data); err != nil {
|
||||
@@ -263,6 +269,7 @@ func (q *rabbitMQMessageClient) handleMessageWithRetry(ctx context.Context, msg
|
||||
}
|
||||
|
||||
// 重试逻辑
|
||||
const maxRetry = 3
|
||||
for attempt := 0; attempt <= maxRetry; attempt++ {
|
||||
if attempt > 0 {
|
||||
g.Log().Infof(ctx, "🔄 消息处理重试 (第%d次)", attempt)
|
||||
|
||||
Reference in New Issue
Block a user