Fix calendar filter, module triggers, API cleanup, consolidate get_current_admin, fix LLM schema

This commit is contained in:
Yuzhiran Dev
2026-05-20 14:15:00 +08:00
parent 498165440f
commit 0a8fbda4fd
23 changed files with 118 additions and 155 deletions
+1 -13
View File
@@ -9,23 +9,11 @@ from ..database import get_db
from ..models import User, Topic, SystemConfig
from ..schemas import UserCreate, UserUpdate, UserResponse
from ..core.audit_logger import audit_log
from .auth import org_filter
from .auth import get_current_admin, org_filter
import json
router = APIRouter(prefix="/api/admin", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token, org_filter
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("/users", response_model=List[UserResponse])
def list_users(
request: Request,
+4 -15
View File
@@ -4,28 +4,17 @@ from typing import List, Optional
from datetime import datetime, timedelta
from ..database import get_db
from ..models import AuditLog
from ..models import AuditLog, User
from ..schemas import AuditLogResponse
from .auth import verify_token
from .auth import get_current_admin
router = APIRouter(prefix="/api/audit", tags=["audit"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("/logs", response_model=List[AuditLogResponse])
def list_audit_logs(
request: Request,
db: Session = Depends(get_db),
admin_user: bool = Depends(get_current_admin),
admin_user: User = Depends(get_current_admin),
username: Optional[str] = Query(None, description="按用户名筛选"),
action: Optional[str] = Query(None, description="按操作类型筛选"),
resource_type: Optional[str] = Query(None, description="按资源类型筛选"),
@@ -64,7 +53,7 @@ def list_audit_actions(
def list_audit_users(
request: Request,
db: Session = Depends(get_db),
admin_user: bool = Depends(get_current_admin),
admin_user: User = Depends(get_current_admin),
limit: int = Query(50, ge=1, le=200)
):
"""获取最近产生审计记录的用户列表"""
+1 -12
View File
@@ -5,21 +5,10 @@ from typing import List
from ..database import get_db
from ..models import Case
from ..schemas import CaseBase, CaseResponse
from .auth import get_current_admin
router = APIRouter(prefix="/api/admin/cases", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("", response_model=List[CaseResponse])
def list_cases(
request: Request,
+1 -12
View File
@@ -5,21 +5,10 @@ from typing import List
from ..database import get_db
from ..models import LLMConfig
from ..schemas import LLMConfigBase, LLMConfigResponse
from .auth import get_current_admin
router = APIRouter(prefix="/api/admin/llmconfigs", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("", response_model=List[LLMConfigResponse])
def list_llm_configs(
request: Request,
+11 -1
View File
@@ -11,6 +11,7 @@ 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.sync import sync_all_topics
from ..core.scheduler import scheduler
from .auth import get_current_user, org_filter
@@ -69,6 +70,15 @@ def trigger_generation(topic_id: str = Body(None, embed=True), db: Session = Dep
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@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}
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), current_user=Depends(get_current_user)):
try:
@@ -212,7 +222,7 @@ def get_modules_status():
log_based: dict = {
"scheduled_collect": {"name": "📡 内容采集", "log": LOGS_DIR / f"collector_{today_str}.log"},
"scheduled_generate": {"name": "🤖 内容创作", "log": LOGS_DIR / f"creator_{today_str}.log"},
"scheduled_optimize": {"name": "🔍 内容优化", "log": LOGS_DIR / f"optimizer_{today_str}.log"},
"scheduled_optimize": {"name": "🔍 合规审查", "log": LOGS_DIR / f"optimizer_{today_str}.log"},
"scheduled_optimize_sources": {"name": "📡 信息源优化", "log": LOGS_DIR / f"optimizer_sources_{today_str}.log"},
"scheduled_metrics_sync": {"name": "📊 指标同步", "log": LOGS_DIR / f"metrics_sync_{today_str}.log"},
}
+1 -12
View File
@@ -5,21 +5,10 @@ from typing import List, Dict, Any
from ..database import get_db
from ..models import SystemConfig
from ..schemas import SystemConfigBase, SystemConfigResponse
from .auth import get_current_admin
router = APIRouter(prefix="/api/admin/systemconfigs", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("", response_model=List[SystemConfigResponse])
def list_system_configs(
request: Request,
+1 -12
View File
@@ -5,21 +5,10 @@ from typing import List, Optional
from ..database import get_db
from ..models import TaskLog
from ..schemas import TaskLogBase, TaskLogResponse
from .auth import get_current_admin
router = APIRouter(prefix="/api/admin/tasklogs", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("", response_model=List[TaskLogResponse])
def list_task_logs(
request: Request,
+1 -28
View File
@@ -6,10 +6,9 @@ from datetime import datetime, date
from pathlib import Path
from ..database import get_db
from ..models import Topic, TopicField, TopicConfigField, Article, PublishRecord, ContentMetrics
from ..models import Topic, TopicField, TopicConfigField, Article, ContentMetrics
from ..schemas import (
TopicCreate, TopicUpdate, TopicResponse, TopicScoreRequest,
PublishRequest, PublishActionRequest, PublishRecordResponse
)
from .auth import get_current_user, org_filter
@@ -241,32 +240,6 @@ def score_topic(
return {"priority_score": topic.priority_score, "total_score": topic.total_score}
@router.post("/{topic_id}/publish")
def publish_topic(topic_id: str, req: PublishRequest, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="Topic not found")
_check_org(topic, current_user, db)
if topic.status not in ("pending", "ready", "draft"):
raise HTTPException(status_code=400, detail=f"选题状态({topic.status})不允许发布")
topic.status = "published"
topic.published_at = date.today()
topic.updated_at = datetime.now()
topic.platform_urls = req.platform_urls
record = PublishRecord(
topic_id=topic_id,
platform="all",
action="publish",
status="success",
description=f"选题 {topic_id} 已发布"
)
db.add(record)
db.commit()
return {"ok": True, "topic_id": topic_id}
@router.get("/{topic_id}/articles", response_model=List[Dict[str, Any]])
def get_topic_articles(topic_id: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)):
topic = db.query(Topic).filter(Topic.id == topic_id).first()
+6 -1
View File
@@ -19,8 +19,13 @@ def run_collector():
script_path = PROJECT_ROOT / "scripts" / "collector.py"
if not script_path.exists():
raise FileNotFoundError(f"Collector script not found: {script_path}")
venv_python = PROJECT_ROOT / "platform" / "backend" / "venv" / "bin" / "python"
if venv_python.exists():
cmd = [str(venv_python), str(script_path)]
else:
cmd = ["python3", str(script_path)]
result = subprocess.run(
["python", str(script_path)],
cmd,
capture_output=True,
text=True,
cwd=PROJECT_ROOT,
+4 -4
View File
@@ -62,7 +62,7 @@ class TaskScheduler:
)
self.scheduler.start()
self._started = True
logger.info("Scheduler started with daily cron triggers (01:30 collect, 02:30 sync, 03:30 generate, 04:30 optimize, 05:00 optimize_sources, 06:00 metrics_sync)")
logger.info("Scheduler started: 01:30 collect, 03:30 generate, 04:30 review, 05:00 optimize_sources, 06:00 metrics_sync")
def shutdown(self):
if self.scheduler.running:
self.scheduler.shutdown()
@@ -86,11 +86,11 @@ class TaskScheduler:
def _run_optimize(self):
try:
logger.info("[Scheduled] Starting compliance optimization...")
logger.info("[Scheduled] Starting compliance review...")
result = run_optimizer()
logger.info("[Scheduled] Optimization completed: %s", result)
logger.info("[Scheduled] Review completed: %s", result)
except Exception as e:
logger.exception("[Scheduled] Optimization failed: %s", e)
logger.exception("[Scheduled] Review failed: %s", e)
def _run_collect(self):
try:
-8
View File
@@ -490,8 +490,6 @@ class LLMConfigResponse(LLMConfigBase):
id: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True)
@@ -519,9 +517,3 @@ class SystemStatus(BaseModel):
execution_time: Optional[float] = None
class OptimizationRequest(BaseModel):
topic_ids: Optional[List[str]] = None
class BatchPublishRequest(BaseModel):
date: str
+12 -5
View File
@@ -106,8 +106,7 @@
<div><span>今日任务</span><span>{{ mod.task_count }} 个</span></div>
<div><span>成功率</span><span>{{ mod.success_rate > 0 ? mod.success_rate + '%' : '暂无' }}</span></div>
<div style="margin-top:10px; border-bottom:none;">
<el-button v-if="mod.id === 'scheduled_optimize_sources'" size="small" type="primary" @click="triggerModule(mod.id)" :loading="runningModule === mod.id">立即运行</el-button>
<el-button v-if="mod.id === 'scheduled_metrics_sync'" size="small" type="primary" @click="triggerModule(mod.id)" :loading="runningModule === mod.id">立即运行</el-button>
<el-button size="small" type="primary" @click="triggerModule(mod.id)" :loading="runningModule === mod.id">立即运行</el-button>
</div>
</div>
</div>
@@ -271,8 +270,8 @@
const end = new Date(today);
end.setDate(end.getDate() + 7);
this.upcomingEntries = (all || [])
.filter(e => { const d = new Date(e.date); return d >= today && d < end; })
.sort((a, b) => a.date.localeCompare(b.date));
.filter(e => { const d = new Date(e.planned_date); return d >= today && d < end; })
.sort((a, b) => a.planned_date.localeCompare(b.planned_date));
}
} catch (e) {
console.error('获取近期计划失败:', e);
@@ -284,9 +283,17 @@
},
async triggerModule(modId) {
this.runningModule = modId;
const endpoints = {
scheduled_collect: '/api/system/collect/run',
scheduled_generate: '/api/system/generate/run',
scheduled_optimize: '/api/system/review/run',
scheduled_optimize_sources: '/api/system/optimize-sources/run',
scheduled_metrics_sync: '/api/system/metrics-sync/run',
};
const endpoint = endpoints[modId];
if (!endpoint) { this.$message.error('未知模块'); this.runningModule = null; return; }
try {
const token = localStorage.getItem('authToken');
const endpoint = modId === 'scheduled_optimize_sources' ? '/api/system/optimize-sources/run' : '/api/system/metrics-sync/run';
const resp = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } });
if (resp.ok) {
const data = await resp.json();
-1
View File
@@ -174,7 +174,6 @@ const TasksApp = {
data() {
const SCHEDULER_JOBS = {
'scheduled_collect': { icon: 'IconRefresh', name: '内容采集', defaultTime: '01:30' },
'scheduled_sync': { icon: 'IconRefresh', name: '数据同步', defaultTime: '02:30' },
'scheduled_generate': { icon: 'IconDocument', name: '内容创作', defaultTime: '03:30' },
'scheduled_optimize': { icon: 'IconSearch', name: '合规审查', defaultTime: '04:30' },
'scheduled_optimize_sources': { icon: 'IconSetting', name: '信息源优化', defaultTime: '05:00' },
+1 -1
View File
@@ -138,7 +138,7 @@
<el-button type="primary" @click="confirmPublish" :loading="publishing" :disabled="!publishPlatforms.zhihu && !publishPlatforms.wechat && !publishPlatforms.xiaohongshu">确认发布</el-button>
</template>
</el-dialog>
<el-dialog v-model="previewVisible" title="选题预览" width="85%" :modal-props="{ closeOnClickModal: false }" :before-close="() => previewVisible = false" class="preview-dialog-custom" :fullscreen="previewFullscreen" close-on-press-escape>
<el-dialog v-model="previewVisible" title="选题预览" width="85%" :modal-props="{ closeOnClickModal: false }" :before-close="() => previewVisible = false" class="preview-dialog-custom" :fullscreen="previewFullscreen" close-on-press-escape :lock-scroll="false">
<div v-if="previewTopic">
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:8px;">
<h2 style="margin:0; font-size:16px;">{{ previewTopic.title }}</h2>