sycommon-python-lib 0.1.56b4__py3-none-any.whl → 0.1.56b6__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.
@@ -145,10 +145,15 @@ class RabbitMQClient:
145
145
  raise RuntimeError("客户端已关闭,无法重新连接")
146
146
 
147
147
  async with self._connect_lock:
148
- # 释放旧资源(回调+通道,单通道无需归还,仅清理状态)
149
- if self._conn_close_callback and self._channel_conn:
150
- self._channel_conn.close_callbacks.discard(
151
- self._conn_close_callback)
148
+ # 1. 清理旧连接回调(防止内存泄漏)
149
+ if self._channel_conn and self._conn_close_callback:
150
+ try:
151
+ self._channel_conn.close_callbacks.discard(
152
+ self._conn_close_callback)
153
+ except Exception:
154
+ pass
155
+
156
+ # 2. 清理状态
152
157
  self._channel = None
153
158
  self._channel_conn = None
154
159
  self._exchange = None
@@ -156,18 +161,14 @@ class RabbitMQClient:
156
161
  self._conn_close_callback = None
157
162
 
158
163
  try:
159
- # 从单通道池获取通道+连接(连接池自动确保通道有效)
164
+ # 3. 获取新通道
160
165
  self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
161
166
 
167
+ # 4. 设置新连接回调(使用 weakref)
162
168
  def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
163
- """连接关闭回调:触发固定间隔重连"""
164
- logger.warning(
165
- f"客户端连接关闭: {conn!r},原因: {exc}", exc_info=exc)
166
- self._reconnect_fail_count += 1
167
- # 超过阈值告警
168
- if self._reconnect_fail_count >= self._reconnect_alert_threshold:
169
- logger.error(
170
- f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
169
+ # 注意:这里需要访问外部的 self,使用闭包或 weakref
170
+ # 简单起见,这里用闭包,但务必在 self.close 或 self.connect 时清理回调
171
+ logger.warning(f"检测到连接关闭: {exc}")
171
172
  if not self._closed:
172
173
  asyncio.create_task(self._safe_reconnect())
173
174
 
@@ -176,20 +177,26 @@ class RabbitMQClient:
176
177
  self._channel_conn.close_callbacks.add(
177
178
  self._conn_close_callback)
178
179
 
179
- # 重建交换机/队列资源
180
+ # 5. 重建资源
180
181
  await self._rebuild_resources()
181
182
 
182
- # 重连成功,重置失败计数器
183
+ # 重置计数
183
184
  self._reconnect_fail_count = 0
184
185
  logger.info("客户端连接初始化完成")
185
186
  except Exception as e:
186
187
  logger.error(f"客户端连接失败: {str(e)}", exc_info=True)
187
- # 清理异常状态
188
- if self._conn_close_callback and self._channel_conn:
189
- self._channel_conn.close_callbacks.discard(
190
- self._conn_close_callback)
188
+ # 失败时也要清理可能产生的残留引用
189
+ if self._channel_conn and self._conn_close_callback:
190
+ try:
191
+ self._channel_conn.close_callbacks.discard(
192
+ self._conn_close_callback)
193
+ except Exception:
194
+ pass
195
+ # 清空状态
191
196
  self._channel = None
192
197
  self._channel_conn = None
198
+ self._conn_close_callback = None
199
+
193
200
  # 触发重连
194
201
  if not self._closed:
195
202
  asyncio.create_task(self._safe_reconnect())
@@ -238,7 +245,7 @@ class RabbitMQClient:
238
245
  logger.info("消息处理器设置成功")
239
246
 
240
247
  async def start_consuming(self) -> Optional[ConsumerTag]:
241
- """启动消息消费(支持自动重连)"""
248
+ """启动消息消费(支持自动重连 + Header 重试计数限制)"""
242
249
  if self._closed:
243
250
  raise RuntimeError("客户端已关闭,无法启动消费")
244
251
 
@@ -251,10 +258,11 @@ class RabbitMQClient:
251
258
  if not self._queue:
252
259
  raise RuntimeError("未配置队列名或队列未创建,无法启动消费")
253
260
 
254
- # 2. 定义消费回调(包含异常处理和重连逻辑)
261
+ # 2. 定义消费回调
255
262
  async def consume_callback(message: AbstractIncomingMessage):
256
263
  try:
257
264
  # 解析消息体
265
+ msg_obj: MQMsgModel
258
266
  if self.auto_parse_json:
259
267
  try:
260
268
  body_dict = json.loads(
@@ -263,19 +271,26 @@ class RabbitMQClient:
263
271
  except json.JSONDecodeError as e:
264
272
  logger.error(
265
273
  f"JSON消息解析失败: {str(e)},消息体: {message.body[:100]}...")
266
- await message.nack(requeue=False) # 解析失败,不重入队
274
+ # 解析失败通常无法重试,直接丢弃
275
+ await message.nack(requeue=False)
267
276
  return
268
277
  else:
269
278
  msg_obj = MQMsgModel(
270
279
  body=message.body.decode("utf-8"),
271
280
  routing_key=message.routing_key,
272
281
  delivery_tag=message.delivery_tag,
282
+ traceId=message.headers.get("trace-id", None),
283
+ headers=message.headers
273
284
  )
274
285
 
286
+ # 统一追踪ID
287
+ SYLogger.set_trace_id(
288
+ message.headers.get("trace-id", None))
289
+
275
290
  # 调用消息处理器
276
291
  await self._message_handler(msg_obj, message)
277
292
 
278
- # 手动ACK
293
+ # 处理成功,手动ACK
279
294
  await message.ack()
280
295
  logger.debug(
281
296
  f"消息处理成功,delivery_tag: {message.delivery_tag}")
@@ -285,19 +300,61 @@ class RabbitMQClient:
285
300
  f"消息处理失败,delivery_tag: {message.delivery_tag}",
286
301
  exc_info=True
287
302
  )
288
- # 处理失败逻辑:首次失败重入队,再次失败丢弃
289
- if message.redelivered:
303
+
304
+ # 1. 获取当前重试次数,默认为 0
305
+ current_retry_count = 0
306
+ if message.headers:
307
+ current_retry_count = int(
308
+ message.headers.get("x-retry-count", 0))
309
+
310
+ # 2. 检查是否超过最大重试次数 (3次)
311
+ MAX_RETRY = 3
312
+ if current_retry_count >= MAX_RETRY:
290
313
  logger.warning(
291
- f"消息已重入队过,本次拒绝入队: {message.delivery_tag}")
314
+ f"消息重试次数已达上限({MAX_RETRY}),丢弃消息。"
315
+ f"delivery_tag: {message.delivery_tag}, routing_key: {message.routing_key}"
316
+ )
317
+ # 丢弃消息(不重新入队)
292
318
  await message.reject(requeue=False)
