first commit
This commit is contained in:
363
internal/queue/README.md
Normal file
363
internal/queue/README.md
Normal file
@@ -0,0 +1,363 @@
|
||||
# RabbitMQ 服务使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
这个 RabbitMQ 服务实现了区块链支付系统的消息队列功能,包括:
|
||||
|
||||
### 消费队列(监听)
|
||||
1. **充值队列 (topup)** - 监听用户充值请求
|
||||
2. **提现队列 (withdraw)** - 监听用户提现请求
|
||||
3. **支付队列 (pay)** - 监听用户支付请求
|
||||
|
||||
### 发布队列(响应)
|
||||
1. **充值响应队列 (topup_resp)** - 发送充值确认结果
|
||||
2. **提现响应队列 (withdraw_resp)** - 发送提现确认结果
|
||||
3. **支付响应队列 (pay_resp)** - 发送支付确认结果
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
go get github.com/rabbitmq/amqp091-go
|
||||
```
|
||||
|
||||
### 2. 配置文件示例 (config.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"rmq_config": {
|
||||
"sub_addr": "amqp://username:password@localhost:5672",
|
||||
"pay": {
|
||||
"queue": "pay.auto.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.auto.routing.key"]
|
||||
},
|
||||
"topup": {
|
||||
"queue": "pay.recharge.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.recharge.routing.key"]
|
||||
},
|
||||
"withdraw": {
|
||||
"queue": "pay.withdraw.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.withdraw.routing.key"]
|
||||
},
|
||||
"pay_resp": {
|
||||
"queue": "pay.auto.return.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.auto.return.routing.key"]
|
||||
},
|
||||
"topup_resp": {
|
||||
"queue": "pay.recharge.return.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.recharge.return.routing.key"]
|
||||
},
|
||||
"withdraw_resp": {
|
||||
"queue": "pay.withdraw.return.queue",
|
||||
"exchange": "pay.exchange",
|
||||
"routing": ["pay.withdraw.return.routing.key"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
message "m2pool-payment/internal/msg"
|
||||
rmq "m2pool-payment/internal/queue"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载配置
|
||||
config := loadConfig() // 你的配置加载逻辑
|
||||
|
||||
// 创建 RabbitMQ 服务
|
||||
rmqServer, err := rmq.NewRabbitMQServer(config.RMQConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("创建 RabbitMQ 服务失败: %v", err)
|
||||
}
|
||||
defer rmqServer.Close()
|
||||
|
||||
// 设置消息处理回调
|
||||
rmqServer.OnTopupMsg = func(msg message.TopupMsg_req) {
|
||||
log.Printf("收到充值请求: %+v", msg)
|
||||
// 处理充值逻辑...
|
||||
// 添加地址监听等
|
||||
}
|
||||
|
||||
rmqServer.OnWithdrawMsg = func(msg message.WithdrawMsg_req) {
|
||||
log.Printf("收到提现请求: %+v", msg)
|
||||
// 处理提现逻辑...
|
||||
// 调用区块链转账
|
||||
}
|
||||
|
||||
rmqServer.OnPayMsg = func(msg message.PayMsg_req) {
|
||||
log.Printf("收到支付请求: %+v", msg)
|
||||
// 处理支付逻辑...
|
||||
// 调用区块链转账
|
||||
}
|
||||
|
||||
// 启动监听
|
||||
if err := rmqServer.Start(); err != nil {
|
||||
log.Fatalf("启动监听失败: %v", err)
|
||||
}
|
||||
|
||||
// 发送响应消息示例
|
||||
rmqServer.PublishTopupResp(message.TopupMsg_resp{
|
||||
Address: "0x123...",
|
||||
Status: 1,
|
||||
Chain: "ETH",
|
||||
Symbol: "USDT",
|
||||
Amount: 100.0,
|
||||
TxHash: "0xabc...",
|
||||
})
|
||||
|
||||
// 保持运行
|
||||
select {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 说明
|
||||
|
||||
### 创建服务
|
||||
|
||||
```go
|
||||
func NewRabbitMQServer(config message.RMQConfig) (*RabbitMQServer, error)
|
||||
```
|
||||
|
||||
创建一个新的 RabbitMQ 服务实例。
|
||||
|
||||
**参数:**
|
||||
- `config`: RabbitMQ 配置
|
||||
|
||||
**返回:**
|
||||
- `*RabbitMQServer`: 服务实例
|
||||
- `error`: 错误信息
|
||||
|
||||
---
|
||||
|
||||
### 启动监听
|
||||
|
||||
```go
|
||||
func (r *RabbitMQServer) Start() error
|
||||
```
|
||||
|
||||
启动所有队列的监听(topup, withdraw, pay)。
|
||||
|
||||
---
|
||||
|
||||
### 消息回调设置
|
||||
|
||||
```go
|
||||
// 充值请求回调
|
||||
rmqServer.OnTopupMsg = func(msg message.TopupMsg_req) {
|
||||
// 处理逻辑
|
||||
}
|
||||
|
||||
// 提现请求回调
|
||||
rmqServer.OnWithdrawMsg = func(msg message.WithdrawMsg_req) {
|
||||
// 处理逻辑
|
||||
}
|
||||
|
||||
// 支付请求回调
|
||||
rmqServer.OnPayMsg = func(msg message.PayMsg_req) {
|
||||
// 处理逻辑
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 发布响应消息
|
||||
|
||||
```go
|
||||
// 发布充值响应
|
||||
func (r *RabbitMQServer) PublishTopupResp(resp message.TopupMsg_resp) error
|
||||
|
||||
// 发布提现响应
|
||||
func (r *RabbitMQServer) PublishWithdrawResp(resp message.WithdrawMsg_resp) error
|
||||
|
||||
// 发布支付响应
|
||||
func (r *RabbitMQServer) PublishPayResp(resp message.PayMsg_resp) error
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 关闭服务
|
||||
|
||||
```go
|
||||
func (r *RabbitMQServer) Close() error
|
||||
```
|
||||
|
||||
优雅关闭 RabbitMQ 连接和所有监听。
|
||||
|
||||
---
|
||||
|
||||
## 消息格式
|
||||
|
||||
### 充值请求 (TopupMsg_req)
|
||||
```json
|
||||
{
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"address": "0x123...",
|
||||
"timestamp": 1234567890,
|
||||
"sign": "signature_string"
|
||||
}
|
||||
```
|
||||
|
||||
### 充值响应 (TopupMsg_resp)
|
||||
```json
|
||||
{
|
||||
"address": "0x123...",
|
||||
"status": 1,
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"amount": 100.5,
|
||||
"tx_hash": "0xabc..."
|
||||
}
|
||||
```
|
||||
|
||||
### 提现请求 (WithdrawMsg_req)
|
||||
```json
|
||||
{
|
||||
"queue_id": "queue_123",
|
||||
"from_address": "0x123...",
|
||||
"to_address": "0x456...",
|
||||
"amount": 50.0,
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"timestamp": 1234567890,
|
||||
"sign": "signature_string"
|
||||
}
|
||||
```
|
||||
|
||||
### 提现响应 (WithdrawMsg_resp)
|
||||
```json
|
||||
{
|
||||
"queue_id": "queue_123",
|
||||
"status": 1,
|
||||
"amount": 50.0,
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"tx_hash": "0xdef..."
|
||||
}
|
||||
```
|
||||
|
||||
### 支付请求 (PayMsg_req)
|
||||
```json
|
||||
{
|
||||
"queue_id": "queue_456",
|
||||
"from_address": "0x123...",
|
||||
"to_address": "0x789...",
|
||||
"amount": 200.0,
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"order_id": "order_789",
|
||||
"timestamp": 1234567890,
|
||||
"sign": "signature_string"
|
||||
}
|
||||
```
|
||||
|
||||
### 支付响应 (PayMsg_resp)
|
||||
```json
|
||||
{
|
||||
"queue_id": "queue_456",
|
||||
"status": 1,
|
||||
"amount": 200.0,
|
||||
"chain": "ETH",
|
||||
"symbol": "USDT",
|
||||
"order_id": "order_789",
|
||||
"tx_hash": "0xghi..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 状态码说明
|
||||
|
||||
- `status = 0` - 失败
|
||||
- `status = 1` - 成功
|
||||
- `status = 2` - 待确认(仅用于链上交易)
|
||||
|
||||
---
|
||||
|
||||
## 特性
|
||||
|
||||
✅ **自动重连** - 连接断开时自动重连
|
||||
✅ **消息持久化** - 消息不会丢失
|
||||
✅ **手动确认** - 处理成功后才确认消息
|
||||
✅ **并发安全** - 支持多 goroutine 并发发布
|
||||
✅ **优雅关闭** - 支持优雅关闭所有连接
|
||||
✅ **错误处理** - 完善的错误日志和重试机制
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **连接地址格式**: `amqp://username:password@host:port/`
|
||||
2. **消息确认**: 只有处理成功的消息才会被确认,失败的消息会重新入队
|
||||
3. **并发安全**: 发布消息时使用了互斥锁,可以安全地从多个 goroutine 调用
|
||||
4. **重连机制**: 连接断开时会自动重连,间隔 3 秒
|
||||
5. **消息持久化**: 消息和队列都是持久化的,重启后不会丢失
|
||||
|
||||
---
|
||||
|
||||
## 工作流程
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ 业务系统 │ ------> │ RabbitMQ │ ------> │ 支付系统 │
|
||||
│ │ 充值/ │ (消息队列) │ 消费 │ (本系统) │
|
||||
│ │ 提现/ │ │ │ │
|
||||
│ │ 支付 │ │ │ │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
|
|
||||
| 监听链上
|
||||
v
|
||||
┌─────────────┐
|
||||
│ 区块链节点 │
|
||||
│ (ETH) │
|
||||
└─────────────┘
|
||||
|
|
||||
| 交易确认
|
||||
v
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ 业务系统 │ <------ │ RabbitMQ │ <------ │ 支付系统 │
|
||||
│ │ 响应 │ (响应队列) │ 发布 │ (本系统) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 连接失败
|
||||
- 检查 RabbitMQ 服务是否运行
|
||||
- 检查用户名密码是否正确
|
||||
- 检查网络连接和端口(默认 5672)
|
||||
|
||||
### 消息未被消费
|
||||
- 检查队列绑定是否正确
|
||||
- 检查 routing key 是否匹配
|
||||
- 查看 RabbitMQ 管理界面的队列状态
|
||||
|
||||
### 消息重复消费
|
||||
- 确保消息处理成功后调用了 Ack
|
||||
- 检查是否有多个消费者实例
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
344
internal/queue/rabbitmq.go
Normal file
344
internal/queue/rabbitmq.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package rmq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
message "m2pool-payment/internal/msg"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
// RabbitMQServer RabbitMQ 服务
|
||||
type RabbitMQServer struct {
|
||||
config message.RMQConfig
|
||||
conn *amqp.Connection
|
||||
channel *amqp.Channel
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// 消息处理回调函数
|
||||
OnTopupMsg func(message.TopupMsg_req) // 充值请求回调
|
||||
OnWithdrawMsg func(message.WithdrawMsg_req) // 提现请求回调
|
||||
OnPayMsg func(message.PayMsg_req) // 支付请求回调
|
||||
}
|
||||
|
||||
// NewRabbitMQServer 创建 RabbitMQ 服务
|
||||
func NewRabbitMQServer(config message.RMQConfig) (*RabbitMQServer, error) {
|
||||
// 创建连接
|
||||
conn, err := amqp.Dial(config.SubAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to RabbitMQ: %w", err)
|
||||
}
|
||||
|
||||
// 创建通道
|
||||
channel, err := conn.Channel()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to open a channel: %w", err)
|
||||
}
|
||||
|
||||
// 创建可取消的 context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
server := &RabbitMQServer{
|
||||
config: config,
|
||||
conn: conn,
|
||||
channel: channel,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// 初始化队列和交换机
|
||||
if err := server.setupQueuesAndExchanges(); err != nil {
|
||||
server.Close()
|
||||
return nil, fmt.Errorf("failed to setup queues: %w", err)
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// setupQueuesAndExchanges 设置队列和交换机
|
||||
func (r *RabbitMQServer) setupQueuesAndExchanges() error {
|
||||
configs := []message.QueueConfig{
|
||||
r.config.PayConfig,
|
||||
r.config.TopUpConfig,
|
||||
r.config.WithdrawConfig,
|
||||
r.config.PayRespConfig,
|
||||
r.config.TopUpRespConfig,
|
||||
r.config.WithdrawRespConfig,
|
||||
}
|
||||
|
||||
for _, cfg := range configs {
|
||||
// 声明交换机
|
||||
err := r.channel.ExchangeDeclare(
|
||||
cfg.ExchangeName, // 交换机名称
|
||||
"direct", // 类型:direct(与现有交换机类型一致)
|
||||
true, // durable
|
||||
false, // auto-deleted
|
||||
false, // internal
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to declare exchange %s: %w", cfg.ExchangeName, err)
|
||||
}
|
||||
|
||||
// 声明队列
|
||||
_, err = r.channel.QueueDeclare(
|
||||
cfg.QueueName, // 队列名称
|
||||
true, // durable
|
||||
false, // delete when unused
|
||||
false, // exclusive
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to declare queue %s: %w", cfg.QueueName, err)
|
||||
}
|
||||
|
||||
// 绑定队列到交换机
|
||||
for _, routingKey := range cfg.Routing {
|
||||
err = r.channel.QueueBind(
|
||||
cfg.QueueName, // 队列名称
|
||||
routingKey, // routing key
|
||||
cfg.ExchangeName, // 交换机名称
|
||||
false, // no-wait
|
||||
nil, // arguments
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bind queue %s to exchange %s with key %s: %w",
|
||||
cfg.QueueName, cfg.ExchangeName, routingKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("✅ 队列配置成功: Queue=%s, Exchange=%s, RoutingKeys=%v",
|
||||
// cfg.QueueName, cfg.ExchangeName, cfg.Routing)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动监听所有队列
|
||||
func (r *RabbitMQServer) Start() error {
|
||||
// 启动充值消息监听
|
||||
go r.consumeTopup()
|
||||
// 启动提现消息监听
|
||||
go r.consumeWithdraw()
|
||||
// 启动支付消息监听
|
||||
go r.consumePay()
|
||||
|
||||
// log.Println("🚀 RabbitMQ 服务启动成功,开始监听消息...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// consumeTopup 消费充值消息
|
||||
func (r *RabbitMQServer) consumeTopup() {
|
||||
r.consumeQueue(
|
||||
r.config.TopUpConfig.QueueName,
|
||||
"topup",
|
||||
func(body []byte) error {
|
||||
var msg message.TopupMsg_req
|
||||
if err := json.Unmarshal(body, &msg); err != nil {
|
||||
return fmt.Errorf("failed to parse topup message: %w", err)
|
||||
}
|
||||
log.Printf("📥 [RMQ] 收到充值请求: Chain=%s, Symbol=%s, Address=%s",
|
||||
msg.Chain, msg.Symbol, msg.Address)
|
||||
|
||||
if r.OnTopupMsg != nil {
|
||||
r.OnTopupMsg(msg)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// consumeWithdraw 消费提现消息
|
||||
func (r *RabbitMQServer) consumeWithdraw() {
|
||||
r.consumeQueue(
|
||||
r.config.WithdrawConfig.QueueName,
|
||||
"withdraw",
|
||||
func(body []byte) error {
|
||||
var msg message.WithdrawMsg_req
|
||||
if err := json.Unmarshal(body, &msg); err != nil {
|
||||
return fmt.Errorf("failed to parse withdraw message: %w", err)
|
||||
}
|
||||
log.Printf("📥 [RMQ] 收到提现请求: QueueId=%s, From=%s, To=%s, Amount=%.2f %s",
|
||||
msg.QueueId, msg.FromAddress, msg.ToAddress, msg.Amount, msg.Symbol)
|
||||
|
||||
if r.OnWithdrawMsg != nil {
|
||||
r.OnWithdrawMsg(msg)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// consumePay 消费支付消息
|
||||
func (r *RabbitMQServer) consumePay() {
|
||||
r.consumeQueue(
|
||||
r.config.PayConfig.QueueName,
|
||||
"pay",
|
||||
func(body []byte) error {
|
||||
var msg message.PayMsg_req
|
||||
if err := json.Unmarshal(body, &msg); err != nil {
|
||||
return fmt.Errorf("failed to parse pay message: %w", err)
|
||||
}
|
||||
log.Printf("📥 [RMQ] 收到支付请求: QueueId=%s, OrderId=%s, From=%s, To=%s, Amount=%.2f %s",
|
||||
msg.QueueId, msg.OrderId, msg.FromAddress, msg.ToAddress, msg.Amount, msg.Symbol)
|
||||
|
||||
if r.OnPayMsg != nil {
|
||||
r.OnPayMsg(msg)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// consumeQueue 通用队列消费方法
|
||||
func (r *RabbitMQServer) consumeQueue(queueName, msgType string, handler func([]byte) error) {
|
||||
for {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
// log.Printf("🛑 停止监听队列: %s", queueName)
|
||||
return
|
||||
default:
|
||||
msgs, err := r.channel.Consume(
|
||||
queueName, // 队列名称
|
||||
"", // consumer tag
|
||||
false, // auto-ack (设置为false,手动确认)
|
||||
false, // exclusive
|
||||
false, // no-local
|
||||
false, // no-wait
|
||||
nil, // args
|
||||
)
|
||||
if err != nil {
|
||||
// log.Printf("❌ 消费队列 %s 失败: %v, 3秒后重试...", queueName, err)
|
||||
time.Sleep(3 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.Printf("✅ 开始监听队列: %s (%s)", queueName, msgType)
|
||||
|
||||
for msg := range msgs {
|
||||
err := handler(msg.Body)
|
||||
if err != nil {
|
||||
// log.Printf("⚠️ 处理 %s 消息失败: %v", msgType, err)
|
||||
// 消息处理失败,不确认,让消息重新入队
|
||||
msg.Nack(false, true)
|
||||
} else {
|
||||
// 消息处理成功,确认消息
|
||||
msg.Ack(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 channel 关闭,等待后重连
|
||||
// log.Printf("⚠️ 队列 %s 连接断开,3秒后重连...", queueName)
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PublishTopupResp 发布充值响应
|
||||
func (r *RabbitMQServer) PublishTopupResp(resp message.TopupMsg_resp) error {
|
||||
return r.publishMessage(
|
||||
r.config.TopUpRespConfig,
|
||||
resp,
|
||||
fmt.Sprintf("充值响应: Address=%s, Status=%d, TxHash=%s",
|
||||
resp.Address, resp.Status, resp.TxHash),
|
||||
)
|
||||
}
|
||||
|
||||
// PublishWithdrawResp 发布提现响应
|
||||
func (r *RabbitMQServer) PublishWithdrawResp(resp message.WithdrawMsg_resp) error {
|
||||
return r.publishMessage(
|
||||
r.config.WithdrawRespConfig,
|
||||
resp,
|
||||
fmt.Sprintf("提现响应: QueueId=%s, Status=%d, TxHash=%s",
|
||||
resp.QueueId, resp.Status, resp.TxHash),
|
||||
)
|
||||
}
|
||||
|
||||
// PublishPayResp 发布支付响应
|
||||
func (r *RabbitMQServer) PublishPayResp(resp message.PayMsg_resp) error {
|
||||
return r.publishMessage(
|
||||
r.config.PayRespConfig,
|
||||
resp,
|
||||
fmt.Sprintf("支付响应: QueueId=%s, OrderId=%s, Status=%d, TxHash=%s",
|
||||
resp.QueueId, resp.OrderId, resp.Status, resp.TxHash),
|
||||
)
|
||||
}
|
||||
|
||||
// publishMessage 通用消息发布方法
|
||||
func (r *RabbitMQServer) publishMessage(config message.QueueConfig, msg interface{}, logMsg string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// 序列化消息
|
||||
body, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal message: %w", err)
|
||||
}
|
||||
|
||||
// 使用第一个 routing key(如果有多个)
|
||||
routingKey := ""
|
||||
if len(config.Routing) > 0 {
|
||||
routingKey = config.Routing[0]
|
||||
}
|
||||
|
||||
// 发布消息
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = r.channel.PublishWithContext(
|
||||
ctx,
|
||||
config.ExchangeName, // 交换机
|
||||
routingKey, // routing key
|
||||
false, // mandatory
|
||||
false, // immediate
|
||||
amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
Body: body,
|
||||
DeliveryMode: amqp.Persistent, // 持久化消息
|
||||
Timestamp: time.Now(),
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to publish message: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("📤 [RMQ] 发送%s", logMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close 关闭连接
|
||||
func (r *RabbitMQServer) Close() error {
|
||||
// log.Println("🛑 正在关闭 RabbitMQ 服务...")
|
||||
|
||||
r.cancel() // 取消所有 goroutine
|
||||
|
||||
if r.channel != nil {
|
||||
if err := r.channel.Close(); err != nil {
|
||||
// log.Printf("⚠️ 关闭 channel 失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.conn != nil {
|
||||
if err := r.conn.Close(); err != nil {
|
||||
// log.Printf("⚠️ 关闭连接失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// log.Println("✅ RabbitMQ 服务已关闭")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsConnected 检查连接状态
|
||||
func (r *RabbitMQServer) IsConnected() bool {
|
||||
return r.conn != nil && !r.conn.IsClosed()
|
||||
}
|
||||
Reference in New Issue
Block a user