Files
ttstd-infinite-canvas/docs/content/docs/backend/canvas-operations-sync.mdx

70 lines
4.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 画布 operations 同步协议
description: 画布对象级写入、并发校验、幂等重放与删除边界
---
# 画布 operations 同步协议
## 请求合同
画布写入使用 `PATCH /api/canvas-projects/:id/operations`。请求体为:
```json
{
"mutationId": "客户端生成的稳定请求 ID",
"baseRevision": 12,
"operations": [
{
"type": "patch_node",
"id": "node-1",
"patch": { "metadata.prompt": "新的提示词" },
"before": { "metadata.prompt": "旧的提示词" }
}
]
}
```
`mutationId` 是重试身份,不是版本号。请求超时、响应丢失、断网、刷新或后台自动重试时,必须使用相同的 `mutationId`、`baseRevision` 和原始 `operations`。修改原始 payload 后复用 ID 会返回 `canvas_mutation_id_reused`。
`baseRevision` 是生成请求时的已确认版本,用于记录和诊断。真正的并发校验在操作本身:字段补丁使用 `before`,对象更新或删除使用 `expected`,顶层字段使用 `beforeValue`。不同字段可以合并;同一字段或同一对象的基线不匹配时返回冲突。可选顶层字段不存在时,客户端必须在 JSON 中将 `value` 或 `beforeValue` 编码为 `null`,不能让 `undefined` 被序列化为缺失属性。服务端仅兼容早期客户端对这些可选字段省略属性的请求,并按 `null` 参与同样的基线校验。
## 操作类型
- `create_canvas`:只在画布不存在时创建,第一项必须是该操作。
- `create_node`:显式新增节点,已有 ID 不会被覆盖。
- `patch_node`:对已有节点按字段路径更新,节点缺失或字段基线不匹配都会失败。
- `remove_node`:按完整对象基线删除节点,并删除关联连接。
- `create_connection`:显式新增连接,两个端点必须存在。
- `update_connection`:按连接对象基线更新,连接缺失或端点缺失都会失败。
- `remove_connection`:按连接对象基线删除。
- `set_field`:更新白名单中的画布字段,并校验 `beforeValue`;字段不存在时用 JSON `null` 表示删除或缺失基线。
只有 create 操作可以创建实体。删除后的迟到编辑、媒体回写或 Agent 更新得到 `canvas_object_missing`,不会隐式重建节点、连接或画布。画布删除后的迟到请求同样不会重建画布;只有新的 `create_canvas` 才能显式重新创建。
## 事务与 receipt
服务端事务按以下顺序处理请求:锁定画布写入、创建或读取 receipt、核对请求摘要、读取当前画布、应用 operations、校验媒体引用、递增 revision、写入画布和完成 receipt。画布内容、revision、receipt 的结果 JSON 在同一事务提交,任一步失败都会回滚。
`canvas_mutation_receipts` 使用 `(user_id, canvas_id, mutation_id)` 唯一约束。相同 ID 和相同摘要返回之前保存的确定性结果,不再次应用操作;相同 ID 和不同摘要返回冲突。receipt 不替代当前画布,也不保存整批版本历史。
结构化容量校验仍在同一事务内使用事务 repository 读取实时用量,但只统计当前账号的素材/画布数量和 JSON 字节数。此路径使用 `UserStructuredStorageUsage`,不扫描任务正文、结果或 API 请求/响应日志;任务与日志配额继续使用原有完整统计。容量上限、创建数量限制、存储锁、CAS 和 receipt 原子提交不变,也不引入可能过期的配额缓存。
## 浏览器 journal 与刷新
浏览器在发送请求前把以下内容写入账号隔离的本地 journal:
- 已确认的画布基线;
- 原始 operations 请求及 mutationId;
- 生成请求时的本地投影;
- 创建时间。
响应成功后删除对应未确认请求,并把服务端返回的完整画布保存为新基线。响应丢失时保留 journal,刷新后使用原始请求重放;请求发出后产生的新编辑不会改写原请求,而是在确认后从新基线生成下一批 operations。服务端完整读取或 Agent 刷新只能更新已确认基线,不能覆盖仍未确认的本地请求之后的修改。
`viewport` 是本地视图状态,不进入 operations。媒体文件先完成资源登记,再提交引用画布的 operations,避免产生只有节点没有资源记录的远端状态。
## Agent 和媒体回写
普通编辑、Agent、媒体生成终态、预演台、创作提交、LibTV/TapNow 导入都必须经过同一 operations 服务。Agent 事件里的 `canvasPatch` 只是客户端通知:小变更携带有界对象增量;删除、重排、顶层字段变化或事件过大时使用 `requiresRefresh: true`,客户端重新读取后继续处理未确认 journal。
媒体终态只更新仍存在且任务绑定未变化的节点。目标节点已删除、任务绑定已改变或资源不可用时,任务记录保留,画布不创建幽灵节点。