Files
wecom_it_smart_desk/docs/08-历史归档/deploy-nas-archived-20260704/backend/app/database.py
T
Simon 44e77dcb0e chore(docs): docs/ 目录全面重新编号 + 重组
**重构前**(旧编号 02-11):
- docs/02-产品需求/      → 00 产品规划/PRD
- docs/03-技术架构/      → 01-05 子目录散落
- docs/04-原型设计/      → 01-02 产品设计(HTML 原型)
- docs/05-原型设计/      → screens/
- docs/06-测试素材/      → 02-E2E / 03-功能 / 04-版本测试
- docs/07-项目管理/      → 任务说明书/日报/计划
- docs/08-安全审计/      → 审计报告
- docs/09-堡垒运维/      → toolbox / deploy
- docs/10-项目管理/      → 任务说明书(重复)
- docs/11-历史归档/      → deploy-nas-archived

**重构后**(新编号 00-07,语义化):
- docs/00-产品开发流程与文档管理规范.md
- docs/00-版本迭代总览.md
- docs/01-产品文档/      (PRD/原型/认证/会话/AI 服务/坐席/集成)
- docs/02-技术文档/      (技术方案/架构图/重构记录/前端改造/实现配置)
- docs/03-测试文档/      (E2E/功能用例/版本报告/缺陷单)
- docs/04-运维文档/      (部署运维/运维指南)
- docs/05-运营文档/      (品牌推广/用户手册)
- docs/06-安全审计/      (审计报告)
- docs/07-项目管理/      (任务说明书/日报/计划/看板)

**净收益**:
- 目录编号与产品文档管理规范对齐(按文档阶段 01-07 编号)
- 消除 02-产品需求 与 10-项目管理 的编号重叠
- 子目录按文档类型分组(如 01-产品文档/00-产品规划、01-产品文档/01-认证与登录)
- 把运维/安全/项目管理从 0X 散落改为 04/06/07

合计 494 文件 + 78495 行 / - 14076 行
2026-08-03 18:46:55 +08:00

147 lines
5.2 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.
# =============================================================================
# 企微智能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