293
- else:
294
- logger.warning(f"消息重入队: {message.delivery_tag}")
295
- await message.nack(requeue=True)
319
+ return
296
320
 
297
- # 连接失效则触发重连
298
- if not await self.is_connected:
299
- logger.warning("连接已失效,触发客户端重连")
300
- asyncio.create_task(self._safe_reconnect())
321
+ # 3. 次数未满,准备重入队
322
+ # 注意:AbstractIncomingMessage.headers 是只读的 (frozendict),
323
+ # 不能直接 message.headers["x-retry-count"] = ...
324
+ # 必须重新构造 headers 字典。
325
+ new_headers = dict(
326
+ message.headers) if message.headers else {}
327
+ new_headers["x-retry-count"] = current_retry_count + 1
328
+
329
+ logger.warning(
330
+ f"消息处理失败,准备第 {current_retry_count + 1} 次重入队。"
331
+ f"delivery_tag: {message.delivery_tag}"
332
+ )
333
+
334
+ try:
335
+ new_msg = Message(
336
+ body=message.body,
337
+ headers=new_headers,
338
+ content_type=message.content_type,
339
+ delivery_mode=message.delivery_mode
340
+ )
341
+
342
+ # 发布到原 Exchange 和 RoutingKey
343
+ # 注意:这里可能会抛出异常,如果抛异常,消息可能会丢失(因为已经 Nack 掉了)
344
+ await self._exchange.publish(
345
+ new_msg,
346
+ routing_key=message.routing_key
347
+ )
348
+
349
+ # 发布成功后,Ack 掉旧消息
350
+ await message.ack()
351
+ logger.info(
352
+ f"消息已重新发布 (重试次数: {new_headers['x-retry-count']})")
353
+
354
+ except Exception as publish_err:
355
+ logger.error(f"重新发布消息失败(消息丢失): {publish_err}")
356
+ # 重新发布失败,只能丢弃或者 Nack(False)
357
+ await message.reject(requeue=False)
301
358
 
302
359
  # 3. 启动消费(单通道消费,避免阻塞发布需确保业务回调非阻塞)
303
360
  self._consumer_tag = await self._queue.consume(consume_callback)
@@ -5,16 +5,26 @@ from aio_pika import connect_robust, RobustChannel, Message
5
5
  from aio_pika.abc import (
6
6
  AbstractRobustConnection, AbstractQueue, AbstractExchange, AbstractMessage
7
7
  )
8
- from aio_pika.exceptions import ChannelClosed
9
- import aiormq.exceptions
10
-
11
8
  from sycommon.logging.kafka_log import SYLogger
12
9
 
13
10
  logger = SYLogger
14
11
 
15
12
 
13
+ class AsyncProperty:
14
+ """实现 await obj.attr 的支持"""
15
+
16
+ def __init__(self, method):
17
+ self.method = method
18
+
19
+ def __get__(self, obj, objtype=None):
20
+ if obj is None:
21
+ return self
22
+ # 关键:当访问 obj.attr 时,直接返回协程对象,而不是方法本身
23
+ return self.method(obj)
24
+
25
+
16
26
  class RabbitMQConnectionPool:
17
- """单连接单通道RabbitMQ客户端(核心特性:依赖connect_robust原生自动重连/恢复 + 仅关闭时释放资源)"""
27
+ """单连接单通道RabbitMQ客户端 (增强版日志)"""
18
28
 
19
29
  def __init__(
20
30
  self,
@@ -29,7 +39,6 @@ class RabbitMQConnectionPool:
29
39
  reconnect_interval: int = 5,
30
40
  prefetch_count: int = 2,
31
41
  ):
32
- # 基础配置校验与初始化
33
42
  self.hosts = [host.strip() for host in hosts if host.strip()]
34
43
  if not self.hosts:
35
44
  raise ValueError("至少需要提供一个RabbitMQ主机地址")
@@ -44,295 +53,318 @@ class RabbitMQConnectionPool:
44
53
  self.reconnect_interval = reconnect_interval
45
54
  self.prefetch_count = prefetch_count
46
55
 
47
- # 初始化时随机选择一个主机地址(固定使用,依赖原生重连)
48
56
  self._current_host: str = random.choice(self.hosts)
49
- logger.info(
50
- f"随机选择RabbitMQ主机: {self._current_host}(依赖connect_robust原生自动重连/恢复)")
57
+ logger.info(f"[INIT] 随机选择RabbitMQ主机: {self._current_host}")
51
58
 
52
- # 核心资源(单连接+单通道,基于原生自动重连)
53
- self._connection: Optional[AbstractRobustConnection] = None # 原生自动重连连接
54
- self._channel: Optional[RobustChannel] = None # 单通道(原生自动恢复)
55
- # 消费者通道跟踪(独立于主通道)
56
- self._consumer_channels: Dict[str,
57
- Tuple[RobustChannel, Callable, bool, dict]] = {}
59
+ # 核心资源
60
+ self._connection: Optional[AbstractRobustConnection] = None
61
+ self._channel: Optional[RobustChannel] = None
62
+ self._consumer_channels: Dict[str, RobustChannel] = {}
58
63
 
59
- # 状态控制(并发安全+生命周期管理)
64
+ # 状态控制
60
65
  self._lock = asyncio.Lock()
61
66
  self._initialized = False
62
67
  self._is_shutdown = False
63
68
 
64
- async def _is_connection_valid(self) -> bool:
65
- """原子化检查连接有效性(所有状态判断均加锁,确保原子性)"""
66
- async with self._lock:
67
- # 优先级:先判断是否关闭,再判断是否初始化,最后判断连接状态
68
- return not self._is_shutdown and self._initialized and self._connection is not None and not self._connection.is_closed
69
-
70
- @property
69
+ @AsyncProperty
71
70
  async def is_alive(self) -> bool:
72
71
  """对外暴露的连接存活状态(原子化判断)"""
73
72
  async with self._lock:
74
73
  if self._is_shutdown:
