sycommon-python-lib 0.1.41__tar.gz → 0.1.45__tar.gz

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 (65) hide show
  1. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/PKG-INFO +7 -7
  2. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/pyproject.toml +11 -7
  3. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/traceid.py +1 -1
  4. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/rabbitmq/rabbitmq_client.py +133 -29
  5. sycommon_python_lib-0.1.45/src/sycommon/rabbitmq/rabbitmq_pool.py +404 -0
  6. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/rabbitmq/rabbitmq_service.py +195 -83
  7. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/services.py +2 -1
  8. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/feign.py +9 -8
  9. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/nacos_service.py +73 -52
  10. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/PKG-INFO +7 -7
  11. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/requires.txt +6 -6
  12. sycommon_python_lib-0.1.41/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -312
  13. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/README.md +0 -0
  14. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/setup.cfg +0 -0
  15. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/command/cli.py +0 -0
  16. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/__init__.py +0 -0
  17. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/Config.py +0 -0
  18. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/DatabaseConfig.py +0 -0
  19. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/EmbeddingConfig.py +0 -0
  20. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/LLMConfig.py +0 -0
  21. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/MQConfig.py +0 -0
  22. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/RerankerConfig.py +0 -0
  23. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/config/__init__.py +0 -0
  24. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/database/base_db_service.py +0 -0
  25. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/database/database_service.py +0 -0
  26. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/health/__init__.py +0 -0
  27. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/health/health_check.py +0 -0
  28. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/health/metrics.py +0 -0
  29. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/health/ping.py +0 -0
  30. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/logging/__init__.py +0 -0
  31. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/logging/kafka_log.py +0 -0
  32. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/logging/logger_wrapper.py +0 -0
  33. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/logging/sql_logger.py +0 -0
  34. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/__init__.py +0 -0
  35. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/context.py +0 -0
  36. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/cors.py +0 -0
  37. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/docs.py +0 -0
  38. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/exception.py +0 -0
  39. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/middleware.py +0 -0
  40. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/monitor_memory.py +0 -0
  41. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/mq.py +0 -0
  42. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/middleware/timeout.py +0 -0
  43. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/__init__.py +0 -0
  44. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/base_http.py +0 -0
  45. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/log.py +0 -0
  46. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/mqlistener_config.py +0 -0
  47. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/mqmsg_model.py +0 -0
  48. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/mqsend_config.py +0 -0
  49. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/models/sso_user.py +0 -0
  50. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/sse/__init__.py +0 -0
  51. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/sse/event.py +0 -0
  52. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/sse/sse.py +0 -0
  53. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/__init__.py +0 -0
  54. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/example.py +0 -0
  55. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/example2.py +0 -0
  56. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/feign_client.py +0 -0
  57. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/synacos/param.py +0 -0
  58. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/tools/__init__.py +0 -0
  59. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/tools/docs.py +0 -0
  60. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/tools/snowflake.py +0 -0
  61. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon/tools/timing.py +0 -0
  62. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
  63. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
  64. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
  65. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.45}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
@@ -1,22 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.41
3
+ Version: 0.1.45
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: aio-pika>=9.5.7
8
- Requires-Dist: aiohttp>=3.13.1
7
+ Requires-Dist: aio-pika>=9.5.8
8
+ Requires-Dist: aiohttp>=3.13.2
9
9
  Requires-Dist: decorator>=5.2.1
10
- Requires-Dist: fastapi>=0.120.0
11
- Requires-Dist: kafka-python>=2.2.15
10
+ Requires-Dist: fastapi>=0.121.2
11
+ Requires-Dist: kafka-python>=2.2.16
12
12
  Requires-Dist: loguru>=0.7.3
13
13
  Requires-Dist: mysql-connector-python>=9.5.0
14
14
  Requires-Dist: nacos-sdk-python>=2.0.9
15
- Requires-Dist: pydantic>=2.12.3
15
+ Requires-Dist: pydantic>=2.12.4
16
16
  Requires-Dist: python-dotenv>=1.2.1
