sycommon-python-lib 0.1.32__py3-none-any.whl → 0.1.56b5__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 (30) hide show
  1. sycommon/config/Config.py +6 -2
  2. sycommon/config/RerankerConfig.py +1 -0
  3. sycommon/database/async_base_db_service.py +36 -0
  4. sycommon/database/async_database_service.py +96 -0
  5. sycommon/llm/__init__.py +0 -0
  6. sycommon/llm/embedding.py +149 -0
  7. sycommon/llm/get_llm.py +246 -0
  8. sycommon/llm/llm_logger.py +126 -0
  9. sycommon/llm/llm_tokens.py +119 -0
  10. sycommon/logging/async_sql_logger.py +65 -0
  11. sycommon/logging/kafka_log.py +21 -9
  12. sycommon/logging/logger_levels.py +23 -0
  13. sycommon/middleware/context.py +2 -0
  14. sycommon/middleware/traceid.py +155 -32
  15. sycommon/notice/__init__.py +0 -0
  16. sycommon/notice/uvicorn_monitor.py +195 -0
  17. sycommon/rabbitmq/rabbitmq_client.py +385 -626
  18. sycommon/rabbitmq/rabbitmq_pool.py +287 -71
  19. sycommon/rabbitmq/rabbitmq_service.py +345 -191
  20. sycommon/services.py +104 -64
  21. sycommon/synacos/feign.py +71 -18
  22. sycommon/synacos/feign_client.py +26 -8
  23. sycommon/synacos/nacos_service.py +91 -54
  24. sycommon/tools/merge_headers.py +97 -0
  25. sycommon/tools/snowflake.py +290 -23
  26. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/METADATA +17 -12
  27. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/RECORD +30 -18
  28. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/WHEEL +0 -0
  29. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/entry_points.txt +0 -0
  30. {sycommon_python_lib-0.1.32.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
+ import json
2
3
  from typing import (
3
- Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set
4
+ Callable, Coroutine, Dict, List, Optional, Type, Union, Any
4
5
  )
5
6
  from pydantic import BaseModel
6
7
  from aio_pika.abc import AbstractIncomingMessage, ConsumerTag
@@ -17,13 +18,15 @@ logger = SYLogger
17
18
 
18
19
  class RabbitMQService:
19
20
  """
20
- RabbitMQ服务封装,管理多个客户端实例,基于连接池实现资源复用
21
+ RabbitMQ服务封装,管理多个客户端实例(适配单通道连接池)
22
+ 核心特性:基于单通道连接池复用资源,简化状态管理,依赖原生自动重连
21
23
  """
22
24
 
23
- # 保存多个客户端实例
25
+ # 保存多个客户端实例(共享单通道,仅维护配置差异)
24
26
  _clients: Dict[str, RabbitMQClient] = {}
25
27
  # 保存消息处理器
26
- _message_handlers: Dict[str, Callable] = {}
28
+ _message_handlers: Dict[str, Callable[[
29
+ MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]]] = {}
27
30
  # 保存消费者任务
28
31
  _consumer_tasks: Dict[str, asyncio.Task] = {}
29
32
  # 保存配置信息
@@ -34,8 +37,6 @@ class RabbitMQService:
34
37
  _consumer_events: Dict[str, asyncio.Event] = {}
35
38
  # 存储消费者标签
36
39
  _consumer_tags: Dict[str, ConsumerTag] = {}
37
- # 跟踪已初始化的队列
38
- _initialized_queues: Set[str] = set()
39
40
  # 异步锁,确保初始化安全
40
41
  _init_locks: Dict[str, asyncio.Lock] = {}
41
42
  # 标记是否有监听器和发送器
@@ -43,16 +44,27 @@ class RabbitMQService:
43
44
  _has_senders: bool = False
44
45
  # 消费启动超时设置
45
46
  CONSUMER_START_TIMEOUT = 30 # 30秒超时
46
- # 连接池实例
47
+ # 连接池实例(单通道)
47
48
  _connection_pool: Optional[RabbitMQConnectionPool] = None
49
+ # 服务关闭标记
50
+ _is_shutdown: bool = False
51
+ # 服务关闭锁
52
+ _shutdown_lock = asyncio.Lock()
53
+ # 连接状态监控任务
54
+ _connection_monitor_task: Optional[asyncio.Task] = None
55
+ # 重连配置
56
+ RECONNECT_INTERVAL = 15 # 重连基础间隔(秒)
48
57
 
49
58
  @classmethod
50
59
  def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
51
- """
52
- 初始化RabbitMQ服务(支持集群配置),同时创建连接池
53
- """
60
+ """初始化RabbitMQ服务(支持集群配置),同时创建单通道连接池"""
54
61
  from sycommon.synacos.nacos_service import NacosService
55
62
 
63
+ # 防止重复初始化
64
+ if cls._config:
65
+ logger.warning("RabbitMQService已初始化,无需重复调用")
66
+ return cls
67
+
56
68
  # 获取MQ配置
57
69
  cls._config = NacosService(config).share_configs.get(
58
70
  "mq.yml", {}).get('spring', {}).get('rabbitmq', {})
@@ -63,22 +75,28 @@ class RabbitMQService:
63
75
  f"RabbitMQ服务初始化 - 集群节点: {cls._config.get('host')}, "
64
76
  f"端口: {cls._config.get('port')}, "
65
77
  f"虚拟主机: {cls._config.get('virtual-host')}, "
66
- f"应用名: {cls._config.get('APP_NAME')}"
78
+ f"应用名: {cls._config.get('APP_NAME')}, "
79
+ f"心跳: {cls._config.get('heartbeat', 30)}s"
67
80
  )
