chore: initial baseline with P0-safety .gitignore

This commit is contained in:
Simon
2026-06-14 16:49:18 +08:00
commit 63262292d7
510 changed files with 146008 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
# =============================================================================
# 企微IT智能服务台 — 数据库连接与 Session 管理
# =============================================================================
# 说明:使用 SQLAlchemy 2.0 的异步引擎和会话管理,负责:
# 1. 创建异步数据库引擎(懒加载,支持 PostgreSQL 和 SQLite
# 2. 创建异步会话工厂
# 3. 提供 get_db 依赖注入函数(FastAPI 路由中使用)
# 4. 自动建表(SQLite 本地开发时自动创建所有表)
# =============================================================================
import logging
from typing import AsyncGenerator, Optional
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
# 导入配置(读取数据库连接地址)
from app.config import settings
from app.utils.response import AppException
logger = logging.getLogger(__name__)
# ----------------------------------------------------------------------
# 声明式基类(单独定义,不依赖引擎创建)
# ----------------------------------------------------------------------
# 所有模型类都继承自 Base,SQLAlchemy 通过它来检测所有模型定义
# Alembic 也通过 Base.metadata 来生成迁移脚本
# ----------------------------------------------------------------------
class Base(DeclarativeBase):
"""SQLAlchemy 声明式基类。
所有模型类都继承此类,SQLAlchemy 通过它管理所有表的元数据。
"""
pass
# ----------------------------------------------------------------------
# 懒加载引擎和会话工厂
# ----------------------------------------------------------------------
_engine: Optional[object] = None
_async_session_factory: Optional[async_sessionmaker] = None
_tables_created: bool = False # 标记是否已自动建表
def _is_sqlite() -> bool:
"""判断当前数据库 URL 是否为 SQLite。"""
return "sqlite" in settings.database_url.lower()
def _get_engine():
"""懒加载获取数据库引擎。
支持 PostgreSQL 和 SQLite 两种后端:
- PostgreSQL: 使用 asyncpg 异步驱动,带连接池
- SQLite: 使用 aiosqlite 异步驱动,无需连接池
"""
global _engine
if _engine is None:
db_url = settings.database_url
if _is_sqlite():
# SQLite 异步驱动:aiosqlite
# 不需要连接池,SQLite 是单文件数据库
_engine = create_async_engine(
db_url,
echo=False,
)
logger.info(f"使用 SQLite 数据库: {db_url}")
else:
# PostgreSQL 异步驱动:asyncpg
_engine = create_async_engine(
db_url.replace("postgresql://", "postgresql+asyncpg://"),
echo=False,
pool_size=5,
max_overflow=10,
pool_pre_ping=True,
)
logger.info(f"使用 PostgreSQL 数据库: {db_url.split('@')[-1]}")
return _engine
def _get_session_factory() -> async_sessionmaker:
"""懒加载获取会话工厂。"""
global _async_session_factory
if _async_session_factory is None:
_async_session_factory = async_sessionmaker(
_get_engine(),
class_=AsyncSession,
expire_on_commit=False,
)
return _async_session_factory
async def _ensure_tables():
"""自动建表(仅 SQLite 本地开发时使用)。
PostgreSQL 环境应使用 Alembic 迁移管理表结构。
SQLite 环境下直接用 metadata.create_all 创建所有表,省去迁移步骤。
"""
global _tables_created
if _tables_created:
return
_tables_created = True
if _is_sqlite():
# 导入所有模型,确保 Base.metadata 知道所有表
import app.models # noqa: F401
engine = _get_engine()
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("SQLite 自动建表完成")
async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""获取数据库会话的依赖注入函数。
在 FastAPI 路由中通过 Depends(get_db) 注入数据库会话。
使用 async with 确保会话在使用后正确关闭。
使用 try/finally 确保异常时也能回滚和关闭。
Yields:
AsyncSession: 异步数据库会话
"""
# 首次调用时自动建表(SQLite
await _ensure_tables()
# 创建一个新的数据库会话(懒加载会话工厂)
try:
session_factory = _get_session_factory()
except Exception as e:
logger.error(f"数据库连接失败(无法创建会话工厂): {e}")
raise AppException(1006, f"数据库连接失败: {str(e)}")
async with session_factory() as session:
try:
# 将会话交给路由函数使用
yield session
# 路由函数执行成功后提交事务
await session.commit()
except Exception:
# 路由函数执行失败时回滚事务
await session.rollback()
# 重新抛出异常,让 FastAPI 的异常处理器处理
raise