17
17
  Requires-Dist: pyyaml>=6.0.3
18
18
  Requires-Dist: sqlalchemy>=2.0.44
19
- Requires-Dist: starlette>=0.48.0
19
+ Requires-Dist: starlette>=0.49.3
20
20
  Requires-Dist: uuid>=1.30
21
21
  Requires-Dist: uvicorn>=0.38.0
22
22
 
@@ -1,23 +1,23 @@
1
1
  [project]
2
2
  name = "sycommon-python-lib"
3
- version = "0.1.41"
3
+ version = "0.1.45"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
- "aio-pika>=9.5.7",
9
- "aiohttp>=3.13.1",
8
+ "aio-pika>=9.5.8",
9
+ "aiohttp>=3.13.2",
10
10
  "decorator>=5.2.1",
11
- "fastapi>=0.120.0",
12
- "kafka-python>=2.2.15",
11
+ "fastapi>=0.121.2",
12
+ "kafka-python>=2.2.16",
13
13
  "loguru>=0.7.3",
14
14
  "mysql-connector-python>=9.5.0",
15
15
  "nacos-sdk-python>=2.0.9",
16
- "pydantic>=2.12.3",
16
+ "pydantic>=2.12.4",
17
17
  "python-dotenv>=1.2.1",
18
18
  "pyyaml>=6.0.3",
19
19
  "sqlalchemy>=2.0.44",
20
- "starlette>=0.48.0",
20
+ "starlette>=0.49.3",
21
21
  "uuid>=1.30",
22
22
  "uvicorn>=0.38.0",
23
23
  ]