75
74
  return False
76
- # 存活条件:未关闭 + 已初始化 + 连接有效 + 主通道有效
77
- return self._initialized and self._connection is not None and not self._connection.is_closed and self._channel is not None and not self._channel.is_closed
78
75
 
79
- async def _create_connection(self) -> AbstractRobustConnection:
80
- """创建原生自动重连连接(仅创建一次,内部自动重试)"""
81
- async with self._lock:
82
- if self._is_shutdown:
83
- raise RuntimeError("客户端已关闭,无法创建连接")
76
+ if not self._initialized:
77
+ return False
84
78
 
85
- conn_url = f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}&reconnect_interval={self.reconnect_interval}&fail_fast=1"
86
- logger.info(f"尝试创建原生自动重连连接: {self._current_host}:{self.port}")
79
+ if self._connection is None or self._connection.is_closed:
80
+ return False
81
+
82
+ # 可选:检查主通道是否存活
83
+ if self._channel is None or self._channel.is_closed:
84
+ # 如果你认为通道断了连接也算死,就保留这行;否则删除
85
+ return False
87
86
 
87
+ return True
88
+
89
+ async def _create_connection_impl(self) -> AbstractRobustConnection:
90
+ """
91
+ 连接创建入口
92
+ """
93
+ conn_url = (
94
+ f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/"
95
+ f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
96
+ f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
97
+ )
98
+ logger.info(
99
+ f"🔌 [CONNECT_START] 尝试创建连接 -> {self._current_host}:{self.port}")
88
100
  try:
89
- conn = await connect_robust(
90
- conn_url,
91
- timeout=self.connection_timeout,
92
- )
93
- logger.info(f"连接创建成功: {self._current_host}:{self.port}(原生自动重连已启用)")
101
+ conn = await connect_robust(conn_url, timeout=self.connection_timeout)
102
+ # 注意:connect_robust 返回时,底层 TCP 可能还在握手,但对象已创建
103
+ logger.info(f"✅ [CONNECT_OK] 连接对象创建成功: {id(conn)}")
94
104
  return conn
95
105
  except Exception as e:
96
- logger.error(f"连接创建失败: {str(e)}", exc_info=True)
97
- raise ConnectionError(
98
- f"无法连接RabbitMQ主机 {self._current_host}:{self.port}") from e
99
-
100
- async def _init_single_channel(self):
101
- """初始化单通道(通道自带原生自动恢复)"""
102
- async with self._lock:
103
- # 先判断是否关闭(优先级最高)
104
- if self._is_shutdown:
105
- raise RuntimeError("客户端已关闭,无法初始化通道")
106
- # 再判断连接是否有效
107
- if not self._connection or self._connection.is_closed:
108
- raise RuntimeError("无有效连接,无法初始化通道")
109
-
110
- # 清理旧通道(如果存在)
111
- if self._channel and not self._channel.is_closed:
112
- await self._channel.close()
106
+ logger.error(f"❌ [CONNECT_FAIL] 连接创建失败: {str(e)}")
107
+ raise ConnectionError(f"无法连接RabbitMQ {self._current_host}") from e
113
108
 
114
- # 创建单通道并设置QOS
115
- try:
116
- self._channel = await self._connection.channel()
117
- await self._channel.set_qos(prefetch_count=self.prefetch_count)
118
- logger.info(f"单通道初始化完成(带原生自动恢复)")
119
- except Exception as e:
120
- logger.error(f"创建单通道失败: {str(e)}", exc_info=True)
121
- raise
109
+ async def _create_channel_impl(self, connection: AbstractRobustConnection) -> RobustChannel:
110
+ """创建通道"""
111
+ try:
112
+ channel = await connection.channel()
113
+ await channel.set_qos(prefetch_count=self.prefetch_count)
114
+ logger.debug(f"✅ [CHANNEL_OK] 通道创建成功: {id(channel)}")
115
+ return channel
116
+ except Exception as e:
117
+ logger.error(f"❌ [CHANNEL_FAIL] 通道创建失败: {str(e)}")
118
+ raise
122
119
 
123
- async def _check_and_recover_channel(self) -> RobustChannel:
124
- """检查并恢复通道(确保通道有效,所有状态判断加锁)"""
120
+ async def _ensure_main_channel(self) -> RobustChannel:
121
+ """确保主通道有效 (原子操作 + 自动轮询重试 + 失败清理)"""
125
122
  async with self._lock:
126
- # 1. 先判断是否关闭(优先级最高)
127
123
  if self._is_shutdown:
128
- raise RuntimeError("客户端已关闭,无法获取通道")
129
- # 2. 检查连接状态
130
- if not self._connection or self._connection.is_closed:
131
- raise RuntimeError("连接已关闭(等待原生重连)")
132
- # 3. 通道失效时重新创建
133
- if not self._channel or self._channel.is_closed:
134
- logger.warning("通道失效,重新创建(依赖原生自动恢复)")
135
- await self._init_single_channel()
124
+ raise RuntimeError("客户端已关闭")
125
+
126
+ # 如果连接对象不存在或已关闭,进入重连流程
127
+ if self._connection is None or self._connection.is_closed:
128
+ retry_hosts = self.hosts.copy() # 复制一份列表用于重试
129
+ last_error = None
130
+ temp_conn = None # 临时变量,用于引用本次尝试创建的连接
131
+
132
+ # 轮询尝试列表中的每一个 Host
133
+ while retry_hosts:
134
+ host = random.choice(retry_hosts)
135
+ retry_hosts.remove(host) # 从待尝试列表中移除
136
+
137
+ logger.info(f"⚠️ [RECONNECT] 尝试连接节点: {host}")
138
+ self._current_host = host
139
+ temp_conn = None # 重置临时引用
140
+
141
+ try:
142
+ conn_url = (
143
+ f"amqp://{self.username}:{self.password}@{host}:{self.port}/"
144
+ f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
145
+ f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
146
+ )
147
+
148
+ # 尝试连接
149
+ temp_conn = await connect_robust(conn_url, timeout=self.connection_timeout)
150
+
151
+ # --- 关键点:连接成功 ---
152
+ self._connection = temp_conn
153
+ temp_conn = None # 清空临时引用,因为所有权已经移交给 self._connection
154
+
155
+ logger.info(f"🔗 [RECONNECT_OK] 成功连接到节点: {host}")
156
+ last_error = None
157
+ break # 成功,跳出循环
158
+
159
+ except Exception as e:
160
+ logger.warning(
161
+ f"❌ [RECONNECT_FAIL] 节点 {host} 不可用: {str(e)}")
162
+
163
+ # 【核心修复】清理失败的连接对象
164
+ if temp_conn is not None:
165
+ try:
166
+ logger.debug(
167
+ f"🧹 [CLEANUP] 正在关闭失败的连接对象: {id(temp_conn)}")
168
+ # 即使连接对象处于异常状态,close() 通常也是安全的
169
+ await temp_conn.close()
170
+ logger.debug(f"✅ [CLEANUP] 失败连接已关闭")
171
+ except Exception as close_err:
172
+ logger.warning(
173
+ f"⚠️ [CLEANUP_ERR] 关闭失败连接时出错: {str(close_err)}")
174
+
175
+ last_error = e
176
+ asyncio.sleep(self.reconnect_interval)
177
+ continue # 继续试下一个
178
+
179
+ # 如果所有节点都试完了还是失败
180
+ if last_error:
181
+ logger.error("💥 [RECONNECT_FATAL] 所有 RabbitMQ 节点均不可用")
182
+ raise ConnectionError("所有 RabbitMQ 节点连接失败") from last_error
183
+
184
+ # 2. 确保主通道存在
185
+ if self._channel is None or self._channel.is_closed:
186
+ logger.info("⚠️ [RECOVER_CHANNEL] 检测到主通道不存在或已关闭,开始恢复...")
187
+ self._channel = await self._create_channel_impl(self._connection)
136
188
 
