TeamMapper 0.4.0 API 使用详解

TeamMapper 0.4.0 REST API 与 Yjs WebSocket 协作协议逐条实测:建图/读写凭据/删除/复制/图片上传/双客户端同步脚本与错误码实录。

TeamMapperTeamMapper实时协作2026/11/02

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,digest sha256: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(adminId d8558598-4bc3-4d87-b1d8-3df7c6da8d92,modificationSecret c6237b28-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
        },

三、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 实时同步子协议携带修改凭据
AIPOST /api/mermaid/createAI 生成导图仅 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。这是 schema NodeBasicsSchema 的必填项,实测踩坑后从源码 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 配置参数详解》第五章调大或降频。

八、参考链接