feat: v3 增强 - FTS5全文搜索+双向链接/知识图谱+自动保存草稿+标签管理+实时预览编辑器+PWA

- FTS5中文分词搜索(需-tags=sqlite_fts5)
- [[wiki链接]]反向链接+SVG知识图谱
- 自动保存草稿(不触发版本历史)
- 标签重命名/合并/删除+使用统计
- 后台编辑实时预览+格式工具栏
- PWA manifest+service worker+移动端适配
- 冒烟测试扩至66例全通过
This commit is contained in:
Your Name
2026-08-11 12:38:41 +08:00
parent cbfaac3d4f
commit b93c6f1e16
14 changed files with 1720 additions and 16 deletions
+100 -2
View File
@@ -243,9 +243,107 @@ report("标签列表正常", st == 200)
st, d = req("GET", "/api/tree")
report("公开树正常", st == 200)
print("═══ 9. FTS5 全文搜索 ═══")
# 创建一篇含中文与英文的笔记用于搜索测试
st, d = req("POST", "/admin/api/notes", {
"title": "搜索引擎优化笔记", "content": "这是一篇关于数据库索引与搜索引擎的笔记 涉及中文分词 ASP.NET",
"category": "技术", "tags": '["全文","优化"]', "is_public": True
})
fts_id = d.get("data", {}).get("id")
report("创建搜索测试笔记", st == 200 and fts_id, f"(got {st})")
# 中文短词(1~2字)搜索
q_enc = urllib.parse.urlencode({"q": "数据库"})
st, d = req("GET", f"/api/notes/search?{q_enc}")
hits = [x.get("title") for x in (d.get("data") or [])]
report("中文短词搜索命中", st == 200 and "搜索引擎优化笔记" in hits, f"(got {hits})")
# 英文搜索
q_enc = urllib.parse.urlencode({"q": "ASP.NET"})
st, d = req("GET", f"/api/notes/search?{q_enc}")
hits = [x.get("title") for x in (d.get("data") or [])]
report("英文搜索命中", st == 200 and "搜索引擎优化笔记" in hits, f"(got {hits})")
# 无结果关键词
q_enc = urllib.parse.urlencode({"q": "不存在的关键词XYZ"})
st, d = req("GET", f"/api/notes/search?{q_enc}")
report("无匹配关键词返回空", st == 200 and d.get("total", 0) == 0, f"(got {d.get('total')})")
print("═══ 10. 双向链接 / 知识图谱 ═══")
# 创建两篇互相/单向链接的笔记
st, da = req("POST", "/admin/api/notes", {
"title": "链接源A", "content": f"参考 [[搜索引擎优化笔记]] 和 [[链接目标B]]",
"is_public": True
})
linkA_id = da.get("data", {}).get("id")
st, db_ = req("POST", "/admin/api/notes", {
"title": "链接目标B", "content": "内容B", "is_public": True
})
linkB_id = db_.get("data", {}).get("id")
report("创建链接笔记成功", st == 200 and linkA_id and linkB_id, f"(got {st})")
# 反向链接:谁链接到了搜索引擎优化笔记(应为 链接源A)
st, d = req("GET", f"/admin/api/notes/{fts_id}/backlinks")
backlinks = [x.get("title") for x in (d.get("data") or [])]
report("反向链接查询", st == 200 and "链接源A" in backlinks, f"(got {backlinks})")
# 知识图谱:应包含节点,且存在 edges
st, d = req("GET", "/admin/api/graph")
g = d.get("data") or {}
gnodes = g.get("nodes") or []
gedges = g.get("edges") or []
report("知识图谱返回节点", st == 200 and any(n.get("title") == "链接源A" for n in gnodes), f"(got {st})")
report("知识图谱含链接边", st == 200 and len(gedges) >= 1, f"(got {len(gedges)})")
print("═══ 11. 自动保存草稿 ═══")
st, d = req("POST", f"/admin/api/notes/{fts_id}/draft", {"content": "这是未保存的草稿"})
report("保存草稿成功", st == 200 and d.get("code") == 0, f"(got {st})")
st, d = req("GET", f"/admin/api/notes/{fts_id}")
report("读取到草稿字段", d.get("data", {}).get("draft_content") == "这是未保存的草稿", f"(got {d.get('data',{}).get('draft_content')})")
st, d = req("DELETE", f"/admin/api/notes/{fts_id}/draft")
report("清除草稿成功", st == 200 and d.get("code") == 0, f"(got {st})")
st, d = req("GET", f"/admin/api/notes/{fts_id}")
report("清除后草稿为空", d.get("data", {}).get("draft_content", "") == "", f"(got {d.get('data',{}).get('draft_content')})")
print("═══ 12. 标签管理 ═══")
# 标签使用统计应包含 test 链接笔记的标签
st, d = req("GET", "/admin/api/tags/usage")
usage = {u.get("name"): u.get("count") for u in (d.get("data") or [])}
report("标签使用统计接口", st == 200 and isinstance(d.get("data"), list), f"(got {st})")
# 重命名标签:给链接源A加个标签再重命名
st, d = req("PUT", f"/admin/api/notes/{linkA_id}", {"tags": '["临时标签","保留"]'})
report("为链接笔记设置标签", st == 200 and d.get("code") == 0, f"(got {st})")
st, d = req("POST", "/admin/api/tags/rename", {"old_name": "临时标签", "new_name": "已重命名"})
report("重命名标签", st == 200 and d.get("data", {}).get("changed", 0) >= 1, f"(got {d})")
st, d = req("GET", f"/admin/api/notes/{linkA_id}")
tag_str = d.get("data", {}).get("tags", "")
report("重命名后笔记标签同步", "已重命名" in tag_str and "临时标签" not in tag_str, f"(got {tag_str})")
# 合并标签
st, d = req("POST", "/admin/api/tags/merge", {"from": "保留", "to": "已重命名"})
report("合并标签", st == 200 and d.get("data", {}).get("changed", 0) >= 1, f"(got {d})")
st, d = req("GET", f"/admin/api/notes/{linkA_id}")
tag_str = d.get("data", {}).get("tags", "")
report("合并后标签去重", "已重命名" in tag_str and "保留" not in tag_str, f"(got {tag_str})")
# 删除标签
st, d = req("DELETE", "/admin/api/tags", {"name": "已重命名"})
report("删除标签", st == 200, f"(got {st})")
# ── 清理测试数据(彻底删除本脚本创建的所有笔记及其版本)──
print("\n═══ 9. 清理测试数据 ═══")
test_ids = [x for x in (note_id, protected_id, tmp_id) if x]
print("\n═══ 13. 清理测试数据 ═══")
test_ids = [x for x in (note_id, protected_id, tmp_id, fts_id, linkA_id, linkB_id) if x]
for tid in test_ids:
if tid:
# 先尝试恢复到非删除态再彻底删除(若在回收站)