From 392f4544f457e4881f70281b785c260cf2ecd48c Mon Sep 17 00:00:00 2001 From: opendance Date: Thu, 17 Sep 2026 00:27:08 +0800 Subject: [PATCH] =?UTF-8?q?chore(contracts):=20=E5=90=8C=E6=AD=A5=200.5.0/?= =?UTF-8?q?0.7.0/0.4.0=EF=BC=88=E5=9C=BA=E6=99=AF=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E4=B8=8E=20Scene=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/VERSION | 6 ++-- contracts/data-models.md | 47 +++++++++++++++++++++++++++- contracts/database.md | 23 +++++++++++++- contracts/rest-api.yaml | 66 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 6 deletions(-) diff --git a/contracts/VERSION b/contracts/VERSION index 2cdfbc4..16044a8 100644 --- a/contracts/VERSION +++ b/contracts/VERSION @@ -1,8 +1,8 @@ # OpenDance Studio 契约版本 # 规则:任何字段级变更必须提升对应条目版本,并回到 opendance-web 修改后再向另两端建迁移任务。 -rest-api: 0.4.0 +rest-api: 0.5.0 websocket-keypoints: 0.1.0 -data-models: 0.6.0 +data-models: 0.7.0 skeleton-topology: 0.2.0 model-formats: 0.5.0 -database: 0.3.0 +database: 0.4.0 diff --git a/contracts/data-models.md b/contracts/data-models.md index 694bd97..ecf33b9 100644 --- a/contracts/data-models.md +++ b/contracts/data-models.md @@ -83,7 +83,7 @@ rejected 已驳回 | `durationMs` | number\|null | | | | `keypointFormat` | string\|null | 读 | 预处理后固定为 `mediapipe33@0.1.0` | | `keypointUrl` | string\|null | 读 | 预处理产物(NDJSON 或.npz 的 JSON 封装) | -| `thumbnailUrl` | string\|null | 读 | 骨架缩略图(`/preprocess` 时生成,动作管理列表展示;未生成时为 null)(v0.6.0) | +| `thumbnailUrl` | string\|null | 读 | 骨架缩略图(`/preprocess` 时生成,动作管理列表展示;未生成时为 null)(v0.7.0) | | `keypointFrames` | number\|null | 读 | 关键点帧数 | | `preprocessState` | enum | 读 | `none` / `running` / `done` / `failed` | | `preprocessError` | string\|null | 读 | | @@ -163,3 +163,48 @@ rejected 已驳回 - 仅包含 `status = 'published'` 的记录。 - `defaultModelId` 由 4.1 的规则解析得出,客户端无需自行实现优先级。 + +--- + +## 7. `Scene`(场景,GLB 为主)(v0.7.0) + +场景与模型的区别:**以 GLB 为主**(不做 humanoid 重绑定、不转 VRM), +并携带**物理参数**(服务端只存参数、不跑物理;仿真在客户端)。 + +| 字段 | 类型 | 必填 | 说明 | +|--|--|--|--| +| `id` | number | 读 | | +| `name` | string | ✅ | 场景名称,<=200 | +| `glbUrl` | string\|null | 读 | 场景 GLB 下载地址 | +| `thumbnailUrl` | string\|null | 读 | 缩略图(服务端离线渲染) | +| `fileSize` | number\|null | 读 | | +| `fileSha256` | string\|null | 读 | | +| `conversionNote` | string\|null | 读 | 由 `.zip`/`.pmx`/`.fbx` 转换而来时的说明 | +| `config` | `SceneConfig` | ✅ | 物理 / 环境 / 出生点 / 相机,见 7.1 | +| `stats` | object\|null | 读 | 场景 GLB 分析(面数 / 网格 / 材质 / warnings) | +| `sourceType` `sourceName` `sourceUrl` `license` `author` | string | ✅ | 来源五元组 | +| `status` | enum | 读 | 上传一律 `pending`,经审核后 `published` | + +### 7.1 `SceneConfig`(冻结) + +```yaml +physics: + gravity: { x: 0, y: -9.8, z: 0 } # 米/秒^2,各分量 ±200 + wind: + base: { x: 0, y: 0, z: 0 } # 基础风向量(米/秒,±100) + gustAmplitude: 0 # 阵风幅度 0~100 + gustFrequency: 0.2 # 阵风频率 Hz,0.01~5 + gustDirection: { x: 1, y: 0, z: 0 } # 阵风方向(±1) + space: world | local # 风的作用坐标系 + ground: { friction: 0.6, restitution: 0, slopeLimit: 45 } # 0~1 / 0~1 / 0~89 度 + colliders: auto | mesh | none # auto=按 GLB 命名约定,mesh=三角网 +environment: + ambientColor: "#8fb4ff" # #RRGGBB + ambientIntensity: 1 # 0~4 + fogDensity: 0 # 0~1(0=关闭) +spawnPoints: [{ x: 0, y: 0, z: 0, yaw: 0 }] # 最多 16 个 +camera: { mode: follow | fixed | orbit, distance: 4.5, height: 1.6 } +``` + +服务端入库时对每项做**边界收敛**(越界夹紧、非法枚举回退默认值), +保证客户端拿到的参数一定可解释;`catalog/export` 的 `scenes[]` 直接下发该结构。 diff --git a/contracts/database.md b/contracts/database.md index 53762cd..7cc2805 100644 --- a/contracts/database.md +++ b/contracts/database.md @@ -36,7 +36,7 @@ DB_LOGGING=false - 每个 migration 必须可逆(提供 `down` 方法) - 迁移文件名格式:`-.ts` -## 增量迁移(v0.2.0 / v0.3.0) +## 增量迁移(v0.2.0 / v0.4.0) | 迁移 | 变更 | |--|--| @@ -63,3 +63,24 @@ draft ──submit──▶ pending ──approve──▶ published - `published` 才可被 Windows 客户端 / Android App 消费 - `/api/catalog/export` 只导出 `published` 记录 + +--- + +## 场景表 `scenes`(v0.4.0 / T-WEB-015) + +| 列 | 类型 | 说明 | +|--|--|--| +| `id` | bigint PK | | +| `name` | varchar(200) | 场景名称 | +| `glb_asset_id` | bigint NULL | 场景 GLB(以 GLB 为主,不转 VRM) | +| `thumbnail_asset_id` | bigint NULL | 缩略图 | +| `config` | **mediumtext** NULL | `SceneConfig` JSON(物理 / 环境 / 出生点 / 相机) | +| `stats` | **mediumtext** NULL | 场景 GLB 分析结果(复用模型检查器) | +| `file_size` / `file_sha256` / `conversion_note` | - | 与 models 同义 | +| `source_type` `source_name` `source_url` `license` `author` | - | 来源五元组(必填) | +| `status` | varchar(16) | 上传一律 `pending`,审核后 `published`/`rejected` | +| `created_at` `updated_at` `deleted_at` | datetime | 软删除 | + +* `config` / `stats` 用 **MEDIUMTEXT**(TEXT 的 64KB 上限在 `models.stats` 上踩过坑); +* 迁移:`AddScenes1789650000000`(可逆,`down` 为 `DROP TABLE scenes`); +* 状态机与审核复用既有约定(审核走 `/api/reviews/scene/:id/approve|reject`)。 diff --git a/contracts/rest-api.yaml b/contracts/rest-api.yaml index e0b9557..6964940 100644 --- a/contracts/rest-api.yaml +++ b/contracts/rest-api.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: OpenDance Studio Web 管理服务端 API - version: 0.4.0 + version: 0.5.0 description: | OpenDance Studio 的唯一数据源与契约权威。 契约版本见 contracts/VERSION。字段语义见 contracts/data-models.md。 @@ -594,6 +594,70 @@ paths: contractVersion: { type: object, additionalProperties: { type: string } } revision: { type: string, description: 目录内容 sha256,变化即需重新拉取 } + /api/scenes: + get: + tags: [scenes] + summary: 场景列表(v0.5.0) + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - name: q + in: query + schema: { type: string } + description: 按 name/author 模糊匹配 + - name: status + in: query + schema: { type: string, enum: [draft, pending, published, rejected] } + responses: + '200': + description: 分页结果 { items, total, page, pageSize } + post: + tags: [scenes] + summary: 上传场景(multipart:file + name + 来源五元组 + 可选 config) + description: | + `file` 以 **.glb 为主**(直接导入,不重编码);`.gltf` 直接导入; + `.zip` / `.pmx` / `.fbx` 由服务端转换为 GLB(场景不做 humanoid 重绑定、不转 VRM)。 + `config` 为 JSON 字符串(或平铺字段 gravity / wind / environment / spawnPoints / camera), + 含物理参数:重力向量、风向动态(基础风 + 阵风)、地面摩擦/弹性/坡度、碰撞体来源。 + 服务端**只存参数、不跑物理**(仿真在客户端)。上传一律 status=pending。 + requestBody: + content: + multipart/form-data: + schema: + type: object + required: [file, name, sourceType, sourceName, sourceUrl, license, author] + properties: + file: { type: string, format: binary } + name: { type: string } + config: { type: string, description: SceneConfig 的 JSON 字符串 } + sourceType: { type: string } + sourceName: { type: string } + sourceUrl: { type: string } + license: { type: string } + author: { type: string } + responses: + '201': + description: 已入库(pending) + /api/scenes/{id}: + parameters: [{ $ref: '#/components/parameters/Id' }] + get: + tags: [scenes] + summary: 场景详情 + responses: + '200': { description: Scene } + '404': { description: 不存在 } + patch: + tags: [scenes] + summary: 更新场景名称 / 物理参数(状态变更走 /api/reviews) + responses: + '200': { description: Scene } + '400': { description: 参数非法或试图直接改状态 } + delete: + tags: [scenes] + summary: 删除场景(软删除) + responses: + '204': { description: 已删除 } + components: parameters: Id: