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