sycommon-python-lib 0.1.9__py3-none-any.whl → 0.1.11__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.

Potentially problematic release.


This version of sycommon-python-lib might be problematic. Click here for more details.

@@ -1,100 +1,115 @@
1
- from typing import Any, Callable, Coroutine, Dict, List, Tuple, Union, Optional, Type
2
1
  import asyncio
3
2
  import logging
3
+ from typing import (
4
+ Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set
5
+ )
4
6
  from pydantic import BaseModel
5
- from aio_pika.abc import AbstractIncomingMessage
7
+ from aio_pika.abc import AbstractIncomingMessage, ConsumerTag
6
8
 
7
- from sycommon.logging.kafka_log import SYLogger
8
9
  from sycommon.models.mqmsg_model import MQMsgModel
9
10
  from sycommon.models.mqlistener_config import RabbitMQListenerConfig
10
11
  from sycommon.models.mqsend_config import RabbitMQSendConfig
11
12
  from sycommon.models.sso_user import SsoUser
12
- from sycommon.rabbitmq.rabbitmq_client import RabbitMQClient
13
+ from sycommon.logging.kafka_log import SYLogger
14
+ from .rabbitmq_client import RabbitMQClient
15
+
16
+ logger = logging.getLogger(__name__)
13
17
 
14
18
 
15
19
  class RabbitMQService:
20
+ """
21
+ RabbitMQ服务封装,管理多个客户端实例,提供发送和接收消息的高级接口
22
+ 负责客户端的创建、配置、生命周期管理和错误处理
23
+ """
24
+
16
25
  # 保存多个客户端实例
17
- clients: Dict[str, RabbitMQClient] = {}
18
- # 保存多个消费者任务
19
- consumer_tasks: Dict[str, asyncio.Task] = {}
26
+ _clients: Dict[str, RabbitMQClient] = {}
20
27
  # 保存消息处理器
21
- message_handlers: Dict[str, Callable] = {}
28
+ _message_handlers: Dict[str, Callable] = {}
29
+ # 保存消费者任务
30
+ _consumer_tasks: Dict[str, asyncio.Task] = {}
22
31
  # 保存配置信息
23
- config: Optional[dict] = None
24
- # 存储发送客户端的名称(即队列名)
25
- sender_client_names: List[str] = []
32
+ _config: Optional[dict] = None
33
+ # 存储发送客户端的名称
34
+ _sender_client_names: List[str] = []
26
35
  # 用于控制消费者任务退出的事件
27
36
  _consumer_events: Dict[str, asyncio.Event] = {}
28
- # 存储消费者标签,用于取消消费
29
- _consumer_tags: Dict[str, str] = {}
30
- # 跟踪已完成的初始化操作(全局状态)
31
- _initialized_queues: Dict[str, Dict[str, bool]] = {}
32
- # 添加异步锁
33
- _init_lock: Dict[str, asyncio.Lock] = {}
37
+ # 存储消费者标签
38
+ _consumer_tags: Dict[str, ConsumerTag] = {}
39
+ # 跟踪已初始化的队列
40
+ _initialized_queues: Set[str] = set()
41
+ # 异步锁,确保初始化安全
42
+ _init_locks: Dict[str, asyncio.Lock] = {}
43
+ # 标记是否有监听器和发送器
34
44
  _has_listeners: bool = False
35
45
  _has_senders: bool = False
46
+ # 消费启动超时设置
47
+ CONSUMER_START_TIMEOUT = 30 # 30秒超时
36
48
 
37
49
  @classmethod
38
50
  def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
39
- """初始化RabbitMQ服务,保存配置和发送器/监听器状态"""
51
+ """
52
+ 初始化RabbitMQ服务(支持集群配置)
53
+ """
40
54
  from sycommon.synacos.nacos_service import NacosService
