c59dd304f7
端口: - 服务端口 8000 → 7329 - 前端开发端口 5173 → 7330 安全: - CORS 收紧为白名单,关闭 credentials - SPA 路由白名单完善 - 前端 XSS 转义 可靠性: - 时区统一为 datetime.now(timezone.utc) - 文章入库改为内存去重 + 增量计数 - OPML 导入改为 body 参数接收 - OPML 导出 URL XML 转义 - 首次抓取改为 BackgroundTasks 异步 - articles.py HTTPException 移到顶部 import - FTS5 异常显式日志 - FTS5 查询加引号包裹防布尔注入 - 中文摘要支持中文标点 - 去掉未使用的 hashlib import 部署: - Dockerfile 锁 python:3.12.7-slim - requirements 锁定具体版本 - healthcheck 不用 curl(镜像里没有) - docker-compose 使用 .env 文件 - 新增 .env 配置文件
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
"""RSS 源健康度检测"""
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import List, Dict
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import Session
|
|
from models import Feed, FetchLog
|
|
|
|
|
|
def get_feed_health(db: Session, feed_id: int = None) -> List[Dict]:
|
|
"""获取 RSS 源健康度信息
|
|
返回每个源的健康状态详情
|
|
"""
|
|
now = datetime.now(timezone.utc)
|
|
query = db.query(Feed)
|
|
if feed_id:
|
|
query = query.filter(Feed.id == feed_id)
|
|
|
|
feeds = query.all()
|
|
results = []
|
|
|
|
for feed in feeds:
|
|
total = feed.success_count + feed.fail_count
|
|
success_rate = round(feed.success_count / total * 100, 1) if total > 0 else 0
|
|
|
|
days_since_fetch = None
|
|
if feed.last_fetch_at:
|
|
days_since_fetch = (now - feed.last_fetch_at).days
|
|
|
|
# 获取最近 7 天抓取记录
|
|
week_ago = now - timedelta(days=7)
|
|
recent_logs = db.query(FetchLog).filter(
|
|
FetchLog.feed_id == feed.id,
|
|
FetchLog.created_at >= week_ago
|
|
).order_by(FetchLog.created_at.desc()).limit(10).all()
|
|
|
|
health = feed.health_status(now=now)
|
|
|
|
results.append({
|
|
"id": feed.id,
|
|
"title": feed.title or feed.url,
|
|
"url": feed.url,
|
|
"is_active": feed.is_active,
|
|
"health_status": health,
|
|
"health_label": _health_label(health),
|
|
"success_rate": success_rate,
|
|
"success_count": feed.success_count,
|
|
"fail_count": feed.fail_count,
|
|
"total_fetches": total,
|
|
"last_fetch_at": feed.last_fetch_at.isoformat() if feed.last_fetch_at else None,
|
|
"days_since_fetch": days_since_fetch,
|
|
"article_count": feed.article_count,
|
|
"last_error": feed.last_error,
|
|
"recent_logs": [
|
|
{
|
|
"status": log.status,
|
|
"articles_fetched": log.articles_fetched,
|
|
"response_time_ms": log.response_time_ms,
|
|
"created_at": log.created_at.isoformat(),
|
|
"error_message": log.error_message if log.status == "fail" else None,
|
|
}
|
|
for log in recent_logs
|
|
],
|
|
})
|
|
|
|
return results
|
|
|
|
|
|
def _health_label(status: str) -> str:
|
|
labels = {
|
|
"healthy": "健康",
|
|
"warning": "警告",
|
|
"unhealthy": "异常",
|
|
"unknown": "未知",
|
|
}
|
|
return labels.get(status, "未知")
|
|
|
|
|
|
def get_overall_stats(db: Session) -> Dict:
|
|
"""获取整体统计信息"""
|
|
total_feeds = db.query(Feed).count()
|
|
active_feeds = db.query(Feed).filter(Feed.is_active == True).count()
|
|
total_articles_count = db.query(func.sum(Feed.article_count)).scalar() or 0
|
|
|
|
# 健康源统计
|
|
feeds = db.query(Feed).all()
|
|
healthy = warning = unhealthy = 0
|
|
now = datetime.now(timezone.utc)
|
|
for feed in feeds:
|
|
status = feed.health_status(now=now)
|
|
if status == "healthy":
|
|
healthy += 1
|
|
elif status == "warning":
|
|
warning += 1
|
|
elif status == "unhealthy":
|
|
unhealthy += 1
|
|
|
|
# 今日抓取
|
|
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
today_fetches = db.query(FetchLog).filter(FetchLog.created_at >= today).count()
|
|
today_success = db.query(FetchLog).filter(
|
|
FetchLog.created_at >= today, FetchLog.status == "success"
|
|
).count()
|
|
|
|
return {
|
|
"total_feeds": total_feeds,
|
|
"active_feeds": active_feeds,
|
|
"total_articles": total_articles_count,
|
|
"healthy_feeds": healthy,
|
|
"warning_feeds": warning,
|
|
"unhealthy_feeds": unhealthy,
|
|
"today_fetches": today_fetches,
|
|
"today_success": today_success,
|
|
"today_success_rate": round(today_success / today_fetches * 100, 1) if today_fetches > 0 else 0,
|
|
}
|