
老师在题目编辑页补了一条辅助线,保存后关闭页面。第二天重新打开,辅助线还在,标签位置也没有变化。已经发给学生的试卷则继续显示审核过的原图。
要同时满足这两个要求,业务系统需要保存完整工程,并让草稿、发布版本和导出图片各自指向明确的记录。画板负责编辑与读写,后端负责持久化、冲突检测和版本保留。
本文以大角几何内嵌编辑器为例,给出一套可以接入题库或教研产品的保存方案。

保存对象应是完整工程
几何工程包含构造定义,也包含变量当前值、样式、文字和相机。只保存对象定义,可能丢失拖动后的位置或当前观察角度;只保存 PNG,则无法恢复可编辑状态。
从编辑器读取完整内容:
javascript
const content = await editor.document.getContent();
加载已保存的工程:
javascript
await editor.document.loadContent(content);
工程格式与保留字段见工程文件数据协议。保存时保留完整内容,不要在业务后端凭经验筛掉"暂时没用"的字段。
PNG 和缩略图可以作为工程版本的衍生产物保存,方便列表预览。用户进入编辑页时读取源工程,进入已发布题目时读取发布记录指定的版本。
数据模型区分当前草稿与历史快照
可以从三类记录开始设计。下表是业务数据模型示例,不是大角几何接口字段:
| 记录 | 关键字段 | 负责什么 |
|---|---|---|
| 工程主记录 | documentId、headVersion、publishedVersion |
关联当前草稿与已发布版本 |
| 版本快照 | documentId、version、content、author、createdAt、requestId |
保存不可变工程及变更来源 |
| 导出记录 | documentId、version、renderProfile、imageKey |
把图片绑定到确切工程版本 |
首个实现可以直接把完整工程作为 JSON 存入数据库;内容较大时,也可以将快照保存到对象存储,数据库记录存储位置与校验值。两种方式都要确保只有完成存储的版本才能成为当前版本。
自动保存频繁的产品,可以将短期草稿检查点与正式版本分层管理。保留周期按产品需求制定;用户明确保存、审核和发布的版本应有清晰的追踪记录。

