sycommon-python-lib 0.1.46__py3-none-any.whl → 0.1.57b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. sycommon/config/Config.py +29 -4
  2. sycommon/config/LangfuseConfig.py +15 -0
  3. sycommon/config/RerankerConfig.py +1 -0
  4. sycommon/config/SentryConfig.py +13 -0
  5. sycommon/database/async_base_db_service.py +36 -0
  6. sycommon/database/async_database_service.py +96 -0
  7. sycommon/llm/__init__.py +0 -0
  8. sycommon/llm/embedding.py +204 -0
  9. sycommon/llm/get_llm.py +37 -0
  10. sycommon/llm/llm_logger.py +126 -0
  11. sycommon/llm/llm_tokens.py +119 -0
  12. sycommon/llm/struct_token.py +192 -0
  13. sycommon/llm/sy_langfuse.py +103 -0
  14. sycommon/llm/usage_token.py +117 -0
  15. sycommon/logging/async_sql_logger.py +65 -0
  16. sycommon/logging/kafka_log.py +200 -434
  17. sycommon/logging/logger_levels.py +23 -0
  18. sycommon/middleware/context.py +2 -0
  19. sycommon/middleware/exception.py +10 -16
  20. sycommon/middleware/timeout.py +2 -1
  21. sycommon/middleware/traceid.py +179 -51
  22. sycommon/notice/__init__.py +0 -0
  23. sycommon/notice/uvicorn_monitor.py +200 -0
  24. sycommon/rabbitmq/rabbitmq_client.py +267 -290
  25. sycommon/rabbitmq/rabbitmq_pool.py +277 -465
  26. sycommon/rabbitmq/rabbitmq_service.py +23 -891
  27. sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
  28. sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
  29. sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
  30. sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
  31. sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
  32. sycommon/sentry/__init__.py +0 -0
  33. sycommon/sentry/sy_sentry.py +35 -0
  34. sycommon/services.py +144 -115
  35. sycommon/synacos/feign.py +18 -7
  36. sycommon/synacos/feign_client.py +26 -8
  37. sycommon/synacos/nacos_client_base.py +119 -0
  38. sycommon/synacos/nacos_config_manager.py +107 -0
  39. sycommon/synacos/nacos_heartbeat_manager.py +144 -0
  40. sycommon/synacos/nacos_service.py +65 -769
  41. sycommon/synacos/nacos_service_discovery.py +157 -0
  42. sycommon/synacos/nacos_service_registration.py +270 -0
  43. sycommon/tools/env.py +62 -0
  44. sycommon/tools/merge_headers.py +117 -0
  45. sycommon/tools/snowflake.py +238 -23
  46. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +18 -11
  47. sycommon_python_lib-0.1.57b1.dist-info/RECORD +89 -0
  48. sycommon_python_lib-0.1.46.dist-info/RECORD +0 -59
  49. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
  50. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
  51. {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,7 @@
1
- from aio_pika import Channel
2
- from typing import Optional
3
1
  import asyncio
4
2
  import json
5
- from typing import Callable, Coroutine, Dict, Any, Union
6
- from aio_pika import Message, DeliveryMode, ExchangeType
3
+ from typing import Optional, Callable, Coroutine, Dict, Any, Union
4
+ from aio_pika import Channel, Message, DeliveryMode, ExchangeType
7
5
  from aio_pika.abc import (
8
6
  AbstractExchange,
9
7
  AbstractQueue,
@@ -15,19 +13,12 @@ from sycommon.rabbitmq.rabbitmq_pool import RabbitMQConnectionPool
15
13
  from sycommon.logging.kafka_log import SYLogger
16
14
  from sycommon.models.mqmsg_model import MQMsgModel
17
15
 
18
-
19
16
  logger = SYLogger
20
17
 
21
18
 
22
19
  class RabbitMQClient:
23
20
  """
24
- RabbitMQ 客户端(支持消息发布、消费、自动重连、异常重试)
25
- 核心特性:
26
- 1. 基于连接池复用资源,性能优化
27
- 2. 连接/通道失效时自动重建,高可用(限制并发重连)
28
- 3. 消息发布支持重试+mandatory机制+超时控制,确保路由有效
29
- 4. 消费支持手动ACK/NACK
30
- 5. 兼容JSON/字符串/字典消息格式
21
+ RabbitMQ 客户端
31
22
  """
32
23
 
33
24
  def __init__(
@@ -36,41 +27,36 @@ class RabbitMQClient:
36
27
  exchange_name: str = "system.topic.exchange",
37
28
  exchange_type: str = "topic",
38
29
  queue_name: Optional[str] = None,
30
+ app_name: Optional[str] = None,
39
31
  routing_key: str = "#",
40
32
  durable: bool = True,
41
33
  auto_delete: bool = False,
42
34
  auto_parse_json: bool = True,
43
35
  create_if_not_exists: bool = True,
44
- prefetch_count: int = 2,
45
36
  **kwargs,
46
37
  ):
47
- # 依赖注入:连接池(必须已初始化)
48
38
  self.connection_pool = connection_pool
49
39
  if not self.connection_pool._initialized:
50
40
  raise RuntimeError("连接池未初始化,请先调用 connection_pool.init_pools()")
51
41
 
52
- # 交换机配置
53
42
  self.exchange_name = exchange_name.strip()
54
43
  try:
55
44
  self.exchange_type = ExchangeType(exchange_type.lower())
56
45
  except ValueError:
57
- SYLogger.warning(f"无效的exchange_type: {exchange_type},默认使用'topic'")
58
- self.exchange_type = ExchangeType.topic
46
+ logger.warning(f"无效的exchange_type: {exchange_type},默认使用'topic'")
47
+ self.exchange_type = ExchangeType.TOPIC
59
48
 
60
- # 队列配置
49
+ self.app_name = app_name.strip() if app_name else None
61
50
  self.queue_name = queue_name.strip() if queue_name else None
62
51
  self.routing_key = routing_key.strip() if routing_key else "#"
63
- self.durable = durable # 消息/队列持久化
64
- self.auto_delete = auto_delete # 无消费者时自动删除队列/交换机
65
- self.auto_parse_json = auto_parse_json # 自动解析JSON消息体
66
- self.create_if_not_exists = create_if_not_exists # 不存在则创建交换机/队列
67
-
68
- # 消费配置
69
- self.prefetch_count = max(1, prefetch_count) # 每次预取消息数(避免消息堆积)
52
+ self.durable = durable
53
+ self.auto_delete = auto_delete
54
+ self.auto_parse_json = auto_parse_json
55
+ self.create_if_not_exists = create_if_not_exists
70
56
 
71
- # 内部状态(资源+连接)
57
+ # 资源状态
72
58
  self._channel: Optional[Channel] = None
73
- self._channel_conn: Optional[AbstractRobustConnection] = None # 通道所属连接
59
+ self._channel_conn: Optional[AbstractRobustConnection] = None
74
60
  self._exchange: Optional[AbstractExchange] = None
75
61
  self._queue: Optional[AbstractQueue] = None
76
62
  self._consumer_tag: Optional[ConsumerTag] = None
@@ -78,266 +64,296 @@ class RabbitMQClient:
78
64
  MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]]] = None
