Files
rssKeeper/backend/models.py
T
congsh c59dd304f7 fix: 端口更换 & 代码审核修复
端口:
- 服务端口 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 配置文件
2026-06-11 14:31:29 +08:00

94 lines
3.5 KiB
Python

"""SQLAlchemy 数据模型"""
from datetime import datetime, timezone
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Feed(Base):
"""RSS 源"""
__tablename__ = "feeds"
id = Column(Integer, primary_key=True, index=True)
url = Column(String(2048), unique=True, nullable=False, index=True)
title = Column(String(512), default="")
description = Column(Text, default="")
category = Column(String(128), default="")
is_active = Column(Boolean, default=True, index=True)
fetch_interval_minutes = Column(Integer, default=60)
# 抓取统计
last_fetch_at = Column(DateTime, nullable=True)
last_fetch_status = Column(String(20), default="")
last_error = Column(Text, default="")
success_count = Column(Integer, default=0)
fail_count = Column(Integer, default=0)
article_count = Column(Integer, default=0)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
# 关联
articles = relationship("Article", back_populates="feed", cascade="all, delete-orphan")
fetch_logs = relationship("FetchLog", back_populates="feed", cascade="all, delete-orphan")
def health_status(self, now: datetime = None):
"""计算健康度
🟢 健康: 成功率 >= 90%, 最近7天有更新
🟡 警告: 成功率 50%-90%, 或超过3天未更新
🔴 异常: 成功率 < 50%, 或超过7天未更新
"""
total = self.success_count + self.fail_count
if total == 0:
return "unknown"
success_rate = self.success_count / total
if now is None:
now = datetime.now(timezone.utc)
days_since_last_fetch = None
if self.last_fetch_at:
days_since_last_fetch = (now - self.last_fetch_at).days
if success_rate >= 0.9 and (days_since_last_fetch is None or days_since_last_fetch <= 7):
return "healthy"
elif success_rate >= 0.5 and (days_since_last_fetch is None or days_since_last_fetch <= 7):
return "warning"
else:
return "unhealthy"
class Article(Base):
"""RSS 文章"""
__tablename__ = "articles"
id = Column(Integer, primary_key=True, index=True)
feed_id = Column(Integer, ForeignKey("feeds.id", ondelete="CASCADE"), nullable=False, index=True)
title = Column(String(1024), default="", index=True)
link = Column(String(2048), unique=True, nullable=False, index=True)
author = Column(String(256), default="")
published_at = Column(DateTime, nullable=True, index=True)
content = Column(Text, default="")
summary = Column(Text, default="")
is_read = Column(Boolean, default=False)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), index=True)
# 关联
feed = relationship("Feed", back_populates="articles")
class FetchLog(Base):
"""抓取日志"""
__tablename__ = "fetch_logs"
id = Column(Integer, primary_key=True, index=True)
feed_id = Column(Integer, ForeignKey("feeds.id", ondelete="CASCADE"), nullable=False, index=True)
status = Column(String(20), nullable=False) # success / fail
articles_fetched = Column(Integer, default=0)
error_message = Column(Text, default="")
response_time_ms = Column(Integer, nullable=True)
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), index=True)
# 关联
feed = relationship("Feed", back_populates="fetch_logs")