sycommon-python-lib 0.1.55b0__py3-none-any.whl → 0.1.56__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.
Files changed (51) hide show
  1. sycommon/config/Config.py +29 -4
  2. sycommon/config/LangfuseConfig.py +15 -0
  3. sycommon/config/RerankerConfig.py +1 -0
  4. sycommon/config/SentryConfig.py +13 -0
  5. sycommon/database/async_base_db_service.py +36 -0
  6. sycommon/database/async_database_service.py +96 -0
  7. sycommon/llm/__init__.py +0 -0
  8. sycommon/llm/embedding.py +204 -0
  9. sycommon/llm/get_llm.py +37 -0
  10. sycommon/llm/llm_logger.py +126 -0
  11. sycommon/llm/llm_tokens.py +119 -0
  12. sycommon/llm/struct_token.py +192 -0
  13. sycommon/llm/sy_langfuse.py +103 -0
  14. sycommon/llm/usage_token.py +117 -0
  15. sycommon/logging/async_sql_logger.py +65 -0
  16. sycommon/logging/kafka_log.py +200 -434
  17. sycommon/logging/logger_levels.py +23 -0
  18. sycommon/middleware/context.py +2 -0
  19. sycommon/middleware/exception.py +10 -16
  20. sycommon/middleware/timeout.py +2 -1
  21. sycommon/middleware/traceid.py +174 -48
  22. sycommon/notice/__init__.py +0 -0
  23. sycommon/notice/uvicorn_monitor.py +200 -0
  24. sycommon/rabbitmq/rabbitmq_client.py +232 -242
  25. sycommon/rabbitmq/rabbitmq_pool.py +278 -218
  26. sycommon/rabbitmq/rabbitmq_service.py +25 -843
  27. sycommon/rabbitmq/rabbitmq_service_client_manager.py +206 -0
  28. sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +73 -0
  29. sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +285 -0
  30. sycommon/rabbitmq/rabbitmq_service_core.py +117 -0
  31. sycommon/rabbitmq/rabbitmq_service_producer_manager.py +238 -0
  32. sycommon/sentry/__init__.py +0 -0
  33. sycommon/sentry/sy_sentry.py +35 -0
  34. sycommon/services.py +132 -103
  35. sycommon/synacos/feign.py +8 -3
  36. sycommon/synacos/feign_client.py +22 -8
  37. sycommon/synacos/nacos_client_base.py +119 -0
  38. sycommon/synacos/nacos_config_manager.py +107 -0
  39. sycommon/synacos/nacos_heartbeat_manager.py +144 -0
  40. sycommon/synacos/nacos_service.py +64 -771
  41. sycommon/synacos/nacos_service_discovery.py +157 -0
  42. sycommon/synacos/nacos_service_registration.py +270 -0
  43. sycommon/tools/env.py +62 -0
  44. sycommon/tools/merge_headers.py +117 -0
  45. sycommon/tools/snowflake.py +133 -120
  46. {sycommon_python_lib-0.1.55b0.dist-info → sycommon_python_lib-0.1.56.dist-info}/METADATA +13 -6
  47. sycommon_python_lib-0.1.56.dist-info/RECORD +89 -0
  48. sycommon_python_lib-0.1.55b0.dist-info/RECORD +0 -59
  49. {sycommon_python_lib-0.1.55b0.dist-info → sycommon_python_lib-0.1.56.dist-info}/WHEEL +0 -0
  50. {sycommon_python_lib-0.1.55b0.dist-info → sycommon_python_lib-0.1.56.dist-info}/entry_points.txt +0 -0
  51. {sycommon_python_lib-0.1.55b0.dist-info → sycommon_python_lib-0.1.56.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,31 +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
22
  _instance: Optional['Services'] = None
22
23
  _app: Optional[FastAPI] = None
23
24
  _user_lifespan: Optional[Callable] = None
24
25
  _shutdown_lock: asyncio.Lock = asyncio.Lock()
25
26
 
26
27
  def __init__(self, config: dict, app: FastAPI):
28
+ super().__init__()
27
29
  if not Services._config:
28
30
  Services._config = config
29
31
  Services._instance = self
30
32
  Services._app = app
33
+
34
+ # 在实例初始化时定义变量,防止类变量污染
35
+ self._pending_async_db_setup: List[Tuple[Callable, str]] = []
36
+
31
37
  self._init_event_loop()
32
38
 
33
39
  def _init_event_loop(self):
34
- """初始化事件循环,确保全局只有一个循环实例"""
40
+ """初始化事件循环"""
35
41
  if not Services._loop:
36
42
  try:
37
43
  Services._loop = asyncio.get_running_loop()
@@ -48,25 +54,31 @@ class Services(metaclass=SingletonMeta):
48
54
  nacos_service: Optional[Callable[[dict], None]] = None,
49
55
  logging_service: Optional[Callable[[dict], None]] = None,
50
56
  database_service: Optional[Union[
51
- Tuple[Callable[[dict, str], None], str],
52
- List[Tuple[Callable[[dict, str], None], str]]
57
+ Tuple[Callable, str],
58
+ List[Tuple[Callable, str]]
53
59
  ]] = None,
54
60
  rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
55
61
  rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
56
62
  ) -> FastAPI:
