""" MariaDB 连接管理。 使用 SQLAlchemy 连接池,通过 SSH 隧道访问远程数据库。 连接失败时自动尝试重建 SSH 隧道并重连。 """ import os import subprocess import time from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker from config.settings import MARIADB_CONFIG _engine = None _SessionLocal = None def _build_url() -> str: cfg = MARIADB_CONFIG return ( "mysql+pymysql://{}:{}" "@{}:{}/{}" "?charset={}" ).format(cfg["user"], cfg["password"], cfg["host"], cfg["port"], cfg["database"], cfg["charset"]) def _reconnect_ssh() -> bool: """自动运行 autossh.sh 重建 SSH 隧道。每次调用都会尝试。""" candidates = [ os.path.join(os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__)))), "shared", "script", "autossh.sh"), os.path.expanduser("~/Downloads/cc-cursor/shared/script/autossh.sh"), ] script = None for c in candidates: if os.path.exists(c): script = c break if script is None: print("[DB] SSH 脚本未找到") return False try: print("[DB] 尝试重建 SSH 隧道: {}".format(script)) result = subprocess.run(["bash", script], capture_output=True, text=True, timeout=15) time.sleep(3) # 等待隧道建立 if result.returncode == 0: print("[DB] SSH 隧道重建完成") return True else: print("[DB] SSH 隧道重建失败: {}".format(result.stderr[:200])) return False except Exception as e: print("[DB] SSH 执行异常: {}".format(e)) return False def _test_engine(engine) -> bool: """测试引擎连接是否存活。""" try: with engine.connect() as conn: conn.execute(text("SELECT 1")) return True except Exception: return False def get_engine(): """获取 SQLAlchemy Engine(懒初始化,断连时自动重连)。""" global _engine, _SessionLocal # 已有引擎但连接断开 → dispose 旧池 → SSH 重连 → 重建引擎 if _engine is not None and not _test_engine(_engine): print("[DB] 连接已断开,尝试自动恢复...") _engine.dispose() # 杀死所有旧连接 _engine = None if _reconnect_ssh(): print("[DB] 连接已恢复") else: print("[DB] 自动恢复失败,将无法连接数据库") # 创建引擎(首次或重建后) if _engine is None: cfg = MARIADB_CONFIG _engine = create_engine( _build_url(), pool_size=cfg["pool_size"], pool_recycle=cfg["pool_recycle"], pool_pre_ping=True, # 每次取连接前先 SELECT 1 验证存活 echo=False, ) # Session factory 下次获取时自动绑定新引擎 _SessionLocal = None return _engine def get_session(): """获取一个新的数据库会话。""" global _SessionLocal if _SessionLocal is None: _SessionLocal = sessionmaker(bind=get_engine()) return _SessionLocal() def test_connection() -> bool: """测试数据库连接是否正常。""" try: engine = get_engine() return _test_engine(engine) except Exception as e: print("[ERROR] 数据库连接失败: {}".format(e)) return False