68
81
 
69
82
  # 保存发送器和监听器存在状态
70
83
  cls._has_listeners = has_listeners
71
84
  cls._has_senders = has_senders
85
+ cls._is_shutdown = False
72
86
 
73
87
  # 初始化连接池(在单独的异步方法中启动)
74
88
  asyncio.create_task(cls._init_connection_pool())
75
89
 
90
+ # 启动连接监控任务(监听连接状态,自动重连)
91
+ cls._connection_monitor_task = asyncio.create_task(
92
+ cls._monitor_connections())
93
+
76
94
  return cls
77
95
 
78
96
  @classmethod
79
97
  async def _init_connection_pool(cls):
80
- """初始化连接池(异步操作)"""
81
- if cls._connection_pool or not cls._config:
98
+ """初始化单通道连接池(异步操作,带重试)"""
99
+ if cls._connection_pool or not cls._config or cls._is_shutdown:
82
100
  return
83
101
 
84
102
  try:
@@ -89,45 +107,137 @@ class RabbitMQService:
89
107
  if not hosts_list:
90
108
  raise ValueError("RabbitMQ集群配置为空,请检查host参数")
91
109
 
92
- # 创建连接池
110
+ global_prefetch_count = cls._config.get('prefetch_count', 2)
111
+
112
+ # 创建单通道连接池(已移除channel_pool_size参数)
93
113
  cls._connection_pool = RabbitMQConnectionPool(
94
114
  hosts=hosts_list,
95
115
  port=cls._config.get('port', 5672),
96
116
  username=cls._config.get('username', ""),
97
117
  password=cls._config.get('password', ""),
98
118
  virtualhost=cls._config.get('virtual-host', "/"),
99
- connection_pool_size=cls._config.get(
100
- 'connection_pool_size', 5), # 连接池大小
101
- channel_pool_size=cls._config.get(
102
- 'channel_pool_size', 10), # 通道池大小
119
+ app_name=cls._config.get("APP_NAME", ""),
120
+ prefetch_count=global_prefetch_count,
103
121
  heartbeat=cls._config.get('heartbeat', 30),
104
- app_name=cls._config.get("APP_NAME", "")
122
+ connection_timeout=cls._config.get('connection_timeout', 30),
123
+ reconnect_interval=cls._config.get('reconnect_interval', 5),
105
124
  )
106
125
 
107
126
  # 初始化连接池
108
- await cls._connection_pool.init_pools()
109
- logger.info("RabbitMQ连接池初始化成功")
127
+ await asyncio.wait_for(cls._connection_pool.init_pools(), timeout=30)
128
+ logger.info("RabbitMQ单通道连接池初始化成功")
110
129
 
111
130
  except Exception as e:
112
131
  logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
