fix(models): 修复上传 PMX 返回 500(models.stats 超 TEXT 上限)与 GBK 丢贴图

现象(真实故障):导入示例文件/【奥黛塔】_by_原神.zip 返回 500 Internal server error。

根因一(500):
  该 PMX 643 根骨骼、93,350 面,转换本身成功(GLB 23.7MB),但分析结果里的
  骨骼树按骨骼逐条展开,JSON 实测 70,980 字节,而 models.stats 是 TEXT
  (上限 65,535)。MySQL/MariaDB 严格模式直接拒绝写入(ER_DATA_TOO_LONG /
  1406),而这一步没有兜底 -> 未捕获 -> 500。

根因二(丢贴图):
  tex/髮.png 的 GBK 字节是 F3 8C。文件名解码先试 Shift-JIS,而 WHATWG 的
  shift_jis 把未定义码位映射成私用区不可见字符(不是 U+FFFD),于是被判为
  「解码成功」,永不回退 GBK;贴图名对不上,该模型 18 张贴图只内嵌了 17 张。

修复:
1. models.stats -> MEDIUMTEXT(实体 mediumtext + transformer,保留 JSON 语义;
   不用 simple-json 以免元数据与库列型不一致),新增迁移
   AddModelStatsMediumText1789550000000;
2. 新增 shrinkStatsForStorage() 兜底:即使库未迁移,也只截断骨骼树
   (父在前,保留祖先链),面数/骨骼数/humanoid 覆盖/可驱动性/warnings 全部保留;
   服务层 saveWithStatsFallback() 仅在 1406/DATA_TOO_LONG 时裁剪重试一次,
   其它数据库错误照旧抛出;
3. 压缩包文件名解码把「私用区字符」也算作解错信号,回退 GBK;实测该模型
   贴图由 17/18 变为 18/18 全部内嵌;
