"""应用装配层:日志、图谱初始化、服务启动。 main.py 只做一件事——调用本模块的 run()。分层关系: main.py 唯一入口(装配) └─ app.bootstrap 启动流程(本模块) ├─ app.config 配置加载(config.yaml + config.json) ├─ app.services 端到端 pipeline 编排 └─ app.api.routes FastAPI 接口层(uvicorn 托管) """ from __future__ import annotations import logging import threading import time import webbrowser import uvicorn from app.config import load_config, resolve logger = logging.getLogger(__name__) def setup_logging() -> None: """全局日志:时间 | 级别 | 模块 | 消息。""" logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", ) def ensure_graph(cfg: dict) -> None: """图谱不存在时先跑一遍端到端 pipeline 构建。""" graph_path = resolve(cfg, "graph_path") if graph_path.exists(): logger.info("检测到已有图谱 %s,直接加载", graph_path) return logger.info("图谱不存在,先执行端到端 pipeline 构建……") from app.services.pipeline import run_pipeline stats = run_pipeline(cfg) logger.info("图谱构建完成:%s", stats["stages"].get("graph")) def _open_browser_later(url: str, delay: float = 1.5) -> None: time.sleep(delay) try: webbrowser.open(url) except Exception as exc: # 浏览器打不开不影响服务 logger.warning("自动打开浏览器失败:%s,请手动访问 %s", exc, url) def run() -> None: """启动流程:日志 → 配置 → 图谱 → 服务 → 自动开浏览器。""" setup_logging() cfg = load_config() ensure_graph(cfg) host, port = str(cfg["host"]), int(cfg["port"]) url = f"http://{host}:{port}" logger.info("CareerNexus 职联启动中:%s", url) threading.Thread(target=_open_browser_later, args=(url,), daemon=True).start() uvicorn.run("app.api.routes:app", host=host, port=port, log_level="info")