aury-boot 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +890 -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.3.dist-info}/METADATA +3 -2
- aury_boot-0.0.3.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.3.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.2.dist-info → aury_boot-0.0.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""服务发现模块。
|
|
2
|
+
|
|
3
|
+
支持多种服务发现方式:
|
|
4
|
+
- 配置文件(推荐,通过 BaseConfig.rpc_client.services)
|
|
5
|
+
- DNS 解析(统一处理 K8s/Docker Compose,通过服务名自动解析)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from aury.boot.common.logging import logger
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from aury.boot.application.config import BaseConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ServiceDiscovery:
|
|
19
|
+
"""服务发现抽象基类。
|
|
20
|
+
|
|
21
|
+
所有服务发现实现都应该继承此类。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def resolve(self, service_name: str) -> str | None:
|
|
25
|
+
"""解析服务地址。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
service_name: 服务名称
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str | None: 服务地址(URL),如果无法解析返回 None
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConfigServiceDiscovery(ServiceDiscovery):
|
|
37
|
+
"""基于配置文件的服务发现。
|
|
38
|
+
|
|
39
|
+
从配置文件中读取服务地址映射。
|
|
40
|
+
|
|
41
|
+
配置格式:
|
|
42
|
+
```python
|
|
43
|
+
services = {
|
|
44
|
+
"user-service": "http://user-service:8000",
|
|
45
|
+
"order-service": "http://order-service:8001",
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, service_config: dict[str, str] | None = None) -> None:
|
|
51
|
+
"""初始化配置服务发现。
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
service_config: 服务配置字典 {service_name: url}
|
|
55
|
+
"""
|
|
56
|
+
self._services: dict[str, str] = service_config or {}
|
|
57
|
+
|
|
58
|
+
def resolve(self, service_name: str) -> str | None:
|
|
59
|
+
"""从配置中解析服务地址。"""
|
|
60
|
+
url = self._services.get(service_name)
|
|
61
|
+
if url:
|
|
62
|
+
logger.debug(f"从配置解析服务: {service_name} -> {url}")
|
|
63
|
+
return url
|
|
64
|
+
logger.warning(f"配置中未找到服务: {service_name}")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def register(self, service_name: str, url: str) -> None:
|
|
68
|
+
"""注册服务地址。
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
service_name: 服务名称
|
|
72
|
+
url: 服务地址
|
|
73
|
+
"""
|
|
74
|
+
self._services[service_name] = url
|
|
75
|
+
logger.info(f"注册服务: {service_name} -> {url}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DNSServiceDiscovery(ServiceDiscovery):
|
|
81
|
+
"""DNS 服务发现(统一处理 K8s/Docker Compose)。
|
|
82
|
+
|
|
83
|
+
直接使用服务名进行 DNS 解析:
|
|
84
|
+
- K8s: 自动使用 service-name(K8s DNS 会在同一 namespace 内解析)
|
|
85
|
+
- Docker Compose: 直接使用 service-name
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
scheme: str = "http",
|
|
91
|
+
default_port: int = 80,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""初始化 DNS 服务发现。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
scheme: 协议(http 或 https)
|
|
97
|
+
default_port: 默认端口
|
|
98
|
+
"""
|
|
99
|
+
self._scheme = scheme
|
|
100
|
+
self._default_port = default_port
|
|
101
|
+
|
|
102
|
+
def resolve(self, service_name: str) -> str | None:
|
|
103
|
+
"""使用 DNS 解析服务地址。
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
service_name: 服务名称(直接作为主机名,DNS 自动解析)
|
|
107
|
+
"""
|
|
108
|
+
# 直接使用服务名作为主机名(DNS 自动处理)
|
|
109
|
+
url = f"{self._scheme}://{service_name}"
|
|
110
|
+
|
|
111
|
+
if self._default_port not in (80, 443):
|
|
112
|
+
url = f"{url}:{self._default_port}"
|
|
113
|
+
|
|
114
|
+
logger.debug(f"使用 DNS 解析服务: {service_name} -> {url}")
|
|
115
|
+
return url
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class CompositeServiceDiscovery(ServiceDiscovery):
|
|
119
|
+
"""组合服务发现。
|
|
120
|
+
|
|
121
|
+
按优先级顺序尝试多种服务发现方式:
|
|
122
|
+
1. 配置文件(最高优先级,通过 BaseConfig.rpc_client.services)
|
|
123
|
+
2. DNS 解析(统一处理 K8s/Docker Compose,自动检测环境)
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
config: "BaseConfig" | None = None,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""初始化组合服务发现。
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
config: 应用配置(BaseConfig),如果为 None,则仅使用默认配置
|
|
134
|
+
"""
|
|
135
|
+
self._discoveries: list[ServiceDiscovery] = []
|
|
136
|
+
|
|
137
|
+
# 从配置中获取 RPC 客户端设置
|
|
138
|
+
if config:
|
|
139
|
+
rpc_client_settings = config.rpc_client
|
|
140
|
+
else:
|
|
141
|
+
# 如果没有提供配置,使用默认配置
|
|
142
|
+
from aury.boot.application.config import RPCClientSettings
|
|
143
|
+
rpc_client_settings = RPCClientSettings()
|
|
144
|
+
|
|
145
|
+
# 1. 配置文件(最高优先级)
|
|
146
|
+
if rpc_client_settings.services:
|
|
147
|
+
self._discoveries.append(ConfigServiceDiscovery(rpc_client_settings.services))
|
|
148
|
+
|
|
149
|
+
# 2. DNS 解析(如果启用)
|
|
150
|
+
if rpc_client_settings.use_dns_fallback:
|
|
151
|
+
logger.info("启用 DNS 服务发现(统一处理 K8s/Docker Compose)")
|
|
152
|
+
self._discoveries.append(
|
|
153
|
+
DNSServiceDiscovery(
|
|
154
|
+
scheme=rpc_client_settings.dns_scheme,
|
|
155
|
+
default_port=rpc_client_settings.dns_port,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def resolve(self, service_name: str) -> str | None:
|
|
160
|
+
"""按优先级顺序解析服务地址。"""
|
|
161
|
+
for discovery in self._discoveries:
|
|
162
|
+
url = discovery.resolve(service_name)
|
|
163
|
+
if url:
|
|
164
|
+
return url
|
|
165
|
+
|
|
166
|
+
logger.error(f"无法解析服务地址: {service_name}(已尝试所有服务发现方式)")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# 全局服务发现实例
|
|
171
|
+
_global_discovery: ServiceDiscovery | None = None
|
|
172
|
+
_global_config: "BaseConfig | None" = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_service_discovery(config: "BaseConfig | None" = None) -> ServiceDiscovery:
|
|
176
|
+
"""获取全局服务发现实例。
|
|
177
|
+
|
|
178
|
+
如果未初始化,使用配置创建组合服务发现。
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
config: 应用配置(可选),如果提供则使用此配置初始化
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
ServiceDiscovery: 服务发现实例
|
|
185
|
+
"""
|
|
186
|
+
global _global_discovery, _global_config
|
|
187
|
+
|
|
188
|
+
# 如果提供了新配置,更新全局配置
|
|
189
|
+
if config is not None:
|
|
190
|
+
_global_config = config
|
|
191
|
+
|
|
192
|
+
# 如果未初始化或配置已更新,重新创建
|
|
193
|
+
if _global_discovery is None or config is not None:
|
|
194
|
+
_global_discovery = CompositeServiceDiscovery(_global_config)
|
|
195
|
+
|
|
196
|
+
return _global_discovery
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def set_service_discovery(discovery: ServiceDiscovery) -> None:
|
|
200
|
+
"""设置全局服务发现实例。
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
discovery: 服务发现实例
|
|
204
|
+
"""
|
|
205
|
+
global _global_discovery
|
|
206
|
+
_global_discovery = discovery
|
|
207
|
+
logger.info("已设置全局服务发现实例")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
__all__ = [
|
|
211
|
+
"CompositeServiceDiscovery",
|
|
212
|
+
"ConfigServiceDiscovery",
|
|
213
|
+
"DNSServiceDiscovery",
|
|
214
|
+
"ServiceDiscovery",
|
|
215
|
+
"get_service_discovery",
|
|
216
|
+
"set_service_discovery",
|
|
217
|
+
]
|
|
218
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""调度器启动器。
|
|
2
|
+
|
|
3
|
+
提供调度器独立进程的启动入口。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from collections.abc import Awaitable, Callable
|
|
10
|
+
import signal
|
|
11
|
+
|
|
12
|
+
from aury.boot.application.config import BaseConfig
|
|
13
|
+
from aury.boot.application.constants import ServiceType
|
|
14
|
+
from aury.boot.common.logging import logger
|
|
15
|
+
from aury.boot.infrastructure.scheduler import SchedulerManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def run_scheduler(
|
|
19
|
+
config: BaseConfig | None = None,
|
|
20
|
+
*,
|
|
21
|
+
register_jobs: Callable[[], None] | Callable[[], Awaitable[None]] | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""运行调度器进程(独立模式)。
|
|
24
|
+
|
|
25
|
+
当 SERVICE_TYPE=scheduler 时,使用此函数启动独立的调度器进程。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
config: 应用配置(可选,默认从环境变量加载)
|
|
29
|
+
register_jobs: 注册任务的回调函数(可选)
|
|
30
|
+
|
|
31
|
+
使用示例:
|
|
32
|
+
async def setup_jobs():
|
|
33
|
+
scheduler = SchedulerManager.get_instance()
|
|
34
|
+
scheduler.add_job(
|
|
35
|
+
func=my_task,
|
|
36
|
+
trigger="interval",
|
|
37
|
+
seconds=60
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
asyncio.run(run_scheduler(register_jobs=setup_jobs))
|
|
42
|
+
"""
|
|
43
|
+
# 加载配置
|
|
44
|
+
if config is None:
|
|
45
|
+
config = BaseConfig()
|
|
46
|
+
|
|
47
|
+
service_config = config.service
|
|
48
|
+
|
|
49
|
+
# 验证服务类型
|
|
50
|
+
try:
|
|
51
|
+
service_type = ServiceType(service_config.service_type.lower())
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
raise ValueError(f"无效的服务类型: {service_config.service_type}") from e
|
|
54
|
+
|
|
55
|
+
if service_type != ServiceType.SCHEDULER:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"此函数仅用于 SERVICE_TYPE=scheduler,当前类型: {service_config.service_type}"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
logger.info("启动调度器进程(独立模式)")
|
|
61
|
+
|
|
62
|
+
# 初始化调度器
|
|
63
|
+
scheduler_manager = SchedulerManager.get_instance()
|
|
64
|
+
await scheduler_manager.initialize()
|
|
65
|
+
|
|
66
|
+
# 注册任务
|
|
67
|
+
if register_jobs:
|
|
68
|
+
logger.info("注册定时任务...")
|
|
69
|
+
if asyncio.iscoroutinefunction(register_jobs):
|
|
70
|
+
await register_jobs()
|
|
71
|
+
else:
|
|
72
|
+
register_jobs()
|
|
73
|
+
jobs = scheduler_manager.get_jobs()
|
|
74
|
+
logger.info(f"已注册 {len(jobs)} 个定时任务")
|
|
75
|
+
else:
|
|
76
|
+
logger.warning("未提供任务注册函数,调度器将不执行任何任务")
|
|
77
|
+
|
|
78
|
+
# 启动调度器
|
|
79
|
+
scheduler_manager.start()
|
|
80
|
+
logger.info("调度器进程已启动,等待任务执行...")
|
|
81
|
+
|
|
82
|
+
# 设置信号处理,优雅关闭
|
|
83
|
+
def signal_handler(signum, frame):
|
|
84
|
+
logger.info(f"收到信号 {signum},正在关闭调度器...")
|
|
85
|
+
scheduler_manager.shutdown()
|
|
86
|
+
logger.info("调度器已关闭")
|
|
87
|
+
asyncio.get_event_loop().stop()
|
|
88
|
+
|
|
89
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
90
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
91
|
+
|
|
92
|
+
# 保持进程运行
|
|
93
|
+
try:
|
|
94
|
+
# 使用 asyncio.Event 保持进程运行
|
|
95
|
+
stop_event = asyncio.Event()
|
|
96
|
+
await stop_event.wait()
|
|
97
|
+
except KeyboardInterrupt:
|
|
98
|
+
logger.info("收到中断信号,正在关闭...")
|
|
99
|
+
scheduler_manager.shutdown()
|
|
100
|
+
logger.info("调度器已关闭")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def run_scheduler_sync(
|
|
104
|
+
config: BaseConfig | None = None,
|
|
105
|
+
*,
|
|
106
|
+
register_jobs: Callable[[], None] | Callable[[], Awaitable[None]] | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""运行调度器进程(同步版本)。
|
|
109
|
+
|
|
110
|
+
这是 run_scheduler 的同步包装,方便直接调用。
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
config: 应用配置(可选,默认从环境变量加载)
|
|
114
|
+
register_jobs: 注册任务的回调函数(可选)
|
|
115
|
+
"""
|
|
116
|
+
asyncio.run(run_scheduler(config=config, register_jobs=register_jobs))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
__all__ = [
|
|
120
|
+
"run_scheduler",
|
|
121
|
+
"run_scheduler_sync",
|
|
122
|
+
]
|
|
123
|
+
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""ASGI 服务器管理。
|
|
2
|
+
|
|
3
|
+
提供 uvicorn 服务器的完整集成。
|
|
4
|
+
|
|
5
|
+
包含:
|
|
6
|
+
- ApplicationServer 类:服务器管理
|
|
7
|
+
- run_app 函数:快速启动
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import signal
|
|
14
|
+
import sys
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from aury.boot.common.logging import logger
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from aury.boot.application.app.base import FoundationApp
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ApplicationServer:
|
|
24
|
+
"""ASGI 应用服务器。
|
|
25
|
+
|
|
26
|
+
基于 uvicorn,提供完整的服务器管理功能。
|
|
27
|
+
|
|
28
|
+
使用示例:
|
|
29
|
+
app = FoundationApp()
|
|
30
|
+
server = ApplicationServer(
|
|
31
|
+
app=app,
|
|
32
|
+
host="0.0.0.0",
|
|
33
|
+
port=8000,
|
|
34
|
+
workers=4,
|
|
35
|
+
)
|
|
36
|
+
server.run()
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
app: FoundationApp,
|
|
42
|
+
*,
|
|
43
|
+
host: str = "127.0.0.1",
|
|
44
|
+
port: int = 8000,
|
|
45
|
+
workers: int = 1,
|
|
46
|
+
reload: bool = False,
|
|
47
|
+
reload_dirs: list[str] | None = None,
|
|
48
|
+
loop: str = "auto",
|
|
49
|
+
http: str = "auto",
|
|
50
|
+
interface: str = "auto",
|
|
51
|
+
debug: bool = False,
|
|
52
|
+
access_log: bool = True,
|
|
53
|
+
use_colors: bool | None = None,
|
|
54
|
+
log_config: dict | None = None,
|
|
55
|
+
ssl_keyfile: str | None = None,
|
|
56
|
+
ssl_certfile: str | None = None,
|
|
57
|
+
ssl_keyfile_password: str | None = None,
|
|
58
|
+
ssl_version: int | None = None,
|
|
59
|
+
ssl_cert_reqs: int | None = None,
|
|
60
|
+
ssl_ca_certs: str | None = None,
|
|
61
|
+
ssl_ciphers: str | None = None,
|
|
62
|
+
**kwargs,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""初始化服务器。
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
app: FastAPI/FoundationApp 应用实例
|
|
68
|
+
host: 监听地址,默认 127.0.0.1
|
|
69
|
+
port: 监听端口,默认 8000
|
|
70
|
+
workers: 工作进程数,默认 1
|
|
71
|
+
reload: 是否启用热重载
|
|
72
|
+
reload_dirs: 热重载监控目录
|
|
73
|
+
loop: 事件循环实现 (auto/asyncio/uvloop)
|
|
74
|
+
http: HTTP 协议版本 (auto/h11/httptools)
|
|
75
|
+
interface: ASGI 接口版本 (auto/asgi3/asgi2)
|
|
76
|
+
debug: 是否启用调试模式
|
|
77
|
+
access_log: 是否记录访问日志
|
|
78
|
+
use_colors: 是否使用彩色输出
|
|
79
|
+
log_config: 自定义日志配置
|
|
80
|
+
ssl_keyfile: SSL 密钥文件路径
|
|
81
|
+
ssl_certfile: SSL 证书文件路径
|
|
82
|
+
ssl_keyfile_password: SSL 密钥密码
|
|
83
|
+
ssl_version: SSL 版本
|
|
84
|
+
ssl_cert_reqs: SSL 证书请求模式
|
|
85
|
+
ssl_ca_certs: SSL CA 证书文件
|
|
86
|
+
ssl_ciphers: SSL 密码套件
|
|
87
|
+
**kwargs: 传递给 uvicorn.Config 的其他参数
|
|
88
|
+
"""
|
|
89
|
+
self.app = app
|
|
90
|
+
self.host = host
|
|
91
|
+
self.port = port
|
|
92
|
+
self.workers = workers
|
|
93
|
+
self.reload = reload
|
|
94
|
+
self.reload_dirs = reload_dirs
|
|
95
|
+
self.loop = loop
|
|
96
|
+
self.http = http
|
|
97
|
+
self.interface = interface
|
|
98
|
+
self.debug = debug
|
|
99
|
+
self.access_log = access_log
|
|
100
|
+
self.use_colors = use_colors
|
|
101
|
+
self.log_config = log_config or self._get_default_log_config()
|
|
102
|
+
self.ssl_keyfile = ssl_keyfile
|
|
103
|
+
self.ssl_certfile = ssl_certfile
|
|
104
|
+
self.ssl_keyfile_password = ssl_keyfile_password
|
|
105
|
+
self.ssl_version = ssl_version
|
|
106
|
+
self.ssl_cert_reqs = ssl_cert_reqs
|
|
107
|
+
self.ssl_ca_certs = ssl_ca_certs
|
|
108
|
+
self.ssl_ciphers = ssl_ciphers
|
|
109
|
+
self.extra_kwargs = kwargs
|
|
110
|
+
|
|
111
|
+
self.server: Any = None
|
|
112
|
+
|
|
113
|
+
def _get_default_log_config(self) -> dict:
|
|
114
|
+
"""获取默认日志配置。
|
|
115
|
+
|
|
116
|
+
整合 uvicorn 日志和框架日志。
|
|
117
|
+
"""
|
|
118
|
+
import uvicorn
|
|
119
|
+
from uvicorn.config import LOGGING_CONFIG
|
|
120
|
+
|
|
121
|
+
config = LOGGING_CONFIG.copy()
|
|
122
|
+
|
|
123
|
+
# 调整日志格式
|
|
124
|
+
config["formatters"]["access"]["format"] = (
|
|
125
|
+
"%(asctime)s | %(client_addr)s - "
|
|
126
|
+
"%(request_line)s | %(status_code)s"
|
|
127
|
+
)
|
|
128
|
+
config["formatters"]["default"]["format"] = (
|
|
129
|
+
"%(asctime)s | %(levelname)s | %(name)s - %(message)s"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return config
|
|
133
|
+
|
|
134
|
+
def get_config(self) -> Any:
|
|
135
|
+
"""获取 uvicorn 配置对象。
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
uvicorn.Config: 服务器配置
|
|
139
|
+
"""
|
|
140
|
+
import uvicorn
|
|
141
|
+
|
|
142
|
+
# 从 uvicorn 0.30.0 开始,Config.__init__ 不再接受 ``debug`` 参数,
|
|
143
|
+
# 这里仅在日志中使用 debug 标志,而不再传递给 uvicorn.Config,
|
|
144
|
+
# 以避免 "unexpected keyword argument 'debug'" 异常。
|
|
145
|
+
return uvicorn.Config(
|
|
146
|
+
app=self.app,
|
|
147
|
+
host=self.host,
|
|
148
|
+
port=self.port,
|
|
149
|
+
workers=self.workers,
|
|
150
|
+
reload=self.reload,
|
|
151
|
+
reload_dirs=self.reload_dirs,
|
|
152
|
+
loop=self.loop,
|
|
153
|
+
http=self.http,
|
|
154
|
+
interface=self.interface,
|
|
155
|
+
access_log=self.access_log,
|
|
156
|
+
use_colors=self.use_colors,
|
|
157
|
+
log_config=self.log_config,
|
|
158
|
+
ssl_keyfile=self.ssl_keyfile,
|
|
159
|
+
ssl_certfile=self.ssl_certfile,
|
|
160
|
+
ssl_keyfile_password=self.ssl_keyfile_password,
|
|
161
|
+
ssl_version=self.ssl_version,
|
|
162
|
+
ssl_cert_reqs=self.ssl_cert_reqs,
|
|
163
|
+
ssl_ca_certs=self.ssl_ca_certs,
|
|
164
|
+
ssl_ciphers=self.ssl_ciphers,
|
|
165
|
+
**self.extra_kwargs,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def run(self) -> None:
|
|
169
|
+
"""运行服务器。
|
|
170
|
+
|
|
171
|
+
阻塞调用,服务器会一直运行直到收到中断信号。
|
|
172
|
+
"""
|
|
173
|
+
import uvicorn
|
|
174
|
+
|
|
175
|
+
config = self.get_config()
|
|
176
|
+
self.server = uvicorn.Server(config)
|
|
177
|
+
|
|
178
|
+
# 设置信号处理
|
|
179
|
+
self._setup_signal_handlers()
|
|
180
|
+
|
|
181
|
+
# 记录启动信息
|
|
182
|
+
self._log_startup_info()
|
|
183
|
+
|
|
184
|
+
# 运行服务器
|
|
185
|
+
try:
|
|
186
|
+
self.server.run()
|
|
187
|
+
except KeyboardInterrupt:
|
|
188
|
+
logger.info("服务器已停止(Ctrl+C)")
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
logger.error(f"服务器错误: {exc}", exc_info=True)
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
|
|
193
|
+
async def run_async(self) -> None:
|
|
194
|
+
"""异步运行服务器。
|
|
195
|
+
|
|
196
|
+
使用示例:
|
|
197
|
+
server = ApplicationServer(app)
|
|
198
|
+
await server.run_async()
|
|
199
|
+
"""
|
|
200
|
+
import uvicorn
|
|
201
|
+
|
|
202
|
+
config = self.get_config()
|
|
203
|
+
self.server = uvicorn.Server(config)
|
|
204
|
+
|
|
205
|
+
# 记录启动信息
|
|
206
|
+
self._log_startup_info()
|
|
207
|
+
|
|
208
|
+
# 运行服务器
|
|
209
|
+
try:
|
|
210
|
+
await self.server.serve()
|
|
211
|
+
except KeyboardInterrupt:
|
|
212
|
+
logger.info("服务器已停止")
|
|
213
|
+
except Exception as exc:
|
|
214
|
+
logger.error(f"服务器错误: {exc}", exc_info=True)
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
def _setup_signal_handlers(self) -> None:
|
|
218
|
+
"""设置信号处理器。
|
|
219
|
+
|
|
220
|
+
处理 SIGTERM 和 SIGINT 信号,确保优雅关闭。
|
|
221
|
+
"""
|
|
222
|
+
def handle_signal(signum, frame):
|
|
223
|
+
logger.info(f"收到信号 {signum},正在关闭...")
|
|
224
|
+
if self.server:
|
|
225
|
+
self.server.should_exit = True
|
|
226
|
+
|
|
227
|
+
signal.signal(signal.SIGINT, handle_signal)
|
|
228
|
+
signal.signal(signal.SIGTERM, handle_signal)
|
|
229
|
+
|
|
230
|
+
def _log_startup_info(self) -> None:
|
|
231
|
+
"""记录启动信息。"""
|
|
232
|
+
protocol = "https" if self.ssl_certfile else "http"
|
|
233
|
+
url = f"{protocol}://{self.host}:{self.port}"
|
|
234
|
+
|
|
235
|
+
logger.info(
|
|
236
|
+
f"服务器启动 | "
|
|
237
|
+
f"地址: {url} | "
|
|
238
|
+
f"工作进程: {self.workers} | "
|
|
239
|
+
f"热重载: {self.reload}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if self.debug:
|
|
243
|
+
logger.warning("调试模式已启用")
|
|
244
|
+
|
|
245
|
+
if self.reload:
|
|
246
|
+
default_dirs = [os.getcwd()] if not self.reload_dirs else self.reload_dirs
|
|
247
|
+
logger.info(f"热重载监控目录: {default_dirs}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def run_app(
|
|
251
|
+
app: FoundationApp,
|
|
252
|
+
*,
|
|
253
|
+
host: str | None = None,
|
|
254
|
+
port: int | None = None,
|
|
255
|
+
workers: int | None = None,
|
|
256
|
+
reload: bool = False,
|
|
257
|
+
**kwargs,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""快速运行应用。
|
|
260
|
+
|
|
261
|
+
从环境变量或参数读取配置。
|
|
262
|
+
|
|
263
|
+
使用示例:
|
|
264
|
+
from aury.boot.application.server import run_app
|
|
265
|
+
|
|
266
|
+
app = FoundationApp()
|
|
267
|
+
run_app(app)
|
|
268
|
+
|
|
269
|
+
环境变量支持:
|
|
270
|
+
SERVER_HOST: 监听地址
|
|
271
|
+
SERVER_PORT: 监听端口
|
|
272
|
+
SERVER_WORKERS: 工作进程数
|
|
273
|
+
SERVER_RELOAD: 是否热重载
|
|
274
|
+
"""
|
|
275
|
+
# 从环境变量读取默认值
|
|
276
|
+
host = host or os.getenv("SERVER_HOST", "127.0.0.1")
|
|
277
|
+
port = port or int(os.getenv("SERVER_PORT", "8000"))
|
|
278
|
+
workers = workers or int(os.getenv("SERVER_WORKERS", "1"))
|
|
279
|
+
reload = reload or os.getenv("SERVER_RELOAD", "").lower() in ("true", "1", "yes")
|
|
280
|
+
|
|
281
|
+
server = ApplicationServer(
|
|
282
|
+
app=app,
|
|
283
|
+
host=host,
|
|
284
|
+
port=port,
|
|
285
|
+
workers=workers,
|
|
286
|
+
reload=reload,
|
|
287
|
+
**kwargs,
|
|
288
|
+
)
|
|
289
|
+
server.run()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
__all__ = [
|
|
293
|
+
"ApplicationServer",
|
|
294
|
+
"run_app",
|
|
295
|
+
]
|
|
296
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""命令行工具模块。
|
|
2
|
+
|
|
3
|
+
统一入口: aum
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
# 轻量配置模块可以直接导入
|
|
9
|
+
from .config import ProjectConfig, get_project_config, save_project_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# 延迟导入 app 和 main,避免加载重型依赖
|
|
13
|
+
def __getattr__(name: str):
|
|
14
|
+
if name in ("app", "main"):
|
|
15
|
+
from .app import main as _main
|
|
16
|
+
if name == "main":
|
|
17
|
+
return _main
|
|
18
|
+
# app 通过 app 模块的 __getattr__ 获取
|
|
19
|
+
from . import app as app_module
|
|
20
|
+
return app_module.app
|
|
21
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"app",
|
|
26
|
+
"main",
|
|
27
|
+
"ProjectConfig",
|
|
28
|
+
"get_project_config",
|
|
29
|
+
"save_project_config",
|
|
30
|
+
]
|