137
189
  return self._channel
138
190
 
139
191
  async def init_pools(self):
140
- """初始化客户端(仅执行一次)"""
192
+ """
193
+ 初始化入口与异常处理 (修复泄漏的关键)
194
+ """
141
195
  async with self._lock:
142
- # 原子化判断:是否已关闭/已初始化
143
196
  if self._is_shutdown:
144
- raise RuntimeError("客户端已关闭,无法初始化")
197
+ raise RuntimeError("客户端已关闭")
145
198
  if self._initialized:
146
- logger.warning("客户端已初始化,无需重复调用")
147
199
  return
148
200
 
201
+ conn_created_in_this_try = None
149
202
  try:
150
- # 1. 创建原生自动重连连接
151
- self._connection = await self._create_connection()
203
+ # 步骤 A: 创建连接 (在锁外进行,避免阻塞其他操作)
204
+ conn = await self._create_connection_impl()
205
+ conn_created_in_this_try = conn # 记录本次创建的对象,用于失败回滚
152
206
 
153
- # 2. 初始化单通道
154
- await self._init_single_channel()
155
-
156
- # 3. 标记为已初始化(加锁保护)
207
+ # 步骤 B: 更新状态和初始化通道 (在锁内进行,保证原子性)
157
208
  async with self._lock:
209
+ if self._is_shutdown:
210
+ # 如果在创建连接期间,外部调用了 close,则必须立即清理刚创建的连接
211
+ logger.warning("⚠️ [ABORT] 检测到关闭信号,放弃初始化并清理资源")
212
+ raise RuntimeError("客户端已关闭")
213
+
214
+ self._connection = conn
215
+ self._channel = await self._create_channel_impl(conn)
158
216
  self._initialized = True
217
+ logger.info(
218
+ f"🚀 [INIT_SUCCESS] 客户端初始化完成. ConnID: {id(self._connection)}")
159
219
 
160
- logger.info("RabbitMQ单通道客户端初始化完成(原生自动重连/恢复已启用)")
161
220
  except Exception as e:
162
- logger.error(f"初始化失败: {str(e)}", exc_info=True)
163
- await self.close() # 初始化失败直接关闭
221
+ logger.error(f"💥 [INIT_ERROR] 初始化流程异常: {str(e)}", exc_info=True)
222
+ # 如果步骤A成功但步骤B失败(例如通道创建失败),或者步骤B中出错,
223
+ # 必须显式关闭在步骤A中创建的连接,否则它会变成“游离连接”。
224
+ if conn_created_in_this_try:
225
+ logger.warning(
226
+ f"🧹 [LEAK_PREVENTION] 检测到初始化失败,正在显式关闭刚创建的连接: {id(conn_created_in_this_try)}")
227
+ try:
228
+ await conn_created_in_this_try.close()
229
+ logger.info(
230
+ f"✅ [CLOSE_OK] 泄漏连接已关闭: {id(conn_created_in_this_try)}")
231
+ except Exception as close_err:
232
+ logger.error(f"❌ [CLOSE_ERR] 关闭泄漏连接时出错: {str(close_err)}")
233
+
234
+ # 如果是因为中途关闭导致的错误,不需要再次调用全局 close,否则调用
235
+ if not self._is_shutdown:
236
+ await self.close()
164
237
  raise
165
238
 
166
239
  async def acquire_channel(self) -> Tuple[RobustChannel, AbstractRobustConnection]:
167
- """获取单通道(返回 (通道, 连接) 元组,保持API兼容)"""
168
- async with self._lock:
169
- # 原子化状态校验
170
- if self._is_shutdown:
171
- raise RuntimeError("客户端已关闭,无法获取通道")
172
- if not self._initialized:
173
- raise RuntimeError("客户端未初始化,请先调用init_pools()")
174
-
175
- # 检查并恢复通道
176
- channel = await self._check_and_recover_channel()
177
- return channel, self._connection # 单通道无需管理"使用中/空闲"状态
178
-
179
- async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
180
- """声明队列(使用单通道)"""
181
- channel, _ = await self.acquire_channel()
182
- return await channel.declare_queue(queue_name, **kwargs)
183
-
184
- async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
185
- """声明交换机(使用单通道)"""
186
- channel, _ = await self.acquire_channel()
187
- return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)
240
+ """获取主通道"""
241
+ if not self._initialized and not self._is_shutdown:
242
+ await self.init_pools()
243
+ return await self._ensure_main_channel(), self._connection
188
244
 
189
245
  async def publish_message(self, routing_key: str, message_body: bytes, exchange_name: str = "", **kwargs):
190
- """发布消息(依赖原生自动重连/恢复)"""
246
+ """发布消息"""
191
247
  channel, _ = await self.acquire_channel()
192
248
  try:
193
249
  exchange = channel.default_exchange if not exchange_name else await channel.get_exchange(exchange_name)
