#!/usr/bin/env python # -*- coding: utf-8 -*- """数据库连接解析器""" import logging import time from typing import Dict, Optional, Tuple from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database import AsyncSessionLocal from core.database_connection.model import DatabaseConnection from core.database_connection.types import ConnectionInfo from core.database_manager.service import parse_database_url from utils.secret_crypto import decrypt_secret logger = logging.getLogger(__name__) _CACHE: Dict[str, Tuple[ConnectionInfo, float]] = {} _CACHE_TTL = 60.0 class ConnectionResolver: @staticmethod def invalidate_cache(code: Optional[str] = None) -> None: if code: _CACHE.pop(code, None) else: _CACHE.clear() @staticmethod def default_connection_info() -> ConnectionInfo: db_info = parse_database_url(settings.DATABASE_URL or "") if not db_info: raise ValueError("Invalid DATABASE_URL configuration") return ConnectionInfo( code="default", db_type=db_info["db_type"], host=db_info["host"], port=db_info["port"], user=db_info["user"], password=db_info["password"], database=db_info["database"], is_system=True, display_name=db_info["database"] or "default", ) @classmethod async def resolve( cls, code: str, db: Optional[AsyncSession] = None, ) -> ConnectionInfo: if code == "default": return cls.default_connection_info() now = time.time() cached = _CACHE.get(code) if cached and now - cached[1] < _CACHE_TTL: return cached[0] if db is None: async with AsyncSessionLocal() as session: info = await cls._resolve_from_db(session, code) else: info = await cls._resolve_from_db(db, code) _CACHE[code] = (info, now) return info @classmethod async def _resolve_from_db(cls, db: AsyncSession, code: str) -> ConnectionInfo: result = await db.execute( select(DatabaseConnection).where( DatabaseConnection.code == code, DatabaseConnection.is_deleted == False, ) ) row = result.scalar_one_or_none() if not row or not row.status: raise ValueError(f"Database connection '{code}' not found or disabled") if row.is_system: return cls.default_connection_info() password = decrypt_secret(row.password_enc or "") if row.password_enc and not password: logger.error( "Failed to decrypt password for connection code=%s (check DB_CONN_SECRET_KEY / JWT_SECRET_KEY)", row.code, ) return ConnectionInfo( code=row.code, db_type=row.db_type, host=row.host, port=row.port, user=row.user or "", password=password, database=row.default_database or "", is_system=False, display_name=row.name, extra_options=row.extra_options or {}, )