diff --git a/contracts/data-models.md b/contracts/data-models.md index da64438..ea0167d 100644 --- a/contracts/data-models.md +++ b/contracts/data-models.md @@ -103,7 +103,8 @@ rejected 已驳回 | `fileSize` | number | 读 | | | `thumbnailUrl` | string\|null | 读 | | | `conversionNote` | string\|null | 读 | 由压缩包转换而来时的说明(`Hatsune Miku.pmx → GLB,已内嵌 2 张贴图`);直传模型为 null | -| `fileSha256` | string\|null | 读 | 模型文件(转换产物)的 sha256,用于换库后定位默认模型 | +| `fileSha256` | string\|null | 读 | 模型文件(转换产物)的 sha256 —— **每次转换都会变**,仅作记录 | +| `sourceSha256` | string\|null | 读 | 上传压缩包的 sha256(稳定);默认模型匹配以它为准,非压缩包上传为 null | | `source*` | | ✅ | 来源五元组 | | `status` | `PublishStatus` | | | | `createdAt` / `updatedAt` | string | 读 | | @@ -115,7 +116,10 @@ rejected 已驳回 ``` 0. 环境变量 DEFAULT_MODEL_ID 指定,且该模型存在且已发布 1. 源码内写死的默认模型种子(web-server/src/config/default-model.ts 的 - DEFAULT_MODEL_SEED):按转换产物 sha256 匹配已发布模型 —— 换库/重建库后仍然有效 + DEFAULT_MODEL_SEED):按 **sourceSha256(上传压缩包的 sha256)** 匹配已发布模型, + 换库/重建库后仍然有效;没有压缩包时退回按产物 sha256,最后按名称兜底。 + ⚠ 不能用产物 sha256 当主键:实测 Assimp 的 GLB 导出不是字节级确定的, + 同一份 zip 每次转换得到的文件 sha256 都不同(BIN chunk 有十几字节浮动) 2. models 中 format = 'vrm' 且 status = 'published' 且 is_primary = 1 3. models 中 format = 'vrm' 且 status = 'published',按 id 升序取第一个 4. models 中 status = 'published',按 (format='vrm' DESC, id ASC) 取第一个 diff --git a/contracts/model-formats.md b/contracts/model-formats.md index 73c639e..36af55c 100644 --- a/contracts/model-formats.md +++ b/contracts/model-formats.md @@ -23,6 +23,7 @@ | 贴图 | GLB 产物里 Assimp 会把贴图写成外部 URI(还带 `\` 反斜杠),服务端把压缩包内的贴图**内嵌**进 GLB 的二进制块,保证单文件可加载 | | 溯源 | 压缩包原件以 `model-archive` 资产留存,写入 `models.source_asset_id` | | 默认模型 | 转换成功且 `autoDefault` 不为 false:`is_primary=1` + `status=published`,并把身份写进源码 `web-server/src/config/default-model.ts`,见 `data-models.md` §4.1 | +| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换三次得到三个不同的 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**压缩包 sha256** 为匹配键,产物 sha256 只作记录 | | 失败 | 压缩包里没有 `.pmx`、Assimp 不可用、两种格式都失败 → **HTTP 422**,不产生模型记录 | 安全:解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE。 diff --git a/contracts/rest-api.yaml b/contracts/rest-api.yaml index 6d9d835..0bbe2bb 100644 --- a/contracts/rest-api.yaml +++ b/contracts/rest-api.yaml @@ -331,8 +331,10 @@ paths: 压缩包原件作为 `model-archive` 资产留存以便溯源。 转换成功且 `autoDefault` 不为 false 时,该模型会被置为全局默认模型 - (`isPrimary=1` + `status=published`),并把身份(名称/格式/产物 sha256) + (`isPrimary=1` + `status=published`),并把身份(名称/格式/**压缩包 sha256**) 写进源码 `web-server/src/config/default-model.ts`。 + 注意:**转换产物不是字节级确定的**(同一份 zip 每次转换的产物 sha256 都不同), + 所以默认模型种子以压缩包 sha256 为匹配键,产物 sha256 只作记录。 requestBody: required: true content: @@ -774,7 +776,11 @@ components: fileSha256: type: string nullable: true - description: 模型文件(转换产物)的 sha256,用于换库后定位默认模型 + description: 模型文件(转换产物)的 sha256 —— 每次转换都会变(Assimp 导出非字节级确定),仅作记录 + sourceSha256: + type: string + nullable: true + description: 上传压缩包的 sha256(稳定);默认模型匹配以它为准,非压缩包上传为 null status: { $ref: '#/components/schemas/PublishStatus' } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time }