适用范围
本文对应 LiTwin 今天新增的项目与场景工程持久化功能,技术栈为 Java 17、Spring Boot 3、Spring JDBC、PostgreSQL 和 Flyway。
本次实现解决四个问题:
- 项目和当前场景在服务重启后仍然保留;
- 场景保存有 revision 和并发冲突保护;
- 历史场景可以查询;
- 回滚通过生成新版本完成,历史快照不被修改。
1. 数据模型
本次新增 Flyway 迁移:
text
V6__project_scene_tables.sql
主要包含三类数据:
| 数据 | 作用 |
|---|---|
project |
项目名称、描述、创建和更新时间 |
scene_current |
每个项目的当前场景、revision、contentHash 和 JSONB 内容 |
scene_version |
每次有效变更后的完整场景快照 |
审计继续复用已有的 audit_log,记录项目、revision、变更类型、原因和 contentHash,不记录完整场景 JSON。
2. API 接口
统一前缀为 /api/v1。
项目接口
text
POST /projects
GET /projects?page=0&pageSize=20
GET /projects/{projectId}
创建项目时,服务端同时创建一个空场景,初始 revision=1,并写入 CREATE 版本快照。
当前场景接口
text
GET /projects/{projectId}/scene
PUT /projects/{projectId}/scene
保存请求至少包含:
json
{
"expectedRevision": 1,
"reason": "保存场景布局",
"content": {}
}
content 必须符合 contracts/scene.schema.json,expectedRevision 必须和当前版本一致。
历史和回滚接口
text
GET /projects/{projectId}/scene/versions
GET /projects/{projectId}/scene/versions/{revision}
POST /projects/{projectId}/scene/rollback
历史列表只返回摘要,完整场景内容通过具体 revision 查询。回滚请求需要同时携带目标版本和当前版本:
json
{
"targetRevision": 1,
"expectedRevision": 3,
"reason": "撤回错误的层级调整"
}
3. 版本语义
版本规则不是简单的"每次请求加一"。
创建
创建项目时生成空场景:
text
revision 1 / changeType CREATE
有效保存
当前版本为 1,保存一份新内容后:
text
revision 2 / changeType UPDATE
保存时会记录变更后的完整快照。
相同内容保存
服务端会计算规范化 JSON 的 SHA-256:
- 对象键按字典序处理;
- 空白不会影响 hash;
- 数组顺序会影响 hash;
- 数值按稳定格式处理。
如果新的 contentHash 和当前版本一致,则认为是幂等 no-op,不增加 revision,也不写新的版本和变更审计。
回滚
回滚不修改旧版本,而是复制目标历史快照生成新版本:
text
revision 1 → revision 2 → revision 3
↘ revision 4 / ROLLBACK
历史表只允许追加,旧版本不会被 UPDATE 或 DELETE。
4. 并发控制
保存和回滚都在事务中执行:
- 对当前场景行加
FOR UPDATE; - 比较请求中的
expectedRevision; - 版本一致时执行条件更新;
- 写入历史快照和审计;
- 任意一步失败,整个事务回滚。
版本不一致返回:
text
409 SCENE_REVISION_CONFLICT
这样可以避免两个页面同时编辑时,后保存的请求直接覆盖前一个人的结果。
5. 场景内容校验
后端使用支持 JSON Schema 2020-12 的校验器,对场景内容执行完整校验。SceneValidator 还补了跨字段语义检查:
content.project.id必须等于路径中的projectId;assetId、nodeId、bindingId、eventId、viewId、boardId必须唯一;- 节点的
assetId和children引用必须存在; - 禁止自引用、循环引用和多父节点;
- 绑定和事件动作引用的节点必须存在;
- 场景大小上限为 5 MiB;
- 自由 JSON 最大嵌套深度为 16;
- 对象键不允许出现
password、privateKey、secret、token、credential等字段。
前端的 validateSceneProject 也补了数量上限、重复 ID、引用关系和环检测,但它只是快速反馈,后端校验才是实际安全边界。

6. 代码入口
本次主要实现文件:
text
backend/api-service/src/main/java/com/litwin/api/controller/ProjectController.java
backend/api-service/src/main/java/com/litwin/api/service/ProjectService.java
backend/api-service/src/main/java/com/litwin/api/service/ProjectStore.java
backend/api-service/src/main/java/com/litwin/api/service/SceneValidator.java
backend/api-service/src/main/resources/db/migration/V6__project_scene_tables.sql
frontend/packages/scene-schema/src/validate.ts
contracts/openapi.yaml
contracts/scene.schema.json
项目文档里,docs/tasks/003-项目与场景工程持久化及版本管理.md 是任务边界和验收标准,docs/20-项目与场景工程持久化实现设计.md 是这次实现的设计记录。第一次接手项目时,先看这两份,比直接翻代码省事。
7. 验收记录
当前实现设计文档记录的结果是:
- Java 测试 31 项通过,0 失败,0 错误;
mvn -q clean package -DskipTests全模块通过;pnpm typecheck和pnpm build通过;- 前端 Node 冒烟检查 11/11。
这些是仓库交接文档中的验收记录,不把它扩大成生产环境已经完成部署。Testcontainers 集成测试仍然依赖可用 Docker 环境,生产 PLC、三维编辑器和发布运行时也不在这次任务范围内。
8. 开发方式
这次仍然使用 AI 大模型辅助实现,但规则没有变:先读任务单和契约,再写代码;不擅自扩大范围;实现后必须有测试和审核记录。模型负责提高实现速度,版本语义、数据边界和安全规则不能交给模型临时决定。