codosdk 1.0.53__tar.gz → 1.0.54__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {codosdk-1.0.53 → codosdk-1.0.54}/PKG-INFO +1 -1
  2. {codosdk-1.0.53 → codosdk-1.0.54}/codosdk.egg-info/PKG-INFO +1 -1
  3. {codosdk-1.0.53 → codosdk-1.0.54}/setup.py +1 -1
  4. codosdk-1.0.54/websdk2/mqhelper.py +144 -0
  5. codosdk-1.0.53/websdk2/mqhelper.py +0 -625
  6. {codosdk-1.0.53 → codosdk-1.0.54}/LICENSE +0 -0
  7. {codosdk-1.0.53 → codosdk-1.0.54}/README.md +0 -0
  8. {codosdk-1.0.53 → codosdk-1.0.54}/codosdk.egg-info/SOURCES.txt +0 -0
  9. {codosdk-1.0.53 → codosdk-1.0.54}/codosdk.egg-info/dependency_links.txt +0 -0
  10. {codosdk-1.0.53 → codosdk-1.0.54}/codosdk.egg-info/requires.txt +0 -0
  11. {codosdk-1.0.53 → codosdk-1.0.54}/codosdk.egg-info/top_level.txt +0 -0
  12. {codosdk-1.0.53 → codosdk-1.0.54}/opssdk/__init__.py +0 -0
  13. {codosdk-1.0.53 → codosdk-1.0.54}/opssdk/utils/__init__.py +0 -0
  14. {codosdk-1.0.53 → codosdk-1.0.54}/pyproject.toml +0 -0
  15. {codosdk-1.0.53 → codosdk-1.0.54}/setup.cfg +0 -0
  16. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/__init__.py +0 -0
  17. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/api_set.py +0 -0
  18. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/__init__.py +0 -0
  19. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/admin_apis.py +0 -0
  20. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/agent_apis.py +0 -0
  21. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/cmdb_apis.py +0 -0
  22. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/kerrigan_apis.py +0 -0
  23. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/mgv4_apis.py +0 -0
  24. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/notice_apis.py +0 -0
  25. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/apis/task_apis.py +0 -0
  26. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/application.py +0 -0
  27. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/base_handler.py +0 -0
  28. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cache.py +0 -0
  29. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cache_context.py +0 -0
  30. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/client.py +0 -0
  31. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cloud/__init__.py +0 -0
  32. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cloud/qcloud_api.py +0 -0
  33. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cloud/ucloud_api.py +0 -0
  34. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/cloud_utils.py +0 -0
  35. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/configs.py +0 -0
  36. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/consts.py +0 -0
  37. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/crud_utils.py +0 -0
  38. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/db_context.py +0 -0
  39. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/error.py +0 -0
  40. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/fetch_coroutine.py +0 -0
  41. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/jwt_token.py +0 -0
  42. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/ldap.py +0 -0
  43. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/logger.py +0 -0
  44. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/model_utils.py +0 -0
  45. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/program.py +0 -0
  46. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/salt_api.py +0 -0
  47. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/sqlalchemy_pagination.py +0 -0
  48. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/tools.py +0 -0
  49. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/utils/__init__.py +0 -0
  50. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/utils/cc_crypto.py +0 -0
  51. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/utils/date_format.py +0 -0
  52. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/utils/pydantic_utils.py +0 -0
  53. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/utils/pydantic_utils_v2.py +0 -0
  54. {codosdk-1.0.53 → codosdk-1.0.54}/websdk2/web_logs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codosdk
3
- Version: 1.0.53
3
+ Version: 1.0.54
4
4
  Summary: CODO项目的Python SDK
5
5
  Home-page: https://github.com/ss1917/codo_sdk/
6
6
  Author: shenshuo
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codosdk
3
- Version: 1.0.53
3
+ Version: 1.0.54
4
4
  Summary: CODO项目的Python SDK
5
5
  Home-page: https://github.com/ss1917/codo_sdk/
6
6
  Author: shenshuo
@@ -9,7 +9,7 @@ desc : CODO SDK
9
9
  import sys
10
10
  from setuptools import setup, find_packages
11
11
 
12
- VERSION = '1.0.53'
12
+ VERSION = '1.0.54'
13
13
 
14
14
  if sys.version_info < (2, 7) or (3, 0) <= sys.version_info < (3, 6):
15
15
  print('This program requires at least Python 2.7 or 3.6 to run.')
