129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
"""
|
||
RabbitMQ 死信队列处理模块
|
||
"""
|
||
|
||
import asyncio
|
||
import aio_pika
|
||
import json
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import Dict, Any
|
||
|
||
from .config import config
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DeadLetterConsumer:
|
||
"""死信队列消费者"""
|
||
|
||
def __init__(self):
|
||
"""初始化死信队列消费者"""
|
||
self.connection = None
|
||
self.channel = None
|
||
self.dead_letter_queue = None
|
||
|
||
async def connect(self):
|
||
"""建立连接"""
|
||
try:
|
||
connection_config = config.get_connection_config()
|
||
self.connection = await aio_pika.connect_robust(connection_config['uri'])
|
||
self.channel = await self.connection.channel()
|
||
await self.channel.set_qos(prefetch_count=connection_config['prefetch_count'])
|
||
|
||
# 声明死信队列
|
||
dead_letter_config = config.get_dead_letter_config()
|
||
self.dead_letter_queue = await self.channel.declare_queue(
|
||
dead_letter_config['dead_letter_queue'],
|
||
durable=True,
|
||
auto_delete=False
|
||
)
|
||
|
||
logger.info("[死信消费者] 已连接")
|
||
|
||
except Exception as e:
|
||
logger.error(f"[死信消费者] 连接失败: {e}")
|
||
raise
|
||
|
||
async def process_dead_letter_message(self, message: aio_pika.IncomingMessage):
|
||
"""处理死信消息"""
|
||
try:
|
||
# 解析死信消息
|
||
dead_letter_data = json.loads(message.body.decode('utf-8'))
|
||
original_message = dead_letter_data.get('original_message', {})
|
||
error_info = dead_letter_data.get('error_info', 'Unknown')
|
||
message_id = dead_letter_data.get('message_id', 'Unknown')
|
||
|
||
# 打印死信消息信息
|
||
logger.error("=" * 50)
|
||
logger.error("[死信消费者] 收到死信消息:")
|
||
logger.error(f"[死信消费者] 消息ID: {message_id}")
|
||
logger.error(f"[死信消费者] 消息内容: {json.dumps(original_message, ensure_ascii=False, indent=2)}")
|
||
logger.error(f"[死信消费者] 错误原因: {error_info}")
|
||
logger.error("=" * 50)
|
||
|
||
# 保存到数据库
|
||
await self.save_to_database(original_message, error_info, message_id)
|
||
|
||
# 确认死信消息
|
||
await message.ack()
|
||
logger.info(f"[死信消费者] 死信消息 {message_id} 处理完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"[死信消费者] 处理死信消息失败: {e}")
|
||
await message.nack(requeue=False) # 拒绝重新入队,避免一致失败出现的死循环
|
||
|
||
async def save_to_database(self, original_message: Dict[str, Any], error_info: str, message_id: str):
|
||
"""保存死信消息到数据库"""
|
||
# 模拟数据库保存操作
|
||
await asyncio.sleep(0.5)
|
||
|
||
# 构建数据库记录
|
||
db_record = {
|
||
'id': message_id,
|
||
'original_message': original_message,
|
||
'error_info': error_info,
|
||
'created_at': datetime.now().isoformat(),
|
||
'status': 'failed'
|
||
}
|
||
|
||
logger.info(f"[死信消费者] 💾 死信消息已保存到数据库: {message_id}")
|
||
logger.info(f"[死信消费者] 数据库记录: {json.dumps(db_record, ensure_ascii=False, indent=2)}")
|
||
|
||
# 这里可以添加实际的数据库操作
|
||
# 例如:await database.insert('dead_letter_messages', db_record)
|
||
|
||
async def start_consuming(self):
|
||
"""开始消费死信消息"""
|
||
self.consumer_tag = await self.dead_letter_queue.consume(self.process_dead_letter_message)
|
||
logger.info("[死信消费者] 开始消费死信消息...")
|
||
|
||
# 保持消费者运行
|
||
await asyncio.Future()
|
||
|
||
async def stop_consuming(self):
|
||
"""停止消费死信消息"""
|
||
if self.dead_letter_queue and self.consumer_tag:
|
||
await self.dead_letter_queue.cancel(self.consumer_tag)
|
||
logger.info("[死信消费者] 已停止消费死信消息")
|
||
|
||
async def close(self):
|
||
"""关闭连接"""
|
||
try:
|
||
await self.stop_consuming()
|
||
if self.connection:
|
||
await self.connection.close()
|
||
logger.info("[死信消费者] 连接已关闭")
|
||
except Exception as e:
|
||
logger.error(f"[死信消费者] 关闭连接时出错: {e}")
|
||
|
||
async def __aenter__(self):
|
||
"""异步上下文管理器入口"""
|
||
await self.connect()
|
||
return self
|
||
|
||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||
"""异步上下文管理器出口"""
|
||
await self.close()
|
||
|