codosdk 1.0.52__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.
- {codosdk-1.0.52 → codosdk-1.0.54}/PKG-INFO +1 -1
- {codosdk-1.0.52 → codosdk-1.0.54}/codosdk.egg-info/PKG-INFO +1 -1
- {codosdk-1.0.52 → codosdk-1.0.54}/setup.py +1 -1
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/application.py +3 -3
- codosdk-1.0.54/websdk2/mqhelper.py +144 -0
- codosdk-1.0.52/websdk2/mqhelper.py +0 -625
- {codosdk-1.0.52 → codosdk-1.0.54}/LICENSE +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/README.md +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/codosdk.egg-info/SOURCES.txt +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/codosdk.egg-info/dependency_links.txt +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/codosdk.egg-info/requires.txt +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/codosdk.egg-info/top_level.txt +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/opssdk/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/opssdk/utils/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/pyproject.toml +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/setup.cfg +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/api_set.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/admin_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/agent_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/cmdb_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/kerrigan_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/mgv4_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/notice_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/apis/task_apis.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/base_handler.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cache.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cache_context.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/client.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cloud/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cloud/qcloud_api.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cloud/ucloud_api.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/cloud_utils.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/configs.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/consts.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/crud_utils.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/db_context.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/error.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/fetch_coroutine.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/jwt_token.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/ldap.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/logger.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/model_utils.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/program.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/salt_api.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/sqlalchemy_pagination.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/tools.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/utils/__init__.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/utils/cc_crypto.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/utils/date_format.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/utils/pydantic_utils.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/websdk2/utils/pydantic_utils_v2.py +0 -0
- {codosdk-1.0.52 → codosdk-1.0.54}/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.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.')
|
|
@@ -64,11 +64,11 @@ class Application(tornadoApp):
|
|
|
64
64
|
logging.info('server address: %(addr)s:%(port)d' % dict(addr=options.addr, port=options.port))
|
|
65
65
|
logging.info('web server start sucessfuled.')
|
|
66
66
|
self.io_loop.start()
|
|
67
|
-
except KeyboardInterrupt:
|
|
67
|
+
except (KeyboardInterrupt, SystemExit):
|
|
68
68
|
self.io_loop.stop()
|
|
69
69
|
logging.info("Server shut down gracefully.")
|
|
70
|
-
except Exception as
|
|
71
|
-
logging.
|
|
70
|
+
except Exception as err:
|
|
71
|
+
logging.exception("Unhandled exception: %s", err)
|
|
72
72
|
|
|
73
73
|
@staticmethod
|
|
74
74
|
def _generate_url_metadata(urls):
|
|
@@ -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
|
|
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
|