3.7 KiB
3.7 KiB
zhiyi-memory 插件 ↔ zhiyid 后端 API 契约(2026-09-10 实测)
场景:Obsidian 织忆插件(
~/.obsidian/plugins/zhiyi-memory/,v1.1.0,只有编译产物 main.js 无源码) 问题:搜索 404、图谱结果异常庞大。逐个端点探测后定位 2 处失配。
1. 插件 HTTP 封装(main.js 里的 z())
async function z(a, n={}) {
let e = n.method ?? "GET";
let t = {"X-API-Key": ee(), "Content-Type": "application/json"};
let r = {method: e, headers: t};
n.body !== void 0 && (r.body = JSON.stringify(n.body));
let o = await fetch(`${J()}${a}`, r); // J() = 硬编码 base,实测 http://localhost:7821
if (!o.ok) throw new Error(`API ${a} failed: ${o.status}`);
let s = await o.text();
return s ? JSON.parse(s) : {};
}
封装本身没有问题(body 序列化、错误抛出都正确)。问题在调用点传的路径与参数名。
2. 失配对照表
| 插件调用 | 服务端实际 | 结果 |
|---|---|---|
POST /api/v1/search/recall {query,namespace,top_k} |
POST /api/v1/recall |
❌ 404 endpoint not found |
GET /api/v1/graph/navigate?entity=X&depth=1 |
POST /api/v1/graph/navigate {entity, max_hops} |
⚠️ 200,但 depth 被忽略 → 返回全图 |
GET /api/v1/memories?... |
同 | ✅ |
GET /api/v1/stats |
同 | ✅ |
GET /api/v1/graph/pagerank |
同 | ✅ |
GET /api/v1/graph/export?namespace=&limit= |
同 | ✅ |
GET /api/v1/distill/status / quota |
同 | ✅ |
修复后:
// 修 1
z("/api/v1/recall", {method:"POST", body:{query:a, namespace:n||v(), top_k:20}})
// 修 2
z("/api/v1/graph/navigate", {method:"POST", body:{entity:a, max_hops:1}})
3. 参数名忽略的验证方法(本次抓到 bug 的关键)
对同一端点传不同参数值,比较返回是否变化:
POST /api/v1/graph/navigate {"entity":"小唯"} → count=3097 (默认全图)
POST /api/v1/graph/navigate {"entity":"小唯","depth":1} → count=3097 ⚠️ 无变化 = depth 被忽略
POST /api/v1/graph/navigate {"entity":"小唯","max_hops":1} → count=277 ✅ 生效
POST /api/v1/graph/navigate {"entity":"小唯","hops":1} → count=3097 ⚠️ 无变化
通则:只测「成功/失败」抓不到这类 bug,必须测「参数生效性」。
4. 改 minified main.js 的安全流程
import shutil, subprocess, os
p = '/home/muc/mc/.obsidian/plugins/zhiyi-memory/main.js'
c = orig = open(p, encoding='utf-8').read()
n1 = orig.count('\"/api/v1/search/recall\"')
c = c.replace('\"/api/v1/search/recall\"', '\"/api/v1/recall\"')
old_nav = 'z(`/api/v1/graph/navigate?entity=${encodeURIComponent(a)}&depth=1`)'
new_nav = 'z("/api/v1/graph/navigate",{method:"POST",body:{entity:a,max_hops:1}})'
n2 = orig.count(old_nav)
c = c.replace(old_nav, new_nav)
assert n1 or n2, '两处都没匹配 → 检查调用点原文(模板字符串引号是反引号)'
shutil.copy2(p, p + '.bak-api-fix')
open(p, 'w', encoding='utf-8').write(c)
r = subprocess.run(['node', '--check', p], capture_output=True, text=True)
print('OK' if r.returncode == 0 else r.stderr[:200])
⚠️ 常见坑:navigate 调用用的是模板字符串(反引号),里面嵌 ${encodeURIComponent(a)} —— 复制调用点原文时引号类型必须一致,否则 replace 静默不匹配。
5. 验证清单
□ grep 出插件所有 api/v1 路径,逐个 curl 探活
□ 每个端点传「不同参数值」验证参数生效性
□ 改完 node --check
□ 备份 .bak-<date>(.obsidian/ 不进 git)
□ 同步到异地副本(WebDAV / NAS)
□ 提醒用户重启 Obsidian 或重载插件才生效