194
250
  message = Message(body=message_body, **kwargs)
195
251
  await exchange.publish(message, routing_key=routing_key)
196
- logger.debug(
197
- f"消息发布成功 - 交换机: {exchange.name}, 路由键: {routing_key}"
198
- )
252
+ logger.debug(f"📤 [PUBLISH] 消息发布成功 - RK: {routing_key}")
199
253
  except Exception as e:
200
- logger.error(f"发布消息失败: {str(e)}", exc_info=True)
201
- raise # 原生会自动重连,无需手动处理
254
+ logger.error(f" [PUBLISH_FAIL] 发布失败: {str(e)}")
255
+ raise
202
256
 
203
257
  async def consume_queue(self, queue_name: str, callback: Callable[[AbstractMessage], asyncio.Future], auto_ack: bool = False, **kwargs):
204
- """消费队列(独立通道,带原生自动恢复)"""
258
+ """消费队列"""
259
+ if not self._initialized:
260
+ await self.init_pools()
261
+
205
262
  async with self._lock:
206
- # 原子化状态校验
207
263
  if self._is_shutdown:
208
- raise RuntimeError("客户端已关闭,无法启动消费")
209
- if not self._initialized:
210
- raise RuntimeError("客户端未初始化,请先调用init_pools()")
264
+ raise RuntimeError("客户端已关闭")
211
265
  if queue_name in self._consumer_channels:
212
- logger.warning(f"队列 {queue_name} 已在消费中,无需重复启动")
266
+ logger.warning(f"⚠️ [CONSUMER_EXISTS] 队列 {queue_name} 已在消费中")
213
267
  return
268
+ if not self._connection or self._connection.is_closed:
269
+ raise RuntimeError("连接不可用,无法启动消费")
214
270
 
215
- # 先声明队列(确保队列存在)
216
271
  await self.declare_queue(queue_name, **kwargs)
217
272
 
218
- # 创建独立的消费者通道(不使用主单通道,避免消费阻塞发布)
219
- async with self._lock:
220
- if self._is_shutdown: # 二次校验:防止创建通道前客户端被关闭
221
- raise RuntimeError("客户端已关闭,无法创建消费者通道")
222
- if not self._connection or self._connection.is_closed:
223
- raise RuntimeError("无有效连接,无法创建消费者通道")
224
- channel = await self._connection.channel()
225
- await channel.set_qos(prefetch_count=self.prefetch_count)
273
+ try:
274
+ # 获取原始连接对象创建新通道
275
+ conn = self._connection
276
+ consumer_channel = await conn.channel()
277
+ await consumer_channel.set_qos(prefetch_count=self.prefetch_count)
278
+ logger.info(
279
+ f"✅ [CONSUMER_CHANNEL_OK] 消费者通道创建: {id(consumer_channel)}")
226
280
 
227
- # 注册消费者通道
228
- self._consumer_channels[queue_name] = (
229
- channel, callback, auto_ack, kwargs)
281
+ async with self._lock:
282
+ if self._is_shutdown:
283
+ await consumer_channel.close()
284
+ return
285
+ self._consumer_channels[queue_name] = consumer_channel
230
286
 
231
- async def consume_callback_wrapper(message: AbstractMessage):
232
- """消费回调包装(处理通道失效,依赖原生恢复)"""
233
- try:
234
- async with self._lock:
235
- # 原子化校验状态:客户端是否关闭 + 通道是否有效 + 连接是否有效
236
- if self._is_shutdown:
237
- logger.warning(f"客户端已关闭,拒绝处理消息(队列: {queue_name})")
238
- if not auto_ack:
239
- await message.nack(requeue=True)
240
- return
241
- channel_valid = not channel.is_closed
242
- conn_valid = self._connection and not self._connection.is_closed
243
-
244
- if not channel_valid or not conn_valid:
245
- logger.warning(f"消费者通道 {queue_name} 失效(等待原生自动恢复)")
287
+ async def consume_callback_wrapper(message: AbstractMessage):
288
+ try:
289
+ await callback(message)
290
+ if not auto_ack:
291
+ await message.ack()
292
+ except Exception as e:
293
+ logger.error(
294
+ f"❌ [CALLBACK_ERR] 消费回调异常 {queue_name}: {str(e)}")
246
295
  if not auto_ack:
247
296
  await message.nack(requeue=True)
248
- return
249
-
250
- # 执行业务回调
251
- await callback(message)
252
- if not auto_ack:
253
- await message.ack()
254
- except ChannelClosed as e:
255
- logger.error(f"消费者通道 {queue_name} 关闭: {str(e)}", exc_info=True)
256
- if not auto_ack:
257
- await message.nack(requeue=True)
258
- except aiormq.exceptions.ChannelInvalidStateError as e:
259
- logger.error(
260
- f"消费者通道 {queue_name} 状态异常: {str(e)}", exc_info=True)
261
- if not auto_ack:
262
- await message.nack(requeue=True)
263
- except Exception as e:
264
- logger.error(
265
- f"消费消息失败(队列: {queue_name}): {str(e)}", exc_info=True)
266
- if not auto_ack:
267
- await message.nack(requeue=True)
268
297
 
