117 lines
6.1 KiB
Markdown
117 lines
6.1 KiB
Markdown
# Canvas 数据持久化:交给 Core 的接口需求
|
||
|
||
## 已核对的现状
|
||
|
||
核对 Core main `e8ed5e4f9ffc526f4a42b001850ffc240420a1e0`:
|
||
|
||
- `internal/platform/materials.go` 的 material GET / PATCH 保存名称、描述、content,并以带引号的 `If-Match` 控制版本。
|
||
- `internal/platform/content.go` 的 `Content` 只有 schema_version/text/article/image/parts,解码器使用 `DisallowUnknownFields`;board 只允许空内容 `{ "schema_version": 1 }`。直接提交 nodes、edges 或样式会被拒绝,不能用现有接口冒充已存库。
|
||
- 未发现 Canvas 快照读写接口。当前前端节点、文本样式、图标、角度、尺寸和连线仍保存在本地草稿及导出的 JSON,尚未存入数据库。
|
||
|
||
## 必需接口
|
||
|
||
建议为 board 增加独立 Canvas 快照,使用独立的版本号,避免改名与编辑画布互相覆盖。
|
||
|
||
后端路径:`/projects/{project}/workspaces/{workspace}/materials/{board}/canvas`
|
||
|
||
前端同源代理路径:`/api/foundry/workspaces/{workspace}/materials/{board}/canvas`。Core 完成后前端需扩展当前代理白名单,并接入下述读写接口;本次没有向不存在的接口发送请求。
|
||
|
||
### GET
|
||
|
||
响应 `200`,`ETag: "7"`:
|
||
|
||
```json
|
||
{
|
||
"version": 7,
|
||
"document": {
|
||
"schema_version": 1,
|
||
"nodes": [
|
||
{
|
||
"id": "text-node-1",
|
||
"type": "material",
|
||
"position": { "x": 100, "y": 200 },
|
||
"width": 320,
|
||
"height": 220,
|
||
"data": {
|
||
"kind": "text",
|
||
"title": "灵感笔记",
|
||
"text": "正文",
|
||
"rotation": 30,
|
||
"fontSize": 24,
|
||
"textStyle": {
|
||
"color": "#37352f",
|
||
"background": "transparent",
|
||
"bold": true,
|
||
"italic": false,
|
||
"underline": false,
|
||
"align": "left",
|
||
"fontFamily": "sans",
|
||
"lineHeight": 1.5
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "icon-node-1",
|
||
"type": "icon",
|
||
"position": { "x": 500, "y": 200 },
|
||
"width": 80,
|
||
"height": 80,
|
||
"data": {
|
||
"kind": "icon",
|
||
"title": "火箭",
|
||
"text": "",
|
||
"icon": "Rocket",
|
||
"rotation": 0,
|
||
"textStyle": { "color": "#78523f" }
|
||
}
|
||
}
|
||
],
|
||
"edges": [],
|
||
"viewport": { "x": 0, "y": 0, "zoom": 1 }
|
||
}
|
||
}
|
||
```
|
||
|
||
没有快照的旧 board 应返回 version=1 的空图谱(nodes/edges 为空、viewport 为 0/0/1),可自动初始化。非 board 或不可访问的材料按已有资源错误约定返回。
|
||
|
||
### PATCH
|
||
|
||
请求 `If-Match: "7"`,body 为 `{ "document": { ...完整快照... } }`,原子替换整张图谱,成功响应 `200`,body `{ "version": 8 }`,`ETag: "8"`。
|
||
|
||
- 必须校验 project/workspace/board 归属及现有成员/材料授权;GET 允许 viewer,PATCH 只允许 admin/editor。
|
||
- 缺版本返回 428,版本冲突返回 412 / `version_conflict`,冲突不得覆盖数据库内容。
|
||
- 无效快照返回 400 / `invalid_canvas`,超限返回 413;拒绝时不能部分写入。
|
||
- 返回持久化版本,刷新、重新登录或另一台设备 GET 时必须能还原全部内容。
|
||
- Canvas 与材料名称/描述分别更新,不能在保存快照时覆盖名称,也不能在改名时清空快照。
|
||
|
||
## 数据字段与校验
|
||
|
||
以本仓库 `src/lib/canvas.ts` 的 `decodeCanvas` 和 `src/lib/canvas-elements.ts` 为当前前端契约:
|
||
|
||
| 数据 | 必须保留或校验的内容 |
|
||
| --- | --- |
|
||
| 节点类型 | text/image/script 使用 material;plain-text 使用 plainText;icon 使用 icon |
|
||
| 几何 | position.x/y 为有限数、绝对值不超过 1,000,000;width/height 成对、20–3000;rotation 为 [0,360) |
|
||
| 文本 | title 最长 300 字符,text 最长 100,000 字符;fontSize 8–160 |
|
||
| 样式 | color 为六位 hex;background 为六位 hex 或 transparent;bold/italic/underline 为布尔;align=left/center/right;fontFamily=sans/serif/mono;lineHeight=1–3 |
|
||
| 图标 | icon 为 Lucide PascalCase 标识,当前例如 Star/Rocket/Heart;颜色及尺寸由相同样式/几何字段确定 |
|
||
| 图片 | src 为 HTTPS 图片 URL 或当前前端支持的 PNG/JPEG/WebP/GIF data URL;单次本地选图最大 2 MB |
|
||
| 连线 | id/source/target/sourceHandle/targetHandle;四侧 handle=top/right/bottom/left;保留旧无名连接点兼容;引用必须存在,不允许连接纯文本或图标 |
|
||
| 视图 | viewport.x/y 为有限数,zoom=0.1–4 |
|
||
|
||
最多 500 个节点、2000 条连线;节点与连线 ID 各自唯一。selected/dragging/measured 等 UI 临时状态不入库。节点 style.width/height 是前端尺寸镜像,可不持久化,恢复时用 width/height 生成。连线按贝塞尔曲线渲染。
|
||
|
||
快照请求体建议支持至少 5 MiB(目前 Core 和前端代理都是 1 MiB,需要同步调整该路由的限制),否则一张 2 MB 图片转为 data URL 后就无法保存。限制应按 UTF-8 字节计算。若 Core 选择使用独立图片上传接口代替 data URL,请提供上传和持久 URL 返回契约,前端再替换图片保存方式。
|
||
|
||
## 图标是否需要专门的后端服务
|
||
|
||
本次图标由已有 npm 包 `lucide-react@1.69.0` 本地渲染,前端提供 45 个图标和搜索。添加、搜索与显示不需要图标下载或目录 API;数据库只需通过 Canvas 快照保存 icon 标识、颜色、位置、尺寸和旋转角度,不保存 SVG 源码。后续如支持多个图标库,再为节点增加图标库及版本标识。
|
||
|
||
## Core 交付验收
|
||
|
||
1. 写入含中文文本、全部 textStyle 字段、旋转尺寸、图标和四向连线的快照,重新 GET 完全还原。
|
||
2. 两个客户端持相同版本写入,只允许一个成功,另一个收到 412。
|
||
3. viewer、其他 workspace 成员及未登录用户不能越权写入;非法引用、无效样式、超限请求不能留下部分数据。
|
||
4. 改名不清空快照,保存快照不改变名称;旧 board 可读取空快照;包含一张 2 MB 图片的合法快照能够保存和还原。
|
||
5. 接口上线后前端接入:优先读取服务端快照、提供本地旧草稿迁移、串行自动保存与冲突处理,并验证跨设备恢复;在此之前不能将前端本地持久化称为数据库保存。
|