113
- # 连接池初始化失败时重试
114
- await asyncio.sleep(1)
115
- asyncio.create_task(cls._init_connection_pool())
132
+ # 连接池初始化失败时重试(未关闭状态下)
133
+ if not cls._is_shutdown:
134
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
135
+ asyncio.create_task(cls._init_connection_pool())
136
+
137
+ @classmethod
138
+ async def _monitor_connections(cls):
139
+ """连接监控任务:定期检查所有客户端连接状态,依赖连接池原生重连"""
140
+ logger.info("RabbitMQ连接监控任务启动")
141
+ while not cls._is_shutdown:
142
+ try:
143
+ await asyncio.sleep(cls.RECONNECT_INTERVAL) # 固定15秒间隔
144
+
145
+ # 跳过未初始化的连接池
146
+ if not cls._connection_pool or not cls._connection_pool._initialized:
147
+ continue
148
+
149
+ # 检查连接池本身状态
150
+ pool_alive = await cls._connection_pool.is_alive
151
+ if not pool_alive:
152
+ logger.error("RabbitMQ连接池已断开,等待原生自动重连")
153
+ continue
154
+
155
+ # 检查所有客户端连接(单通道场景下,客户端状态依赖通道有效性)
156
+ for client_name, client in list(cls._clients.items()):
157
+ try:
158
+ client_connected = await client.is_connected
159
+ if not client_connected:
160
+ logger.warning(
161
+ f"客户端 '{client_name}' 连接异常,触发重连")
162
+ asyncio.create_task(
163
+ cls._reconnect_client(client_name, client))
164
+ except Exception as e:
165
+ logger.error(
166
+ f"监控客户端 '{client_name}' 连接状态失败: {str(e)}", exc_info=True)
167
+
168
+ except Exception as e:
169
+ logger.error("RabbitMQ连接监控任务异常", exc_info=True)
170
+ await asyncio.sleep(cls.RECONNECT_INTERVAL) # 异常后延迟重启监控
171
+
172
+ logger.info("RabbitMQ连接监控任务停止")
173
+
174
+ @classmethod
175
+ async def _clean_client_resources(cls, client: RabbitMQClient):
176
+ """清理客户端无效资源(单通道场景下仅重置状态,无需归还通道)"""
177
+ try:
178
+ # 先停止消费(避免消费中操作资源)
179
+ if client._consumer_tag:
180
+ await client.stop_consuming()
181
+ logger.debug("客户端无效资源清理完成(单通道无需归还)")
182
+ except Exception as e:
183
+ logger.warning(f"释放客户端无效资源失败: {str(e)}")
184
+ finally:
185
+ # 强制重置客户端状态(通道由连接池自动恢复)
186
+ client._channel = None
187
+ client._channel_conn = None
188
+ client._exchange = None
189
+ client._queue = None
190
+ client._consumer_tag = None
191
+
192
+ @classmethod
193
+ async def _reconnect_client(cls, client_name: str, client: RabbitMQClient) -> bool:
194
+ """客户端重连(依赖连接池原生重连,仅重建客户端资源)"""
195
+ if cls._is_shutdown or not await cls._connection_pool.is_alive:
196
+ return False
197
+
198
+ # 重连冷却
199
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
200
+
201
+ try:
202
+ # 清理旧资源
203
+ await cls._clean_client_resources(client)
204
+
205
+ # 执行重连(客户端内部会从连接池获取新通道)
206
+ await client.connect()
207
+
208
+ # 验证重连结果
209
+ if await client.is_connected:
210
+ logger.info(f"客户端 '{client_name}' 重连成功")
211
+ # 如果是消费者,重新启动消费
212
+ if client_name in cls._message_handlers:
213
+ await cls.start_consumer(client_name)
214
+ return True
215
+ else:
216
+ logger.warning(f"客户端 '{client_name}' 重连失败:资源未完全初始化")
217
+ return False
218
+ except Exception as e:
219
+ logger.error(f"客户端 '{client_name}' 重连失败: {str(e)}", exc_info=True)
220
+ # 重连失败后,由监控任务再次触发(避免死循环)
221
+ return False
116
222
 
117
223
  @classmethod
118
224
  async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
119
- if not cls._connection_pool:
225
+ """创建客户端实例(适配单通道连接池的RabbitMQClient)"""
226
+ if cls._is_shutdown:
227
+ raise RuntimeError("RabbitMQService已关闭,无法创建客户端")
228
+
229
+ if not cls._connection_pool or not cls._connection_pool._initialized:
120
230
  # 等待连接池初始化
121
231
  start_time = asyncio.get_event_loop().time()
122
- while not cls._connection_pool:
232
+ while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
123
233
  if asyncio.get_event_loop().time() - start_time > 30:
124
234
  raise TimeoutError("等待连接池初始化超时")
125
235
  await asyncio.sleep(1)
236
+ if cls._is_shutdown:
237
+ raise RuntimeError("服务关闭中,取消创建客户端")
126
238
 
127
239
  app_name = kwargs.get('app_name', cls._config.get(
128
240
  "APP_NAME", "")) if cls._config else ""
129
-
130
- # 确定是否为发送器
131
241
  is_sender = not cls._has_listeners
132
242
 
133
243
  # 根据组件类型决定是否允许创建队列
@@ -147,7 +257,7 @@ class RabbitMQService:
147
257
  f"允许创建: {create_if_not_exists}"
148
258
  )
149
259
 