57
63
  load_dotenv()
58
- # 保存应用实例和配置
64
+ setup_logger_levels()
59
65
  cls._app = app
60
66
  cls._config = config
67
+ # 保存原始的用户 lifespan
61
68
  cls._user_lifespan = app.router.lifespan_context
62
- # 设置文档
69
+
63
70
  applications.get_swagger_ui_html = custom_swagger_ui_html
64
71
  applications.get_redoc_html = custom_redoc_html
65
- # 设置app.state host, port
72
+
66
73
  if not cls._config:
67
- config = yaml.safe_load(open('app.yaml', 'r', encoding='utf-8'))
68
- cls._config = config
69
- # 使用config
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
+
70
82
  app.state.config = {
71
83
  "host": cls._config.get('Host', '0.0.0.0'),
72
84
  "port": cls._config.get('Port', 8080),
@@ -74,74 +86,96 @@ class Services(metaclass=SingletonMeta):
74
86
  "h11_max_incomplete_event_size": cls._config.get('H11MaxIncompleteEventSize', 1024 * 1024 * 10)
75
87
  }
76
88
 
77
- # 立即配置非异步服务(在应用启动前)
78
89
  if middleware:
79
- middleware(app, config)
90
+ middleware(app, cls._config)
80
91
 
81
92
  if nacos_service:
82
- nacos_service(config)
93
+ nacos_service(cls._config)
83
94
 
84
95
  if logging_service:
85
- logging_service(config)
96
+ logging_service(cls._config)
86
97
 
87
- if database_service:
88
- cls._setup_database_static(database_service, config)
98
+ sy_sentry_init()
89
99
 
90
- # 创建组合生命周期管理器
91
100
  @asynccontextmanager
92
- async def combined_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
93
- # 1. 执行Services自身的初始化
94
- instance = cls(config, app)
95
-
96
- # 明确判断是否有有效的监听器/发送器配置
97
- has_valid_listeners = bool(
98
- rabbitmq_listeners and len(rabbitmq_listeners) > 0)
99
- has_valid_senders = bool(
100
- 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)
101
104
 
102
105
  try:
103
- # 只有存在监听器或发送器时才初始化RabbitMQService
104
- if has_valid_listeners or has_valid_senders:
105
- await instance._setup_mq_async(
106
- rabbitmq_listeners=rabbitmq_listeners if has_valid_listeners else None,
107
- rabbitmq_senders=rabbitmq_senders if has_valid_senders else None,
108
- has_listeners=has_valid_listeners,
109
- has_senders=has_valid_senders
110
- )
111
- cls._initialized = True
112
- logging.info("Services初始化完成")
113
- except Exception as e:
114
- logging.error(f"Services初始化失败: {str(e)}", exc_info=True)
115
- raise
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
116
157
 
117
- app.state.services = instance
158
+ app_instance.state.services = instance
118
159
 
