Skip to content

🗄️ 数据库仓储层

app/core/database/ 是 SQLite 仓储层,无 ORM,直接用 sqlite3 + 参数化 SQL。

技术栈

  • 数据库:SQLite(Python 标准库 sqlite3
  • PRAGMAjournal_mode=WALsynchronous=NORMALbusy_timeout=5000
  • 迁移ALTER TABLE 增量迁移函数,无需手写迁移脚本
  • 默认路径data/sync_records.db

Repository 模式

每个表对应一个 Repository 类,继承 base_repository.py 的基类,共享一个 DatabaseConnection(单例 database_manager),通过 _lock 串行化写操作。

Repository说明
sync_recordssync_records.py同步记录,含 match_trace JSON、match_scoresource
pending_candidatespending_candidates.py待确认候选(匹配失败时沉淀),部分唯一索引去重
pending_sync_queuepending_sync_queue.pyReplay 待同步队列,部分唯一索引去重
in_app_notificationsinbox.py站内信
trakt_config / trakt_sync_historytrakt.pyTrakt 多用户配置与同步历史
feiniu_sync_history / feiniu_metafeiniu.py飞牛同步历史与启动水位
announcement_read_state / llm_usageinbox.py / llm_usage.py公告已读状态 / LLM 调用计量

常见参数(PRAGMA)

python
cursor.execute("PRAGMA journal_mode=WAL")       # WAL 模式,并发读写稳定
cursor.execute("PRAGMA synchronous=NORMAL")     # 平衡性能与安全
cursor.execute("PRAGMA busy_timeout=5000")      # 写锁等待 5 秒

表结构演进

表结构通过 connection.py 中的 ALTER TABLE 增量迁移函数实现,应用启动时自动检测并执行。新增字段只需:

  1. _init_database()CREATE TABLE 中加字段(新库直接有)
  2. 写一个 _ensure_xxx_field() 迁移函数(老库自动补)
python
def _ensure_sync_records_media_type(conn) -> None:
    """老库补 media_type 字段"""
    cursor = conn.execute("PRAGMA table_info(sync_records)")
    columns = {row[1] for row in cursor.fetchall()}
    if "media_type" not in columns:
        conn.execute(
            "ALTER TABLE sync_records ADD COLUMN media_type TEXT DEFAULT 'episode'"
        )

常见场景

写记录

python
from app.core.database import database_manager

database_manager.log_sync_record(
    user_name="alice",
    title="测试番剧",
    season=1, episode=1,
    status="success",
    source="emby",
    match_trace=trace.to_json(),
)

查询记录

python
records = database_manager.get_sync_records(
    user_name="alice",
    source="emby",
    limit=20,
)

测试中 mock 数据库

tmp_path fixture 创建临时 SQLite,或用 MonkeyPatch 替换 database_manager._conn

python
def test_log_record(tmp_path, monkeypatch):
    db_path = tmp_path / "test.db"
    # 用临时库替换单例连接
    ...