微信小程序页面迁移校验之前 P5任务处理之前

This commit is contained in:
Neo
2026-03-09 01:19:21 +08:00
parent 263bf96035
commit 6e20987d2f
1112 changed files with 153824 additions and 219694 deletions

View File

@@ -10,12 +10,79 @@ from pathlib import Path
from dotenv import load_dotenv
# CHANGE 2026-03-07 | 项目根目录定位:防止 junction/symlink 穿透到 D 盘
# 背景C:\NeoZQYY 是 junction → D:\NeoZQYY\...\repo
# Path(__file__).resolve() 和 absolute() 都可能解析到 D 盘,
# 导致加载 D 盘的 .env路径全指向 D 盘ETL 命令因此携带错误路径。
# 策略:环境变量 > 已知固定路径 > __file__ 推算(最后手段)
import logging as _logging
_cfg_logger = _logging.getLogger("app.config")
def _find_project_root() -> Path:
"""定位项目根目录,返回包含 .env 的路径。
优先级:
1. 环境变量 NEOZQYY_ROOT最可靠显式指定
2. __file__ 向上推算,但用 junction 安全的方式
"""
# 1. 环境变量显式指定(部署时设置,最可靠)
env_root = os.environ.get("NEOZQYY_ROOT")
if env_root:
p = Path(env_root)
if (p / ".env").exists():
_cfg_logger.info("[ROOT] 策略1命中: NEOZQYY_ROOT=%s", p)
return p
_cfg_logger.warning("[ROOT] NEOZQYY_ROOT=%s 但 .env 不存在,跳过", env_root)
# 2. 从 __file__ 推算apps/backend/app/config.py → 上 3 级)
raw_file = Path(__file__)
abs_file = raw_file.absolute()
candidate = abs_file.parents[3]
_cfg_logger.info(
"[ROOT] 策略2: __file__=%s | absolute=%s | candidate=%s",
raw_file, abs_file, candidate,
)
# CHANGE 2026-03-07 | 防护:如果推算路径包含 test/repo 或 prod/repo 等
# 多环境子目录,说明发生了 junction/symlink 穿透到 D 盘部署结构,
# 此时向上搜索找到真正的项目根(包含 .env 的最浅目录)
candidate_str = str(candidate)
if any(seg in candidate_str for seg in ("\\test\\", "\\prod\\", "/test/", "/prod/")):
_cfg_logger.warning(
"[ROOT] 检测到多环境子目录穿透: %s,启动向上搜索", candidate_str
)
elif (candidate / ".env").exists():
_cfg_logger.info("[ROOT] 策略2命中: %s", candidate)
return candidate
# 3. 向上搜索:应对 junction 穿透导致层级偏移的情况
cur = abs_file.parent
for i in range(10):
if (cur / ".env").exists():
_cfg_logger.info("[ROOT] 策略3命中(第%d级): %s", i, cur)
return cur
parent = cur.parent
if parent == cur:
break
cur = parent
_cfg_logger.warning("[ROOT] 所有策略均未命中,回退到: %s", candidate)
return candidate
_project_root = _find_project_root()
_cfg_logger.info("项目根目录: %s", _project_root)
# 根 .env公共配置
_root_env = Path(__file__).resolve().parents[3] / ".env"
_root_env = _project_root / ".env"
_cfg_logger.info("加载根 .env: %s (存在: %s)", _root_env, _root_env.exists())
load_dotenv(_root_env, override=False)
# 应用级 .env.local私有覆盖优先级更高
_local_env = Path(__file__).resolve().parents[1] / ".env.local"
_local_env = _project_root / "apps" / "backend" / ".env.local"
load_dotenv(_local_env, override=True)
@@ -24,6 +91,14 @@ def get(key: str, default: str | None = None) -> str | None:
return os.getenv(key, default)
def _require_env(key: str) -> str:
"""必需的环境变量,缺失时立即报错。"""
raise RuntimeError(
f"必需的环境变量 {key} 未设置。"
f"请在 .env 中显式配置(当前 .env 路径: {_root_env}"
)
# ---- 数据库连接参数 ----
DB_HOST: str = get("DB_HOST", "localhost")
DB_PORT: str = get("DB_PORT", "5432")
@@ -55,11 +130,55 @@ CORS_ORIGINS: list[str] = [
]
# ---- ETL 项目路径 ----
# ETL CLI 的工作目录(子进程 cwd缺省时按 monorepo 相对路径推算
ETL_PROJECT_PATH: str = get(
"ETL_PROJECT_PATH",
str(Path(__file__).resolve().parents[3] / "apps" / "etl" / "connectors" / "feiqiu"),
)
# CHANGE 2026-03-06 | 必须在 .env 显式设置,禁止依赖 __file__ 推算
# 原因__file__ 推算依赖 uvicorn 启动位置,不同部署环境会指向错误代码副本
ETL_PROJECT_PATH: str = get("ETL_PROJECT_PATH") or _require_env("ETL_PROJECT_PATH")
# ETL 子进程 Python 可执行路径
# CHANGE 2026-03-06 | 必须在 .env 显式设置,避免 PATH 歧义
ETL_PYTHON_EXECUTABLE: str = get("ETL_PYTHON_EXECUTABLE") or _require_env("ETL_PYTHON_EXECUTABLE")
# ---- 运维面板 ----
# CHANGE 2026-03-06 | 必须在 .env 显式设置,消除 __file__ 推算风险
OPS_SERVER_BASE: str = get("OPS_SERVER_BASE") or _require_env("OPS_SERVER_BASE")
# CHANGE 2026-03-07 | 启动时验证关键路径:
# 1. 路径必须实际存在于文件系统
# 2. 路径不得包含多环境子目录test/repo、prod/repo这是 junction 穿透的标志
_cfg_logger.info("ETL_PROJECT_PATH = %s", ETL_PROJECT_PATH)
_cfg_logger.info("ETL_PYTHON_EXECUTABLE = %s", ETL_PYTHON_EXECUTABLE)
_cfg_logger.info("OPS_SERVER_BASE = %s", OPS_SERVER_BASE)
for _var_name, _var_val in [
("ETL_PROJECT_PATH", ETL_PROJECT_PATH),
("ETL_PYTHON_EXECUTABLE", ETL_PYTHON_EXECUTABLE),
("OPS_SERVER_BASE", OPS_SERVER_BASE),
]:
# 检测 junction 穿透特征:路径中包含 \test\repo 或 \prod\repo
_normalized = _var_val.replace("/", "\\")
if "\\test\\repo" in _normalized or "\\prod\\repo" in _normalized:
_cfg_logger.error(
"路径穿透检测: %s=%s 包含多环境子目录,"
"说明 .env 来自 junction 穿透后的 D 盘副本。"
"当前 .env 路径: %s | NEOZQYY_ROOT: %s",
_var_name, _var_val, _root_env,
os.environ.get("NEOZQYY_ROOT", "<未设置>"),
)
raise RuntimeError(
f"配置路径异常: {_var_name}={_var_val} 包含多环境子目录"
f"test/repo 或 prod/repo疑似加载了错误的 .env。"
f" 当前 .env: {_root_env}"
f" 请确保 NEOZQYY_ROOT 环境变量指向正确的项目根目录。"
)
# ---- 微信小程序 ----
WX_APPID: str = get("WX_APPID", "")
WX_SECRET: str = get("WX_SECRET", "")
# 开发模式WX_DEV_MODE=true 时启用 mock 登录端点,跳过微信 code2Session
WX_DEV_MODE: bool = get("WX_DEV_MODE", "false").lower() in ("true", "1", "yes")
# ---- 营业日分割点 ----
BUSINESS_DAY_START_HOUR: int = int(get("BUSINESS_DAY_START_HOUR", "8"))
# ---- 通用 ----
TIMEZONE: str = get("TIMEZONE", "Asia/Shanghai")