sycommon-python-lib 0.1.1__py3-none-any.whl → 0.1.3__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.
Potentially problematic release.
This version of sycommon-python-lib might be problematic. Click here for more details.
- sycommon/config/EmbeddingConfig.py +0 -1
- sycommon/config/MQConfig.py +15 -0
- sycommon/database/base_db_service.py +30 -0
- sycommon/logging/kafka_log.py +14 -38
- sycommon/middleware/middleware.py +23 -16
- sycommon/middleware/mq.py +9 -0
- sycommon/models/base_http.py +99 -0
- sycommon/models/mqlistener_config.py +38 -0
- sycommon/models/mqmsg_model.py +11 -0
- sycommon/models/mqsend_config.py +8 -0
- sycommon/models/sso_user.py +60 -0
- sycommon/rabbitmq/rabbitmq_client.py +721 -0
- sycommon/rabbitmq/rabbitmq_service.py +476 -0
- sycommon/services.py +208 -20
- sycommon/synacos/feign.py +14 -10
- sycommon/synacos/nacos_service.py +252 -188
- {sycommon_python_lib-0.1.1.dist-info → sycommon_python_lib-0.1.3.dist-info}/METADATA +2 -1
- {sycommon_python_lib-0.1.1.dist-info → sycommon_python_lib-0.1.3.dist-info}/RECORD +20 -10
- {sycommon_python_lib-0.1.1.dist-info → sycommon_python_lib-0.1.3.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.1.dist-info → sycommon_python_lib-0.1.3.dist-info}/top_level.txt +0 -0
sycommon/services.py
CHANGED
|
@@ -1,29 +1,217 @@
|
|
|
1
|
-
from typing import Callable, List, Tuple
|
|
2
|
-
|
|
1
|
+
from typing import Any, Callable, Dict, List, Tuple, Union, Optional, Awaitable
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
import yaml
|
|
3
9
|
from sycommon.config.Config import SingletonMeta
|
|
10
|
+
from sycommon.models.mqlistener_config import RabbitMQListenerConfig
|
|
11
|
+
from sycommon.models.mqsend_config import RabbitMQSendConfig
|
|
12
|
+
from sycommon.rabbitmq.rabbitmq_service import RabbitMQService
|
|
4
13
|
|
|
5
14
|
|
|
6
15
|
class Services(metaclass=SingletonMeta):
|
|
16
|
+
_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
17
|
+
_config: Optional[dict] = None
|
|
18
|
+
_initialized: bool = False
|
|
19
|
+
_registered_senders: List[str] = []
|
|
20
|
+
_mq_tasks: List[asyncio.Task] = []
|
|
21
|
+
_pending_setup: Optional[Callable[..., Awaitable[None]]] = None
|
|
22
|
+
_instance: Optional['Services'] = None
|
|
23
|
+
|
|
7
24
|
def __init__(self, config):
|
|
8
|
-
|
|
25
|
+
if not Services._config:
|
|
26
|
+
Services._config = config
|
|
27
|
+
Services._instance = self
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_lifespan(cls, config):
|
|
31
|
+
"""返回 FastAPI 的 lifespan 管理器"""
|
|
32
|
+
cls._config = config
|
|
33
|
+
|
|
34
|
+
@asynccontextmanager
|
|
35
|
+
async def lifespan(app):
|
|
36
|
+
# 应用启动时初始化
|
|
37
|
+
cls._loop = asyncio.get_running_loop()
|
|
38
|
+
cls._initialized = True
|
|
39
|
+
logging.info("Services initialized with FastAPI event loop")
|
|
40
|
+
|
|
41
|
+
# 执行之前缓存的MQ设置
|
|
42
|
+
if cls._pending_setup:
|
|
43
|
+
try:
|
|
44
|
+
await cls._pending_setup()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
logging.error(f"执行MQ初始化失败: {str(e)}", exc_info=True)
|
|
47
|
+
finally:
|
|
48
|
+
cls._pending_setup = None
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
yield
|
|
52
|
+
finally:
|
|
53
|
+
# 应用关闭时清理
|
|
54
|
+
await cls.shutdown()
|
|
55
|
+
logging.info("Services shutdown completed")
|
|
9
56
|
|
|
10
|
-
|
|
11
|
-
|
|
57
|
+
return lifespan
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def plugins(cls,
|
|
61
|
+
middleware: Optional[Tuple[Callable, FastAPI]] = None,
|
|
62
|
+
nacos_service: Optional[Callable] = None,
|
|
63
|
+
logging_service: Optional[Callable] = None,
|
|
64
|
+
database_service: Optional[Union[Tuple[Callable, str],
|
|
65
|
+
List[Tuple[Callable, str]]]] = None,
|
|
66
|
+
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
67
|
+
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
类方法:注册各种服务插件
|
|
71
|
+
确保单例实例存在后调用实例方法的register_plugins
|
|
72
|
+
"""
|
|
73
|
+
# 确保实例已创建
|
|
74
|
+
if not cls._instance:
|
|
75
|
+
if not cls._config:
|
|
76
|
+
raise ValueError("Services尚未初始化,请先提供配置")
|
|
77
|
+
cls._instance = Services(cls._config)
|
|
78
|
+
|
|
79
|
+
cls._instance.register_plugins(
|
|
80
|
+
middleware=middleware,
|
|
81
|
+
nacos_service=nacos_service,
|
|
82
|
+
logging_service=logging_service,
|
|
83
|
+
database_service=database_service,
|
|
84
|
+
rabbitmq_listeners=rabbitmq_listeners,
|
|
85
|
+
rabbitmq_senders=rabbitmq_senders
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def register_plugins(
|
|
89
|
+
self,
|
|
90
|
+
middleware: Optional[Tuple[Callable, FastAPI]] = None,
|
|
91
|
+
nacos_service: Optional[Callable] = None,
|
|
92
|
+
logging_service: Optional[Callable] = None,
|
|
93
|
+
database_service: Optional[Union[Tuple[Callable,
|
|
94
|
+
str], List[Tuple[Callable, str]]]] = None,
|
|
95
|
+
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
96
|
+
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
|
|
97
|
+
) -> None:
|
|
98
|
+
"""实例方法:实际执行各种服务插件的注册逻辑"""
|
|
99
|
+
# 注册非异步服务
|
|
100
|
+
if middleware:
|
|
101
|
+
self._setup_middleware(middleware)
|
|
12
102
|
if nacos_service:
|
|
13
|
-
nacos_service(self.
|
|
14
|
-
# 注册日志服务
|
|
103
|
+
nacos_service(self._config)
|
|
15
104
|
if logging_service:
|
|
16
|
-
logging_service(self.
|
|
17
|
-
# 注册数据库服务
|
|
105
|
+
logging_service(self._config)
|
|
18
106
|
if database_service:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
107
|
+
self._setup_database(database_service)
|
|
108
|
+
|
|
109
|
+
RabbitMQService.init(self._config)
|
|
110
|
+
|
|
111
|
+
# MQ设置异步函数
|
|
112
|
+
async def setup_mq_components():
|
|
113
|
+
if rabbitmq_senders:
|
|
114
|
+
self._setup_senders(rabbitmq_senders)
|
|
115
|
+
if rabbitmq_listeners:
|
|
116
|
+
self._setup_listeners(rabbitmq_listeners)
|
|
117
|
+
|
|
118
|
+
# 存储到_pending_setup,等待lifespan异步执行
|
|
119
|
+
Services._pending_setup = setup_mq_components
|
|
120
|
+
|
|
121
|
+
def _setup_database(self, database_service):
|
|
122
|
+
if isinstance(database_service, tuple):
|
|
123
|
+
db_setup, db_name = database_service
|
|
124
|
+
db_setup(self._config, db_name)
|
|
125
|
+
elif isinstance(database_service, list):
|
|
126
|
+
for db_setup, db_name in database_service:
|
|
127
|
+
db_setup(self._config, db_name)
|
|
128
|
+
|
|
129
|
+
def _setup_middleware(self, middleware):
|
|
130
|
+
if isinstance(middleware, tuple):
|
|
131
|
+
middleware_setup, app = middleware
|
|
132
|
+
middleware_setup(app, self._config)
|
|
133
|
+
|
|
134
|
+
def _setup_senders(self, rabbitmq_senders):
|
|
135
|
+
Services._registered_senders = [
|
|
136
|
+
sender.queue_name for sender in rabbitmq_senders]
|
|
137
|
+
if self._loop: # 确保loop存在
|
|
138
|
+
task = self._loop.create_task(
|
|
139
|
+
self._setup_and_wait(
|
|
140
|
+
RabbitMQService.setup_senders, rabbitmq_senders)
|
|
141
|
+
)
|
|
142
|
+
self._mq_tasks.append(task)
|
|
143
|
+
logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
|
|
144
|
+
|
|
145
|
+
def _setup_listeners(self, rabbitmq_listeners):
|
|
146
|
+
if self._loop: # 确保loop存在
|
|
147
|
+
task = self._loop.create_task(
|
|
148
|
+
self._setup_and_wait(
|
|
149
|
+
RabbitMQService.setup_listeners, rabbitmq_listeners)
|
|
150
|
+
)
|
|
151
|
+
self._mq_tasks.append(task)
|
|
152
|
+
|
|
153
|
+
async def _setup_and_wait(self, setup_func, *args, **kwargs):
|
|
154
|
+
try:
|
|
155
|
+
await setup_func(*args, **kwargs)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logging.error(
|
|
158
|
+
f"Error in {setup_func.__name__}: {str(e)}", exc_info=True)
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
async def send_message(
|
|
162
|
+
cls,
|
|
163
|
+
queue_name: str,
|
|
164
|
+
data: Union[str, Dict[str, Any], BaseModel, None],
|
|
165
|
+
max_retries: int = 3, # 最大重试次数
|
|
166
|
+
retry_delay: float = 1.0, # 重试间隔(秒)
|
|
167
|
+
**kwargs
|
|
168
|
+
) -> None:
|
|
169
|
+
"""发送消息,添加重试机制处理发送器不存在的情况"""
|
|
170
|
+
if not cls._initialized or not cls._loop:
|
|
171
|
+
logging.error("Services not properly initialized!")
|
|
172
|
+
raise ValueError("服务未正确初始化")
|
|
173
|
+
|
|
174
|
+
# 重试逻辑
|
|
175
|
+
for attempt in range(max_retries):
|
|
176
|
+
try:
|
|
177
|
+
# 检查发送器是否已注册
|
|
178
|
+
if queue_name not in cls._registered_senders:
|
|
179
|
+
# 可能是初始化尚未完成,尝试刷新注册列表
|
|
180
|
+
cls._registered_senders = RabbitMQService.sender_client_names
|
|
181
|
+
if queue_name not in cls._registered_senders:
|
|
182
|
+
raise ValueError(f"发送器 {queue_name} 未注册")
|
|
183
|
+
|
|
184
|
+
# 获取发送器
|
|
185
|
+
sender = RabbitMQService.get_sender(queue_name)
|
|
186
|
+
if not sender:
|
|
187
|
+
raise ValueError(f"发送器 '{queue_name}' 不存在")
|
|
188
|
+
|
|
189
|
+
# 发送消息
|
|
190
|
+
await RabbitMQService.send_message(data, queue_name, ** kwargs)
|
|
191
|
+
logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
|
|
192
|
+
return # 成功发送,退出函数
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
# 最后一次尝试失败则抛出异常
|
|
196
|
+
if attempt == max_retries - 1:
|
|
197
|
+
logging.error(
|
|
198
|
+
f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
# 非最后一次尝试,记录警告并等待重试
|
|
202
|
+
logging.warning(
|
|
203
|
+
f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
|
|
204
|
+
f"{retry_delay}秒后重试..."
|
|
205
|
+
)
|
|
206
|
+
await asyncio.sleep(retry_delay)
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
async def shutdown():
|
|
210
|
+
for task in Services._mq_tasks:
|
|
211
|
+
if not task.done():
|
|
212
|
+
task.cancel()
|
|
213
|
+
try:
|
|
214
|
+
await task
|
|
215
|
+
except asyncio.CancelledError:
|
|
216
|
+
pass
|
|
217
|
+
await RabbitMQService.shutdown()
|
sycommon/synacos/feign.py
CHANGED
|
@@ -46,7 +46,7 @@ from sycommon.synacos.nacos_service import NacosService
|
|
|
46
46
|
# return None
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
def feign_client(service_name: str, path_prefix: str = ""):
|
|
49
|
+
def feign_client(service_name: str, path_prefix: str = "", default_timeout: float | None = None):
|
|
50
50
|
# Feign风格客户端装饰器
|
|
51
51
|
"""声明式HTTP客户端装饰器"""
|
|
52
52
|
def decorator(cls):
|
|
@@ -56,6 +56,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
|
|
|
56
56
|
self.nacos_manager = NacosService(None)
|
|
57
57
|
self.path_prefix = path_prefix
|
|
58
58
|
self.session = aiohttp.ClientSession()
|
|
59
|
+
self.default_timeout = default_timeout
|
|
59
60
|
|
|
60
61
|
def __getattr__(self, name):
|
|
61
62
|
func = getattr(cls, name)
|
|
@@ -67,6 +68,8 @@ def feign_client(service_name: str, path_prefix: str = ""):
|
|
|
67
68
|
path = request_meta.get("path", "")
|
|
68
69
|
headers = request_meta.get("headers", {})
|
|
69
70
|
|
|
71
|
+
timeout = kwargs.pop('timeout', self.default_timeout)
|
|
72
|
+
|
|
70
73
|
# 获取版本信息
|
|
71
74
|
version = headers.get('s-y-version')
|
|
72
75
|
|
|
@@ -120,7 +123,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
|
|
|
120
123
|
headers=headers,
|
|
121
124
|
params=params,
|
|
122
125
|
data=data,
|
|
123
|
-
timeout=
|
|
126
|
+
timeout=timeout
|
|
124
127
|
) as response:
|
|
125
128
|
return await self._handle_response(response)
|
|
126
129
|
else:
|
|
@@ -131,7 +134,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
|
|
|
131
134
|
headers=headers,
|
|
132
135
|
params=params,
|
|
133
136
|
json=body,
|
|
134
|
-
timeout=
|
|
137
|
+
timeout=timeout
|
|
135
138
|
) as response:
|
|
136
139
|
return await self._handle_response(response)
|
|
137
140
|
except Exception as e:
|
|
@@ -188,15 +191,15 @@ def feign_upload(field_name: str = "file"):
|
|
|
188
191
|
|
|
189
192
|
|
|
190
193
|
async def feign(service_name, api_path, method='GET', params=None, headers=None, file_path=None,
|
|
191
|
-
path_params=None,
|
|
194
|
+
path_params=None, body=None, files=None, form_data=None, timeout=None):
|
|
192
195
|
"""
|
|
193
|
-
feign 函数,支持 form-data
|
|
196
|
+
feign 函数,支持 form-data 表单上传文件和其他字段,支持自定义超时时间
|
|
194
197
|
|
|
195
198
|
参数:
|
|
196
199
|
files: 字典,用于上传文件,格式: {'field_name': (filename, file_content)}
|
|
197
200
|
form_data: 字典,用于上传表单字段
|
|
201
|
+
timeout: 超时时间(秒),None 表示不设置超时
|
|
198
202
|
"""
|
|
199
|
-
file_stream = None
|
|
200
203
|
session = aiohttp.ClientSession()
|
|
201
204
|
try:
|
|
202
205
|
# 获取健康的服务实例
|
|
@@ -259,7 +262,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
|
|
|
259
262
|
headers=headers,
|
|
260
263
|
params=params,
|
|
261
264
|
data=data,
|
|
262
|
-
timeout=
|
|
265
|
+
timeout=timeout
|
|
263
266
|
) as response:
|
|
264
267
|
return await _handle_feign_response(response)
|
|
265
268
|
else:
|
|
@@ -272,7 +275,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
|
|
|
272
275
|
headers=headers,
|
|
273
276
|
params=params,
|
|
274
277
|
json=body,
|
|
275
|
-
timeout=
|
|
278
|
+
timeout=timeout
|
|
276
279
|
) as response:
|
|
277
280
|
return await _handle_feign_response(response)
|
|
278
281
|
except aiohttp.ClientError as e:
|
|
@@ -281,8 +284,9 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
|
|
|
281
284
|
print(f"请求服务接口时出错: {e}")
|
|
282
285
|
return None
|
|
283
286
|
except Exception as e:
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
import traceback
|
|
288
|
+
SYLogger.error(f"nacos:请求服务接口时出错: {traceback.format_exc()}")
|
|
289
|
+
print(f"nacos:请求服务接口时出错: {traceback.format_exc()}")
|
|
286
290
|
return None
|
|
287
291
|
finally:
|
|
288
292
|
SYLogger.info(f"nacos:结束调用服务: {service_name}, {api_path}")
|