fix: content quality, image format, task monitor, calendar data source, search UI & sort

This commit is contained in:
Yuzhiran Dev
2026-05-26 11:26:10 +08:00
parent b2d043b231
commit ac8644d752
36 changed files with 2294 additions and 873 deletions
+75 -12
View File
@@ -3,16 +3,17 @@ import subprocess
from fastapi import APIRouter, HTTPException, Depends, Body
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime, date
from datetime import datetime, date, timezone, timedelta
from pathlib import Path
from typing import Dict, Any, List, Optional
import os
import os
import json
from ..database import get_db
from ..models import Topic, Article, TaskConfig, TaskLog
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
from ..core.generator import run_creator, get_generator_status, _running_processes as _generator_running
from ..core.optimizer import run_optimizer, get_optimizer_status, _running_processes as _optimizer_running
from ..core.collector import run_collector, get_collector_status, _running_processes as _collector_running
import threading
from ..core.sync import sync_all_topics
from ..core.scheduler import scheduler
@@ -26,6 +27,32 @@ LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/system", tags=["system"])
_active_monitors: Dict[int, dict] = {}
def _monitor_subprocess(log_id: int, proc, module_id: str, task_name: str, db_session_factory):
"""阻塞等待子进程退出(最长 1800s),完成后更新 task_logs"""
try:
returncode = proc.wait(timeout=1800)
except subprocess.TimeoutExpired:
proc.kill()
returncode = -1
logger.warning("Subprocess %s (pid=%s) killed after 1800s timeout", module_id, proc.pid)
finished_at = datetime.now(timezone.utc)
try:
db = db_session_factory()
log = db.query(TaskLog).filter(TaskLog.id == log_id).first()
if log:
log.status = "success" if returncode == 0 else "failed"
log.finished_at = finished_at
if log.started_at:
log.duration = int((finished_at - log.started_at).total_seconds())
db.commit()
db.close()
except Exception as e:
logger.warning("Failed to update task log %s: %s", log_id, e)
finally:
_active_monitors.pop(log_id, None)
def _aggregate_status_counts(q):
"""聚合状态计数,兼容中英文状态值"""
raw = q.with_entities(Topic.status, func.count()).group_by(Topic.status).all()
@@ -60,12 +87,22 @@ def get_status(db: Session = Depends(get_db)):
}
}
@router.post("/generate/run", dependencies=[Depends(get_current_user)])
@router.post("/generate/run")
def trigger_generation(topic_id: Optional[str] = None, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
logger.info(f"Generation triggered by {current_user.username}, topic_id={topic_id}")
try:
result = run_creator(topic_id)
return {"message": "内容创作已后台启动", "pid": result.get("pid")}
from ..database import SessionLocal
log = TaskLog(module_id="scheduled_generate", task_name="🤖 内容创作", status="running", message="内容创作已启动", triggered_by="manual", started_at=datetime.now(timezone.utc))
db.add(log)
db.commit()
log_id = log.id
proc_info = result.get("proc") or result
proc = _generator_running.get("generator", {}).get("process") if "pid" in result else None
if proc:
t = threading.Thread(target=_monitor_subprocess, args=(log_id, proc, "scheduled_generate", "🤖 内容创作", SessionLocal), daemon=True)
t.start()
return {"message": "内容创作已后台启动", "pid": result.get("pid"), "log_id": log_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@@ -76,12 +113,21 @@ def generation_status():
return {"status": "idle", "message": "当前无运行中的创作任务"}
return status
@router.post("/collect/run", dependencies=[Depends(get_current_user)])
@router.post("/collect/run")
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}
from ..database import SessionLocal
log = TaskLog(module_id="scheduled_collect", task_name="📡 内容采集", status="running", message="内容采集已启动", triggered_by="manual", started_at=datetime.now(timezone.utc))
db.add(log)
db.commit()
log_id = log.id
proc = _collector_running.get("collector", {}).get("process") if "pid" in result else None
if proc:
t = threading.Thread(target=_monitor_subprocess, args=(log_id, proc, "scheduled_collect", "📡 内容采集", SessionLocal), daemon=True)
t.start()
return {"message": "内容采集已后台启动", "result": result, "log_id": log_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@@ -92,11 +138,20 @@ def collection_status():
return {"status": "idle", "message": "当前无运行中的采集任务"}
return status
@router.post("/review/run", dependencies=[Depends(get_current_user)])
@router.post("/review/run")
def trigger_review(topic_ids: Optional[List[str]] = None, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
try:
result = run_optimizer(topic_ids)
return {"message": "合规审查已后台启动", "pid": result.get("pid")}
from ..database import SessionLocal
log = TaskLog(module_id="scheduled_optimize", task_name="🔍 合规审查", status="running", message="合规审查已启动", triggered_by="manual", started_at=datetime.now(timezone.utc))
db.add(log)
db.commit()
log_id = log.id
proc = _optimizer_running.get("optimizer", {}).get("process") if "pid" in result else None
if proc:
t = threading.Thread(target=_monitor_subprocess, args=(log_id, proc, "scheduled_optimize", "🔍 合规审查", SessionLocal), daemon=True)
t.start()
return {"message": "合规审查已后台启动", "pid": result.get("pid"), "log_id": log_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@@ -177,17 +232,24 @@ def trigger_metrics_sync():
raise HTTPException(status_code=500, detail=str(e))
@router.post("/refresh-search-cache/run")
def trigger_refresh_search_cache():
def trigger_refresh_search_cache(db: Session = Depends(get_db), current_user=Depends(get_current_user)):
try:
import sys as sys_mod
scripts_dir = Path(__file__).parent.parent.parent.parent / "scripts"
from ..database import SessionLocal as _ss
proc = subprocess.Popen(
[sys_mod.executable, str(scripts_dir / "opencode_search.py"), "--refresh-cache"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
cwd=scripts_dir.parent.parent
)
logger.info("Search cache refresh started (pid=%s)", proc.pid)
return {"message": "搜索缓存刷新已后台启动", "pid": proc.pid}
log = TaskLog(module_id="scheduled_refresh_search_cache", task_name="🔍 搜索缓存", status="running", message="搜索缓存刷新已启动", triggered_by="manual", started_at=datetime.now(timezone.utc), result_data={"pid": proc.pid})
db.add(log)
db.commit()
log_id = log.id
t = threading.Thread(target=_monitor_subprocess, args=(log_id, proc, "scheduled_refresh_search_cache", "🔍 搜索缓存", _ss), daemon=True)
t.start()
return {"message": "搜索缓存刷新已后台启动", "pid": proc.pid, "log_id": log_id}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@@ -259,6 +321,7 @@ def get_modules_status(db: Session = Depends(get_db)):
"scheduled_optimize": {"name": "🔍 合规审查", "cron": "03:00", "params_desc": {"auto_pass_threshold": "自动通过分数阈值"}},
"scheduled_optimize_sources": {"name": "📡 信息源优化", "cron": "05:00", "params_desc": {}},
"scheduled_metrics_sync": {"name": "📊 指标同步", "cron": "06:00", "params_desc": {}},
"scheduled_task_monitor": {"name": "⏰ 任务监控", "cron": "*", "params_desc": {}},
}
modules = []