codosdk 1.0.40__tar.gz → 1.0.41__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.
- {codosdk-1.0.40 → codosdk-1.0.41}/PKG-INFO +1 -1
- {codosdk-1.0.40 → codosdk-1.0.41}/codosdk.egg-info/PKG-INFO +1 -1
- {codosdk-1.0.40 → codosdk-1.0.41}/setup.py +1 -1
- codosdk-1.0.41/websdk2/mqhelper.py +502 -0
- codosdk-1.0.40/websdk2/mqhelper.py +0 -120
- {codosdk-1.0.40 → codosdk-1.0.41}/LICENSE +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/README.md +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/codosdk.egg-info/SOURCES.txt +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/codosdk.egg-info/dependency_links.txt +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/codosdk.egg-info/requires.txt +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/codosdk.egg-info/top_level.txt +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/opssdk/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/opssdk/utils/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/pyproject.toml +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/setup.cfg +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/api_set.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/admin_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/agent_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/cmdb_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/kerrigan_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/mgv4_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/notice_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/apis/task_apis.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/application.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/base_handler.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cache.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cache_context.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/client.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cloud/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cloud/qcloud_api.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cloud/ucloud_api.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/cloud_utils.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/configs.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/consts.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/crud_utils.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/db_context.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/error.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/fetch_coroutine.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/jwt_token.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/ldap.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/logger.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/model_utils.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/program.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/salt_api.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/sqlalchemy_pagination.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/tools.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/utils/__init__.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/utils/cc_crypto.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/utils/date_format.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/utils/pydantic_utils.py +0 -0
- {codosdk-1.0.40 → codosdk-1.0.41}/websdk2/web_logs.py +0 -0
|
@@ -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.
|
|
12
|
+
VERSION = '1.0.41'
|
|
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,502 @@
|
|
|
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 threading
|
|
13
|
+
from typing import Optional, List, Dict, Any, Callable
|
|
14
|
+
from .consts import const
|
|
15
|
+
from .configs import configs
|
|
16
|
+
from .error import ConfigError
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger('pika')
|
|
19
|
+
logger.setLevel(logging.WARNING)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# class MessageQueueBase(object):
|
|
23
|
+
# def __init__(self, exchange, exchange_type, routing_key='', routing_keys=None, queue_name='', no_ack=False,
|
|
24
|
+
# mq_key=const.DEFAULT_MQ_KEY):
|
|
25
|
+
# mq_config = configs[const.MQ_CONFIG_ITEM][mq_key]
|
|
26
|
+
# if const.MQ_ADDR not in mq_config:
|
|
27
|
+
# raise ConfigError(const.MQ_ADDR)
|
|
28
|
+
# if const.MQ_PORT not in mq_config:
|
|
29
|
+
# raise ConfigError(const.MQ_PORT)
|
|
30
|
+
# if const.MQ_VHOST not in mq_config:
|
|
31
|
+
# raise ConfigError(const.MQ_VHOST)
|
|
32
|
+
# if const.MQ_USER not in mq_config:
|
|
33
|
+
# raise ConfigError(const.MQ_USER)
|
|
34
|
+
# if const.MQ_PWD not in mq_config:
|
|
35
|
+
# raise ConfigError(const.MQ_PWD)
|
|
36
|
+
# self.addr = mq_config[const.MQ_ADDR]
|
|
37
|
+
# self.port = int(mq_config[const.MQ_PORT])
|
|
38
|
+
# self.vhost = mq_config[const.MQ_VHOST]
|
|
39
|
+
# self.user = mq_config[const.MQ_USER]
|
|
40
|
+
# self.pwd = mq_config[const.MQ_PWD]
|
|
41
|
+
# self.__exchange = exchange
|
|
42
|
+
# self.__exchange_type = exchange_type
|
|
43
|
+
# self.__routing_key = routing_key
|
|
44
|
+
# self.__routing_keys = routing_keys
|
|
45
|
+
# self.__queue_name = queue_name
|
|
46
|
+
# self.__no_ack = no_ack
|
|
47
|
+
# self.__channel = None
|
|
48
|
+
# self.__connection = None
|
|
49
|
+
#
|
|
50
|
+
# def start_consuming(self, exchange_durable=False):
|
|
51
|
+
# channel = self.create_channel()
|
|
52
|
+
#
|
|
53
|
+
# channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type, durable=exchange_durable)
|
|
54
|
+
# if self.__queue_name:
|
|
55
|
+
# result = channel.queue_declare(queue=self.__queue_name, durable=True)
|
|
56
|
+
# else:
|
|
57
|
+
# result = channel.queue_declare('', exclusive=True)
|
|
58
|
+
# if self.__routing_keys and isinstance(self.__routing_keys, list):
|
|
59
|
+
# for binding_key in self.__routing_keys:
|
|
60
|
+
# channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=binding_key)
|
|
61
|
+
# else:
|
|
62
|
+
# channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=self.__routing_key)
|
|
63
|
+
#
|
|
64
|
+
# channel.basic_qos(prefetch_count=1)
|
|
65
|
+
# channel.basic_consume(result.method.queue, self.call_back, self.__no_ack)
|
|
66
|
+
# logging.info('[*]Queue %s started.' % (result.method.queue))
|
|
67
|
+
#
|
|
68
|
+
# channel.start_consuming()
|
|
69
|
+
#
|
|
70
|
+
# def __enter__(self):
|
|
71
|
+
# self.__channel = self.create_channel()
|
|
72
|
+
# return self
|
|
73
|
+
#
|
|
74
|
+
# def __exit__(self, exc_type, exc_val, exc_tb):
|
|
75
|
+
# self.__connection.close()
|
|
76
|
+
#
|
|
77
|
+
# def close_channel(self):
|
|
78
|
+
# self.__connection.close()
|
|
79
|
+
#
|
|
80
|
+
# def create_channel(self):
|
|
81
|
+
# credentials = pika.PlainCredentials(self.user, self.pwd)
|
|
82
|
+
# self.__connection = pika.BlockingConnection(
|
|
83
|
+
# pika.ConnectionParameters(self.addr, self.port, self.vhost, credentials=credentials))
|
|
84
|
+
# channel = self.__connection.channel()
|
|
85
|
+
# return channel
|
|
86
|
+
#
|
|
87
|
+
# def new_channel(self):
|
|
88
|
+
# self.__channel = self.create_channel()
|
|
89
|
+
# return self
|
|
90
|
+
#
|
|
91
|
+
# def call_back(self, ch, method, properties, body):
|
|
92
|
+
# try:
|
|
93
|
+
# logging.info('get message')
|
|
94
|
+
# self.on_message(body)
|
|
95
|
+
#
|
|
96
|
+
# if not self.__no_ack:
|
|
97
|
+
# ch.basic_ack(delivery_tag=method.delivery_tag)
|
|
98
|
+
# except:
|
|
99
|
+
# logging.error(traceback.format_exc())
|
|
100
|
+
# if not self.__no_ack:
|
|
101
|
+
# ch.basic_nack(delivery_tag=method.delivery_tag)
|
|
102
|
+
#
|
|
103
|
+
# def on_message(self, body):
|
|
104
|
+
# pass
|
|
105
|
+
#
|
|
106
|
+
# def publish_message(self, body, durable=True, exchange_durable=False):
|
|
107
|
+
# self.__channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type,
|
|
108
|
+
# durable=exchange_durable)
|
|
109
|
+
# if self.__queue_name:
|
|
110
|
+
# result = self.__channel.queue_declare(queue=self.__queue_name)
|
|
111
|
+
# else:
|
|
112
|
+
# result = self.__channel.queue_declare("", exclusive=True, auto_delete=True)
|
|
113
|
+
#
|
|
114
|
+
# self.__channel.queue_bind(exchange=self.__exchange, queue=result.method.queue)
|
|
115
|
+
#
|
|
116
|
+
# if durable:
|
|
117
|
+
# properties = pika.BasicProperties(delivery_mode=2)
|
|
118
|
+
# self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body,
|
|
119
|
+
# properties=properties)
|
|
120
|
+
# else:
|
|
121
|
+
# self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body)
|
|
122
|
+
# logging.info('Publish message %s sucessfuled.' % body)
|
|
123
|
+
class RabbitMQConnectionPool:
|
|
124
|
+
"""RabbitMQ连接池管理器 - 实现一个应用一个连接的最佳实践"""
|
|
125
|
+
|
|
126
|
+
_connections: Dict[str, Optional[pika.BlockingConnection]] = {}
|
|
127
|
+
_connection_lock = threading.Lock()
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def get_connection(cls, mq_key: str = const.DEFAULT_MQ_KEY) -> pika.BlockingConnection:
|
|
131
|
+
"""获取或创建连接"""
|
|
132
|
+
if mq_key not in cls._connections or not cls._is_connection_healthy(mq_key):
|
|
133
|
+
with cls._connection_lock:
|
|
134
|
+
# 双重检查锁定
|
|
135
|
+
if mq_key not in cls._connections or not cls._is_connection_healthy(mq_key):
|
|
136
|
+
cls._create_connection(mq_key)
|
|
137
|
+
|
|
138
|
+
return cls._connections[mq_key]
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def _create_connection(cls, mq_key: str) -> None:
|
|
142
|
+
"""创建新连接"""
|
|
143
|
+
try:
|
|
144
|
+
# 清理旧连接
|
|
145
|
+
cls._cleanup_connection(mq_key)
|
|
146
|
+
|
|
147
|
+
# 获取并验证配置
|
|
148
|
+
mq_config = configs[const.MQ_CONFIG_ITEM][mq_key]
|
|
149
|
+
cls._validate_config(mq_config)
|
|
150
|
+
|
|
151
|
+
# 创建连接
|
|
152
|
+
credentials = pika.PlainCredentials(
|
|
153
|
+
mq_config[const.MQ_USER],
|
|
154
|
+
mq_config[const.MQ_PWD]
|
|
155
|
+
)
|
|
156
|
+
connection = pika.BlockingConnection(
|
|
157
|
+
pika.ConnectionParameters(
|
|
158
|
+
host=mq_config[const.MQ_ADDR],
|
|
159
|
+
port=int(mq_config[const.MQ_PORT]),
|
|
160
|
+
virtual_host=mq_config[const.MQ_VHOST],
|
|
161
|
+
credentials=credentials,
|
|
162
|
+
heartbeat=600,
|
|
163
|
+
blocked_connection_timeout=300
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
cls._connections[mq_key] = connection
|
|
168
|
+
logging.info(f"Created new RabbitMQ connection for key: {mq_key}")
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logging.error(f"Failed to create connection for {mq_key}: {e}")
|
|
172
|
+
cls._connections[mq_key] = None
|
|
173
|
+
raise
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def _is_connection_healthy(cls, mq_key: str) -> bool:
|
|
177
|
+
"""检查连接健康状态"""
|
|
178
|
+
connection = cls._connections.get(mq_key)
|
|
179
|
+
try:
|
|
180
|
+
return connection and connection.is_open
|
|
181
|
+
except Exception:
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def _cleanup_connection(cls, mq_key: str) -> None:
|
|
186
|
+
"""清理旧连接"""
|
|
187
|
+
old_connection = cls._connections.get(mq_key)
|
|
188
|
+
if old_connection:
|
|
189
|
+
try:
|
|
190
|
+
old_connection.close()
|
|
191
|
+
logging.debug(f"Cleaned up old connection for {mq_key}")
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logging.warning(f"Error cleaning up connection {mq_key}: {e}")
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def _validate_config(cls, mq_config: Dict[str, Any]) -> None:
|
|
197
|
+
"""验证MQ配置"""
|
|
198
|
+
required_keys = [const.MQ_ADDR, const.MQ_PORT, const.MQ_VHOST, const.MQ_USER, const.MQ_PWD]
|
|
199
|
+
for key in required_keys:
|
|
200
|
+
if key not in mq_config:
|
|
201
|
+
raise ConfigError(key)
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def close_all_connections(cls) -> None:
|
|
205
|
+
"""关闭所有连接"""
|
|
206
|
+
with cls._connection_lock:
|
|
207
|
+
for mq_key, connection in cls._connections.items():
|
|
208
|
+
if connection:
|
|
209
|
+
try:
|
|
210
|
+
connection.close()
|
|
211
|
+
logging.info(f"Closed connection for {mq_key}")
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logging.warning(f"Error closing connection {mq_key}: {e}")
|
|
214
|
+
cls._connections.clear()
|
|
215
|
+
logging.info("All connections closed")
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def get_connection_status(cls) -> Dict[str, Any]:
|
|
219
|
+
"""获取所有连接的状态"""
|
|
220
|
+
status = {}
|
|
221
|
+
for mq_key, connection in cls._connections.items():
|
|
222
|
+
status[mq_key] = {
|
|
223
|
+
'exists': connection is not None,
|
|
224
|
+
'is_open': cls._is_connection_healthy(mq_key)
|
|
225
|
+
}
|
|
226
|
+
return status
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class MessageQueueBase:
|
|
230
|
+
|
|
231
|
+
def __init__(self, exchange: str, exchange_type: str, routing_key: str = '',
|
|
232
|
+
routing_keys: Optional[List[str]] = None, queue_name: str = '',
|
|
233
|
+
no_ack: bool = False, mq_key: str = const.DEFAULT_MQ_KEY,
|
|
234
|
+
max_retries: int = 3):
|
|
235
|
+
"""初始化MessageQueue"""
|
|
236
|
+
self._exchange = exchange
|
|
237
|
+
self._exchange_type = exchange_type
|
|
238
|
+
self._routing_key = routing_key
|
|
239
|
+
self._routing_keys = routing_keys or []
|
|
240
|
+
self._queue_name = queue_name
|
|
241
|
+
self._no_ack = no_ack
|
|
242
|
+
self._mq_key = mq_key
|
|
243
|
+
self._max_retries = max_retries
|
|
244
|
+
|
|
245
|
+
self.logger = logging.getLogger(f"{self.__class__.__name__}_{exchange}")
|
|
246
|
+
self._lock = threading.Lock()
|
|
247
|
+
|
|
248
|
+
# 如果queue_name为空,记录告警(但不阻止实例化,因为发布者不需要队列名)
|
|
249
|
+
if not queue_name:
|
|
250
|
+
self.logger.warning(
|
|
251
|
+
f"[WARNING] MessageQueue created without queue_name. "
|
|
252
|
+
f"Exchange: {exchange}, Type: {exchange_type}, RoutingKey: {routing_key}. "
|
|
253
|
+
f"This is only acceptable for publishers. If you plan to consume messages, "
|
|
254
|
+
f"you must provide a queue_name to avoid anonymous queues."
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def _get_channel(self) -> Any:
|
|
258
|
+
"""获取新的通道"""
|
|
259
|
+
connection = RabbitMQConnectionPool.get_connection(self._mq_key)
|
|
260
|
+
return connection.channel()
|
|
261
|
+
|
|
262
|
+
def _with_retry(self, operation: Callable, *args, **kwargs) -> Any:
|
|
263
|
+
"""带重试机制执行操作"""
|
|
264
|
+
last_exception = None
|
|
265
|
+
|
|
266
|
+
for attempt in range(self._max_retries + 1):
|
|
267
|
+
try:
|
|
268
|
+
return operation(*args, **kwargs)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
last_exception = e
|
|
271
|
+
if attempt == self._max_retries:
|
|
272
|
+
self.logger.error(f"All {self._max_retries + 1} attempts failed: {e}")
|
|
273
|
+
break
|
|
274
|
+
else:
|
|
275
|
+
self.logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
|
|
276
|
+
# 触发连接重建
|
|
277
|
+
RabbitMQConnectionPool._connections[self._mq_key] = None
|
|
278
|
+
|
|
279
|
+
raise last_exception
|
|
280
|
+
|
|
281
|
+
def close(self):
|
|
282
|
+
"""显式关闭方法"""
|
|
283
|
+
self.logger.debug(f"MessageQueue instance for {self._exchange} closed")
|
|
284
|
+
|
|
285
|
+
# ==================== 发布消息接口 ====================
|
|
286
|
+
|
|
287
|
+
def publish(self, body: Any, routing_key: Optional[str] = None,
|
|
288
|
+
durable: bool = True, exchange_durable: bool = False) -> None:
|
|
289
|
+
"""发布消息"""
|
|
290
|
+
|
|
291
|
+
def _publish_operation():
|
|
292
|
+
channel = self._get_channel()
|
|
293
|
+
try:
|
|
294
|
+
actual_routing_key = routing_key or self._routing_key
|
|
295
|
+
|
|
296
|
+
# 声明交换机
|
|
297
|
+
channel.exchange_declare(
|
|
298
|
+
exchange=self._exchange,
|
|
299
|
+
exchange_type=self._exchange_type,
|
|
300
|
+
durable=exchange_durable
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# 如果有队列名,声明并绑定队列
|
|
304
|
+
if self._queue_name:
|
|
305
|
+
channel.queue_declare(queue=self._queue_name)
|
|
306
|
+
channel.queue_bind(
|
|
307
|
+
exchange=self._exchange,
|
|
308
|
+
queue=self._queue_name,
|
|
309
|
+
routing_key=actual_routing_key
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# 发布消息
|
|
313
|
+
properties = pika.BasicProperties(delivery_mode=2) if durable else None
|
|
314
|
+
channel.basic_publish(
|
|
315
|
+
exchange=self._exchange,
|
|
316
|
+
routing_key=actual_routing_key,
|
|
317
|
+
body=body,
|
|
318
|
+
properties=properties
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
self.logger.info(
|
|
322
|
+
f'Published message to exchange:{self._exchange}, routing_key:{actual_routing_key}')
|
|
323
|
+
|
|
324
|
+
finally:
|
|
325
|
+
if channel and channel.is_open:
|
|
326
|
+
channel.close()
|
|
327
|
+
|
|
328
|
+
self._with_retry(_publish_operation)
|
|
329
|
+
|
|
330
|
+
# ==================== 消费消息接口 ====================
|
|
331
|
+
|
|
332
|
+
def start_consuming(self, exchange_durable: bool = False, callback: Optional[Callable] = None) -> None:
|
|
333
|
+
"""开始消费消息"""
|
|
334
|
+
|
|
335
|
+
def _consume_operation():
|
|
336
|
+
channel = self._get_channel()
|
|
337
|
+
try:
|
|
338
|
+
# 声明交换机
|
|
339
|
+
channel.exchange_declare(exchange=self._exchange, exchange_type=self._exchange_type,
|
|
340
|
+
durable=exchange_durable)
|
|
341
|
+
# 声明队列
|
|
342
|
+
if self._queue_name:
|
|
343
|
+
result = channel.queue_declare(queue=self._queue_name, durable=True)
|
|
344
|
+
else:
|
|
345
|
+
result = channel.queue_declare('', exclusive=True)
|
|
346
|
+
|
|
347
|
+
# 绑定路由键
|
|
348
|
+
if self._routing_keys:
|
|
349
|
+
for binding_key in self._routing_keys:
|
|
350
|
+
channel.queue_bind(
|
|
351
|
+
exchange=self._exchange,
|
|
352
|
+
queue=result.method.queue,
|
|
353
|
+
routing_key=binding_key
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
channel.queue_bind(
|
|
357
|
+
exchange=self._exchange,
|
|
358
|
+
queue=result.method.queue,
|
|
359
|
+
routing_key=self._routing_key
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# 设置QoS
|
|
363
|
+
channel.basic_qos(prefetch_count=1)
|
|
364
|
+
|
|
365
|
+
# 设置消费回调
|
|
366
|
+
message_callback = callback or self.call_back
|
|
367
|
+
channel.basic_consume(result.method.queue, message_callback, self._no_ack)
|
|
368
|
+
|
|
369
|
+
self.logger.info(f'[*]Queue {result.method.queue} started consuming')
|
|
370
|
+
|
|
371
|
+
# 开始消费(这会阻塞)
|
|
372
|
+
channel.start_consuming()
|
|
373
|
+
|
|
374
|
+
except KeyboardInterrupt:
|
|
375
|
+
self.logger.info("Received interrupt signal, stopping consumption")
|
|
376
|
+
if channel and channel.is_open:
|
|
377
|
+
channel.stop_consuming()
|
|
378
|
+
finally:
|
|
379
|
+
if channel and channel.is_open:
|
|
380
|
+
channel.close()
|
|
381
|
+
|
|
382
|
+
self._with_retry(_consume_operation)
|
|
383
|
+
|
|
384
|
+
def call_back(self, ch: Any, method: Any, properties: Any, body: bytes) -> None:
|
|
385
|
+
"""默认消息回调处理"""
|
|
386
|
+
try:
|
|
387
|
+
self.logger.info('Received message')
|
|
388
|
+
self.on_message(body)
|
|
389
|
+
|
|
390
|
+
if not self._no_ack:
|
|
391
|
+
ch.basic_ack(delivery_tag=method.delivery_tag)
|
|
392
|
+
except Exception as e:
|
|
393
|
+
self.logger.error(f"Message processing failed: {e}\n{traceback.format_exc()}")
|
|
394
|
+
if not self._no_ack:
|
|
395
|
+
ch.basic_nack(delivery_tag=method.delivery_tag)
|
|
396
|
+
|
|
397
|
+
def on_message(self, body: bytes) -> None:
|
|
398
|
+
"""消息处理方法,子类可重写"""
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
# ==================== 向后兼容接口 ====================
|
|
402
|
+
|
|
403
|
+
def create_channel(self) -> Any:
|
|
404
|
+
"""创建通道(向后兼容)"""
|
|
405
|
+
return self._get_channel()
|
|
406
|
+
|
|
407
|
+
def new_channel(self) -> 'MessageQueueBase':
|
|
408
|
+
"""创建新通道(向后兼容)"""
|
|
409
|
+
RabbitMQConnectionPool.get_connection(self._mq_key)
|
|
410
|
+
return self
|
|
411
|
+
|
|
412
|
+
def check(self) -> None:
|
|
413
|
+
"""检查连接状态(向后兼容)"""
|
|
414
|
+
try:
|
|
415
|
+
connection = RabbitMQConnectionPool.get_connection(self._mq_key)
|
|
416
|
+
if not connection.is_open:
|
|
417
|
+
RabbitMQConnectionPool._connections[self._mq_key] = None
|
|
418
|
+
RabbitMQConnectionPool.get_connection(self._mq_key)
|
|
419
|
+
except Exception as e:
|
|
420
|
+
self.logger.warning(f"Connection check failed: {e}")
|
|
421
|
+
RabbitMQConnectionPool._connections[self._mq_key] = None
|
|
422
|
+
|
|
423
|
+
# 统一的向后兼容发布方法
|
|
424
|
+
def publish_message(self, body: Any, durable: bool = True, exchange_durable: bool = False) -> None:
|
|
425
|
+
"""向后兼容的发布方法"""
|
|
426
|
+
self.publish(body, durable=durable, exchange_durable=exchange_durable)
|
|
427
|
+
|
|
428
|
+
def close_channel(self) -> None:
|
|
429
|
+
"""关闭连接(向后兼容)"""
|
|
430
|
+
RabbitMQConnectionPool._cleanup_connection(self._mq_key)
|
|
431
|
+
|
|
432
|
+
# ==================== 上下文管理器支持 ====================
|
|
433
|
+
|
|
434
|
+
def __enter__(self) -> 'MessageQueueBase':
|
|
435
|
+
"""进入上下文管理器"""
|
|
436
|
+
RabbitMQConnectionPool.get_connection(self._mq_key)
|
|
437
|
+
return self
|
|
438
|
+
|
|
439
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
440
|
+
"""退出上下文管理器"""
|
|
441
|
+
self.close()
|
|
442
|
+
|
|
443
|
+
# ==================== 工具方法 ====================
|
|
444
|
+
|
|
445
|
+
def get_connection_status(self) -> Dict[str, Any]:
|
|
446
|
+
"""获取连接状态信息"""
|
|
447
|
+
return {
|
|
448
|
+
'mq_key': self._mq_key,
|
|
449
|
+
'exchange': self._exchange,
|
|
450
|
+
'exchange_type': self._exchange_type,
|
|
451
|
+
'routing_key': self._routing_key,
|
|
452
|
+
'queue_name': self._queue_name,
|
|
453
|
+
'connection_healthy': RabbitMQConnectionPool._is_connection_healthy(self._mq_key),
|
|
454
|
+
'max_retries': self._max_retries
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# ==================== 便捷工厂函数 ====================
|
|
459
|
+
|
|
460
|
+
def create_publisher(exchange: str, exchange_type: str, routing_key: str = '',
|
|
461
|
+
mq_key: str = const.DEFAULT_MQ_KEY, **kwargs) -> MessageQueueBase:
|
|
462
|
+
"""创建发布者实例"""
|
|
463
|
+
return MessageQueueBase(exchange, exchange_type, routing_key, mq_key=mq_key, **kwargs)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def create_consumer(exchange: str, exchange_type: str, queue_name: str,
|
|
467
|
+
routing_key: str = '', routing_keys: Optional[List[str]] = None,
|
|
468
|
+
mq_key: str = const.DEFAULT_MQ_KEY, **kwargs) -> MessageQueueBase:
|
|
469
|
+
"""创建消费者实例"""
|
|
470
|
+
return MessageQueueBase(exchange, exchange_type, routing_key, routing_keys,
|
|
471
|
+
queue_name, mq_key=mq_key, **kwargs)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def publish_once(exchange: str, exchange_type: str, message: Any,
|
|
475
|
+
routing_key: str = '', durable: bool = True,
|
|
476
|
+
exchange_durable: bool = False, mq_key: str = const.DEFAULT_MQ_KEY) -> None:
|
|
477
|
+
"""一次性发布消息的便捷函数"""
|
|
478
|
+
try:
|
|
479
|
+
with create_publisher(exchange, exchange_type, routing_key, mq_key) as publisher:
|
|
480
|
+
publisher.publish(message, durable=durable, exchange_durable=exchange_durable)
|
|
481
|
+
logging.info(f"Successfully published one-time message to {exchange}:{routing_key}")
|
|
482
|
+
except Exception as e:
|
|
483
|
+
logging.error(f"Failed to publish one-time message to {exchange}:{routing_key} - {e}")
|
|
484
|
+
raise
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ==================== 使用示例 ====================
|
|
488
|
+
|
|
489
|
+
if __name__ == "__main__":
|
|
490
|
+
# 示例1:推荐方式 - 使用上下文管理器
|
|
491
|
+
print("=== 推荐方式:上下文管理器 ===")
|
|
492
|
+
with create_publisher('test_exchange', 'direct', 'test.key') as publisher:
|
|
493
|
+
publisher.publish("Hello with auto resource management!")
|
|
494
|
+
|
|
495
|
+
# 示例2:一次性发布 - 最简单的方式
|
|
496
|
+
print("=== 一次性发布 ===")
|
|
497
|
+
publish_once('test_exchange', 'direct', "One-time message", 'test.key')
|
|
498
|
+
|
|
499
|
+
# 示例3:向后兼容的使用方式
|
|
500
|
+
print("=== 向后兼容使用 ===")
|
|
501
|
+
with MessageQueueBase('test_exchange', 'direct', routing_key='test.key') as old_mq:
|
|
502
|
+
old_mq.publish_message("Hello from old interface!")
|
|
@@ -1,120 +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
|
-
from .consts import const
|
|
13
|
-
from .configs import configs
|
|
14
|
-
from .error import ConfigError
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger('pika')
|
|
17
|
-
logger.setLevel(logging.WARNING)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class MessageQueueBase(object):
|
|
21
|
-
def __init__(self, exchange, exchange_type, routing_key='', routing_keys=None, queue_name='', no_ack=False,
|
|
22
|
-
mq_key=const.DEFAULT_MQ_KEY):
|
|
23
|
-
mq_config = configs[const.MQ_CONFIG_ITEM][mq_key]
|
|
24
|
-
if const.MQ_ADDR not in mq_config:
|
|
25
|
-
raise ConfigError(const.MQ_ADDR)
|
|
26
|
-
if const.MQ_PORT not in mq_config:
|
|
27
|
-
raise ConfigError(const.MQ_PORT)
|
|
28
|
-
if const.MQ_VHOST not in mq_config:
|
|
29
|
-
raise ConfigError(const.MQ_VHOST)
|
|
30
|
-
if const.MQ_USER not in mq_config:
|
|
31
|
-
raise ConfigError(const.MQ_USER)
|
|
32
|
-
if const.MQ_PWD not in mq_config:
|
|
33
|
-
raise ConfigError(const.MQ_PWD)
|
|
34
|
-
self.addr = mq_config[const.MQ_ADDR]
|
|
35
|
-
self.port = int(mq_config[const.MQ_PORT])
|
|
36
|
-
self.vhost = mq_config[const.MQ_VHOST]
|
|
37
|
-
self.user = mq_config[const.MQ_USER]
|
|
38
|
-
self.pwd = mq_config[const.MQ_PWD]
|
|
39
|
-
self.__exchange = exchange
|
|
40
|
-
self.__exchange_type = exchange_type
|
|
41
|
-
self.__routing_key = routing_key
|
|
42
|
-
self.__routing_keys = routing_keys
|
|
43
|
-
self.__queue_name = queue_name
|
|
44
|
-
self.__no_ack = no_ack
|
|
45
|
-
self.__channel = None
|
|
46
|
-
self.__connection = None
|
|
47
|
-
|
|
48
|
-
def start_consuming(self, exchange_durable=False):
|
|
49
|
-
channel = self.create_channel()
|
|
50
|
-
|
|
51
|
-
channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type, durable=exchange_durable)
|
|
52
|
-
if self.__queue_name:
|
|
53
|
-
result = channel.queue_declare(queue=self.__queue_name, durable=True)
|
|
54
|
-
else:
|
|
55
|
-
result = channel.queue_declare('', exclusive=True)
|
|
56
|
-
if self.__routing_keys and isinstance(self.__routing_keys, list):
|
|
57
|
-
for binding_key in self.__routing_keys:
|
|
58
|
-
channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=binding_key)
|
|
59
|
-
else:
|
|
60
|
-
channel.queue_bind(exchange=self.__exchange, queue=result.method.queue, routing_key=self.__routing_key)
|
|
61
|
-
|
|
62
|
-
channel.basic_qos(prefetch_count=1)
|
|
63
|
-
channel.basic_consume(result.method.queue, self.call_back, self.__no_ack)
|
|
64
|
-
logging.info('[*]Queue %s started.' % (result.method.queue))
|
|
65
|
-
|
|
66
|
-
channel.start_consuming()
|
|
67
|
-
|
|
68
|
-
def __enter__(self):
|
|
69
|
-
self.__channel = self.create_channel()
|
|
70
|
-
return self
|
|
71
|
-
|
|
72
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
73
|
-
self.__connection.close()
|
|
74
|
-
|
|
75
|
-
def close_channel(self):
|
|
76
|
-
self.__connection.close()
|
|
77
|
-
|
|
78
|
-
def create_channel(self):
|
|
79
|
-
credentials = pika.PlainCredentials(self.user, self.pwd)
|
|
80
|
-
self.__connection = pika.BlockingConnection(
|
|
81
|
-
pika.ConnectionParameters(self.addr, self.port, self.vhost, credentials=credentials))
|
|
82
|
-
channel = self.__connection.channel()
|
|
83
|
-
return channel
|
|
84
|
-
|
|
85
|
-
def new_channel(self):
|
|
86
|
-
self.__channel = self.create_channel()
|
|
87
|
-
return self
|
|
88
|
-
|
|
89
|
-
def call_back(self, ch, method, properties, body):
|
|
90
|
-
try:
|
|
91
|
-
logging.info('get message')
|
|
92
|
-
self.on_message(body)
|
|
93
|
-
|
|
94
|
-
if not self.__no_ack:
|
|
95
|
-
ch.basic_ack(delivery_tag=method.delivery_tag)
|
|
96
|
-
except:
|
|
97
|
-
logging.error(traceback.format_exc())
|
|
98
|
-
if not self.__no_ack:
|
|
99
|
-
ch.basic_nack(delivery_tag=method.delivery_tag)
|
|
100
|
-
|
|
101
|
-
def on_message(self, body):
|
|
102
|
-
pass
|
|
103
|
-
|
|
104
|
-
def publish_message(self, body, durable=True, exchange_durable=False):
|
|
105
|
-
self.__channel.exchange_declare(exchange=self.__exchange, exchange_type=self.__exchange_type,
|
|
106
|
-
durable=exchange_durable)
|
|
107
|
-
if self.__queue_name:
|
|
108
|
-
result = self.__channel.queue_declare(queue=self.__queue_name)
|
|
109
|
-
else:
|
|
110
|
-
result = self.__channel.queue_declare("", exclusive=True, auto_delete=True)
|
|
111
|
-
|
|
112
|
-
self.__channel.queue_bind(exchange=self.__exchange, queue=result.method.queue)
|
|
113
|
-
|
|
114
|
-
if durable:
|
|
115
|
-
properties = pika.BasicProperties(delivery_mode=2)
|
|
116
|
-
self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body,
|
|
117
|
-
properties=properties)
|
|
118
|
-
else:
|
|
119
|
-
self.__channel.basic_publish(exchange=self.__exchange, routing_key=self.__routing_key, body=body)
|
|
120
|
-
logging.info('Publish message %s sucessfuled.' % body)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|