Files
2026-05-29 13:27:41 +08:00

83 lines
2.8 KiB
Python

"""SQLite 持久化层。占坑期方案,够用即可。
- 单文件 sqlite3,默认 `/opt/duobibi-server/data.db`(env `DUOBIBI_DB_PATH` 可覆盖)
- 用 stdlib sqlite3,不引入 SQLAlchemy(占坑期不需要 ORM)
- 表结构:
- `user`:phone 当主键(占坑期账号体系)
- `wish_item`:用户的心愿单,phone 关联 user,deleted_at 软删,updated_at 用于同步冲突解决
并发说明:sqlite3 默认每个连接独占,FastAPI 是 thread pool 跑 sync handler。
每次请求新开 connection + use as ctx manager,简单可靠。
"""
from __future__ import annotations
import os
import sqlite3
from contextlib import contextmanager
from typing import Iterator
DB_PATH = os.environ.get("DUOBIBI_DB_PATH", "/opt/duobibi-server/data.db")
def _connect() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH, timeout=10.0, isolation_level=None)
conn.row_factory = sqlite3.Row
# WAL 提升并发读写;FastAPI thread pool 下多线程访问同一文件时有用
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
@contextmanager
def get_conn() -> Iterator[sqlite3.Connection]:
"""每次请求开一个新 connection,with 块结束自动 close。"""
conn = _connect()
try:
yield conn
finally:
conn.close()
def init_schema() -> None:
"""启动时调用,IF NOT EXISTS 建表。改 schema 需手动 migration(写额外 SQL)。"""
with get_conn() as conn:
conn.executescript(
"""
CREATE TABLE IF NOT EXISTS user (
phone TEXT PRIMARY KEY,
created_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS wish_item (
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone TEXT NOT NULL,
title TEXT NOT NULL,
target_price REAL,
note TEXT,
notify_enabled INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
deleted_at INTEGER,
FOREIGN KEY (phone) REFERENCES user(phone) ON DELETE CASCADE
);
-- 按用户拉取 / 增量同步主索引
CREATE INDEX IF NOT EXISTS idx_wish_phone_updated
ON wish_item(phone, updated_at DESC);
"""
)
def upsert_user(phone: str, now_ms: int) -> None:
"""登录成功时调:有则更新 last_seen_at,无则插入。"""
with get_conn() as conn:
conn.execute(
"""
INSERT INTO user (phone, created_at, last_seen_at)
VALUES (?, ?, ?)
ON CONFLICT(phone) DO UPDATE SET last_seen_at = excluded.last_seen_at
""",
(phone, now_ms, now_ms),
)