几何画板工程持久化与版本管理方案:变更监听、自动保存与版本恢复

本文提出面向教育题库场景的几何画板工程持久化方案,涵盖完整工程保存、草稿与历史快照分离、变更事件驱动的防抖自动保存、串行化写入队列、后端事务级版本控制(含冲突检测与请求去重)、发布版本与编辑草稿解耦、历史版本恢复机制(以新版本形式回滚)及多异常场景下的故障恢复验证。方案支持AI辅助生成、跨会话编辑与试卷资产溯源。

老师在题目编辑页补了一条辅助线,保存后关闭页面。第二天重新打开,辅助线还在,标签位置也没有变化。已经发给学生的试卷则继续显示审核过的原图。

要同时满足这两个要求,业务系统需要保存完整工程,并让草稿、发布版本和导出图片各自指向明确的记录。画板负责编辑与读写,后端负责持久化、冲突检测和版本保留。

本文以大角几何内嵌编辑器为例,给出一套可以接入题库或教研产品的保存方案。

保存对象应是完整工程

几何工程包含构造定义,也包含变量当前值、样式、文字和相机。只保存对象定义,可能丢失拖动后的位置或当前观察角度;只保存 PNG,则无法恢复可编辑状态。

从编辑器读取完整内容:

const content = await editor.document.getContent();

加载已保存的工程:

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 状态元素。后端按下一节实现版本检查和请求去重。

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 事件分为请求与成功两个阶段。监听器应在请求阶段等待后端完成,成功后返回确认:

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 } 后,后端先验证用户对该工程的写入权限,再在事务中处理以下步骤:

  1. 按工程和 requestId 查找已完成请求;相同请求返回原结果,并核对内容摘要一致。
  2. 锁定或以条件更新方式检查当前版本是否等于 baseVersion。
  3. 不相等则返回 409;相等则插入新快照,并把主记录推进到新版本。
  4. 事务提交后返回已确认的版本号。

请求去重必须先于旧版本判定。否则第一次写入成功但响应丢失后,重试会被误判为版本冲突。数据库还应对工程与请求 ID 设置唯一约束,覆盖重复请求并发到达的情况。

如果快照存放在对象存储,可以先写入不可变对象,再提交数据库记录。数据库提交失败留下的未引用对象由后台清理;不能在快照尚未可读时提前更新当前版本。

两个标签页同时编辑时,后写入的一方应看到冲突提示,并可以保存为副本或人工比较。几何对象有依赖关系,第一版产品可以先提供安全的人工处理流程,再考虑自动合并。

发布版本与版本恢复分开处理

审核通过后,将 publishedVersion 指向确认版本。教师之后继续编辑,只推进草稿,已经发布的试卷继续引用原有快照。

恢复历史版本也可以采用“创建新版本”的方式。例如当前为第 8 版,用户希望恢复第 5 版:读取第 5 版内容,以当前第 8 版为基线提交,创建第 9 版,并记录恢复来源。这样既恢复了内容,也保留第 6 至第 8 版的历史。

恢复历史内容时创建新版本,并保留中间版本记录(AI 生成方案示意)。

编辑器的撤销、重做用于当前编辑过程;服务端版本用于跨会话追踪。两者应在界面和存储设计上分别处理。图片同样应关联版本,恢复工程后按需要重新渲染,避免继续展示旧缩略图。

验证重新打开与故障恢复

验收先检查“修改—保存—关闭—重开”,再补三类异常:保存后响应丢失、两个页面同时修改、保存过程中继续编辑。每类异常都要检查服务器最终版本和页面状态是否一致。

最后从一个历史版本恢复内容,确认它产生新的记录,并检查已发布题目是否仍读取原版本。至此,几何工程就能作为业务资产持续修订,而每张正式出图也有可追踪的来源。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值