package routing import ( amqp "github.com/rabbitmq/amqp091-go" "goskeleton/app/global/variable" "goskeleton/app/utils/rabbitmq/error_record" "time" ) func CreateConsumer(options ...OptionsConsumer) (*consumer, error) { // 获取配置信息 conn, err := amqp.Dial(variable.ConfigYml.GetString("RabbitMq.Routing.Addr")) exchangeType := variable.ConfigYml.GetString("RabbitMq.Routing.ExchangeType") exchangeName := variable.ConfigYml.GetString("RabbitMq.Routing.ExchangeName") queueName := variable.ConfigYml.GetString("RabbitMq.Routing.QueueName") durable := variable.ConfigYml.GetBool("RabbitMq.Routing.Durable") reconnectInterval := variable.ConfigYml.GetDuration("RabbitMq.Routing.OffLineReconnectIntervalSec") retryTimes := variable.ConfigYml.GetInt("RabbitMq.Routing.RetryCount") if err != nil { return nil, err } cons := &consumer{ connect: conn, exchangeType: exchangeType, exchangeName: exchangeName, queueName: queueName, durable: durable, connErr: conn.NotifyClose(make(chan *amqp.Error, 1)), offLineReconnectIntervalSec: reconnectInterval, retryTimes: retryTimes, receivedMsgBlocking: make(chan struct{}), status: 1, } // rabbitmq 如果启动了延迟消息队列模式。继续初始化一些参数 for _, val := range options { val.apply(cons) } return cons, nil } // 定义一个消息队列结构体:Routing 模型 type consumer struct { connect *amqp.Connection exchangeType string exchangeName string queueName string durable bool occurError error connErr chan *amqp.Error routeKey string // 断线重连,结构体内部使用 callbackForReceived func(receivedData string) // 断线重连,结构体内部使用 offLineReconnectIntervalSec time.Duration retryTimes int callbackOffLine func(err *amqp.Error) // 断线重连,结构体内部使用 enableDelayMsgPlugin bool // 是否使用延迟队列模式 receivedMsgBlocking chan struct{} // 接受消息时用于阻塞消息处理函数 status byte // 客户端状态:1=正常;0=异常 } // Received 接收、处理消息 func (c *consumer) Received(routeKey string, callbackFunDealMsg func(receivedData string)) { defer func() { c.close() }() // 将回调函数地址赋值给结构体变量,用于掉线重连使用 c.routeKey = routeKey c.callbackForReceived = callbackFunDealMsg go func(key string) { ch, err := c.connect.Channel() c.occurError = error_record.ErrorDeal(err) defer func() { _ = ch.Close() }() // 声明exchange交换机 err = ch.ExchangeDeclare( c.exchangeName, //exchange name c.exchangeType, //exchange kind c.durable, //数据是否持久化 !c.durable, //所有连接断开时,交换机是否删除 false, false, nil, ) // 声明队列 queue, err := ch.QueueDeclare( c.queueName, c.durable, true, false, false, nil, ) c.occurError = error_record.ErrorDeal(err) //队列绑定 err = ch.QueueBind( queue.Name, key, // routing 模式,生产者会将消息投递至交换机的route_key, 消费者匹配不同的key获取消息、处理 c.exchangeName, false, nil, ) c.occurError = error_record.ErrorDeal(err) if err != nil { return } msgs, err := ch.Consume( queue.Name, // 队列名称 "", // 消费者标记,请确保在一个消息频道唯一 true, //是否自动确认,这里设置为 true,自动确认 false, //是否私有队列,false标识允许多个 consumer 向该队列投递消息,true 表示独占 false, //RabbitMQ不支持noLocal标志。 false, // 队列如果已经在服务器声明,设置为 true ,否则设置为 false; nil, ) c.occurError = error_record.ErrorDeal(err) if err == nil { for { select { case msg := <-msgs: // 消息处理 if c.status == 1 && len(msg.Body) > 0 { callbackFunDealMsg(string(msg.Body)) } else if c.status == 0 { return } } } } else { return } }(routeKey) if _, isOk := <-c.receivedMsgBlocking; isOk { c.status = 0 close(c.receivedMsgBlocking) } } // OnConnectionError 消费者端,掉线重连失败后的错误回调 func (c *consumer) OnConnectionError(callbackOfflineErr func(err *amqp.Error)) { c.callbackOffLine = callbackOfflineErr go func() { select { case err := <-c.connErr: var i = 1 for i = 1; i <= c.retryTimes; i++ { // 自动重连机制 time.Sleep(c.offLineReconnectIntervalSec * time.Second) // 发生连接错误时,中断原来的消息监听(包括关闭连接) if c.status == 1 { c.receivedMsgBlocking <- struct{}{} } conn, err := CreateConsumer() if err != nil { continue } else { go func() { c.connErr = conn.connect.NotifyClose(make(chan *amqp.Error, 1)) go conn.OnConnectionError(c.callbackOffLine) conn.Received(c.routeKey, c.callbackForReceived) }() // 新的客户端重连成功后,释放旧的回调函数 - OnConnectionError if c.status == 0 { return } break } } if i > c.retryTimes { callbackOfflineErr(err) // 如果超过最大重连次数,同样需要释放回调函数 - OnConnectionError if c.status == 0 { return } } } }() } // close 关闭连接 func (c *consumer) close() { _ = c.connect.Close() }