79
65
  self._closed = False
80
66
 
81
- # 线程安全锁
67
+ # 并发控制
82
68
  self._consume_lock = asyncio.Lock()
83
69
  self._connect_lock = asyncio.Lock()
84
- # 跟踪连接关闭回调(用于后续移除)
70
+
71
+ # 防止并发重连覆盖
72
+ self._connecting = False
73
+ self._connect_condition = asyncio.Condition()
74
+
85
75
  self._conn_close_callback: Optional[Callable] = None
86
- # 控制重连频率的信号量(限制并发重连数,默认1个)
87
76
  self._reconnect_semaphore = asyncio.Semaphore(1)
88
- # 固定重连间隔15秒(全局统一)
89
- self._RECONNECT_INTERVAL = 15
90
- # 重连任务锁(确保同一时间只有一个重连任务)
91
- self._reconnect_task_lock = asyncio.Lock()
92
- # 跟踪当前重连任务(避免重复创建)
93
77
  self._current_reconnect_task: Optional[asyncio.Task] = None
94
- # 连接失败计数器(用于告警)
95
- self._reconnect_fail_count = 0
96
- # 连接失败告警阈值
97
- self._reconnect_alert_threshold = 5
78
+ self._RECONNECT_INTERVAL = 15
98
79
 
99
80
  @property
100
81
  async def is_connected(self) -> bool:
101
- """异步检查客户端连接状态(属性,不可调用)"""
102
82
  if self._closed:
103
83
  return False
104
84
  try:
