aury-boot 0.0.2__py3-none-any.whl → 0.0.4__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.
- aury/boot/__init__.py +66 -0
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +120 -0
- aury/boot/application/app/__init__.py +39 -0
- aury/boot/application/app/base.py +511 -0
- aury/boot/application/app/components.py +434 -0
- aury/boot/application/app/middlewares.py +101 -0
- aury/boot/application/config/__init__.py +44 -0
- aury/boot/application/config/settings.py +663 -0
- aury/boot/application/constants/__init__.py +19 -0
- aury/boot/application/constants/components.py +50 -0
- aury/boot/application/constants/scheduler.py +28 -0
- aury/boot/application/constants/service.py +29 -0
- aury/boot/application/errors/__init__.py +55 -0
- aury/boot/application/errors/chain.py +80 -0
- aury/boot/application/errors/codes.py +67 -0
- aury/boot/application/errors/exceptions.py +238 -0
- aury/boot/application/errors/handlers.py +320 -0
- aury/boot/application/errors/response.py +120 -0
- aury/boot/application/interfaces/__init__.py +76 -0
- aury/boot/application/interfaces/egress.py +224 -0
- aury/boot/application/interfaces/ingress.py +98 -0
- aury/boot/application/middleware/__init__.py +22 -0
- aury/boot/application/middleware/logging.py +451 -0
- aury/boot/application/migrations/__init__.py +13 -0
- aury/boot/application/migrations/manager.py +685 -0
- aury/boot/application/migrations/setup.py +237 -0
- aury/boot/application/rpc/__init__.py +63 -0
- aury/boot/application/rpc/base.py +108 -0
- aury/boot/application/rpc/client.py +294 -0
- aury/boot/application/rpc/discovery.py +218 -0
- aury/boot/application/scheduler/__init__.py +13 -0
- aury/boot/application/scheduler/runner.py +123 -0
- aury/boot/application/server/__init__.py +296 -0
- aury/boot/commands/__init__.py +30 -0
- aury/boot/commands/add.py +76 -0
- aury/boot/commands/app.py +105 -0
- aury/boot/commands/config.py +177 -0
- aury/boot/commands/docker.py +367 -0
- aury/boot/commands/docs.py +284 -0
- aury/boot/commands/generate.py +1277 -0
- aury/boot/commands/init.py +892 -0
- aury/boot/commands/migrate/__init__.py +37 -0
- aury/boot/commands/migrate/app.py +54 -0
- aury/boot/commands/migrate/commands.py +303 -0
- aury/boot/commands/scheduler.py +124 -0
- aury/boot/commands/server/__init__.py +21 -0
- aury/boot/commands/server/app.py +541 -0
- aury/boot/commands/templates/generate/api.py.tpl +105 -0
- aury/boot/commands/templates/generate/model.py.tpl +17 -0
- aury/boot/commands/templates/generate/repository.py.tpl +19 -0
- aury/boot/commands/templates/generate/schema.py.tpl +29 -0
- aury/boot/commands/templates/generate/service.py.tpl +48 -0
- aury/boot/commands/templates/project/CLI.md.tpl +92 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +1397 -0
- aury/boot/commands/templates/project/README.md.tpl +111 -0
- aury/boot/commands/templates/project/admin_console_init.py.tpl +50 -0
- aury/boot/commands/templates/project/config.py.tpl +30 -0
- aury/boot/commands/templates/project/conftest.py.tpl +26 -0
- aury/boot/commands/templates/project/env.example.tpl +213 -0
- aury/boot/commands/templates/project/gitignore.tpl +128 -0
- aury/boot/commands/templates/project/main.py.tpl +41 -0
- aury/boot/commands/templates/project/modules/api.py.tpl +19 -0
- aury/boot/commands/templates/project/modules/exceptions.py.tpl +84 -0
- aury/boot/commands/templates/project/modules/schedules.py.tpl +18 -0
- aury/boot/commands/templates/project/modules/tasks.py.tpl +20 -0
- aury/boot/commands/worker.py +143 -0
- aury/boot/common/__init__.py +35 -0
- aury/boot/common/exceptions/__init__.py +114 -0
- aury/boot/common/i18n/__init__.py +16 -0
- aury/boot/common/i18n/translator.py +272 -0
- aury/boot/common/logging/__init__.py +716 -0
- aury/boot/contrib/__init__.py +10 -0
- aury/boot/contrib/admin_console/__init__.py +18 -0
- aury/boot/contrib/admin_console/auth.py +137 -0
- aury/boot/contrib/admin_console/discovery.py +69 -0
- aury/boot/contrib/admin_console/install.py +172 -0
- aury/boot/contrib/admin_console/utils.py +44 -0
- aury/boot/domain/__init__.py +79 -0
- aury/boot/domain/exceptions/__init__.py +132 -0
- aury/boot/domain/models/__init__.py +51 -0
- aury/boot/domain/models/base.py +69 -0
- aury/boot/domain/models/mixins.py +135 -0
- aury/boot/domain/models/models.py +96 -0
- aury/boot/domain/pagination/__init__.py +279 -0
- aury/boot/domain/repository/__init__.py +23 -0
- aury/boot/domain/repository/impl.py +423 -0
- aury/boot/domain/repository/interceptors.py +47 -0
- aury/boot/domain/repository/interface.py +106 -0
- aury/boot/domain/repository/query_builder.py +348 -0
- aury/boot/domain/service/__init__.py +11 -0
- aury/boot/domain/service/base.py +73 -0
- aury/boot/domain/transaction/__init__.py +404 -0
- aury/boot/infrastructure/__init__.py +104 -0
- aury/boot/infrastructure/cache/__init__.py +31 -0
- aury/boot/infrastructure/cache/backends.py +348 -0
- aury/boot/infrastructure/cache/base.py +68 -0
- aury/boot/infrastructure/cache/exceptions.py +37 -0
- aury/boot/infrastructure/cache/factory.py +94 -0
- aury/boot/infrastructure/cache/manager.py +274 -0
- aury/boot/infrastructure/database/__init__.py +39 -0
- aury/boot/infrastructure/database/config.py +71 -0
- aury/boot/infrastructure/database/exceptions.py +44 -0
- aury/boot/infrastructure/database/manager.py +317 -0
- aury/boot/infrastructure/database/query_tools/__init__.py +164 -0
- aury/boot/infrastructure/database/strategies/__init__.py +198 -0
- aury/boot/infrastructure/di/__init__.py +15 -0
- aury/boot/infrastructure/di/container.py +393 -0
- aury/boot/infrastructure/events/__init__.py +33 -0
- aury/boot/infrastructure/events/bus.py +362 -0
- aury/boot/infrastructure/events/config.py +52 -0
- aury/boot/infrastructure/events/consumer.py +134 -0
- aury/boot/infrastructure/events/middleware.py +51 -0
- aury/boot/infrastructure/events/models.py +63 -0
- aury/boot/infrastructure/monitoring/__init__.py +529 -0
- aury/boot/infrastructure/scheduler/__init__.py +19 -0
- aury/boot/infrastructure/scheduler/exceptions.py +37 -0
- aury/boot/infrastructure/scheduler/manager.py +478 -0
- aury/boot/infrastructure/storage/__init__.py +38 -0
- aury/boot/infrastructure/storage/base.py +164 -0
- aury/boot/infrastructure/storage/exceptions.py +37 -0
- aury/boot/infrastructure/storage/factory.py +88 -0
- aury/boot/infrastructure/tasks/__init__.py +24 -0
- aury/boot/infrastructure/tasks/config.py +45 -0
- aury/boot/infrastructure/tasks/constants.py +37 -0
- aury/boot/infrastructure/tasks/exceptions.py +37 -0
- aury/boot/infrastructure/tasks/manager.py +490 -0
- aury/boot/testing/__init__.py +24 -0
- aury/boot/testing/base.py +122 -0
- aury/boot/testing/client.py +163 -0
- aury/boot/testing/factory.py +154 -0
- aury/boot/toolkit/__init__.py +21 -0
- aury/boot/toolkit/http/__init__.py +367 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/METADATA +3 -2
- aury_boot-0.0.4.dist-info/RECORD +137 -0
- aury_boot-0.0.2.dist-info/RECORD +0 -5
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""应用框架基类。
|
|
2
|
+
|
|
3
|
+
提供 FoundationApp、Middleware 和 Component 基类。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from contextlib import asynccontextmanager
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any, ClassVar
|
|
13
|
+
|
|
14
|
+
from fastapi import FastAPI, Request, status
|
|
15
|
+
from fastapi.responses import JSONResponse
|
|
16
|
+
from starlette.middleware import Middleware as StarletteMiddleware
|
|
17
|
+
|
|
18
|
+
from aury.boot.application.config import BaseConfig
|
|
19
|
+
from aury.boot.application.errors import global_exception_handler
|
|
20
|
+
from aury.boot.application.interfaces.egress import SuccessResponse
|
|
21
|
+
from aury.boot.common.logging import logger, register_log_sink, setup_logging
|
|
22
|
+
from aury.boot.infrastructure.cache import CacheManager
|
|
23
|
+
from aury.boot.infrastructure.database import DatabaseManager
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Middleware(ABC):
|
|
27
|
+
"""HTTP 中间件基类。
|
|
28
|
+
|
|
29
|
+
用于处理 HTTP 请求的拦截和处理,如 CORS、请求日志等。
|
|
30
|
+
中间件在应用构造阶段同步注册,在 HTTP 请求处理时执行。
|
|
31
|
+
|
|
32
|
+
生命周期:
|
|
33
|
+
1. ``build()`` - 在应用构造阶段(lifespan 之前)同步调用。
|
|
34
|
+
返回 Starlette Middleware 实例,由框架统一注册。
|
|
35
|
+
|
|
36
|
+
设计原则:
|
|
37
|
+
- 专注 HTTP 请求处理
|
|
38
|
+
- 同步注册(必须在 lifespan 之前)
|
|
39
|
+
- 按 order 排序执行
|
|
40
|
+
|
|
41
|
+
使用示例:
|
|
42
|
+
class MyMiddleware(Middleware):
|
|
43
|
+
name = "my_middleware"
|
|
44
|
+
enabled = True
|
|
45
|
+
order = 10 # 数字小的先执行
|
|
46
|
+
|
|
47
|
+
def build(self, config: BaseConfig) -> StarletteMiddleware:
|
|
48
|
+
return StarletteMiddleware(SomeMiddlewareClass, **options)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
name: str = "middleware"
|
|
52
|
+
enabled: bool = True
|
|
53
|
+
order: int = 0 # 执行顺序,数字小的先执行
|
|
54
|
+
|
|
55
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
56
|
+
"""是否可以启用此中间件。
|
|
57
|
+
|
|
58
|
+
子类可以重写此方法以实现条件化启用。
|
|
59
|
+
默认检查 enabled 属性。
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
config: 应用配置
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
是否启用
|
|
66
|
+
"""
|
|
67
|
+
return self.enabled
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def build(self, config: BaseConfig) -> StarletteMiddleware:
|
|
71
|
+
"""构建中间件实例。
|
|
72
|
+
|
|
73
|
+
返回 Starlette Middleware 实例,由框架在构造 FastAPI 时统一注册。
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
config: 应用配置
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Starlette Middleware 实例
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Component(ABC):
|
|
85
|
+
"""基础设施组件基类。
|
|
86
|
+
|
|
87
|
+
用于管理基础设施的生命周期,如数据库、缓存、任务队列等。
|
|
88
|
+
组件在应用启动时异步初始化,在应用关闭时异步清理。
|
|
89
|
+
|
|
90
|
+
生命周期:
|
|
91
|
+
1. ``setup()`` - 在应用启动时(lifespan 开始阶段)异步调用。
|
|
92
|
+
2. ``teardown()`` - 在应用关闭时(lifespan 结束阶段)异步调用。
|
|
93
|
+
|
|
94
|
+
使用示例:
|
|
95
|
+
class MyService(Component):
|
|
96
|
+
name = "my_service"
|
|
97
|
+
enabled = True
|
|
98
|
+
depends_on = ["database", "cache"]
|
|
99
|
+
|
|
100
|
+
async def setup(self, app: FoundationApp, config: BaseConfig):
|
|
101
|
+
# 异步初始化逻辑
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
async def teardown(self, app: FoundationApp):
|
|
105
|
+
# 清理逻辑
|
|
106
|
+
pass
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
name: str = "component"
|
|
110
|
+
enabled: bool = True
|
|
111
|
+
depends_on: ClassVar[list[str]] = []
|
|
112
|
+
|
|
113
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
114
|
+
"""是否可以启用此组件。
|
|
115
|
+
|
|
116
|
+
子类可以重写此方法以实现条件化启用。
|
|
117
|
+
默认检查 enabled 属性。
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
config: 应用配置
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
是否启用
|
|
124
|
+
"""
|
|
125
|
+
return self.enabled
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
129
|
+
"""组件启动时调用(异步,在 lifespan 开始阶段)。
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
app: 应用实例
|
|
133
|
+
config: 应用配置
|
|
134
|
+
"""
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
@abstractmethod
|
|
138
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
139
|
+
"""组件关闭时调用(异步,在 lifespan 结束阶段)。
|
|
140
|
+
|
|
141
|
+
用于清理资源。
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
app: 应用实例
|
|
145
|
+
"""
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class FoundationApp(FastAPI):
|
|
150
|
+
"""优雅、抽象、可扩展的应用框架。
|
|
151
|
+
|
|
152
|
+
设计特点:
|
|
153
|
+
- 分离:中间件和组件分开管理,职责清晰
|
|
154
|
+
- 可扩展:只需改类属性,即可添加/移除/替换功能
|
|
155
|
+
- 不受限:无固定的初始化步骤,组件按依赖关系动态排序
|
|
156
|
+
|
|
157
|
+
中间件(Middleware):
|
|
158
|
+
处理 HTTP 请求拦截,如 CORS、请求日志等。
|
|
159
|
+
在应用构造阶段同步注册,按 order 排序执行。
|
|
160
|
+
|
|
161
|
+
组件(Component):
|
|
162
|
+
管理基础设施生命周期,如数据库、缓存、任务队列等。
|
|
163
|
+
在应用启动时异步初始化,按依赖关系拓扑排序。
|
|
164
|
+
|
|
165
|
+
默认中间件(可覆盖):
|
|
166
|
+
- RequestLoggingMiddleware(请求日志)
|
|
167
|
+
- CORSMiddleware(跨域处理)
|
|
168
|
+
|
|
169
|
+
默认组件(可覆盖):
|
|
170
|
+
- DatabaseComponent(数据库)
|
|
171
|
+
- CacheComponent(缓存)
|
|
172
|
+
- TaskComponent(任务队列)
|
|
173
|
+
- SchedulerComponent(调度器)
|
|
174
|
+
- MigrationComponent(数据库迁移)
|
|
175
|
+
|
|
176
|
+
使用示例:
|
|
177
|
+
# 基础应用
|
|
178
|
+
app = FoundationApp()
|
|
179
|
+
|
|
180
|
+
# 自定义应用
|
|
181
|
+
class MyApp(FoundationApp):
|
|
182
|
+
middlewares = [
|
|
183
|
+
RequestLoggingMiddleware,
|
|
184
|
+
CORSMiddleware,
|
|
185
|
+
]
|
|
186
|
+
components = [
|
|
187
|
+
DatabaseComponent,
|
|
188
|
+
CacheComponent,
|
|
189
|
+
MyCustomComponent,
|
|
190
|
+
]
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# 默认中间件列表(子类可以覆盖)
|
|
194
|
+
middlewares: ClassVar[list[type[Middleware] | Middleware]] = []
|
|
195
|
+
|
|
196
|
+
# 默认组件列表(子类可以覆盖)
|
|
197
|
+
components: ClassVar[list[type[Component] | Component]] = []
|
|
198
|
+
|
|
199
|
+
def __init__(
|
|
200
|
+
self,
|
|
201
|
+
config: BaseConfig | None = None,
|
|
202
|
+
*,
|
|
203
|
+
title: str = "Aury Service",
|
|
204
|
+
version: str = "1.0.0",
|
|
205
|
+
description: str | None = None,
|
|
206
|
+
**kwargs: Any,
|
|
207
|
+
) -> None:
|
|
208
|
+
"""初始化应用。
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
config: 应用配置(可选,默认从环境变量加载)
|
|
212
|
+
title: 应用标题
|
|
213
|
+
version: 应用版本
|
|
214
|
+
description: 应用描述
|
|
215
|
+
**kwargs: 传递给 FastAPI 的其他参数
|
|
216
|
+
"""
|
|
217
|
+
# 加载配置
|
|
218
|
+
if config is None:
|
|
219
|
+
config = BaseConfig()
|
|
220
|
+
self._config = config
|
|
221
|
+
|
|
222
|
+
# 记录调用者模块(用于自动发现 schedules 等模块)
|
|
223
|
+
frame = sys._getframe(1)
|
|
224
|
+
self._caller_module = frame.f_globals.get("__name__", "__main__")
|
|
225
|
+
|
|
226
|
+
# 初始化日志(必须在其他操作之前)
|
|
227
|
+
setup_logging(
|
|
228
|
+
log_level=config.log.level,
|
|
229
|
+
log_dir=config.log.dir,
|
|
230
|
+
service_type=config.service.service_type,
|
|
231
|
+
rotation_time=config.log.rotation_time,
|
|
232
|
+
retention_days=config.log.retention_days,
|
|
233
|
+
enable_file_rotation=config.log.enable_file_rotation,
|
|
234
|
+
enable_console=config.log.enable_console,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# 注册 access 日志(HTTP 请求日志)
|
|
238
|
+
register_log_sink("access", filter_key="access")
|
|
239
|
+
|
|
240
|
+
# 初始化中间件和组件管理
|
|
241
|
+
self._middlewares: dict[str, Middleware] = {}
|
|
242
|
+
self._components: dict[str, Component] = {}
|
|
243
|
+
self._lifecycle_listeners: dict[str, list[Callable]] = {}
|
|
244
|
+
|
|
245
|
+
# 创建生命周期管理器
|
|
246
|
+
@asynccontextmanager
|
|
247
|
+
async def lifespan(app: FastAPI):
|
|
248
|
+
"""应用生命周期管理。"""
|
|
249
|
+
# 启动
|
|
250
|
+
await self._on_startup()
|
|
251
|
+
yield
|
|
252
|
+
# 关闭
|
|
253
|
+
await self._on_shutdown()
|
|
254
|
+
|
|
255
|
+
# 收集中间件和组件实例并过滤
|
|
256
|
+
self._collect_middlewares()
|
|
257
|
+
self._collect_components()
|
|
258
|
+
|
|
259
|
+
# 构建中间件实例列表,传给 FastAPI
|
|
260
|
+
middleware_instances = self._build_middlewares()
|
|
261
|
+
|
|
262
|
+
# 调用父类构造函数
|
|
263
|
+
super().__init__(
|
|
264
|
+
title=title,
|
|
265
|
+
version=version,
|
|
266
|
+
description=description,
|
|
267
|
+
lifespan=lifespan,
|
|
268
|
+
middleware=middleware_instances,
|
|
269
|
+
**kwargs,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# 异常处理
|
|
273
|
+
self.add_exception_handler(Exception, global_exception_handler)
|
|
274
|
+
|
|
275
|
+
# 设置路由
|
|
276
|
+
self.setup_routes()
|
|
277
|
+
|
|
278
|
+
def _collect_middlewares(self) -> None:
|
|
279
|
+
"""收集并实例化所有中间件。
|
|
280
|
+
|
|
281
|
+
这一步在 super().__init__() 之前执行,只做实例化和过滤,
|
|
282
|
+
不调用任何需要 app 已完成构造的方法。
|
|
283
|
+
"""
|
|
284
|
+
for item in self.middlewares:
|
|
285
|
+
# 支持类或实例
|
|
286
|
+
if isinstance(item, type):
|
|
287
|
+
middleware = item()
|
|
288
|
+
else:
|
|
289
|
+
middleware = item
|
|
290
|
+
|
|
291
|
+
if middleware.can_enable(self._config):
|
|
292
|
+
self._middlewares[middleware.name] = middleware
|
|
293
|
+
logger.debug(f"中间件已收集: {middleware.name}")
|
|
294
|
+
|
|
295
|
+
def _collect_components(self) -> None:
|
|
296
|
+
"""收集并实例化所有组件。
|
|
297
|
+
|
|
298
|
+
这一步在 super().__init__() 之前执行,只做实例化和过滤,
|
|
299
|
+
不调用任何需要 app 已完成构造的方法。
|
|
300
|
+
"""
|
|
301
|
+
for item in self.components:
|
|
302
|
+
# 支持类或实例
|
|
303
|
+
if isinstance(item, type):
|
|
304
|
+
component = item()
|
|
305
|
+
else:
|
|
306
|
+
component = item
|
|
307
|
+
|
|
308
|
+
if component.can_enable(self._config):
|
|
309
|
+
self._components[component.name] = component
|
|
310
|
+
logger.debug(f"组件已收集: {component.name}")
|
|
311
|
+
|
|
312
|
+
def _build_middlewares(self) -> list[StarletteMiddleware]:
|
|
313
|
+
"""构建所有中间件实例。
|
|
314
|
+
|
|
315
|
+
在 super().__init__() 之前调用,返回中间件实例列表传给 FastAPI。
|
|
316
|
+
按 order 升序排序,Starlette 会反向构建栈,最后的最先执行。
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Starlette Middleware 实例列表
|
|
320
|
+
"""
|
|
321
|
+
# 按 order 降序排序(Starlette 反向构建,所以降序排列后最小的在最外层)
|
|
322
|
+
sorted_middlewares = sorted(
|
|
323
|
+
self._middlewares.values(),
|
|
324
|
+
key=lambda m: m.order,
|
|
325
|
+
reverse=True,
|
|
326
|
+
)
|
|
327
|
+
result = []
|
|
328
|
+
for middleware in sorted_middlewares:
|
|
329
|
+
instance = middleware.build(self._config)
|
|
330
|
+
result.append(instance)
|
|
331
|
+
logger.debug(f"中间件已构建: {middleware.name}")
|
|
332
|
+
return result
|
|
333
|
+
|
|
334
|
+
async def _on_startup(self) -> None:
|
|
335
|
+
"""启动所有组件。"""
|
|
336
|
+
logger.info("应用启动中...")
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
# 拓扑排序
|
|
340
|
+
sorted_components = self._topological_sort(list(self._components.values()))
|
|
341
|
+
|
|
342
|
+
# 启动组件
|
|
343
|
+
for component in sorted_components:
|
|
344
|
+
try:
|
|
345
|
+
await component.setup(self, self._config)
|
|
346
|
+
logger.info(f"组件启动成功: {component.name}")
|
|
347
|
+
except Exception as e:
|
|
348
|
+
logger.error(f"组件启动失败 ({component.name}): {e}")
|
|
349
|
+
raise
|
|
350
|
+
|
|
351
|
+
logger.info("应用启动完成")
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logger.error(f"应用启动异常: {e}")
|
|
355
|
+
raise
|
|
356
|
+
|
|
357
|
+
async def _on_shutdown(self) -> None:
|
|
358
|
+
"""关闭所有组件。"""
|
|
359
|
+
logger.info("应用关闭中...")
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
# 反序关闭
|
|
363
|
+
sorted_components = self._topological_sort(
|
|
364
|
+
list(self._components.values()),
|
|
365
|
+
reverse=True,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
for component in sorted_components:
|
|
369
|
+
try:
|
|
370
|
+
await component.teardown(self)
|
|
371
|
+
logger.info(f"组件关闭成功: {component.name}")
|
|
372
|
+
except Exception as e:
|
|
373
|
+
logger.warning(f"组件关闭失败 ({component.name}): {e}")
|
|
374
|
+
|
|
375
|
+
logger.info("应用关闭完成")
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logger.error(f"应用关闭异常: {e}")
|
|
379
|
+
|
|
380
|
+
def _topological_sort(
|
|
381
|
+
self,
|
|
382
|
+
components: list[Component],
|
|
383
|
+
reverse: bool = False,
|
|
384
|
+
) -> list[Component]:
|
|
385
|
+
"""拓扑排序组件。
|
|
386
|
+
|
|
387
|
+
按照 depends_on 关系排序,确保依赖先启动。
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
components: 组件列表
|
|
391
|
+
reverse: 是否反序(用于关闭)
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
排序后的组件列表
|
|
395
|
+
"""
|
|
396
|
+
# 构建图
|
|
397
|
+
component_map = {comp.name: comp for comp in components}
|
|
398
|
+
visited = set()
|
|
399
|
+
result = []
|
|
400
|
+
|
|
401
|
+
def visit(name: str, visiting: set) -> None:
|
|
402
|
+
if name in visited:
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
if name in visiting:
|
|
406
|
+
logger.warning(f"检测到循环依赖: {name}")
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
visiting.add(name)
|
|
410
|
+
|
|
411
|
+
# 访问依赖
|
|
412
|
+
if name in component_map:
|
|
413
|
+
for dep in component_map[name].depends_on:
|
|
414
|
+
if dep in component_map:
|
|
415
|
+
visit(dep, visiting)
|
|
416
|
+
|
|
417
|
+
visiting.remove(name)
|
|
418
|
+
visited.add(name)
|
|
419
|
+
|
|
420
|
+
if name in component_map:
|
|
421
|
+
result.append(component_map[name])
|
|
422
|
+
|
|
423
|
+
# 访问所有组件
|
|
424
|
+
for comp in components:
|
|
425
|
+
visit(comp.name, set())
|
|
426
|
+
|
|
427
|
+
# 反序用于关闭
|
|
428
|
+
if reverse:
|
|
429
|
+
result.reverse()
|
|
430
|
+
|
|
431
|
+
return result
|
|
432
|
+
|
|
433
|
+
def setup_routes(self) -> None:
|
|
434
|
+
"""设置路由。
|
|
435
|
+
|
|
436
|
+
子类可以重写此方法来自定义路由配置。
|
|
437
|
+
"""
|
|
438
|
+
# 健康检查端点(默认启用)
|
|
439
|
+
self.setup_health_check()
|
|
440
|
+
|
|
441
|
+
def setup_health_check(self) -> None:
|
|
442
|
+
"""设置健康检查端点。
|
|
443
|
+
|
|
444
|
+
使用 Aury 框架的默认健康检查逻辑。
|
|
445
|
+
路径和启用状态可通过 BaseConfig.health_check 配置。
|
|
446
|
+
|
|
447
|
+
子类可以重写此方法来自定义健康检查逻辑。
|
|
448
|
+
"""
|
|
449
|
+
# 检查是否启用
|
|
450
|
+
if not self._config.health_check.enabled:
|
|
451
|
+
logger.debug("Aury 默认健康检查端点已禁用")
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
health_path = self._config.health_check.path
|
|
455
|
+
|
|
456
|
+
@self.get(health_path, tags=["health"])
|
|
457
|
+
async def health_check(request: Request) -> SuccessResponse:
|
|
458
|
+
"""Aury 框架默认健康检查端点。
|
|
459
|
+
|
|
460
|
+
检查数据库、缓存等基础设施组件的健康状态。
|
|
461
|
+
注意:此端点与服务自身的健康检查端点独立,可通过配置自定义路径。
|
|
462
|
+
"""
|
|
463
|
+
health_status = {
|
|
464
|
+
"status": "healthy",
|
|
465
|
+
"checks": {},
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
# 检查数据库(如果已初始化)
|
|
469
|
+
db_manager = DatabaseManager.get_instance()
|
|
470
|
+
if db_manager._initialized:
|
|
471
|
+
try:
|
|
472
|
+
await db_manager.health_check()
|
|
473
|
+
health_status["checks"]["database"] = "ok"
|
|
474
|
+
except Exception as e:
|
|
475
|
+
health_status["status"] = "unhealthy"
|
|
476
|
+
health_status["checks"]["database"] = f"error: {e!s}"
|
|
477
|
+
|
|
478
|
+
# 检查缓存(如果已初始化)
|
|
479
|
+
cache_manager = CacheManager.get_instance()
|
|
480
|
+
if cache_manager._backend:
|
|
481
|
+
try:
|
|
482
|
+
await cache_manager.get("__health_check__", default=None)
|
|
483
|
+
health_status["checks"]["cache"] = "ok"
|
|
484
|
+
except Exception as e:
|
|
485
|
+
health_status["checks"]["cache"] = f"error: {e!s}"
|
|
486
|
+
|
|
487
|
+
# 返回状态码
|
|
488
|
+
status_code = (
|
|
489
|
+
status.HTTP_200_OK
|
|
490
|
+
if health_status["status"] == "healthy"
|
|
491
|
+
else status.HTTP_503_SERVICE_UNAVAILABLE
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
return JSONResponse(
|
|
495
|
+
content=SuccessResponse(data=health_status).model_dump(mode="json"),
|
|
496
|
+
status_code=status_code,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
logger.info(f"Aury 默认健康检查端点已注册: {health_path}")
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
def config(self) -> BaseConfig:
|
|
503
|
+
"""获取应用配置。"""
|
|
504
|
+
return self._config
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
__all__ = [
|
|
508
|
+
"Component",
|
|
509
|
+
"Middleware",
|
|
510
|
+
]
|
|
511
|
+
|