150
- # 创建客户端实例
260
+ # 创建客户端实例(适配精简版RabbitMQClient参数)
151
261
  client = RabbitMQClient(
152
262
  connection_pool=cls._connection_pool,
153
263
  exchange_name=cls._config.get(
@@ -160,38 +270,35 @@ class RabbitMQService:
160
270
  auto_delete=kwargs.get('auto_delete', False),
161
271
  auto_parse_json=kwargs.get('auto_parse_json', True),
162
272
  create_if_not_exists=create_if_not_exists,
163
- connection_timeout=kwargs.get('connection_timeout', 10),
164
- rpc_timeout=kwargs.get('rpc_timeout', 10),
165
- reconnection_delay=kwargs.get('reconnection_delay', 1),
166
- max_reconnection_attempts=kwargs.get(
167
- 'max_reconnection_attempts', 5),
168
273
  prefetch_count=kwargs.get('prefetch_count', 2),
169
- consumption_stall_threshold=kwargs.get(
170
- 'consumption_stall_threshold', 10),
171
274
  )
172
275
 
173
- # 使用declare_queue控制是否声明队列(发送器不声明,监听器声明)
174
- await client.connect(declare_queue=not is_sender)
276
+ # 连接客户端(单通道场景下快速连接)
277
+ await client.connect()
278
+
175
279
  return client
176
280
 
177
281
  @classmethod
178
282
  async def get_client(
179
283
  cls,
180
- client_name: str = "default", ** kwargs
284
+ client_name: str = "default", **kwargs
181
285
  ) -> RabbitMQClient:
182
- """
183
- 获取或创建RabbitMQ客户端(基于连接池)
184
- """
286
+ """获取或创建RabbitMQ客户端(单通道池,线程安全)"""
287
+ if cls._is_shutdown:
288
+ raise RuntimeError("RabbitMQService已关闭,无法获取客户端")
289
+
185
290
  if not cls._config:
186
291
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
187
292
 
188
293
  # 等待连接池就绪
189
- if not cls._connection_pool:
294
+ if not cls._connection_pool or not cls._connection_pool._initialized:
190
295
  start_time = asyncio.get_event_loop().time()
191
- while not cls._connection_pool:
296
+ while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
192
297
  if asyncio.get_event_loop().time() - start_time > 30:
193
298
  raise TimeoutError("等待连接池初始化超时")
194
299
  await asyncio.sleep(1)
300
+ if cls._is_shutdown:
301
+ raise RuntimeError("服务关闭中,取消获取客户端")
195
302
 
196
303
  # 确保锁存在
197
304
  if client_name not in cls._init_locks:
@@ -204,18 +311,18 @@ class RabbitMQService:
204
311
  is_sender = not cls._has_listeners or (
205
312
  not kwargs.get('create_if_not_exists', True))
206
313
 
207
- if client.is_connected:
314
+ if await client.is_connected:
208
315
  # 如果是监听器但队列未初始化,重新连接
209
- if not is_sender and not client.queue:
316
+ if not is_sender and not client._queue:
210
317
  logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
211
318
  client.create_if_not_exists = True
212
- await client.connect(force_reconnect=True, declare_queue=True)
319
+ await client.connect()
213
320
  return client
214
321
  else:
215
- logger.info(f"客户端 '{client_name}' 连接已关闭,重新连接")
322
+ logger.info(f"客户端 '{client_name}' 连接已断开,重新连接")
216
323
  if not is_sender:
217
324
  client.create_if_not_exists = True
218
- await client.connect(declare_queue=not is_sender)
325
+ await client.connect()
219
326
  return client
220
327
 
221
328
  # 创建新客户端
@@ -231,23 +338,12 @@ class RabbitMQService:
231
338
  app_name=cls._config.get("APP_NAME", ""),
232
339
  **kwargs
233
340
  )
234
- await client.connect(declare_queue=False)
235
341
  cls._clients[client_name] = client
236
342
  return client
237
343
 
238
- # 监听器逻辑
344
+ # 监听器逻辑(单通道支持多队列声明,无需跟踪已初始化队列)
239
345
  kwargs['create_if_not_exists'] = True
240
346
 
241
- # 检查队列是否已初始化
242
- if initial_queue_name in cls._initialized_queues:
243
- logger.info(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
244
- client = await cls._create_client(
245
- initial_queue_name, ** kwargs
246
- )
247
- await client.connect(declare_queue=True)
248
- cls._clients[client_name] = client
249
- return client
250
-
251
347
  # 创建并连接客户端
252
348
  client = await cls._create_client(
253
349
  initial_queue_name,
@@ -255,29 +351,24 @@ class RabbitMQService:
255
351
  **kwargs
256
352
  )
257
353
 
258
- client.create_if_not_exists = True
259
- await client.connect(declare_queue=True)
260
-
261
354
  # 验证队列是否创建成功
262
- if not client.queue:
355
+ if not client._queue:
263
356
  logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
264
357
  client.create_if_not_exists = True
265
- await client.connect(force_reconnect=True, declare_queue=True)
266
- if not client.queue:
358
+ await client.connect()
359
+ if not client._queue:
267
360
  raise Exception(f"无法创建队列 '{initial_queue_name}'")
268
361
 
269
- # 记录已初始化的队列
270
- final_queue_name = client.queue_name
271
- if final_queue_name:
272
- cls._initialized_queues.add(final_queue_name)
273
-
274
362
  cls._clients[client_name] = client
275
363
  return client
276
364
 
277
- # 以下方法逻辑与原有保持一致(无需修改)
278
365
  @classmethod
279
- async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, ** kwargs) -> None:
280
- """设置消息发送器"""
366
+ async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **kwargs) -> None:
367
+ """设置消息发送器(适配单通道客户端)"""
368
+ if cls._is_shutdown:
369
+ logger.warning("服务已关闭,无法设置发送器")
370
+ return
371
+
281
372
  cls._has_senders = True
282
373
  cls._has_listeners = has_listeners
283
374
  logger.info(f"开始设置 {len(senders)} 个消息发送器")
@@ -306,8 +397,8 @@ class RabbitMQService:
306
397
  # 获取或创建客户端
307
398
  if normalized_name in cls._clients:
