134 lines
4.4 KiB
Python
134 lines
4.4 KiB
Python
import errno
|
|
import logging
|
|
import socket
|
|
from contextlib import asynccontextmanager
|
|
|
|
import uvicorn
|
|
from fastapi import FastAPI
|
|
|
|
try:
|
|
import psutil
|
|
except Exception: # pragma: no cover - optional runtime dependency for richer diagnostics
|
|
psutil = None
|
|
|
|
from config import Config
|
|
from workflows.workflow_manager import WorkflowManager
|
|
from services.integrations.nacos_service import load_nacos_config, load_service_config, NacosManager
|
|
from services.tools.tool_router import ToolRouter
|
|
from services.core.prompt_manager import get_prompt_manager
|
|
from api import endpoints
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _find_listening_process_on_port(port: int) -> dict | None:
|
|
if psutil is None:
|
|
return None
|
|
|
|
try:
|
|
for conn in psutil.net_connections(kind="tcp"):
|
|
local_port = getattr(getattr(conn, "laddr", None), "port", None)
|
|
if local_port != port or conn.status != psutil.CONN_LISTEN:
|
|
continue
|
|
|
|
pid = conn.pid
|
|
if pid is None:
|
|
return {"pid": None, "process_name": None, "cmdline": None}
|
|
|
|
try:
|
|
proc = psutil.Process(pid)
|
|
return {
|
|
"pid": pid,
|
|
"process_name": proc.name(),
|
|
"cmdline": " ".join(proc.cmdline()),
|
|
}
|
|
except Exception:
|
|
return {"pid": pid, "process_name": None, "cmdline": None}
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
|
|
def _ensure_port_available(host: str, port: int) -> None:
|
|
probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
probe.bind((host, port))
|
|
except OSError as exc:
|
|
if exc.errno == errno.EADDRINUSE or getattr(exc, "winerror", None) == 10048:
|
|
process_info = _find_listening_process_on_port(port) or {}
|
|
owner_bits = []
|
|
if process_info.get("pid") is not None:
|
|
owner_bits.append(f"pid={process_info['pid']}")
|
|
if process_info.get("process_name"):
|
|
owner_bits.append(f"name={process_info['process_name']}")
|
|
if process_info.get("cmdline"):
|
|
owner_bits.append(f"cmdline={process_info['cmdline']}")
|
|
|
|
logger.error(
|
|
"端口占用,服务无法启动: host=%s port=%s config=%s%s",
|
|
host,
|
|
port,
|
|
Config._get_config_path(),
|
|
f" owner=({' | '.join(owner_bits)})" if owner_bits else "",
|
|
)
|
|
logger.error("请先停止占用该端口的进程,或修改 `config/config.ini` 中 `[app].port`。")
|
|
raise SystemExit(1) from exc
|
|
raise
|
|
finally:
|
|
probe.close()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""创建 FastAPI 应用"""
|
|
service_config = load_service_config()
|
|
nacos_config = load_nacos_config()
|
|
default_model_section = Config._config.get("app", "model_section", fallback=Config.DEFAULT_MODEL_SECTION)
|
|
|
|
workflow_manager = WorkflowManager(default_model_section=default_model_section)
|
|
nacos_manager = NacosManager(nacos_config=nacos_config, service_config=service_config)
|
|
tool_router = ToolRouter()
|
|
prompt_manager = get_prompt_manager()
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""应用生命周期管理"""
|
|
logger.info("🚀 服务启动中...")
|
|
app.state.workflow_manager = workflow_manager
|
|
app.state.nacos_manager = nacos_manager
|
|
app.state.service_config = service_config
|
|
app.state.tool_router = tool_router
|
|
app.state.prompt_manager = prompt_manager
|
|
|
|
await nacos_manager.start()
|
|
logger.info("✅ 服务准备就绪: %s on %s:%s", service_config.service_name, service_config.ip, service_config.port)
|
|
yield
|
|
logger.info("🛑 服务关闭中...")
|
|
await nacos_manager.stop()
|
|
logger.info("✅ 服务已关闭")
|
|
|
|
app = FastAPI(
|
|
title="More Dots API",
|
|
description="More Dots 工作流服务",
|
|
version=service_config.metadata.get("version", "1.0.0"),
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.include_router(endpoints.router)
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
service_config = load_service_config()
|
|
_ensure_port_available(service_config.host, service_config.port)
|
|
uvicorn.run(
|
|
"server:app",
|
|
host=service_config.host,
|
|
port=service_config.port,
|
|
reload=False,
|
|
log_level="info",
|
|
)
|