@@ -25,5 +25,9 @@ dependencies = [
25
25
  [tool.setuptools]
26
26
  packages = {find = {where = ["src"]}}
27
27
 
28
+ [build-system]
29
+ requires = ["setuptools"]
30
+ build-backend = "setuptools.build_meta"
31
+
28
32
  [project.scripts]
29
33
  sycommon = "command.cli:main"
@@ -79,7 +79,7 @@ def setup_trace_id_handler(app):
79
79
 
80
80
  content_type = response.headers.get("Content-Type", "")
81
81
 
82
- # 处理 SSE 响应 - 关键修复点
82
+ # 处理 SSE 响应
83
83
  if "text/event-stream" in content_type:
84
84
  # 流式响应不能有Content-Length,移除它
85
85
  if "Content-Length" in response.headers:
@@ -9,16 +9,13 @@ from aio_pika.abc import (
9
9
  AbstractQueue,
10
10
  AbstractIncomingMessage,
11
11
  ConsumerTag,
12
- AbstractRobustConnection
12
+ AbstractRobustConnection,
13
13
  )
14
14
  from sycommon.rabbitmq.rabbitmq_pool import RabbitMQConnectionPool
15
15
  from sycommon.logging.kafka_log import SYLogger
16
16
  from sycommon.models.mqmsg_model import MQMsgModel
17
17
 
18
18
 
19
- # 最大重试次数限制
20
- MAX_RETRY_COUNT = 3
21
-
22
19
  logger = SYLogger
23
20
 
24
21
 
@@ -27,10 +24,10 @@ class RabbitMQClient:
27
24
  RabbitMQ 客户端(支持消息发布、消费、自动重连、异常重试)
28
25
  核心特性:
29
26
  1. 基于连接池复用资源,性能优化
30
- 2. 连接/通道失效时自动重建,高可用
31
- 3. 消息发布支持重试,消费支持手动ACK/NACK
32
- 4. 兼容JSON/字符串/字典消息格式
33
- 5. 严格的协程调用规范,避免"未等待"警告
27
+ 2. 连接/通道失效时自动重建,高可用(限制并发重连)
28
+ 3. 消息发布支持重试+mandatory机制+超时控制,确保路由有效
29
+ 4. 消费支持手动ACK/NACK
30
+ 5. 兼容JSON/字符串/字典消息格式
34
31
  """
35
32
 
36
33
  def __init__(
@@ -45,7 +42,6 @@ class RabbitMQClient:
45
42
  auto_parse_json: bool = True,
46
43
  create_if_not_exists: bool = True,
47
44
  prefetch_count: int = 2,
48
- # 兼容旧代码参数(无需使用)
49
45
  **kwargs,
50
46
  ):
51
47
  # 依赖注入:连接池(必须已初始化)
@@ -85,6 +81,20 @@ class RabbitMQClient:
85
81
  # 线程安全锁
86
82
  self._consume_lock = asyncio.Lock()
87
83
  self._connect_lock = asyncio.Lock()
84
+ # 跟踪连接关闭回调(用于后续移除)
85
+ self._conn_close_callback: Optional[Callable] = None
86
+ # 控制重连频率的信号量(限制并发重连数,默认1个)
87
+ self._reconnect_semaphore = asyncio.Semaphore(1)
88
+ # 固定重连间隔15秒(全局统一)
89
+ self._RECONNECT_INTERVAL = 15
90
+ # 重连任务锁(确保同一时间只有一个重连任务)
91
+ self._reconnect_task_lock = asyncio.Lock()
92
+ # 跟踪当前重连任务(避免重复创建)
93
+ self._current_reconnect_task: Optional[asyncio.Task] = None
94
+ # 连接失败计数器(用于告警)
95
+ self._reconnect_fail_count = 0
96
+ # 连接失败告警阈值
97
+ self._reconnect_alert_threshold = 5
88
98
 
89
99
  @property
90
100
  async def is_connected(self) -> bool:
@@ -104,14 +114,16 @@ class RabbitMQClient:
104
114
  return False
105
115
 
106
116
  async def connect(self) -> None:
107
- """建立连接并初始化交换机/队列(支持重连)"""
108
117
  if self._closed:
109
118
  raise RuntimeError("客户端已关闭,无法重新连接")
110
119
 
111
120
  async with self._connect_lock:
112
- # 释放旧的无效资源
121
+ # 释放旧资源(保留原有回调清理逻辑)
113
122
  if self._channel and self._channel_conn:
114
123
  try:
124
+ if self._conn_close_callback and self._channel_conn:
125
+ self._channel_conn.close_callbacks.discard(
126
+ self._conn_close_callback)
115
127
  await self.connection_pool.release_channel(self._channel, self._channel_conn)
116
128
  except Exception as e:
117
129
  SYLogger.warning(f"释放旧通道失败: {str(e)}")
@@ -119,16 +131,38 @@ class RabbitMQClient:
119
131
  self._channel_conn = None
120
132
  self._exchange = None
121
133
  self._queue = None
134
+ self._conn_close_callback = None
122
135
 
123
136
  try:
124
- # 1. 从连接池获取通道+连接
137
+ # 从连接池获取通道+连接(连接池返回的是 RobustChannel)
125
138
  self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
126
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)
156
+
127
157
  # 2. 设置预取计数(限流)
128
158
  await self._channel.set_qos(prefetch_count=self.prefetch_count)
129
159
  SYLogger.debug(f"设置预取计数: {self.prefetch_count}")
130
160
 
131
- # 3. 声明交换机
161
+ # 3. 低版本 RobustChannel 说明:默认启用异步发布确认,无显式确认方法
162
+ SYLogger.debug(
163
+ "基于 RobustChannel 异步发布确认(低版本 aio-pika 不支持显式确认方法)")
164
+
165
+ # 4. 声明交换机
132
166
  self._exchange = await self._channel.declare_exchange(
133
167
  name=self.exchange_name,
134
168
  type=self.exchange_type,
@@ -139,7 +173,7 @@ class RabbitMQClient:
139
173
  SYLogger.info(
140
174
  f"交换机初始化成功: {self.exchange_name}(类型: {self.exchange_type.value})")
141
175
 
142
- # 4. 声明队列(如果配置了队列名)
176
+ # 5. 声明队列(如果配置了队列名)
143
177
  if self.queue_name:
144
178
  self._queue = await self._channel.declare_queue(
145
179
  name=self.queue_name,
@@ -157,10 +191,15 @@ class RabbitMQClient:
157
191
  f"(绑定交换机: {self.exchange_name}, routing_key: {self.routing_key})"
158
192
  )
159
193
 
194
+ # 重连成功,重置失败计数器
195
+ self._reconnect_fail_count = 0
160
196
  SYLogger.info("客户端连接初始化完成")
161
197
  except Exception as e:
162
198
  SYLogger.error(f"客户端连接失败: {str(e)}", exc_info=True)
163
199
  # 清理异常状态
200
+ if self._conn_close_callback and self._channel_conn:
201
+ self._channel_conn.close_callbacks.discard(
202
+ self._conn_close_callback)
164
203
  if self._channel and self._channel_conn:
165
204
  try:
166
205
  await self.connection_pool.release_channel(self._channel, self._channel_conn)
@@ -168,8 +207,45 @@ class RabbitMQClient:
168
207
  pass
169
208
  self._channel = None
170
209
  self._channel_conn = None
210
+ # 触发重连(固定间隔)
211
+ if not self._closed:
212
+ asyncio.create_task(self._safe_reconnect())
171
213
  raise
172
214
 
215
+ async def _safe_reconnect(self):
216
+ """安全重连:信号量控制并发+固定15秒间隔,避免短时间大量重连"""
217
+ # 1. 信号量控制:限制同时进行的重连任务数(默认1个)
218
+ async with self._reconnect_semaphore:
219
+ # 2. 检查是否已有重连任务在运行(双重保障)
220
+ if self._current_reconnect_task and not self._current_reconnect_task.done():
221
+ SYLogger.debug("已有重连任务在运行,跳过重复触发")
222
+ return
223
+
224
+ async with self._reconnect_task_lock:
225
+ if self._closed or await self.is_connected:
226
+ SYLogger.debug("客户端已关闭或已连接,取消重连")
227
+ return
228
+
229
+ # 3. 固定15秒重连间隔(避免频繁重试)
230
+ SYLogger.info(f"将在15秒后尝试重连...")
231
+ await asyncio.sleep(self._RECONNECT_INTERVAL)
232
+
233
+ if self._closed or await self.is_connected:
234
+ SYLogger.debug("重连等待期间客户端状态变化,取消重连")
235
+ return
236
+
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
248
+
173
249
  async def set_message_handler(
174
250
  self,
175
251
  handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]],
@@ -242,7 +318,7 @@ class RabbitMQClient:
242
318
  # 检查连接状态,失效则触发重连
243
319
  if not await self.is_connected:
244
320
  SYLogger.warning("连接已失效,触发客户端重连")
245
- asyncio.create_task(self.connect())
321
+ asyncio.create_task(self._safe_reconnect())
246
322
 
247
323
  # 3. 启动消费
248
324
  self._consumer_tag = await self._queue.consume(consume_callback)
@@ -272,7 +348,7 @@ class RabbitMQClient:
272
348
  retry_count: int = 3,
273
349
  ) -> None:
274
350
  """
275
- 发布消息(支持自动重试、JSON序列化)
351
+ 发布消息(支持自动重试、mandatory路由校验、5秒超时控制)
276
352
  :param message_body: 消息体(字符串/字典/MQMsgModel)
277
353
  :param headers: 消息头(可选)
278
354
  :param content_type: 内容类型(默认application/json)
@@ -314,40 +390,64 @@ class RabbitMQClient:
314
390
  SYLogger.warning(f"发布消息前连接失效,触发重连(retry: {retry})")
315
391
  await self.connect()
316
392
 
317
- # 发布消息
318
- await self._exchange.publish(
393
+ # 核心:发布消息(添加 mandatory=True 和 timeout=5.0)
394
+ publish_result = await self._exchange.publish(
319
395
  message=message,
320
396
  routing_key=self.routing_key or self.queue_name or "#",
397
+ mandatory=True, # 强制路由到至少一个队列,否则返回None
398
+ timeout=5.0 # 5秒超时控制,避免无限阻塞
321
399
  )
400
+
401
+ # 处理 mandatory=True 结果:未路由到队列返回 None,直接抛出异常
402
+ if publish_result is None:
403
+ raise RuntimeError(
404
+ f"消息未找到匹配的队列(routing_key: {self.routing_key}),mandatory=True 触发失败"
405
+ )
406
+
407
+ # 低版本 RobustChannel 异步确认,无需显式等待,仅日志说明
322
408
  SYLogger.info(
323
409
  f"消息发布成功(retry: {retry}),routing_key: {self.routing_key},"
324
- f"delivery_mode: {delivery_mode.value}"
410
+ f"delivery_mode: {delivery_mode.value},mandatory: True,timeout: 5.0s"
325
411
  )
326
412
  return
413
+ except asyncio.TimeoutError:
414
+ SYLogger.error(
415
+ f"消息发布超时(retry: {retry}/{retry_count-1}),超时时间: 5.0s"
416
+ )
417
+ except RuntimeError as e:
418
+ # 捕获 mandatory 未路由等业务异常
419
+ SYLogger.error(
420
+ f"消息发布业务失败(retry: {retry}/{retry_count-1}): {str(e)}"
421
+ )
327
422
  except Exception as e:
328
423
  SYLogger.error(
329
424
  f"消息发布失败(retry: {retry}/{retry_count-1}): {str(e)}",
330
425
  exc_info=True
331
426
  )
332
- # 清理失效状态,下次重试时重连
427
+ # 清理失效状态,下次重试重连
333
428
  self._exchange = None
334
- # 重试间隔(指数退避)
335
- await asyncio.sleep(0.5 * (2 ** retry))
429
+ # 指数退避重试间隔(0.5s, 1s, 2s...)
430
+ await asyncio.sleep(0.5 * (2 ** retry))
336
431
 
337
- # 所有重试失败,抛出异常
432
+ # 所有重试失败,抛出最终异常
338
433
  raise RuntimeError(
339
- f"消息发布失败(已重试{retry_count}次),routing_key: {self.routing_key}"
434
+ f"消息发布失败(已重试{retry_count}次),routing_key: {self.routing_key}"
435
+ f"mandatory: True,timeout: 5.0s"
340
436
  )
341
437
 
342
438
  async def close(self) -> None:
343
- """关闭客户端(释放资源)"""
344
- if self._closed:
345
- SYLogger.warning("客户端已关闭,无需重复操作")
346
- return
347
-
439
+ """关闭客户端(移除回调+释放资源)"""
348
440
  self._closed = True
349
441
  SYLogger.info("开始关闭RabbitMQ客户端...")
350
442
 
443
+ # 停止重连任务
444
+ if self._current_reconnect_task and not self._current_reconnect_task.done():
445
+ self._current_reconnect_task.cancel()
446
+ try:
447
+ await self._current_reconnect_task
448
+ except asyncio.CancelledError:
449
+ SYLogger.debug("重连任务已取消")
450
+
351
451
  # 1. 停止消费
352
452
  await self.stop_consuming()
353
453
 
@@ -355,6 +455,10 @@ class RabbitMQClient:
355
455
  async with self._connect_lock:
356
456
  if self._channel and self._channel_conn:
357
457
  try:
458
+ # 移除连接关闭回调
459
+ if self._conn_close_callback:
460
+ self._channel_conn.close_callbacks.discard(
461
+ self._conn_close_callback)
358
462
  await self.connection_pool.release_channel(self._channel, self._channel_conn)
359
463
  SYLogger.info("通道释放成功")
360
464
  except Exception as e: