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:
+66
-6
@@ -26,7 +26,7 @@ def crawl(
|
||||
from app.database import SessionLocal, engine
|
||||
from app.database import init_db as _init
|
||||
from app.models import Paper
|
||||
from app.services.crawler import crawl_daily
|
||||
from app.services.jobs import create_job, run_job
|
||||
from app.utils import today_str, yesterday_str
|
||||
from sqlalchemy import func, select
|
||||
|
||||
@@ -55,7 +55,13 @@ def crawl(
|
||||
return
|
||||
|
||||
typer.echo(f"📡 开始抓取 {target} ...")
|
||||
result = asyncio.run(crawl_daily(db, target, top_n))
|
||||
job = create_job(
|
||||
db,
|
||||
"crawl_daily",
|
||||
owner="cli_crawl",
|
||||
payload={"target_date": target, "top_n": top_n},
|
||||
)
|
||||
result = asyncio.run(run_job(db, job.id))
|
||||
|
||||
# 未指定日期且今天失败或无数据时,自动回退到昨天
|
||||
need_fallback = not date_str and (
|
||||
@@ -76,7 +82,13 @@ def crawl(
|
||||
else:
|
||||
typer.echo(f"🔄 {target} 无数据,尝试 {fallback} ...")
|
||||
target = fallback
|
||||
result = asyncio.run(crawl_daily(db, target, top_n))
|
||||
job = create_job(
|
||||
db,
|
||||
"crawl_daily",
|
||||
owner="cli_crawl",
|
||||
payload={"target_date": target, "top_n": top_n},
|
||||
)
|
||||
result = asyncio.run(run_job(db, job.id))
|
||||
|
||||
if result["status"] == "success":
|
||||
typer.echo(
|
||||
@@ -110,7 +122,7 @@ def summarize(
|
||||
from app.config import settings
|
||||
from app.database import SessionLocal, engine
|
||||
from app.database import init_db as _init
|
||||
from app.services.summarizer import summarize_batch, summarize_single
|
||||
from app.services.jobs import create_job, run_job
|
||||
|
||||
import os
|
||||
|
||||
@@ -142,11 +154,25 @@ def summarize(
|
||||
try:
|
||||
if arxiv_id:
|
||||
typer.echo(f"🤖 开始总结 {arxiv_id} (mode={pdf_mode}) ...")
|
||||
result = asyncio.run(summarize_single(db, arxiv_id, pdf_mode=pdf_mode))
|
||||
job = create_job(
|
||||
db,
|
||||
"summarize_one",
|
||||
owner="cli_summarize",
|
||||
payload={"arxiv_id": arxiv_id, "pdf_mode": pdf_mode, "force": False},
|
||||
)
|
||||
else:
|
||||
typer.echo(f"🤖 开始批量总结 pending 论文 (mode={pdf_mode}) ...")
|
||||
result = asyncio.run(summarize_batch(db, pdf_mode=pdf_mode))
|
||||
job = create_job(
|
||||
db,
|
||||
"summarize_batch",
|
||||
owner="cli_summarize",
|
||||
payload={"pdf_mode": pdf_mode},
|
||||
)
|
||||
|
||||
result = asyncio.run(run_job(db, job.id))
|
||||
if result.get("status") == "failed":
|
||||
typer.echo(f"❌ 总结失败:{result.get('error')}", err=True)
|
||||
raise typer.Exit(code=1)
|
||||
typer.echo(f"✅ 总结完成:{result}")
|
||||
except NotFoundError as exc:
|
||||
typer.echo(f"❌ {exc.message}", err=True)
|
||||
@@ -172,5 +198,39 @@ def init_db():
|
||||
typer.echo(f"✅ 数据库已初始化:{settings.db_path}")
|
||||
|
||||
|
||||
@cli_app.command("rebuild-derived")
|
||||
def rebuild_derived(
|
||||
fts: bool = typer.Option(False, "--fts", help="重建 FTS5 全文索引"),
|
||||
chroma: bool = typer.Option(False, "--chroma", help="重建 ChromaDB 语义索引"),
|
||||
):
|
||||
"""重建可派生数据索引。"""
|
||||
from app.config import settings
|
||||
from app.database import SessionLocal, engine
|
||||
from app.database import init_db as _init
|
||||
from app.services.jobs import create_job, run_job
|
||||
|
||||
import os
|
||||
|
||||
if not fts and not chroma:
|
||||
fts = True
|
||||
|
||||
os.makedirs(settings.db_path.parent, exist_ok=True)
|
||||
_init(engine)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
for job_type in [
|
||||
*(["reindex_fts"] if fts else []),
|
||||
*(["reindex_chroma"] if chroma else []),
|
||||
]:
|
||||
job = create_job(db, job_type, owner="cli_rebuild_derived", payload={})
|
||||
result = asyncio.run(run_job(db, job.id))
|
||||
typer.echo(f"{job_type}: {result}")
|
||||
if result.get("status") == "failed":
|
||||
raise typer.Exit(code=1)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_app()
|
||||
|
||||
Reference in New Issue
Block a user