sycommon-python-lib 0.1.46__py3-none-any.whl → 0.1.57b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sycommon/config/Config.py +29 -4
- sycommon/config/LangfuseConfig.py +15 -0
- sycommon/config/RerankerConfig.py +1 -0
- sycommon/config/SentryConfig.py +13 -0
- sycommon/database/async_base_db_service.py +36 -0
- sycommon/database/async_database_service.py +96 -0
- sycommon/llm/__init__.py +0 -0
- sycommon/llm/embedding.py +204 -0
- sycommon/llm/get_llm.py +37 -0
- sycommon/llm/llm_logger.py +126 -0
- sycommon/llm/llm_tokens.py +119 -0
- sycommon/llm/struct_token.py +192 -0
- sycommon/llm/sy_langfuse.py +103 -0
- sycommon/llm/usage_token.py +117 -0
- sycommon/logging/async_sql_logger.py +65 -0
- sycommon/logging/kafka_log.py +200 -434
- sycommon/logging/logger_levels.py +23 -0
- sycommon/middleware/context.py +2 -0
- sycommon/middleware/exception.py +10 -16
- sycommon/middleware/timeout.py +2 -1
- sycommon/middleware/traceid.py +179 -51
- sycommon/notice/__init__.py +0 -0
- sycommon/notice/uvicorn_monitor.py +200 -0
- sycommon/rabbitmq/rabbitmq_client.py +267 -290
- sycommon/rabbitmq/rabbitmq_pool.py +277 -465
- sycommon/rabbitmq/rabbitmq_service.py +23 -891
- sycommon/rabbitmq/rabbitmq_service_client_manager.py +211 -0
- sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
- sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
- sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
- sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
- sycommon/sentry/__init__.py +0 -0
- sycommon/sentry/sy_sentry.py +35 -0
- sycommon/services.py +144 -115
- sycommon/synacos/feign.py +18 -7
- sycommon/synacos/feign_client.py +26 -8
- sycommon/synacos/nacos_client_base.py +119 -0
- sycommon/synacos/nacos_config_manager.py +107 -0
- sycommon/synacos/nacos_heartbeat_manager.py +144 -0
- sycommon/synacos/nacos_service.py +65 -769
- sycommon/synacos/nacos_service_discovery.py +157 -0
- sycommon/synacos/nacos_service_registration.py +270 -0
- sycommon/tools/env.py +62 -0
- sycommon/tools/merge_headers.py +117 -0
- sycommon/tools/snowflake.py +238 -23
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/METADATA +18 -11
- sycommon_python_lib-0.1.57b1.dist-info/RECORD +89 -0
- sycommon_python_lib-0.1.46.dist-info/RECORD +0 -59
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.57b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from typing import List, Optional, Union, Dict, Any
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
8
|
+
from sycommon.models.mqsend_config import RabbitMQSendConfig
|
|
9
|
+
from sycommon.config.Config import Config
|
|
10
|
+
from sycommon.models.sso_user import SsoUser
|
|
11
|
+
from sycommon.models.mqmsg_model import MQMsgModel
|
|
12
|
+
from sycommon.rabbitmq.rabbitmq_client import RabbitMQClient
|
|
13
|
+
from sycommon.rabbitmq.rabbitmq_service_client_manager import RabbitMQClientManager
|
|
14
|
+
|
|
15
|
+
logger = SYLogger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RabbitMQProducerManager(RabbitMQClientManager):
|
|
19
|
+
"""
|
|
20
|
+
RabbitMQ生产者管理类 - 负责发送器设置、消息发送
|
|
21
|
+
"""
|
|
22
|
+
# 发送器相关状态
|
|
23
|
+
_sender_client_names: List[str] = []
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **kwargs) -> None:
|
|
27
|
+
"""设置消息发送器(适配client_type参数,确保发送器不创建队列)"""
|
|
28
|
+
if cls._is_shutdown:
|
|
29
|
+
logger.warning("服务已关闭,无法设置发送器")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
cls.set_mode_flags(has_listeners=has_listeners, has_senders=True)
|
|
33
|
+
logger.info(f"开始设置 {len(senders)} 个消息发送器,纯发送器模式: {not has_listeners}")
|
|
34
|
+
|
|
35
|
+
for idx, sender_config in enumerate(senders):
|
|
36
|
+
try:
|
|
37
|
+
if not sender_config.queue_name:
|
|
38
|
+
raise ValueError(f"发送器配置第{idx+1}项缺少queue_name")
|
|
39
|
+
|
|
40
|
+
prefetch_count = sender_config.prefetch_count
|
|
41
|
+
queue_name = sender_config.queue_name
|
|
42
|
+
app_name = cls._config.get(
|
|
43
|
+
"APP_NAME", "") if cls._config else ""
|
|
44
|
+
|
|
45
|
+
# 处理发送器客户端名称(非队列名)
|
|
46
|
+
normalized_name = queue_name
|
|
47
|
+
if app_name and normalized_name.endswith(f".{app_name}"):
|
|
48
|
+
normalized_name = normalized_name[:-len(f".{app_name}")]
|
|
49
|
+
logger.info(f"发送器客户端名称移除app-name后缀: {normalized_name}")
|
|
50
|
+
|
|
51
|
+
# 检查是否已初始化
|
|
52
|
+
if normalized_name in cls._sender_client_names:
|
|
53
|
+
logger.info(f"发送客户端 '{normalized_name}' 已存在,跳过")
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# ===== 处理已有客户端重连 =====
|
|
57
|
+
if normalized_name in cls._clients:
|
|
58
|
+
client = cls._clients[normalized_name]
|
|
59
|
+
if not await client.is_connected:
|
|
60
|
+
client.queue_name = normalized_name
|
|
61
|
+
client.create_if_not_exists = False
|
|
62
|
+
await client.connect()
|
|
63
|
+
else:
|
|
64
|
+
client = await cls.get_client(
|
|
65
|
+
client_name=normalized_name,
|
|
66
|
+
client_type="sender",
|
|
67
|
+
exchange_type=sender_config.exchange_type,
|
|
68
|
+
durable=sender_config.durable,
|
|
69
|
+
auto_delete=sender_config.auto_delete,
|
|
70
|
+
auto_parse_json=sender_config.auto_parse_json,
|
|
71
|
+
queue_name=queue_name,
|
|
72
|
+
create_if_not_exists=False,
|
|
73
|
+
prefetch_count=prefetch_count,
|
|
74
|
+
**kwargs
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 记录客户端
|
|
78
|
+
if normalized_name not in cls._clients:
|
|
79
|
+
cls._clients[normalized_name] = client
|
|
80
|
+
logger.info(f"发送客户端 '{normalized_name}' 已添加")
|
|
81
|
+
|
|
82
|
+
if normalized_name not in cls._sender_client_names:
|
|
83
|
+
cls._sender_client_names.append(normalized_name)
|
|
84
|
+
logger.info(f"发送客户端 '{normalized_name}' 初始化成功(纯发送器模式)")
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(
|
|
88
|
+
f"初始化发送客户端第{idx+1}项失败: {str(e)}", exc_info=True)
|
|
89
|
+
|
|
90
|
+
logger.info(
|
|
91
|
+
f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器,纯发送器模式: {not has_listeners}")
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
95
|
+
"""获取发送客户端"""
|
|
96
|
+
if cls._is_shutdown:
|
|
97
|
+
logger.warning("服务已关闭,无法获取发送器")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
if not queue_name:
|
|
101
|
+
logger.warning("发送器名称不能为空")
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
# 检查是否在已注册的发送器中
|
|
105
|
+
if queue_name in cls._sender_client_names and queue_name in cls._clients:
|
|
106
|
+
client = cls._clients[queue_name]
|
|
107
|
+
if await client.is_connected:
|
|
108
|
+
return client
|
|
109
|
+
else:
|
|
110
|
+
logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
|
|
111
|
+
try:
|
|
112
|
+
client.create_if_not_exists = False
|
|
113
|
+
await client.connect()
|
|
114
|
+
if await client.is_connected:
|
|
115
|
+
return client
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
# 检查是否带有app-name后缀
|
|
121
|
+
app_name = cls._config.get("APP_NAME", "") if cls._config else ""
|
|
122
|
+
if app_name:
|
|
123
|
+
suffixed_name = f"{queue_name}.{app_name}"
|
|
124
|
+
if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
|
|
125
|
+
client = cls._clients[suffixed_name]
|
|
126
|
+
if await client.is_connected:
|
|
127
|
+
return client
|
|
128
|
+
else:
|
|
129
|
+
logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
|
|
130
|
+
try:
|
|
131
|
+
client.create_if_not_exists = False
|
|
132
|
+
await client.connect()
|
|
133
|
+
if await client.is_connected:
|
|
134
|
+
return client
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
|
|
137
|
+
|
|
138
|
+
logger.info(f"未找到可用的发送器 '{queue_name}'")
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
async def send_message(
|
|
143
|
+
cls,
|
|
144
|
+
data: Union[BaseModel, str, Dict[str, Any], None],
|
|
145
|
+
queue_name: str, **kwargs
|
|
146
|
+
) -> None:
|
|
147
|
+
"""发送消息到指定队列"""
|
|
148
|
+
if cls._is_shutdown:
|
|
149
|
+
raise RuntimeError("RabbitMQService已关闭,无法发送消息")
|
|
150
|
+
|
|
151
|
+
# 获取发送客户端
|
|
152
|
+
sender = await cls.get_sender(queue_name)
|
|
153
|
+
if not sender:
|
|
154
|
+
error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
|
|
155
|
+
logger.error(error_msg)
|
|
156
|
+
raise ValueError(error_msg)
|
|
157
|
+
|
|
158
|
+
# 确保连接有效
|
|
159
|
+
if not await sender.is_connected:
|
|
160
|
+
logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
|
|
161
|
+
max_retry = 3
|
|
162
|
+
retry_count = 0
|
|
163
|
+
last_exception = None
|
|
164
|
+
|
|
165
|
+
while retry_count < max_retry and not cls._is_shutdown:
|
|
166
|
+
try:
|
|
167
|
+
sender.create_if_not_exists = False
|
|
168
|
+
await sender.connect()
|
|
169
|
+
if await sender.is_connected:
|
|
170
|
+
logger.info(
|
|
171
|
+
f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
|
|
172
|
+
break
|
|
173
|
+
except Exception as e:
|
|
174
|
+
last_exception = e
|
|
175
|
+
retry_count += 1
|
|
176
|
+
logger.warning(
|
|
177
|
+
f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
|
|
178
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
179
|
+
|
|
180
|
+
if retry_count >= max_retry and not await sender.is_connected:
|
|
181
|
+
error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
|
|
182
|
+
logger.error(f"{error_msg}: {str(last_exception)}")
|
|
183
|
+
raise Exception(error_msg) from last_exception
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# 处理消息数据
|
|
187
|
+
msg_content = ""
|
|
188
|
+
if isinstance(data, str):
|
|
189
|
+
msg_content = data
|
|
190
|
+
elif isinstance(data, BaseModel):
|
|
191
|
+
msg_content = data.model_dump_json()
|
|
192
|
+
elif isinstance(data, dict):
|
|
193
|
+
msg_content = json.dumps(data, ensure_ascii=False)
|
|
194
|
+
|
|
195
|
+
# 创建标准消息模型
|
|
196
|
+
mq_message = MQMsgModel(
|
|
197
|
+
topicCode=queue_name.split('.')[0] if queue_name else "",
|
|
198
|
+
msg=msg_content,
|
|
199
|
+
correlationDataId=kwargs.get(
|
|
200
|
+
'correlationDataId', logger.get_trace_id()),
|
|
201
|
+
groupId=kwargs.get('groupId', ''),
|
|
202
|
+
dataKey=kwargs.get('dataKey', ""),
|
|
203
|
+
manualFlag=kwargs.get('manualFlag', False),
|
|
204
|
+
traceId=logger.get_trace_id()
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# 构建消息头
|
|
208
|
+
namespaceId = Config().config.get('Nacos', {}).get('namespaceId', '')
|
|
209
|
+
tenant_id = "T000002" if namespaceId == "prod" or namespaceId == "wsuat1" else "T000003"
|
|
210
|
+
mq_header = {
|
|
211
|
+
"context": SsoUser(
|
|
212
|
+
tenant_id=tenant_id,
|
|
213
|
+
customer_id="SYSTEM",
|
|
214
|
+
user_id="SYSTEM",
|
|
215
|
+
user_name="SYSTEM",
|
|
216
|
+
request_path="/",
|
|
217
|
+
req_type="SYSTEM",
|
|
218
|
+
trace_id=logger.get_trace_id(),
|
|
219
|
+
).model_dump_json(),
|
|
220
|
+
"tenant_id": logger.get_trace_id(),
|
|
221
|
+
"createTime": str(int(time.time() * 1000)),
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# 发送消息
|
|
225
|
+
await sender.publish(
|
|
226
|
+
message_body=mq_message.model_dump_json(),
|
|
227
|
+
headers=mq_header,
|
|
228
|
+
content_type="application/json"
|
|
229
|
+
)
|
|
230
|
+
logger.info(f"消息发送成功 (队列: {queue_name})")
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"消息发送失败: {str(e)}", exc_info=True)
|
|
233
|
+
raise
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def clear_senders(cls) -> None:
|
|
237
|
+
"""清理发送器状态"""
|
|
238
|
+
cls._sender_client_names.clear()
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from sycommon.config.Config import Config
|
|
4
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
5
|
+
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
|
6
|
+
# from sentry_sdk.integrations.logging import LoggingIntegration
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sy_sentry_init():
|
|
10
|
+
config = Config().config
|
|
11
|
+
server_name = config.get('Name', '')
|
|
12
|
+
environment = config.get('Nacos', {}).get('namespaceId', '')
|
|
13
|
+
sentry_configs = config.get('SentryConfig', [])
|
|
14
|
+
target_config = next(
|
|
15
|
+
(item for item in sentry_configs if item.get('name') == server_name), None)
|
|
16
|
+
if target_config:
|
|
17
|
+
target_dsn = target_config.get('dsn')
|
|
18
|
+
target_enable = target_config.get('enable')
|
|
19
|
+
current_version = datetime.now().strftime("%Y-%m-%d %H:%M:%S-version")
|
|
20
|
+
if target_config and target_dsn and target_enable:
|
|
21
|
+
try:
|
|
22
|
+
sentry_sdk.init(
|
|
23
|
+
dsn=target_dsn,
|
|
24
|
+
traces_sample_rate=1.0,
|
|
25
|
+
server_name=server_name,
|
|
26
|
+
environment=environment,
|
|
27
|
+
release=current_version,
|
|
28
|
+
integrations=[
|
|
29
|
+
FastApiIntegration(),
|
|
30
|
+
# LoggingIntegration(level=logging.INFO,
|
|
31
|
+
# event_level=logging.ERROR)
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
SYLogger.error(f"Sentry初始化失败: {str(e)}")
|
sycommon/services.py
CHANGED
|
@@ -7,32 +7,37 @@ from fastapi import FastAPI, applications
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
8
|
from typing import Any, Callable, Dict, List, Tuple, Union, Optional, AsyncGenerator
|
|
9
9
|
from sycommon.config.Config import SingletonMeta
|
|
10
|
+
from sycommon.logging.logger_levels import setup_logger_levels
|
|
10
11
|
from sycommon.models.mqlistener_config import RabbitMQListenerConfig
|
|
11
12
|
from sycommon.models.mqsend_config import RabbitMQSendConfig
|
|
12
13
|
from sycommon.rabbitmq.rabbitmq_service import RabbitMQService
|
|
13
14
|
from sycommon.tools.docs import custom_redoc_html, custom_swagger_ui_html
|
|
15
|
+
from sycommon.sentry.sy_sentry import sy_sentry_init
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class Services(metaclass=SingletonMeta):
|
|
17
19
|
_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
18
20
|
_config: Optional[dict] = None
|
|
19
21
|
_initialized: bool = False
|
|
20
|
-
_registered_senders: List[str] = []
|
|
21
|
-
_mq_tasks: List[asyncio.Task] = []
|
|
22
22
|
_instance: Optional['Services'] = None
|
|
23
23
|
_app: Optional[FastAPI] = None
|
|
24
24
|
_user_lifespan: Optional[Callable] = None
|
|
25
25
|
_shutdown_lock: asyncio.Lock = asyncio.Lock()
|
|
26
26
|
|
|
27
27
|
def __init__(self, config: dict, app: FastAPI):
|
|
28
|
+
super().__init__()
|
|
28
29
|
if not Services._config:
|
|
29
30
|
Services._config = config
|
|
30
31
|
Services._instance = self
|
|
31
32
|
Services._app = app
|
|
33
|
+
|
|
34
|
+
# 在实例初始化时定义变量,防止类变量污染
|
|
35
|
+
self._pending_async_db_setup: List[Tuple[Callable, str]] = []
|
|
36
|
+
|
|
32
37
|
self._init_event_loop()
|
|
33
38
|
|
|
34
39
|
def _init_event_loop(self):
|
|
35
|
-
"""
|
|
40
|
+
"""初始化事件循环"""
|
|
36
41
|
if not Services._loop:
|
|
37
42
|
try:
|
|
38
43
|
Services._loop = asyncio.get_running_loop()
|
|
@@ -49,95 +54,128 @@ class Services(metaclass=SingletonMeta):
|
|
|
49
54
|
nacos_service: Optional[Callable[[dict], None]] = None,
|
|
50
55
|
logging_service: Optional[Callable[[dict], None]] = None,
|
|
51
56
|
database_service: Optional[Union[
|
|
52
|
-
Tuple[Callable
|
|
53
|
-
List[Tuple[Callable
|
|
57
|
+
Tuple[Callable, str],
|
|
58
|
+
List[Tuple[Callable, str]]
|
|
54
59
|
]] = None,
|
|
55
60
|
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
56
61
|
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
|
|
57
62
|
) -> FastAPI:
|
|
58
63
|
load_dotenv()
|
|
59
|
-
|
|
64
|
+
setup_logger_levels()
|
|
60
65
|
cls._app = app
|
|
61
66
|
cls._config = config
|
|
67
|
+
# 保存原始的用户 lifespan
|
|
62
68
|
cls._user_lifespan = app.router.lifespan_context
|
|
63
|
-
|
|
69
|
+
|
|
64
70
|
applications.get_swagger_ui_html = custom_swagger_ui_html
|
|
65
71
|
applications.get_redoc_html = custom_redoc_html
|
|
66
|
-
|
|
72
|
+
|
|
67
73
|
if not cls._config:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
try:
|
|
75
|
+
with open('app.yaml', 'r', encoding='utf-8') as f:
|
|
76
|
+
config = yaml.safe_load(f)
|
|
77
|
+
cls._config = config
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
logging.warning("未找到 app.yaml,将使用空配置启动")
|
|
80
|
+
cls._config = {}
|
|
81
|
+
|
|
71
82
|
app.state.config = {
|
|
72
83
|
"host": cls._config.get('Host', '0.0.0.0'),
|
|
73
84
|
"port": cls._config.get('Port', 8080),
|
|
74
85
|
"workers": cls._config.get('Workers', 1),
|
|
75
|
-
"h11_max_incomplete_event_size": cls._config.get('H11MaxIncompleteEventSize', 1024 * 1024 *
|
|
86
|
+
"h11_max_incomplete_event_size": cls._config.get('H11MaxIncompleteEventSize', 1024 * 1024 * 10)
|
|
76
87
|
}
|
|
77
88
|
|
|
78
|
-
# 立即配置非异步服务(在应用启动前)
|
|
79
89
|
if middleware:
|
|
80
|
-
middleware(app,
|
|
90
|
+
middleware(app, cls._config)
|
|
81
91
|
|
|
82
92
|
if nacos_service:
|
|
83
|
-
nacos_service(
|
|
93
|
+
nacos_service(cls._config)
|
|
84
94
|
|
|
85
95
|
if logging_service:
|
|
86
|
-
logging_service(
|
|
96
|
+
logging_service(cls._config)
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
cls._setup_database_static(database_service, config)
|
|
98
|
+
sy_sentry_init()
|
|
90
99
|
|
|
91
|
-
# 创建组合生命周期管理器
|
|
92
100
|
@asynccontextmanager
|
|
93
|
-
async def combined_lifespan(
|
|
94
|
-
#
|
|
95
|
-
instance = cls(config,
|
|
96
|
-
has_listeners = bool(
|
|
97
|
-
rabbitmq_listeners and len(rabbitmq_listeners) > 0)
|
|
98
|
-
has_senders = bool(rabbitmq_senders and len(rabbitmq_senders) > 0)
|
|
101
|
+
async def combined_lifespan(app_instance: FastAPI) -> AsyncGenerator[None, None]:
|
|
102
|
+
# 获取 Services 实例
|
|
103
|
+
instance = cls(config, app_instance)
|
|
99
104
|
|
|
100
105
|
try:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
# 1. 处理数据库服务
|
|
107
|
+
if database_service:
|
|
108
|
+
instance._pending_async_db_setup = []
|
|
109
|
+
|
|
110
|
+
items = [database_service] if isinstance(
|
|
111
|
+
database_service, tuple) else database_service
|
|
112
|
+
for item in items:
|
|
113
|
+
db_setup_func, db_name = item
|
|
114
|
+
if asyncio.iscoroutinefunction(db_setup_func):
|
|
115
|
+
logging.info(f"注册异步数据库服务: {db_name}")
|
|
116
|
+
instance._pending_async_db_setup.append(item)
|
|
117
|
+
else:
|
|
118
|
+
logging.info(f"执行同步数据库服务: {db_name}")
|
|
119
|
+
try:
|
|
120
|
+
db_setup_func(config, db_name)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logging.error(
|
|
123
|
+
f"同步数据库服务 {db_name} 初始化失败: {e}", exc_info=True)
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
# 2. 执行挂起的异步数据库初始化
|
|
127
|
+
if instance._pending_async_db_setup:
|
|
128
|
+
logging.info("开始执行异步数据库初始化...")
|
|
129
|
+
for db_setup_func, db_name in instance._pending_async_db_setup:
|
|
130
|
+
try:
|
|
131
|
+
await db_setup_func(config, db_name)
|
|
132
|
+
logging.info(f"异步数据库服务 {db_name} 初始化成功")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logging.error(
|
|
135
|
+
f"异步数据库服务 {db_name} 初始化失败: {e}", exc_info=True)
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
# 3. 初始化 MQ
|
|
139
|
+
has_valid_listeners = bool(
|
|
140
|
+
rabbitmq_listeners and len(rabbitmq_listeners) > 0)
|
|
141
|
+
has_valid_senders = bool(
|
|
142
|
+
rabbitmq_senders and len(rabbitmq_senders) > 0)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
if has_valid_listeners or has_valid_senders:
|
|
146
|
+
await instance._setup_mq_async(
|
|
147
|
+
rabbitmq_listeners=rabbitmq_listeners if has_valid_listeners else None,
|
|
148
|
+
rabbitmq_senders=rabbitmq_senders if has_valid_senders else None,
|
|
149
|
+
has_listeners=has_valid_listeners,
|
|
150
|
+
has_senders=has_valid_senders
|
|
151
|
+
)
|
|
152
|
+
cls._initialized = True
|
|
153
|
+
logging.info("Services初始化完成")
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logging.error(f"MQ初始化失败: {str(e)}", exc_info=True)
|
|
156
|
+
raise
|
|
112
157
|
|
|
113
|
-
|
|
158
|
+
app_instance.state.services = instance
|
|
114
159
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
160
|
+
# 4. 执行用户定义的生命周期
|
|
161
|
+
if cls._user_lifespan:
|
|
162
|
+
async with cls._user_lifespan(app_instance):
|
|
163
|
+
yield
|
|
164
|
+
else:
|
|
165
|
+
yield
|
|
121
166
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
167
|
+
except Exception:
|
|
168
|
+
# 如果启动过程中发生任何异常,确保进入 shutdown
|
|
169
|
+
logging.error("启动阶段发生异常,准备执行清理...")
|
|
170
|
+
raise
|
|
171
|
+
finally:
|
|
172
|
+
# 无论成功或失败,都会执行关闭逻辑
|
|
173
|
+
await cls.shutdown()
|
|
174
|
+
logging.info("Services已关闭")
|
|
125
175
|
|
|
126
|
-
# 设置组合生命周期
|
|
127
176
|
app.router.lifespan_context = combined_lifespan
|
|
128
|
-
|
|
129
177
|
return app
|
|
130
178
|
|
|
131
|
-
@staticmethod
|
|
132
|
-
def _setup_database_static(database_service, config):
|
|
133
|
-
"""静态方法:设置数据库服务"""
|
|
134
|
-
if isinstance(database_service, tuple):
|
|
135
|
-
db_setup, db_name = database_service
|
|
136
|
-
db_setup(config, db_name)
|
|
137
|
-
elif isinstance(database_service, list):
|
|
138
|
-
for db_setup, db_name in database_service:
|
|
139
|
-
db_setup(config, db_name)
|
|
140
|
-
|
|
141
179
|
async def _setup_mq_async(
|
|
142
180
|
self,
|
|
143
181
|
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
@@ -145,53 +183,53 @@ class Services(metaclass=SingletonMeta):
|
|
|
145
183
|
has_listeners: bool = False,
|
|
146
184
|
has_senders: bool = False,
|
|
147
185
|
):
|
|
148
|
-
"""异步设置MQ
|
|
149
|
-
|
|
186
|
+
"""异步设置MQ相关服务"""
|
|
187
|
+
if not (has_listeners or has_senders):
|
|
188
|
+
logging.info("无RabbitMQ监听器/发送器配置,跳过RabbitMQService初始化")
|
|
189
|
+
return
|
|
190
|
+
|
|
150
191
|
RabbitMQService.init(self._config, has_listeners, has_senders)
|
|
151
192
|
|
|
152
|
-
# 等待RabbitMQ连接池初始化完成(最多等待30秒)
|
|
153
193
|
start_time = asyncio.get_event_loop().time()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
timeout = 30 # 超时时间秒
|
|
195
|
+
|
|
196
|
+
# 等待连接池初始化
|
|
197
|
+
while not (RabbitMQService._connection_pool and RabbitMQService._connection_pool._initialized) \
|
|
198
|
+
and not RabbitMQService._is_shutdown:
|
|
199
|
+
if asyncio.get_event_loop().time() - start_time > timeout:
|
|
200
|
+
logging.error("RabbitMQ连接池初始化超时")
|
|
201
|
+
raise TimeoutError(f"RabbitMQ连接池初始化超时({timeout}秒)")
|
|
202
|
+
|
|
203
|
+
logging.debug("等待RabbitMQ连接池初始化...")
|
|
204
|
+
await asyncio.sleep(0.5)
|
|
205
|
+
|
|
206
|
+
if RabbitMQService._is_shutdown:
|
|
207
|
+
raise RuntimeError("RabbitMQService 在初始化期间被关闭")
|
|
208
|
+
|
|
209
|
+
if has_senders and rabbitmq_senders:
|
|
210
|
+
if has_listeners and rabbitmq_listeners:
|
|
164
211
|
for sender in rabbitmq_senders:
|
|
165
212
|
for listener in rabbitmq_listeners:
|
|
166
213
|
if sender.queue_name == listener.queue_name:
|
|
167
214
|
sender.prefetch_count = listener.prefetch_count
|
|
168
215
|
await self._setup_senders_async(rabbitmq_senders, has_listeners)
|
|
169
216
|
|
|
170
|
-
|
|
171
|
-
if rabbitmq_listeners:
|
|
217
|
+
if has_listeners and rabbitmq_listeners:
|
|
172
218
|
await self._setup_listeners_async(rabbitmq_listeners, has_senders)
|
|
173
219
|
|
|
174
|
-
# 验证初始化结果
|
|
175
220
|
if has_listeners:
|
|
176
|
-
|
|
177
|
-
listener_count = len(RabbitMQService._clients)
|
|
221
|
+
listener_count = len(RabbitMQService._consumer_tasks)
|
|
178
222
|
logging.info(f"监听器初始化完成,共启动 {listener_count} 个消费者")
|
|
179
223
|
if listener_count == 0:
|
|
180
|
-
logging.warning("
|
|
224
|
+
logging.warning("未成功初始化任何监听器,请检查配置或MQ服务状态")
|
|
181
225
|
|
|
182
226
|
async def _setup_senders_async(self, rabbitmq_senders, has_listeners: bool):
|
|
183
|
-
"""
|
|
184
|
-
Services._registered_senders = [
|
|
185
|
-
sender.queue_name for sender in rabbitmq_senders]
|
|
186
|
-
|
|
187
|
-
# 将是否有监听器的信息传递给RabbitMQService(异步调用)
|
|
227
|
+
"""设置发送器"""
|
|
188
228
|
await RabbitMQService.setup_senders(rabbitmq_senders, has_listeners)
|
|
189
|
-
|
|
190
|
-
Services._registered_senders = RabbitMQService._sender_client_names
|
|
191
|
-
logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
|
|
229
|
+
logging.info(f"RabbitMQ发送器注册完成")
|
|
192
230
|
|
|
193
231
|
async def _setup_listeners_async(self, rabbitmq_listeners, has_senders: bool):
|
|
194
|
-
"""
|
|
232
|
+
"""设置监听器"""
|
|
195
233
|
await RabbitMQService.setup_listeners(rabbitmq_listeners, has_senders)
|
|
196
234
|
|
|
197
235
|
@classmethod
|
|
@@ -202,7 +240,7 @@ class Services(metaclass=SingletonMeta):
|
|
|
202
240
|
max_retries: int = 3,
|
|
203
241
|
retry_delay: float = 1.0, **kwargs
|
|
204
242
|
) -> None:
|
|
205
|
-
"""
|
|
243
|
+
"""发送消息"""
|
|
206
244
|
if not cls._initialized or not cls._loop:
|
|
207
245
|
logging.error("Services not properly initialized!")
|
|
208
246
|
raise ValueError("服务未正确初始化")
|
|
@@ -213,18 +251,13 @@ class Services(metaclass=SingletonMeta):
|
|
|
213
251
|
|
|
214
252
|
for attempt in range(max_retries):
|
|
215
253
|
try:
|
|
216
|
-
#
|
|
217
|
-
if queue_name not in cls._registered_senders:
|
|
218
|
-
cls._registered_senders = RabbitMQService._sender_client_names
|
|
219
|
-
if queue_name not in cls._registered_senders:
|
|
220
|
-
raise ValueError(f"发送器 {queue_name} 未注册")
|
|
221
|
-
|
|
222
|
-
# 获取发送器(适配新的异步get_sender方法)
|
|
254
|
+
# 依赖 RabbitMQService 的内部状态
|
|
223
255
|
sender = await RabbitMQService.get_sender(queue_name)
|
|
256
|
+
|
|
224
257
|
if not sender:
|
|
225
|
-
raise ValueError(
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"发送器 '{queue_name}' 不存在或未在 RabbitMQService 中注册")
|
|
226
260
|
|
|
227
|
-
# 发送消息(调用RabbitMQService的异步send_message)
|
|
228
261
|
await RabbitMQService.send_message(data, queue_name, **kwargs)
|
|
229
262
|
logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
|
|
230
263
|
return
|
|
@@ -234,35 +267,31 @@ class Services(metaclass=SingletonMeta):
|
|
|
234
267
|
logging.error(
|
|
235
268
|
f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
|
|
236
269
|
raise
|
|
237
|
-
|
|
238
270
|
logging.warning(
|
|
239
|
-
f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
|
|
240
|
-
f"{retry_delay}秒后重试..."
|
|
241
|
-
)
|
|
271
|
+
f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},{retry_delay}秒后重试...")
|
|
242
272
|
await asyncio.sleep(retry_delay)
|
|
243
273
|
|
|
244
274
|
@classmethod
|
|
245
275
|
async def shutdown(cls):
|
|
246
|
-
"""
|
|
276
|
+
"""关闭所有服务"""
|
|
247
277
|
async with cls._shutdown_lock:
|
|
248
278
|
if RabbitMQService._is_shutdown:
|
|
249
279
|
logging.info("RabbitMQService已关闭,无需重复操作")
|
|
250
280
|
return
|
|
251
281
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
try:
|
|
257
|
-
await task
|
|
258
|
-
except asyncio.CancelledError:
|
|
259
|
-
logging.info(f"MQ任务 {task.get_name()} 已取消")
|
|
260
|
-
|
|
261
|
-
# 关闭RabbitMQ服务(异步调用)
|
|
262
|
-
await RabbitMQService.shutdown()
|
|
282
|
+
try:
|
|
283
|
+
await RabbitMQService.shutdown()
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logging.error(f"关闭 RabbitMQService 时发生异常: {e}", exc_info=True)
|
|
263
286
|
|
|
264
|
-
# 清理全局状态
|
|
265
287
|
cls._initialized = False
|
|
266
|
-
|
|
267
|
-
|
|
288
|
+
|
|
289
|
+
# 清理实例数据
|
|
290
|
+
if cls._instance:
|
|
291
|
+
cls._instance._pending_async_db_setup.clear()
|
|
292
|
+
|
|
293
|
+
# 这对于热重载(reload)时防止旧实例内存泄漏至关重要
|
|
294
|
+
if cls._app:
|
|
295
|
+
cls._app.state.services = None
|
|
296
|
+
|
|
268
297
|
logging.info("所有服务已关闭")
|