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,434 @@
|
|
|
1
|
+
"""默认组件实现。
|
|
2
|
+
|
|
3
|
+
提供所有内置基础设施组件的实现。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import pkgutil
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from aury.boot.application.app.base import Component, FoundationApp
|
|
13
|
+
from aury.boot.application.config import BaseConfig
|
|
14
|
+
from aury.boot.application.constants import ComponentName, ServiceType
|
|
15
|
+
from aury.boot.application.migrations import MigrationManager
|
|
16
|
+
from aury.boot.common.logging import logger
|
|
17
|
+
from aury.boot.infrastructure.cache import CacheManager
|
|
18
|
+
from aury.boot.infrastructure.database import DatabaseManager
|
|
19
|
+
from aury.boot.infrastructure.scheduler import SchedulerManager
|
|
20
|
+
from aury.boot.infrastructure.storage import StorageManager
|
|
21
|
+
from aury.boot.infrastructure.tasks import TaskManager
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DatabaseComponent(Component):
|
|
25
|
+
"""数据库组件。"""
|
|
26
|
+
|
|
27
|
+
name = ComponentName.DATABASE
|
|
28
|
+
enabled = True
|
|
29
|
+
depends_on: ClassVar[list[str]] = []
|
|
30
|
+
|
|
31
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
32
|
+
"""仅当配置了数据库 URL 时启用。"""
|
|
33
|
+
return self.enabled and bool(config.database.url)
|
|
34
|
+
|
|
35
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
36
|
+
"""初始化数据库。"""
|
|
37
|
+
try:
|
|
38
|
+
db_manager = DatabaseManager.get_instance()
|
|
39
|
+
if not db_manager._initialized:
|
|
40
|
+
await db_manager.initialize(
|
|
41
|
+
url=config.database.url,
|
|
42
|
+
echo=config.database.echo,
|
|
43
|
+
pool_size=config.database.pool_size,
|
|
44
|
+
max_overflow=config.database.max_overflow,
|
|
45
|
+
pool_timeout=config.database.pool_timeout,
|
|
46
|
+
pool_recycle=config.database.pool_recycle,
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"数据库初始化失败: {e}")
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
53
|
+
"""关闭数据库。"""
|
|
54
|
+
try:
|
|
55
|
+
db_manager = DatabaseManager.get_instance()
|
|
56
|
+
if db_manager._initialized:
|
|
57
|
+
await db_manager.cleanup()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"数据库关闭失败: {e}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CacheComponent(Component):
|
|
63
|
+
"""缓存组件。"""
|
|
64
|
+
|
|
65
|
+
name = ComponentName.CACHE
|
|
66
|
+
enabled = True
|
|
67
|
+
depends_on: ClassVar[list[str]] = []
|
|
68
|
+
|
|
69
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
70
|
+
"""仅当配置了缓存类型时启用。"""
|
|
71
|
+
return self.enabled and bool(config.cache.cache_type)
|
|
72
|
+
|
|
73
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
74
|
+
"""初始化缓存。"""
|
|
75
|
+
try:
|
|
76
|
+
cache_manager = CacheManager.get_instance()
|
|
77
|
+
if not cache_manager._backend:
|
|
78
|
+
await cache_manager.init_app({
|
|
79
|
+
"CACHE_TYPE": config.cache.cache_type,
|
|
80
|
+
"CACHE_URL": config.cache.url,
|
|
81
|
+
"CACHE_MAX_SIZE": config.cache.max_size,
|
|
82
|
+
})
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.warning(f"缓存初始化失败(非关键): {e}")
|
|
85
|
+
|
|
86
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
87
|
+
"""关闭缓存。"""
|
|
88
|
+
try:
|
|
89
|
+
cache_manager = CacheManager.get_instance()
|
|
90
|
+
if cache_manager._backend:
|
|
91
|
+
await cache_manager.cleanup()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.warning(f"缓存关闭失败: {e}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class StorageComponent(Component):
|
|
97
|
+
"""对象存储组件。
|
|
98
|
+
|
|
99
|
+
设计要点(解耦):
|
|
100
|
+
- 使用 aury-sdk-storage 的 StorageConfig/StorageBackend,Application 层仅做装配
|
|
101
|
+
- 建议由应用的 `StorageSettings` 读取环境变量并构造 SDK 的 StorageConfig
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
name = ComponentName.STORAGE
|
|
105
|
+
enabled = True
|
|
106
|
+
depends_on: ClassVar[list[str]] = []
|
|
107
|
+
|
|
108
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
109
|
+
st = getattr(config, "storage", None)
|
|
110
|
+
return self.enabled and bool(getattr(st, "enabled", True))
|
|
111
|
+
|
|
112
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
113
|
+
try:
|
|
114
|
+
from aury.boot.infrastructure.storage import StorageBackend, StorageConfig
|
|
115
|
+
|
|
116
|
+
storage_manager = StorageManager.get_instance()
|
|
117
|
+
st = config.storage
|
|
118
|
+
storage_config = StorageConfig(
|
|
119
|
+
backend=StorageBackend(st.type),
|
|
120
|
+
access_key_id=st.access_key_id,
|
|
121
|
+
access_key_secret=st.access_key_secret,
|
|
122
|
+
session_token=st.session_token,
|
|
123
|
+
endpoint=st.endpoint,
|
|
124
|
+
region=st.region,
|
|
125
|
+
bucket_name=st.bucket_name,
|
|
126
|
+
base_path=st.base_path,
|
|
127
|
+
addressing_style=st.addressing_style,
|
|
128
|
+
role_arn=st.role_arn,
|
|
129
|
+
role_session_name=st.role_session_name,
|
|
130
|
+
external_id=st.external_id,
|
|
131
|
+
sts_endpoint=st.sts_endpoint,
|
|
132
|
+
sts_region=st.sts_region,
|
|
133
|
+
sts_duration_seconds=st.sts_duration_seconds,
|
|
134
|
+
)
|
|
135
|
+
await storage_manager.init(storage_config)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.warning(f"存储初始化失败(非关键): {e}")
|
|
138
|
+
|
|
139
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
140
|
+
try:
|
|
141
|
+
storage_manager = StorageManager.get_instance()
|
|
142
|
+
await storage_manager.cleanup()
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.warning(f"存储关闭失败: {e}")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TaskComponent(Component):
|
|
148
|
+
"""任务队列组件(Worker 模式)。"""
|
|
149
|
+
|
|
150
|
+
name = ComponentName.TASK_QUEUE
|
|
151
|
+
enabled = True
|
|
152
|
+
depends_on: ClassVar[list[str]] = []
|
|
153
|
+
|
|
154
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
155
|
+
"""仅当是 Worker 模式且配置了 broker URL 时启用。"""
|
|
156
|
+
return (
|
|
157
|
+
self.enabled
|
|
158
|
+
and config.service.service_type == ServiceType.WORKER.value
|
|
159
|
+
and bool(config.task.broker_url)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
163
|
+
"""初始化任务队列。"""
|
|
164
|
+
try:
|
|
165
|
+
from aury.boot.infrastructure.tasks.constants import TaskRunMode
|
|
166
|
+
|
|
167
|
+
task_manager = TaskManager.get_instance()
|
|
168
|
+
# 将 application 层的 ServiceType.WORKER 转换为 infrastructure 层的 TaskRunMode.WORKER
|
|
169
|
+
# ServiceType.API 和 ServiceType.SCHEDULER 转换为 TaskRunMode.PRODUCER
|
|
170
|
+
if config.service.service_type == ServiceType.WORKER.value:
|
|
171
|
+
run_mode = TaskRunMode.WORKER
|
|
172
|
+
else:
|
|
173
|
+
run_mode = TaskRunMode.PRODUCER
|
|
174
|
+
|
|
175
|
+
await task_manager.initialize(
|
|
176
|
+
run_mode=run_mode,
|
|
177
|
+
broker_url=config.task.broker_url,
|
|
178
|
+
)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.warning(f"任务队列初始化失败(非关键): {e}")
|
|
181
|
+
|
|
182
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
183
|
+
"""无需显式清理。"""
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class SchedulerComponent(Component):
|
|
188
|
+
"""调度器组件。
|
|
189
|
+
|
|
190
|
+
支持两种任务发现方式:
|
|
191
|
+
1. 配置方式:通过 SCHEDULER_JOB_MODULES 指定要加载的模块
|
|
192
|
+
2. 自动感知:无配置时自动发现 schedules 模块
|
|
193
|
+
|
|
194
|
+
@scheduler.scheduled_job() 装饰器可以在模块导入时使用,
|
|
195
|
+
任务会被收集到待注册列表,在 start() 时注册并启动。
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
name = ComponentName.SCHEDULER
|
|
199
|
+
enabled = True
|
|
200
|
+
depends_on: ClassVar[list[str]] = []
|
|
201
|
+
|
|
202
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
203
|
+
"""仅当是 API 服务且启用调度器时启用。"""
|
|
204
|
+
return (
|
|
205
|
+
self.enabled
|
|
206
|
+
and config.service.service_type == ServiceType.API.value
|
|
207
|
+
and config.scheduler.enabled
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _autodiscover_schedules(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
211
|
+
"""自动发现并加载定时任务模块。
|
|
212
|
+
|
|
213
|
+
发现策略:
|
|
214
|
+
1. 如果配置了 schedule_modules,直接加载配置的模块
|
|
215
|
+
2. 优先尝试项目包名和服务名:
|
|
216
|
+
- {pyproject.tool.aury.package}.schedules
|
|
217
|
+
- {SERVICE_NAME}.schedules(当 SERVICE_NAME 不是默认值时)
|
|
218
|
+
3. 从 app._caller_module 推断:
|
|
219
|
+
- __main__ / main → 尝试 schedules
|
|
220
|
+
- myapp.main → 尝试 myapp.schedules
|
|
221
|
+
4. 回退:尝试导入 schedules
|
|
222
|
+
"""
|
|
223
|
+
modules_to_load: list[str] = []
|
|
224
|
+
|
|
225
|
+
# 策略 1:配置优先
|
|
226
|
+
if config.scheduler.schedule_modules:
|
|
227
|
+
modules_to_load = list(config.scheduler.schedule_modules)
|
|
228
|
+
logger.debug(f"使用配置的定时任务模块: {modules_to_load}")
|
|
229
|
+
else:
|
|
230
|
+
# 策略 2:项目包名与服务名
|
|
231
|
+
try:
|
|
232
|
+
from aury.boot.commands.config import get_project_config
|
|
233
|
+
cfg = get_project_config()
|
|
234
|
+
if cfg.has_package:
|
|
235
|
+
modules_to_load.append(f"{cfg.package}.schedules")
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
service_name = (getattr(config.service, "name", None) or "").strip()
|
|
240
|
+
if service_name and service_name not in {"app", "main"}:
|
|
241
|
+
modules_to_load.append(f"{service_name}.schedules")
|
|
242
|
+
|
|
243
|
+
# 策略 3:从调用者模块推断
|
|
244
|
+
caller = getattr(app, "_caller_module", "__main__")
|
|
245
|
+
if caller in ("__main__", "main"):
|
|
246
|
+
modules_to_load.append("schedules")
|
|
247
|
+
elif "." in caller:
|
|
248
|
+
package = caller.rsplit(".", 1)[0]
|
|
249
|
+
modules_to_load.extend([f"{package}.schedules", "schedules"])
|
|
250
|
+
else:
|
|
251
|
+
modules_to_load.extend([f"{caller}.schedules", "schedules"])
|
|
252
|
+
|
|
253
|
+
# 去重,保持顺序
|
|
254
|
+
seen = set()
|
|
255
|
+
modules_to_load = [m for m in modules_to_load if not (m in seen or seen.add(m))]
|
|
256
|
+
|
|
257
|
+
# 加载模块
|
|
258
|
+
for module_name in modules_to_load:
|
|
259
|
+
try:
|
|
260
|
+
module = importlib.import_module(module_name)
|
|
261
|
+
logger.info(f"已自动加载定时任务模块: {module_name}")
|
|
262
|
+
|
|
263
|
+
# 递归加载包下所有子模块
|
|
264
|
+
if hasattr(module, "__path__"):
|
|
265
|
+
for _, submodule_name, _ in pkgutil.walk_packages(
|
|
266
|
+
module.__path__, prefix=f"{module_name}."
|
|
267
|
+
):
|
|
268
|
+
try:
|
|
269
|
+
importlib.import_module(submodule_name)
|
|
270
|
+
logger.debug(f"已加载子模块: {submodule_name}")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.warning(f"加载子模块失败 ({submodule_name}): {e}")
|
|
273
|
+
|
|
274
|
+
# 如果成功加载一个,且不是配置模式,就停止
|
|
275
|
+
if not config.scheduler.schedule_modules:
|
|
276
|
+
break
|
|
277
|
+
except ImportError:
|
|
278
|
+
logger.debug(f"定时任务模块不存在: {module_name}")
|
|
279
|
+
except Exception as e:
|
|
280
|
+
logger.warning(f"加载定时任务模块失败 ({module_name}): {e}")
|
|
281
|
+
|
|
282
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
283
|
+
"""启动调度器。
|
|
284
|
+
|
|
285
|
+
1. 自动发现并加载定时任务模块
|
|
286
|
+
2. 启动调度器(注册装饰器收集的任务)
|
|
287
|
+
"""
|
|
288
|
+
try:
|
|
289
|
+
# 自动发现并加载定时任务模块
|
|
290
|
+
self._autodiscover_schedules(app, config)
|
|
291
|
+
|
|
292
|
+
# 启动调度器
|
|
293
|
+
scheduler = SchedulerManager.get_instance()
|
|
294
|
+
scheduler.start()
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.warning(f"调度器启动失败(非关键): {e}")
|
|
297
|
+
|
|
298
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
299
|
+
"""关闭调度器。"""
|
|
300
|
+
try:
|
|
301
|
+
scheduler = SchedulerManager.get_instance()
|
|
302
|
+
if scheduler._scheduler and scheduler._scheduler.running:
|
|
303
|
+
scheduler.shutdown()
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.warning(f"调度器关闭失败: {e}")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class MigrationComponent(Component):
|
|
309
|
+
"""数据库迁移组件。
|
|
310
|
+
|
|
311
|
+
自动执行数据库迁移(升级到最新版本)。
|
|
312
|
+
|
|
313
|
+
配置选项:
|
|
314
|
+
- `ENABLE_AUTO_MIGRATION`:是否启用自动迁移(默认:True)
|
|
315
|
+
- `ALEMBIC_CONFIG_PATH`:Alembic 配置文件路径(默认:alembic.ini)
|
|
316
|
+
- `AUTO_MIGRATE_ON_STARTUP`:应用启动时是否自动执行迁移(默认:True)
|
|
317
|
+
|
|
318
|
+
使用示例:
|
|
319
|
+
# 在应用启动时自动执行迁移
|
|
320
|
+
app = FoundationApp()
|
|
321
|
+
# MigrationComponent 会在 DatabaseComponent 之后自动执行迁移
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
name = ComponentName.MIGRATIONS
|
|
325
|
+
enabled = True
|
|
326
|
+
depends_on: ClassVar[list[str]] = [ComponentName.DATABASE]
|
|
327
|
+
|
|
328
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
329
|
+
"""仅当配置了数据库 URL 时启用。"""
|
|
330
|
+
return self.enabled and bool(config.database.url)
|
|
331
|
+
|
|
332
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
333
|
+
"""执行数据库迁移。
|
|
334
|
+
|
|
335
|
+
在应用启动时自动执行所有待处理的迁移,升级数据库到最新版本。
|
|
336
|
+
"""
|
|
337
|
+
try:
|
|
338
|
+
# 创建迁移管理器(从配置读取参数)
|
|
339
|
+
migration_settings = config.migration
|
|
340
|
+
migration_manager = MigrationManager(
|
|
341
|
+
database_url=config.database.url,
|
|
342
|
+
config_path=migration_settings.config_path,
|
|
343
|
+
script_location=migration_settings.script_location,
|
|
344
|
+
model_modules=migration_settings.model_modules,
|
|
345
|
+
auto_create=migration_settings.auto_create,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# 检查是否有迁移需要执行
|
|
349
|
+
logger.info("🔄 检查数据库迁移...")
|
|
350
|
+
status = await migration_manager.status()
|
|
351
|
+
|
|
352
|
+
pending = status.get("pending", [])
|
|
353
|
+
applied = status.get("applied", [])
|
|
354
|
+
|
|
355
|
+
if pending:
|
|
356
|
+
logger.info("📊 数据库迁移状态:")
|
|
357
|
+
logger.info(f" 已执行: {len(applied)} 个迁移")
|
|
358
|
+
logger.info(f" 待执行: {len(pending)} 个迁移")
|
|
359
|
+
|
|
360
|
+
# 执行迁移到最新版本
|
|
361
|
+
logger.info("⏳ 执行数据库迁移...")
|
|
362
|
+
await migration_manager.upgrade(revision="head")
|
|
363
|
+
|
|
364
|
+
logger.info("✅ 数据库迁移完成")
|
|
365
|
+
else:
|
|
366
|
+
logger.info("✅ 数据库已是最新版本,无需迁移")
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.error(f"❌ 数据库迁移失败: {e}", exc_info=True)
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
372
|
+
"""无需清理。"""
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class AdminConsoleComponent(Component):
|
|
377
|
+
"""管理后台组件(SQLAdmin Admin Console)。
|
|
378
|
+
|
|
379
|
+
配置选项(环境变量前缀 ADMIN_ / ADMIN_AUTH_):
|
|
380
|
+
- ADMIN_ENABLED: 是否启用(默认 False)
|
|
381
|
+
- ADMIN_PATH: 后台路径(默认 /api/admin-console)
|
|
382
|
+
- ADMIN_DATABASE_URL: 同步数据库 URL(可选)
|
|
383
|
+
- ADMIN_AUTH_MODE: basic/bearer/none/custom/jwt
|
|
384
|
+
- ADMIN_AUTH_*: 认证参数
|
|
385
|
+
|
|
386
|
+
注意:sqladmin 通常要求同步 SQLAlchemy Engine。
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
name = ComponentName.ADMIN_CONSOLE
|
|
390
|
+
enabled = True
|
|
391
|
+
depends_on: ClassVar[list[str]] = [ComponentName.DATABASE]
|
|
392
|
+
|
|
393
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
394
|
+
return self.enabled and bool(getattr(getattr(config, "admin", None), "enabled", False))
|
|
395
|
+
|
|
396
|
+
async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
|
|
397
|
+
try:
|
|
398
|
+
from aury.boot.contrib.admin_console import install_admin_console
|
|
399
|
+
|
|
400
|
+
install_admin_console(app, config)
|
|
401
|
+
except ImportError as e:
|
|
402
|
+
logger.error(f"管理后台启用失败:缺少依赖(请安装 aury-boot[admin]): {e}")
|
|
403
|
+
raise
|
|
404
|
+
except Exception as e:
|
|
405
|
+
logger.error(f"管理后台初始化失败: {e}")
|
|
406
|
+
raise
|
|
407
|
+
|
|
408
|
+
async def teardown(self, app: FoundationApp) -> None:
|
|
409
|
+
# SQLAdmin 路由挂载后无需额外 teardown
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# 设置默认组件
|
|
414
|
+
FoundationApp.components = [
|
|
415
|
+
DatabaseComponent,
|
|
416
|
+
MigrationComponent,
|
|
417
|
+
AdminConsoleComponent,
|
|
418
|
+
CacheComponent,
|
|
419
|
+
StorageComponent,
|
|
420
|
+
TaskComponent,
|
|
421
|
+
SchedulerComponent,
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
__all__ = [
|
|
426
|
+
"AdminConsoleComponent",
|
|
427
|
+
"CacheComponent",
|
|
428
|
+
"DatabaseComponent",
|
|
429
|
+
"MigrationComponent",
|
|
430
|
+
"SchedulerComponent",
|
|
431
|
+
"StorageComponent",
|
|
432
|
+
"TaskComponent",
|
|
433
|
+
]
|
|
434
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""默认中间件实现。
|
|
2
|
+
|
|
3
|
+
提供所有内置 HTTP 中间件的实现。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware as FastAPICORSMiddleware
|
|
9
|
+
from starlette.middleware import Middleware as StarletteMiddleware
|
|
10
|
+
|
|
11
|
+
from aury.boot.application.app.base import FoundationApp, Middleware
|
|
12
|
+
from aury.boot.application.config import BaseConfig
|
|
13
|
+
from aury.boot.application.constants import MiddlewareName
|
|
14
|
+
from aury.boot.application.middleware.logging import (
|
|
15
|
+
RequestLoggingMiddleware as StarletteRequestLoggingMiddleware,
|
|
16
|
+
WebSocketLoggingMiddleware as StarletteWebSocketLoggingMiddleware,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CORSMiddleware",
|
|
21
|
+
"RequestLoggingMiddleware",
|
|
22
|
+
"WebSocketLoggingMiddleware",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RequestLoggingMiddleware(Middleware):
|
|
27
|
+
"""请求日志中间件。
|
|
28
|
+
|
|
29
|
+
自动记录所有 HTTP 请求的详细信息,包括:
|
|
30
|
+
- 请求方法、路径、查询参数
|
|
31
|
+
- 客户端 IP、User-Agent
|
|
32
|
+
- 响应状态码、耗时
|
|
33
|
+
- 链路追踪 ID(X-Trace-ID / X-Request-ID)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name = MiddlewareName.REQUEST_LOGGING
|
|
37
|
+
enabled = True
|
|
38
|
+
order = 0 # 最先执行,确保日志记录所有请求
|
|
39
|
+
|
|
40
|
+
def build(self, config: BaseConfig) -> StarletteMiddleware:
|
|
41
|
+
"""构建请求日志中间件实例。"""
|
|
42
|
+
return StarletteMiddleware(StarletteRequestLoggingMiddleware)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CORSMiddleware(Middleware):
|
|
46
|
+
"""CORS 跨域处理中间件。
|
|
47
|
+
|
|
48
|
+
处理跨域资源共享(CORS)请求,允许配置:
|
|
49
|
+
- 允许的源(origins)
|
|
50
|
+
- 允许的方法(methods)
|
|
51
|
+
- 允许的头(headers)
|
|
52
|
+
- 凭证支持(credentials)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
name = MiddlewareName.CORS
|
|
56
|
+
enabled = True
|
|
57
|
+
order = 10 # 在请求日志之后执行
|
|
58
|
+
|
|
59
|
+
def can_enable(self, config: BaseConfig) -> bool:
|
|
60
|
+
"""仅当配置了 origins 时启用。"""
|
|
61
|
+
return self.enabled and bool(config.cors.origins)
|
|
62
|
+
|
|
63
|
+
def build(self, config: BaseConfig) -> StarletteMiddleware:
|
|
64
|
+
"""构建 CORS 中间件实例。"""
|
|
65
|
+
return StarletteMiddleware(
|
|
66
|
+
FastAPICORSMiddleware,
|
|
67
|
+
allow_origins=config.cors.origins,
|
|
68
|
+
allow_credentials=config.cors.allow_credentials,
|
|
69
|
+
allow_methods=config.cors.allow_methods,
|
|
70
|
+
allow_headers=config.cors.allow_headers,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class WebSocketLoggingMiddleware(Middleware):
|
|
75
|
+
"""WebSocket 日志中间件。
|
|
76
|
+
|
|
77
|
+
记录 WebSocket 连接生命周期:
|
|
78
|
+
- 连接建立/断开
|
|
79
|
+
- 消息收发统计
|
|
80
|
+
- 异常断开
|
|
81
|
+
- 链路追踪 ID
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
name = MiddlewareName.WEBSOCKET_LOGGING
|
|
85
|
+
enabled = True
|
|
86
|
+
order = 1 # 紧随 HTTP 日志中间件
|
|
87
|
+
|
|
88
|
+
def build(self, config: BaseConfig) -> StarletteMiddleware:
|
|
89
|
+
"""构建 WebSocket 日志中间件实例。"""
|
|
90
|
+
return StarletteMiddleware(
|
|
91
|
+
StarletteWebSocketLoggingMiddleware,
|
|
92
|
+
log_messages=config.log.websocket_log_messages,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# 设置默认中间件
|
|
97
|
+
FoundationApp.middlewares = [
|
|
98
|
+
RequestLoggingMiddleware,
|
|
99
|
+
WebSocketLoggingMiddleware,
|
|
100
|
+
CORSMiddleware,
|
|
101
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""配置模块。
|
|
2
|
+
|
|
3
|
+
提供所有应用共享的基础配置结构。
|
|
4
|
+
使用 pydantic-settings 进行分层分级配置管理。
|
|
5
|
+
|
|
6
|
+
设计原则:
|
|
7
|
+
- Application 层配置完全独立,不依赖 Infrastructure 层
|
|
8
|
+
- 配置是纯粹的数据模型定义
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .settings import (
|
|
12
|
+
BaseConfig,
|
|
13
|
+
CacheSettings,
|
|
14
|
+
CORSSettings,
|
|
15
|
+
DatabaseSettings,
|
|
16
|
+
EventSettings,
|
|
17
|
+
HealthCheckSettings,
|
|
18
|
+
LogSettings,
|
|
19
|
+
MigrationSettings,
|
|
20
|
+
RPCClientSettings,
|
|
21
|
+
RPCServiceSettings,
|
|
22
|
+
SchedulerSettings,
|
|
23
|
+
ServerSettings,
|
|
24
|
+
ServiceSettings,
|
|
25
|
+
TaskSettings,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"BaseConfig",
|
|
30
|
+
"CacheSettings",
|
|
31
|
+
"CORSSettings",
|
|
32
|
+
"DatabaseSettings",
|
|
33
|
+
"EventSettings",
|
|
34
|
+
"HealthCheckSettings",
|
|
35
|
+
"LogSettings",
|
|
36
|
+
"MigrationSettings",
|
|
37
|
+
"RPCClientSettings",
|
|
38
|
+
"RPCServiceSettings",
|
|
39
|
+
"SchedulerSettings",
|
|
40
|
+
"ServerSettings",
|
|
41
|
+
"ServiceSettings",
|
|
42
|
+
"TaskSettings",
|
|
43
|
+
]
|
|
44
|
+
|