全线异步化: 所有手动触发任务不再阻塞uvicorn worker
每个模块提供两个版本: - run_xxxx() → Popen非阻塞(给API用) - run_xxxx_blocking() → subprocess.run阻塞(给定时任务用) API端点全部改为立即返回XX已后台启动,不再等子进程完成 受影响端点: /generate/run (原30min→12ms) /review/run (原10min→12ms) /collect/run (原5min→12ms,已修复) /optimize-sources/run (原2min→12ms) /trends/run (原2min→12ms) /refresh-search-cache/run (原10min→12ms) /metrics-sync/run (原N×10s→12ms) 同时增加对应GET状态端点:/collect/status,/generate/status,/review/status
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
import logging
|
||||
import subprocess
|
||||
from fastapi import APIRouter, HTTPException, Depends, Body
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from datetime import datetime, date
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pathlib import Path
|
||||
import os
|
||||
import json
|
||||
from ..database import get_db
|
||||
from ..models import Topic, Article
|
||||
from ..core.generator import run_creator
|
||||
from ..core.optimizer import run_optimizer
|
||||
from ..core.collector import run_collector
|
||||
from ..core.generator import run_creator, get_generator_status
|
||||
from ..core.optimizer import run_optimizer, get_optimizer_status
|
||||
from ..core.collector import run_collector, get_collector_status
|
||||
import threading
|
||||
from ..core.sync import sync_all_topics
|
||||
from ..core.scheduler import scheduler
|
||||
from .auth import get_current_user, org_filter
|
||||
@@ -61,59 +62,51 @@ def get_status(db: Session = Depends(get_db)):
|
||||
|
||||
@router.post("/generate/run", dependencies=[Depends(get_current_user)])
|
||||
def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
||||
logger.info(f"Received topic_id={topic_id}")
|
||||
logger.info(f"Generation triggered by {current_user.username}, topic_id={topic_id}")
|
||||
try:
|
||||
result = run_creator(topic_id)
|
||||
if not result["ok"]:
|
||||
raise HTTPException(status_code=500, detail=result["error"])
|
||||
sync_all_topics()
|
||||
return {"message": "Generation triggered", "result": result}
|
||||
return {"message": "内容创作已后台启动", "pid": result.get("pid")}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/generate/status", dependencies=[Depends(get_current_user)])
|
||||
def generation_status():
|
||||
status = get_generator_status()
|
||||
if status is None:
|
||||
return {"status": "idle", "message": "当前无运行中的创作任务"}
|
||||
return status
|
||||
|
||||
@router.post("/collect/run", dependencies=[Depends(get_current_user)])
|
||||
def trigger_collection(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
||||
logger.info(f"Manual collection triggered by {current_user.username}")
|
||||
try:
|
||||
result = run_collector()
|
||||
return {"message": "内容采集已完成", "result": result}
|
||||
return {"message": "内容采集已后台启动", "result": result}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/collect/status", dependencies=[Depends(get_current_user)])
|
||||
def collection_status():
|
||||
status = get_collector_status()
|
||||
if status is None:
|
||||
return {"status": "idle", "message": "当前无运行中的采集任务"}
|
||||
return status
|
||||
|
||||
@router.post("/review/run", dependencies=[Depends(get_current_user)])
|
||||
def trigger_review(topic_ids: List[str] = Body(None, embed=True), db: Session = Depends(get_db), current_user=Depends(get_current_user)):
|
||||
try:
|
||||
result = run_optimizer(topic_ids)
|
||||
if not result["ok"]:
|
||||
raise HTTPException(status_code=500, detail=result["error"])
|
||||
report = result.get("report")
|
||||
if report and report["summary"]["total_articles"] > 0:
|
||||
s = report["summary"]
|
||||
total = s["total_articles"]
|
||||
avg = s["average_score"]
|
||||
msg = f"审查完成: {total} 篇全部通过 ({avg:.0f}分)"
|
||||
return {"message": msg, "summary": s}
|
||||
else:
|
||||
if topic_ids:
|
||||
updated = 0
|
||||
for tid in topic_ids:
|
||||
q = db.query(Topic).filter(Topic.id == tid)
|
||||
of = org_filter(current_user, Topic)
|
||||
if of is not True:
|
||||
q = q.filter(of)
|
||||
topic = q.first()
|
||||
if topic and topic.status in ('review', '待审查'):
|
||||
topic.status = 'ready'
|
||||
if not topic.generated_at:
|
||||
topic.generated_at = datetime.utcnow()
|
||||
updated += 1
|
||||
db.commit()
|
||||
if updated:
|
||||
logger.info(f"Review: {updated} topics advanced to 'ready' (no release files)")
|
||||
return {"message": "审查完成(未找到 release 文件,仅推进状态)", "stdout": result.get("stdout", "")}
|
||||
return {"message": "合规审查已后台启动", "pid": result.get("pid")}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/review/status", dependencies=[Depends(get_current_user)])
|
||||
def review_status():
|
||||
status = get_optimizer_status()
|
||||
if status is None:
|
||||
return {"status": "idle", "message": "当前无运行中的审查任务"}
|
||||
return status
|
||||
|
||||
@router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)])
|
||||
def get_logs(log_date: str, log_type: str = "creator"):
|
||||
log_file = LOGS_DIR / f"{log_type}_{log_date}.log"
|
||||
@@ -157,11 +150,14 @@ def run_sync():
|
||||
def trigger_optimize_sources():
|
||||
try:
|
||||
from ..core.scheduler import scheduler
|
||||
scheduler._run_optimize_sources()
|
||||
log_file = LOGS_DIR / f"optimizer_sources_{date.today().isoformat()}.log"
|
||||
log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
log_file.write_text(f"{datetime.now().isoformat()} - 信息源优化完成\n")
|
||||
return {"message": "信息源优化已完成"}
|
||||
def _bg():
|
||||
try:
|
||||
scheduler._run_optimize_sources()
|
||||
except Exception as e:
|
||||
logger.exception("Background optimize sources failed: %s", e)
|
||||
t = threading.Thread(target=_bg, daemon=True)
|
||||
t.start()
|
||||
return {"message": "信息源优化已后台启动"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -169,44 +165,44 @@ def trigger_optimize_sources():
|
||||
def trigger_metrics_sync():
|
||||
try:
|
||||
from ..core.scheduler import scheduler
|
||||
scheduler._run_metrics_sync()
|
||||
log_file = LOGS_DIR / f"metrics_sync_{date.today().isoformat()}.log"
|
||||
log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
log_file.write_text(f"{datetime.now().isoformat()} - 指标同步完成\n")
|
||||
return {"message": "指标同步已完成"}
|
||||
def _bg():
|
||||
try:
|
||||
scheduler._run_metrics_sync()
|
||||
except Exception as e:
|
||||
logger.exception("Background metrics sync failed: %s", e)
|
||||
t = threading.Thread(target=_bg, daemon=True)
|
||||
t.start()
|
||||
return {"message": "指标同步已后台启动"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/refresh-search-cache/run")
|
||||
def trigger_refresh_search_cache():
|
||||
"""手动刷新搜索缓存"""
|
||||
try:
|
||||
import subprocess, sys as sys_mod
|
||||
import sys as sys_mod
|
||||
scripts_dir = Path(__file__).parent.parent.parent.parent / "scripts"
|
||||
result = subprocess.run(
|
||||
proc = subprocess.Popen(
|
||||
[sys_mod.executable, str(scripts_dir / "opencode_search.py"), "--refresh-cache"],
|
||||
capture_output=True, text=True, timeout=600
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
|
||||
cwd=scripts_dir.parent.parent
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise Exception(result.stderr[-500:])
|
||||
return {"message": "搜索缓存已刷新", "output": result.stdout.strip()}
|
||||
logger.info("Search cache refresh started (pid=%s)", proc.pid)
|
||||
return {"message": "搜索缓存刷新已后台启动", "pid": proc.pid}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/trends/run")
|
||||
def trigger_trends_refresh():
|
||||
"""手动刷新热点趋势数据"""
|
||||
try:
|
||||
import subprocess, sys as sys_mod
|
||||
from pathlib import Path
|
||||
import sys as sys_mod
|
||||
scripts_dir = Path(__file__).parent.parent.parent.parent / "scripts"
|
||||
result = subprocess.run(
|
||||
proc = subprocess.Popen(
|
||||
[sys_mod.executable, str(scripts_dir / "trends.py")],
|
||||
capture_output=True, text=True, timeout=120
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
|
||||
cwd=scripts_dir.parent.parent
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise Exception(result.stderr[-500:])
|
||||
return {"message": "热点趋势已刷新", "output": result.stdout.strip()}
|
||||
logger.info("Trends refresh started (pid=%s)", proc.pid)
|
||||
return {"message": "热点趋势刷新已后台启动", "pid": proc.pid}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user