269
- logger.info(f"开始消费队列: {queue_name}(通道带原生自动恢复)")
270
-
271
- try:
272
- await channel.basic_consume(
273
- queue_name,
274
- consumer_callback=consume_callback_wrapper,
275
- auto_ack=auto_ack,
276
- **kwargs
298
+ await consumer_channel.basic_consume(
299
+ queue_name, consumer_callback=consume_callback_wrapper, auto_ack=auto_ack, **kwargs
277
300
  )
301
+ logger.info(f"🎧 [CONSUME_START] 开始消费队列: {queue_name}")
302
+
278
303
  except Exception as e:
279
- logger.error(f"启动消费失败(队列: {queue_name}): {str(e)}", exc_info=True)
280
- # 清理异常资源
281
- try:
282
- async with self._lock:
283
- if not channel.is_closed:
284
- await channel.close()
285
- # 移除无效的消费者通道注册
286
- if queue_name in self._consumer_channels:
287
- del self._consumer_channels[queue_name]
288
- except Exception as close_e:
289
- logger.warning(f"关闭消费者通道失败: {str(close_e)}")
304
+ logger.error(f"💥 [CONSUME_ERR] 启动消费失败 {queue_name}: {str(e)}")
305
+ async with self._lock:
306
+ if queue_name in self._consumer_channels:
307
+ del self._consumer_channels[queue_name]
290
308
  raise
291
309
 
292
310
  async def close(self):
293
- """关闭客户端(释放所有资源,原子化状态管理)"""
311
+ """
312
+ 资源销毁入口
313
+ """
294
314
  async with self._lock:
295
315
  if self._is_shutdown:
296
- logger.warning("客户端已关闭,无需重复操作")
297
316
  return
298
- # 先标记为关闭,阻止后续所有操作(原子化修改)
299
317
  self._is_shutdown = True
300
318
  self._initialized = False
319
+ # 记录即将关闭的连接ID
320
+ conn_to_close_id = id(
321
+ self._connection) if self._connection else None
301
322
 
302
- logger.info("开始关闭RabbitMQ单通道客户端(释放所有资源)...")
323
+ logger.info(f"🛑 [CLOSE_START] 开始关闭客户端... (准备关闭连接: {conn_to_close_id})")
303
324
 
304
- # 1. 关闭所有消费者通道
305
- consumer_channels = []
325
+ # 1. 关闭消费者通道
326
+ channels_to_close = []
306
327
  async with self._lock:
307
- consumer_channels = list(self._consumer_channels.values())
328
+ channels_to_close = list(self._consumer_channels.values())
308
329
  self._consumer_channels.clear()
309
- for channel, _, _, _ in consumer_channels:
330
+
331
+ for ch in channels_to_close:
310
332
  try:
311
- if not channel.is_closed:
312
- await channel.close()
333
+ if not ch.is_closed:
334
+ await ch.close()
335
+ logger.debug(f"✅ [CLOSE_CHANNEL] 消费者通道已关闭")
313
336
  except Exception as e:
314
- logger.warning(f"关闭消费者通道失败: {str(e)}")
337
+ logger.warning(f"❌ [CLOSE_CHANNEL_ERR] 关闭消费者通道失败: {str(e)}")
315
338
 
316
- # 2. 关闭主单通道
339
+ # 2. 关闭主通道
317
340
  if self._channel:
318
341
  try:
319
- async with self._lock:
320
- if not self._channel.is_closed:
321
- await self._channel.close()
322
- except Exception as e:
323
- logger.warning(f"关闭主通道失败: {str(e)}")
342
+ if not self._channel.is_closed:
343
+ await self._channel.close()
344
+ logger.info(f"✅ [CLOSE_CHANNEL] 主通道已关闭")
345
+ except Exception:
346
+ pass
324
347
  self._channel = None
325
348
 
326
- # 3. 关闭连接(终止原生自动重连)
349
+ # 3. 关闭连接
350
+ # 确保在 finally 或显式 close 中调用 connection.close()
327
351
  if self._connection:
328
352
  try:
329
- async with self._lock:
330
- if not self._connection.is_closed:
331
- await self._connection.close()
332
- logger.info(
333
- f"已关闭连接: {self._current_host}:{self.port}(终止原生自动重连)")
353
+ # 打印关闭操作
354
+ logger.info(f"🔌 [CLOSE_CONN] 正在关闭连接: {id(self._connection)}")
355
+ await self._connection.close()
356
+ logger.info(f"✅ [CLOSE_OK] 连接已成功关闭: {id(self._connection)}")
334
357
  except Exception as e:
335
- logger.warning(f"关闭连接失败: {str(e)}")
358
+ logger.warning(f"❌ [CLOSE_ERR] 关闭连接失败: {str(e)}")
336
359
  self._connection = None
337
360
 
338
- logger.info("RabbitMQ单通道客户端已完全关闭")
361
+ logger.info("🏁 [CLOSE_DONE] RabbitMQ客户端已完全关闭")
362
+
363
+ # --- 辅助方法省略 (declare_queue 等) ---
364
+ async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
365
+ channel, _ = await self.acquire_channel()
366
+ return await channel.declare_queue(queue_name, **kwargs)
367
+
368
+ async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
369
+ channel, _ = await self.acquire_channel()
370
+ return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)
@@ -1,11 +1,13 @@
1
1
  import asyncio
2
2
  import json
3
+ import time
3
4
  from typing import (
4
5
  Callable, Coroutine, Dict, List, Optional, Type, Union, Any
5
6
  )
6
7
  from pydantic import BaseModel
7
8
  from aio_pika.abc import AbstractIncomingMessage, ConsumerTag
8
9
 
10
+ from sycommon.config.Config import Config
9
11
  from sycommon.models.mqmsg_model import MQMsgModel
10
12
  from sycommon.models.mqlistener_config import RabbitMQListenerConfig
11
13
  from sycommon.models.mqsend_config import RabbitMQSendConfig
@@ -238,14 +240,16 @@ class RabbitMQService:
238
240
 
239
241
  app_name = kwargs.get('app_name', cls._config.get(
240
242
  "APP_NAME", "")) if cls._config else ""
241
- is_sender = not cls._has_listeners
243
+ # 纯发送器场景(没有监听器),强制不创建队列
244
+ is_sender_only = not cls._has_listeners and cls._has_senders
242
245
 
243
- # 根据组件类型决定是否允许创建队列
244
- create_if_not_exists = cls._has_listeners # 只有监听器允许创建队列
246
+ # 发送器场景下强制禁用队列创建
247
+ create_if_not_exists = False if is_sender_only else (
248
+ cls._has_listeners)
245
249
 
246
- # 为监听器队列名称拼接应用名
247
250
  processed_queue_name = queue_name
248
- if create_if_not_exists and not is_sender and processed_queue_name and app_name:
251
+ # 只有非纯发送器场景才处理队列名称拼接
252
+ if not is_sender_only and create_if_not_exists and processed_queue_name and app_name:
249
253
  if not processed_queue_name.endswith(f".{app_name}"):
250
254
  processed_queue_name = f"{processed_queue_name}.{app_name}"
251
255
  logger.info(f"监听器队列名称自动拼接app-name: {processed_queue_name}")
@@ -253,8 +257,9 @@ class RabbitMQService:
253
257
  logger.info(f"监听器队列已包含app-name: {processed_queue_name}")
254
258
 
