feat: Add RabbitMQ message queue integration with exchange and queue management support

- Add rabbitmq/amqp091-go v1.11.0 dependency to go.mod
- Add MessageQueue struct with connection, channel, and url fields
- Add Message struct with Type, Payload, and Timestamp fields
- Add NewMessageQueue constructor with RABBITMQ_URL environment variable support and amqp://guest:guest@localhost:5672/ default
- Add Close method for connection cleanup
- Add DeclareQueue method with durable configuration
- Add Publish method with 5
This commit is contained in:
nutcas3
2026-04-25 06:15:11 +03:00
parent 19a4aaee01
commit 76d1f7b1ec
3 changed files with 217 additions and 0 deletions
+1
View File
@@ -140,6 +140,7 @@ require (
github.com/prometheus/client_golang v1.23.2
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/rabbitmq/amqp091-go v1.11.0
github.com/stripe/stripe-go/v82 v82.5.1
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
+3
View File
@@ -137,6 +137,7 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
@@ -263,6 +264,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+213
View File
@@ -0,0 +1,213 @@
package mq
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
// MessageQueue provides a simple interface for message queue operations
type MessageQueue struct {
connection *amqp.Connection
channel *amqp.Channel
url string
}
// Message represents a message to be sent/received
type Message struct {
Type string `json:"type"`
Payload map[string]interface{} `json:"payload"`
Timestamp time.Time `json:"timestamp"`
}
// NewMessageQueue creates a new message queue connection
func NewMessageQueue(url string) (*MessageQueue, error) {
if url == "" {
url = os.Getenv("RABBITMQ_URL")
if url == "" {
url = "amqp://guest:guest@localhost:5672/"
}
}
conn, err := amqp.Dial(url)
if err != nil {
return nil, fmt.Errorf("failed to connect to RabbitMQ: %w", err)
}
ch, err := conn.Channel()
if err != nil {
conn.Close()
return nil, fmt.Errorf("failed to open channel: %w", err)
}
mq := &MessageQueue{
connection: conn,
channel: ch,
url: url,
}
return mq, nil
}
// Close closes the connection to the message queue
func (mq *MessageQueue) Close() error {
if mq.channel != nil {
mq.channel.Close()
}
if mq.connection != nil {
return mq.connection.Close()
}
return nil
}
// DeclareQueue declares a queue
func (mq *MessageQueue) DeclareQueue(name string) error {
_, err := mq.channel.QueueDeclare(
name,
true, // durable
false, // autoDelete
false, // exclusive
false, // noWait
nil, // args
)
if err != nil {
return fmt.Errorf("failed to declare queue %s: %w", name, err)
}
return nil
}
// Publish publishes a message to a queue
func (mq *MessageQueue) Publish(queue string, msg Message) error {
msg.Timestamp = time.Now()
body, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = mq.channel.PublishWithContext(
ctx,
"", // exchange
queue, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "application/json",
DeliveryMode: amqp.Persistent,
Body: body,
Timestamp: time.Now(),
},
)
if err != nil {
return fmt.Errorf("failed to publish message: %w", err)
}
return nil
}
// Consume starts consuming messages from a queue
func (mq *MessageQueue) Consume(queue string, handler func(Message) error) error {
msgs, err := mq.channel.Consume(
queue,
"", // consumerTag
false, // autoAck
false, // exclusive
false, // noLocal
false, // noWait
nil, // args
)
if err != nil {
return fmt.Errorf("failed to register consumer: %w", err)
}
for msg := range msgs {
var mqMsg Message
if err := json.Unmarshal(msg.Body, &mqMsg); err != nil {
log.Printf("Failed to unmarshal message: %v", err)
msg.Nack(false, false)
continue
}
if err := handler(mqMsg); err != nil {
log.Printf("Handler error: %v", err)
msg.Nack(false, true) // requeue on error
continue
}
msg.Ack(false)
}
return nil
}
// PublishToExchange publishes a message to an exchange
func (mq *MessageQueue) PublishToExchange(exchange, routingKey string, msg Message) error {
msg.Timestamp = time.Now()
body, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = mq.channel.PublishWithContext(
ctx,
exchange, // exchange
routingKey, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "application/json",
DeliveryMode: amqp.Persistent,
Body: body,
Timestamp: time.Now(),
},
)
if err != nil {
return fmt.Errorf("failed to publish message: %w", err)
}
return nil
}
// DeclareExchange declares an exchange
func (mq *MessageQueue) DeclareExchange(name, kind string) error {
err := mq.channel.ExchangeDeclare(
name,
kind,
true, // durable
false, // autoDelete
false, // internal
false, // noWait
nil, // args
)
if err != nil {
return fmt.Errorf("failed to declare exchange %s: %w", name, err)
}
return nil
}
// BindQueue binds a queue to an exchange
func (mq *MessageQueue) BindQueue(queue, exchange, routingKey string) error {
err := mq.channel.QueueBind(
queue,
routingKey,
exchange,
false, // noWait
nil, // args
)
if err != nil {
return fmt.Errorf("failed to bind queue %s to exchange %s: %w", queue, exchange, err)
}
return nil
}