@@ -0,0 +1,144 @@
1
+ # !/usr/bin/env python
2
+ # -*-coding:utf-8-*-
3
+ """
4
+ Author : ming
5
+ date : 2017/3/3 下午9:31
6
+ role : rabbitMQ 操作类
7
+ """
8
+
9
+ import logging
10
+ import traceback
11
+ import pika
12
+ from .consts import const
13
+ from .configs import configs
14
+ from .error import ConfigError
15
+
16
+ # 配置pika日志级别
17
+ logger = logging.getLogger('pika')
18
+ logger.setLevel(logging.ERROR)
19
+
20
+ # 屏蔽pika诊断日志噪音
21
+ pika_diagnostic_logger = logging.getLogger('pika.diagnostic_utils')
22
+ pika_diagnostic_logger.setLevel(logging.CRITICAL)
23
+
24
+ # 屏蔽pika适配器日志噪音
25
+ pika_adapter_logger = logging.getLogger('pika.adapters')
26
+ pika_adapter_logger.setLevel(logging.CRITICAL)
27
+
28
+
29
+ class MessageQueueBase:
30
+ def __init__(self, exchange, exchange_type, routing_key="", routing_keys=None,
31
+ queue_name="", no_ack=False, mq_key=const.DEFAULT_MQ_KEY):
32
+
33
+ mq_config = configs[const.MQ_CONFIG_ITEM].get(mq_key)
34
+ if not mq_config:
35
+ raise ConfigError(f"MQ config {mq_key} not found")
36
+
37
+ required_keys = [const.MQ_ADDR, const.MQ_PORT, const.MQ_VHOST, const.MQ_USER, const.MQ_PWD]
38
+ for k in required_keys:
39
+ if k not in mq_config:
40
+ raise ConfigError(k)
41
+
42
+ self.addr = mq_config[const.MQ_ADDR]
43
+ self.port = int(mq_config[const.MQ_PORT])
44
+ self.vhost = mq_config[const.MQ_VHOST]
45
+ self.user = mq_config[const.MQ_USER]
46
+ self.pwd = mq_config[const.MQ_PWD]
47
+
48
+ self.exchange = exchange
49
+ self.exchange_type = exchange_type
50
+ self.routing_key = routing_key
51
+ self.routing_keys = routing_keys or []
52
+ self.queue_name = queue_name
53
+ self.no_ack = no_ack
54
+
55
+ self.connection = None
56
+ self.channel = None
57
+
58
+ # ========== Context Manager ==========
59
+ def __enter__(self):
60
+ self.channel = self.create_channel()
61
+ return self
62
+
63
+ def __exit__(self, exc_type, exc_val, exc_tb):
64
+ self.close()
65
+
66
+ # ========== Connection / Channel ==========
67
+ def create_channel(self):
68
+ credentials = pika.PlainCredentials(self.user, self.pwd)
69
+ self.connection = pika.BlockingConnection(
70
+ pika.ConnectionParameters(self.addr, self.port, self.vhost, credentials=credentials)
71
+ )
72
+ self.channel = self.connection.channel()
73
+ return self.channel
74
+
75
+ def close(self):
76
+ if self.connection and not self.connection.is_closed:
77
+ self.connection.close()
78
+
79
+ def new_channel(self):
80
+ self.channel = self.create_channel()
81
+ return self
82
+
83
+ # ========== Consume ==========
84
+ def start_consuming(self, exchange_durable=False):
85
+ ch = self.create_channel()
86
+ self._declare_exchange(ch, exchange_durable)
87
+ queue_name = self._declare_and_bind_queue(ch)
88
+
89
+ ch.basic_qos(prefetch_count=1)
90
+ ch.basic_consume(queue=queue_name, on_message_callback=self.call_back, auto_ack=self.no_ack)
91
+ logger.info(f"[*] Queue {queue_name} started.")
92
+ ch.start_consuming()
93
+
94
+ def call_back(self, ch, method, properties, body):
95
+ try:
96
+ logger.info("Received message")
97
+ self.on_message(body)
98
+ if not self.no_ack:
99
+ ch.basic_ack(delivery_tag=method.delivery_tag)
100
+ except Exception:
101
+ logger.error(traceback.format_exc())
102
+ if not self.no_ack:
103
+ ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
104
+
105
+ def on_message(self, body):
106
+ """子类实现具体逻辑"""
107
+ raise NotImplementedError
108
+
109
+ # ========== Publish ==========
110
+ def publish_message(self, body, durable=True, exchange_durable=False):
111
+ if not self.channel:
112
+ self.create_channel()
113
+
114
+ self._declare_exchange(self.channel, exchange_durable)
115
+ queue_name = self._declare_and_bind_queue(self.channel)
116
+
117
+ props = pika.BasicProperties(delivery_mode=2) if durable else None
118
+ self.channel.basic_publish(
119
+ exchange=self.exchange,
120
+ routing_key=self.routing_key,
121
+ body=body,
122
+ properties=props,
123
+ )
124
+ logger.info(f"Publish message success: {body} -> queue {queue_name}")
125
+
126
+ # ========== Private Helpers ==========
127
+ def _declare_exchange(self, ch, exchange_durable):
128
+ ch.exchange_declare(exchange=self.exchange,
129
+ exchange_type=self.exchange_type,
130
+ durable=exchange_durable)
131
+
132
+ def _declare_and_bind_queue(self, ch):
133
+ if self.queue_name:
134
+ result = ch.queue_declare(queue=self.queue_name, durable=True)
135
+ else:
136
+ result = ch.queue_declare(queue="", exclusive=True, auto_delete=True)
137
+
138
+ queue_name = result.method.queue
139
+ if self.routing_keys:
140
+ for binding_key in self.routing_keys:
141
+ ch.queue_bind(exchange=self.exchange, queue=queue_name, routing_key=binding_key)
142
+ else:
143
+ ch.queue_bind(exchange=self.exchange, queue=queue_name, routing_key=self.routing_key)
144
+ return queue_name
@@ -1,625 +0,0 @@
1
- # !/usr/bin/env python
2
- # -*-coding:utf-8-*-
3
- """
4
- Author : ming
5
- date : 2017/3/3 下午9:31
6
- role : rabbitMQ 操作类
7
- """
8
-
9
- import logging
10
- import traceback
11
- import pika
12
- import time
13
- import threading
14
- from typing import Optional, List, Dict, Any, Callable
15
- from .consts import const
16
- from .configs import configs
17
- from .error import ConfigError
18
-
19
- # 配置pika日志级别
20
- pika_logger = logging.getLogger('pika')
21
- pika_logger.setLevel(logging.ERROR)
22
-
23
- # 屏蔽pika诊断日志噪音
24
- pika_diagnostic_logger = logging.getLogger('pika.diagnostic_utils')
25
- pika_diagnostic_logger.setLevel(logging.CRITICAL)
26
-
27
- # 屏蔽pika适配器日志噪音
28
- pika_adapter_logger = logging.getLogger('pika.adapters')
29
- pika_adapter_logger.setLevel(logging.CRITICAL)
30
-
31
-
32
- # class MessageQueueBase(object):
33
- # def __init__(self, exchange, exchange_type, routing_key='', routing_keys=None, queue_name='', no_ack=False,
34
- # mq_key=const.DEFAULT_MQ_KEY):
35
- # mq_config = configs[const.MQ_CONFIG_ITEM][mq_key]
36
- # if const.MQ_ADDR not in mq_config:
37
- # raise ConfigError(const.MQ_ADDR)
38
- # if const.MQ_PORT not in mq_config:
39
- # raise ConfigError(const.MQ_PORT)
40
- # if const.MQ_VHOST not in mq_config:
41
- # raise ConfigError(const.MQ_VHOST)
42
- # if const.MQ_USER not in mq_config:
43
- # raise ConfigError(const.MQ_USER)
44
- # if const.MQ_PWD not in mq_config:
45
- # raise ConfigError(const.MQ_PWD)
46
- # self.addr = mq_config[const.MQ_ADDR]
47
- # self.port = int(mq_config[const.MQ_PORT])
48
- # self.vhost = mq_config[const.MQ_VHOST]
49
- # self.user = mq_config[const.MQ_USER]
50
- # self.pwd = mq_config[const.MQ_PWD]
51
- # self.__exchange = exchange
52
- # self.__exchange_type = exchange_type
53
- # self.__routing_key = routing_key
54
- # self.__routing_keys = routing_keys
55
- # self.__queue_name = queue_name
56
- # self.__no_ack = no_ack
57
- # self.__channel = None
58
- # self.__connection = None
59
- #
60
- # def start_consuming(self, exchange_durable=False):
61
- # channel = self.create_channel()
62
- #
63
- # channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type, durable=exchange_durable)
64
- # if self.__queue_name:
65
- # result = channel.queue_declare(queue=self.__queue_name, durable=True)
66
- # else:
67
- # result = channel.queue_declare('', exclusive=True)
68
- # if self.__routing_keys and isinstance(self.__routing_keys, list):
69
- # for binding_key in self.__routing_keys:
70
- # channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=binding_key)
71
- # else:
72
- # channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=self.__routing_key)
73
- #
74
- # channel.basic_qos(prefetch_count=1)
75
- # channel.basic_consume(result.method.queue, self.call_back, self.__no_ack)
76
- # logging.info('[*]Queue %s started.' % (result.method.queue))
77
- #
78
- # channel.start_consuming()
79
- #
80
- # def __enter__(self):
81
- # self.__channel = self.create_channel()
82
- # return self
83
- #
84
- # def __exit__(self, exc_type, exc_val, exc_tb):
85
- # self.__connection.close()
86
- #
87
- # def close_channel(self):
88
- # self.__connection.close()
89
- #
90
- # def create_channel(self):
91
- # credentials = pika.PlainCredentials(self.user, self.pwd)
92
- # self.__connection = pika.BlockingConnection(
93
- # pika.ConnectionParameters(self.addr, self.port, self.vhost, credentials=credentials))
94
- # channel = self.__connection.channel()
95
- # return channel
96
- #
97
- # def new_channel(self):
98
- # self.__channel = self.create_channel()
99
- # return self
100
- #
101
- # def call_back(self, ch, method, properties, body):
102
- # try:
103
- # logging.info('get message')
104
- # self.on_message(body)
105
- #
106
- # if not self.__no_ack:
107
- # ch.basic_ack(delivery_tag=method.delivery_tag)
108
- # except:
109
- # logging.error(traceback.format_exc())
110
- # if not self.__no_ack:
111
- # ch.basic_nack(delivery_tag=method.delivery_tag)
112
- #
113
- # def on_message(self, body):
114
- # pass
115
- #
116
- # def publish_message(self, body, durable=True, exchange_durable=False):
117
- # self.__channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type,
118
- # durable=exchange_durable)
119
- # if self.__queue_name:
120
- # result = self.__channel.queue_declare(queue=self.__queue_name)
121
- # else:
122
- # result = self.__channel.queue_declare("", exclusive=True, auto_delete=True)
123
- #
124
- # self.__channel.queue_bind(exchange=self.__exchange, queue=result.method.queue)
125
- #
126
- # if durable:
127
- # properties = pika.BasicProperties(delivery_mode=2)
128
- # self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body,
129
- # properties=properties)
130
- # else:
131
- # self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body)
132
- # logging.info('Publish message %s sucessfuled.' % body)
133
- class RabbitMQConnectionPool:
134
- """RabbitMQ连接池管理器 - 实现一个应用一个连接的最佳实践"""
135
-
136
- _connections: Dict[str, Optional[pika.BlockingConnection]] = {}
137
- _connection_lock = threading.Lock()
138
-
139
- @classmethod
140
- def get_connection(cls, mq_key: str = const.DEFAULT_MQ_KEY) -> pika.BlockingConnection:
141
- """获取或创建连接"""
142
- if mq_key not in cls._connections or not cls._is_connection_healthy(mq_key):
143
- with cls._connection_lock:
144
- # 双重检查锁定
145
- if mq_key not in cls._connections or not cls._is_connection_healthy(mq_key):
146
- cls._create_connection(mq_key)
147
-
148
- return cls._connections[mq_key]
149
-
150
- @classmethod
151
- def _create_connection(cls, mq_key: str) -> None:
152
- """创建新连接"""
153
- try:
154
- # 清理旧连接
155
- cls._cleanup_connection(mq_key)
156
-
157
- # 获取并验证配置
158
- mq_config = configs[const.MQ_CONFIG_ITEM][mq_key]
159
- cls._validate_config(mq_config)
160
-
161
- # 创建连接参数,优化超时和重试设置
162
- credentials = pika.PlainCredentials(
163
- mq_config[const.MQ_USER],
164
- mq_config[const.MQ_PWD]
165
- )
166
-
167
- connection_params = pika.ConnectionParameters(
168
- host=mq_config[const.MQ_ADDR],
169
- port=int(mq_config[const.MQ_PORT]),
170
- virtual_host=mq_config[const.MQ_VHOST],
171
- credentials=credentials,
172
- heartbeat=600, # 减少心跳间隔
173
- blocked_connection_timeout=300, # 减少阻塞超时
174
- socket_timeout=60, # 添加socket超时
175
- connection_attempts=2, # 连接重试次数
176
- retry_delay=2, # 重试延迟
177
- stack_timeout=60 # 栈超时
178
- )
179
-
180
- connection = pika.BlockingConnection(connection_params)
181
-
182
- # 验证连接状态
183
- if not connection.is_open:
184
- raise Exception("Connection failed to open properly")
185
-
186
- cls._connections[mq_key] = connection
187
- logging.info(f"Created new RabbitMQ connection for key: {mq_key}")
188
-
189
- except Exception as e:
190
- logging.error(f"Failed to create connection for {mq_key}: {e}")
191
- cls._connections[mq_key] = None
192
- raise
193
-
194
- @classmethod
195
- def _is_connection_healthy(cls, mq_key: str) -> bool:
196
- """检查连接健康状态"""
197
- connection = cls._connections.get(mq_key)
198
- try:
199
- return connection and connection.is_open
200
- except Exception:
201
- return False
202
-
203
- @classmethod
204
- def _cleanup_connection(cls, mq_key: str) -> None:
205
- """清理旧连接"""
206
- old_connection = cls._connections.get(mq_key)
207
- if old_connection:
208
- try:
209
- old_connection.close()
210
- logging.debug(f"Cleaned up old connection for {mq_key}")
211
- except Exception as e:
212
- logging.warning(f"Error cleaning up connection {mq_key}: {e}")
213
-
214
- @classmethod
215
- def _validate_config(cls, mq_config: Dict[str, Any]) -> None:
216
- """验证MQ配置"""
217
- required_keys = [const.MQ_ADDR, const.MQ_PORT, const.MQ_VHOST, const.MQ_USER, const.MQ_PWD]
218
- for key in required_keys:
219
- if key not in mq_config:
220
- raise ConfigError(key)
221
-
222
- @classmethod
223
- def close_all_connections(cls) -> None:
224
- """关闭所有连接"""
225
- with cls._connection_lock:
226
- for mq_key, connection in cls._connections.items():
227
- if connection:
228
- try:
229
- connection.close()
230
- logging.info(f"Closed connection for {mq_key}")
231
- except Exception as e:
232
- logging.warning(f"Error closing connection {mq_key}: {e}")
233
- cls._connections.clear()
234
- logging.info("All connections closed")
235
-
236
- @classmethod
237
- def get_connection_status(cls) -> Dict[str, Any]:
238
- """获取所有连接的状态"""
239
- status = {}
240
- for mq_key, connection in cls._connections.items():
241
- status[mq_key] = {
242
- 'exists': connection is not None,
243
- 'is_open': cls._is_connection_healthy(mq_key)
244
- }
245
- return status
246
-
247
-
248
- class MessageQueueBase:
249
-
250
- def __init__(self, exchange: str, exchange_type: str, routing_key: str = '',
251
- routing_keys: Optional[List[str]] = None, queue_name: str = '',
252
- no_ack: bool = False, mq_key: str = const.DEFAULT_MQ_KEY,
253
- max_retries: int = 3):
254
- """初始化MessageQueue"""
255
- self._exchange = exchange
256
- self._exchange_type = exchange_type
257
- self._routing_key = routing_key
258
- self._routing_keys = routing_keys or []
259
- self._queue_name = queue_name
260
- self._no_ack = no_ack
261
- self._mq_key = mq_key
262
- self._max_retries = max_retries
263
-
264
- self.logger = logging.getLogger(f"{self.__class__.__name__}_{exchange}")
265
- self._lock = threading.Lock()
266
-
267
- # 如果queue_name为空,记录告警(但不阻止实例化,因为发布者不需要队列名)
268
- if not queue_name:
269
- self.logger.warning(
270
- f"[WARNING] MessageQueue created without queue_name. "
271
- f"Exchange: {exchange}, Type: {exchange_type}, RoutingKey: {routing_key}. "
272
- f"This is only acceptable for publishers. If you plan to consume messages, "
273
- f"you must provide a queue_name to avoid anonymous queues."
274
- )
275
-
276
- def _get_channel(self) -> Any:
277
- """获取新的通道 - 使用更安全的方式避免pika并发Bug"""
278
- max_attempts = 2 # 减少重试次数避免过度重试
279
-
280
- for attempt in range(max_attempts):
281
- try:
282
- # 每次都创建新连接,避免连接复用导致的并发问题
283
- if attempt > 0:
284
- # 强制清理并等待
285
- with RabbitMQConnectionPool._connection_lock:
286
- try:
287
- if self._mq_key in RabbitMQConnectionPool._connections:
288
- RabbitMQConnectionPool._cleanup_connection(self._mq_key)
289
- RabbitMQConnectionPool._connections[self._mq_key] = None
290
- except:
291
- pass
292
-
293
- # 等待稍长时间让连接完全清理
294
- time.sleep(0.5)
295
-
296
- connection = RabbitMQConnectionPool.get_connection(self._mq_key)
297
- if not connection:
298
- raise Exception("Failed to get connection")
299
-
300
- # 简化状态检查,避免触发pika内部Bug
301
- if hasattr(connection, 'is_open') and not connection.is_open:
302
- raise Exception("Connection is not open")
303
-
304
- # 直接创建channel,不做额外的心跳检查
305
- channel = connection.channel()
306
- if not channel:
307
- raise Exception("Failed to create channel")
308
-
309
- if hasattr(channel, 'is_open') and not channel.is_open:
310
- raise Exception("Channel is not open")
311
-
312
- return channel
313
-
314
- except Exception as e:
315
- self.logger.warning(f"Failed to get channel (attempt {attempt + 1}/{max_attempts}): {e}")
316
- if attempt == max_attempts - 1:
317
- # 最后一次失败,创建一个临时连接
318
- try:
319
- return self._create_emergency_channel()
320
- except Exception as emergency_e:
321
- raise Exception(f"All channel attempts failed. Last error: {e}, Emergency error: {emergency_e}")
322
-
323
- raise Exception("Unexpected error in channel creation")
324
-
325
- def _create_emergency_channel(self) -> Any:
326
- """创建紧急通道 - 直接连接,不使用连接池"""
327
- try:
328
- mq_config = configs[const.MQ_CONFIG_ITEM][self._mq_key]
329
-
330
- credentials = pika.PlainCredentials(
331
- mq_config[const.MQ_USER],
332
- mq_config[const.MQ_PWD]
333
- )
334
-
335
- # 使用最简单的连接参数
336
- connection_params = pika.ConnectionParameters(
337
- host=mq_config[const.MQ_ADDR],
338
- port=int(mq_config[const.MQ_PORT]),
339
- virtual_host=mq_config[const.MQ_VHOST],
340
- credentials=credentials,
341
- heartbeat=0, # 禁用心跳
342
- socket_timeout=10
343
- )
344
-
345
- connection = pika.BlockingConnection(connection_params)
346
- channel = connection.channel()
347
-
348
- self.logger.info("Created emergency channel")
349
- return channel
350
-
351
- except Exception as e:
352
- self.logger.error(f"Failed to create emergency channel: {e}")
353
- raise
354
-
355
- def _with_retry(self, operation: Callable, *args, **kwargs) -> Any:
356
- """带重试机制执行操作 - 优化版本"""
357
- last_exception = None
358
-
359
- for attempt in range(self._max_retries + 1):
360
- try:
361
- return operation(*args, **kwargs)
362
- except Exception as e:
363
- last_exception = e
364
- if attempt == self._max_retries:
365
- self.logger.error(f"All {self._max_retries + 1} attempts failed: {e}")
366
- break
367
- else:
368
- self.logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
369
-
370
- # 只在特定错误类型时清理连接
371
- error_str = str(e).lower()
372
- if any(keyword in error_str for keyword in [
373
- 'connection', 'channel', 'socket', 'transport',
374
- 'callback', 'deque', 'abort', 'closed', 'asynctransport'
375
- ]):
376
- # 安全地清理连接
377
- with RabbitMQConnectionPool._connection_lock:
378
- try:
379
- if self._mq_key in RabbitMQConnectionPool._connections:
380
- RabbitMQConnectionPool._cleanup_connection(self._mq_key)
381
- RabbitMQConnectionPool._connections[self._mq_key] = None
382
- except Exception as cleanup_e:
383
- self.logger.debug(f"Error during connection cleanup: {cleanup_e}")
384
-
385
- # 递增延迟重试,但不要太长
386
- time.sleep(min(0.1 * (2 ** attempt), 2.0))
387
-
388
- raise last_exception
389
-
390
- def close(self):
391
- """显式关闭方法"""
392
- self.logger.debug(f"MessageQueue instance for {self._exchange} closed")
393
-
394
- # ==================== 发布消息接口 ====================
395
-
396
- def publish(self, body: Any, routing_key: Optional[str] = None,
397
- durable: bool = True, exchange_durable: bool = False,
398
- queue_durable: bool = None) -> None: # 🔥 新增参数:允许单独控制队列持久性
399
- """发布消息"""
400
-
401
- def _publish_operation():
402
- channel = self._get_channel()
403
- try:
404
- actual_routing_key = routing_key or self._routing_key
405
-
406
- # 声明交换机
407
- channel.exchange_declare(
408
- exchange=self._exchange,
409
- exchange_type=self._exchange_type,
410
- durable=exchange_durable
411
- )
412
-
413
- # 如果有队列名,声明并绑定队列
414
- if self._queue_name:
415
- # 🔥 修改:支持单独控制队列持久性,解决队列配置冲突问题
416
- actual_queue_durable = queue_durable if queue_durable is not None else durable
417
- channel.queue_declare(queue=self._queue_name, durable=actual_queue_durable)
418
- channel.queue_bind(
419
- exchange=self._exchange,
420
- queue=self._queue_name,
421
- routing_key=actual_routing_key
422
- )
423
-
424
- # 发布消息
425
- properties = pika.BasicProperties(delivery_mode=2) if durable else None
426
- channel.basic_publish(
427
- exchange=self._exchange,
428
- routing_key=actual_routing_key,
429
- body=body,
430
- properties=properties
431
- )
432
-
433
- self.logger.info(
434
- f'Published message to exchange:{self._exchange}, routing_key:{actual_routing_key}')
435
-
436
- finally:
437
- if channel and hasattr(channel, 'is_open') and channel.is_open:
438
- try:
439
- channel.close()
440
- except:
441
- pass # 忽略关闭时的错误
442
-
443
- self._with_retry(_publish_operation)
444
-
445
- # ==================== 消费消息接口 ====================
446
-
447
- def start_consuming(self, exchange_durable: bool = False, callback: Optional[Callable] = None) -> None:
448
- """开始消费消息"""
449
-
450
- def _consume_operation():
451
- channel = self._get_channel()
452
- try:
453
- # 声明交换机
454
- channel.exchange_declare(exchange=self._exchange, exchange_type=self._exchange_type,
455
- durable=exchange_durable)
456
- # 声明队列
457
- if self._queue_name:
458
- result = channel.queue_declare(queue=self._queue_name, durable=True)
459
- else:
460
- result = channel.queue_declare('', exclusive=True)
461
-
462
- # 绑定路由键
463
- if self._routing_keys:
464
- for binding_key in self._routing_keys:
465
- channel.queue_bind(
466
- exchange=self._exchange,
467
- queue=result.method.queue,
468
- routing_key=binding_key
469
- )
470
- else:
471
- channel.queue_bind(
472
- exchange=self._exchange,
473
- queue=result.method.queue,
474
- routing_key=self._routing_key
475
- )
476
-
477
- # 设置QoS
478
- channel.basic_qos(prefetch_count=1)
479
-
480
- # 设置消费回调
481
- message_callback = callback or self.call_back
482
- channel.basic_consume(result.method.queue, message_callback, self._no_ack)
483
-
484
- self.logger.info(f'[*]Queue {result.method.queue} started consuming')
485
-
486
- # 开始消费(这会阻塞)
487
- channel.start_consuming()
488
-
489
- except KeyboardInterrupt:
490
- self.logger.info("Received interrupt signal, stopping consumption")
491
- if channel and hasattr(channel, 'is_open') and channel.is_open:
492
- try:
493
- channel.stop_consuming()
494
- except:
495
- pass
496
- raise
497
- finally:
498
- if channel and hasattr(channel, 'is_open') and channel.is_open:
499
- try:
500
- channel.close()
501
- except:
502
- pass
503
-
504
- self._with_retry(_consume_operation)
505
-
506
- def call_back(self, ch: Any, method: Any, properties: Any, body: bytes) -> None:
507
- """默认消息回调处理"""
508
- try:
509
- # self.logger.info('Received message')
510
- self.on_message(body)
511
-
512
- if not self._no_ack:
513
- ch.basic_ack(delivery_tag=method.delivery_tag)
514
- except Exception as e:
515
- self.logger.error(f"Message processing failed: {e}\n{traceback.format_exc()}")
516
- if not self._no_ack:
517
- ch.basic_nack(delivery_tag=method.delivery_tag)
518
-
519
- def on_message(self, body: bytes) -> None:
520
- """消息处理方法,子类可重写"""
521
- pass
522
-
523
- # ==================== 向后兼容接口 ====================
524
-
525
- def create_channel(self) -> Any:
526
- """创建通道(向后兼容)"""
527
- return self._get_channel()
528
-
529
- def new_channel(self) -> 'MessageQueueBase':
530
- """创建新通道(向后兼容)"""
531
- RabbitMQConnectionPool.get_connection(self._mq_key)
532
- return self
533
-
534
- def check(self) -> None:
535
- """检查连接状态(向后兼容)"""
536
- try:
537
- connection = RabbitMQConnectionPool.get_connection(self._mq_key)
538
- if not connection.is_open:
539
- RabbitMQConnectionPool._connections[self._mq_key] = None
540
- RabbitMQConnectionPool.get_connection(self._mq_key)
541
- except Exception as e:
542
- self.logger.warning(f"Connection check failed: {e}")
543
- RabbitMQConnectionPool._connections[self._mq_key] = None
544
-
545
- # 统一的向后兼容发布方法
546
- def publish_message(self, body: Any, durable: bool = True, exchange_durable: bool = False,
547
- queue_durable: bool = None) -> None: # 🔥 新增参数:向后兼容方法也支持队列持久性控制
548
- """向后兼容的发布方法"""
549
- self.publish(body, durable=durable, exchange_durable=exchange_durable, queue_durable=queue_durable)
550
-
551
- def close_channel(self) -> None:
552
- """关闭连接(向后兼容)"""
553
- RabbitMQConnectionPool._cleanup_connection(self._mq_key)
554
-
555
- # ==================== 上下文管理器支持 ====================
556
-
557
- def __enter__(self) -> 'MessageQueueBase':
558
- """进入上下文管理器"""
559
- RabbitMQConnectionPool.get_connection(self._mq_key)
560
- return self
561
-
562
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
563
- """退出上下文管理器"""
564
- self.close()
565
-
566
- # ==================== 工具方法 ====================
567
-
568
- def get_connection_status(self) -> Dict[str, Any]:
569
- """获取连接状态信息"""
570
- return {
571
- 'mq_key': self._mq_key,
572
- 'exchange': self._exchange,
573
- 'exchange_type': self._exchange_type,
574
- 'routing_key': self._routing_key,
575
- 'queue_name': self._queue_name,
576
- 'connection_healthy': RabbitMQConnectionPool._is_connection_healthy(self._mq_key),
577
- 'max_retries': self._max_retries
578
- }
579
-
580
-
581
- # ==================== 便捷工厂函数 ====================
582
-
583
- def create_publisher(exchange: str, exchange_type: str, routing_key: str = '',
584
- mq_key: str = const.DEFAULT_MQ_KEY, **kwargs) -> MessageQueueBase:
585
- """创建发布者实例"""
586
- return MessageQueueBase(exchange, exchange_type, routing_key, mq_key=mq_key, **kwargs)
587
-
588
-
589
- def create_consumer(exchange: str, exchange_type: str, queue_name: str,
590
- routing_key: str = '', routing_keys: Optional[List[str]] = None,
591
- mq_key: str = const.DEFAULT_MQ_KEY, **kwargs) -> MessageQueueBase:
592
- """创建消费者实例"""
593
- return MessageQueueBase(exchange, exchange_type, routing_key, routing_keys,
594
- queue_name, mq_key=mq_key, **kwargs)
595
-
596
-
597
- def publish_once(exchange: str, exchange_type: str, message: Any,
598
- routing_key: str = '', durable: bool = True,
599
- exchange_durable: bool = False, mq_key: str = const.DEFAULT_MQ_KEY) -> None:
600
- """一次性发布消息的便捷函数"""
601
- try:
602
- with create_publisher(exchange, exchange_type, routing_key, mq_key) as publisher:
603
- publisher.publish(message, durable=durable, exchange_durable=exchange_durable)
604
- logging.info(f"Successfully published one-time message to {exchange}:{routing_key}")
605
- except Exception as e:
606
- logging.error(f"Failed to publish one-time message to {exchange}:{routing_key} - {e}")
607
- raise
608
-
609
-
610
- # ==================== 使用示例 ====================
611
-
612
- if __name__ == "__main__":
613
- # 示例1:推荐方式 - 使用上下文管理器
614
- print("=== 推荐方式:上下文管理器 ===")
615
- with create_publisher('test_exchange', 'direct', 'test.key') as publisher:
616
- publisher.publish("Hello with auto resource management!")
617
-
618
- # 示例2:一次性发布 - 最简单的方式
619
- print("=== 一次性发布 ===")
620
- publish_once('test_exchange', 'direct', "One-time message", 'test.key')
621
-
622
- # 示例3:向后兼容的使用方式
623
- print("=== 向后兼容使用 ===")
624
- with MessageQueueBase('test_exchange', 'direct', routing_key='test.key', queue_name='task_test_queue') as old_mq:
625
- old_mq.publish_message("Hello from old interface!")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes