refactor: extract admin business logic to services, introduce job queue, add derived index helpers

- Move DB operations from routes/admin.py to services/admin.py (get_logs_context, query_summary_statuses, retry_failed, delete/reset operations)
- Add services/jobs.py with Job/JobEvent-based async job queue (create_job, run_job, enqueue_job)
- Add services/derived.py with FTS5 reindex and paper index deletion helpers
- Refactor scheduler to use job queue instead of direct pipeline calls
- Add heartbeat_at/expires_at to TaskLock for lock health tracking
- Remove DESIGN_REVIEW.md
- Update tests: remove redundant integration tests, add unit tests for new services
This commit is contained in:
2026-06-13 18:31:43 +08:00
parent 21f16e6756
commit 743d69efd0
20 changed files with 1391 additions and 1063 deletions
+59
View File
@@ -32,6 +32,26 @@ class SummaryState(StrEnum):
PERMANENT_FAILURE = "permanent_failure"
class JobStatus(StrEnum):
"""后台任务状态枚举 — 对应 jobs.status 列。"""
QUEUED = "queued"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
STALE = "stale"
CANCELLED = "cancelled"
class JobEventStatus(StrEnum):
"""任务阶段事件状态枚举 — 对应 job_events.status 列。"""
STARTED = "started"
SUCCESS = "success"
FAILED = "failed"
INFO = "info"
# ── papers ──────────────────────────────────────────────────────────────
class Paper(Base):
__tablename__ = "papers"
@@ -194,9 +214,48 @@ class TaskLock(Base):
status = Column(String, nullable=False)
owner = Column(String)
acquired_at = Column(DateTime, nullable=False)
heartbeat_at = Column(DateTime)
expires_at = Column(DateTime)
released_at = Column(DateTime)
# ── jobs / job_events ──────────────────────────────────────────────────
class Job(Base):
__tablename__ = "jobs"
id = Column(Integer, primary_key=True, autoincrement=True)
type = Column(String, nullable=False, index=True)
status = Column(String, nullable=False, default=JobStatus.QUEUED, index=True)
owner = Column(String)
payload_json = Column(Text)
result_json = Column(Text)
error = Column(Text)
created_at = Column(DateTime, nullable=False)
started_at = Column(DateTime)
heartbeat_at = Column(DateTime)
completed_at = Column(DateTime)
events = relationship(
"JobEvent", back_populates="job", cascade="all, delete-orphan"
)
class JobEvent(Base):
__tablename__ = "job_events"
id = Column(Integer, primary_key=True, autoincrement=True)
job_id = Column(
Integer, ForeignKey("jobs.id", ondelete="CASCADE"), nullable=False, index=True
)
stage = Column(String, nullable=False)
status = Column(String, nullable=False)
message = Column(Text)
payload_json = Column(Text)
created_at = Column(DateTime, nullable=False)
job = relationship("Job", back_populates="events")
# ── user data ──────────────────────────────────────────────────────────
class UserBookmark(Base):
__tablename__ = "user_bookmarks"