308
399
  client = cls._clients[normalized_name]
309
- if not client.is_connected:
310
- await client.connect(declare_queue=False)
400
+ if not await client.is_connected:
401
+ await client.connect()
311
402
  else:
312
403
  client = await cls.get_client(
313
404
  client_name=normalized_name,
@@ -318,7 +409,7 @@ class RabbitMQService:
318
409
  queue_name=queue_name,
319
410
  create_if_not_exists=False,
320
411
  prefetch_count=prefetch_count,
321
- ** kwargs
412
+ **kwargs
322
413
  )
323
414
 
324
415
  # 记录客户端
@@ -337,8 +428,12 @@ class RabbitMQService:
337
428
  logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
338
429
 
339
430
  @classmethod
340
- async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, ** kwargs) -> None:
341
- """设置消息监听器"""
431
+ async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **kwargs) -> None:
432
+ """设置消息监听器(适配单通道客户端)"""
433
+ if cls._is_shutdown:
434
+ logger.warning("服务已关闭,无法设置监听器")
435
+ return
436
+
342
437
  cls._has_listeners = True
343
438
  cls._has_senders = has_senders
344
439
  logger.info(f"开始设置 {len(listeners)} 个消息监听器")
@@ -348,9 +443,11 @@ class RabbitMQService:
348
443
  # 转换配置并强制设置create_if_not_exists为True
349
444
  listener_dict = listener_config.model_dump()
350
445
  listener_dict['create_if_not_exists'] = True
446
+ listener_dict['prefetch_count'] = listener_config.prefetch_count
351
447
  queue_name = listener_dict['queue_name']
352
448
 
353
- logger.info(f"设置监听器 {idx+1}/{len(listeners)}: {queue_name}")
449
+ logger.info(
450
+ f"设置监听器 {idx+1}/{len(listeners)}: {queue_name} (prefetch_count: {listener_config.prefetch_count})")
354
451
 
355
452
  # 添加监听器
356
453
  await cls.add_listener(**listener_dict)
@@ -375,7 +472,8 @@ class RabbitMQService:
375
472
  running_clients = []
376
473
 
377
474
  while len(running_clients) < len(required_clients) and \
378
- (asyncio.get_event_loop().time() - start_time) < timeout:
475
+ (asyncio.get_event_loop().time() - start_time) < timeout and \
476
+ not cls._is_shutdown:
379
477
 
380
478
  running_clients = [
381
479
  name for name, task in cls._consumer_tasks.items()
@@ -387,7 +485,7 @@ class RabbitMQService:
387
485
  await asyncio.sleep(1)
388
486
 
389
487
  failed_clients = [
390
- name for name in required_clients if name not in running_clients]
488
+ name for name in required_clients if name not in running_clients and not cls._is_shutdown]
391
489
  if failed_clients:
392
490
  logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
393
491
  for client_name in failed_clients:
@@ -398,9 +496,13 @@ class RabbitMQService:
398
496
  async def add_listener(
399
497
  cls,
400
498
  queue_name: str,
401
- handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], ** kwargs
499
+ handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **kwargs
402
500
  ) -> None:
403
- """添加消息监听器"""
501
+ """添加消息监听器(线程安全,单通道场景)"""
502
+ if cls._is_shutdown:
503
+ logger.warning("服务已关闭,无法添加监听器")
504
+ return
505
+
404
506
  if not cls._config:
405
507
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
406
508
 
@@ -421,17 +523,35 @@ class RabbitMQService:
421
523
 
422
524
  @classmethod
423
525
  async def start_all_consumers(cls) -> None:
424
- """启动所有已注册的消费者"""
526
+ """启动所有已注册的消费者(单通道场景,避免阻塞)"""
527
+ if cls._is_shutdown:
528
+ logger.warning("服务已关闭,无法启动消费者")
529
+ return
530
+
425
531
  for client_name in cls._message_handlers:
426
532
  await cls.start_consumer(client_name)
427
533
 
428
534
  @classmethod
429
535
  async def start_consumer(cls, client_name: str) -> None:
430
- """启动指定客户端的消费者"""
431
- if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
432
- logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
536
+ """启动指定客户端的消费者(单通道消费,需确保回调非阻塞)"""
537
+ if cls._is_shutdown:
538
+ logger.warning("服务已关闭,无法启动消费者")
433
539
  return
434
540
 
541
+ # 检查任务状态,避免重复创建
542
+ if client_name in cls._consumer_tasks:
543
+ existing_task = cls._consumer_tasks[client_name]
544
+ if not existing_task.done():
545
+ # 检查任务是否处于异常状态,仅在异常时重启
546
+ if existing_task.exception() is not None:
547
+ logger.info(f"消费者 '{client_name}' 任务异常,重启")
548
+ existing_task.cancel()
549
+ else:
550
+ logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
551
+ return
552
+ else:
553
+ logger.info(f"消费者 '{client_name}' 任务已完成,重新启动")
554
+
435
555
  if client_name not in cls._clients:
436
556
  raise ValueError(f"RabbitMQ客户端 '{client_name}' 未初始化")
437
557
 
@@ -443,31 +563,44 @@ class RabbitMQService:
443
563
  handler = cls.default_message_handler
444
564
 
445
565
  # 设置消息处理器
446
- client.set_message_handler(handler)
566
+ await client.set_message_handler(handler)
447
567
 
448
568
  # 确保客户端已连接
449
569
  start_time = asyncio.get_event_loop().time()
450
- while not client.is_connected:
570
+ while not await client.is_connected and not cls._is_shutdown:
451
571
  if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
452
572
  raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
453
573
 
454
574
  logger.info(f"等待客户端 '{client_name}' 连接就绪...")
455
575
  await asyncio.sleep(1)
576
+ if cls._is_shutdown:
577
+ return
456
578
 
457
579
  # 创建停止事件
458
580
  stop_event = asyncio.Event()
459
581
  cls._consumer_events[client_name] = stop_event
460
582
 
461
- # 定义消费任务
583
+ # 定义消费任务(单通道场景下,消费回调需非阻塞)
462
584
  async def consume_task():
463
585
  try:
464
586
  # 启动消费,带重试机制
465
- max_attempts = 5
587
+ max_attempts = 3
466
588
  attempt = 0
467
589
  consumer_tag = None
468
590
 
469
- while attempt < max_attempts and not stop_event.is_set():
591
+ while attempt < max_attempts and not stop_event.is_set() and not cls._is_shutdown:
470
592
  try:
593
+ # 启动消费前再次校验连接和队列状态
594
+ if not await client.is_connected:
595
+ logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
596
+ await client.connect()
597
+
598
+ # 确保队列和处理器已就绪
599
+ if not client._queue:
600
+ raise Exception("队列未初始化完成")
601
+ if not client._message_handler:
602
+ raise Exception("消息处理器未设置")
603
+
471
604
  consumer_tag = await client.start_consuming()
472
605
  if consumer_tag:
473
606
  break