105
- # 校验通道+连接+核心资源都有效
106
85
  return (
107
86
  self._channel and not self._channel.is_closed
108
87
  and self._channel_conn and not self._channel_conn.is_closed
109
88
  and self._exchange is not None
110
89
  and (not self.queue_name or self._queue is not None)
111
90
  )
112
- except Exception as e:
113
- SYLogger.warning(f"检查连接状态失败: {str(e)}")
91
+ except Exception:
114
92
  return False
115
93
 
94
+ async def _rebuild_resources(self) -> None:
95
+ if not self._channel or self._channel.is_closed:
96
+ raise RuntimeError("无有效通道,无法重建资源")
97
+
98
+ # 声明交换机
99
+ self._exchange = await self._channel.declare_exchange(
100
+ name=self.exchange_name,
101
+ type=self.exchange_type,
102
+ durable=self.durable,
103
+ auto_delete=self.auto_delete,
104
+ passive=not self.create_if_not_exists,
105
+ )
106
+ logger.info(f"交换机重建成功: {self.exchange_name}")
107
+
108
+ # 声明队列
109
+ if self.queue_name and self.queue_name.endswith(f".{self.app_name}"):
110
+ self._queue = await self._channel.declare_queue(
111
+ name=self.queue_name,
112
+ durable=self.durable,
113
+ auto_delete=self.auto_delete,
114
+ passive=not self.create_if_not_exists,
115
+ )
116
+ await self._queue.bind(exchange=self._exchange, routing_key=self.routing_key)
117
+ logger.info(f"队列重建成功: {self.queue_name}")
118
+
116
119
  async def connect(self) -> None:
117
120
  if self._closed:
118
121
  raise RuntimeError("客户端已关闭,无法重新连接")
119
122
 
123
+ # 1. 并发控制:使用 _connect_lock 保证只有一个协程在执行连接流程
120
124
  async with self._connect_lock:
121
- # 释放旧资源(保留原有回调清理逻辑)
122
- if self._channel and self._channel_conn:
125
+ # 如果已经在连了,等待其完成
126
+ if self._connecting:
127
+ logger.debug("连接正在进行中,等待现有连接完成...")
123
128
  try:
124
- if self._conn_close_callback and self._channel_conn:
129
+ # 等待条件变量,超时设为 60 秒防止死等
130
+ await asyncio.wait_for(
131
+ self._connect_condition.wait_for(
132
+ lambda: not self._connecting),
133
+ timeout=60.0
134
+ )
135
+ except asyncio.TimeoutError:
136
+ raise RuntimeError("等待连接超时")
137
+
138
+ # 等待结束后,再次检查状态
139
+ if not await self.is_connected:
140
+ raise RuntimeError("等待重连后,连接状态依然无效")
141
+ return
142
+
143
+ # 标记开始连接
144
+ self._connecting = True
145
+
146
+ # 释放 _connect_lock,允许其他协程读取状态,但在连接完成前阻止新的连接请求
147
+ # 注意:这里释放了 _connect_lock,但 self._connecting = True 阻止了新的连接流程
148
+
149
+ try:
150
+ # --- 阶段1: 清理旧资源 ---
151
+ # 重新获取锁进行资源清理
152
+ async with self._connect_lock:
153
+ was_consuming = self._consumer_tag is not None
154
+
155
+ if self._channel_conn and self._conn_close_callback:
156
+ try:
125
157
  self._channel_conn.close_callbacks.discard(
126
158
  self._conn_close_callback)
127
- await self.connection_pool.release_channel(self._channel, self._channel_conn)
128
- except Exception as e:
129
- SYLogger.warning(f"释放旧通道失败: {str(e)}")
130
- self._channel = None
131
- self._channel_conn = None
132
- self._exchange = None
133
- self._queue = None
134
- self._conn_close_callback = None
159
+ except Exception:
160
+ pass
135
161
 