255
259
  logger.info(
256
- f"创建客户端 - 队列: {processed_queue_name}, 发送器: {is_sender}, "
257
- f"允许创建: {create_if_not_exists}"
260
+ f"创建客户端 - 队列: {processed_queue_name if not is_sender_only else 'N/A'}, "
261
+ f"纯发送器模式: {is_sender_only}, "
262
+ f"允许创建队列: {create_if_not_exists}"
258
263
  )
259
264
 
260
265
  # 创建客户端实例(适配精简版RabbitMQClient参数)
@@ -263,9 +268,12 @@ class RabbitMQService:
263
268
  exchange_name=cls._config.get(
264
269
  'exchange_name', "system.topic.exchange"),
265
270
  exchange_type=kwargs.get('exchange_type', "topic"),
266
- queue_name=processed_queue_name,
271
+ # 纯发送器场景下队列名称设为None,避免队列相关操作
272
+ queue_name=None if is_sender_only else processed_queue_name,
267
273
  routing_key=kwargs.get(
268
- 'routing_key', f"{processed_queue_name.split('.')[0]}.#" if processed_queue_name else "#"),
274
+ 'routing_key',
275
+ f"{queue_name.split('.')[0]}.#" if queue_name and not is_sender_only else "#"
276
+ ),
269
277
  durable=kwargs.get('durable', True),
270
278
  auto_delete=kwargs.get('auto_delete', False),
271
279
  auto_parse_json=kwargs.get('auto_parse_json', True),
@@ -308,33 +316,32 @@ class RabbitMQService:
308
316
  # 如果客户端已存在且连接有效,直接返回
309
317
  if client_name in cls._clients:
310
318
  client = cls._clients[client_name]
311
- is_sender = not cls._has_listeners or (
312
- not kwargs.get('create_if_not_exists', True))
319
+ is_sender_only = not cls._has_listeners and cls._has_senders
313
320
 
314
321
  if await client.is_connected:
315
- # 如果是监听器但队列未初始化,重新连接
316
- if not is_sender and not client._queue:
322
+ # 只有非纯发送器场景才检查队列初始化状态
323
+ if not is_sender_only and not client._queue and cls._has_listeners:
317
324
  logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
318
325
  client.create_if_not_exists = True
319
326
  await client.connect()
320
327
  return client
321
328
  else:
322
329
  logger.info(f"客户端 '{client_name}' 连接已断开,重新连接")
323
- if not is_sender:
324
- client.create_if_not_exists = True
330
+ if not is_sender_only:
331
+ client.create_if_not_exists = cls._has_listeners
325
332
  await client.connect()
326
333
  return client
327
334
 
328
335
  # 创建新客户端
329
336
  initial_queue_name = kwargs.pop('queue_name', '')
330
- is_sender = not cls._has_listeners or (
331
- not kwargs.get('create_if_not_exists', True))
337
+ is_sender_only = not cls._has_listeners and cls._has_senders
332
338
 
333
- # 发送器特殊处理
334
- if is_sender:
339
+ # 发送器特殊处理 - 纯发送器场景强制不创建队列
340
+ if is_sender_only:
335
341
  kwargs['create_if_not_exists'] = False
342
+ # 纯发送器场景下不传递队列名称,避免队列创建
336
343
  client = await cls._create_client(
337
- initial_queue_name,
344
+ "", # 空队列名
338
345
  app_name=cls._config.get("APP_NAME", ""),
339
346
  **kwargs
340
347
  )
@@ -351,8 +358,8 @@ class RabbitMQService:
351
358
  **kwargs
352
359
  )
353
360
 
354
- # 验证队列是否创建成功
355
- if not client._queue:
361
+ # 只有非纯发送器场景才验证队列创建
362
+ if not is_sender_only and not client._queue:
356
363
  logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
357
364
  client.create_if_not_exists = True
358
365
  await client.connect()
@@ -371,7 +378,7 @@ class RabbitMQService:
371
378
 
372
379
  cls._has_senders = True
373
380
  cls._has_listeners = has_listeners
374
- logger.info(f"开始设置 {len(senders)} 个消息发送器")
381
+ logger.info(f"开始设置 {len(senders)} 个消息发送器,纯发送器模式: {not has_listeners}")
375
382
 
376
383
  for idx, sender_config in enumerate(senders):
377
384
  try:
@@ -394,10 +401,12 @@ class RabbitMQService:
394
401
  logger.info(f"发送客户端 '{normalized_name}' 已存在,跳过")
395
402
  continue
396
403
 
397
- # 获取或创建客户端
404
+ # 获取或创建客户端 - 强制禁用队列创建
398
405
  if normalized_name in cls._clients:
399
406
  client = cls._clients[normalized_name]
400
407
  if not await client.is_connected:
408
+ # 纯发送器场景下连接时也不创建队列
409
+ client.create_if_not_exists = False
401
410
  await client.connect()
402
411
  else:
403
412
  client = await cls.get_client(
@@ -407,7 +416,7 @@ class RabbitMQService:
407
416
  auto_delete=sender_config.auto_delete,
408
417
  auto_parse_json=sender_config.auto_parse_json,
409
418
  queue_name=queue_name,
410
- create_if_not_exists=False,
419
+ create_if_not_exists=False, # 强制不创建队列
411
420
  prefetch_count=prefetch_count,
412
421
  **kwargs
413
422
  )
@@ -419,13 +428,14 @@ class RabbitMQService:
419
428
 
420
429
  if normalized_name not in cls._sender_client_names:
421
430
  cls._sender_client_names.append(normalized_name)
422
- logger.info(f"发送客户端 '{normalized_name}' 初始化成功")
431
+ logger.info(f"发送客户端 '{normalized_name}' 初始化成功(纯发送器模式)")
423
432
 
424
433
  except Exception as e:
425
434
  logger.error(
426
435
  f"初始化发送客户端第{idx+1}项失败: {str(e)}", exc_info=True)
427
436
 
428
- logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
437
+ logger.info(
438
+ f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器,纯发送器模式: {not has_listeners}")
429
439
 
430
440
  @classmethod
431
441
  async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **kwargs) -> None:
@@ -559,8 +569,8 @@ class RabbitMQService:
559
569
  handler = cls._message_handlers.get(client_name)
560
570
 
561
571
  if not handler:
562
- logger.warning(f"未找到客户端 '{client_name}' 的处理器,使用默认处理器")
563
- handler = cls.default_message_handler
572
+ logger.warning(f"未找到客户端 '{client_name}' 的处理器")
573
+ return
564
574
 
565
575
  # 设置消息处理器