119
- # 2. 执行用户定义的生命周期
120
- if cls._user_lifespan:
121
- async with cls._user_lifespan(app):
122
- yield # 应用运行阶段
123
- else:
124
- yield # 没有用户生命周期时直接 yield
160
+ # 4. 执行用户定义的生命周期
161
+ if cls._user_lifespan:
162
+ async with cls._user_lifespan(app_instance):
163
+ yield
164
+ else:
165
+ yield
125
166
 
126
- # 3. 执行Services的关闭逻辑
127
- await cls.shutdown()
128
- logging.info("Services已关闭")
167
+ except Exception:
168
+ # 如果启动过程中发生任何异常,确保进入 shutdown
169
+ logging.error("启动阶段发生异常,准备执行清理...")
170
+ raise
171
+ finally:
172
+ # 无论成功或失败,都会执行关闭逻辑
173
+ await cls.shutdown()
174
+ logging.info("Services已关闭")
129
175
 
130
- # 设置组合生命周期
131
176
  app.router.lifespan_context = combined_lifespan
132
-
133
177
  return app
134
178
 
135
- @staticmethod
136
- def _setup_database_static(database_service, config):
137
- """静态方法:设置数据库服务"""
138
- if isinstance(database_service, tuple):
139
- db_setup, db_name = database_service
140
- db_setup(config, db_name)
141
- elif isinstance(database_service, list):
142
- for db_setup, db_name in database_service:
143
- db_setup(config, db_name)
144
-
145
179
  async def _setup_mq_async(
146
180
  self,
147
181
  rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
@@ -149,27 +183,30 @@ class Services(metaclass=SingletonMeta):
149
183
  has_listeners: bool = False,
150
184
  has_senders: bool = False,
151
185
  ):
152
- """异步设置MQ相关服务(适配单通道RabbitMQService)"""
153
- # ========== 只有需要使用MQ时才初始化 ==========
186
+ """异步设置MQ相关服务"""
154
187
  if not (has_listeners or has_senders):
155
188
  logging.info("无RabbitMQ监听器/发送器配置,跳过RabbitMQService初始化")
156
189
  return
157
190
 
158
- # 仅当有监听器或发送器时,才执行RabbitMQService初始化
159
191
  RabbitMQService.init(self._config, has_listeners, has_senders)
160
192
 
161
- # 优化:等待连接池“存在且初始化完成”(避免提前执行后续逻辑)
162
193
  start_time = asyncio.get_event_loop().time()
163
- while not (RabbitMQService._connection_pool and RabbitMQService._connection_pool._initialized) and not RabbitMQService._is_shutdown:
164
- if asyncio.get_event_loop().time() - start_time > 30:
165
- raise TimeoutError("RabbitMQ连接池初始化超时(30秒)")
166
- logging.info("等待RabbitMQ连接池初始化...")
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连接池初始化...")
167
204
  await asyncio.sleep(0.5)
168
205
 
169
- # ========== 保留原有严格的发送器/监听器初始化判断 ==========
170
- # 只有配置了发送器才执行发送器初始化
206
+ if RabbitMQService._is_shutdown:
207
+ raise RuntimeError("RabbitMQService 在初始化期间被关闭")
208
+
171
209
  if has_senders and rabbitmq_senders:
172
- # 判断是否有监听器,如果有遍历监听器列表,队列名一样将prefetch_count属性设置到发送器对象中
173
210
  if has_listeners and rabbitmq_listeners:
174
211
  for sender in rabbitmq_senders:
175
212
  for listener in rabbitmq_listeners:
@@ -177,31 +214,22 @@ class Services(metaclass=SingletonMeta):
177
214
  sender.prefetch_count = listener.prefetch_count
178
215
  await self._setup_senders_async(rabbitmq_senders, has_listeners)
179
216
 
180
- # 只有配置了监听器才执行监听器初始化
181
217
  if has_listeners and rabbitmq_listeners:
182
218
  await self._setup_listeners_async(rabbitmq_listeners, has_senders)
183
219
 
184
- # 验证初始化结果
185
220
  if has_listeners:
186
- # 异步获取客户端数量(适配新的RabbitMQService)
187
221
  listener_count = len(RabbitMQService._consumer_tasks)
188
222
  logging.info(f"监听器初始化完成,共启动 {listener_count} 个消费者")
