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 配置文件
87 lines
2.5 KiB
Python
87 lines
2.5 KiB
Python
"""SQLite FTS5 全文搜索封装"""
|
|
from sqlalchemy import text
|
|
from database import engine
|
|
|
|
|
|
def search_articles(query: str, limit: int = 50, offset: int = 0):
|
|
"""全文搜索文章
|
|
返回 [(article_id, title, content_snippet, rank), ...]
|
|
"""
|
|
if not query or not query.strip():
|
|
return [], 0
|
|
|
|
# 转义 FTS5 特殊字符(双引号、* 等)
|
|
# 简单策略:将用户查询视为一个整体短语,加引号包裹
|
|
query = query.replace('"', '""').strip()
|
|
if not query:
|
|
return [], 0
|
|
# 用双引号包裹,避免 FTS5 布尔操作符被误解析
|
|
query = f'"{query}"'
|
|
|
|
conn = engine.raw_connection()
|
|
cursor = conn.cursor()
|
|
|
|
try:
|
|
# 使用 FTS5 查询
|
|
sql = """
|
|
SELECT a.id, a.title, a.summary, a.link, a.published_at, a.created_at,
|
|
f.id as feed_id, f.title as feed_title, f.category,
|
|
rank
|
|
FROM articles_fts
|
|
JOIN articles a ON articles_fts.rowid = a.id
|
|
JOIN feeds f ON a.feed_id = f.id
|
|
WHERE articles_fts MATCH ?
|
|
ORDER BY rank
|
|
LIMIT ? OFFSET ?
|
|
"""
|
|
cursor.execute(sql, (query, limit, offset))
|
|
rows = cursor.fetchall()
|
|
|
|
# 获取总数
|
|
count_sql = """
|
|
SELECT COUNT(*) FROM articles_fts WHERE articles_fts MATCH ?
|
|
"""
|
|
cursor.execute(count_sql, (query,))
|
|
total = cursor.fetchone()[0]
|
|
|
|
results = []
|
|
for row in rows:
|
|
results.append({
|
|
"id": row[0],
|
|
"title": row[1],
|
|
"summary": row[2],
|
|
"link": row[3],
|
|
"published_at": row[4],
|
|
"created_at": row[5],
|
|
"feed_id": row[6],
|
|
"feed_title": row[7],
|
|
"category": row[8],
|
|
})
|
|
|
|
return results, total
|
|
except Exception as e:
|
|
# FTS5 查询失败时返回空结果
|
|
return [], 0
|
|
finally:
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
|
|
def rebuild_fts_index():
|
|
"""重建 FTS5 索引(数据不一致时使用)"""
|
|
conn = engine.raw_connection()
|
|
cursor = conn.cursor()
|
|
try:
|
|
cursor.execute("DELETE FROM articles_fts")
|
|
cursor.execute("""
|
|
INSERT INTO articles_fts(rowid, title, content)
|
|
SELECT id, title, content FROM articles
|
|
""")
|
|
conn.commit()
|
|
return True
|
|
except Exception:
|
|
return False
|
|
finally:
|
|
cursor.close()
|
|
conn.close()
|