566
576
  await client.set_message_handler(handler)
@@ -671,15 +681,6 @@ class RabbitMQService:
671
681
  task.add_done_callback(task_done_callback)
672
682
  logger.info(f"消费者任务 '{client_name}' 已创建")
673
683
 
674
- @classmethod
675
- async def default_message_handler(cls, parsed_data: MQMsgModel, original_message: AbstractIncomingMessage) -> None:
676
- """默认消息处理器"""
677
- logger.info(f"\n===== 收到消息 [{original_message.routing_key}] =====")
678
- logger.info(f"关联ID: {parsed_data.correlationDataId}")
679
- logger.info(f"主题代码: {parsed_data.topicCode}")
680
- logger.info(f"消息内容: {parsed_data.msg}")
681
- logger.info("===================\n")
682
-
683
684
  @classmethod
684
685
  async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
685
686
  """获取发送客户端(适配单通道池)"""
@@ -699,6 +700,8 @@ class RabbitMQService:
699
700
  else:
700
701
  logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
701
702
  try:
703
+ # 重连时也强制不创建队列
704
+ client.create_if_not_exists = False
702
705
  await client.connect()
703
706
  if await client.is_connected:
704
707
  return client
@@ -717,6 +720,8 @@ class RabbitMQService:
717
720
  else:
718
721
  logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
719
722
  try:
723
+ # 重连时也强制不创建队列
724
+ client.create_if_not_exists = False
720
725
  await client.connect()
721
726
  if await client.is_connected:
722
727
  return client
@@ -752,6 +757,8 @@ class RabbitMQService:
752
757
 
753
758
  while retry_count < max_retry and not cls._is_shutdown:
754
759
  try:
760
+ # 重连时强制不创建队列
761
+ sender.create_if_not_exists = False
755
762
  await sender.connect()
756
763
  if await sender.is_connected:
757
764
  logger.info(
@@ -792,16 +799,20 @@ class RabbitMQService:
792
799
  )
793
800
 
794
801
  # 构建消息头
802
+ namespaceId = Config().config.get('Nacos', {}).get('namespaceId', '未知环境')
803
+ tenant_id = "T000002" if namespaceId == "prod" or namespaceId == "wsuat1" else "T000003"
795
804
  mq_header = {
796
805
  "context": SsoUser(
797
- tenant_id="T000002",
806
+ tenant_id=tenant_id,
798
807
  customer_id="SYSTEM",
799
808
  user_id="SYSTEM",
800
809
  user_name="SYSTEM",
801
- request_path="",
810
+ request_path="/",
802
811
  req_type="SYSTEM",
803
812
  trace_id=logger.get_trace_id(),
804
- ).model_dump_json()
813
+ ).model_dump_json(),
814
+ "tenant_id": logger.get_trace_id(),
815
+ "createTime": str(int(time.time() * 1000)),
805
816
  }
806
817
 
807
818
  # 发送消息(单通道场景下依赖原生异步确认)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.56b4
3
+ Version: 0.1.56b6
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -46,9 +46,9 @@ sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6
46
46
  sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
47
47
  sycommon/notice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  sycommon/notice/uvicorn_monitor.py,sha256=NRQVWs3jzZq9IY0M55s7LvOKtacsNpfCGGzH5071dGw,7181
49
- sycommon/rabbitmq/rabbitmq_client.py,sha256=GkuYILMZJnvgZs4ID46I-w_UGzzI28YUydKgkTIDhIs,20226
50
- sycommon/rabbitmq/rabbitmq_pool.py,sha256=QtUcK4HuepRqRmy5XkUQo8gDgj74fr77CX7T5rN0y4I,15640
51
- sycommon/rabbitmq/rabbitmq_service.py,sha256=wpEJynP0gzbnmMyB02sfR9nTWB4hfTTP00xQxDJu644,38119
49
+ sycommon/rabbitmq/rabbitmq_client.py,sha256=jClUm9Ao9vZ2IZg7IXIEaDbYYnV8qtmtm77vdmwpKE4,22599
50
+ sycommon/rabbitmq/rabbitmq_pool.py,sha256=5ruFVViSCKA7ToZmR2dZIkP0zT0ZcfsnkSGbrdkg0Tk,16141
51
+ sycommon/rabbitmq/rabbitmq_service.py,sha256=yMNO2N3SxmkPs7jsyqja9EeDsirlxo_zJLDTHlwYB7A,39058
52
52
  sycommon/sse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  sycommon/sse/event.py,sha256=k_rBJy23R7crtzQeetT0Q73D8o5-5p-eESGSs_BPOj0,2797
54
54
  sycommon/sse/sse.py,sha256=__CfWEcYxOxQ-HpLor4LTZ5hLWqw9-2X7CngqbVHsfw,10128
@@ -64,8 +64,8 @@ sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
64
64
  sycommon/tools/merge_headers.py,sha256=HV_i52Q-9se3SP8qh7ZGYl8bP7Fxtal4CGVkyMwEdM8,4373
65
65
  sycommon/tools/snowflake.py,sha256=lVEe5mNCOgz5OqGQpf5_nXaGnRJlI2STX2s-ppTtanA,11947
66
66
  sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
67
- sycommon_python_lib-0.1.56b4.dist-info/METADATA,sha256=c2XIy1w2EUZHGBPqJQvPTb6eYH2i5jTVNmnV-cPD-hs,7226
68
- sycommon_python_lib-0.1.56b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
- sycommon_python_lib-0.1.56b4.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
70
- sycommon_python_lib-0.1.56b4.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
71
- sycommon_python_lib-0.1.56b4.dist-info/RECORD,,
67
+ sycommon_python_lib-0.1.56b6.dist-info/METADATA,sha256=LeQQG3OiyqTXd1H5AHtmaHXIut9tS3L3swY-a9FkSIs,7226
68
+ sycommon_python_lib-0.1.56b6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
69
+ sycommon_python_lib-0.1.56b6.dist-info/entry_points.txt,sha256=q_h2nbvhhmdnsOUZEIwpuoDjaNfBF9XqppDEmQn9d_A,46
70
+ sycommon_python_lib-0.1.56b6.dist-info/top_level.txt,sha256=98CJ-cyM2WIKxLz-Pf0AitWLhJyrfXvyY8slwjTXNuc,17
71
+ sycommon_python_lib-0.1.56b6.dist-info/RECORD,,