189
223
  if listener_count == 0:
190
224
  logging.warning("未成功初始化任何监听器,请检查配置或MQ服务状态")
191
225
 
192
226
  async def _setup_senders_async(self, rabbitmq_senders, has_listeners: bool):
193
- """设置发送器(适配新的RabbitMQService异步方法)"""
194
- Services._registered_senders = [
195
- sender.queue_name for sender in rabbitmq_senders]
196
-
197
- # 将是否有监听器的信息传递给RabbitMQService(异步调用)
227
+ """设置发送器"""
198
228
  await RabbitMQService.setup_senders(rabbitmq_senders, has_listeners)
199
- # 更新已注册的发送器(从RabbitMQService获取实际注册的名称)
200
- Services._registered_senders = RabbitMQService._sender_client_names
201
- logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
229
+ logging.info(f"RabbitMQ发送器注册完成")
202
230
 
203
231
  async def _setup_listeners_async(self, rabbitmq_listeners, has_senders: bool):
204
- """设置监听器(适配新的RabbitMQService异步方法)"""
232
+ """设置监听器"""
205
233
  await RabbitMQService.setup_listeners(rabbitmq_listeners, has_senders)
206
234
 
207
235
  @classmethod
@@ -212,7 +240,7 @@ class Services(metaclass=SingletonMeta):
212
240
  max_retries: int = 3,
213
241
  retry_delay: float = 1.0, **kwargs
214
242
  ) -> None:
215
- """发送消息,添加重试机制(适配单通道RabbitMQService)"""
243
+ """发送消息"""
216
244
  if not cls._initialized or not cls._loop:
217
245
  logging.error("Services not properly initialized!")
218
246
  raise ValueError("服务未正确初始化")
@@ -223,18 +251,13 @@ class Services(metaclass=SingletonMeta):
223
251
 
224
252
  for attempt in range(max_retries):
225
253
  try:
226
- # 验证发送器是否注册
227
- if queue_name not in cls._registered_senders:
228
- cls._registered_senders = RabbitMQService._sender_client_names
229
- if queue_name not in cls._registered_senders:
230
- raise ValueError(f"发送器 {queue_name} 未注册")
231
-
232
- # 获取发送器(适配新的异步get_sender方法)
254
+ # 依赖 RabbitMQService 的内部状态
233
255
  sender = await RabbitMQService.get_sender(queue_name)
256
+
234
257
  if not sender:
235
- raise ValueError(f"发送器 '{queue_name}' 不存在或连接无效")
258
+ raise ValueError(
259
+ f"发送器 '{queue_name}' 不存在或未在 RabbitMQService 中注册")
236
260
 
237
- # 发送消息(调用RabbitMQService的异步send_message)
238
261
  await RabbitMQService.send_message(data, queue_name, **kwargs)
239
262
  logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
240
263
  return
@@ -244,25 +267,31 @@ class Services(metaclass=SingletonMeta):
244
267
  logging.error(
245
268
  f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
246
269
  raise
247
-
248
270
  logging.warning(
249
- f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
250
- f"{retry_delay}秒后重试..."
251
- )
271
+ f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},{retry_delay}秒后重试...")
252
272
  await asyncio.sleep(retry_delay)
253
273
 
254
274
  @classmethod
255
275
  async def shutdown(cls):
256
- """关闭所有服务(适配单通道RabbitMQService关闭逻辑)"""
276
+ """关闭所有服务"""
257
277
  async with cls._shutdown_lock:
258
278
  if RabbitMQService._is_shutdown:
259
279
  logging.info("RabbitMQService已关闭,无需重复操作")
260
280
  return
261
281
 
262
- # 关闭RabbitMQ服务(异步调用,内部会关闭所有客户端+消费任务)
263
- await RabbitMQService.shutdown()
282
+ try:
283
+ await RabbitMQService.shutdown()
284
+ except Exception as e:
285
+ logging.error(f"关闭 RabbitMQService 时发生异常: {e}", exc_info=True)
264
286
 
265
- # 清理全局状态
266
287
  cls._initialized = False
267
- cls._registered_senders.clear()
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("所有服务已关闭")