@@ -476,14 +609,20 @@ class RabbitMQService:
476
609
  logger.warning(
477
610
  f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
478
611
  if attempt < max_attempts:
479
- await asyncio.sleep(1)
612
+ await asyncio.sleep(2)
613
+
614
+ if cls._is_shutdown:
615
+ return
480
616
 
481
617
  if not consumer_tag:
482
618
  raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
483
619
 
484
620
  # 记录消费者标签
485
621
  cls._consumer_tags[client_name] = consumer_tag
486
- logger.info(f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
622
+ logger.info(
623
+ f"消费者 '{client_name}' 开始消费(单通道),tag: {consumer_tag},"
624
+ f"注意:消费回调需非阻塞,避免影响其他客户端"
625
+ )
487
626
 
488
627
  # 等待停止事件
489
628
  await stop_event.wait()
@@ -495,8 +634,9 @@ class RabbitMQService:
495
634
  logger.error(
496
635
  f"消费者 '{client_name}' 错误: {str(e)}", exc_info=True)
497
636
  # 非主动停止时尝试重启
498
- if not stop_event.is_set():
637
+ if not stop_event.is_set() and not cls._is_shutdown:
499
638
  logger.info(f"尝试重启消费者 '{client_name}'")
639
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
500
640
  asyncio.create_task(cls.start_consumer(client_name))
501
641
  finally:
502
642
  # 清理资源
@@ -525,8 +665,7 @@ class RabbitMQService:
525
665
  t.result()
526
666
  except Exception as e:
527
667
  logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
528
- # 任务异常时自动重启(如果服务未关闭)
529
- if client_name in cls._message_handlers: # 检查处理器是否仍存在
668
+ if client_name in cls._message_handlers and not cls._is_shutdown:
530
669
  asyncio.create_task(cls.start_consumer(client_name))
531
670
 
532
671
  task.add_done_callback(task_done_callback)
@@ -542,63 +681,90 @@ class RabbitMQService:
542
681
  logger.info("===================\n")
543
682
 
544
683
  @classmethod
545
- def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
546
- """获取发送客户端"""
684
+ async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
685
+ """获取发送客户端(适配单通道池)"""
686
+ if cls._is_shutdown:
687
+ logger.warning("服务已关闭,无法获取发送器")
688
+ return None
689
+
547
690
  if not queue_name:
548
691
  logger.warning("发送器名称不能为空")
549
692
  return None
550
693
 
551
694
  # 检查是否在已注册的发送器中
552
695
  if queue_name in cls._sender_client_names and queue_name in cls._clients:
553
- return cls._clients[queue_name]
696
+ client = cls._clients[queue_name]
697
+ if await client.is_connected:
698
+ return client
699
+ else:
700
+ logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
701
+ try:
702
+ await client.connect()
703
+ if await client.is_connected:
704
+ return client
705
+ except Exception as e:
706
+ logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
707
+ return None
554
708
 
555
709
  # 检查是否带有app-name后缀
556
710
  app_name = cls._config.get("APP_NAME", "") if cls._config else ""
557
- if app_name and f"{queue_name}.{app_name}" in cls._sender_client_names:
558
- return cls._clients.get(f"{queue_name}.{app_name}")
711
+ if app_name:
712
+ suffixed_name = f"{queue_name}.{app_name}"
713
+ if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
714
+ client = cls._clients[suffixed_name]
715
+ if await client.is_connected:
716
+ return client
717
+ else:
718
+ logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
719
+ try:
720
+ await client.connect()
721
+ if await client.is_connected:
722
+ return client
723
+ except Exception as e:
724
+ logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
559
725
 
560
- logger.info(f"未找到发送器 '{queue_name}'")
726
+ logger.info(f"未找到可用的发送器 '{queue_name}'")
561
727
  return None
562
728
 
563
729
  @classmethod
564
730
  async def send_message(
565
731
  cls,
566
732
  data: Union[BaseModel, str, Dict[str, Any], None],
567
- queue_name: str, ** kwargs
733
+ queue_name: str, **kwargs
568
734
  ) -> None:
569
- """发送消息到指定队列"""
735
+ """发送消息到指定队列(单通道场景下快速发送)"""
736
+ if cls._is_shutdown:
737
+ raise RuntimeError("RabbitMQService已关闭,无法发送消息")
738
+
570
739
  # 获取发送客户端
571
- sender = cls.get_sender(queue_name)
740
+ sender = await cls.get_sender(queue_name)
572
741
  if not sender:
573
742
  error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
574
743
  logger.error(error_msg)
575
744
  raise ValueError(error_msg)
576
745
 
577
746
  # 确保连接有效
578
- if not sender.is_connected:
747
+ if not await sender.is_connected:
579
748
  logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
580
- max_retry = 3 # 最大重试次数
749
+ max_retry = 3
581
750
  retry_count = 0
582
751
  last_exception = None
583
752
 
584
- while retry_count < max_retry:
753
+ while retry_count < max_retry and not cls._is_shutdown:
585
754
  try:
586
- # 尝试重连,每次重试间隔1秒
587
- await sender.connect(force_reconnect=True, declare_queue=False)
588
- logger.info(
589
- f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
590
- break # 重连成功则退出循环
755
+ await sender.connect()
756
+ if await sender.is_connected:
757
+ logger.info(
758
+ f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
759
+ break
591
760
  except Exception as e:
592
761
  last_exception = e
593
762
  retry_count += 1
594
763
  logger.warning(
595
- f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}"
596
- )
597
- if retry_count < max_retry:
598
- await asyncio.sleep(1) # 重试前等待1秒
764
+ f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
765
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
599
766
 
600
- # 所有重试都失败则抛出异常
601
- if retry_count >= max_retry and not sender.is_connected:
767
+ if retry_count >= max_retry and not await sender.is_connected:
602
768
  error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
603
769
  logger.error(f"{error_msg}: {str(last_exception)}")
604
770
  raise Exception(error_msg) from last_exception
@@ -611,19 +777,18 @@ class RabbitMQService:
611
777
  elif isinstance(data, BaseModel):
612
778
  msg_content = data.model_dump_json()
613
779
  elif isinstance(data, dict):
614
- import json
615
- msg_content = json.dumps(data)
780
+ msg_content = json.dumps(data, ensure_ascii=False)
616
781
 
617
782
  # 创建标准消息模型
618
783
  mq_message = MQMsgModel(
619
784
  topicCode=queue_name.split('.')[0] if queue_name else "",
620
785
  msg=msg_content,
621
786
  correlationDataId=kwargs.get(
622
- 'correlationDataId', SYLogger.get_trace_id()),
787
+ 'correlationDataId', logger.get_trace_id()),
623
788
  groupId=kwargs.get('groupId', ''),
624
789
  dataKey=kwargs.get('dataKey', ""),
625
790
  manualFlag=kwargs.get('manualFlag', False),
626
- traceId=SYLogger.get_trace_id()
791
+ traceId=logger.get_trace_id()
627
792
  )
628
793
 
629
794
  # 构建消息头
@@ -635,11 +800,11 @@ class RabbitMQService:
635
800
  user_name="SYSTEM",
636
801
  request_path="",
637
802
  req_type="SYSTEM",
638
- trace_id=SYLogger.get_trace_id(),
803
+ trace_id=logger.get_trace_id(),
639
804
  ).model_dump_json()
640
805
  }
641
806
 
