这个知识库的后端就是一个只读 API:查文章、看统计、读知识页。几千行代码,跑在一台 2 核机器上。这篇说一下结构和几个关键决策。
为什么只读
内容由后台脚本写入(同步、编译),API 只负责查询。好处:
- 不需要处理并发写入、事务、锁
- 可以用 SQLite 的只读模式打开,天然防误写
- 缓存策略简单,读多写少
只有「加新内容」这类操作走 POST,且必须过口令验证。
只读打开 SQLite
import sqlite3
def conn():
c = sqlite3.connect(f"file:{DB}?mode=ro", uri=True, timeout=5)
c.row_factory = sqlite3.Row
return c
mode=ro 是内核级保证,任何写操作直接报错。比在代码里小心不要写要可靠得多。
timeout=5 避免写入方持有锁时立即报 database is locked。
结构
app/
├── serve.py # FastAPI 入口,路由都在这里
├── ask.py # 检索逻辑
├── store.py # 数据库 schema 和工具函数
├── llm.py # 模型调用
└── webapp/index.html # 单页前端
路由按用途分:读(GET)、写(POST,带口令)、配置(GET/PUT)。写操作的入口只有几个,都要过校验。
一个路由的样子
from fastapi import FastAPI, HTTPException, Query
app = FastAPI()
@app.get("/api/articles")
def list_articles(q: str = "", page: int = Query(1, ge=1),
size: int = Query(20, le=100)):
with conn() as c:
if q:
rows = c.execute(
"SELECT id, title, url, source FROM article_fts f "
"JOIN articles a ON a.id = f.rowid WHERE article_fts MATCH ? "
"LIMIT ? OFFSET ?", (q, size, (page - 1) * size)).fetchall()
else:
rows = c.execute(
"SELECT id, title, url, source FROM articles "
"ORDER BY published_at DESC LIMIT ? OFFSET ?",
(size, (page - 1) * size)).fetchall()
return {"items": [dict(r) for r in rows]}
Query(1, ge=1) 自动做参数校验,le=100 防止有人一次要 10000 条把服务拖垮。这类边界必须在 API 层挡住,不能靠前端。
分页
LIMIT/OFFSET 在数据量大时越翻越慢。更好的做法是游标分页:
SELECT * FROM articles WHERE id < ? ORDER BY id DESC LIMIT 20;
前端传上一页最后一条的 id,不用算 offset。数据量上万之后差别明显。
静态页面
把 index.html 挂在根路径,前端用原生 JS 调 API,不引框架:
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
@app.get("/")
def index():
return FileResponse(os.path.join(BASE, "webapp/index.html"))
一个 HTML 文件搞定所有页面,没有构建步骤,改完刷新就生效。对内部工具来说,这比上 Vue/React 划算得多。
部署
ExecStart=/www/blog-ai/.venv/bin/python -m uvicorn serve:app --host 127.0.0.1 --port 8800
uvicorn 监听本地,Nginx 反代到公网,加 Basic Auth。这样应用不直接暴露,也不用在代码里处理 TLS。
几点经验
- 日志用 print 就够,systemd 会收进 journalctl,不用上日志框架
- 错误信息要脱敏,
HTTPException(500, str(exc)[:300])截断,避免把路径、SQL 泄出去 - 加个 /api/stats,返回文章数、同步时间,方便监控和健康检查
- 写接口要有白名单,只接受固定字段,不要
**kwargs直接传进 SQL
一句话
只读 API 的价值在「简单可靠」。不需要 ORM,不需要迁移框架,SQL 写得清楚,跑起来就不会出意外。
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/archives/705
共有 0 条评论