diff --git a/app/api/qgis_map_export.py b/app/api/qgis_map_export.py index 5c7db0e..f3a7ded 100644 --- a/app/api/qgis_map_export.py +++ b/app/api/qgis_map_export.py @@ -154,23 +154,51 @@ def _extract_center_from_condition(event_type: str, condition: dict) -> tuple: # 构建 QGIS 服务配置字典 # ============================================================ -def _build_qgis_config(batch_folder: str) -> dict: - """构建 QGIS 服务配置(含批次输出目录)""" - from app.services.qgis.qgis_env import ( - get_docker_container, get_host_file_store, get_container_file_store, - get_docker_project_dir, - ) - gpkg_dir = get_gpkg_dir() - - # Docker 模式:config 中的路径必须是容器内路径(模板修改器在容器内运行) +def _check_docker_running() -> bool: + """检测 Docker 容器是否在运行""" + from app.services.qgis.qgis_env import get_docker_container try: result = subprocess.run( ["docker", "inspect", "--format={{.State.Running}}", get_docker_container()], capture_output=True, text=True, timeout=5, ) - is_docker = result.stdout.strip() == "true" + return result.stdout.strip() == "true" except Exception: - is_docker = False + return False + + +def _list_container_templates(container: str, event_type: str) -> list: + """Docker 模式下:扫描容器内模板目录,返回模板文件名列表""" + from app.services.qgis.qgis_env import get_docker_container + container_tpl_dir = getattr(settings, "QGIS_DOCKER_TEMPLATE_DIR", "") or "/data/template" + container_path = f"{container_tpl_dir.rstrip('/')}/{event_type}" + try: + result = subprocess.run( + ["docker", "exec", container, "ls", container_path], + capture_output=True, text=True, timeout=10, + ) + if result.returncode != 0: + logger.error(f"[Docker] 列出容器模板失败: {result.stderr.strip()}") + return [] + files = [ + f.strip() for f in result.stdout.splitlines() + if f.strip().endswith(".qgz") and not f.strip().startswith("tmp") + ] + logger.info(f"[Docker] 容器内模板扫描: {container_path} → {len(files)} 个模板") + return sorted(files) + except Exception as e: + logger.error(f"[Docker] 列出容器模板异常: {e}") + return [] + + +def _build_qgis_config(batch_folder: str) -> dict: + """构建 QGIS 服务配置(含批次输出目录和 Docker 模式标志)""" + from app.services.qgis.qgis_env import ( + get_docker_container, get_host_file_store, get_container_file_store, + get_docker_project_dir, + ) + gpkg_dir = get_gpkg_dir() + is_docker = _check_docker_running() if is_docker: # GPKG 目录:优先使用容器内本地路径(预拷贝后绕过 WSL2 9P) @@ -206,6 +234,7 @@ def _build_qgis_config(batch_folder: str) -> dict: }, "static_layers": build_static_layers_config(gpkg_dir), "batch_folder": batch_folder, + "is_docker": is_docker, } @@ -242,15 +271,22 @@ def _background_export(inference_id: int) -> None: config = _build_qgis_config(batch_folder) # 2. 扫描模板 - template_base = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), - "app", "data", "template" - ) - template_dir = os.path.join(template_base, event_type) - template_files = sorted([ - f for f in os.listdir(template_dir) - if f.endswith(".qgz") and not f.startswith("tmp") - ]) + is_docker = config.get("is_docker", False) + if is_docker: + container_tpl = getattr(settings, "QGIS_DOCKER_TEMPLATE_DIR", "") or "/data/template" + template_dir = f"{container_tpl.rstrip('/')}/{event_type}" + template_files = _list_container_templates(get_docker_container(), event_type) + else: + tpl_subdir = getattr(settings, "QGIS_HOST_TEMPLATE_SUBDIR", "app/data/template") + template_base = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + *tpl_subdir.replace("\\", "/").split("/") + ) + template_dir = os.path.join(template_base, event_type) + template_files = sorted([ + f for f in os.listdir(template_dir) + if f.endswith(".qgz") and not f.startswith("tmp") + ]) priority_keywords = getattr(settings, "QGIS_PRIORITY_TEMPLATES", []) if priority_keywords: def _priority_order(name: str) -> tuple: diff --git a/app/services/qgis/qgis_env.py b/app/services/qgis/qgis_env.py index 6a3b82c..d9dcaff 100644 --- a/app/services/qgis/qgis_env.py +++ b/app/services/qgis/qgis_env.py @@ -172,6 +172,8 @@ def map_template_to_container(host_path: str) -> str: 主机: F:/project/xian/xian_algorithm_new/app/data/template/rainfall/xxx.qgz 容器: /data/template/rainfall/xxx.qgz + + 已为容器路径时直接返回(幂等)。 """ try: from config import settings @@ -179,16 +181,21 @@ def map_template_to_container(host_path: str) -> str: except Exception: container_tpl = "" + # 已经是容器路径,直接返回 + normalized = host_path.replace("\\", "/") + if container_tpl and normalized.startswith(container_tpl.rstrip("/") + "/"): + return normalized + if not container_tpl: # fallback: 用通用映射(走 9P,慢) return map_host_to_container(host_path) - normalized = host_path.replace("\\", "/").lower() - # 找 "app/data/template/" 后面的部分 - marker = "app/data/template/" - idx = normalized.find(marker) + normalized_lower = normalized.lower() + # 找模板子目录后面的相对路径(主机端 marker 从配置读取) + marker = getattr(settings, "QGIS_HOST_TEMPLATE_SUBDIR", "app/data/template").rstrip("/") + "/" + idx = normalized_lower.find(marker.lower()) if idx >= 0: - relative = host_path.replace("\\", "/")[idx + len(marker):] + relative = normalized[idx + len(marker):] return f"{container_tpl.rstrip('/')}/{relative}" return map_host_to_container(host_path) diff --git a/settings.toml b/settings.toml index df68cd9..415f1c6 100644 --- a/settings.toml +++ b/settings.toml @@ -12,6 +12,8 @@ PREDICT_PROBABILITY_THRESHOLD = 50 QGIS_GPKG_DIR = "app/data/gpkg" # 容器内 GPKG 本地路径 QGIS_DOCKER_GPKG_DIR = "/data/gpkg" +# 模板目录(相对于项目根,用于路径映射时的 marker 匹配) +QGIS_HOST_TEMPLATE_SUBDIR = "app/data/template" # 容器内模板本地路径 QGIS_DOCKER_TEMPLATE_DIR = "/data/template" # 专题图输出子目录