642
- # 发送消息
807
+ # 发送消息(单通道场景下依赖原生异步确认)
643
808
  await sender.publish(
644
809
  message_body=mq_message.model_dump_json(),
645
810
  headers=mq_header,
@@ -652,72 +817,61 @@ class RabbitMQService:
652
817
 
653
818
  @classmethod
654
819
  async def shutdown(cls, timeout: float = 15.0) -> None:
655
- """优雅关闭所有资源(新增连接池关闭逻辑)"""
656
- start_time = asyncio.get_event_loop().time()
657
- logger.info("开始关闭RabbitMQ服务...")
658
-
659
- # 发送停止信号给所有消费者
660
- for client_name, event in cls._consumer_events.items():
661
- event.set()
662
- logger.info(f"已向消费者 '{client_name}' 发送退出信号")
663
-
664
- # 等待消费者任务完成
665
- remaining_time = max(
666
- 0.0, timeout - (asyncio.get_event_loop().time() - start_time))
667
- if remaining_time > 0 and cls._consumer_tasks:
668
- try:
669
- done, pending = await asyncio.wait(
670
- list(cls._consumer_tasks.values()),
671
- timeout=remaining_time,
672
- return_when=asyncio.ALL_COMPLETED
673
- )
674
-
675
- # 处理超时的任务
676
- for task in pending:
677
- task_name = task.get_name()
678
- logger.warning(f"任务 '{task_name}' 关闭超时,强制取消")
820
+ """优雅关闭所有资源(单通道场景下简化关闭流程)"""
821
+ async with cls._shutdown_lock:
822
+ if cls._is_shutdown:
823
+ logger.info("RabbitMQService已关闭,无需重复操作")
824
+ return
825
+
826
+ cls._is_shutdown = True
827
+ logger.info("开始关闭RabbitMQ服务...")
828
+
829
+ # 1. 停止连接监控任务
830
+ if cls._connection_monitor_task and not cls._connection_monitor_task.done():
831
+ cls._connection_monitor_task.cancel()
832
+ try:
833
+ await asyncio.wait_for(cls._connection_monitor_task, timeout=timeout)
834
+ except asyncio.TimeoutError:
835
+ logger.warning("连接监控任务关闭超时")
836
+ except Exception as e:
837
+ logger.error(f"关闭连接监控任务失败: {str(e)}")
838
+
839
+ # 2. 停止所有消费者任务
840
+ for client_name, task in cls._consumer_tasks.items():
841
+ if not task.done():
842
+ # 触发停止事件
843
+ if client_name in cls._consumer_events:
844
+ cls._consumer_events[client_name].set()
845
+ # 取消任务
679
846
  task.cancel()
680
847
  try:
681
- await task
682
- except (asyncio.CancelledError, RuntimeError):
683
- pass
684
-
685
- except Exception as e:
686
- logger.error(f"等待消费者任务完成时出错: {str(e)}")
687
-
688
- # 关闭所有客户端连接
689
- remaining_time = max(
690
- 0.0, timeout - (asyncio.get_event_loop().time() - start_time))
691
- if remaining_time > 0 and cls._clients:
692
- client_count = len(cls._clients)
693
- client_timeout = remaining_time / client_count # 平均分配剩余时间
848
+ await asyncio.wait_for(task, timeout=timeout)
849
+ except Exception as e:
850
+ logger.error(f"关闭消费者 '{client_name}' 失败: {str(e)}")
694
851
 
695
- for name, client in cls._clients.items():
852
+ # 3. 关闭所有客户端(单通道场景下客户端关闭仅清理状态)
853
+ for client in cls._clients.values():
696
854
  try:
697
- await asyncio.wait_for(client.close(), timeout=client_timeout)
855
+ await client.close()
698
856
  except Exception as e:
699
- logger.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
700
- logger.info(f"客户端 '{name}' 已关闭")
857
+ logger.error(f"关闭客户端失败: {str(e)}")
701
858
 
702
- # 关闭连接池
703
- if cls._connection_pool:
704
- try:
705
- await cls._connection_pool.close()
706
- logger.info("RabbitMQ连接池已关闭")
707
- except Exception as e:
708
- logger.warning(f"关闭连接池时出错: {str(e)}")
709
-
710
- # 清理所有状态
711
- cls._clients.clear()
712
- cls._consumer_tasks.clear()
713
- cls._message_handlers.clear()
714
- cls._sender_client_names.clear()
715
- cls._consumer_events.clear()
716
- cls._consumer_tags.clear()
717
- cls._initialized_queues.clear()
718
- cls._init_locks.clear()
719
- cls._has_listeners = False
720
- cls._has_senders = False
721
- cls._connection_pool = None
722
-
723
- logger.info("RabbitMQ服务已完全关闭")
859
+ # 4. 关闭连接池(单通道池关闭会释放所有资源)
860
+ if cls._connection_pool and cls._connection_pool._initialized:
861
+ try:
862
+ await cls._connection_pool.close()
863
+ logger.info("RabbitMQ单通道连接池已关闭")
864
+ except Exception as e:
865
+ logger.error(f"关闭连接池失败: {str(e)}")
866
+
867
+ # 5. 清理状态
868
+ cls._clients.clear()
869
+ cls._message_handlers.clear()
870
+ cls._consumer_tasks.clear()
871
+ cls._consumer_events.clear()
872
+ cls._consumer_tags.clear()
873
+ cls._sender_client_names.clear()
874
+ cls._init_locks.clear()
875
+ cls._config = None
876
+
877
+ logger.info("RabbitMQService已完全关闭")