136
- try:
137
- # 从连接池获取通道+连接(连接池返回的是 RobustChannel)
138
- self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
139
-
140
- def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
141
- """连接关闭回调:触发固定间隔重连"""
142
- SYLogger.warning(
143
- f"客户端连接关闭: {conn!r},原因: {exc}", exc_info=exc)
144
- self._reconnect_fail_count += 1
145
- # 超过阈值告警
146
- if self._reconnect_fail_count >= self._reconnect_alert_threshold:
147
- SYLogger.error(
148
- f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
149
- if not self._closed:
150
- asyncio.create_task(self._safe_reconnect())
151
-
152
- self._conn_close_callback = on_conn_closed
153
- if self._channel_conn:
154
- self._channel_conn.close_callbacks.add(
155
- self._conn_close_callback)
162
+ self._channel = None
163
+ self._channel_conn = None
164
+ self._exchange = None
165
+ self._queue = None
166
+ self._conn_close_callback = None
156
167
 
157
- # 2. 设置预取计数(限流)
158
- await self._channel.set_qos(prefetch_count=self.prefetch_count)
159
- SYLogger.debug(f"设置预取计数: {self.prefetch_count}")
160
-
161
- # 3. 低版本 RobustChannel 说明:默认启用异步发布确认,无显式确认方法
162
- SYLogger.debug(
163
- "基于 RobustChannel 异步发布确认(低版本 aio-pika 不支持显式确认方法)")
164
-
165
- # 4. 声明交换机
166
- self._exchange = await self._channel.declare_exchange(
167
- name=self.exchange_name,
168
- type=self.exchange_type,
169
- durable=self.durable,
170
- auto_delete=self.auto_delete,
171
- passive=not self.create_if_not_exists, # passive=True时,不存在则报错
172
- )
173
- SYLogger.info(
174
- f"交换机初始化成功: {self.exchange_name}(类型: {self.exchange_type.value})")
168
+ # --- 阶段2: 获取新连接 (耗时IO) ---
169
+ self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
170
+
171
+ # 设置回调
172
+ def on_conn_closed(conn, exc):
173
+ logger.warning(f"检测到连接关闭: {exc}")
174
+ if not self._closed and not self._connecting:
175
+ asyncio.create_task(self._safe_reconnect())
175
176
 
176
- # 5. 声明队列(如果配置了队列名)
177
- if self.queue_name:
177
+ self._conn_close_callback = on_conn_closed
178
+ if self._channel_conn:
179
+ self._channel_conn.close_callbacks.add(
180
+ self._conn_close_callback)
181
+
182
+ # 重建资源
183
+ await self._rebuild_resources()
184
+
185
+ # --- 阶段3: 恢复消费 ---
186
+ if was_consuming and self._message_handler and self.queue_name and self.queue_name.endswith(f".{self.app_name}"):
187
+ logger.info("🔄 检测到重连前处于消费状态,尝试自动恢复...")
188
+ try:
178
189
  self._queue = await self._channel.declare_queue(
179
190
  name=self.queue_name,
180
191
  durable=self.durable,
181
192
  auto_delete=self.auto_delete,
182
- passive=not self.create_if_not_exists,
183
- )
184
- # 绑定队列到交换机
185
- await self._queue.bind(
186
- exchange=self._exchange,
187
- routing_key=self.routing_key,
188
- )
189
- SYLogger.info(
190
- f"队列初始化成功: {self.queue_name} "
191
- f"(绑定交换机: {self.exchange_name}, routing_key: {self.routing_key})"
193
+ passive=False,
192
194
  )
195
+ await self._queue.bind(exchange=self._exchange, routing_key=self.routing_key)
196
+ self._consumer_tag = await self._queue.consume(self._process_message_callback)
197
+ logger.info(f"✅ 消费已自动恢复: {self._consumer_tag}")
198
+ except Exception as e:
199
+ logger.error(f"❌ 自动恢复消费失败: {e}")
200
+ self._consumer_tag = None
201
+ else:
202
+ self._consumer_tag = None
193
203
 
194
- # 重连成功,重置失败计数器
195
- self._reconnect_fail_count = 0
196
- SYLogger.info("客户端连接初始化完成")
197
- except Exception as e:
198
- SYLogger.error(f"客户端连接失败: {str(e)}", exc_info=True)
199
- # 清理异常状态
200
- if self._conn_close_callback and self._channel_conn:
204
+ logger.info("客户端连接初始化完成")
205
+
206
+ except Exception as e:
207
+ logger.error(f"客户端连接失败: {str(e)}", exc_info=True)
208
+
209
+ # 异常时清理资源
210
+ async with self._connect_lock:
211
+ if self._channel_conn and self._conn_close_callback:
201
212
  self._channel_conn.close_callbacks.discard(
202
213
  self._conn_close_callback)
203
- if self._channel and self._channel_conn:
204
- try:
205
- await self.connection_pool.release_channel(self._channel, self._channel_conn)
206
- except:
207
- pass
208
214
  self._channel = None
209
215
  self._channel_conn = None
210
- # 触发重连(固定间隔)
211
- if not self._closed:
212
- asyncio.create_task(self._safe_reconnect())
213
- raise
216
+ self._consumer_tag = None
217
+
218
+ raise
219
+
220
+ finally:
221
+ # 【关键修复】必须在持有 Condition 内部锁的情况下调用 notify_all
222
+ # 这里使用 async with self._connect_condition: 自动完成 acquire() ... notify_all() ... release()
223
+ async with self._connect_condition:
224
+ self._connecting = False
225
+ self._connect_condition.notify_all()
214
226
 
215
227
  async def _safe_reconnect(self):
216
- """安全重连:信号量控制并发+固定15秒间隔,避免短时间大量重连"""
217
- # 1. 信号量控制:限制同时进行的重连任务数(默认1个)
228
+ """安全重连任务(仅用于被动监听连接关闭)"""
218
229
  async with self._reconnect_semaphore:
219
- # 2. 检查是否已有重连任务在运行(双重保障)
220
- if self._current_reconnect_task and not self._current_reconnect_task.done():
221
- SYLogger.debug("已有重连任务在运行,跳过重复触发")
230
+ if self._closed:
222
231
  return
223
232
 
224
- async with self._reconnect_task_lock:
225
- if self._closed or await self.is_connected:
226
- SYLogger.debug("客户端已关闭或已连接,取消重连")
227
- return
233
+ # 如果已经在重连,直接忽略
234
+ if self._connecting:
235
+ return
228
236
 
229
- # 3. 固定15秒重连间隔(避免频繁重试)
230
- SYLogger.info(f"将在15秒后尝试重连...")
231
- await asyncio.sleep(self._RECONNECT_INTERVAL)
237
+ logger.info(f"将在{self._RECONNECT_INTERVAL}秒后尝试重连...")
238
+ await asyncio.sleep(self._RECONNECT_INTERVAL)
232
239
 
233
- if self._closed or await self.is_connected:
234
- SYLogger.debug("重连等待期间客户端状态变化,取消重连")
235
- return
240
+ if self._closed or await self.is_connected:
241
+ return
236
242
 
237
- try:
238
- # 4. 执行重连
239
- SYLogger.info("开始重连RabbitMQ客户端...")
240
- self._current_reconnect_task = asyncio.create_task(
241
- self.connect())
242
- await self._current_reconnect_task
243
- except Exception as e:
244
- SYLogger.warning(f"重连失败: {str(e)}")
245
- # 重连失败后,不主动触发下一次(等待连接关闭回调再次触发,避免死循环)
246
- finally:
247
- self._current_reconnect_task = None
243
+ try:
244
+ self._current_reconnect_task = asyncio.create_task(
245
+ self.connect())
246
+ await self._current_reconnect_task
247
+ except Exception as e:
248
+ logger.warning(f"重连失败: {str(e)}")
249
+ finally:
250
+ self._current_reconnect_task = None
248
251
 
249
- async def set_message_handler(
250
- self,
251
- handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]],
252
- ) -> None:
253
- """设置消息处理器(必须是协程函数)"""
252
+ async def set_message_handler(self, handler: Callable[..., Coroutine]) -> None:
254
253
  if not asyncio.iscoroutinefunction(handler):
