sycommon-python-lib 0.1.8__py3-none-any.whl → 0.1.10__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/middleware/docs.py +30 -0
- sycommon/middleware/middleware.py +4 -0
- sycommon/middleware/traceid.py +32 -4
- sycommon/models/base_http.py +27 -25
- sycommon/rabbitmq/rabbitmq_client.py +68 -40
- sycommon/rabbitmq/rabbitmq_service.py +103 -46
- sycommon/services.py +122 -127
- sycommon/tools/docs.py +42 -0
- {sycommon_python_lib-0.1.8.dist-info → sycommon_python_lib-0.1.10.dist-info}/METADATA +1 -1
- {sycommon_python_lib-0.1.8.dist-info → sycommon_python_lib-0.1.10.dist-info}/RECORD +12 -10
- {sycommon_python_lib-0.1.8.dist-info → sycommon_python_lib-0.1.10.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.8.dist-info → sycommon_python_lib-0.1.10.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from fastapi import FastAPI, APIRouter
|
|
2
|
+
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def setup_docs_handler(app: FastAPI):
|
|
6
|
+
docs_router = APIRouter()
|
|
7
|
+
|
|
8
|
+
@docs_router.get("/docs", include_in_schema=False)
|
|
9
|
+
async def custom_swagger_ui_html():
|
|
10
|
+
return get_swagger_ui_html(
|
|
11
|
+
openapi_url=app.openapi_url,
|
|
12
|
+
title=f"{app.title}",
|
|
13
|
+
swagger_favicon_url="https://static.sytechnology.com/img/sylogopng.png",
|
|
14
|
+
swagger_js_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui-bundle.js",
|
|
15
|
+
swagger_css_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui.css",
|
|
16
|
+
swagger_ui_parameters={"defaultModelsExpandDepth": -1},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@docs_router.get("/redoc", include_in_schema=False)
|
|
20
|
+
async def custom_redoc_html():
|
|
21
|
+
return get_redoc_html(
|
|
22
|
+
openapi_url=app.openapi_url,
|
|
23
|
+
title=f"{app.title}",
|
|
24
|
+
redoc_favicon_url="https://static.sytechnology.com/img/sylogopng.png",
|
|
25
|
+
redoc_js_url="https://cdn.bootcdn.net/ajax/libs/redoc/2.1.5/redoc.standalone.js",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app.include_router(docs_router)
|
|
29
|
+
|
|
30
|
+
return app
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from sycommon.health.ping import setup_ping_handler
|
|
2
2
|
from sycommon.middleware.cors import setup_cors_handler
|
|
3
|
+
from sycommon.middleware.docs import setup_docs_handler
|
|
3
4
|
from sycommon.middleware.exception import setup_exception_handler
|
|
4
5
|
from sycommon.middleware.monitor_memory import setup_monitor_memory_middleware
|
|
5
6
|
from sycommon.middleware.mq import setup_mq_middleware
|
|
@@ -36,4 +37,7 @@ class Middleware:
|
|
|
36
37
|
# 添加mq中间件
|
|
37
38
|
# app = setup_mq_middleware(app)
|
|
38
39
|
|
|
40
|
+
# doc
|
|
41
|
+
# app = setup_docs_handler(app)
|
|
42
|
+
|
|
39
43
|
return app
|
sycommon/middleware/traceid.py
CHANGED
|
@@ -78,13 +78,19 @@ def setup_trace_id_handler(app):
|
|
|
78
78
|
response = await call_next(request)
|
|
79
79
|
|
|
80
80
|
content_type = response.headers.get("Content-Type", "")
|
|
81
|
+
|
|
82
|
+
# 处理 SSE 响应 - 关键修复点
|
|
81
83
|
if "text/event-stream" in content_type:
|
|
82
|
-
#
|
|
84
|
+
# 流式响应不能有Content-Length,移除它
|
|
85
|
+
if "Content-Length" in response.headers:
|
|
86
|
+
del response.headers["Content-Length"]
|
|
83
87
|
response.headers["x-traceId-header"] = trace_id
|
|
84
88
|
return response
|
|
85
89
|
|
|
90
|
+
# 处理普通响应
|
|
86
91
|
response_body = b""
|
|
87
92
|
try:
|
|
93
|
+
# 收集所有响应块
|
|
88
94
|
async for chunk in response.body_iterator:
|
|
89
95
|
response_body += chunk
|
|
90
96
|
|
|
@@ -98,14 +104,36 @@ def setup_trace_id_handler(app):
|
|
|
98
104
|
data["traceId"] = trace_id
|
|
99
105
|
new_body = json.dumps(
|
|
100
106
|
data, ensure_ascii=False).encode()
|
|
107
|
+
|
|
108
|
+
# 创建新响应,确保Content-Length正确
|
|
101
109
|
response = Response(
|
|
102
110
|
content=new_body,
|
|
103
111
|
status_code=response.status_code,
|
|
104
|
-
headers=dict(response.headers)
|
|
112
|
+
headers=dict(response.headers),
|
|
113
|
+
media_type=response.media_type
|
|
105
114
|
)
|
|
115
|
+
# 显式设置正确的Content-Length
|
|
106
116
|
response.headers["Content-Length"] = str(len(new_body))
|
|
107
117
|
except json.JSONDecodeError:
|
|
108
|
-
|
|
118
|
+
# 如果不是JSON,恢复原始响应体并更新长度
|
|
119
|
+
response = Response(
|
|
120
|
+
content=response_body,
|
|
121
|
+
status_code=response.status_code,
|
|
122
|
+
headers=dict(response.headers),
|
|
123
|
+
media_type=response.media_type
|
|
124
|
+
)
|
|
125
|
+
response.headers["Content-Length"] = str(
|
|
126
|
+
len(response_body))
|
|
127
|
+
else:
|
|
128
|
+
# 非JSON响应,恢复原始响应体
|
|
129
|
+
response = Response(
|
|
130
|
+
content=response_body,
|
|
131
|
+
status_code=response.status_code,
|
|
132
|
+
headers=dict(response.headers),
|
|
133
|
+
media_type=response.media_type
|
|
134
|
+
)
|
|
135
|
+
response.headers["Content-Length"] = str(
|
|
136
|
+
len(response_body))
|
|
109
137
|
except StopAsyncIteration:
|
|
110
138
|
pass
|
|
111
139
|
|
|
@@ -129,7 +157,7 @@ def setup_trace_id_handler(app):
|
|
|
129
157
|
"uploaded_files": files_info if files_info else None
|
|
130
158
|
}
|
|
131
159
|
error_message_str = json.dumps(error_message, ensure_ascii=False)
|
|
132
|
-
SYLogger.error(error_message_str)
|
|
160
|
+
SYLogger.error(error_message_str)
|
|
133
161
|
raise
|
|
134
162
|
finally:
|
|
135
163
|
# 清理上下文变量,防止泄漏
|
sycommon/models/base_http.py
CHANGED
|
@@ -3,14 +3,15 @@ from pydantic import BaseModel, Field
|
|
|
3
3
|
from fastapi.responses import JSONResponse
|
|
4
4
|
from fastapi import status
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# 支持任意类型的泛型约束
|
|
7
7
|
T = TypeVar('T')
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class BaseResponseModel(BaseModel, Generic[T]):
|
|
11
|
-
"""
|
|
12
|
-
code: int = Field(default=0, description="
|
|
13
|
-
message: str = Field(default=
|
|
11
|
+
"""基础响应模型,支持自定义状态码"""
|
|
12
|
+
code: int = Field(default=0, description="业务响应码,默认0成功,1失败,支持自定义")
|
|
13
|
+
message: str | None = Field(default=None, description="业务响应信息")
|
|
14
|
+
success: bool = Field(default=True, description="请求是否成功")
|
|
14
15
|
data: T | None = Field(default=None, description="业务响应数据,支持任意类型")
|
|
15
16
|
traceId: str | None = Field(default=None, description="请求链路追踪ID")
|
|
16
17
|
|
|
@@ -22,22 +23,22 @@ class BaseResponseModel(BaseModel, Generic[T]):
|
|
|
22
23
|
def build_response_content(
|
|
23
24
|
data: T | Any = None,
|
|
24
25
|
code: int = 0,
|
|
25
|
-
message: str =
|
|
26
|
+
message: str = None
|
|
26
27
|
) -> dict:
|
|
27
28
|
"""
|
|
28
|
-
|
|
29
|
+
构建响应内容字典,自动根据code判断success
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
message: 响应信息
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
响应内容字典,格式为{"code": int, "message": str, "data": Any}
|
|
31
|
+
规则:
|
|
32
|
+
- code为0时success=True(默认成功)
|
|
33
|
+
- 其他任何code值success=False(包括200等自定义状态码)
|
|
37
34
|
"""
|
|
35
|
+
# 成功状态仅当code为0时成立,其他任何code都视为失败
|
|
36
|
+
success = code == 0 or code == 200
|
|
37
|
+
|
|
38
38
|
response = BaseResponseModel(
|
|
39
39
|
code=code,
|
|
40
40
|
message=message,
|
|
41
|
+
success=success,
|
|
41
42
|
data=data
|
|
42
43
|
)
|
|
43
44
|
|
|
@@ -45,6 +46,7 @@ def build_response_content(
|
|
|
45
46
|
return {
|
|
46
47
|
"code": response.code,
|
|
47
48
|
"message": response.message,
|
|
49
|
+
"success": response.success,
|
|
48
50
|
"data": response.data.model_dump()
|
|
49
51
|
}
|
|
50
52
|
else:
|
|
@@ -54,10 +56,10 @@ def build_response_content(
|
|
|
54
56
|
def create_response(
|
|
55
57
|
data: T | Any = None,
|
|
56
58
|
code: int = 0,
|
|
57
|
-
message: str =
|
|
59
|
+
message: str = None,
|
|
58
60
|
status_code: int = status.HTTP_200_OK
|
|
59
61
|
) -> JSONResponse:
|
|
60
|
-
"""
|
|
62
|
+
"""创建完整响应,支持自定义业务状态码"""
|
|
61
63
|
content = build_response_content(data=data, code=code, message=message)
|
|
62
64
|
return JSONResponse(
|
|
63
65
|
content=content,
|
|
@@ -65,18 +67,18 @@ def create_response(
|
|
|
65
67
|
)
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
def success_response(data: T | Any = None,
|
|
69
|
-
"""
|
|
70
|
-
return create_response(data=data,
|
|
70
|
+
def success_response(data: T | Any = None, code: int = 0) -> JSONResponse:
|
|
71
|
+
"""快捷创建成功响应(code=0, success=True)"""
|
|
72
|
+
return create_response(data=data, code=code)
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
def error_response(
|
|
74
|
-
message: str =
|
|
76
|
+
message: str = None,
|
|
75
77
|
code: int = 1,
|
|
76
78
|
data: T | Any = None,
|
|
77
79
|
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
78
80
|
) -> JSONResponse:
|
|
79
|
-
"""
|
|
81
|
+
"""快捷创建错误响应,支持自定义错误码(如200)"""
|
|
80
82
|
return create_response(
|
|
81
83
|
data=data,
|
|
82
84
|
code=code,
|
|
@@ -85,15 +87,15 @@ def error_response(
|
|
|
85
87
|
)
|
|
86
88
|
|
|
87
89
|
|
|
88
|
-
def success_content(data: T | Any = None
|
|
89
|
-
"""
|
|
90
|
-
return build_response_content(data=data
|
|
90
|
+
def success_content(data: T | Any = None) -> dict:
|
|
91
|
+
"""构建成功响应内容字典"""
|
|
92
|
+
return build_response_content(data=data)
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
def error_content(
|
|
94
|
-
message: str =
|
|
96
|
+
message: str = None,
|
|
95
97
|
code: int = 1,
|
|
96
98
|
data: T | Any = None
|
|
97
99
|
) -> dict:
|
|
98
|
-
"""
|
|
100
|
+
"""构建错误响应内容字典,支持自定义错误码"""
|
|
99
101
|
return build_response_content(data=data, code=code, message=message)
|
|
@@ -183,8 +183,14 @@ class RabbitMQClient:
|
|
|
183
183
|
await asyncio.sleep(1)
|
|
184
184
|
return False
|
|
185
185
|
|
|
186
|
-
async def connect(self, force_reconnect: bool = False,
|
|
187
|
-
"""
|
|
186
|
+
async def connect(self, force_reconnect: bool = False, declare_queue: bool = True) -> None:
|
|
187
|
+
"""建立连接并检查/创建资源,新增declare_queue参数控制是否声明队列"""
|
|
188
|
+
# 增加日志确认参数状态
|
|
189
|
+
logging.debug(
|
|
190
|
+
f"connect() 调用 - force_reconnect={force_reconnect}, "
|
|
191
|
+
f"declare_queue={declare_queue}, create_if_not_exists={self.create_if_not_exists}"
|
|
192
|
+
)
|
|
193
|
+
|
|
188
194
|
if self.is_connected and not force_reconnect:
|
|
189
195
|
return
|
|
190
196
|
|
|
@@ -196,7 +202,8 @@ class RabbitMQClient:
|
|
|
196
202
|
f"尝试连接RabbitMQ - 主机: {self.host}:{self.port}, "
|
|
197
203
|
f"虚拟主机: {self.virtualhost}, "
|
|
198
204
|
f"队列: {self.queue_name}, "
|
|
199
|
-
f"
|
|
205
|
+
f"声明队列: {declare_queue}, "
|
|
206
|
+
f"允许创建: {self.create_if_not_exists}"
|
|
200
207
|
)
|
|
201
208
|
|
|
202
209
|
# 重置状态
|
|
@@ -207,13 +214,13 @@ class RabbitMQClient:
|
|
|
207
214
|
retries = 0
|
|
208
215
|
last_exception = None
|
|
209
216
|
|
|
210
|
-
while retries <
|
|
217
|
+
while retries < 3: # 使用固定重试次数
|
|
211
218
|
try:
|
|
212
219
|
# 关闭旧连接
|
|
213
220
|
if self.connection and not self.connection.is_closed:
|
|
214
221
|
await self.connection.close()
|
|
215
222
|
|
|
216
|
-
# 建立新连接
|
|
223
|
+
# 建立新连接
|
|
217
224
|
self.connection = await asyncio.wait_for(
|
|
218
225
|
aio_pika.connect_robust(
|
|
219
226
|
host=self.host,
|
|
@@ -223,7 +230,7 @@ class RabbitMQClient:
|
|
|
223
230
|
virtualhost=self.virtualhost,
|
|
224
231
|
heartbeat=self.heartbeat,
|
|
225
232
|
client_properties={
|
|
226
|
-
"connection_name": self.app_name or "rabbitmq-client"}
|
|
233
|
+
"connection_name": self.app_name or "rabbitmq-client"}
|
|
227
234
|
),
|
|
228
235
|
timeout=self.connection_timeout
|
|
229
236
|
)
|
|
@@ -262,28 +269,29 @@ class RabbitMQClient:
|
|
|
262
269
|
)
|
|
263
270
|
logging.info(f"使用已存在的交换机 '{self.exchange_name}'")
|
|
264
271
|
|
|
265
|
-
# 2. 处理队列
|
|
266
|
-
if self.queue_name:
|
|
272
|
+
# 2. 处理队列 - 只有declare_queue为True时才处理
|
|
273
|
+
if declare_queue and self.queue_name:
|
|
267
274
|
queue_exists = await self._check_queue_exists()
|
|
268
275
|
|
|
269
276
|
if not queue_exists:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
self.queue = await asyncio.wait_for(
|
|
273
|
-
self.channel.declare_queue(
|
|
274
|
-
name=self.queue_name,
|
|
275
|
-
durable=self.durable,
|
|
276
|
-
auto_delete=self.auto_delete,
|
|
277
|
-
exclusive=False,
|
|
278
|
-
passive=False
|
|
279
|
-
),
|
|
280
|
-
timeout=self.rpc_timeout
|
|
281
|
-
)
|
|
282
|
-
self._queue_exists = True
|
|
283
|
-
logging.info(f"已创建队列 '{self.queue_name}'")
|
|
284
|
-
else:
|
|
277
|
+
# 关键检查点:确保有权限创建队列
|
|
278
|
+
if not self.create_if_not_exists:
|
|
285
279
|
raise Exception(
|
|
286
280
|
f"队列 '{self.queue_name}' 不存在且不允许自动创建")
|
|
281
|
+
|
|
282
|
+
# 创建队列
|
|
283
|
+
self.queue = await asyncio.wait_for(
|
|
284
|
+
self.channel.declare_queue(
|
|
285
|
+
name=self.queue_name,
|
|
286
|
+
durable=self.durable,
|
|
287
|
+
auto_delete=self.auto_delete,
|
|
288
|
+
exclusive=False,
|
|
289
|
+
passive=False
|
|
290
|
+
),
|
|
291
|
+
timeout=self.rpc_timeout
|
|
292
|
+
)
|
|
293
|
+
self._queue_exists = True
|
|
294
|
+
logging.info(f"已创建队列 '{self.queue_name}'")
|
|
287
295
|
else:
|
|
288
296
|
# 获取已有队列
|
|
289
297
|
self.queue = await asyncio.wait_for(
|
|
@@ -292,12 +300,18 @@ class RabbitMQClient:
|
|
|
292
300
|
)
|
|
293
301
|
logging.info(f"使用已存在的队列 '{self.queue_name}'")
|
|
294
302
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
# 3. 绑定队列到交换机
|
|
304
|
+
if self.queue and self.exchange:
|
|
305
|
+
bound = await self._bind_queue()
|
|
306
|
+
if not bound:
|
|
307
|
+
raise Exception(
|
|
308
|
+
f"队列 '{self.queue_name}' 绑定到交换机 '{self.exchange_name}' 失败")
|
|
309
|
+
else:
|
|
310
|
+
# 不声明队列时,将队列相关状态设为False
|
|
311
|
+
self.queue = None
|
|
312
|
+
self._queue_exists = False
|
|
313
|
+
self._queue_bound = False
|
|
314
|
+
logging.debug(f"跳过队列 '{self.queue_name}' 的声明和绑定")
|
|
301
315
|
|
|
302
316
|
# 如果之前在消费,重新开始消费
|
|
303
317
|
if self._is_consuming and self.message_handler:
|
|
@@ -308,23 +322,22 @@ class RabbitMQClient:
|
|
|
308
322
|
self._start_keepalive_task()
|
|
309
323
|
|
|
310
324
|
self._update_activity_timestamp()
|
|
311
|
-
logging.info(
|
|
325
|
+
logging.info(
|
|
326
|
+
f"RabbitMQ客户端连接成功 (队列: {self.queue_name}, 声明队列: {declare_queue})")
|
|
312
327
|
return
|
|
313
328
|
|
|
314
329
|
except Exception as e:
|
|
315
330
|
retries += 1
|
|
316
331
|
last_exception = e
|
|
317
332
|
logging.warning(
|
|
318
|
-
f"连接失败({retries}/
|
|
333
|
+
f"连接失败({retries}/3): {str(e)}, create_if_not_exists={self.create_if_not_exists}, 重试中...")
|
|
319
334
|
|
|
320
|
-
if retries <
|
|
335
|
+
if retries < 3:
|
|
321
336
|
await asyncio.sleep(self.reconnection_delay)
|
|
322
337
|
|
|
323
|
-
logging.error(f"最终连接失败: {str(last_exception)}"
|
|
338
|
+
logging.error(f"最终连接失败: {str(last_exception)}")
|
|
324
339
|
raise Exception(
|
|
325
|
-
f"经过{
|
|
326
|
-
f"最后错误: {str(last_exception)}"
|
|
327
|
-
)
|
|
340
|
+
f"经过3次重试后仍无法完成连接和资源初始化。最后错误: {str(last_exception)}")
|
|
328
341
|
|
|
329
342
|
def _start_connection_monitor(self):
|
|
330
343
|
"""启动连接监控任务,检测连接/通道关闭"""
|
|
@@ -534,16 +547,31 @@ class RabbitMQClient:
|
|
|
534
547
|
self.message_handler = handler
|
|
535
548
|
|
|
536
549
|
async def start_consuming(self, timeout: Optional[float] = None) -> str:
|
|
537
|
-
"""开始消费消息并返回consumer_tag
|
|
550
|
+
"""开始消费消息并返回consumer_tag,支持超时控制和队列检查重试"""
|
|
538
551
|
if self._is_consuming:
|
|
539
552
|
logging.debug("已经在消费中,返回现有consumer_tag")
|
|
540
553
|
return self._consumer_tag
|
|
541
554
|
|
|
542
|
-
|
|
543
|
-
|
|
555
|
+
# 增加队列检查和连接确保逻辑
|
|
556
|
+
max_attempts = 5
|
|
557
|
+
attempt = 0
|
|
558
|
+
while attempt < max_attempts:
|
|
559
|
+
if not self.is_connected:
|
|
560
|
+
await self.connect()
|
|
561
|
+
|
|
562
|
+
if self.queue:
|
|
563
|
+
break
|
|
564
|
+
|
|
565
|
+
attempt += 1
|
|
566
|
+
logging.warning(f"队列尚未初始化,等待后重试({attempt}/{max_attempts})")
|
|
567
|
+
await asyncio.sleep(1)
|
|
544
568
|
|
|
545
569
|
if not self.queue:
|
|
546
|
-
|
|
570
|
+
# 最后尝试一次显式连接并声明队列
|
|
571
|
+
logging.warning("最后尝试重新连接并声明队列")
|
|
572
|
+
await self.connect(force_reconnect=True, declare_queue=True)
|
|
573
|
+
if not self.queue:
|
|
574
|
+
raise Exception("队列未初始化,多次尝试后仍无法创建")
|
|
547
575
|
|
|
548
576
|
if not self.message_handler:
|
|
549
577
|
raise Exception("未设置消息处理函数")
|
|
@@ -31,15 +31,22 @@ class RabbitMQService:
|
|
|
31
31
|
_initialized_queues: Dict[str, Dict[str, bool]] = {}
|
|
32
32
|
# 添加异步锁
|
|
33
33
|
_init_lock: Dict[str, asyncio.Lock] = {}
|
|
34
|
+
_has_listeners: bool = False
|
|
35
|
+
_has_senders: bool = False
|
|
34
36
|
|
|
35
37
|
@classmethod
|
|
36
|
-
def init(cls, config: dict) -> Type['RabbitMQService']:
|
|
37
|
-
"""初始化RabbitMQ
|
|
38
|
+
def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
|
|
39
|
+
"""初始化RabbitMQ服务,保存配置和发送器/监听器状态"""
|
|
38
40
|
from sycommon.synacos.nacos_service import NacosService
|
|
39
41
|
# 获取 common 配置
|
|
40
42
|
cls.config = NacosService(config).share_configs.get(
|
|
41
43
|
"mq.yml", {}).get('spring', {}).get('rabbitmq', {})
|
|
42
44
|
cls.config["APP_NAME"] = config.get("Name", "")
|
|
45
|
+
|
|
46
|
+
# 保存发送器和监听器存在状态
|
|
47
|
+
cls._has_listeners = has_listeners
|
|
48
|
+
cls._has_senders = has_senders
|
|
49
|
+
|
|
43
50
|
return cls()
|
|
44
51
|
|
|
45
52
|
@classmethod
|
|
@@ -56,21 +63,35 @@ class RabbitMQService:
|
|
|
56
63
|
|
|
57
64
|
@classmethod
|
|
58
65
|
def create_client(cls, mq_config: dict, queue_name: str, **kwargs):
|
|
59
|
-
"""创建并返回新的RabbitMQClient
|
|
60
|
-
#
|
|
61
|
-
|
|
66
|
+
"""创建并返回新的RabbitMQClient实例,遵循队列创建规则"""
|
|
67
|
+
# 获取当前项目名
|
|
68
|
+
app_name = kwargs.get('app_name', cls.config.get(
|
|
69
|
+
"APP_NAME", "")) if cls.config else kwargs.get('app_name', "")
|
|
62
70
|
|
|
63
71
|
# 确保只在需要时处理一次队列名称
|
|
64
72
|
processed_queue_name = queue_name
|
|
65
73
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
# 通过上下文判断是否为发送器
|
|
75
|
+
# 发送器场景:当没有监听器时
|
|
76
|
+
is_sender = not cls._has_listeners
|
|
77
|
+
|
|
78
|
+
# 核心逻辑:根据组件存在状态决定是否允许创建队列
|
|
79
|
+
# 1. 只有发送器:不允许创建队列
|
|
80
|
+
# 2. 只有监听器:允许创建队列
|
|
81
|
+
# 3. 两者都存在:允许创建队列(由监听器负责)
|
|
82
|
+
create_if_not_exists = cls._has_listeners # 只要有监听器就允许创建
|
|
83
|
+
|
|
84
|
+
# 当需要创建队列且是监听器时,拼接项目名
|
|
85
|
+
if create_if_not_exists and not is_sender and processed_queue_name and app_name:
|
|
70
86
|
if not processed_queue_name.endswith(f".{app_name}"):
|
|
71
87
|
processed_queue_name = f"{processed_queue_name}.{app_name}"
|
|
72
88
|
logging.debug(f"监听器队列名称自动拼接app-name: {processed_queue_name}")
|
|
73
89
|
|
|
90
|
+
logging.debug(
|
|
91
|
+
f"队列创建权限 - 监听器存在: {cls._has_listeners}, 发送器存在: {cls._has_senders}, "
|
|
92
|
+
f"是否发送器: {is_sender}, 允许创建: {create_if_not_exists}, 队列: {processed_queue_name}"
|
|
93
|
+
)
|
|
94
|
+
|
|
74
95
|
return RabbitMQClient(
|
|
75
96
|
host=mq_config.get('host', ""),
|
|
76
97
|
port=mq_config.get('port', 0),
|
|
@@ -80,7 +101,7 @@ class RabbitMQService:
|
|
|
80
101
|
exchange_name=mq_config.get(
|
|
81
102
|
'exchange_name', "system.topic.exchange"),
|
|
82
103
|
exchange_type=kwargs.get('exchange_type', "topic"),
|
|
83
|
-
queue_name=processed_queue_name,
|
|
104
|
+
queue_name=processed_queue_name,
|
|
84
105
|
routing_key=kwargs.get(
|
|
85
106
|
'routing_key', f"{processed_queue_name.split('.')[0]}.#" if processed_queue_name else "#"),
|
|
86
107
|
durable=kwargs.get('durable', True),
|
|
@@ -89,7 +110,7 @@ class RabbitMQService:
|
|
|
89
110
|
create_if_not_exists=create_if_not_exists,
|
|
90
111
|
connection_timeout=kwargs.get('connection_timeout', 10),
|
|
91
112
|
rpc_timeout=kwargs.get('rpc_timeout', 5),
|
|
92
|
-
app_name=
|
|
113
|
+
app_name=app_name
|
|
93
114
|
)
|
|
94
115
|
|
|
95
116
|
@classmethod
|
|
@@ -105,52 +126,80 @@ class RabbitMQService:
|
|
|
105
126
|
async with cls._init_lock[client_name]:
|
|
106
127
|
if client_name in cls.clients:
|
|
107
128
|
client = cls.clients[client_name]
|
|
129
|
+
# 移除is_sender判断,通过上下文推断
|
|
130
|
+
is_sender = not cls._has_listeners or (
|
|
131
|
+
not kwargs.get('create_if_not_exists', True))
|
|
132
|
+
|
|
108
133
|
if client.is_connected:
|
|
109
|
-
|
|
134
|
+
if not is_sender and not client.queue:
|
|
135
|
+
logging.debug(f"客户端 '{client_name}' 存在但队列未初始化,重新连接")
|
|
136
|
+
client.create_if_not_exists = True
|
|
137
|
+
await client.connect(force_reconnect=True, declare_queue=True)
|
|
138
|
+
else:
|
|
139
|
+
logging.debug(f"客户端 '{client_name}' 已存在且连接有效,直接返回")
|
|
110
140
|
return client
|
|
111
141
|
else:
|
|
112
142
|
logging.debug(f"客户端 '{client_name}' 存在但连接已关闭,重新连接")
|
|
113
|
-
|
|
143
|
+
if not is_sender:
|
|
144
|
+
client.create_if_not_exists = True
|
|
145
|
+
await client.connect(declare_queue=not is_sender)
|
|
114
146
|
return client
|
|
115
147
|
|
|
116
148
|
initial_queue_name = kwargs.pop('queue_name', '')
|
|
117
|
-
|
|
149
|
+
# 移除is_sender参数,通过上下文推断
|
|
150
|
+
is_sender = not cls._has_listeners or (
|
|
151
|
+
not kwargs.get('create_if_not_exists', True))
|
|
152
|
+
|
|
153
|
+
# 发送器特殊处理
|
|
154
|
+
if is_sender:
|
|
155
|
+
kwargs['create_if_not_exists'] = False
|
|
118
156
|
|
|
119
|
-
# 检查队列是否已初始化
|
|
120
|
-
if initial_queue_name in cls._initialized_queues:
|
|
121
|
-
logging.debug(f"队列 '{initial_queue_name}' 已初始化过,直接创建客户端")
|
|
122
157
|
client = RabbitMQService.create_client(
|
|
123
158
|
mq_config,
|
|
124
159
|
initial_queue_name,
|
|
125
160
|
app_name=cls.config.get("APP_NAME", ""),
|
|
126
|
-
**kwargs
|
|
161
|
+
**kwargs # 不再传递is_sender参数
|
|
127
162
|
)
|
|
128
|
-
|
|
163
|
+
|
|
164
|
+
await client.connect(declare_queue=False)
|
|
129
165
|
cls.clients[client_name] = client
|
|
130
166
|
return client
|
|
131
167
|
|
|
132
|
-
#
|
|
133
|
-
|
|
168
|
+
# 监听器逻辑
|
|
169
|
+
kwargs['create_if_not_exists'] = True
|
|
134
170
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
if initial_queue_name in cls._initialized_queues:
|
|
172
|
+
logging.debug(f"队列 '{initial_queue_name}' 已初始化过,直接创建客户端")
|
|
173
|
+
client = RabbitMQService.create_client(
|
|
174
|
+
mq_config,
|
|
175
|
+
initial_queue_name,
|
|
176
|
+
# 不再传递is_sender参数
|
|
177
|
+
app_name=cls.config.get("APP_NAME", ""), ** kwargs
|
|
178
|
+
)
|
|
179
|
+
await client.connect(declare_queue=True)
|
|
180
|
+
cls.clients[client_name] = client
|
|
181
|
+
return client
|
|
141
182
|
|
|
142
|
-
# 创建客户端(这里会处理队列名称,添加app-name)
|
|
143
183
|
client = RabbitMQService.create_client(
|
|
144
184
|
mq_config,
|
|
145
185
|
initial_queue_name,
|
|
146
186
|
app_name=cls.config.get("APP_NAME", ""),
|
|
147
|
-
|
|
148
|
-
connection_timeout=mq_config.get('connection_timeout', 15),
|
|
149
|
-
rpc_timeout=mq_config.get('rpc_timeout', 5), ** kwargs
|
|
187
|
+
**kwargs # 不再传递is_sender参数
|
|
150
188
|
)
|
|
151
189
|
|
|
152
|
-
|
|
153
|
-
|
|
190
|
+
client.create_if_not_exists = True
|
|
191
|
+
logging.debug(
|
|
192
|
+
f"监听器客户端创建 - create_if_not_exists={client.create_if_not_exists}")
|
|
193
|
+
|
|
194
|
+
await client.connect(declare_queue=True)
|
|
195
|
+
|
|
196
|
+
if not client.queue:
|
|
197
|
+
logging.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
|
|
198
|
+
client.create_if_not_exists = True
|
|
199
|
+
await client.connect(force_reconnect=True, declare_queue=True)
|
|
200
|
+
if not client.queue:
|
|
201
|
+
raise Exception(f"无法创建队列 '{initial_queue_name}'")
|
|
202
|
+
|
|
154
203
|
final_queue_name = client.queue_name
|
|
155
204
|
|
|
156
205
|
if final_queue_name not in cls._initialized_queues:
|
|
@@ -163,8 +212,11 @@ class RabbitMQService:
|
|
|
163
212
|
return client
|
|
164
213
|
|
|
165
214
|
@classmethod
|
|
166
|
-
async def setup_senders(cls, senders: List[RabbitMQSendConfig]):
|
|
167
|
-
"""设置MQ发送客户端
|
|
215
|
+
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False):
|
|
216
|
+
"""设置MQ发送客户端"""
|
|
217
|
+
cls._has_listeners = has_listeners
|
|
218
|
+
cls._has_senders = True # 明确标记存在发送器
|
|
219
|
+
|
|
168
220
|
async def setup_sender_tasks():
|
|
169
221
|
for idx, sender_config in enumerate(senders):
|
|
170
222
|
try:
|
|
@@ -173,20 +225,23 @@ class RabbitMQService:
|
|
|
173
225
|
|
|
174
226
|
normalized_name = sender_config.queue_name
|
|
175
227
|
app_name = cls.config.get("APP_NAME", "")
|
|
228
|
+
|
|
176
229
|
if app_name and normalized_name.endswith(f".{app_name}"):
|
|
177
230
|
normalized_name = normalized_name[:-
|
|
178
231
|
len(f".{app_name}")]
|
|
232
|
+
logging.debug(
|
|
233
|
+
f"发送器队列名称移除app-name后缀: {normalized_name}")
|
|
179
234
|
|
|
180
235
|
if normalized_name in cls.sender_client_names:
|
|
181
|
-
logging.debug(f"发送客户端 '{normalized_name}'
|
|
236
|
+
logging.debug(f"发送客户端 '{normalized_name}' 已存在,跳过")
|
|
182
237
|
continue
|
|
183
238
|
|
|
184
239
|
if normalized_name in cls.clients:
|
|
185
240
|
client = cls.clients[normalized_name]
|
|
186
241
|
if not client.is_connected:
|
|
187
|
-
await client.connect()
|
|
242
|
+
await client.connect(declare_queue=False)
|
|
188
243
|
else:
|
|
189
|
-
#
|
|
244
|
+
# 移除is_sender参数传递
|
|
190
245
|
client = await cls.setup_rabbitmq(
|
|
191
246
|
cls.config,
|
|
192
247
|
client_name=normalized_name,
|
|
@@ -195,12 +250,12 @@ class RabbitMQService:
|
|
|
195
250
|
auto_delete=sender_config.auto_delete,
|
|
196
251
|
auto_parse_json=sender_config.auto_parse_json,
|
|
197
252
|
queue_name=sender_config.queue_name,
|
|
198
|
-
create_if_not_exists=False #
|
|
253
|
+
create_if_not_exists=False # 仅通过此参数控制
|
|
199
254
|
)
|
|
200
255
|
|
|
201
256
|
if normalized_name not in cls.clients:
|
|
202
257
|
cls.clients[normalized_name] = client
|
|
203
|
-
logging.info(f"
|
|
258
|
+
logging.info(f"发送客户端 '{normalized_name}' 已添加")
|
|
204
259
|
|
|
205
260
|
if normalized_name not in cls.sender_client_names:
|
|
206
261
|
cls.sender_client_names.append(normalized_name)
|
|
@@ -217,8 +272,11 @@ class RabbitMQService:
|
|
|
217
272
|
raise
|
|
218
273
|
|
|
219
274
|
@classmethod
|
|
220
|
-
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig]):
|
|
275
|
+
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False):
|
|
221
276
|
"""设置MQ监听器 - 确保自动创建队列"""
|
|
277
|
+
# 存在监听器,设置标志
|
|
278
|
+
cls._has_listeners = True
|
|
279
|
+
|
|
222
280
|
for listener_config in listeners:
|
|
223
281
|
# 将监听器配置转换为字典并添加到监听器
|
|
224
282
|
# 强制设置create_if_not_exists为True
|
|
@@ -337,19 +395,17 @@ class RabbitMQService:
|
|
|
337
395
|
|
|
338
396
|
@classmethod
|
|
339
397
|
def get_sender(cls, client_name: Optional[str] = None) -> Optional[RabbitMQClient]:
|
|
340
|
-
"""
|
|
398
|
+
"""获取发送客户端(仅返回已注册的客户端)"""
|
|
341
399
|
if not client_name:
|
|
342
400
|
logging.warning("发送器名称不能为空")
|
|
343
401
|
return None
|
|
344
402
|
|
|
345
|
-
#
|
|
403
|
+
# 仅精确匹配已注册的客户端
|
|
346
404
|
if client_name in cls.clients:
|
|
347
405
|
return cls.clients[client_name]
|
|
348
406
|
|
|
349
|
-
# 不允许通过拼接app-name反向查找(避免绕过注册)
|
|
350
407
|
app_name = cls.config.get("APP_NAME", "") if cls.config else ""
|
|
351
408
|
if app_name and not client_name.endswith(f".{app_name}"):
|
|
352
|
-
# 不尝试拼接app-name查找,确保只有注册的名称可被找到
|
|
353
409
|
return None
|
|
354
410
|
|
|
355
411
|
logging.debug(f"发送器 '{client_name}' 不在已注册客户端列表中")
|
|
@@ -359,7 +415,7 @@ class RabbitMQService:
|
|
|
359
415
|
async def send_message(
|
|
360
416
|
cls,
|
|
361
417
|
data: Union[BaseModel, str, Dict[str, Any], None],
|
|
362
|
-
queue_name: Optional[str] = None, **
|
|
418
|
+
queue_name: Optional[str] = None, **kwargs
|
|
363
419
|
) -> None:
|
|
364
420
|
"""发送消息到RabbitMQ"""
|
|
365
421
|
sender = cls.get_sender(queue_name)
|
|
@@ -472,5 +528,6 @@ class RabbitMQService:
|
|
|
472
528
|
cls.clients.clear()
|
|
473
529
|
cls.sender_client_names.clear()
|
|
474
530
|
cls._init_lock.clear()
|
|
531
|
+
cls._has_listeners = False # 重置标志
|
|
475
532
|
|
|
476
533
|
logging.info("RabbitMQ服务已完全关闭")
|
sycommon/services.py
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
from typing import Any, Callable, Dict, List, Tuple, Union, Optional,
|
|
1
|
+
from typing import Any, Callable, Dict, List, Tuple, Union, Optional, AsyncGenerator
|
|
2
2
|
import asyncio
|
|
3
3
|
import logging
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
|
-
from
|
|
6
|
-
from fastapi import FastAPI
|
|
5
|
+
from fastapi import FastAPI, applications
|
|
7
6
|
from pydantic import BaseModel
|
|
8
|
-
import yaml
|
|
9
7
|
from sycommon.config.Config import SingletonMeta
|
|
10
8
|
from sycommon.models.mqlistener_config import RabbitMQListenerConfig
|
|
11
9
|
from sycommon.models.mqsend_config import RabbitMQSendConfig
|
|
12
10
|
from sycommon.rabbitmq.rabbitmq_service import RabbitMQService
|
|
11
|
+
from sycommon.tools.docs import custom_redoc_html, custom_swagger_ui_html
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Services(metaclass=SingletonMeta):
|
|
@@ -18,187 +17,179 @@ class Services(metaclass=SingletonMeta):
|
|
|
18
17
|
_initialized: bool = False
|
|
19
18
|
_registered_senders: List[str] = []
|
|
20
19
|
_mq_tasks: List[asyncio.Task] = []
|
|
21
|
-
_pending_setup: Optional[Callable[..., Awaitable[None]]] = None
|
|
22
20
|
_instance: Optional['Services'] = None
|
|
21
|
+
_app: Optional[FastAPI] = None
|
|
23
22
|
|
|
24
|
-
def __init__(self, config):
|
|
23
|
+
def __init__(self, config: dict, app: FastAPI):
|
|
25
24
|
if not Services._config:
|
|
26
25
|
Services._config = config
|
|
27
26
|
Services._instance = self
|
|
27
|
+
Services._app = app
|
|
28
|
+
self._init_event_loop()
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
30
|
+
def _init_event_loop(self):
|
|
31
|
+
"""初始化事件循环,确保全局只有一个循环实例"""
|
|
32
|
+
if not Services._loop:
|
|
50
33
|
try:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logging.info("Services shutdown completed")
|
|
56
|
-
|
|
57
|
-
return lifespan
|
|
34
|
+
Services._loop = asyncio.get_running_loop()
|
|
35
|
+
except RuntimeError:
|
|
36
|
+
Services._loop = asyncio.new_event_loop()
|
|
37
|
+
asyncio.set_event_loop(Services._loop)
|
|
58
38
|
|
|
59
39
|
@classmethod
|
|
60
|
-
def plugins(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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,
|
|
40
|
+
def plugins(
|
|
41
|
+
cls,
|
|
42
|
+
app: FastAPI,
|
|
43
|
+
config: dict,
|
|
44
|
+
middleware: Optional[Callable[[FastAPI, dict], None]] = None,
|
|
45
|
+
nacos_service: Optional[Callable[[dict], None]] = None,
|
|
46
|
+
logging_service: Optional[Callable[[dict], None]] = None,
|
|
47
|
+
database_service: Optional[Union[
|
|
48
|
+
Tuple[Callable[[dict, str], None], str],
|
|
49
|
+
List[Tuple[Callable[[dict, str], None], str]]
|
|
50
|
+
]] = None,
|
|
95
51
|
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
96
52
|
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
|
|
97
|
-
) ->
|
|
98
|
-
|
|
99
|
-
|
|
53
|
+
) -> FastAPI:
|
|
54
|
+
# 保存应用实例和配置
|
|
55
|
+
cls._app = app
|
|
56
|
+
cls._config = config
|
|
57
|
+
# 设置文档
|
|
58
|
+
applications.get_swagger_ui_html = custom_swagger_ui_html
|
|
59
|
+
applications.get_redoc_html = custom_redoc_html
|
|
60
|
+
|
|
61
|
+
# 立即配置非异步服务(在应用启动前)
|
|
100
62
|
if middleware:
|
|
101
|
-
|
|
63
|
+
middleware(app, config)
|
|
64
|
+
|
|
102
65
|
if nacos_service:
|
|
103
|
-
nacos_service(
|
|
66
|
+
nacos_service(config)
|
|
67
|
+
|
|
104
68
|
if logging_service:
|
|
105
|
-
logging_service(
|
|
69
|
+
logging_service(config)
|
|
70
|
+
|
|
106
71
|
if database_service:
|
|
107
|
-
|
|
72
|
+
cls._setup_database_static(database_service, config)
|
|
73
|
+
|
|
74
|
+
# 创建生命周期管理器
|
|
75
|
+
@asynccontextmanager
|
|
76
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
77
|
+
# 启动阶段 - 执行初始化
|
|
78
|
+
instance = cls(config, app)
|
|
79
|
+
|
|
80
|
+
# 检查是否存在监听器
|
|
81
|
+
has_listeners = bool(
|
|
82
|
+
rabbitmq_listeners and len(rabbitmq_listeners) > 0)
|
|
83
|
+
# 检查是否存在发送器
|
|
84
|
+
has_senders = bool(
|
|
85
|
+
rabbitmq_senders and len(rabbitmq_senders) > 0)
|
|
86
|
+
|
|
87
|
+
# 执行MQ异步初始化,传递是否有监听器的标志
|
|
88
|
+
try:
|
|
89
|
+
await instance._setup_mq_async(
|
|
90
|
+
rabbitmq_listeners=rabbitmq_listeners,
|
|
91
|
+
rabbitmq_senders=rabbitmq_senders,
|
|
92
|
+
has_listeners=has_listeners,
|
|
93
|
+
has_senders=has_senders
|
|
94
|
+
)
|
|
95
|
+
cls._initialized = True
|
|
96
|
+
logging.info("所有服务初始化完成")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logging.error(f"服务初始化失败: {str(e)}", exc_info=True)
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
# 将实例挂载到app上
|
|
102
|
+
app.state.services = instance
|
|
108
103
|
|
|
109
|
-
|
|
104
|
+
yield # 应用运行阶段
|
|
110
105
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
self._setup_senders(rabbitmq_senders)
|
|
115
|
-
if rabbitmq_listeners:
|
|
116
|
-
self._setup_listeners(rabbitmq_listeners)
|
|
106
|
+
# 关闭阶段 - 清理资源
|
|
107
|
+
await cls.shutdown()
|
|
108
|
+
logging.info("所有服务已关闭")
|
|
117
109
|
|
|
118
|
-
#
|
|
119
|
-
|
|
110
|
+
# 设置生命周期
|
|
111
|
+
app.router.lifespan_context = lifespan
|
|
120
112
|
|
|
121
|
-
|
|
113
|
+
return app
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _setup_database_static(database_service, config):
|
|
117
|
+
"""静态方法:设置数据库服务"""
|
|
122
118
|
if isinstance(database_service, tuple):
|
|
123
119
|
db_setup, db_name = database_service
|
|
124
|
-
db_setup(
|
|
120
|
+
db_setup(config, db_name)
|
|
125
121
|
elif isinstance(database_service, list):
|
|
126
122
|
for db_setup, db_name in database_service:
|
|
127
|
-
db_setup(
|
|
123
|
+
db_setup(config, db_name)
|
|
128
124
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
async def _setup_mq_async(
|
|
126
|
+
self,
|
|
127
|
+
rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
|
|
128
|
+
rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None,
|
|
129
|
+
has_listeners: bool = False,
|
|
130
|
+
has_senders: bool = False,
|
|
131
|
+
):
|
|
132
|
+
"""异步设置MQ相关服务,保存发送器和监听器存在状态"""
|
|
133
|
+
# 保存状态到类变量
|
|
134
|
+
Services._has_listeners = has_listeners
|
|
135
|
+
Services._has_senders = has_senders
|
|
136
|
+
|
|
137
|
+
# 初始化RabbitMQ服务,传递状态
|
|
138
|
+
RabbitMQService.init(self._config, has_listeners, has_senders)
|
|
139
|
+
|
|
140
|
+
# 设置发送器,传递是否有监听器的标志
|
|
141
|
+
if rabbitmq_senders:
|
|
142
|
+
await self._setup_senders_async(rabbitmq_senders, has_listeners)
|
|
143
|
+
|
|
144
|
+
# 设置监听器,传递是否有发送器的标志
|
|
145
|
+
if rabbitmq_listeners:
|
|
146
|
+
await self._setup_listeners_async(rabbitmq_listeners, has_senders)
|
|
147
|
+
|
|
148
|
+
async def _setup_senders_async(self, rabbitmq_senders, has_listeners: bool):
|
|
135
149
|
Services._registered_senders = [
|
|
136
150
|
sender.queue_name for sender in rabbitmq_senders]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
RabbitMQService.setup_senders, rabbitmq_senders)
|
|
141
|
-
)
|
|
142
|
-
self._mq_tasks.append(task)
|
|
151
|
+
|
|
152
|
+
# 将是否有监听器的信息传递给RabbitMQService
|
|
153
|
+
await RabbitMQService.setup_senders(rabbitmq_senders, has_listeners)
|
|
143
154
|
logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
|
|
144
155
|
|
|
145
|
-
def
|
|
146
|
-
|
|
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)
|
|
156
|
+
async def _setup_listeners_async(self, rabbitmq_listeners, has_senders: bool):
|
|
157
|
+
await RabbitMQService.setup_listeners(rabbitmq_listeners, has_senders)
|
|
159
158
|
|
|
160
159
|
@classmethod
|
|
161
160
|
async def send_message(
|
|
162
161
|
cls,
|
|
163
162
|
queue_name: str,
|
|
164
163
|
data: Union[str, Dict[str, Any], BaseModel, None],
|
|
165
|
-
max_retries: int = 3,
|
|
166
|
-
retry_delay: float = 1.0,
|
|
167
|
-
**kwargs
|
|
164
|
+
max_retries: int = 3,
|
|
165
|
+
retry_delay: float = 1.0, ** kwargs
|
|
168
166
|
) -> None:
|
|
169
|
-
"""
|
|
167
|
+
"""发送消息,添加重试机制"""
|
|
170
168
|
if not cls._initialized or not cls._loop:
|
|
171
169
|
logging.error("Services not properly initialized!")
|
|
172
170
|
raise ValueError("服务未正确初始化")
|
|
173
171
|
|
|
174
|
-
# 重试逻辑
|
|
175
172
|
for attempt in range(max_retries):
|
|
176
173
|
try:
|
|
177
|
-
# 检查发送器是否已注册
|
|
178
174
|
if queue_name not in cls._registered_senders:
|
|
179
|
-
# 可能是初始化尚未完成,尝试刷新注册列表
|
|
180
175
|
cls._registered_senders = RabbitMQService.sender_client_names
|
|
181
176
|
if queue_name not in cls._registered_senders:
|
|
182
177
|
raise ValueError(f"发送器 {queue_name} 未注册")
|
|
183
178
|
|
|
184
|
-
# 获取发送器
|
|
185
179
|
sender = RabbitMQService.get_sender(queue_name)
|
|
186
180
|
if not sender:
|
|
187
181
|
raise ValueError(f"发送器 '{queue_name}' 不存在")
|
|
188
182
|
|
|
189
|
-
|
|
190
|
-
await RabbitMQService.send_message(data, queue_name, ** kwargs)
|
|
183
|
+
await RabbitMQService.send_message(data, queue_name, **kwargs)
|
|
191
184
|
logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
|
|
192
|
-
return
|
|
185
|
+
return
|
|
193
186
|
|
|
194
187
|
except Exception as e:
|
|
195
|
-
# 最后一次尝试失败则抛出异常
|
|
196
188
|
if attempt == max_retries - 1:
|
|
197
189
|
logging.error(
|
|
198
190
|
f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
|
|
199
191
|
raise
|
|
200
192
|
|
|
201
|
-
# 非最后一次尝试,记录警告并等待重试
|
|
202
193
|
logging.warning(
|
|
203
194
|
f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
|
|
204
195
|
f"{retry_delay}秒后重试..."
|
|
@@ -207,6 +198,8 @@ class Services(metaclass=SingletonMeta):
|
|
|
207
198
|
|
|
208
199
|
@staticmethod
|
|
209
200
|
async def shutdown():
|
|
201
|
+
"""关闭所有服务"""
|
|
202
|
+
# 取消所有MQ任务
|
|
210
203
|
for task in Services._mq_tasks:
|
|
211
204
|
if not task.done():
|
|
212
205
|
task.cancel()
|
|
@@ -214,4 +207,6 @@ class Services(metaclass=SingletonMeta):
|
|
|
214
207
|
await task
|
|
215
208
|
except asyncio.CancelledError:
|
|
216
209
|
pass
|
|
210
|
+
|
|
211
|
+
# 关闭RabbitMQ服务
|
|
217
212
|
await RabbitMQService.shutdown()
|
sycommon/tools/docs.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html, swagger_ui_default_parameters
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def custom_swagger_ui_html(*args, **kwargs):
|
|
5
|
+
custom_params = {
|
|
6
|
+
"dom_id": "#swagger-ui",
|
|
7
|
+
"layout": "BaseLayout",
|
|
8
|
+
"deepLinking": True,
|
|
9
|
+
"showExtensions": True,
|
|
10
|
+
"showCommonExtensionsExtensions": True,
|
|
11
|
+
"defaultModelsExpandDepth": -1,
|
|
12
|
+
"persistAuthorization": True,
|
|
13
|
+
"displayRequestDuration": True
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# 初始化合并参数为默认参数的副本
|
|
17
|
+
merged_params = swagger_ui_default_parameters.copy()
|
|
18
|
+
|
|
19
|
+
# 安全地合并kwargs中的参数(处理可能为None的情况)
|
|
20
|
+
if "swagger_ui_parameters" in kwargs and kwargs["swagger_ui_parameters"] is not None:
|
|
21
|
+
merged_params.update(kwargs["swagger_ui_parameters"])
|
|
22
|
+
|
|
23
|
+
# 最后应用自定义参数
|
|
24
|
+
merged_params.update(custom_params)
|
|
25
|
+
kwargs["swagger_ui_parameters"] = merged_params
|
|
26
|
+
|
|
27
|
+
return get_swagger_ui_html(
|
|
28
|
+
*args, ** kwargs,
|
|
29
|
+
swagger_favicon_url="https://static.sytechnology.com/img/sylogopng.png",
|
|
30
|
+
swagger_js_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui-bundle.js",
|
|
31
|
+
swagger_css_url="https://cdn.bootcdn.net/ajax/libs/swagger-ui/5.27.1/swagger-ui.css",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def custom_redoc_html(*args, **kwargs):
|
|
36
|
+
return get_redoc_html(
|
|
37
|
+
*args,
|
|
38
|
+
**kwargs,
|
|
39
|
+
redoc_favicon_url='https://static.sytechnology.com/img/sylogopng.png',
|
|
40
|
+
# redoc_js_url="https://cdn.jsdelivr.net/npm/@stardustai/redoc@2.0.0-rc.66/bundles/redoc.browser.lib.min.js",
|
|
41
|
+
with_google_fonts=False,
|
|
42
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sycommon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
sycommon/services.py,sha256=
|
|
2
|
+
sycommon/services.py,sha256=_XgQF72-NisNV2bQJcHPWSIDd_RG-g8RmGN_QxQ8noE,8104
|
|
3
3
|
sycommon/config/Config.py,sha256=9yO5b8WfvEDvkyrGrlwrLFasgh_-MjcEvGF20Gz5Xo4,3041
|
|
4
4
|
sycommon/config/DatabaseConfig.py,sha256=ILiUuYT9_xJZE2W-RYuC3JCt_YLKc1sbH13-MHIOPhg,804
|
|
5
5
|
sycommon/config/EmbeddingConfig.py,sha256=gPKwiDYbeu1GpdIZXMmgqM7JqBIzCXi0yYuGRLZooMI,362
|
|
@@ -18,28 +18,30 @@ sycommon/logging/logger_wrapper.py,sha256=TiHsrIIHiQMzXgXK12-0KIpU9GhwQJOoHslakz
|
|
|
18
18
|
sycommon/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
sycommon/middleware/context.py,sha256=_5ghpv4u_yAvjkgM-XDx8OnO-YI1XtntHrXsHJHZkwo,88
|
|
20
20
|
sycommon/middleware/cors.py,sha256=bGwDPI5Bfr0yH0hM-K7wK8mur5YqTy_XVZqlsdY_WdU,301
|
|
21
|
+
sycommon/middleware/docs.py,sha256=bVdDBIHXGVBv562MQLSroa1DgHoObxR9gFzv71PIejg,1187
|
|
21
22
|
sycommon/middleware/exception.py,sha256=Bk8IchNND1wg6tUX9hf4xWeEJhvA-E_zE9ysBpRZrqE,3010
|
|
22
|
-
sycommon/middleware/middleware.py,sha256=
|
|
23
|
+
sycommon/middleware/middleware.py,sha256=KP7oM6rBowzF89wxBAUWArlC8GklrroP--gm4DtyAuo,1331
|
|
23
24
|
sycommon/middleware/monitor_memory.py,sha256=pYRK-wRuDd6enSg9Pf8tQxPdYQS6S0AyjyXeKFRLKEs,628
|
|
24
25
|
sycommon/middleware/mq.py,sha256=4wBqiT5wJGcrfjk2GSr0_U3TStBxoNpHTzcRxVlMEHE,183
|
|
25
26
|
sycommon/middleware/timeout.py,sha256=fImlAPLm4Oa8N9goXtT_0os1GZPCi9F92OgXU81DgDU,656
|
|
26
|
-
sycommon/middleware/traceid.py,sha256=
|
|
27
|
+
sycommon/middleware/traceid.py,sha256=oGTJ2jtdea_3NgaAwXLpUug5dGUYRQeM4r1n2icuvC8,6839
|
|
27
28
|
sycommon/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
sycommon/models/base_http.py,sha256=
|
|
29
|
+
sycommon/models/base_http.py,sha256=hcebQSPlGtx0GrLNK02VTDpviCBILaMrWijmlKEQu3I,3013
|
|
29
30
|
sycommon/models/log.py,sha256=rZpj6VkDRxK3B6H7XSeWdYZshU8F0Sks8bq1p6pPlDw,500
|
|
30
31
|
sycommon/models/mqlistener_config.py,sha256=PPwhAVJ2AWvVAvNox_1t0fuBKTyRH3Ui9cuuU-q7Byo,1590
|
|
31
32
|
sycommon/models/mqmsg_model.py,sha256=cxn0M5b0utQK6crMYmL-1waeGYHvK3AlGaRy23clqTE,277
|
|
32
33
|
sycommon/models/mqsend_config.py,sha256=NQX9dc8PpuquMG36GCVhJe8omAW1KVXXqr6lSRU6D7I,268
|
|
33
34
|
sycommon/models/sso_user.py,sha256=i1WAN6k5sPcPApQEdtjpWDy7VrzWLpOrOQewGLGoGIw,2702
|
|
34
|
-
sycommon/rabbitmq/rabbitmq_client.py,sha256=
|
|
35
|
-
sycommon/rabbitmq/rabbitmq_service.py,sha256=
|
|
35
|
+
sycommon/rabbitmq/rabbitmq_client.py,sha256=QLsn_jqWtBihmBXgrFRZx9S5AhtnvWIuolkfOHno0Wc,30782
|
|
36
|
+
sycommon/rabbitmq/rabbitmq_service.py,sha256=Cl3zWa1PVv3kqpYWKtkP-SomXUSgVNDqzkhE8PuvCR0,22274
|
|
36
37
|
sycommon/synacos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
38
|
sycommon/synacos/feign.py,sha256=ghbI2_6g1C5P0EcU8y2DeDZ9E7Mv0T1iHwptLX1U6NA,12075
|
|
38
39
|
sycommon/synacos/nacos_service.py,sha256=8l2qaVg5GQSTybGrcdmg0B5jRNAzbOPM3mjQ6T04A2I,30816
|
|
39
40
|
sycommon/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
+
sycommon/tools/docs.py,sha256=OPj2ETheuWjXLyaXtaZPbwmJKfJaYXV5s4XMVAUNrms,1607
|
|
40
42
|
sycommon/tools/snowflake.py,sha256=rc-VUjBMMpdAvbnHroVwfVt1xzApJbTCthUy9mglAuw,237
|
|
41
43
|
sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
|
|
42
|
-
sycommon_python_lib-0.1.
|
|
43
|
-
sycommon_python_lib-0.1.
|
|
44
|
-
sycommon_python_lib-0.1.
|
|
45
|
-
sycommon_python_lib-0.1.
|
|
44
|
+
sycommon_python_lib-0.1.10.dist-info/METADATA,sha256=UteBh6HBuWlcC-6wGj2ro9zGDM0f1O5HM39o6yi2jMM,7005
|
|
45
|
+
sycommon_python_lib-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
46
|
+
sycommon_python_lib-0.1.10.dist-info/top_level.txt,sha256=qb-vRf3lrmIaCLrGZxsv6k8Q8l3_lGuAeFtwjh0wYCQ,9
|
|
47
|
+
sycommon_python_lib-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|