TeamMapper 0.4.0 API 使用详解
TeamMapper 0.4.0 REST API 与 Yjs WebSocket 协作协议逐条实测:建图/读写凭据/删除/复制/图片上传/双客户端同步脚本与错误码实录。
TeamMapper 0.4.0 API 使用详解
目标:逐条实测 TeamMapper 0.4.0 的 REST API(设置/建图/读图/删图/复制/图片上传)与 Yjs WebSocket 协作协议(连接握手、消息格式、读写权限),给出可直接复用的 curl 与客户端脚本。所有请求与响应均为 2026-11-02 在 Ubuntu 26.04 部署环境实测采集,无转述。
一、环境信息
- 被测服务:TeamMapper 0.4.0(
ghcr.io/b310-digital/teammapper:latest,digestsha256:c565ceb598bd...,release v0.4.0-1),部署见本专栏《TeamMapper 0.4.0 Docker Compose 部署指南》 - 端点:
http://127.0.0.1:8144 - 客户端工具:curl 8.x、python3(解析 JSON)、node v24(Yjs 协议脚本,依赖
yjs y-protocols lib0 ws) - 测试图:
8f6b81e3-5d41-4b76-b811-6682b7dc6e07(adminIdd8558598-4bc3-4d87-b1d8-3df7c6da8d92,modificationSecretc6237b28-48d5-4a20-8dbb-082c17137d19,下文以$MAP/$ADMIN/$SEC代指,值即本行实值)
二、前置条件
- 服务健康、设置接口可达(也是首次调用验证):
curl -s http://127.0.0.1:8144/api/settings | python3 -m json.tool | head -8
{
"systemSettings": {
"info": {
"name": "TeamMapper",
"version": "0.1.9",
"aiModel": null
},
- 后端路由表以启动日志为准(
docker logs实测节选,见《TeamMapper 0.4.0 Docker Compose 部署指南》 4.4):REST 面为/api/maps、/api/maps/:id/images、/api/settings;WS 面为/yjs。
三、API 总览
| 域 | 方法与路径 | 用途 | 鉴权 |
|---|---|---|---|
| 设置 | GET /api/settings | 前端初始化配置 | 无 |
| 导图 | POST /api/maps | 建图 | 无(返回管理/修改凭据) |
| 导图 | GET /api/maps/:id | 读图(JSON 导出源) | 头 x-map-modification-secret 决定 writable |
| 导图 | GET /api/maps | 「我的导图」列表 | person_id 签名 cookie(JWT_SECRET) |
| 导图 | DELETE /api/maps/:id | 删图 | body adminId 精确匹配 |
| 导图 | POST /api/maps/:id/duplicate | 匿名复制 | 无(IP 频控 5 次/分) |
| 图片 | POST /api/maps/:id/images | 上传图片 | 修改凭据 |
| 图片 | GET /api/maps/:id/images/:imageId | 取图 | — |
| 协作 | WS /yjs?mapId=<uuid> | Yjs 实时同步 | 子协议携带修改凭据 |
| AI | POST /api/mermaid/create | AI 生成导图 | 仅 AI_ENABLED=true 时挂载(实测关闭时 404) |
认证模型(无账号体系的关键设计):每张图创建时服务端生成两把凭据随响应返回——adminId 仅用于删除;modificationSecret 用于编辑(REST 头或 WS 子协议)。凭据即能力,泄露即可改/删图,分享链接按此区分「可编辑/只读」。
四、认证与首次调用
建图(首次调用,同时拿凭据):
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"rootNode":{"name":"E2E 中心主题","colors":{},"font":{},"image":{}}}' \
http://127.0.0.1:8144/api/maps
实测响应(节选,data 内为首节点完整对象):
{
"map": {
"uuid": "8f6b81e3-5d41-4b76-b811-6682b7dc6e07",
"deleteAfterDays": 30,
"deletedAt": "2026-12-02T06:46:47.788Z",
"createdAt": "2026-11-02T06:46:47.777Z"
},
"adminId": "d8558598-4bc3-4d87-b1d8-3df7c6da8d92",
"modificationSecret": "c6237b28-48d5-4a20-8dbb-082c17137d19"
}
同一请求缺 rootNode 时的 400 回显(valibot 严格校验,逐字段报错):
{
"message": [
{ "kind": "schema", "type": "object", "expected": "\"rootNode\"", "path": [...] }
],
"error": "Bad Request",
"statusCode": 400
}
⚠️ 注意:
rootNode除name外必须带colors/font/image三个对象键(空对象{}即可),否则同样 400。这是 schemaNodeBasicsSchema的必填项,实测踩坑后从源码packages/shared/src/schemas/index.ts确认。
五、各 API 域详解
5.1 读图与 writable 判定(GET /api/maps/:id)
不带凭据(只读视角):
curl -s http://127.0.0.1:8144/api/maps/$MAP | python3 -c "import sys,json;d=json.load(sys.stdin);print('writable:',d['writable'])"
writable: False
带修改凭据(编辑视角,前端「可编辑分享链接」即透传此头):
curl -s -H "x-map-modification-secret: $SEC" http://127.0.0.1:8144/api/maps/$MAP \
| python3 -c "import sys,json;d=json.load(sys.stdin);print('writable(with secret):',d['writable'])"
writable(with secret): True
响应体即完整 JSON 导出(data 节点数组 + arrows + 元数据),节点字段:id/name/parent/isRoot/k/coordinates/colors/font/image/link/protected。带正确凭据的 GET 还会重置 30 天保留计时(源码:仅 writable 时 updateLastAccessed,防止匿名访问给图续命)。
错误码:不存在/非法 UUID → 404(实测已删图返回 404)。
5.2 「我的导图」列表(GET /api/maps)
依赖 person_id 签名 cookie(JWT_SECRET 签发,浏览器首次建图时由前端持有)。无 cookie 实测:
curl -s http://127.0.0.1:8144/api/maps
[]
脚本化场景没有浏览器 cookie,此端点基本用不上;按 uuid 直接 GET 是程序化读取的正路。
5.3 删图(DELETE /api/maps/:id)
正确 adminId(另建临时图 b9520609-6b1b-46dd-b430-429fafef2f8e 实测):
curl -s -X DELETE -H 'Content-Type: application/json' \
-d '{"adminId":"<该图的adminId>"}' http://127.0.0.1:8144/api/maps/$TMP_MAP
DELETE(正确adminId) -> 200
GET(已删图) -> 404
错误 adminId 的实测行为——返回 200 但不删除(controller 源码 if (mmpMap && mmpMap.adminId === body.adminId) 才执行删除,不匹配静默 no-op):
DELETE(错误adminId) -> 200
GET(主图仍在) -> 200
⚠️ 注意:不能以 HTTP 状态码判断删除是否成功,删完必须 GET 复核 404。删除会同时关闭该图全部 WS 连接并销毁内存文档。
5.4 复制导图(POST /api/maps/:id/duplicate)
curl -s -X POST -o /dev/null -w 'duplicate -> %{http_code}\n' \
http://127.0.0.1:8144/api/maps/$MAP/duplicate
duplicate 端点 -> 201
返回新图完整对象(新 uuid/adminId/secret)。匿名可用,受 IP 频控(默认 5 次/60s,超限 429)。
5.5 图片上传(POST /api/maps/:id/images)
multipart 上传,返回节点内联引用(v0.4.0 起图片独立存表,节点存 image:<uuid> 引用):
curl -s -X POST -H "x-map-modification-secret: $SEC" \
-F "file=@tiny.png" http://127.0.0.1:8144/api/maps/$MAP/images
{"reference":"image:0fa40eb3-3574-4699-9f2d-3c81b0765cec"}
不带凭据实测 403:{"message":"Forbidden","statusCode":403}。限制:单张 ≤150000 字节、单图总量 ≤2000000 字节、每分钟 ≤30 次(见《TeamMapper 0.4.0 配置参数详解》 5.5)。
5.6 AI 生图端点(POST /api/mermaid/create)
AI_ENABLED=false(默认)时路由不挂载,实测:
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"mindmapDescription":"测试主题","language":"en","levels":2,"childrenPerNode":2}' \
-w '\nHTTP %{http_code}\n' http://127.0.0.1:8144/api/mermaid/create
{"message":"Cannot POST /api/mermaid/create","error":"Not Found","statusCode":404}
HTTP 404
开启 AI_ENABLED=true 并配好 AI_LLM_* 后,该端点按描述生成 mermaid mindmap 文本,前端解析成图。
5.7 Yjs WebSocket 协作协议(WS /yjs)
握手要素(源码 yjs-gateway.service.ts/yjsProtocol.ts 核对 + 实测连通):
- URL:
ws://<host>:8144/yjs?mapId=<uuid>(也接受路径形态/yjs/<uuid>) Sec-WebSocket-Protocol必须含teammapper.v2,否则升级被拒 HTTP 426(版本护栏,防旧前端写坏数据)- 可编辑连接再携带
teammapper.secret.<modificationSecret>;握手响应只回teammapper.v2,不回显 secret - 消息帧:二进制,首字节 var-uint 消息类型
0=Sync、1=Awareness;Sync 内再分0=SyncStep1/1=SyncStep2/2=Update - 连接建立后服务端主动推送 SyncStep1 + SyncStep2(全量文档状态)+ 现有 Awareness,而非等客户端 SyncStep1(源码注释:防异步建连期间丢消息)
- 只读连接(无 secret)的 SyncStep2/Update 被服务端丢弃(
processReadOnlySyncMessage),SyncStep1 仍响应 - 关闭码:4000 缺参/4001 图已删/4004 图不存在/4500 内部错/1013 稍后重试/1008 超消息限额;心跳 ping 30s
双客户端实测(node 脚本,A 带 secret 写入、B 只读接收):
node collab.mjs $MAP $SEC
[A] 初始节点: ["E2E 中心主题"]
[A] 已发送新增节点 update, 节点数: 2
[B] 收到广播后节点: ["协作新增节点-来自客户端A","E2E 中心主题"]
[B] 节点数: 2
[A] B写入后A侧节点数(应不变): 2
RESULT: COLLAB_OK
脚本核心(完整可跑,依赖 npm i yjs y-protocols lib0 ws):
import WebSocket from 'ws'
import * as Y from 'yjs'
import * as syncProtocol from 'y-protocols/sync'
import * as encoding from 'lib0/encoding'
import * as decoding from 'lib0/decoding'
const doc = new Y.Doc()
const ws = new WebSocket(`ws://127.0.0.1:8144/yjs?mapId=${MAP}`,
['teammapper.v2', `teammapper.secret.${SEC}`])
ws.on('message', (data) => {
const d = decoding.createDecoder(new Uint8Array(data))
if (decoding.readVarUint(d) === 0) { // Sync
const t = decoding.readVarUint(d)
if (t === 1) syncProtocol.readSyncStep2(d, doc, null) // 全量状态
else if (t === 2) Y.applyUpdate(doc, decoding.readVarUint8Array(d)) // 增量
}
})
// 写入:改 doc.getMap('nodes') 后把 Y.encodeStateAsUpdate(doc) 包成 Update 帧发出
写入的节点必须是 Y.Map 且 id 字段与 map key 一致,否则服务端校验拒绝并重置该图同步(yjsValidation.ts 实测逻辑)。
六、分页/过滤/排序通用约定
TeamMapper REST 面没有分页/过滤/排序参数:GET /api/maps/:id 按 uuid 精确取图,GET /api/maps 返回该访客全部图的数组(无参数)。批量导出需自行遍历已知 uuid 列表;服务端「全部图」清单只能查库(typeorm query "select * from mmp_map",见《TeamMapper 0.4.0 Docker Compose 部署指南》第九章运维命令)。
七、常见问题
Q1:WS 连接被 HTTP 426 拒绝?
Sec-WebSocket-Protocol 未含 teammapper.v2。自写客户端时按 5.7 设置子协议数组。
Q2:建图 400 但看不出缺什么?
响应 message 数组逐项给出缺失键与路径(见第四章回显),按 expected 字段补即可;最小合法体是 {"rootNode":{"name":"x","colors":{},"font":{},"image":{}}}。
Q3:DELETE 返回 200 但图还在? adminId 不匹配时是静默 no-op(5.3 实测),核对凭据后重试,并以 GET 404 为准判断删除成功。
Q4:程序化改图内容走 REST 还是 WS? REST 无写端点(POST 只建空图),内容修改必须走 Yjs WS(模拟客户端发 Update),或「GET 导出 JSON → 改 → 重新 POST 建图」的换图策略。
Q5:图片上传 413/429?
分别对应超过 UPLOAD_IMAGE_MAX_SIZE_BYTES(150000)与频控 30 次/分,按《TeamMapper 0.4.0 配置参数详解》第五章调大或降频。
八、参考链接
- 路由与协议源码:
teammapper-backend/src/map/controllers/(maps.controller.ts / yjs-gateway.service.ts / yjsProtocol.ts,main 分支 2026-11-02) - 线协议 schema:
packages/shared/src/schemas/index.ts - 部署与凭据获取:见本专栏《TeamMapper 0.4.0 Docker Compose 部署指南》
- 同类自托管系统的 API 篇写法可对照 《Papermerge 3.5.3 API 使用详解》