背景(用户反馈 + 本地用真 Assimp 6.0.4 复现): PMX 的着色模型(卡通阶梯 + 球面贴图 + 平坦环境光)与 glTF PBR 不是一回事, Assimp 转换会丢掉其中一部分。实测样本【奥黛塔】_by_原神.zip(39 个材质): 1. 主因:PMX 里 36 个材质带 drawFlag bit0(不剔除背面),而导出的 GLB 里 doubleSided 一个都没有 -> 头发/裙摆/睫毛这类薄片被单面剔除, 看起来就是颜色/贴图不对; 2. PMX 的 shininess/specular/ambient 与 PBR 无映射;Assimp 还会带上 KHR_materials_specular / KHR_materials_volume 等与 MMD 无关的扩展; 3. Assimp 把 accessor.min/max 写坏(实测 min = 2.2250738585072014e-308), Godot 等客户端用它算包围盒。 修复: 1. 新增 src/models/pmx-materials.ts:只读 PMX 材质段(布局与权威库 mmd-parser 一致,注意贴图索引宽度是 1 字节),拿到 name/diffuse/specular/shininess/ ambient/drawFlag/球面贴图/toon/faceCount; 2. 新增 src/models/glb-materials.ts:转换后按索引把 PMX 参数补回 GLB —— doubleSided、metallicFactor=0、roughnessFactor=clamp(1-shininess/100,0.5,1)、 baseColorFactor x diffuse(含 alpha);清掉与 MMD 无关的扩展; 修正全部浮点 accessor 的 min/max;支持 KHR_materials_unlit(MODEL_SHADING); 3. 抽出 src/common/glb-chunks.ts(JSON/BIN chunk 读写),vrm-writer 改为复用; 4. 能力边界写清楚:PMX 的球面贴图(该模型 15 项)与 toon 阶梯在 glTF core 里 没有对应槽位,任何转换器都无法保留,数量写进 conversion_note 供审核查看; 5. 新增 test/mmd-materials.spec.ts 9 项;规模 15 文件 / 220 项; 6. 新增 MODEL_SHADING 配置(默认 pbr,可选 unlit),文档同步(README.md/.txt、DSH.md、.env.example)。
5.6 KiB
5.6 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 # 15 个文件 / 220 项通过(含 admin-bone-filter 8 项、.env 预检 12 项、Assimp 13 项、缩略图贴图 6 项、MMD 材质对齐 9 项)
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) |