diff --git a/app/config/paths.py b/app/config/paths.py index 185b174..c01b282 100644 --- a/app/config/paths.py +++ b/app/config/paths.py @@ -18,6 +18,14 @@ DBN_CONFIG_DIR = CONFIG_DIR / "dbn" def get_logger(name: str = "algorithm"): - """获取日志记录器(loguru 不可用时自动回退标准 logging)""" + """ + 获取日志记录器的便捷函数 + + Args: + name: 日志名称 + + Returns: + logging.Logger 实例 + """ from app.utils.logger import LoggerManager return LoggerManager.get_logger(name, str(LOG_DIR)) diff --git a/app/services/qgis/qgis_env.py b/app/services/qgis/qgis_env.py index 9545032..615e9f1 100644 --- a/app/services/qgis/qgis_env.py +++ b/app/services/qgis/qgis_env.py @@ -11,6 +11,7 @@ QGIS 环境检测与子进程配置模块。 - get_runner_script(): 获取 qgis_runner.py 的路径 """ import os +import tempfile from pathlib import Path from app.config.paths import get_logger @@ -73,32 +74,29 @@ def get_qgis_python_path(qgis_root: str = None) -> str | None: def build_qgis_command(qgis_root: str = None) -> list[str]: """ - 构建通过静态启动脚本运行 QGIS 子进程的命令列表。 - - Windows: 调用 app/script/run_qgis.bat(通过 QGIS_ROOT 环境变量定位) - Linux: 直接调用 QGIS Python 解释器 + 构建通过 .bat 包装器启动 QGIS 子进程的命令列表。 + 设置环境变量并启动 QGIS Python 3.12。 """ import platform + if platform.system() != "Windows": + python_path = get_qgis_python_path(qgis_root) + if not python_path: + raise RuntimeError("未找到 QGIS Python 解释器") + return [python_path, get_runner_script()] + if qgis_root is None: qgis_root = _detect_qgis_root() or "D:/QGIS" + python_path = get_qgis_python_path(qgis_root) + if not python_path: + raise RuntimeError("未找到 QGIS Python 3.12 解释器") + runner_script = get_runner_script() if not os.path.isfile(runner_script): raise RuntimeError(f"QGIS Runner 脚本不存在: {runner_script}") - if platform.system() == "Windows": - script_dir = Path(__file__).parent.parent.parent / "script" - bat_path = str(script_dir / "run_qgis.bat") - if not os.path.isfile(bat_path): - raise RuntimeError(f"QGIS 启动脚本不存在: {bat_path}") - # 设置环境变量让 .bat 能找到 QGIS - os.environ["QGIS_ROOT"] = qgis_root - return ["cmd.exe", "/c", bat_path, runner_script] - else: - python_path = get_qgis_python_path(qgis_root) - if not python_path: - raise RuntimeError("未找到 QGIS Python 解释器") - return [python_path, runner_script] + bat_path = _generate_bat_wrapper(qgis_root, python_path, runner_script) + return ["cmd.exe", "/c", bat_path] def is_qgis_available(qgis_root: str = None) -> bool: @@ -111,6 +109,41 @@ def get_runner_script() -> str: return str(Path(__file__).parent / "qgis_runner.py") +def _generate_bat_wrapper(qgis_root: str, python_path: str, runner_script: str) -> str: + """生成 .bat 包装脚本,设置 QGIS 环境变量并启动 runner""" + qgis_app_dir = os.path.join(qgis_root, "apps", "qgis-ltr").replace("/", "\\") + python_dir = os.path.join(qgis_root, "apps", "Python312").replace("/", "\\") + qt5_plugins = os.path.join(qgis_root, "apps", "Qt5", "plugins").replace("/", "\\") + qtplugins = os.path.join(qgis_app_dir, "qtplugins").replace("/", "\\") + gdal_data = os.path.join(qgis_root, "apps", "gdal", "share", "gdal").replace("/", "\\") + qgis_python_dir = os.path.join(qgis_app_dir, "python").replace("/", "\\") + qgis_bin = os.path.join(qgis_app_dir, "bin").replace("/", "\\") + qt5_bin = os.path.join(qgis_root, "apps", "Qt5", "bin").replace("/", "\\") + gdal_lib = os.path.join(qgis_root, "apps", "gdal", "lib").replace("/", "\\") + + bat_content = f"""@echo off +set "PYTHONHOME={python_dir}" +set "PYTHONPATH={qgis_python_dir}" +set "QGIS_PREFIX_PATH={qgis_app_dir}" +set "QT_PLUGIN_PATH={qtplugins};{qt5_plugins}" +set "GDAL_DATA={gdal_data}" +set "PYTHONUTF8=1" +set "GDAL_FILENAME_IS_UTF8=YES" +set "VSI_CACHE=TRUE" +set "VSI_CACHE_SIZE=1000000" +set "PATH={qgis_bin};{qt5_bin};{gdal_lib};%PATH%" +"{python_path}" "{runner_script}" %* +""" + + bat_dir = os.path.join(tempfile.gettempdir(), "qgis_runner") + os.makedirs(bat_dir, exist_ok=True) + bat_path = os.path.join(bat_dir, "run_qgis.bat") + with open(bat_path, "w", encoding="utf-8") as f: + f.write(bat_content) + logger.debug(f"生成 QGIS 包装脚本: {bat_path}") + return bat_path + + def _detect_qgis_root() -> str | None: """ 自动检测 QGIS 安装根目录。 diff --git a/app/utils/logger.py b/app/utils/logger.py index 4b0319b..e835efd 100644 --- a/app/utils/logger.py +++ b/app/utils/logger.py @@ -1,42 +1,32 @@ """ 日志工具类 -主进程使用 loguru,QGIS 子进程回退到标准 logging。 +使用 loguru 提供增强的日志功能,支持按天分割、自动清理过期日志 """ import sys from pathlib import Path - -try: - from loguru import logger as _loguru_logger - _HAS_LOGURU = True -except ImportError: - _HAS_LOGURU = False - import logging as _std_logging +from loguru import logger class LoggerManager: - """日志管理器 - 优先 loguru,回退标准 logging""" + """日志管理器 - 基于 loguru""" _configured = False - _fallback_loggers = {} @classmethod def get_logger(cls, name: str = "algorithm", log_dir: str = "logs"): - if _HAS_LOGURU: - if not cls._configured: - cls._configure_loguru(name, log_dir) - return _loguru_logger - else: - if name not in cls._fallback_loggers: - logger = _std_logging.getLogger(name) - logger.setLevel(_std_logging.INFO) - if not logger.handlers: - h = _std_logging.StreamHandler(sys.stderr) - h.setFormatter(_std_logging.Formatter( - "%(asctime)s [%(threadName)s] %(levelname)-5s %(name)s - %(message)s" - )) - logger.addHandler(h) - cls._fallback_loggers[name] = logger - return cls._fallback_loggers[name] + """ + 获取日志记录器(loguru 不需要传统意义上的 logger 实例) + + Args: + name: 日志名称(用于文件命名) + log_dir: 日志目录 + + Returns: + loguru.logger 实例 + """ + if not cls._configured: + cls._configure_loguru(name, log_dir) + return logger @classmethod def _configure_loguru(cls, name: str, log_dir: str): @@ -48,14 +38,14 @@ class LoggerManager: log_dir: 日志目录 """ # 移除默认的 stderr handler - _loguru_logger.remove() + logger.remove() # 创建日志目录 log_path = Path(log_dir) log_path.mkdir(parents=True, exist_ok=True) # 控制台 Handler - 彩色输出 - _loguru_logger.add( + logger.add( sink=sys.stderr, level="INFO", format="{time:YYYY-MM-DD HH:mm:ss} [{thread.name}] {level: <5} {name} - {message}", @@ -64,7 +54,7 @@ class LoggerManager: # 文件 Handler - 按大小分割,Windows 兼容 log_file = log_path / f"{name}.log" - _loguru_logger.add( + logger.add( sink=str(log_file), level="DEBUG", format="{time:YYYY-MM-DD HH:mm:ss} [{thread.name}] {level: <5} {name} - {message}",