feat(sync): 每小时后台自动同步 + 手动拉取最新接口
- services/scheduler.py:通过 job_locks 表跨 worker 抢占,
gunicorn 多进程下一个周期只跑一次;claim 超时 30 分钟自动释放,
避免 worker 中途挂掉把任务永久卡死
- POST /api/garmin/sync-latest:同步执行,窗口 clamp 到 1..7 天
- GET /api/garmin/auto-sync:返回上次/下次运行时间
- db.py:注释里的分号会被 SCHEMA.split(";") 截断,改为先剥注释再切分
19 项调度器测试,全量 324 项通过
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,17 @@ CREATE TABLE IF NOT EXISTS personal_records (
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- Coordination for work that must happen once per interval regardless of how
|
||||
-- many gunicorn workers are running. A worker claims a job by writing its
|
||||
-- row, and the others see a fresh claim and stand down. Without this the
|
||||
-- hourly sync would fire once per worker.
|
||||
CREATE TABLE IF NOT EXISTS job_locks (
|
||||
name VARCHAR(64) PRIMARY KEY,
|
||||
holder VARCHAR(64),
|
||||
claimed_at DATETIME,
|
||||
last_run_at DATETIME
|
||||
);
|
||||
|
||||
-- Rendezvous for the interactive MFA login.
|
||||
-- garth asks for the code through a *blocking* callback, so the login parks in
|
||||
-- a background thread while the code arrives in a separate HTTP request that
|
||||
@@ -334,14 +345,25 @@ def _migrate(cur):
|
||||
|
||||
|
||||
# --- Public API -------------------------------------------------------------
|
||||
def _statements(schema):
|
||||
"""Split a schema script into statements.
|
||||
|
||||
Comments are stripped first: splitting the raw text on ';' would cut a
|
||||
comment that happens to contain one in half and hand the remainder to the
|
||||
database as SQL.
|
||||
"""
|
||||
lines = [ln for ln in schema.splitlines() if not ln.strip().startswith("--")]
|
||||
for stmt in "\n".join(lines).split(";"):
|
||||
stmt = stmt.strip()
|
||||
if stmt:
|
||||
yield stmt
|
||||
|
||||
|
||||
def init_db():
|
||||
conn = _connect()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
for stmt in SCHEMA.split(";"):
|
||||
stmt = stmt.strip()
|
||||
if not stmt:
|
||||
continue
|
||||
for stmt in _statements(SCHEMA):
|
||||
cur.execute(_adapt_sql(stmt))
|
||||
_migrate(cur)
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user