Files
2026-07-13 12:34:57 +08:00

836 lines
32 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
MyBoot 应用程序主类
提供类似 Spring Boot 的自动配置和快速启动功能
"""
import asyncio
import os
import signal
from contextlib import asynccontextmanager
from typing import Any, Callable, Dict, List, Optional, Type
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from loguru import logger
from .auto_configuration import auto_discover, apply_auto_configuration
from .config import get_settings
from .container import Container
from .logger import setup_logging
from .scheduler import Scheduler
from .server import ServerManager
from ..exceptions import MyBootException
from ..utils import get_local_ip
from ..web.middleware import Middleware
# 全局应用实例注册表(用于在路由函数中获取当前应用实例)
_current_app: Optional['Application'] = None
def app() -> 'Application':
"""获取当前应用实例"""
if _current_app is None:
raise RuntimeError("应用实例未初始化,请确保应用已创建并启动")
return _current_app
class Application:
"""MyBoot 应用程序主类"""
def __init__(
self,
name: str = "MyBoot App",
config_file: Optional[str] = None,
**kwargs
):
"""
初始化应用程序
Args:
name: 应用程序名称
config_file: 配置文件路径
**kwargs: 其他配置参数
"""
self.name = name
self.config = get_settings(config_file)
# 应用配置
self._apply_config(kwargs)
# 获取应用版本号(从配置文件读取,默认 0.0.1)
self.version = self.config.get("app.version", "0.0.1")
# 初始化 loguru 日志系统(包括第三方库日志级别配置)
setup_logging(self.config)
# Prometheus 多进程指标环境(须在 prometheus_client 被 import 之前配置)
from ..metrics import setup_multiproc_env
setup_multiproc_env(self.config, self.name)
self.logger = logger.bind(name=self.name)
# Worker 信息(多进程模式下由环境变量设置)
self._worker_id = int(os.environ.get("MYBOOT_WORKER_ID", "1"))
self._worker_count = int(os.environ.get("MYBOOT_WORKER_COUNT", "1"))
self._is_primary_worker = os.environ.get("MYBOOT_IS_PRIMARY_WORKER", "1") == "1"
# Scheduler 默认只在 primary worker 启动(可通过 scheduler.on_all_workers
# 全局配置覆盖)。任务级 all_workers=True 的任务在非 primary worker 注册时,
# 注册门控会调用 scheduler.enable() 按需启用调度器实例。
scheduler_on_all_workers = self.config.get("scheduler.on_all_workers", False)
scheduler_globally_enabled = self.config.get("scheduler.enabled", True)
self._scheduler_enabled = (
self._is_primary_worker or scheduler_on_all_workers
) and scheduler_globally_enabled
self.scheduler = Scheduler(config=self.config, enabled=self._scheduler_enabled)
# 中间件列表
self.middlewares: List[Middleware] = []
# 路由处理器
self.route_handlers: Dict[str, Callable] = {}
# 服务注册表
self.services: Dict[str, Any] = {}
# 模型注册表
self.models: Dict[str, Any] = {}
# 客户端注册表
self.clients: Dict[str, Any] = {}
# 组件注册表
self.components: Dict[str, Any] = {}
# 统一容器接口(支持从 container、services、clients 中获取实例)
self.container = Container(self)
# 启动钩子
self.startup_hooks: List[Callable] = []
self.shutdown_hooks: List[Callable] = []
# Worker 生命周期钩子(@on_worker_start / @on_worker_stop
# 每个 worker 进程的 lifespan 中各触发一次;单 worker 模式下也触发一次
self.worker_start_hooks: List[Callable] = []
self.worker_stop_hooks: List[Callable] = []
# FastAPI 应用实例
self._fastapi_app: Optional[FastAPI] = None
# 服务器实例
self._server: Optional[Any] = None
# 注册信号处理器
self._register_signal_handlers()
# 创建 FastAPI 应用
self._fastapi_app = self._create_fastapi_app()
# 自动配置标志
self.auto_configuration_enabled = kwargs.get('auto_configuration', True)
self.auto_discover_package = kwargs.get('auto_discover_package', 'app')
# 服务器管理器
self.server_manager = ServerManager()
# 注册为当前应用实例
global _current_app
_current_app = self
def _apply_config(self, kwargs: Dict[str, Any]) -> None:
"""应用配置参数"""
for key, value in kwargs.items():
self.config.set(key, value)
def _register_signal_handlers(self) -> None:
"""注册信号处理器"""
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def _signal_handler(self, signum: int, frame) -> None:
"""信号处理器"""
self.logger.info(f"收到信号 {signum},开始优雅关闭...")
asyncio.create_task(self.shutdown())
def add_middleware(self, middleware: Middleware) -> None:
"""添加中间件"""
self.middlewares.append(middleware)
self.logger.debug(f"已添加中间件: {middleware.__class__.__name__}")
def add_startup_hook(self, hook: Callable) -> None:
"""添加启动钩子"""
self.startup_hooks.append(hook)
self.logger.debug(f"已添加启动钩子: {hook.__name__}")
def add_shutdown_hook(self, hook: Callable) -> None:
"""添加关闭钩子"""
self.shutdown_hooks.append(hook)
self.logger.debug(f"已添加关闭钩子: {hook.__name__}")
def add_worker_start_hook(self, hook: Callable) -> None:
"""添加 worker 启动钩子
在每个 worker 进程的 lifespan 启动阶段触发(startup_hooks 之后、
调度器启动之前),单 worker 模式下也触发一次。
"""
self.worker_start_hooks.append(hook)
self.logger.debug(f"已添加 worker 启动钩子: {hook.__name__}")
def add_worker_stop_hook(self, hook: Callable) -> None:
"""添加 worker 停止钩子
在每个 worker 进程的 lifespan 关闭阶段触发(调度器停止之后、
shutdown_hooks 之前)。
注意:Windows 多 worker 模式下父进程通过 terminate()(硬终止)
清理 worker 进程,lifespan 关闭阶段可能不会执行,
因此 worker 停止钩子在 Windows 上不保证触发。
"""
self.worker_stop_hooks.append(hook)
self.logger.debug(f"已添加 worker 停止钩子: {hook.__name__}")
def register_service(self, name: str, service: Any) -> None:
"""注册服务"""
self.services[name] = service
self.logger.debug(f"已注册服务: {name}")
def get_service(self, name: str) -> Any:
"""获取服务
优先返回 app.services 中的单例实例;非单例(request/factory)服务
不在 app.services 中预存,回退到 DI 容器按需解析。
"""
if name in self.services:
return self.services[name]
di_container = getattr(self, 'di_container', None)
if di_container is not None and di_container.has_service(name):
return di_container.get_service(name)
return None
def has_service(self, name: str) -> bool:
"""检查是否有服务"""
return name in self.services
def get_client(self, name: str) -> Any:
"""获取客户端"""
return self.clients.get(name)
def has_client(self, name: str) -> bool:
"""检查是否有客户端"""
return name in self.clients
def get_component(self, name: str) -> Any:
"""获取组件"""
return self.components.get(name)
def has_component(self, name: str) -> bool:
"""检查是否有组件"""
return name in self.components
def route(
self,
path: str,
methods: Optional[List[str]] = None,
**kwargs
) -> Callable:
"""
装饰器:注册路由
Args:
path: 路由路径
methods: HTTP 方法列表
**kwargs: 其他 FastAPI 路由参数
"""
if methods is None:
methods = ["GET"]
def decorator(func: Callable) -> Callable:
# 存储路由处理器
route_key = f"{','.join(methods)}:{path}"
self.route_handlers[route_key] = func
self.logger.debug(f"已注册路由: {methods} {path} -> {func.__name__}")
return func
return decorator
def get(self, path: str, **kwargs) -> Callable:
"""GET 路由装饰器"""
return self.route(path, ["GET"], **kwargs)
def post(self, path: str, **kwargs) -> Callable:
"""POST 路由装饰器"""
return self.route(path, ["POST"], **kwargs)
def put(self, path: str, **kwargs) -> Callable:
"""PUT 路由装饰器"""
return self.route(path, ["PUT"], **kwargs)
def delete(self, path: str, **kwargs) -> Callable:
"""DELETE 路由装饰器"""
return self.route(path, ["DELETE"], **kwargs)
def patch(self, path: str, **kwargs) -> Callable:
"""PATCH 路由装饰器"""
return self.route(path, ["PATCH"], **kwargs)
def _create_fastapi_app(self) -> FastAPI:
"""创建 FastAPI 应用实例"""
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 执行启动钩子
for hook in self.startup_hooks:
try:
if asyncio.iscoroutinefunction(hook):
await hook()
else:
hook()
except Exception as e:
self.logger.error(f"启动钩子执行失败: {e}", exc_info=True)
# 执行 worker 启动钩子(每个 worker 进程的 lifespan 各触发一次,
# 位于 startup_hooks 之后、调度器启动之前)
for hook in self.worker_start_hooks:
try:
if asyncio.iscoroutinefunction(hook):
await hook()
else:
hook()
except Exception as e:
self.logger.error(f"Worker 启动钩子执行失败: {e}", exc_info=True)
# 启动调度器
if self.scheduler.has_jobs():
self.scheduler.start()
self.logger.info("✅ 任务调度器已启动")
yield
# 关闭
self.logger.info(f"🛑 关闭 {self.name}...")
# 停止调度器
if self.scheduler.is_running():
self.scheduler.stop()
self.logger.info("✅ 任务调度器已停止")
# 执行 worker 停止钩子(调度器停止之后、shutdown_hooks 之前)
# 注意:Windows 多 worker 模式下父进程使用 terminate() 硬终止
# workerlifespan 关闭阶段可能不执行,stop 钩子不保证触发
for hook in self.worker_stop_hooks:
try:
if asyncio.iscoroutinefunction(hook):
await hook()
else:
hook()
except Exception as e:
self.logger.error(f"Worker 停止钩子执行失败: {e}", exc_info=True)
# 自动关闭 client:对具有 close() 方法的 client 实例兜底调用
# worker_stop_hooks 之后,shutdown_hooks 之前)。
# 应用若已自行 close,建议实现为幂等;二次 close 的异常降为 warning
for client_name, client_instance in list(self.clients.items()):
close_method = getattr(client_instance, "close", None)
if not callable(close_method):
continue
try:
if asyncio.iscoroutinefunction(close_method):
await close_method()
else:
close_method()
self.logger.debug(f"Client 已自动关闭: {client_name}")
except Exception as e:
self.logger.warning(f"Client '{client_name}' close() 异常(已忽略): {e}")
# 执行关闭钩子
for hook in self.shutdown_hooks:
try:
if asyncio.iscoroutinefunction(hook):
await hook()
else:
hook()
except Exception as e:
self.logger.error(f"关闭钩子执行失败: {e}", exc_info=True)
# Prometheus multiproc:标记本进程退出,清理 gauge 残留文件
# (内部已兜底 try/except,此处再防御一层确保关闭流程不被打断)
try:
from ..metrics import mark_current_process_dead
mark_current_process_dead()
except Exception:
pass
# 创建 FastAPI 应用
app = FastAPI(
title=self.name,
version=self.version,
lifespan=lifespan,
)
# 添加 CORS 中间件(如果配置了 server.cors
cors_config = self.config.get("server.cors")
if cors_config:
app.add_middleware(
CORSMiddleware,
allow_origins=cors_config.get("allow_origins", ["*"]),
allow_credentials=cors_config.get("allow_credentials", True),
allow_methods=cors_config.get("allow_methods", ["*"]),
allow_headers=cors_config.get("allow_headers", ["*"]),
)
self.logger.debug("CORS 中间件已启用")
# Prometheus 指标(metrics.enabled 时启用,可选依赖缺失只告警不报错)
from ..metrics import is_available as metrics_available, is_enabled as metrics_enabled
metrics_path = str(self.config.get("metrics.path", "/metrics") or "/metrics")
metrics_ready = False
if metrics_enabled(self.config):
if metrics_available():
metrics_ready = True
from ..metrics import _coerce_bool
if _coerce_bool(self.config.get("metrics.http_metrics", True), True):
from ..metrics import HttpMetricsMiddleware
app.add_middleware(HttpMetricsMiddleware, metrics_path=metrics_path)
self.logger.debug("HTTP 指标中间件已启用")
else:
self.logger.warning(
"metrics.enabled=true 但未安装 prometheus-client"
"指标功能不可用。安装方式: pip install myboot[metrics]"
)
# 添加自定义中间件
for middleware in self.middlewares:
app.add_middleware(middleware.middleware_class, **middleware.kwargs)
# 添加响应格式化中间件(最后添加,因为它会最先执行)
# FastAPI 中间件是后进先出(LIFO),所以最后添加的中间件会最先处理响应
response_format_enabled = self.config.get("server.response_format.enabled", True)
if response_format_enabled:
from myboot.web.middleware import ResponseFormatterMiddleware
exclude_paths = list(self.config.get("server.response_format.exclude_paths", []) or [])
if metrics_ready:
# metrics 端点输出 Prometheus 文本格式,不做 JSON 包装
exclude_paths.append(metrics_path)
app.add_middleware(
ResponseFormatterMiddleware,
exclude_paths=exclude_paths,
auto_wrap=True
)
self.logger.debug("响应格式化中间件已启用")
# 注册路由
self._register_routes(app)
# 注册异常处理器
self._register_exception_handlers(app)
# 添加健康检查端点
self._add_health_endpoints(app)
# 挂载 Prometheus 指标端点(懒初始化,首次请求才 import prometheus_client
if metrics_ready:
from ..metrics import make_metrics_asgi_app
app.mount(metrics_path, make_metrics_asgi_app())
self.logger.debug(f"Prometheus 指标端点已挂载: {metrics_path}")
return app
def _register_routes(self, app: FastAPI) -> None:
"""注册路由到 FastAPI 应用"""
for route_key, handler in self.route_handlers.items():
methods, path = route_key.split(":", 1)
method_list = methods.split(",")
# 添加路由到 FastAPI
app.add_api_route(
path,
handler,
methods=method_list,
name=handler.__name__
)
def _register_exception_handlers(self, app: FastAPI) -> None:
"""注册异常处理器"""
def _extract_error_messages(errors: list) -> list:
"""从错误列表中提取错误消息"""
messages = []
for error in errors:
msg = error.get('msg', 'Validation Error')
# 移除 "Value error, " 前缀
if msg.startswith('Value error, '):
msg = msg[len('Value error, '):]
messages.append(msg)
return messages
@app.exception_handler(MyBootException)
async def myboot_exception_handler(request: Request, exc: MyBootException):
"""MyBoot 异常处理器"""
self.logger.error(f"MyBoot 异常: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"success": False,
"code": 500,
"message": "Internal Server Error",
"data": {
"type": exc.__class__.__name__
}
}
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""HTTP 异常处理器"""
self.logger.warning(f"HTTP 异常: {exc.status_code} - {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"code": exc.status_code,
"message": "HTTP Error"
}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""请求验证异常处理器 - 返回友好的错误信息"""
errors = exc.errors()
# 只提取错误消息
error_messages = _extract_error_messages(errors)
# 使用第一个错误消息作为主要错误信息
error_msg = error_messages[0] if error_messages else 'Validation Error'
self.logger.warning(f"请求验证失败: {error_messages}")
return JSONResponse(
status_code=422,
content={
"success": False,
"code": 422,
"message": error_msg,
"data": {
"fieldErrors": error_messages
}
}
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""全局异常处理器"""
self.logger.error(f"未处理的异常: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"success": False,
"code": 500,
"message": "Internal Server Error",
"data": {
"type": exc.__class__.__name__
}
}
)
def _add_health_endpoints(self, app: FastAPI) -> None:
"""添加健康检查端点"""
@app.get("/health")
async def health_check():
"""健康检查端点"""
return {
"status": "healthy",
"app": self.name,
"version": self.version,
"uptime": "running"
}
@app.get("/health/ready")
async def readiness_check():
"""就绪检查端点"""
return {
"status": "ready",
"app": self.name,
"services": {
"scheduler": self.scheduler.is_running() if self.scheduler.has_jobs() else "disabled"
}
}
@app.get("/health/live")
async def liveness_check():
"""存活检查端点"""
return {
"status": "alive",
"app": self.name
}
def run(
self,
host: str = "0.0.0.0",
port: int = 8000,
reload: bool = False,
workers: int = 1,
app_path: Optional[str] = None,
**kwargs
) -> None:
"""
运行应用程序
Args:
host: 主机地址
port: 端口号
reload: 是否开启热重载
workers: 工作进程数
app_path: 应用模块路径(多 workers 模式必需),格式为 "module.path:app_name"
例如: "main:app" 表示从 main.py 导入 app 变量
如果 app 是通过 get_fastapi_app() 获取,使用 "main:app.get_fastapi_app()"
**kwargs: 其他服务器参数
Example:
# 单进程模式(默认)
app.run(host="0.0.0.0", port=8000)
# 多进程模式(4个 workers
app.run(
host="0.0.0.0",
port=8000,
workers=4,
app_path="main:app.get_fastapi_app()"
)
"""
# 从配置中获取参数
host = self.config.get("server.host", host)
port = self.config.get("server.port", port)
reload = self.config.get("server.reload", reload)
workers = self.config.get("server.workers", workers)
app_path = self.config.get("server.app_path", app_path)
# 白名单方式收集 server.* 中的 Hypercorn 配置
# 别名转换(如 max_incomplete_request_size)由 server.py 的 HYPERCORN_CONFIG_ALIASES 统一处理
server_section = self.config.get("server") or {}
server_kwargs = {}
for k in ("backlog", "read_timeout", "keep_alive_timeout", "graceful_timeout",
"reloader", "use_reloader", "max_incomplete_request_size"):
if k in server_section:
server_kwargs[k] = server_section[k]
# 合并:配置文件 < run(**kwargs) 覆盖
run_kwargs = {**server_kwargs, **kwargs}
# 自动发现和配置
# 多 worker 模式(workers > 1 且提供了 app_path)下,父进程只做 AST 自动发现
# (无副作用,预热 .myboot_cache_*.json 供子进程使用),实例化(apply_auto_configuration
# 延迟到每个 worker 进程内的 bootstrap_worker() 执行,
# 避免 fork 模式下所有 worker 共享父进程预先创建的 client/service 实例(issue #11)。
# 单 worker 模式与缺少 app_path 的回退路径(父进程自己 serve)保持原有行为不变。
if self.auto_configuration_enabled:
self.logger.info("🔍 开始自动发现组件...")
auto_discover(self.auto_discover_package)
if workers > 1 and app_path:
self.logger.info("⏩ 多 worker 模式:自动配置延迟到各 worker 进程内执行")
else:
apply_auto_configuration(self)
# 获取真实 IP 用于日志显示(服务器仍然使用配置的 host 绑定)
display_host = get_local_ip() if host == "0.0.0.0" else host
# 显示服务器信息
self.logger.info(f"🌐 服务器启动: http://{display_host}:{port}")
self.logger.info(f"📚 API 文档: http://{display_host}:{port}/docs")
self.logger.info(f"🔍 健康检查: http://{display_host}:{port}/health")
self.logger.info(f"⚙️ 服务器类型: Hypercorn")
self.logger.info(f"🔧 工作进程: {workers}")
if workers > 1 and not app_path:
self.logger.warning(
"⚠️ 多 workers 模式需要提供 app_path 参数,"
"例如: app.run(workers=4, app_path='main:app.get_fastapi_app()')"
)
# 启动服务器
try:
self.server_manager.start_server(
app=self._fastapi_app,
host=host,
port=port,
reload=reload,
workers=workers,
app_path=app_path,
**run_kwargs
)
except KeyboardInterrupt:
self.logger.info("收到中断信号,正在关闭...")
finally:
asyncio.run(self.shutdown())
async def shutdown(self) -> None:
"""优雅关闭应用程序"""
if self._server:
# 服务器关闭逻辑
pass
# 停止调度器
if self.scheduler.is_running():
self.scheduler.stop()
self.logger.info("应用程序已关闭")
def add_route(self, path: str, handler: Callable, methods: List[str] = None, **kwargs) -> None:
"""添加路由到 FastAPI 应用"""
if self._fastapi_app is None:
self._fastapi_app = self._create_fastapi_app()
if methods is None:
methods = ['GET']
# 使用 FastAPI 的 add_api_route 方法
self._fastapi_app.add_api_route(path, handler, methods=methods, **kwargs)
def get_fastapi_app(self) -> FastAPI:
"""获取 FastAPI 应用实例"""
if self._fastapi_app is None:
self._fastapi_app = self._create_fastapi_app()
return self._fastapi_app
def bootstrap_worker(self) -> FastAPI:
"""Worker 进程内引导(多 worker 模式下由 server._worker_serve 调用)
spawnWindows)与 forkLinux/macOS)两种模式在此收敛:
1. 重读 MYBOOT_WORKER_ID 等环境变量——它们在 _worker_serve 中设置,
晚于 Application 构造(spawn 子进程在 multiprocessing 引导阶段
重新 import 用户主模块时即构造 Application),因此 __init__ 读到
的是默认值,必须在这里刷新。
2. 重算 scheduler 门控并重建 Scheduler——修复「每个 worker 都自认
primary、调度器多跑」的问题;同时丢弃 fork 继承的、绑定到
fork 前实例的任务注册。
3. 防御性重置——若检测到 fork 继承的预引导状态(父进程曾手动执行
apply_auto_configuration),清空 DI 容器与各注册表并重建
FastAPI 应用,丢弃绑定到 fork 前控制器实例的路由。
4. 在 worker 进程内执行自动配置——所有 client/service/component/
controller 在本 worker 内实例化(issue #11 核心修复),同时修复
Windows spawn 模式下 worker 无用户路由的问题。
Returns:
本 worker 进程内完成引导的 FastAPI 应用实例
"""
# 1. 重读 worker 环境变量
self._worker_id = int(os.environ.get("MYBOOT_WORKER_ID", "1"))
self._worker_count = int(os.environ.get("MYBOOT_WORKER_COUNT", "1"))
self._is_primary_worker = os.environ.get("MYBOOT_IS_PRIMARY_WORKER", "1") == "1"
# 2. 重算调度器门控并重建调度器(默认仅 primary worker 启用,
# 可通过 scheduler.on_all_workers 配置覆盖;任务级 all_workers=True
# 的任务在本 worker 注册时由注册门控按需调用 scheduler.enable()
scheduler_on_all_workers = self.config.get("scheduler.on_all_workers", False)
scheduler_globally_enabled = self.config.get("scheduler.enabled", True)
self._scheduler_enabled = (
self._is_primary_worker or scheduler_on_all_workers
) and scheduler_globally_enabled
self.scheduler = Scheduler(config=self.config, enabled=self._scheduler_enabled)
# 3. 防御性重置:fork 子进程若继承了父进程已引导的状态
# (正常延迟引导路径下这些注册表都是空的,不会进入此分支)
di_container = getattr(self, 'di_container', None)
inherited = bool(
(di_container is not None and di_container.service_providers)
or self.services or self.clients or self.components
)
if inherited:
self.logger.warning(
"检测到 fork 继承的预引导状态,重置注册表后在 worker 内重建实例..."
)
if di_container is not None:
di_container.clear()
self.services.clear()
self.clients.clear()
self.components.clear()
if hasattr(self, '_client_type_map'):
self._client_type_map.clear()
if hasattr(self, '_component_type_map'):
self._component_type_map.clear()
# 注意:worker 钩子由 _auto_register_worker_hooks 重新注册,
# 先清空避免重复(此分支下父进程已经 apply 过一次)
self.worker_start_hooks.clear()
self.worker_stop_hooks.clear()
# 重建 FastAPI 应用,丢弃绑定到 fork 前控制器实例的路由
self._fastapi_app = self._create_fastapi_app()
# 4. 在 worker 进程内执行自动配置
# - spawn 子进程:全局管理器是全新的,auto_discover 执行扫描(命中父进程缓存)
# - fork 子进程:管理器已发现完成,auto_discover 幂等守卫直接返回
if self.auto_configuration_enabled:
self.logger.info(f"🔍 Worker-{self._worker_id} 开始进程内引导...")
auto_discover(self.auto_discover_package)
apply_auto_configuration(self)
return self.get_fastapi_app()
# ==================== Worker 信息 ====================
@property
def worker_id(self) -> int:
"""
获取当前 Worker ID(从 1 开始)
单进程模式下返回 1
"""
return self._worker_id
@property
def worker_count(self) -> int:
"""
获取 Worker 总数
单进程模式下返回 1
"""
return self._worker_count
@property
def is_primary_worker(self) -> bool:
"""
是否为主 WorkerWorker ID = 1
适用于只需要在一个 worker 执行的任务(如定时任务、初始化任务)
Example:
if app.is_primary_worker:
# 只在主 worker 执行
await init_cache()
"""
return self._is_primary_worker
@property
def is_multi_worker_mode(self) -> bool:
"""是否运行在多 Worker 模式"""
return self._worker_count > 1
# 便捷函数
def create_app(
name: str = "MyBoot App",
config_file: Optional[str] = None,
**kwargs
) -> Application:
"""创建 MyBoot 应用程序实例"""
return Application(name, config_file, **kwargs)
def get_service(name: str):
return _current_app.get_service(name)
def get_client(name: str):
return _current_app.get_client(name)
def get_container() -> Container:
"""获取容器实例"""
if _current_app is None:
raise RuntimeError("应用实例未初始化,请确保应用已创建并启动")
return _current_app.container