4. 新增 12 项测试(test/model-stats-limit.spec.ts 9 项 + 压缩包编码/目录条目 3 项);
5. 版本 0.2.3,README(.md/.txt)/DSH.md 补修订说明、500 的 FAQ 与 stats 容量约束。
This commit is contained in:
opendance
2026-09-16 03:30:12 +08:00
parent afed67e175
commit 8e8cc0475b
16 changed files with 464 additions and 35 deletions
+25 -7
View File
@@ -1,5 +1,5 @@
================================================================
OpenDance Studio -- Web 管理服务端 v0.2.2 使用说明
OpenDance Studio -- Web 管理服务端 v0.2.3 使用说明
================================================================
本服务端是 OpenDance Studio 三端中的第一端, 负责管理歌曲、VRM 模型与
@@ -8,6 +8,19 @@ OpenDance Studio -- Web 管理服务端 v0.2.2 使用说明
重要: 本服务端 不部署、不管理 MySQL。数据库为部署方提供的现有实例,
连接信息通过 .env 注入。
v0.2.3 修订
1. 修复上传 PMX 返回 500(models.stats 容量): 分析结果里的骨骼树按骨骼逐条
展开, 实测一个 643 骨骼的 PMX(【奥黛塔】_by_原神.zip)就有 70,980 字节,
而该列是 TEXT(上限 65,535), MySQL 严格模式直接拒绝写入(1406)导致 500。
列迁移到 MEDIUMTEXT(AddModelStatsMediumText), 并新增
shrinkStatsForStorage 兜底: 即使库没迁移, 也只截断骨骼树、保留面数/
骨骼数/humanoid 覆盖/可驱动性, 绝不让上传失败。
2. 修复 GBK 压缩包丢贴图: 文件名解码先试 Shift-JIS, 而 WHATWG 的 shift_jis
会把未定义码位映射成私用区不可见字符(不是 U+FFFD), 于是像 tex/髮.png
(GBK 字节 F3 8C)这样的名字被判为"解码成功"而永不回退 GBK, 贴图名对不上
就丢图。现在私用区也算解错信号; 实测该模型贴图由 17/18 变为 18/18 全部内嵌。
3. 新增 12 项测试(test/model-stats-limit.spec.ts 9 项 + 压缩包编码/目录条目 3 项)。
v0.2.2 修订
1. 修复 Assimp 缺失时的报错与部署体验: 服务器没装 Assimp 时, 上传 .pmx/.fbx
(或含它们的压缩包)只得到两条 导出失败(ENOENT): spawn assimp ENOENT,
@@ -45,11 +58,11 @@ v0.2.1 修订
2. 准备一个现有的 MySQL / MariaDB 实例, 并建好库与账号(见下方"前置要求")
本项目不会创建数据库本身
3. 取得发布包并校验(地址用你们自己的制品库替换)
curl -LO https://<制品库地址>/opendance-web-v0.2.2.tar.gz
curl -LO https://<制品库地址>/opendance-web-v0.2.3.tar.gz
curl -LO https://<制品库地址>/SHA256SUMS
sha256sum -c SHA256SUMS # 期望输出 "OK"
4. 解压并生成配置(交互式引导, 逐项问答, 口令不回显)
tar -xzf opendance-web-v0.2.2.tar.gz && cd opendance-web-v0.2.2
tar -xzf opendance-web-v0.2.3.tar.gz && cd opendance-web-v0.2.3
bash deploy/init-env.sh
5. 安装(建用户与目录 -> 装依赖 -> 迁移 -> 注册系统服务 -> 等健康检查)
bash deploy/install.sh
@@ -107,8 +120,8 @@ v0.2.1 修订
(本项目不会创建数据库本身, 也不需要 GRANT OPTION / FILE / SUPER 等管理权限)
部署步骤
tar -xzf opendance-web-v0.2.2.tar.gz
cd opendance-web-v0.2.2
tar -xzf opendance-web-v0.2.3.tar.gz
cd opendance-web-v0.2.3
bash deploy/init-env.sh # 首次启动引导: 逐项问答生成 deploy/.env(见下)
bash deploy/install.sh # 已以 root 登录, 不需要 sudo
@@ -239,6 +252,11 @@ deploy/.env 初始化引导(deploy/init-env.sh)
(RHEL 系 dnf install -y assimp; install.sh 现在会尝试自动安装)。
装好后不需要重启服务; 若不在服务 PATH 中, 在 deploy/.env 里把 ASSIMP_BIN
设为绝对路径。只上传 .glb / .vrm 则不需要它。
- 导入模型返回 500 Internal server error
老库上多为 models.stats 超长(骨骼树按骨骼展开, 643 骨骼即 70,980 字节
> TEXT 上限): 执行 ALTER TABLE models MODIFY stats MEDIUMTEXT NULL;
或部署 v0.2.3(install.sh 会自动跑迁移)。v0.2.3 起服务端另有兜底,
不会再 500。
- 安装时报 command not found / 退出码 127, 且提示指向 deploy/.env 某一行
该行的值含空格或 shell 元字符却没加引号, 被 shell 当成命令执行了。按预检
提示给这个值加引号(如 MODEL_CONVERT_PMX="Hatsune Miku.pmx")后重跑。
@@ -269,11 +287,11 @@ deploy/.env 初始化引导(deploy/init-env.sh)
cd web-server
npm run contracts:check # 契约版本一致性
npm run typecheck # TypeScript 严格检查
npm run test # 193 项单元测试 / 12 个文件(VMD 31、模型压缩包 24、
npm run test # 205 项单元测试 / 13 个文件(VMD 31、模型压缩包 24、
# 模型检查器 25、缩略图 8、骨骼过滤 8、
# 预处理 25、init-env 16、admin-port 16、
# access-log 9、VRM 检查器 6、.env 预检 12、
# Assimp 定位与安装 13)
# Assimp 定位与安装 13、stats 容量与编码 12)
npm run build # 生产构建
npm run start:prod & # 启动
npm run test:smoke # 57 项端到端冒烟(需要数据库可连通)