feat(models): 发布 v0.2.0 —— GLB/VRM 主力格式、自动缩略图与分析、模型审核与骨骼预览
需求 1:系统确定以 GLB / VRM 为模型主力格式
- 服务端新增支持库:
- model-inspector.ts:解析 GLB/VRM 的面数、顶点、材质、贴图、形变目标、
**骨骼树(名称/层级/是否蒙皮/humanoid 映射/子节点数)**、humanoid 覆盖率与可驱动性
- model-thumbnail.ts:**纯 TS 软件光栅化**(z-buffer + Lambert + 完整蒙皮矩阵)
生成 512×512 PNG 缩略图,零原生依赖、不装 headless-gl
- vrm-writer.ts:GLB → VRM 1.0(写入 VRMC_vrm 扩展与 humanoid 映射)
- gltf-packer.ts:glTF(.gltf + .bin + 贴图)→ 单文件 GLB
- humanoid 候选表与 Godot 客户端 humanoid_map.gd **逐字对齐**,并有单测断言两边一致
(服务端判定的"可驱动" = 客户端实际能否驱动)
需求 2:模型管理
- .glb / .vrm / .gltf **直接导入**(不重编码,GLB 含 VRM 扩展会自动识别为 VRM)
- .pmx / .fbx 服务端转换:assimp → .glb →(失败)→ .gltf 后自行打包成 GLB
→(MODEL_CONVERT_FORMAT=vrm 时)补 VRMC_vrm 产出 VRM 1.0;每步 assimp info 回读校验
- .zip **自动搜索包内可用模型**:.vrm → .glb → .gltf(直接导入)优先,
其次 .pmx → .fbx(转换);同优先级内按"指名字 → 目录最浅 → 文件最大"
- 入库后自动分析(models.stats)与生成缩略图(models.thumbnail_asset_id),
失败均不阻断入库;管理端列表显示小缩略图、预览放开到 vrm/glb/gltf
需求 3:审核功能
- 上传的模型状态**一律 pending**(不再自动发布、不再自动设默认)
- 审核界面新增模型面板:体积、面数、顶点、材质、贴图、骨骼、形变目标等指标,
humanoid 覆盖进度、可驱动性结论(缺哪些必需骨骼)、warnings,以及
**骨骼详细预览**(层级缩进、蒙皮/Humanoid 徽章、子节点数、搜索保留祖先链、
仅看蒙皮、限高滚动 + 只渲染前 400 行)
- 审核通过可带 setPrimary=true 一并设为默认模型(此时才写源码种子);
POST /api/models/:id/primary 仅允许 published(否则 409)
- scripts/import-default-model.mjs 改为"上传 → 审核通过 + 设默认"流程
其它:
- models 新增 stats 列(迁移 AddModelStats1789460000000,已在真实库执行)
- 契约升至 rest-api/data-models/model-formats 0.4.0、database 0.3.0(另两库已同步)
- 版本号 0.1.0 → 0.2.0(package.json ×2 + lockfile + project.yaml)
- 文档:README.txt / DSH.md / project.yaml / tasks 005·007·010 全面更新;
顶层开发书更新为 v1.4
验证:168 项单测通过(10 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实端到端:MMD 压缩包 → GLB + 分析(面数 9071/骨骼 238/humanoid 0-22) + 缩略图(15KB PNG);
GLB 直接导入 sha256 与源文件一致;审核通过后 defaultModelId 由 VRM 变为该模型
This commit is contained in:
@@ -1,21 +1,24 @@
|
||||
# T-WEB-007 上传审核流程
|
||||
|
||||
- **状态**:done
|
||||
- **状态**:done(v0.2.0 追加:模型审核面板 / 骨骼详细界面 / `setPrimary`)
|
||||
- **依赖**:T-WEB-003 / 004 / 005
|
||||
|
||||
## 目标
|
||||
|
||||
对歌曲、动作、模型三类资产实施统一的状态机与审核流水,确保只有审核通过的资产
|
||||
被客户端消费。
|
||||
被客户端消费。模型上传**一律 `pending`**,审核是模型可见与成为默认模型的唯一入口。
|
||||
|
||||
## load
|
||||
|
||||
```
|
||||
DSH.md
|
||||
contracts/rest-api.yaml # 锚点:#/paths/~1api~1reviews
|
||||
contracts/data-models.md # 锚点:§1.2 PublishStatus、§5 ReviewRecord
|
||||
contracts/data-models.md # 锚点:§1.2 PublishStatus、§4.1 默认模型选择规则、§5 ReviewRecord
|
||||
contracts/database.md # 锚点:状态机
|
||||
web-server/src/reviews/*
|
||||
web-server/src/models/model-inspector.ts
|
||||
web-server/admin/src/components/ModelReviewPanel.tsx
|
||||
web-server/admin/src/lib/bone-tree.ts
|
||||
```
|
||||
|
||||
## 状态机(契约冻结)
|
||||
@@ -31,7 +34,7 @@ draft ──submit──▶ pending ──approve──▶ published
|
||||
| GET | `/api/reviews` | 不传 `status` → 审核流水;传 `status` → 该状态待审资产(三类合并) |
|
||||
| GET | `/api/reviews/:targetType/:id/history` | 单个资产的审核历史 |
|
||||
| POST | `/api/reviews/:targetType/:id/submit` | → `pending` |
|
||||
| POST | `/api/reviews/:targetType/:id/approve` | → `published` |
|
||||
| POST | `/api/reviews/:targetType/:id/approve` | → `published`;**模型**可带 `setPrimary:true` |
|
||||
| POST | `/api/reviews/:targetType/:id/reject` | → `rejected`,**必须填写意见** |
|
||||
| POST | `/api/reviews/:targetType/:id/withdraw` | → `draft` |
|
||||
|
||||
@@ -44,12 +47,58 @@ draft ──submit──▶ pending ──approve──▶ published
|
||||
`reviewer` / `comment`。
|
||||
- 三类资产的直连 PATCH 也会校验状态机,避免绕过审核流程。
|
||||
- `/api/catalog/export` 与 `/api/catalog/version` 只导出 `published`。
|
||||
- **模型上传只能 pending**:`ModelsService.create()` 固定
|
||||
`status=PublishStatus.Pending` + `isPrimary=false`,请求体里的 `autoDefault`
|
||||
等旁路一律不生效(硬性约束,见 DSH.md §6.9)。
|
||||
|
||||
## 模型审核面板(v0.2.0)
|
||||
|
||||
待审列表只有 `PendingItem`(不含模型详情),面板用 `item.targetId` 现拉
|
||||
`GET /api/models/:id`(`admin/src/components/ModelReviewPanel.tsx`):
|
||||
|
||||
| 区域 | 内容 |
|
||||
|---|---|
|
||||
| 基本 | 缩略图、格式、状态、文件地址、转换说明 |
|
||||
| 指标 | 体积(`fileSize`)/ 格式 / 容器 / VRM 版本 / 面数 / 顶点 / 网格 / 材质 / 贴图 / 骨骼 / 节点 / 形变目标 / 动画 / 图元 / 蒙皮 / 骨骼最大层级 |
|
||||
| 可驱动性 | 结论横幅:16 根必需 humanoid 骨齐全 → 可驱动;不全 → 列出缺哪些(直接标红);非 glTF 容器 → 只能审核来源与体积 |
|
||||
| humanoid 覆盖 | 已映射 / 总数(22)+ 进度条 + 已命中规范名徽章明细 |
|
||||
| warnings | `ModelStats.warnings` 逐条列出 |
|
||||
| 骨骼详细界面 | 扁平骨骼树按 `depth` 缩进(14px/层),显示骨骼名、是否蒙皮徽章、humanoid 映射徽章、子节点数;支持按名搜索(保留祖先链)、「仅显示蒙皮骨骼」开关、限高滚动 + **只渲染前 400 行**(超出提示还有多少根),千根骨骼不卡 |
|
||||
| stats 为空 | 提示"暂无分析数据(旧数据或后端尚未完成分析)",只能按格式/体积/来源判断 |
|
||||
|
||||
### 通过 / 驳回 / 设为默认
|
||||
|
||||
- 面板底部勾选「同时设为全局默认模型」→ 点「通过」时请求体带 `setPrimary:true`:
|
||||
`ReviewsService.transition()` 保存模型为 `published` 后调用
|
||||
`ModelsService.setPrimary(id)`(同一事务清其余 `is_primary`),并把身份写进源码
|
||||
`src/config/default-model.ts`。
|
||||
- 单独设置走 `POST /api/models/:id/primary`:**仅 `published` 允许,否则 409**。
|
||||
- 驳回必须填意见(400)。
|
||||
|
||||
### 骨骼树过滤(`admin/src/lib/bone-tree.ts`,纯函数、可单测)
|
||||
|
||||
- `filterBoneTree(tree, keyword, onlySkin)`:先按关键字(大小写不敏感)与 `onlySkin`
|
||||
取命中集合,再沿父链补齐**祖先链**,保证按 `depth` 缩进的树不断裂;空关键字 +
|
||||
非 `onlySkin` 时直接返回副本。
|
||||
- `boneTreeStats(tree)`:总数 / 蒙皮数 / 已映射 humanoid 数 / 最大层级。
|
||||
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
cd web-server
|
||||
npx vitest run # 10 个文件 / 168 项通过(含 admin-bone-filter 8 项)
|
||||
npm run test:smoke
|
||||
```
|
||||
|
||||
冒烟 [5] 段落:submit → pending、待审列表包含、驳回无意见 400、approve → published、
|
||||
重复审核 409、流水恰好 2 条。
|
||||
|
||||
| 验收项 | 状态 |
|
||||
|---|---|
|
||||
| 模型上传只能是 pending | ✅ `create()` 固定 `PublishStatus.Pending`,不自动发布/设默认 |
|
||||
| 模型审核面板展示指标 + 分析 | ✅ 体积/格式/容器/VRM 版本/面数/顶点/网格/材质/贴图/骨骼/节点/形变目标/动画 |
|
||||
| 骨骼详细界面与过滤 | ✅ 缩进 + 蒙皮/映射徽章 + 搜索保留祖先链 + 仅蒙皮开关 + 限高 + 前 400 行(`admin-bone-filter.spec.ts` 8 项) |
|
||||
| `setPrimary` 随审核通过生效 | ✅ `POST /api/reviews/model/22/approve` + `setPrimary:true` → 200、`status=published`、`isPrimary=true`,源码种子写入 `sourceSha256=72eb6882…` |
|
||||
| 单独设默认仅 published | ✅ `POST /api/models/:id/primary` 非 published → 409 |
|
||||
| 驳回必须填意见 | ✅ 无 `comment` → 400 |
|
||||
| 目录重建后默认模型 | ✅ `GET /api/catalog/export` 的 `defaultModelId` 由 VRM 变为 **22**(库中另有 2 个已发布 VRM) |
|
||||
|
||||
Reference in New Issue
Block a user