• v0.1.0 dbd9155b40

    zijunchiang released this 2026-09-14 19:02:20 +08:00 | 63 commits to main since this release

    OpenDance Studio — Web 管理服务端 v0.1.0

    三端开发顺序中的阶段一交付物:NestJS + TypeORM + 现有 MySQL 实例。

    本次修订(三条需求)

    1. 模型上传支持压缩包自动转换:上传 MMD 模型包 .zip → 服务端解压选 .pmx
      → Assimp 转 FBX(导出后回读校验,失败自动回退 GLB 并把贴图内嵌)
      → 入库;转换成功后设为默认模型并把身份写进源码;
    2. 首次启动的 .env 引导流程:deploy/init-env.sh 交互式逐项填写、校验、
      可做连通性自检,非交互场景绝不挂住;
    3. 默认监听 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(默认)时:

    1. 把该模型置为 is_primary=1 + status=published;
    2. 把身份写进 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 16
    

    2. 首次启动的 .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.js GLTFLoader + 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