Files
opendance-web/tasks/007-upload-review.md
T
opendance 39bfd390e3 feat(models): 模型一律转 VRM(契约 0.5.0)+ 可用性门槛 + 管理端版本号展示
按《DSH 开发文档 v1.6》4.9 与第十一章实现,Web 端第一步:

1. 契约先行(先改 contracts/ 再同步三端):
   * model-formats.md -> 0.5.0:模型主力格式改为 VRM,所有上传模型一律转 VRM
     (.pmx/.fbx: assimp -> GLB -> VRM;glb/gltf: 包装为 VRM);
     新增 humanoid 硬门槛与"不达标回退 GLB + usable=false"的交付表;
   * data-models.md -> 0.5.0:Model 新增 usable / unusableReason,明确
     usable=false 不进入 catalog/export;format 说明改为主力 vrm;
   * VERSION 与 contract-versions.ts 同步(三端契约文件逐字节一致)。
2. 实现:
   * MODEL_CONVERT_FORMAT 默认改为 vrm(deploy/.env.example 同步);
   * 新增 isUsableModel()/unusableReason():usable 等价于"已产出 VRM",
     原因从 stats 里带出 humanoid 覆盖度;
   * catalog/export 与 catalog/version 指纹都只统计 format=vrm 的已发布模型,
     resolveDefaultModelId 同样只在可用模型中选;
   * 管理端:模型列表与审核面板显示「不可用」及原因;
   * 管理端左下角展示当前版本号(构建期从 web-server/package.json 注入
     __APP_VERSION__,与 GET /health 同源),并去掉写死的"契约 rest-api 0.1.0"。
3. 测试:新增 test/model-usability.spec.ts 5 项;规模 16 文件 / 225 项。
4. 版本 0.3.0。
2026-09-17 00:05:02 +08:00

5.7 KiB

T-WEB-007 上传审核流程

  • 状态: 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、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

状态机(契约冻结)

draft ──submit──▶ pending ──approve──▶ published
                     │
                     └──reject──▶ rejected ──submit──▶ pending
方法 路径 说明
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; 模型可带 setPrimary:true
POST /api/reviews/:targetType/:id/reject -> rejected, 必须填写意见
POST /api/reviews/:targetType/:id/withdraw -> draft

关键实现

  • STATUS_TRANSITIONS 是状态机的唯一来源;非法流转一律 409, 重复审核同一状态同样 409。
  • 驳回未填 comment -> 400(合规要求:拒绝侵权内容需留痕)。
  • 每次成功流转都写一条 review_records,记录 fromStatus / toStatus / 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 数 / 最大层级。

验收

cd web-server
npx vitest run        # 16 个文件 / 225 项通过(含 admin-bone-filter 8 项、.env 预检 12 项、Assimp 13 项、缩略图贴图 6 项、MMD 材质对齐 9 项、模型可用性 5 项)
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)