41
- # 获取 common 配置
42
- cls.config = NacosService(config).share_configs.get(
55
+
56
+ # 获取MQ配置
57
+ cls._config = NacosService(config).share_configs.get(
43
58
  "mq.yml", {}).get('spring', {}).get('rabbitmq', {})
44
- cls.config["APP_NAME"] = config.get("Name", "")
59
+ cls._config["APP_NAME"] = config.get("Name", "")
60
+
61
+ # 打印关键配置信息(显示所有集群节点)
62
+ logger.info(
63
+ f"RabbitMQ服务初始化 - 集群节点: {cls._config.get('host')}, "
64
+ f"端口: {cls._config.get('port')}, "
65
+ f"虚拟主机: {cls._config.get('virtual-host')}, "
66
+ f"应用名: {cls._config.get('APP_NAME')}"
67
+ )
45
68
 
46
69
  # 保存发送器和监听器存在状态
47
70
  cls._has_listeners = has_listeners
48
71
  cls._has_senders = has_senders
49
72
 
50
- return cls()
51
-
52
- @classmethod
53
- async def check_queue_exists(cls, channel, queue_name: str) -> bool:
54
- """检查队列是否存在"""
55
- try:
56
- await channel.declare_queue(
57
- name=queue_name,
58
- passive=True # 被动模式:仅检查队列是否存在
59
- )
60
- return True
61
- except Exception as e:
62
- return False
73
+ return cls
63
74
 
64
75
  @classmethod
65
- def create_client(cls, mq_config: dict, queue_name: str, **kwargs):
66
- """创建并返回新的RabbitMQClient实例,遵循队列创建规则"""
67
- # 获取当前项目名
68
- app_name = kwargs.get('app_name', cls.config.get(
69
- "APP_NAME", "")) if cls.config else kwargs.get('app_name', "")
70
-
71
- # 确保只在需要时处理一次队列名称
72
- processed_queue_name = queue_name
73
-
74
- # 通过上下文判断是否为发送器
75
- # 发送器场景:当没有监听器时
76
+ async def _create_client(cls, mq_config: dict, queue_name: str, **kwargs) -> RabbitMQClient:
77
+ """
78
+ 创建RabbitMQ客户端实例(支持集群节点列表)
79
+ """
80
+ app_name = kwargs.get('app_name', cls._config.get(
81
+ "APP_NAME", "")) if cls._config else ""
82
+
83
+ # 确定是否为发送器
76
84
  is_sender = not cls._has_listeners
77
85
 
78
- # 核心逻辑:根据组件存在状态决定是否允许创建队列
79
- # 1. 只有发送器:不允许创建队列
80
- # 2. 只有监听器:允许创建队列
81
- # 3. 两者都存在:允许创建队列(由监听器负责)
82
- create_if_not_exists = cls._has_listeners # 只要有监听器就允许创建
86
+ # 根据组件类型决定是否允许创建队列
87
+ create_if_not_exists = cls._has_listeners # 只有监听器允许创建队列
83
88
 
84
- # 当需要创建队列且是监听器时,拼接项目名
89
+ # 为监听器队列名称拼接应用名
90
+ processed_queue_name = queue_name
85
91
  if create_if_not_exists and not is_sender and processed_queue_name and app_name:
86
92
  if not processed_queue_name.endswith(f".{app_name}"):
87
93
  processed_queue_name = f"{processed_queue_name}.{app_name}"
88
- logging.debug(f"监听器队列名称自动拼接app-name: {processed_queue_name}")
94
+ logger.info(f"监听器队列名称自动拼接app-name: {processed_queue_name}")
95
+ else:
96
+ logger.info(f"监听器队列已包含app-name: {processed_queue_name}")
89
97
 
90
- logging.debug(
91
- f"队列创建权限 - 监听器存在: {cls._has_listeners}, 发送器存在: {cls._has_senders}, "
92
- f"是否发送器: {is_sender}, 允许创建: {create_if_not_exists}, 队列: {processed_queue_name}"
98
+ logger.debug(
99
+ f"创建客户端 - 队列: {processed_queue_name}, 发送器: {is_sender}, "
100
+ f"允许创建: {create_if_not_exists}"
93
101
  )
94
102
 
103
+ # 关键修改:将逗号分隔的host字符串拆分为集群节点列表
104
+ hosts_str = mq_config.get('host', "")
105
+ hosts_list = [host.strip()
106
+ for host in hosts_str.split(',') if host.strip()]
107
+ if not hosts_list:
108
+ raise ValueError("RabbitMQ集群配置为空,请检查host参数")
109
+
95
110
  return RabbitMQClient(
96
- host=mq_config.get('host', ""),
97
- port=mq_config.get('port', 0),
111
+ hosts=hosts_list,
112
+ port=mq_config.get('port', 5672),
98
113
  username=mq_config.get('username', ""),
99
114
  password=mq_config.get('password', ""),
100
115
  virtualhost=mq_config.get('virtual-host', "/"),
@@ -110,342 +125,476 @@ class RabbitMQService:
110
125
  create_if_not_exists=create_if_not_exists,
111
126
  connection_timeout=kwargs.get('connection_timeout', 10),
112
127
  rpc_timeout=kwargs.get('rpc_timeout', 5),
113
- app_name=app_name
128
+ app_name=app_name,
129
+ reconnection_delay=kwargs.get('reconnection_delay', 1),
130
+ max_reconnection_attempts=kwargs.get(
131
+ 'max_reconnection_attempts', 5),
132
+ heartbeat=kwargs.get('heartbeat', 60),
133
+ prefetch_count=kwargs.get('prefetch_count', 2),
134
+ message_process_timeout=kwargs.get('message_process_timeout', 30),
135
+ consumption_stall_threshold=kwargs.get(
136
+ 'consumption_stall_threshold', 120)
114
137
  )
115
138
 
116
139
  @classmethod
117
- async def setup_rabbitmq(
140
+ async def get_client(
118
141
  cls,
119
- mq_config: dict,
120
142
  client_name: str = "default", ** kwargs
121
143
  ) -> RabbitMQClient:
122
- """初始化RabbitMQ客户端并注册到服务中"""
123
- if client_name not in cls._init_lock:
124
- cls._init_lock[client_name] = asyncio.Lock()
125
-
126
- async with cls._init_lock[client_name]:
127
- if client_name in cls.clients:
128
- client = cls.clients[client_name]
129
- # 移除is_sender判断,通过上下文推断
144
+ """
145
+ 获取或创建RabbitMQ客户端
146
+
147
+ :param client_name: 客户端名称
148
+ :param kwargs: 客户端参数
149
+ :return: RabbitMQClient实例
150
+ """
151
+ if not cls._config:
152
+ raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
153
+
154
+ # 确保锁存在
155
+ if client_name not in cls._init_locks:
156
+ cls._init_locks[client_name] = asyncio.Lock()
157
+
158
+ async with cls._init_locks[client_name]:
159
+ # 如果客户端已存在且连接有效,直接返回
160
+ if client_name in cls._clients:
161
+ client = cls._clients[client_name]
130
162
  is_sender = not cls._has_listeners or (
131
163
  not kwargs.get('create_if_not_exists', True))
132
164
 
133
165
  if client.is_connected:
166
+ # 如果是监听器但队列未初始化,重新连接
134
167
  if not is_sender and not client.queue:
135
- logging.debug(f"客户端 '{client_name}' 存在但队列未初始化,重新连接")
168
+ logger.debug(f"客户端 '{client_name}' 队列未初始化,重新连接")
136
169
  client.create_if_not_exists = True
137
170
  await client.connect(force_reconnect=True, declare_queue=True)
138
- else:
139
- logging.debug(f"客户端 '{client_name}' 已存在且连接有效,直接返回")
140
171
  return client
141
172
  else:
142
- logging.debug(f"客户端 '{client_name}' 存在但连接已关闭,重新连接")
173
+ logger.debug(f"客户端 '{client_name}' 连接已关闭,重新连接")
143
174
  if not is_sender:
144
175
  client.create_if_not_exists = True
145
176
  await client.connect(declare_queue=not is_sender)
146
177
  return client
147
178
 
179
+ # 创建新客户端
148
180
  initial_queue_name = kwargs.pop('queue_name', '')
149
- # 移除is_sender参数,通过上下文推断
150
181
  is_sender = not cls._has_listeners or (
151
182
  not kwargs.get('create_if_not_exists', True))
152
183
 
153
184
  # 发送器特殊处理
154
185
  if is_sender:
155
186
  kwargs['create_if_not_exists'] = False
156
-
157
- client = RabbitMQService.create_client(
158
- mq_config,
187
+ client = await cls._create_client(
188
+ cls._config,
159
189
  initial_queue_name,
160
- app_name=cls.config.get("APP_NAME", ""),
161
- **kwargs # 不再传递is_sender参数
190
+ app_name=cls._config.get("APP_NAME", ""),
191
+ **kwargs
162
192
  )
163
-
164
193
  await client.connect(declare_queue=False)
165
- cls.clients[client_name] = client
194
+ cls._clients[client_name] = client
166
195
  return client
167
196
 
168
197
  # 监听器逻辑
169
198
  kwargs['create_if_not_exists'] = True
170
199
 
200
+ # 检查队列是否已初始化
171
201
  if initial_queue_name in cls._initialized_queues:
172
- logging.debug(f"队列 '{initial_queue_name}' 已初始化过,直接创建客户端")
173
- client = RabbitMQService.create_client(
174
- mq_config,
175
- initial_queue_name,
176
- # 不再传递is_sender参数
177
- app_name=cls.config.get("APP_NAME", ""), ** kwargs
202
+ logger.debug(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
203
+ client = await cls._create_client(
204
+ cls._config,
205
+ initial_queue_name, ** kwargs
178
206
  )
179
207
  await client.connect(declare_queue=True)
180
- cls.clients[client_name] = client
208
+ cls._clients[client_name] = client
181
209
  return client
182
210
 
183
- client = RabbitMQService.create_client(
184
- mq_config,
211
+ # 创建并连接客户端
212
+ client = await cls._create_client(
213
+ cls._config,
185
214
  initial_queue_name,
186
- app_name=cls.config.get("APP_NAME", ""),
187
- **kwargs # 不再传递is_sender参数
215
+ app_name=cls._config.get("APP_NAME", ""),
216
+ **kwargs
188
217
  )
189
218
 
190
219
  client.create_if_not_exists = True
191
- logging.debug(
192
- f"监听器客户端创建 - create_if_not_exists={client.create_if_not_exists}")
193
-
194
220
  await client.connect(declare_queue=True)
195
221
 
222
+ # 验证队列是否创建成功
196
223
  if not client.queue:
197
- logging.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
224
+ logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
198
225
  client.create_if_not_exists = True
199
226
  await client.connect(force_reconnect=True, declare_queue=True)
200
227
  if not client.queue:
201
228
  raise Exception(f"无法创建队列 '{initial_queue_name}'")
202
229
 
230
+ # 记录已初始化的队列
203
231
  final_queue_name = client.queue_name
232
+ if final_queue_name:
233
+ cls._initialized_queues.add(final_queue_name)
204
234
 
205
- if final_queue_name not in cls._initialized_queues:
206
- cls._initialized_queues[final_queue_name] = {
207
- "declared": True,
208
- "bound": True
209
- }
210
-
211
- cls.clients[client_name] = client
235
+ cls._clients[client_name] = client
212
236
  return client
213
237
 
214
238
  @classmethod
215
- async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False):
216
- """设置MQ发送客户端"""
239
+ async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False) -> None:
240
+ """
241
+ 设置消息发送器
242
+
243
+ :param senders: 发送器配置列表
244
+ :param has_listeners: 是否同时存在监听器
245
+ """
246
+ cls._has_senders = True
247
+ # 保存监听器存在状态
217
248
  cls._has_listeners = has_listeners
218
- cls._has_senders = True # 明确标记存在发送器
249
+ logger.info(f"开始设置 {len(senders)} 个消息发送器")
219
250
 
220
- async def setup_sender_tasks():
221
- for idx, sender_config in enumerate(senders):
222
- try:
223
- if not sender_config.queue_name:
224
- raise ValueError(f"发送器配置第{idx+1}项缺少queue_name")
225
-
226
- normalized_name = sender_config.queue_name
227
- app_name = cls.config.get("APP_NAME", "")
228
-
229
- if app_name and normalized_name.endswith(f".{app_name}"):
230
- normalized_name = normalized_name[:-
231
- len(f".{app_name}")]
232
- logging.debug(
233
- f"发送器队列名称移除app-name后缀: {normalized_name}")
234
-
235
- if normalized_name in cls.sender_client_names:
236
- logging.debug(f"发送客户端 '{normalized_name}' 已存在,跳过")
237
- continue
238
-
239
- if normalized_name in cls.clients:
240
- client = cls.clients[normalized_name]
241
- if not client.is_connected:
242
- await client.connect(declare_queue=False)
243
- else:
244
- # 移除is_sender参数传递
245
- client = await cls.setup_rabbitmq(
246
- cls.config,
247
- client_name=normalized_name,
248
- exchange_type=sender_config.exchange_type,
249
- durable=sender_config.durable,
250
- auto_delete=sender_config.auto_delete,
251
- auto_parse_json=sender_config.auto_parse_json,
252
- queue_name=sender_config.queue_name,
253
- create_if_not_exists=False # 仅通过此参数控制
254
- )
255
-
256
- if normalized_name not in cls.clients:
257
- cls.clients[normalized_name] = client
258
- logging.info(f"发送客户端 '{normalized_name}' 已添加")
259
-
260
- if normalized_name not in cls.sender_client_names:
261
- cls.sender_client_names.append(normalized_name)
262
- logging.info(f"发送客户端 '{normalized_name}' 初始化成功")
251
+ for idx, sender_config in enumerate(senders):
252
+ try:
253
+ if not sender_config.queue_name:
254
+ raise ValueError(f"发送器配置第{idx+1}项缺少queue_name")
255
+
256
+ queue_name = sender_config.queue_name
257
+ app_name = cls._config.get(
258
+ "APP_NAME", "") if cls._config else ""
259
+
260
+ # 处理发送器队列名称,移除可能的app-name后缀
261
+ normalized_name = queue_name
262
+ if app_name and normalized_name.endswith(f".{app_name}"):
263
+ normalized_name = normalized_name[:-len(f".{app_name}")]
264
+ logger.debug(f"发送器队列名称移除app-name后缀: {normalized_name}")
265
+
266
+ # 检查是否已初始化
267
+ if normalized_name in cls._sender_client_names:
268
+ logger.debug(f"发送客户端 '{normalized_name}' 已存在,跳过")
269
+ continue
270
+
271
+ # 获取或创建客户端
272
+ if normalized_name in cls._clients:
273
+ client = cls._clients[normalized_name]
274
+ if not client.is_connected:
275
+ await client.connect(declare_queue=False)
276
+ else:
277
+ client = await cls.get_client(
278
+ client_name=normalized_name,
279
+ exchange_type=sender_config.exchange_type,
280
+ durable=sender_config.durable,
281
+ auto_delete=sender_config.auto_delete,
282
+ auto_parse_json=sender_config.auto_parse_json,
283
+ queue_name=queue_name,
284
+ create_if_not_exists=False
285
+ )
263
286
 
264
- except Exception as e:
265
- logging.error(
266
- f"初始化发送客户端第{idx+1}项失败: {str(e)}", exc_info=True)
287
+ # 记录客户端
288
+ if normalized_name not in cls._clients:
289
+ cls._clients[normalized_name] = client
290
+ logger.info(f"发送客户端 '{normalized_name}' 已添加")
267
291
 
268
- try:
269
- await setup_sender_tasks()
270
- except Exception as e:
271
- logging.error(f"设置发送器时发生错误: {str(e)}", exc_info=True)
272
- raise
292
+ if normalized_name not in cls._sender_client_names:
293
+ cls._sender_client_names.append(normalized_name)
294
+ logger.info(f"发送客户端 '{normalized_name}' 初始化成功")
295
+
296
+ except Exception as e:
297
+ logger.error(
298
+ f"初始化发送客户端第{idx+1}项失败: {str(e)}", exc_info=True)
299
+
300
+ logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
273
301
 
274
302
  @classmethod
275
- async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False):
276
- """设置MQ监听器 - 确保自动创建队列"""
277
- # 存在监听器,设置标志
303
+ async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False) -> None:
304
+ """
305
+ 设置消息监听器
306
+
307
+ :param listeners: 监听器配置列表
308
+ :param has_senders: 是否同时存在发送器
309
+ """
278
310
  cls._has_listeners = True
311
+ # 保存发送器存在状态
312
+ cls._has_senders = has_senders
313
+ logger.info(f"开始设置 {len(listeners)} 个消息监听器")
314
+
315
+ for idx, listener_config in enumerate(listeners):
316
+ try:
317
+ # 转换配置并强制设置create_if_not_exists为True
318
+ listener_dict = listener_config.model_dump()
319
+ listener_dict['create_if_not_exists'] = True
320
+ queue_name = listener_dict['queue_name']
321
+
322
+ logger.info(f"设置监听器 {idx+1}/{len(listeners)}: {queue_name}")
323
+
324
+ # 添加监听器
325
+ await cls.add_listener(**listener_dict)
326
+ except Exception as e:
327
+ logger.error(
328
+ f"设置监听器 {idx+1} 失败: {str(e)}", exc_info=True)
329
+ logger.warning("继续处理其他监听器")
279
330
 
280
- for listener_config in listeners:
281
- # 将监听器配置转换为字典并添加到监听器
282
- # 强制设置create_if_not_exists为True
283
- listener_dict = listener_config.model_dump()
284
- listener_dict['create_if_not_exists'] = True
285
- await cls.add_listener(**listener_dict)
286
331
  # 启动所有消费者
287
332
  await cls.start_all_consumers()
288
333
 
334
+ # 验证消费者启动结果
335
+ await cls._verify_consumers_started()
336
+
337
+ logger.info(f"消息监听器设置完成")
338
+
339
+ @classmethod
340
+ async def _verify_consumers_started(cls, timeout: int = 30) -> None:
341
+ """
342
+ 验证消费者是否成功启动
343
+
344
+ :param timeout: 超时时间(秒)
345
+ """
346
+ start_time = asyncio.get_event_loop().time()
347
+ required_clients = list(cls._message_handlers.keys())
348
+ running_clients = []
349
+
350
+ # 等待所有消费者启动或超时
351
+ while len(running_clients) < len(required_clients) and \
352
+ (asyncio.get_event_loop().time() - start_time) < timeout:
353
+
354
+ running_clients = [
355
+ name for name, task in cls._consumer_tasks.items()
356
+ if not task.done() and name in cls._consumer_tags
357
+ ]
358
+
359
+ logger.info(
360
+ f"消费者启动验证: {len(running_clients)}/{len(required_clients)} 已启动")
361
+ await asyncio.sleep(2)
362
+
363
+ # 检查未成功启动的消费者
364
+ failed_clients = [
365
+ name for name in required_clients if name not in running_clients]
366
+ if failed_clients:
367
+ logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
368
+ # 尝试重新启动失败的消费者
369
+ for client_name in failed_clients:
370
+ logger.info(f"尝试重新启动消费者: {client_name}")
371
+ asyncio.create_task(cls.start_consumer(client_name))
372
+
289
373
  @classmethod
290
374
  async def add_listener(
291
375
  cls,
292
376
  queue_name: str,
293
- handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine], ** kwargs
377
+ handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], ** kwargs
294
378
  ) -> None:
295
- """添加RabbitMQ监听器 - 确保自动创建队列并拼接app-name"""
296
- if not cls.config:
379
+ """
380
+ 添加消息监听器
381
+
382
+ :param queue_name: 队列名称
383
+ :param handler: 消息处理函数
384
+ :param kwargs: 其他参数
385
+ """
386
+ if not cls._config:
297
387
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
298
388
 
299
- if queue_name in cls.message_handlers:
300
- logging.debug(f"监听器 '{queue_name}' 已存在,跳过重复添加")
389
+ if queue_name in cls._message_handlers:
390
+ logger.debug(f"监听器 '{queue_name}' 已存在,跳过重复添加")
301
391
  return
302
392
 
303
- # 为监听器强制设置create_if_not_exists=True
304
- kwargs['create_if_not_exists'] = True
305
-
306
- # 创建并初始化客户端(会处理队列名称)
307
- await cls.setup_rabbitmq(
308
- cls.config,
393
+ # 创建并初始化客户端
394
+ await cls.get_client(
309
395
  client_name=queue_name,
310
396
  queue_name=queue_name,
311
397
  **kwargs
312
398
  )
313
399
 
314
400
  # 注册消息处理器
315
- cls.register_handler(queue_name, handler)
316
-
317
- @classmethod
318
- def register_handler(
319
- cls,
320
- client_name: str,
321
- handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine]
322
- ) -> None:
323
- """为特定客户端注册消息处理器"""
324
- cls.message_handlers[client_name] = handler
401
+ cls._message_handlers[queue_name] = handler
402
+ logger.info(f"监听器 '{queue_name}' 已添加")
325
403
 
326
404
  @classmethod
327
405
  async def start_all_consumers(cls) -> None:
328
- """启动所有已注册客户端的消费者"""
329
- for client_name in cls.clients:
406
+ """启动所有已注册的消费者"""
407
+ for client_name in cls._message_handlers:
330
408
  await cls.start_consumer(client_name)
331
409
 
332
410
  @classmethod
333
- async def start_consumer(cls, client_name: str = "default") -> None:
334
- """启动指定客户端的消费者"""
335
- if client_name in cls.consumer_tasks and not cls.consumer_tasks[client_name].done():
336
- logging.debug(f"消费者 '{client_name}' 已在运行中,无需重复启动")
411
+ async def start_consumer(cls, client_name: str) -> None:
412
+ """
413
+ 启动指定客户端的消费者
414
+
415
+ :param client_name: 客户端名称
416
+ """
417
+ if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
418
+ logger.debug(f"消费者 '{client_name}' 已在运行中,无需重复启动")
337
419
  return
338
420
 
339
- if client_name not in cls.clients:
421
+ if client_name not in cls._clients:
340
422
  raise ValueError(f"RabbitMQ客户端 '{client_name}' 未初始化")
341
423
 
342
- client = cls.clients[client_name]
343
- handler = cls.message_handlers.get(client_name)
424
+ client = cls._clients[client_name]
425
+ handler = cls._message_handlers.get(client_name)
344
426
 
345
427
  if not handler:
346
- logging.warning(f"未找到客户端 '{client_name}' 的处理器,使用默认处理器")
428
+ logger.warning(f"未找到客户端 '{client_name}' 的处理器,使用默认处理器")
347
429
  handler = cls.default_message_handler
348
430
 
431
+ # 设置消息处理器
349
432
  client.set_message_handler(handler)
350
433
 
434
+ # 确保客户端已连接
435
+ start_time = asyncio.get_event_loop().time()
436
+ while not client.is_connected:
437
+ if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
438
+ raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
439
+
440
+ logger.debug(f"等待客户端 '{client_name}' 连接就绪...")
441
+ await asyncio.sleep(1)
442
+
443
+ # 创建停止事件
351
444
  stop_event = asyncio.Event()
352
445
  cls._consumer_events[client_name] = stop_event
353
446
 
447
+ # 定义消费任务
354
448
  async def consume_task():
355
449
  try:
356
- consumer_tag = await client.start_consuming()
450
+ # 启动消费,带重试机制
451
+ max_attempts = 5
452
+ attempt = 0
453
+ consumer_tag = None
454
+
455
+ while attempt < max_attempts and not stop_event.is_set():
456
+ try:
457
+ consumer_tag = await client.start_consuming()
458
+ if consumer_tag:
459
+ break
460
+ except Exception as e:
461
+ attempt += 1
462
+ logger.warning(
463
+ f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
464
+ if attempt < max_attempts:
465
+ await asyncio.sleep(2)
466
+
467
+ if not consumer_tag:
468
+ raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
469
+
470
+ # 记录消费者标签
357
471
  cls._consumer_tags[client_name] = consumer_tag
358
- logging.info(f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
359
-
360
- while not stop_event.is_set():
361
- await asyncio.sleep(0.1)
472
+ logger.info(f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
362
473
 
363
- logging.info(f"消费者 '{client_name}' 退出循环")
474
+ # 等待停止事件
475
+ await stop_event.wait()
476
+ logger.info(f"收到停止信号,消费者 '{client_name}' 准备退出")
364
477
 
365
478
  except asyncio.CancelledError:
366
- logging.info(f"消费者 '{client_name}' 被取消")
479
+ logger.info(f"消费者 '{client_name}' 被取消")
367
480
  except Exception as e:
368
- logging.error(
481
+ logger.error(
369
482
  f"消费者 '{client_name}' 错误: {str(e)}", exc_info=True)
483
+ # 非主动停止时尝试重启
484
+ if not stop_event.is_set():
485
+ logger.info(f"尝试重启消费者 '{client_name}'")
486
+ asyncio.create_task(cls.start_consumer(client_name))
370
487
  finally:
371
- await client.stop_consuming()
372
- logging.info(f"消费者 '{client_name}' 已完成清理")
488
+ # 清理资源
489
+ try:
490
+ await client.stop_consuming()
491
+ except Exception as e:
492
+ logger.error(f"停止消费者 '{client_name}' 时出错: {str(e)}")
373
493
 
494
+ # 移除状态记录
495
+ if client_name in cls._consumer_tags:
496
+ del cls._consumer_tags[client_name]
497
+ if client_name in cls._consumer_events:
498
+ del cls._consumer_events[client_name]
499
+
500
+ logger.info(f"消费者 '{client_name}' 已停止")
501
+
502
+ # 创建并跟踪消费任务
374
503
  task = asyncio.create_task(
375
504
  consume_task(), name=f"consumer-{client_name}")
376
- cls.consumer_tasks[client_name] = task
505
+ cls._consumer_tasks[client_name] = task
377
506
 
378
- def task_exception_handler(t: asyncio.Task):
507
+ # 添加任务完成回调
508
+ def task_done_callback(t: asyncio.Task) -> None:
379
509
  try:
380
510
  if t.done():
381
511
  t.result()
382
512
  except Exception as e:
383
- logging.error(f"消费者任务 '{client_name}' 异常: {str(e)}")
513
+ logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
514
+ # 任务异常时自动重启(如果服务未关闭)
515
+ if client_name in cls._message_handlers: # 检查处理器是否仍存在
516
+ asyncio.create_task(cls.start_consumer(client_name))
384
517
 
385
- task.add_done_callback(task_exception_handler)
518
+ task.add_done_callback(task_done_callback)
519
+ logger.info(f"消费者任务 '{client_name}' 已创建")
386
520
 
387
521
  @classmethod
388
- async def default_message_handler(cls, parsed_data: MQMsgModel, original_message):
522
+ async def default_message_handler(cls, parsed_data: MQMsgModel, original_message: AbstractIncomingMessage) -> None:
389
523
  """默认消息处理器"""
390
- logging.info(f"\n===== 收到消息 [{original_message.routing_key}] =====")
391
- logging.info(f"关联ID: {parsed_data.correlationDataId}")
392
- logging.info(f"主题代码: {parsed_data.topicCode}")
393
- logging.info(f"消息内容: {parsed_data.msg}")
394
- logging.info("===================\n")
524
+ logger.info(f"\n===== 收到消息 [{original_message.routing_key}] =====")
525
+ logger.info(f"关联ID: {parsed_data.correlationDataId}")
526
+ logger.info(f"主题代码: {parsed_data.topicCode}")
527
+ logger.info(f"消息内容: {parsed_data.msg}")
528
+ logger.info("===================\n")
395
529
 
396
530
  @classmethod
397
- def get_sender(cls, client_name: Optional[str] = None) -> Optional[RabbitMQClient]:
398
- """获取发送客户端(仅返回已注册的客户端)"""
399
- if not client_name:
400
- logging.warning("发送器名称不能为空")
531
+ def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
532
+ """
533
+ 获取发送客户端
534
+
535
+ :param queue_name: 队列名称
536
+ :return: RabbitMQClient实例或None
537
+ """
538
+ if not queue_name:
539
+ logger.warning("发送器名称不能为空")
401
540
  return None
402
541
 
403
- # 仅精确匹配已注册的客户端
404
- if client_name in cls.clients:
405
- return cls.clients[client_name]
542
+ # 检查是否在已注册的发送器中
543
+ if queue_name in cls._sender_client_names and queue_name in cls._clients:
544
+ return cls._clients[queue_name]
406
545
 
407
- app_name = cls.config.get("APP_NAME", "") if cls.config else ""
408
- if app_name and not client_name.endswith(f".{app_name}"):
409
- return None
546
+ # 检查是否带有app-name后缀
547
+ app_name = cls._config.get("APP_NAME", "") if cls._config else ""
548
+ if app_name and f"{queue_name}.{app_name}" in cls._sender_client_names:
549
+ return cls._clients.get(f"{queue_name}.{app_name}")
410
550
 
411
- logging.debug(f"发送器 '{client_name}' 不在已注册客户端列表中")
551
+ logger.debug(f"未找到发送器 '{queue_name}'")
412
552
  return None
413
553
 
414
554
  @classmethod
415
555
  async def send_message(
416
556
  cls,
417
557
  data: Union[BaseModel, str, Dict[str, Any], None],
418
- queue_name: Optional[str] = None, **kwargs
558
+ queue_name: str, ** kwargs
419
559
  ) -> None:
420
- """发送消息到RabbitMQ"""
560
+ """
561
+ 发送消息到指定队列
562
+
563
+ :param data: 消息数据
564
+ :param queue_name: 队列名称
565
+ :param kwargs: 其他参数
566
+ """
567
+ # 获取发送客户端
421
568
  sender = cls.get_sender(queue_name)
422
-
423
569
  if not sender:
424
570
  error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
425
- logging.error(error_msg)
571
+ logger.error(error_msg)
426
572
  raise ValueError(error_msg)
427
573
 
428
- if not sender.connection or sender.connection.is_closed:
429
- logging.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
574
+ # 确保连接有效
575
+ if not sender.is_connected:
576
+ logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
430
577
  try:
431
578
  await sender.connect(force_reconnect=True)
432
579
  except Exception as e:
433
- logging.error(f"发送器 '{queue_name}' 重新连接失败: {str(e)}")
580
+ logger.error(f"发送器 '{queue_name}' 重新连接失败: {str(e)}")
434
581
  raise
435
582
 
436
583
  try:
437
- msg = ''
584
+ # 处理消息数据
585
+ msg_content = ""
438
586
  if isinstance(data, str):
439
- msg = data
587
+ msg_content = data
440
588
  elif isinstance(data, BaseModel):
441
- msg = data.model_dump_json()
589
+ msg_content = data.model_dump_json()
442
590
  elif isinstance(data, dict):
443
591
  import json
444
- msg = json.dumps(data)
592
+ msg_content = json.dumps(data)
445
593
 
594
+ # 创建标准消息模型
446
595
  mq_message = MQMsgModel(
447
596
  topicCode=queue_name.split('.')[0] if queue_name else "",
448
- msg=msg,
597
+ msg=msg_content,
449
598
  correlationDataId=kwargs.get(
450
599
  'correlationDataId', SYLogger.get_trace_id()),
451
600
  groupId=kwargs.get('groupId', ''),
@@ -454,7 +603,7 @@ class RabbitMQService:
454
603
  traceId=SYLogger.get_trace_id()
455
604
  )
456
605
 
457
- # 不设置Java会解析失败导致丢掉消息
606
+ # 构建消息头
458
607
  mq_header = {
459
608
  "context": SsoUser(
460
609
  tenant_id="T000002",
@@ -467,67 +616,81 @@ class RabbitMQService:
467
616
  ).model_dump_json()
468
617
  }
469
618
 
470
- await sender.send_message(
619
+ # 发送消息
620
+ await sender.publish(
471
621
  message_body=mq_message.model_dump_json(),
472
622
  headers=mq_header,
623
+ content_type="application/json"
473
624
  )
474
- logging.info(
475
- f"消息发送成功 (客户端: {queue_name or cls.sender_client_names[0]})")
625
+ logger.info(f"消息发送成功 (队列: {queue_name})")
476
626
  except Exception as e:
477
- logging.error(f"消息发送失败: {str(e)}", exc_info=True)
627
+ logger.error(f"消息发送失败: {str(e)}", exc_info=True)
478
628
  raise
479
629
 
480
630
  @classmethod
481
- async def shutdown(cls, timeout: float = 5.0) -> None:
482
- """优雅关闭所有客户端和消费者任务"""
631
+ async def shutdown(cls, timeout: float = 10.0) -> None:
632
+ """
633
+ 优雅关闭所有资源
634
+
635
+ :param timeout: 超时时间(秒)
636
+ """
483
637
  start_time = asyncio.get_event_loop().time()
638
+ logger.info("开始关闭RabbitMQ服务...")
484
639
 
640
+ # 发送停止信号给所有消费者
485
641
  for client_name, event in cls._consumer_events.items():
486
642
  event.set()
487
- logging.info(f"已向消费者 '{client_name}' 发送退出信号")
643
+ logger.info(f"已向消费者 '{client_name}' 发送退出信号")
488
644
 
645
+ # 等待消费者任务完成
489
646
  remaining_time = max(
490
647
  0.0, timeout - (asyncio.get_event_loop().time() - start_time))
491
- if remaining_time > 0:
492
- tasks_to_wait = [
493
- t for t in cls.consumer_tasks.values() if not t.done()]
494
- if tasks_to_wait:
495
- try:
496
- done, pending = await asyncio.wait(
497
- tasks_to_wait,
498
- timeout=remaining_time,
499
- return_when=asyncio.ALL_COMPLETED
500
- )
648
+ if remaining_time > 0 and cls._consumer_tasks:
649
+ try:
650
+ # 等待所有消费者任务完成或超时
651
+ done, pending = await asyncio.wait(
652
+ list(cls._consumer_tasks.values()),
653
+ timeout=remaining_time,
654
+ return_when=asyncio.ALL_COMPLETED
655
+ )
501
656
 
502
- for task in pending:
503
- task_name = task.get_name()
504
- logging.warning(f"任务 '{task_name}' 关闭超时,强制取消")
505
- task.cancel()
506
- try:
507
- await task
508
- except (asyncio.CancelledError, RuntimeError):
509
- pass
657
+ # 处理超时的任务
658
+ for task in pending:
659
+ task_name = task.get_name()
660
+ logger.warning(f"任务 '{task_name}' 关闭超时,强制取消")
661
+ task.cancel()
662
+ try:
663
+ await task
664
+ except (asyncio.CancelledError, RuntimeError):
665
+ pass
510
666
 
511
- except Exception as e:
512
- logging.error(f"等待消费者任务完成时出错: {str(e)}")
667
+ except Exception as e:
668
+ logger.error(f"等待消费者任务完成时出错: {str(e)}")
513
669
 
670
+ # 关闭所有客户端连接
514
671
  remaining_time = max(
515
672
  0.0, timeout - (asyncio.get_event_loop().time() - start_time))
516
- if remaining_time > 0:
517
- for name, client in cls.clients.items():
673
+ if remaining_time > 0 and cls._clients:
674
+ client_count = len(cls._clients)
675
+ client_timeout = remaining_time / client_count # 平均分配剩余时间
676
+
677
+ for name, client in cls._clients.items():
518
678
  try:
519
- await asyncio.wait_for(client.stop_consuming(), timeout=remaining_time/len(cls.clients))
520
- await asyncio.wait_for(client.close(), timeout=remaining_time/len(cls.clients))
679
+ await asyncio.wait_for(client.close(), timeout=client_timeout)
521
680
  except Exception as e:
522
- logging.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
523
- logging.info(f"RabbitMQ客户端 '{name}' 已关闭")
524
-
525
- cls.consumer_tasks.clear()
681
+ logger.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
682
+ logger.info(f"客户端 '{name}' 已关闭")
683
+
684
+ # 清理所有状态
685
+ cls._clients.clear()
686
+ cls._consumer_tasks.clear()
687
+ cls._message_handlers.clear()
688
+ cls._sender_client_names.clear()
526
689
  cls._consumer_events.clear()
527
690
  cls._consumer_tags.clear()
528
- cls.clients.clear()
529
- cls.sender_client_names.clear()
530
- cls._init_lock.clear()
531
- cls._has_listeners = False # 重置标志
691
+ cls._initialized_queues.clear()
692
+ cls._init_locks.clear()
693
+ cls._has_listeners = False
694
+ cls._has_senders = False
532
695
 
533
- logging.info("RabbitMQ服务已完全关闭")
696
+ logger.info("RabbitMQ服务已完全关闭")