Files
yu-zhi-ran/platform/backend/app/api/system.py
T
Yuzhiran Dev fef435cc78 feat: 审查流程优化+采集定时调度+全链路LLM提示词升级
- 审查:移除 manual_review,改为迭代LLM修复(最多3次),合规分回写Topic
- 调度:scheduler 新增话题采集定时任务 scheduled_collect (01:30)
- 提示词:全链路8文件≈24个提示词升级,增强SEO/平台推荐/真人感
2026-05-14 21:52:02 +08:00

215 lines
8.8 KiB
Python

import logging
from fastapi import APIRouter, HTTPException, Depends, Body
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime, date
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.sync import sync_all_topics
from ..core.scheduler import scheduler
from .auth import get_current_user
PROJECT_ROOT = Path(__file__).resolve().parents[4]
if os.getenv('PROJECT_ROOT'):
PROJECT_ROOT = Path(os.getenv('PROJECT_ROOT'))
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/system", tags=["system"])
def _aggregate_status_counts(db: Session):
"""聚合状态计数,兼容中英文状态值"""
raw = db.query(Topic.status, func.count()).group_by(Topic.status).all()
mapping = {
'pending': ['pending', '待处理'],
'review': ['review', '待审查'],
'ready': ['ready', '待发布'],
'published': ['published', '已发布']
}
counts = {'pending': 0, 'review': 0, 'ready': 0, 'published': 0}
for status_val, cnt in raw:
for key, aliases in mapping.items():
if status_val in aliases:
counts[key] += cnt
break
return counts
@router.get("/status")
def get_status(db: Session = Depends(get_db)):
total = db.query(Topic).count()
counts = _aggregate_status_counts(db)
today = date.today()
today_count = db.query(Topic).filter(func.date(Topic.created_at) == today).count()
return {
"stats": {
"total": total,
"pending": counts['pending'],
"review": counts['review'],
"ready": counts['ready'],
"published": counts['published'],
"today": today_count
}
}
@router.post("/generate/run", dependencies=[Depends(get_current_user)])
def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Depends(get_db)):
logger.info(f"Received 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}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@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)):
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:
topic = db.query(Topic).filter(Topic.id == tid).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", "")}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@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"
if not log_file.exists():
raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}")
content = log_file.read_text(encoding='utf-8')
lines = content.splitlines()[-100:] if log_type != "collector" else content.splitlines()[-200:]
return {"log_date": log_date, "log_type": log_type, "content": lines}
@router.get("/pipeline/status", dependencies=[Depends(get_current_user)])
def get_pipeline_status(db: Session = Depends(get_db)):
total = db.query(Topic).count()
counts = _aggregate_status_counts(db)
log_files = {
"collector": LOGS_DIR / f"collector_{date.today().isoformat()}.log",
"creator": LOGS_DIR / f"creator_{date.today().isoformat()}.log",
"optimizer": LOGS_DIR / f"optimizer_{date.today().isoformat()}.log",
}
pipeline_status = {}
for name, log_file in log_files.items():
if log_file.exists():
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
pipeline_status[name] = {"last_run": mtime.isoformat(), "exists": True}
else:
pipeline_status[name] = {"exists": False, "last_run": None}
return {"topics_count": total, "status_distribution": counts, "pipeline_modules": pipeline_status}
@router.post("/sync/run")
def run_sync():
try:
sync_all_topics()
return {"message": "Sync completed (DB → JSON backup)"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/automation/topics")
def list_automation_topics(db: Session = Depends(get_db)):
try:
topics = db.query(Topic).order_by(Topic.created_at.desc()).limit(100).all()
result = []
for t in topics:
result.append({
"id": t.id,
"title": t.title,
"field": t.field,
"status": t.status,
"priority": t.priority,
"priority_score": t.priority_score,
"total_score": t.total_score,
"created_at": t.created_at.isoformat() if t.created_at else None,
"updated_at": t.updated_at.isoformat() if t.updated_at else None,
"ready_at": t.ready_at.isoformat() if t.ready_at else None,
"compliance_score": t.compliance_score
})
return {"count": len(result), "topics": result[:50]}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/refresh")
def refresh_all():
try:
sync_all_topics()
return {"message": "Refresh completed"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/scheduler/status", dependencies=[Depends(get_current_user)])
def get_scheduler_status():
return {"running": scheduler._started, "jobs": scheduler.get_jobs()}
@router.get("/modules/status", dependencies=[Depends(get_current_user)])
def get_modules_status():
today_str = date.today().isoformat()
module_logs = {
"creator": {
"key": "🤖 内容创作引擎",
"log": LOGS_DIR / f"creator_{today_str}.log",
"config_file": PROJECT_ROOT / "automation" / "data" / "outlines" / today_str,
},
"optimizer": {
"key": "🔍 内容优化器",
"log": LOGS_DIR / f"optimizer_{today_str}.log",
"config_file": PROJECT_ROOT / "automation" / "data" / "drafts" / today_str,
},
"collector": {
"key": "📡 内容收集器",
"log": LOGS_DIR / f"collector_{today_str}.log",
"config_file": PROJECT_ROOT / "automation" / "data",
},
}
modules = []
for mod_id, cfg in module_logs.items():
log_file = cfg["log"]
last_run = None
task_count = 0
success_rate = None
if log_file.exists():
mtime = datetime.fromtimestamp(log_file.stat().st_mtime)
last_run = mtime.strftime("%Y-%m-%d %H:%M")
content = log_file.read_text(encoding="utf-8", errors="ignore")
task_count = content.count("完成") + content.count("success") + content.count("SUCCESS")
total = task_count + content.count("失败") + content.count("failed") + content.count("ERROR")
success_rate = round(task_count / total * 100) if total > 0 else None
modules.append({
"id": mod_id,
"title": cfg["key"],
"status": "running" if log_file.exists() else "stopped",
"last_run": last_run or "从未运行",
"task_count": task_count,
"success_rate": success_rate,
})
return {"modules": modules, "scheduler": {"running": scheduler._started, "jobs": scheduler.get_jobs()}}