-
released this
2026-09-14 19:02:20 +08:00 | 63 commits to main since this releaseOpenDance Studio — Web 管理服务端 v0.1.0
三端开发顺序中的阶段一交付物:NestJS + TypeORM + 现有 MySQL 实例。
本次修订(三条需求)
- 模型上传支持压缩包自动转换:上传 MMD 模型包
.zip→ 服务端解压选.pmx
→ Assimp 转 FBX(导出后回读校验,失败自动回退 GLB 并把贴图内嵌)
→ 入库;转换成功后设为默认模型并把身份写进源码; - 首次启动的 .env 引导流程:
deploy/init-env.sh交互式逐项填写、校验、
可做连通性自检,非交互场景绝不挂住; - 默认监听
0.0.0.0(代码、两份.env模板、安装脚本与引导脚本四处对齐并有测试守护)。
之前的双端口分离、VMD 支持、不使用 Docker / 不部署反代等变更继续有效。
1. 模型压缩包自动转换(
.zip)POST /api/models的file现在也接受.zip(MMD 模型包):.zip ─▶ ZIP 读取器 ─▶ 选主模型(.pmx) ─▶ Assimp 导出 FBX ─▶ 回读校验 ─┬─通过─▶ 入库 FBX (STORE/DEFLATE, (指名字/最浅/最大) └─失败─▶ 导出 GLB zip-slip 防护) (贴图内嵌) ─▶ 入库 GLB- 回读校验是必须的:Assimp 的 FBX 导出器对带蒙皮的大骨架模型会写出损坏文件。
实测本包(714 骨骼的 PMX):导出退出码 11、产物回读报
FBX-DOM no Objects dictionary found。只看退出码会把坏文件入库。 - 回退结果:
Hatsune Miku.pmx → GLB,1,152,464 字节,2 张贴图已内嵌,238 关节,
1 网格 / 240 节点 / 3 材质。转换耗时 < 1s(含解压与回读校验)。 - 贴图内嵌:Assimp 在 GLB 里把贴图写成外部 URI(还带
\反斜杠),
服务端把压缩包内的贴图搬进 GLB 的 BIN chunk,保证单文件可加载(否则是白模)。 - 溯源:压缩包原件存为
model-archive资产并记入models.source_asset_id;
转换说明写入models.conversion_note。 - 安全:解压前逐条校验路径(拒绝绝对路径与
..)、拒绝加密条目、
只支持 STORE/DEFLATE,不支持的方式明确报错。 - 失败语义:包内无
.pmx、Assimp 不可用、两种格式都失败 → HTTP 422,不产生记录。 - 顺带修掉了 multipart 中文文件名乱码(
模å_…→模型_…)。
默认模型写进源码
转换成功且
autoDefault不为 false(默认)时:- 把该模型置为
is_primary=1+status=published; - 把身份写进
web-server/src/config/default-model.ts(名称 / 格式 / 产物 sha256 / 转换来源)。
GET /api/catalog/export的defaultModelId解析顺序:0. DEFAULT_MODEL_ID 环境变量(存在且已发布) 1. 源码种子 DEFAULT_MODEL_SEED(按产物 sha256 匹配,换库/重建库仍然有效) 2. 已发布且 is_primary 的 VRM 3. 任意已发布 VRM 4. id 最小的已发布模型实测:库中存在 2 个 VRM 模型时,
defaultModelId解析为 16(初音未来 Hatsune Miku / glb)。运维可用 CLI 完成同样的事(在 systemd 沙箱外,因此能写源码):
cd /opt/opendance/web-server node scripts/import-default-model.mjs 模型.zip --url http://127.0.0.1:3000 \ --name "初音未来 Hatsune Miku" --author "..." --license "..." \ --source-name "..." --source-url "https://..." # 或只补写源码:node scripts/import-default-model.mjs --write-source-only --model-id 162. 首次启动的 .env 引导
bash deploy/install.sh # 缺 deploy/.env 时自动进入引导 bash deploy/init-env.sh # 也可以单独跑 bash deploy/init-env.sh --check # 只校验不改动 bash deploy/init-env.sh --force # 已有 .env 也重新问一遍逐项询问
DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME / PORT / ADMIN_PORT / HOST,
回车保留默认;口令不回显、写盘不加引号;校验必填与端口范围(1..65535);
可用 Node + mysql2 做数据库连通性自检(口令只经环境变量传给子进程,失败仅告警);
只替换这 8 个键、保留模板注释,写完chmod 600并回读校验;结尾打印摘要(口令***)。非交互场景绝不挂住:
-y/--yes/--non-interactive、
OPENDANCE_NONINTERACTIVE=1、stdin 为/dev/null或关闭时只校验不提问;
所有读取都带超时,EOF/超时立即退出。3. 默认监听 0.0.0.0
代码默认值、
deploy/.env.example、web-server/.env.example、install.sh与
init-env.sh五处一致为0.0.0.0,并有单元测试守护(含"两份模板都写着
HOST=0.0.0.0"的断言)。反代与后端同机时可改为127.0.0.1,
管理端还可用ADMIN_HOST单独限制来源。交付与验收
契约校验 ✓ rest-api 0.3.0 / data-models 0.3.0 / model-formats 0.3.0 / database 0.2.0;路由 29 条一致 类型检查 ✓ tsc --noEmit 通过(strict) 单元测试 ✓ 125 / 125 通过(7 个文件) 生产构建 ✓ nest build + vite build 通过 压缩包转换 ✓ 真实 MMD 包 → HTTP 201 / 0.89s / format=glb / isPrimary=true / status=published 默认模型 ✓ 源码种子生效,defaultModelId 从 VRM 回落到 #16 GLB 可用性 ✓ 下载 1,152,464 字节;1 mesh / 240 nodes / 1 skin(238 joints) / 2 张内嵌贴图 .env 引导 ✓ 交互与非交互两条路径均实测;管道交互可写值且保留注释 打包完整性 ✓ 产物含 ZIP 读取器、转换模块、迁移、引导脚本与 CLI部署更新步骤
curl -LO https://gitea.kasugano.cn/zijunchiang/opendance-web/releases/download/v0.1.0/opendance-web-v0.1.0.tar.gz tar -xzf opendance-web-v0.1.0.tar.gz && cd opendance-web-v0.1.0 cp /opt/opendance/deploy/.env deploy/.env grep -q '^ADMIN_PORT=' deploy/.env || echo 'ADMIN_PORT=3001' >> deploy/.env grep -q '^HOST=' deploy/.env || echo 'HOST=0.0.0.0' >> deploy/.env bash deploy/install.sh # 会执行新的迁移 AddModelConversionFields前置:转换功能需要服务器能调用 Assimp(
ASSIMP_BIN,默认assimp)。
未安装时压缩包上传会返回 422 并提示安装;apt install assimp-utils libassimp6即可。附:模型格式在本系统中的实际能力(实测)
原生可加载的只有 glTF 系:Windows 客户端(Godot
GLTFDocument)与管理端预览
(three.jsGLTFLoader+VRMLoaderPlugin)都只认 GLB / glTF / VRM;
FBX 与 PMX 两个客户端都不能直接加载(管理端对 fbx 明确给"无法解析"提示)。转换方向 结果(Assimp 6.0.4 实测) PMX → GLB ✅ 可用(本项目通路,贴图会内嵌) PMX → glTF ✅ 可用 PMX → FBX ❌ 失败(退出码 11,产物回读报 FBX-DOM no Objects dictionary found)VRM → GLB ✅ 可用(17 MB→17 MB,822 ms) VRM → FBX ✅ 可用(33 MB,1030 ms) FBX → GLB ✅ 可用(21 MB,1847 ms) GLB → FBX ⚠️ 视模型而定;本项目来源(PMX)的 GLB 必失败 转换不是无损的(实测):
- PMX → GLB 丢失:物理刚体/关节、IK 链、付与/D 骨、卡通(toon)与球面(sphere)贴图、
轮廓线参数、顶点色、多套 UV;骨骼 714 → 蒙皮用 238;面部表情(morph) 0 个
(源 PMX 里まばたき/ウィンク/にこり等表情确实存在);材质 2 → 3。 - VRM → GLB 丢失:
extensionsUsed里的VRM消失 → humanoid 骨骼语义、
expression 绑定、spring bone、meta(许可/作者)、first-person 设置全部丢失;
92 个 blend shape 作为 glTF morph target 保留,但 VRM 语义名丢失。
→ 系统不再把它识别为 VRM(vrmVersion变 null,VRM 优先规则不再适用)。 - GLB → VRM 做不到:需要 VRM 专用工具写入 humanoid/expression/springBone/meta,
Assimp 不会写 VRM 扩展。本系统没有这个方向的实现。 - FBX → GLB:几何/蒙皮/形态目标保留;若 FBX 引用外部贴图,单文件上传时贴图缺失
(实测 #17 转换后 images=0);材质被近似拆分(1 → 7)。 - 任何目标格式都无法承载 PMX 的物理与卡通渲染参数。
更关键的是骨骼命名:模型能被驱动的前提是骨骼名能映射到 humanoid 规范
(客户端humanoid_map.gd的候选名以英文/VRoid/3ds Max Biped 为主)。实测覆盖率:模型 humanoid 覆盖 能否驱动(16 根必需骨) PMX→GLB(Hatsune Miku,日文 MMD 骨名) 0 / 22 ❌ 不能 VRM #18(中文骨名) 7 / 22 ❌ 不能 FBX #17 → GLB(Bip001 命名) 17 / 22 ⚠️ 只缺左右小腿 结论:格式对了不等于能用,还要有 humanoid 骨骼命名(或补一张映射表)。
已知设计取舍
- 默认模型种子以上传压缩包的 sha256 为匹配键:实测 Assimp 的 GLB 导出不是字节级
确定的(同一份 zip 连续转换三次得到三个不同 sha256,大小一致、BIN chunk 有十几字节浮动),
用产物 sha256 当键会失效。产物 sha256 仍写入fileSha256字段但仅作记录。
已知事项
- 模型压缩包里的授权信息:本仓库内置的默认模型(初音未来 Hatsune Miku,
Boku Wa Luka)
压缩包内没有许可文件,且疑似游戏提取资源。已按"未标注、对外使用前请确认授权"
记入license字段;对外发布前请核实并更新来源五元组。 - 未内置管理端登录认证,请在反代层加 Basic Auth / IP 白名单,或只在内网暴露管理端端口。
- Assimp 的 FBX 导出器对复杂蒙皮模型不可用(本项目已自动回退 GLB);
若必须得到 FBX,需要 Blender + mmd_tools 之类的专用转换器,当前未纳入。
Downloads
- 模型上传支持压缩包自动转换:上传 MMD 模型包