当前草稿、已发布版本与各自图片分别关联(AI 生成业务系统设计示意)。
用变更事件标记未保存状态
编辑器提供 contentChange 事件。它适合触发防抖保存,并让宿主显示"有未保存修改"。正式保存前再读取一次 getContent(),可以取得当前完整内容。
防抖只减少请求数量,还需要串行化写入。否则,前一次网络请求较慢,可能在新请求之后完成,造成保存顺序与用户操作顺序不一致。
下面给出保存控制器的核心示例。假设 editor 已初始化,record.version 来自业务后端;宿主页面提供 save-state 状态元素。后端按下一节实现版本检查和请求去重。
javascript
const state = document.getElementById('save-state');
let baseVersion = record.version;
let changeSeq = 0;
let savedSeq = 0;
let timer;
let queue = Promise.resolve();
let blocked = false;
let uncertain = false;
function show(text) { state.textContent = text; }
async function writeSnapshot(operation) {
for (let attempt = 0; attempt < 2; attempt++) {
try {
const response = await fetch(`/api/geometry-docs/${record.id}/versions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(operation),
signal: AbortSignal.timeout(15_000),
});
if (response.status === 409) {
blocked = true;
throw new Error('版本冲突,请先比较本地内容与服务器版本');
}
if (!response.ok) throw new Error('保存请求未完成');
const result = await response.json();
if (!Number.isInteger(result.version)) throw new Error('缺少确认版本');
return result;
} catch (error) {
if (blocked) throw error;
if (attempt === 1) {
uncertain = true;
throw new Error(`保存结果待确认,请查询请求 ${operation.requestId}`);
}
// 复用同一 requestId 和快照,避免响应丢失造成重复版本。
}
}
}
function saveLatest() {
const task = queue.then(async () => {
if (blocked || uncertain) throw new Error('请先处理冲突或确认上次保存结果');
const seq = changeSeq;
const content = await editor.document.getContent();
const result = await writeSnapshot({
requestId: crypto.randomUUID(), baseVersion, content,
});
baseVersion = result.version;
savedSeq = Math.max(savedSeq, seq);
show(savedSeq === changeSeq ? '已保存' : '仍有修改待保存');
return result;
});
queue = task.catch(() => {});
return task;
}
const offChange = editor.on('contentChange', () => {
changeSeq += 1;
show('有未保存修改');
clearTimeout(timer);
timer = setTimeout(() => {
saveLatest().catch(error => show(error.message));
}, 1000);
});
一秒防抖、十五秒超时和两次尝试都是示例参数。uncertain 表示网络中断后无法确定保存是否已提交,后端需要提供按 requestId 查询结果的能力。确认成功后更新版本并解除状态;确认未提交后,才允许继续保存。不要在结果不明时直接创建另一份相同请求。
同样,版本冲突需要先展示差异或保留本地副本,不能在自动保存循环里反复覆盖。这个控制器处理正常编辑的写入顺序,冲突解决界面由宿主实现。

宿主界面区分未保存、已保存、结果待确认和版本冲突(AI 生成交互设计示意)。
手动保存也走同一个写入队列
大角几何编辑器的 save 事件分为请求与成功两个阶段。监听器应在请求阶段等待后端完成,成功后返回确认:
javascript
const offSave = editor.on('save', async event => {
if (event.stage !== 'request') return;
clearTimeout(timer);
try {
await saveLatest();
return { status: 'success' };
} catch (error) {
show(error.message);
return { status: 'error', message: error.message };
}
});
自动保存与手动保存复用同一个队列和版本状态,可避免两条保存入口并行写入。保存期间出现新编辑时,宿主应继续显示存在未保存修改;如果需要按钮的完成状态严格对应点击时的内容,可短暂阻止编辑并等待保存完成。
监听和返回格式见编辑模式文档。页面退出时取消订阅并清理定时器;有未保存内容时提示用户处理,不能依赖页面关闭请求一定送达。由代码发起的工程替换或 AI 结果加载,也应在宿主流程中显式安排保存。
后端用事务阻止旧版本覆盖
收到 { requestId, baseVersion, content } 后,后端先验证用户对该工程的写入权限,再在事务中处理以下步骤:
- 按工程和
requestId查找已完成请求;相同请求返回原结果,并核对内容摘要一致。 - 锁定或以条件更新方式检查当前版本是否等于
baseVersion。 - 不相等则返回 409;相等则插入新快照,并把主记录推进到新版本。
- 事务提交后返回已确认的版本号。
请求去重必须先于旧版本判定。否则第一次写入成功但响应丢失后,重试会被误判为版本冲突。数据库还应对工程与请求 ID 设置唯一约束,覆盖重复请求并发到达的情况。
如果快照存放在对象存储,可以先写入不可变对象,再提交数据库记录。数据库提交失败留下的未引用对象由后台清理;不能在快照尚未可读时提前更新当前版本。
两个标签页同时编辑时,后写入的一方应看到冲突提示,并可以保存为副本或人工比较。几何对象有依赖关系,第一版产品可以先提供安全的人工处理流程,再考虑自动合并。
发布版本与版本恢复分开处理
审核通过后,将 publishedVersion 指向确认版本。教师之后继续编辑,只推进草稿,已经发布的试卷继续引用原有快照。
恢复历史版本也可以采用"创建新版本"的方式。例如当前为第 8 版,用户希望恢复第 5 版:读取第 5 版内容,以当前第 8 版为基线提交,创建第 9 版,并记录恢复来源。这样既恢复了内容,也保留第 6 至第 8 版的历史。

恢复历史内容时创建新版本,并保留中间版本记录(AI 生成方案示意)。
编辑器的撤销、重做用于当前编辑过程;服务端版本用于跨会话追踪。两者应在界面和存储设计上分别处理。图片同样应关联版本,恢复工程后按需要重新渲染,避免继续展示旧缩略图。
验证重新打开与故障恢复
验收先检查"修改---保存---关闭---重开",再补三类异常:保存后响应丢失、两个页面同时修改、保存过程中继续编辑。每类异常都要检查服务器最终版本和页面状态是否一致。
最后从一个历史版本恢复内容,确认它产生新的记录,并检查已发布题目是否仍读取原版本。至此,几何工程就能作为业务资产持续修订,而每张正式出图也有可追踪的来源。