255
- raise TypeError("消息处理器必须是协程函数(使用 async def 定义)")
256
-
254
+ raise TypeError("消息处理器必须是协程函数")
257
255
  async with self._consume_lock:
258
256
  self._message_handler = handler
259
- SYLogger.info("消息处理器设置成功")
257
+
258
+ async def _process_message_callback(self, message: AbstractIncomingMessage):
259
+ try:
260
+ msg_obj: MQMsgModel
261
+ if self.auto_parse_json:
262
+ try:
263
+ body_dict = json.loads(message.body.decode("utf-8"))
264
+ msg_obj = MQMsgModel(**body_dict)
265
+ except json.JSONDecodeError as e:
266
+ logger.error(f"JSON解析失败: {e}")
267
+ await message.nack(requeue=False)
268
+ return
269
+ else:
270
+ msg_obj = MQMsgModel(
271
+ body=message.body.decode("utf-8"),
272
+ routing_key=message.routing_key,
273
+ delivery_tag=message.delivery_tag,
274
+ traceId=message.headers.get("trace-id"),
275
+ )
276
+
277
+ SYLogger.set_trace_id(msg_obj.traceId)
278
+
279
+ if self._message_handler:
280
+ await self._message_handler(msg_obj, message)
281
+
282
+ await message.ack()
283
+
284
+ except Exception as e:
285
+ logger.error(f"消息处理异常: {e}", exc_info=True)
286
+ headers = dict(message.headers) if message.headers else {}
287
+ current_retry = int(headers.get("x-retry-count", 0))
288
+
289
+ if current_retry >= 3:
290
+ logger.warning(f"重试次数超限,丢弃消息: {message.delivery_tag}")
291
+ await message.reject(requeue=False)
292
+ else:
293
+ headers["x-retry-count"] = current_retry + 1
294
+ try:
295
+ new_msg = Message(
296
+ body=message.body,
297
+ headers=headers,
298
+ content_type=message.content_type,
299
+ delivery_mode=message.delivery_mode
300
+ )
301
+ # 这里的 publish 如果失败,会触发重连机制
302
+ # 但注意,当前是在回调线程中,建议做好异常捕获
303
+ await self._exchange.publish(new_msg, routing_key=message.routing_key)
304
+ await message.ack()
305
+ except Exception as pub_err:
306
+ logger.error(f"重试发布失败: {pub_err}")
307
+ await message.reject(requeue=False)
260
308
 
