Files
xian_algorithm_new/app/services/qgis/qgis_env.py
T
2026-06-21 22:30:04 +08:00

237 lines
7.1 KiB
Python
Raw 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.
"""
QGIS 环境配置模块 — Docker 模式。
所有 QGIS 操作通过 docker exec 在容器内执行,
主进程(Python 3.10)无需安装 QGIS 或处理 DLL 加载。
容器内使用 qgis/qgis 官方镜像(QGIS 3.x + Python 3)。
本模块提供:
- get_docker_container(): 获取 Docker 容器名称
- get_docker_project_dir(): 获取容器内项目根目录
- build_docker_exec_cmd(): 构建 docker exec 命令
- build_docker_volume_mounts(): 构建 docker run 卷挂载参数
- get_runner_script(): 获取 qgis_runner.py 的绝对路径
- get_container_python_path(): 获取容器内 Python 解释器路径
"""
import os
import platform
from pathlib import Path
from app.config.paths import get_logger
logger = get_logger("qgis.env")
IS_WINDOWS = platform.system() == "Windows"
# ============================================================
# Docker 容器配置
# ============================================================
def get_docker_container() -> str:
"""获取 Docker 容器名称/ID"""
try:
from config import settings
container = getattr(settings, "QGIS_DOCKER_CONTAINER", "") or ""
if container.strip():
return container.strip()
except Exception:
pass
# 环境变量覆盖
env_container = os.environ.get("QGIS_DOCKER_CONTAINER", "")
if env_container.strip():
return env_container.strip()
return "qgis-server"
def get_docker_project_dir() -> str:
"""获取容器内项目根目录(代码挂载目标路径)"""
try:
from config import settings
project_dir = getattr(settings, "QGIS_DOCKER_PROJECT_DIR", "") or ""
if project_dir.strip():
return project_dir.strip()
except Exception:
pass
env_dir = os.environ.get("QGIS_DOCKER_PROJECT_DIR", "")
if env_dir.strip():
return env_dir.strip()
return "/app"
def get_container_python_path() -> str:
"""获取容器内 Python 解释器路径"""
try:
from config import settings
python_path = getattr(settings, "QGIS_DOCKER_PYTHON", "") or ""
if python_path.strip():
return python_path.strip()
except Exception:
pass
env_path = os.environ.get("QGIS_DOCKER_PYTHON", "")
if env_path.strip():
return env_path.strip()
# qgis/qgis 官方镜像默认路径
return "/usr/bin/python3"
# ============================================================
# docker exec 命令构建
# ============================================================
def build_docker_exec_cmd(python_path: str, runner_script: str, json_file: str) -> list:
"""
构建 docker exec 命令。
Args:
python_path: 容器内 Python 路径(如 /usr/bin/python3
runner_script: 容器内 runner 脚本路径
json_file: 容器内 JSON 请求文件路径
Returns:
docker exec 命令列表
"""
container = get_docker_container()
project_dir = get_docker_project_dir()
# Qt 平台插件(从配置读取,避免硬编码)
try:
from config import settings
qt_platform = getattr(settings, "QGIS_DOCKER_QT_PLATFORM", "offscreen")
except Exception:
qt_platform = "offscreen"
cmd = [
"docker", "exec",
"-w", project_dir,
"-e", f"QT_QPA_PLATFORM={qt_platform}",
container,
python_path, runner_script, json_file,
]
return cmd
# ============================================================
# docker run 卷挂载(首次启动容器用)
# ============================================================
def get_host_file_store() -> str:
"""获取主机端文件输出目录"""
try:
from config import settings
fs = getattr(settings, "FILE_STORE_DIR", "") or ""
if fs.strip():
return fs.strip().replace("\\", "/")
except Exception:
pass
return "G:/files"
def get_container_file_store() -> str:
"""获取容器内文件输出目录(Linux 路径)"""
# 挂载时主机 G:/files → 容器内 /files
host = get_host_file_store()
# 取最后一段目录名作为容器内路径
tail = host.rstrip("/").rsplit("/", 1)[-1]
return f"/{tail}"
def map_host_to_container(path: str) -> str:
"""将主机路径映射为容器内路径。
优先匹配 file_storeG:/files → /files),其次匹配项目根目录(F:/project/... → /app)。
"""
normalized = path.replace("\\", "/")
# 1. file_store 路径映射
host_fs = get_host_file_store()
container_fs = get_container_file_store()
if normalized.lower().startswith(host_fs.lower()):
return container_fs + normalized[len(host_fs):]
# 2. 项目根目录映射
project_root = str(Path(__file__).parent.parent.parent.parent).replace("\\", "/")
project_dir = get_docker_project_dir()
if normalized.lower().startswith(project_root.lower()):
return project_dir + normalized[len(project_root):]
return normalized
def map_container_to_host(path: str) -> str:
"""将容器内路径映射回主机路径"""
host = get_host_file_store()
container = get_container_file_store()
if path.startswith(container):
return host + path[len(container):]
return path
def build_docker_volume_mounts() -> list:
"""
构建 Docker 卷挂载参数列表(用于 docker run)。
挂载内容:
1. 项目代码目录 → 容器内 /app(只读)
2. 输出文件目录 → 容器内 /files(读写)
"""
project_root = str(Path(__file__).parent.parent.parent.parent)
host_file_store = get_host_file_store()
container_file_store = get_container_file_store()
project_dir = get_docker_project_dir()
mounts = [
f"{project_root}:{project_dir}:ro",
f"{host_file_store}:{container_file_store}",
]
return mounts
def build_docker_run_cmd(image: str = None) -> list:
"""
构建 docker run 启动命令(首次部署用)。
Returns:
docker run 命令列表
"""
if image is None:
try:
from config import settings
image = getattr(settings, "QGIS_DOCKER_IMAGE", "") or ""
if not image.strip():
image = "qgis/qgis:latest"
except Exception:
image = "qgis/qgis:latest"
# 容器保活命令(从配置读取)
try:
from config import settings
keep_alive = getattr(settings, "QGIS_DOCKER_KEEP_ALIVE", "sleep infinity")
except Exception:
keep_alive = "sleep infinity"
container = get_docker_container()
mounts = build_docker_volume_mounts()
cmd = [
"docker", "run", "-d",
"--name", container,
"--restart", "unless-stopped",
]
for m in mounts:
cmd.extend(["-v", m])
cmd.append(image)
# 保活命令
cmd.extend(keep_alive.split())
return cmd
# ============================================================
# Runner 脚本路径
# ============================================================
def get_runner_script() -> str:
"""获取 qgis_runner.py 的绝对路径(主机端)"""
return str(Path(__file__).parent / "qgis_runner.py")