261
309
  async def start_consuming(self) -> Optional[ConsumerTag]:
262
- """启动消息消费(支持自动重连)"""
263
310
  if self._closed:
264
311
  raise RuntimeError("客户端已关闭,无法启动消费")
265
312
 
266
313
  async with self._consume_lock:
267
- # 1. 校验前置条件
268
314
  if not self._message_handler:
269
- raise RuntimeError("未设置消息处理器,请先调用 set_message_handler()")
315
+ raise RuntimeError("未设置消息处理器")
316
+
270
317
  if not await self.is_connected:
271
318
  await self.connect()
272
- if not self._queue:
273
- raise RuntimeError("未配置队列名,无法启动消费")
274
319
 
275
- # 2. 定义消费回调(包含异常处理和重连逻辑)
276
- async def consume_callback(message: AbstractIncomingMessage):
277
- try:
278
- # 解析消息体
279
- if self.auto_parse_json:
280
- try:
281
- body_dict = json.loads(
282
- message.body.decode("utf-8"))
283
- msg_obj = MQMsgModel(**body_dict)
284
- except json.JSONDecodeError as e:
285
- SYLogger.error(
286
- f"JSON消息解析失败: {str(e)},消息体: {message.body[:100]}...")
287
- await message.nack(requeue=False) # 解析失败,不重入队
288
- return
289
- else:
290
- msg_obj = MQMsgModel(
291
- body=message.body.decode("utf-8"),
292
- routing_key=message.routing_key,
293
- delivery_tag=message.delivery_tag,
294
- )
295
-
296
- # 调用消息处理器(必须await,避免协程未等待警告)
297
- await self._message_handler(msg_obj, message)
298
-
299
- # 手动ACK(消息处理成功)
300
- await message.ack()
301
- SYLogger.debug(
302
- f"消息处理成功,delivery_tag: {message.delivery_tag}")
303
-
304
- except Exception as e:
305
- SYLogger.error(
306
- f"消息处理失败,delivery_tag: {message.delivery_tag}",
307
- exc_info=True
320
+ if not self._queue:
321
+ if self.queue_name and self.queue_name.endswith(f".{self.app_name}"):
322
+ self._queue = await self._channel.declare_queue(
323
+ name=self.queue_name,
324
+ durable=self.durable,
325
+ auto_delete=self.auto_delete,
326
+ passive=not self.create_if_not_exists,
308
327
  )
309
- # 处理失败逻辑:首次失败重入队,再次失败丢弃
310
- if message.redelivered:
311
- SYLogger.warning(
312
- f"消息已重入队过,本次拒绝入队: {message.delivery_tag}")
313
- await message.reject(requeue=False)
314
- else:
315
- SYLogger.warning(f"消息重入队: {message.delivery_tag}")
316
- await message.nack(requeue=True)
317
-
318
- # 检查连接状态,失效则触发重连
319
- if not await self.is_connected:
320
- SYLogger.warning("连接已失效,触发客户端重连")
321
- asyncio.create_task(self._safe_reconnect())
322
-
323
- # 3. 启动消费
324
- self._consumer_tag = await self._queue.consume(consume_callback)
325
- SYLogger.info(
326
- f"开始消费队列: {self._queue.name},consumer_tag: {self._consumer_tag}"
327
- )
328
+ await self._queue.bind(exchange=self._exchange, routing_key=self.routing_key)
329
+ else:
330
+ raise RuntimeError("未配置队列名")
331
+
332
+ self._consumer_tag = await self._queue.consume(self._process_message_callback)
333
+ logger.info(
334
+ f"开始消费队列: {self._queue.name},tag: {self._consumer_tag}")
328
335
  return self._consumer_tag
329
336
 
330
337
  async def stop_consuming(self) -> None:
331
- """停止消息消费"""
332
338
  async with self._consume_lock:
333
- if self._consumer_tag and self._queue and not self._queue.is_closed:
339
+ if self._consumer_tag and self._queue and self._channel:
334
340
  try:
335
341
  await self._queue.cancel(self._consumer_tag)
336
- SYLogger.info(f"停止消费成功,consumer_tag: {self._consumer_tag}")
342
+ logger.info(f"停止消费成功: {self._consumer_tag}")
337
343
  except Exception as e:
338
- SYLogger.error(f"停止消费失败: {str(e)}", exc_info=True)
339
- finally:
340
- self._consumer_tag = None
344
+ logger.warning(f"停止消费异常: {e}")
345
+ self._consumer_tag = None
346
+
347
+ async def _handle_publish_failure(self):
348
+ try:
349
+ logger.info("检测到发布异常,强制连接池切换节点...")
350
+ await self.connection_pool.force_reconnect()
351
+ # 连接池切换后,必须刷新客户端资源
352
+ await self.connect()
353
+ logger.info("故障转移完成,资源已刷新")
354
+ except Exception as e:
355
+ logger.error(f"故障转移失败: {e}")
356
+ raise
341
357
 
342
358
  async def publish(
343
359
  self,
@@ -347,18 +363,9 @@ class RabbitMQClient:
347
363
  delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT,
348
364
  retry_count: int = 3,
349
365
  ) -> None:
350
- """
351
- 发布消息(支持自动重试、mandatory路由校验、5秒超时控制)
352
- :param message_body: 消息体(字符串/字典/MQMsgModel)
353
- :param headers: 消息头(可选)
354
- :param content_type: 内容类型(默认application/json)
355
- :param delivery_mode: 投递模式(PERSISTENT=持久化,TRANSIENT=非持久化)
356
- :param retry_count: 重试次数(默认3次)
357
- """
358
366
  if self._closed:
359
367
  raise RuntimeError("客户端已关闭,无法发布消息")
360
368
 
361
- # 处理消息体序列化
362
369
  try:
363
370
  if isinstance(message_body, MQMsgModel):
364
371
  body = json.dumps(message_body.to_dict(),
@@ -371,102 +378,72 @@ class RabbitMQClient:
371
378
  else:
372
379
  raise TypeError(f"不支持的消息体类型: {type(message_body)}")
373
380
  except Exception as e:
374
- SYLogger.error(f"消息体序列化失败: {str(e)}", exc_info=True)
381
+ logger.error(f"消息体序列化失败: {e}")
375
382
  raise
376
383
 
377
- # 构建消息对象
378
- message = Message(
379
- body=body,
380
- headers=headers or {},
381
- content_type=content_type,
382
- delivery_mode=delivery_mode,
383
- )
384
+ message = Message(body=body, headers=headers or {},
385
+ content_type=content_type, delivery_mode=delivery_mode)
386
+ last_exception = None
384
387
 
385
- # 发布重试逻辑
386
388
  for retry in range(retry_count):
387
389
  try:
388
- # 确保连接有效
389
390
  if not await self.is_connected:
390
- SYLogger.warning(f"发布消息前连接失效,触发重连(retry: {retry})")
391
391
  await self.connect()
392
392
 
393
- # 核心:发布消息(添加 mandatory=True timeout=5.0)
394
- publish_result = await self._exchange.publish(
393
+ result = await self._exchange.publish(
395
394
  message=message,
396
- routing_key=self.routing_key or self.queue_name or "#",
397
- mandatory=True, # 强制路由到至少一个队列,否则返回None
398
- timeout=5.0 # 5秒超时控制,避免无限阻塞
395
+ routing_key=self.routing_key,
396
+ mandatory=True,
397
+ timeout=5.0
399
398
  )
400
399
 
401
- # 处理 mandatory=True 结果:未路由到队列返回 None,直接抛出异常
402
- if publish_result is None:
403
- raise RuntimeError(
404
- f"消息未找到匹配的队列(routing_key: {self.routing_key}),mandatory=True 触发失败"
405
- )
400
+ if result is None:
401
+ raise RuntimeError(f"消息未找到匹配的队列: {self.routing_key}")
406
402
 
407
- # 低版本 RobustChannel 异步确认,无需显式等待,仅日志说明
408
- SYLogger.info(
409
- f"消息发布成功(retry: {retry}),routing_key: {self.routing_key},"
410
- f"delivery_mode: {delivery_mode.value},mandatory: True,timeout: 5.0s"
411
- )
403
+ logger.info(f"发布成功: {self.routing_key}")
412
404
  return
413
- except asyncio.TimeoutError:
414
- SYLogger.error(
415
- f"消息发布超时(retry: {retry}/{retry_count-1}),超时时间: 5.0s"
416
- )
405
+
417
406
  except RuntimeError as e:
418
- # 捕获 mandatory 未路由等业务异常
419
- SYLogger.error(
420
- f"消息发布业务失败(retry: {retry}/{retry_count-1}): {str(e)}"
421
- )
407
+ if "未找到匹配的队列" in str(e):
408
+ raise
409
+ last_exception = str(e)
410
+ await self._handle_publish_failure()
411
+
422
412
  except Exception as e:
423
- SYLogger.error(
424
- f"消息发布失败(retry: {retry}/{retry_count-1}): {str(e)}",
425
- exc_info=True
426
- )
427
- # 清理失效状态,下次重试重连
428
- self._exchange = None
429
- # 指数退避重试间隔(0.5s, 1s, 2s...)
430
- await asyncio.sleep(0.5 * (2 ** retry))
413
+ last_exception = str(e)
414
+ logger.error(f"发布异常: {e}")
415
+ await self._handle_publish_failure()
431
416
 
432
- # 所有重试失败,抛出最终异常
433
- raise RuntimeError(
434
- f"消息发布失败(已重试{retry_count}次),routing_key: {self.routing_key}"
435
- f"mandatory: True,timeout: 5.0s"
436
- )
417
+ await asyncio.sleep(5)
418
+
419
+ raise RuntimeError(f"消息发布最终失败: {last_exception}")
437
420
 
438
421
  async def close(self) -> None:
439
- """关闭客户端(移除回调+释放资源)"""
440
422
  self._closed = True
441
- SYLogger.info("开始关闭RabbitMQ客户端...")
423
+ logger.info("开始关闭RabbitMQ客户端...")
442
424
 
443
- # 停止重连任务
444
425
  if self._current_reconnect_task and not self._current_reconnect_task.done():
445
426
  self._current_reconnect_task.cancel()
446
427
  try:
447
428
  await self._current_reconnect_task
448
429
  except asyncio.CancelledError:
449
- SYLogger.debug("重连任务已取消")
430
+ pass
450
431
 
451
- # 1. 停止消费
452
432
  await self.stop_consuming()
453
433
 
454
- # 2. 释放通道到连接池
455
434
  async with self._connect_lock:
456
- if self._channel and self._channel_conn:
457
- try:
458
- # 移除连接关闭回调
459
- if self._conn_close_callback:
460
- self._channel_conn.close_callbacks.discard(
461
- self._conn_close_callback)
462
- await self.connection_pool.release_channel(self._channel, self._channel_conn)
463
- SYLogger.info("通道释放成功")
464
- except Exception as e:
465
- SYLogger.error(f"通道释放失败: {str(e)}", exc_info=True)
435
+ if self._conn_close_callback and self._channel_conn:
436
+ self._channel_conn.close_callbacks.discard(
437
+ self._conn_close_callback)
438
+
466
439
  self._channel = None
467
440
  self._channel_conn = None
468
441
  self._exchange = None
469
442
  self._queue = None
470
443
  self._message_handler = None
471
444
 
472
- SYLogger.info("RabbitMQ客户端已完全关闭")
445
+ # 确保唤醒可能正在等待 connect 的任务
446
+ self._connecting = False
447
+ self._connect_condition.notify_all()
448
+
449
+ logger.info("客户端已关闭")