zijunchiang
|
12041f2e58
|
fix(upload,models): 上传落盘补齐兜底 + 不可用模型归因准确(v0.7.5)
两个真实缺陷,都由本轮的「跨端边界验收」暴露:
一、上传落盘:去重命中时删掉了唯一副本(T-WEB-017)
persistTempFile(动作 / 模型 / 场景上传走的通路)在 sha256 去重命中时直接
safeUnlink(tempPath) 并返回旧记录 —— 一旦记录指向的路径不存在(换过 STORAGE_DIR、
只恢复数据库、手工删过文件),上传仍返回 201,但后续动作预处理 500
(实测 ENOENT .../<sha>.vmd)、模型分析与缩略图静默失败。持久化通路 persistBuffer
早在上一轮就补了「文件缺失则按原路径补写」,这条通路漏了;现在同样处理:
先 existsSync(absolutePath(existing)),缺失则按记录里的原路径 rename(跨设备退化为
copyFile)补写,只有确认目标存在才删临时文件。
新增 test/storage-persist.spec.ts(5 项):首次上传 / 去重命中且文件在 /
去重命中但文件已丢失(补写而非删除)/ persistBuffer 回归 / 空文件拒绝。
反向验证:临时还原修复后第 3 条立刻失败,恢复后 5 项全过。
二、不可用模型的原因归因不准确(T-WEB-017)
unusableReason() 只要 stats 里有 humanoid 数字就归因于 humanoid,于是一个直接上传的
.glb(骨架完全合规、drivable=true、只是没有 VRM 容器)会被写成
「humanoid 不达标(19/22)」,把审核引向"重新绑定骨骼"而不是"以 VRM 交付"。
实测(跨端验收用真实 PMX→Assimp→GLB,886 KB):
修复前 → humanoid 不达标(19/22),未产出 VRM
修复后 → 未产出 VRM(格式为 GLB;humanoid 覆盖 19/22 已达标),需重新以 VRM 交付才能进入客户端目录
现在按 drivable 区分:不满足 16 根必需骨才归因 humanoid;达标但没 VRM 容器则明确说"已达标"。
新增 2 项单测(model-usability 5 → 7 项)。
三、跨端边界验收(Windows × Web)新增两类场景,双方都断言
* 不可用模型:真实 PMX 用 Assimp 转 GLB 后入库(等价"转换只产出 GLB")→
usable=false、原因准确、**不进入 catalog/export**、defaultModelId 不变;
包内客户端的 bundle.models 里没有任何非 VRM 模型;
* 场景 config 只给 {physics:{gravity:{y:-9.8}}} → 服务端补齐默认
(碰撞体 auto、地面 0.6/45°、静风、相机 follow、环境光 1、出生点 1 条),
客户端解析后逐项一致。
实测:npm run verify 全绿(契约 6 项 / 35 路由 / 版本 0.7.5 / 27 文件 309 项测试 /
build / 产物级实体注册核对 / 启动冒烟);跨端集成验收在**发布包起的服务端**上全部通过。
版本 0.7.4 → 0.7.5。
|
2026-09-18 03:03:50 +08:00 |
|
zijunchiang
|
7a74f598be
|
fix(entities): 场景功能的真正根因 —— Scene 未注册进 ENTITIES(v0.7.4)
部署 v0.7.3 后,/health 的 scenes.error 给出决定性线索:
No metadata for "Scene" was found.
src/entities/index.ts 的 ENTITIES 一直漏了 Scene:
export const ENTITIES = [Asset, Song, Motion, CharacterModel, ReviewRecord];
TypeORM 只按 ENTITIES 注册 metadata,漏注册的后果极其隐蔽:
- 启动完全正常(DI 可解析、/health 正常、migrationsPending 0);
- 迁移也正常(AddScenes 已执行,表其实是建好的);
- 但第一次查 Scene 就抛 No metadata,于是 /api/scenes、/api/catalog/export、
/api/catalog/version、/api/reviews?status=pending 全部 500
→ 客户端拉不到任何目录、管理端待审列表打不开。
即:此前"数据库缺 scenes 表"的判断也是错的(v0.7.3 的降级与诊断脚本因此仍然值得保留
—— 它们把"场景坏了"限制成"场景不可用",而不是整站不可用)。
修复:ENTITIES 加入 Scene 并导出。
新增静态防线 test/entities-registry.spec.ts(4 项):扫描 src/entities/*.entity.ts 里的
@Entity() 类并逐一与 ENTITIES 对账(漏一个即失败)、检查无重复/无多余、核对 AddScenes
迁移文件存在。已做反向验证:把 Scene 从 ENTITIES 移除后该测试立刻失败。
这类缺陷既过得了 tsc、也过得了启动冒烟(应用能启动),只能靠静态核对兜住。
文档:README.md/.txt 增加 v0.7.4 修订说明;tasks/015-scene-management.md 增
「缺陷修复记录四」;开发文档 §4.12 补「新增实体必须加入 ENTITIES」的硬性要求。
验证:npm run verify 通过(23 文件 / 285 项测试 + 契约/路由/版本一致 + 启动冒烟);
发布产物 opendance-web-v0.7.4.tar.gz
sha256 e680c23fc382b6c39498608ea0154f8f80e9e6b5c13518f0f88095644ce88fe1。
|
2026-09-17 03:48:52 +08:00 |
|
zijunchiang
|
f3b97fec53
|
fix(scenes): 场景表不可用不再拖垮整站(生产事故修复)+ 诊断脚本
现场(真实部署 v0.7.2):数据库缺 scenes 表,于是
/api/scenes、/api/scenes/:id、/api/catalog/export、/api/catalog/version、
/api/reviews?status=pending 全部 500,而 /api/songs、/api/motions、/api/models
正常。后果不是"场景不可用",而是客户端拉不到任何目录(Windows/Android 全废)、
管理端待审列表打不开。`/health` 却显示 database.connected=true、
migrationsPending=0 —— 因为 DataSource.showMigrations() 只比较**代码里加载到的**
迁移;部署的 dist 若缺 AddScenes.js,该迁移不在比较集合里,于是"没有待执行迁移"
但表不存在(最隐蔽的一种不一致)。
修复思路:契约与文档都写明「场景是可选资源」(没有可用场景时客户端回退内置空
场景),所以场景表的问题不允许影响与场景无关的功能 —— 改为降级:
- ScenesService 新增 queryScenes() 包装:数据库层面失败时返回空结果,**只记一次
日志**(避免刷屏),原因写入 diagnostics();下一次查询成功即自动复位;
- ReviewsService.listByStatus() 对场景查询单独 catch,待审列表照常返回其它资源;
- /health 新增 scenes: { available, error } —— 降级但绝不静默;
- HealthModule 增加 imports: [ScenesModule](HealthController 注入了 ScenesService,
Nest 的 DI 按模块解析,漏了会直接 UnknownDependenciesException;
scripts/check-bootstrap.mjs 已能拦住这一类)。
新增 web-server/scripts/diagnose-scenes.mjs(默认只读):打印运行期真实连的库、
表清单、typeorm_migrations 记录、**代码实际加载的迁移**、scenes 实际列 vs 实体
期望列的逐列 diff;`--fix` 仅在缺表时用迁移里同一份 DDL 幂等补表。
顺带修正 models.controller.ts 的误导性注释:模型上传**不接受** isPrimary/status
(契约:上传一律 pending,默认模型由审核通过时带 setPrimary=true 或
POST /api/models/:id/primary 设定)。test/smoke.mjs 此前正是按错误预期断言
"isPrimary 已置位",已改为:上传断言 pending + isPrimary=false → 审核通过时带
setPrimary=true → 再断言 isPrimary=true;同时给目录导出/指纹等检查补上失败细节
(HTTP 状态 + 响应体),并把待审列表查询的 pageSize 放大到 200(避免库里待审项多
时新条目被挤出第一页)。
文档:README.md/.txt 增加 v0.7.3 修订说明;tasks/015-scene-management.md 增
「缺陷修复记录二」;开发文档 §4.12 补充硬性要求(场景不可用必须降级 + /health 暴露
+ showMigrations 的陷阱说明)。
测试:新增 test/scenes-degradation.spec.ts(6 项);npm run verify 通过
(契约六项一致、35 条路由、21 文件 / 276 项测试、nest build、启动冒烟);
发布产物解压后 npm ci --omit=dev + node dist/main.js 实测打印「应用版本 v0.7.3」。
注意:Gitea 附件上传通道仍故障(1 KB 也瞬时 500),因此本版**尚未上传 Release**,
产物先在本地交付(sha256 见 SHA256SUMS)。
|
2026-09-17 03:24:56 +08:00 |
|
zijunchiang
|
2433cd22a5
|
fix(boot): CatalogModule 漏 import ScenesModule 导致产物起不来 + /health 版本号错
两个都是**只有真正启动服务才会暴露**的缺陷,v0.7.0 / v0.7.1 的产物受影响。
1. 【致命】CatalogModule 漏 import ScenesModule
CatalogService 因为 catalog/export 要下发 scenes[] 而注入 ScenesService,但
CatalogModule 的 imports 只列了 SongsModule / MotionsModule / ModelsModule。
Nest 的 DI 按模块解析,于是 node dist/main.js 在 bootstrap 阶段直接抛
UnknownDependenciesException: Nest can't resolve dependencies of the
CatalogService (... ? ...) —— 服务完全起不来(装得上、起不来)。
为什么三层检查全绿:nest build/tsc 看不出运行时 DI;258 项单元测试直接 new
服务或测纯函数,从不编译 AppModule;打包脚本只校验文件清单与压缩包完整性。
修复:CatalogModule.imports 补上 ScenesModule。
2. /health 的 version 在原生部署下永远是 0.1.0
原实现 process.env.npm_package_version ?? '0.1.0',而该变量只在 npm 脚本下
存在;正式部署是原生服务(ExecStart=node dist/main.js),npm 不参与 ->
恒为 undefined -> 回退到硬编码 0.1.0,与管理端左下角展示的 vX.Y.Z 冲突,
违反文档 11.2 第 4 条「可核对:Web 端至少与 GET /health 的 version 一致」。
修复:新增 src/config/app-version.ts 作为运行时唯一解析点
(APP_VERSION -> 运行入口旁/工作目录的 package.json(带包名校验)
-> npm_package_version -> 显式 0.0.0),/health 改用 resolveAppVersion(),
main.ts 启动首行也打印版本号。新增 12 项测试,其中一项显式模拟无 npm
环境变量的原生部署。
防线(都挂进了 verify 与打包脚本)
- scripts/check-bootstrap.mjs:以正式部署方式(node dist/main.js、无 npm 环境
变量、不需要数据库)拉起真实产物,断言①无依赖解析类错误②启动日志的应用版本
等于 package.json ③已推进到数据库连接阶段。已做反向验证:删掉 ScenesModule
后该检查立刻失败。
- scripts/check-version.mjs(并入 contracts:check):校验
web-server/package.json = admin/package.json = project.yaml,正则禁止
health.controller.ts 再出现硬编码版本回退,管理端已构建时断言产物 JS 含 vX.Y.Z。
- npm run verify 现在包含 bootstrap:check;package-release.sh 在构建后、打包前
各跑一次(构建管理端后再跑 version:check,因为此时才需要校验产物内版本)。
文档:README.md / README.txt 增加 v0.7.2 修订说明;
tasks/011-version-display.md 与 tasks/015-scene-management.md 补「缺陷修复记录」
(含为什么当时没被发现的三层检查对照表)。
验证:npm run verify 通过(契约六项一致、35 条路由、258 项测试、nest build、
启动冒烟);产物 opendance-web-v0.7.2.tar.gz
sha256 f04ecc85d17517ec00754238616eb1237cdb2fe17440af6dce731827c0922b26。
|
2026-09-17 01:49:52 +08:00 |
|
zijunchiang
|
3738bd91b9
|
release(v0.7.1): 契约 websocket-keypoints 0.2.0 + 补齐场景规格与任务文档
契约变更(T-WEB-016,与 Android T-AND-011「两端一起改」配套)
- websocket-keypoints 0.1.0 -> 0.2.0,均为向后兼容(子协议名与二进制帧
version 均不变,只新增可选控制字段与一种新控制类型):
1.1 保活:接收端必须声明 silentTimeoutMs 与 heartbeatIntervalMs,且心跳必须
小于静默超时的一半。根因是客户端只能用 OkHttp 的协议层 ping,而它由
底层库自动应答、**不暴露给应用层**,接收端根本看不到,无法续命;
3.1 hello.deviceId(该安装实例的稳定标识,不使用任何硬件序列号);
3.2/3.3 welcome/config 携带保活字段;
3.6 应用层 ping/pong(回显 sentAtMs,pong 另带 serverAtMs 供算 RTT);
5 设备去重:同一 deviceId 只保留最新连接,旧连接以 1000 关闭
(刻意用 1000:对端据 §7 不会重连,否则形成"挤掉-重连-再挤掉"循环);
7 关闭码与重连策略:1000/1001/1002 与主动停止不重连,其余重连;
7.1 退避参数冻结:min(500ms*2^(n-1), 15000ms) + ±20% 抖动;
8 连接状态机(两端同名,日志可直接对照)。
- contracts/VERSION 与 src/config/contract-versions.ts 同步到 0.2.0。
补齐 rest-api.yaml 漏写的场景结构(只补规格,不改线格式,版本号不变)
- CatalogBundle 增加 scenes[](标注 0.5.0 起下发);
- 新增 Scene / SceneConfig / Vec3 schemas。catalog/export 自 v0.7.0 起就返回
scenes[],data-models 7 也已冻结,但规格漏写会让两端对字段边界
(如 0~1、±200)的理解不一致。
deploy/proxy/README.md 新增第九节:Gitea 站点的附件上传上限
- 与一~五节的 Web 服务反代是**两个不同站点**;实测 30.5 MB / 40 MB 上传返回
HTTP 413,根因是 IIS 默认 maxAllowedContentLength = 30,000,000 字节;
- 给出放宽到 256 MB 的 IIS / ARR / Gitea app.ini / nginx 四处配置与复验命令;
- 这解释了 Windows 端 v0.1.0 为何被拆成 zip.part0/1/2(交付缺陷)。
文档与脚本
- 补齐 tasks/011~016 与 project.yaml 条目(T-WEB-011~015 此前已在代码中落地
却缺任务说明书);project.yaml 的 contracts_version 由单个 0.4.0 改为六项字典;
- scripts/package-release.sh 的版本号改为从 web-server/package.json 读取
(与 GET /health 的 version、管理端左下角展示同源),并与 project.yaml 交叉校验;
同时补上 SHA256SUMS 与自校验(此前是手工生成的)。
验证:npm run verify 通过(contracts:check 六项一致 + 35 条路由一致、
typecheck、246 项测试、nest build);产物 opendance-web-v0.7.1.tar.gz
sha256 27f92cfb125bbcf29077d4ad90c44720cbca46d23ddef12cd7826d73bd8865e2。
|
2026-09-17 01:30:41 +08:00 |
|
opendance
|
f2e231c0b2
|
feat(admin): 场景管理页——列表/上传/物理参数编辑 + 3D 预览(T-WEB-015 补齐)
- 新增 admin/src/pages/Scenes.tsx(路由 /scenes + 侧栏「场景管理」入口):
* 列表:40px 缩略图(点击进详情)、物理摘要、体积、状态、软删除;
* 上传表单:GLB 文件 + 名称 + 来源五元组 + 物理参数(重力、风基础向量、
阵风幅度/频率、地面摩擦/坡度、碰撞体、环境光),multipart 走 POST /api/scenes;
* 详情:复用 VrmPreview(懒加载 three.js 分包)预览场景 GLB + 参数可视化编辑
并通过 PATCH /api/scenes/:id 保存;
- 新增 admin/src/lib/scene-physics.ts:DEFAULT_SCENE_CONFIG(与契约 7.1、服务端
scene-config.ts 单一真源,测试断言两端一致)与 physicsSummary();
- 场景页两栏自适应样式;types/endpoints 补齐场景五接口。
- 测试:场景用例增至 9 项;规模 19 文件 / 245 项。版本 0.7.0。
|
2026-09-17 00:32:05 +08:00 |
|
opendance
|
584b7eaca9
|
feat(scenes): 场景管理后端 + 契约 0.5.0/0.7.0/0.4.0(T-WEB-015)
契约先行(三端逐字节一致,已同步 windows/android):
- rest-api -> 0.5.0:新增 GET/POST /api/scenes、GET/PATCH/DELETE /api/scenes/{id}
- data-models -> 0.7.0:新增 §7 Scene 与 7.1 SceneConfig(物理/环境/出生点/相机)
- database -> 0.4.0:新增 scenes 表(config/stats 用 MEDIUMTEXT,迁移可逆)
后端:
- entities/scene.entity.ts + 迁移 AddScenes1789650000000(scenes 表)
- scenes/scene-config.ts:SceneConfig 定义 + 默认值 + **边界收敛**(越界夹紧、
非法枚举回退),保证客户端拿到的参数一定可解释;服务端只存参数不跑物理
- scenes/scenes.service.ts:上传(.glb/.gltf 直接导入;.zip/.pmx/.fbx 转 GLB,
场景不做 humanoid 重绑定、不转 VRM)、列表/详情/更新/软删除、缩略图复用模型
光栅化器、分析复用模型检查器、config 同时接受 JSON 字符串与平铺字段
- scenes/scenes.controller.ts + scenes.module.ts(app.module 注册)
- 审核复用 /api/reviews/:type/:id:TargetType.Scene 接入待审列表与状态流转
- catalog/export 增 scenes[],目录指纹纳入场景变更
- 枚举:TargetType.Scene、AssetKind.Scene / SceneThumbnail
测试:新增 test/scene-config.spec.ts 6 项(默认值/夹紧/枚举回退/出生点上限/颜色/
序列化);规模 19 文件 / 243 项;check-routes 35 条一致。版本 0.6.0。
注:管理端场景页(GLB 预览 + 物理参数可视化)与 Windows 场景选择留待下一轮。
|
2026-09-17 00:27:08 +08:00 |
|
opendance
|
aad60cbd2e
|
feat(motions): 动作预览——骨架缩略图 + 管理端 3D 回放(契约 data-models 0.6.0)
按《DSH 开发文档 v1.6》4.11(T-WEB-014,用户标注「重要」):
服务端:
- 抽出公共 PNG 编码器 src/common/png.ts(模型缩略图改为复用,零原生依赖不变)
- 新增 src/motions/motion-thumbnail.ts:关键点序列 -> 正视图骨架 PNG
(复用 33 点 PARENT_TABLE 连线,包围盒按整段计算,低置信度点不画)
- motions 增 thumbnail_asset_id(迁移 AddMotionThumbnail)+ AssetKind.MotionThumbnail
- /preprocess 生成缩略图并落库,失败只记 warning;MotionDto 增 thumbnailUrl(契约 0.6.0)
管理端:
- 新增 MotionPreview:懒加载 NDJSON -> 骨架线框 + 关节实例球体
支持播放/暂停、上一帧/下一帧、时间轴拖动、0.25x-2x 变速、循环
叠加帧号/当前时间/总时长,按整段包围盒自动取景
three.js 单独分包(lazy + Suspense),主包 241 kB 不涨
- 动作列表新增 40px 骨架缩略图列(点击开预览)+ 预览按钮 + 内联预览面板
- 新增 admin/src/lib/keypoint-sequence.ts 与 lib/skeleton-topology.ts
测试:新增 8 项;规模 18 文件 / 237 项。版本 0.5.0。
|
2026-09-17 00:21:09 +08:00 |
|
opendance
|
9eae6f3e43
|
feat(models): 骨骼重绑定——修复 MMD 模型 humanoid 恒为 0/22(契约 skeleton-topology 0.2.0)
根因:normalizeBoneName() 只保留 a-z0-9,**中日文字符被整个丢掉**:
'上半身'/'左ひじ'/'首' 归一化后都是空串,MMD 模型的骨名必然失配,
humanoid 覆盖率恒为 0/22 -> 不可驱动 -> 只能回退交付 GLB 并标记不可用。
修复:
1. 归一化保留 CJK/平假名/片假名/长音符/々,并把全角 ASCII(足IK)折成半角;
2. 候选表补齐四类命名(22 根 x 8-13 个候选):VRoid / Mixamo /
MMD 标准骨架(下半身/上半身/上半身2/首/頭/左肩/左腕/左ひじ/左手首/
左足/左ひざ/左足首/左つま先)/ 常见中文名(臀部/头/脖子/左大臂/左大腿…);
精确匹配保证「左足IK」不会抢占「左足」;
3. Windows 客户端 humanoid_map.gd 的 CANDIDATES 由服务端表**生成写入**,
normalize() 同步改造,parity 测试断言键/候选顺序完全一致;
4. 契约 skeleton-topology -> 0.2.0:新增 §6「内部动作标准与模型骨骼重绑定」
(三层标准、22/16 根骨、归一化与匹配规则、实测记录);
5. 新增 4 项测试;规模 17 文件 / 229 项。
实测:奥黛塔 PMX 0/22 -> 21/22、初音未来 PMX 0/22 -> 19/22,均 drivable=true 且产出 VRM。
版本 0.4.0。
|
2026-09-17 00:10:07 +08:00 |
|
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 |
|
opendance
|
eebccd55b4
|
fix(models): MMD 材质对齐——补回双面标志、规范化 PBR 参数、可选 unlit 着色
背景(用户反馈 + 本地用真 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)。
|
2026-09-16 18:55:25 +08:00 |
|
opendance
|
ae8a68e98f
|
fix(thumbnail): 缩略图采样 baseColorTexture,修复 MMD 模型显示为白模
现象(用户反馈):导入 PMX 转换出的 GLB 后,列表页/审核面板里的模型是白模,
脸部颜色信息全部丢失。
定位过程(本地装真 Assimp 6.0.4 + 该模型完整复现):
1. 转换本身正常:23.7MB GLB、643 骨骼、93,350 面;
2. 18 张贴图逐字节与压缩包原图一致;用一个权威 PMX 解析器比对后确认
39 个材质 -> 贴图的对应关系与 PMX 完全一致(不是映射错);
3. UV 采样也正确:脸部材质取到的就是皮肤色 (246,221,216);
4. 真正的问题在**我们自己的缩略图渲染器**:它刻意不解码 baseColorTexture,
只用 baseColorFactor 上色(见文件头取舍说明);而 MMD 模型的
baseColorFactor 基本都是白色 -> 缩略图必然全白。管理端 3D 预览有贴图,
只有列表/审核的缩略图是白的。
修复:
1. model-thumbnail.ts 内置极简 PNG 解码器(zlib inflate + 逐行反滤波,
8 位非隔行,colorType 0/2/3/4/6),按 baseColorFactor x 贴图逐像素采样,
透视校正 UV;解码失败/非 PNG/隔行扫描回退纯色,绝不抛错;
2. 按 alphaMode 处理透明度:OPAQUE 忽略贴图 alpha(MMD 语义)、
MASK 按 alphaCutoff 丢弃、BLEND 做 source-over;
3. 取景收紧 FRAMING 1.6 -> 1.3,40px 列表缩略图主体更清楚;
4. 新增 test/model-thumbnail-texture.spec.ts 6 项(含白模回归:
baseColorFactor 全白 + 红贴图必须输出偏红,以及 MASK/OPAQUE/坏图回退);
5. 版本 0.2.4,README(.md/.txt)/DSH.md 补修订说明与 FAQ(含重新生成缩略图
的 reanalyze-models.mjs --force 命令)。
|
2026-09-16 04:02:35 +08:00 |
|
opendance
|
8e8cc0475b
|
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 容量约束。
|
2026-09-16 03:30:12 +08:00 |
|
opendance
|
da03777995
|
fix(models): Assimp 缺失时给出可操作报错,部署脚本自动安装
现象:服务器未安装 Assimp,上传 PMX 压缩包只得到
GLB 直出:导出失败(ENOENT):spawn assimp ENOENT
glTF 直出:导出失败(ENOENT):spawn assimp ENOENT
用户看不出缺什么、怎么装,也不知道只传 glb/vrm 其实不需要它。
修复:
1. 新增 src/common/assimp.ts:按 ASSIMP_BIN -> PATH -> /usr/bin、/usr/local/bin
等常见目录定位可执行文件(只认文件,不认同名目录),转换前先预检;
找不到就一次性给出各发行版安装命令、查过的路径与替代办法,
不再打两条 ENOENT。动作预处理(FBX/glTF -> BVH)复用同一文案。
2. 新增 deploy/assimp-lib.sh:install.sh 按发行版自动安装
(apt-get / dnf / yum / zypper / apk / pacman),装在非常规目录时把绝对
路径写入 deploy/.env 的 ASSIMP_BIN;装不上只告警不中止,可用
SKIP_ASSIMP_INSTALL=1 跳过。check-access.sh 自检增加 Assimp 一行。
3. /health 增加 assimp.path;管理端「概览 -> 服务健康」显示 Assimp 状态,
不可用时给出安装提示。
4. 新增 test/assimp-locate.spec.ts 13 项;强化「assimp 不存在」用例断言安装命令。
测试规模 12 文件 / 193 项。
5. 版本 0.2.2,README(.md/.txt)/DSH.md 同步(前置要求、FAQ、环境变量说明)。
|
2026-09-16 02:55:32 +08:00 |
|
opendance
|
ea58520fc2
|
fix(deploy): .env 中带空格的值未加引号导致 install.sh 以 127 失败
现象:
./deploy/.env: line 53: Miku.pmx: command not found
[install] 第 53 行执行失败(退出码 127)
根因:deploy/.env.example 的 MODEL_CONVERT_PMX=Hatsune Miku.pmx 值含空格
却没加引号,install.sh 用 `set -a; . deploy/.env` 载入时被切成
「赋值 + 命令」,Miku.pmx 被当成命令执行。
修复:
1. 模板改为 MODEL_CONVERT_PMX="Hatsune Miku.pmx"(占位符值同样加引号);
2. 新增 deploy/env-lib.sh:.env 语法预检,install.sh / ctl.sh 在 source
之前检查,报错给出「哪一行 / 原文 / 改法」, 与反引号建议单引号;
3. install.sh 的 ERR trap 补上出错文件名(lineno 属于被 source 的 .env,
只报行号无法定位);
4. 新增 test/env-file.spec.ts 12 项回归测试,锁住「模板可被 shell 原样载入」;
5. 版本升到 0.2.1,README(.md/.txt)/DSH.md 补 .env 写法约定、FAQ 与测试数量。
|
2026-09-15 22:26:58 +08:00 |
|
opendance
|
60c182f1b2
|
docs: 消除 Gitea 模棱两可 Unicode 告警(全角标点紧邻 ASCII 时改为半角)
依据 Gitea v1.27.3 modules/charset/escape_stream.go 的判定规则
(analyzeDetectResults:仅当左右相邻字符都不是非 ASCII 时才标记,
且 _common 表恒定生效),把说明文档中会被标记的全角标点
(,:;?!() 等)改为半角,并保证成对括号宽度一致。
中文语境中的全角标点(左右均为中文)保持原样,不动代码与契约语义。
同时删除临时探针目录 _probe/(仅用于确认 Gitea 判定规则)。
|
2026-09-15 21:45:51 +08:00 |
|
zijunchiang
|
ebd871018f
|
docs(readme): 新增初次部署清单与常见问题;统一 root 执行写法;清除 markdown 粗体标记与残留真实库名
|
2026-09-15 21:27:29 +08:00 |
|
zijunchiang
|
9f08d316d0
|
docs: 清除文档中的真实部署信息与易混淆 Unicode 字符
需求:公开仓库的说明文档不应暴露开发/部署期信息,且 Gitea 会对 README.txt
报"含模棱两可的 Unicode 字符"。
敏感信息(全部替换为占位符或文档示例值):
- 真实数据库实例:DB_HOST=192.168.150.10 / DB_USER=dpendance / DB_NAME=dpendance
→ <数据库主机> / opendance_app / opendance(web-server/.env.example、
deploy/.env.example、contracts/database.md ×3 库、project.yaml、README.txt
的建库 SQL、tasks/001、tasks/002)
- 内网网段 192.168.150.0/24 / 192.168.150.20 / 192.168.1.10
→ RFC 5737 文档网段 192.0.2.0/24 等(含测试夹具,行为不变)
- 真实 Gitea 域名 → https://<Gitea 地址>/<owner>/<repo>(systemd 单元、
Windows/Android README 的交叉链接)
- 测试夹具里的真实口令字面量 → 虚构口令(仍覆盖特殊字符场景)
- 保留 /opt/opendance、/var/lib/opendance 等产品默认安装路径(非敏感)
README.txt 易混淆字符:
- 全角拉丁字母(I/K 等)与全角 ASCII 标点((),:;?)→ 半角 + 中文排版空格
- 正文里的乱码字节样例(模å…)→ 改为文字描述
- × → x,★ → *
- 注意:代码/测试/契约里的全角「IK」是 MMD 真实骨骼名,**必须保留**,
已回滚误改(mmd-skeleton.ts、mmd-vmd.spec.ts、inspect-vmd.mjs、各契约)
验证:168 项单测通过(含 VMD 31 项,确认全角骨骼名未被破坏)。
|
2026-09-15 21:25:25 +08:00 |
|
zijunchiang
|
53bf182476
|
feat(models): 发布 v0.2.0 —— GLB/VRM 主力格式、自动缩略图与分析、模型审核与骨骼预览
需求 1:系统确定以 GLB / VRM 为模型主力格式
- 服务端新增支持库:
- model-inspector.ts:解析 GLB/VRM 的面数、顶点、材质、贴图、形变目标、
**骨骼树(名称/层级/是否蒙皮/humanoid 映射/子节点数)**、humanoid 覆盖率与可驱动性
- model-thumbnail.ts:**纯 TS 软件光栅化**(z-buffer + Lambert + 完整蒙皮矩阵)
生成 512×512 PNG 缩略图,零原生依赖、不装 headless-gl
- vrm-writer.ts:GLB → VRM 1.0(写入 VRMC_vrm 扩展与 humanoid 映射)
- gltf-packer.ts:glTF(.gltf + .bin + 贴图)→ 单文件 GLB
- humanoid 候选表与 Godot 客户端 humanoid_map.gd **逐字对齐**,并有单测断言两边一致
(服务端判定的"可驱动" = 客户端实际能否驱动)
需求 2:模型管理
- .glb / .vrm / .gltf **直接导入**(不重编码,GLB 含 VRM 扩展会自动识别为 VRM)
- .pmx / .fbx 服务端转换:assimp → .glb →(失败)→ .gltf 后自行打包成 GLB
→(MODEL_CONVERT_FORMAT=vrm 时)补 VRMC_vrm 产出 VRM 1.0;每步 assimp info 回读校验
- .zip **自动搜索包内可用模型**:.vrm → .glb → .gltf(直接导入)优先,
其次 .pmx → .fbx(转换);同优先级内按"指名字 → 目录最浅 → 文件最大"
- 入库后自动分析(models.stats)与生成缩略图(models.thumbnail_asset_id),
失败均不阻断入库;管理端列表显示小缩略图、预览放开到 vrm/glb/gltf
需求 3:审核功能
- 上传的模型状态**一律 pending**(不再自动发布、不再自动设默认)
- 审核界面新增模型面板:体积、面数、顶点、材质、贴图、骨骼、形变目标等指标,
humanoid 覆盖进度、可驱动性结论(缺哪些必需骨骼)、warnings,以及
**骨骼详细预览**(层级缩进、蒙皮/Humanoid 徽章、子节点数、搜索保留祖先链、
仅看蒙皮、限高滚动 + 只渲染前 400 行)
- 审核通过可带 setPrimary=true 一并设为默认模型(此时才写源码种子);
POST /api/models/:id/primary 仅允许 published(否则 409)
- scripts/import-default-model.mjs 改为"上传 → 审核通过 + 设默认"流程
其它:
- models 新增 stats 列(迁移 AddModelStats1789460000000,已在真实库执行)
- 契约升至 rest-api/data-models/model-formats 0.4.0、database 0.3.0(另两库已同步)
- 版本号 0.1.0 → 0.2.0(package.json ×2 + lockfile + project.yaml)
- 文档:README.txt / DSH.md / project.yaml / tasks 005·007·010 全面更新;
顶层开发书更新为 v1.4
验证:168 项单测通过(10 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实端到端:MMD 压缩包 → GLB + 分析(面数 9071/骨骼 238/humanoid 0-22) + 缩略图(15KB PNG);
GLB 直接导入 sha256 与源文件一致;审核通过后 defaultModelId 由 VRM 变为该模型
|
2026-09-15 20:37:53 +08:00 |
|
zijunchiang
|
e4351c5062
|
feat(models): 模型压缩包自动转换 + 默认模型写进源码;首次启动 .env 引导;HOST 默认 0.0.0.0
需求 1:模型上传支持压缩包(MMD 模型包)
- common/zip-reader.ts:纯 TS 极简 ZIP 读取器(EOCD/中央目录、STORE+DEFLATE、
ZIP64 大小字段、Shift-JIS/GBK 文件名回退、拒绝 zip-slip 与加密条目)
- models/model-convert.ts:解压 → 选主模型(指名字/最浅/最大)→ Assimp 导出 FBX
→ **assimp info 回读校验**(Assimp 对带蒙皮大骨架模型会写出损坏文件:
实测 714 骨骼 PMX 导出退出码 11、产物回读报 FBX-DOM no Objects dictionary found)
→ 失败自动回退 GLB;两种都失败则 422
- models/glb-textures.ts:把 Assimp 写成外部 URI 的贴图内嵌进 GLB 的 BIN chunk,
保证模型库里单文件即可加载(否则是白模)
- 溯源:压缩包原件存为 model-archive 资产;models 新增 source_asset_id /
conversion_note(迁移 1789450000000-AddModelConversionFields,已在真实库上
验证 up/down 可逆)
- 默认模型:转换成功且 autoDefault 不为 false → is_primary=1 + published,
并把身份(名称/格式/产物 sha256)写进源码 src/config/default-model.ts;
resolveDefaultModelId 增加 DEFAULT_MODEL_ID 与源码种子两步(按 sha256 匹配,
换库仍有效),实测库中有 2 个 VRM 时默认模型仍解析为该 GLB
- scripts/import-default-model.mjs:运维 CLI(上传 zip 并写源码 / --write-source-only)
- 修复 multipart 中文文件名乱码(upload.util.decodeUploadName)
- DTO/契约新增 conversionNote、fileSha256;管理端 accept 增加 .zip 并展示转换来源
需求 2:首次启动 .env 引导
- deploy/init-env.sh:交互式逐项填写 8 个键(回车保留默认、口令不回显不加引号、
端口校验、mysql2 连通性自检且口令只经环境变量传递、只改这 8 个键并保留模板注释、
chmod 600、结尾打码摘要);--force/--check/-y/-i/--skip-db-check 等开关;
非交互(-y、OPENDANCE_NONINTERACTIVE、stdin 关闭)绝不提问、读取带超时
- install.sh 缺 .env 时改为调用引导,失败才 die
需求 3:默认监听 0.0.0.0
- 代码默认值、两份 .env 模板、install.sh、init-env.sh 五处一致,并新增测试守护
(未设置 HOST 时为 0.0.0.0;两份模板都写着 HOST=0.0.0.0)
契约:rest-api / data-models / model-formats → 0.3.0,database → 0.2.0(另两库已同步)
验证:125 项单测通过(7 文件);契约 6 项与 29 条路由一致;typecheck/build 通过;
真实包端到端 HTTP 201 / 0.89s / format=glb / isPrimary=true / published;
GLB 1,152,464 字节、1 mesh / 240 nodes / 1 skin(238 joints) / 2 张内嵌贴图;
迁移 revert→run 实测可逆且数据无损恢复
|
2026-09-15 13:55:54 +08:00 |
|
zijunchiang
|
bba5bcf59e
|
feat(web): 端口分离 + VMD 动作支持;契约升至 0.2.0
需求 1:管理端端口与软件 API 端口分离
- 软件 API 端口 PORT(默认 3000)只提供 /api、/health、/static、/contracts,
不提供管理页面(未命中路径 404);管理端端口 ADMIN_PORT(默认 3001)
提供管理页面与全部接口;ADMIN_HOST 可单独限制管理端来源
- main.ts 同进程监听两个端口,用 req.socket.localPort 区分端口行为;
ADMIN_PORT=PORT 为单端口兼容模式(只创建一个 server,无 EADDRINUSE)
- 端口非法在生产环境中止启动;ADMIN_PORT=PORT 只 warn
- 启动日志分别打印两个端口的访问地址
需求 2:动作数据支持 VMD(MikuMikuDance)
- vmd-parser.ts:二进制解析(30 字节头 + Shift-JIS 模型名 + 111 字节/条骨骼
关键帧 + morph 段)、MMD 贝塞尔插值、GBK 模型名回退、固定 30fps
- mmd-skeleton.ts:标准 MMD 骨架;静止比例从参考 PMX 骨骼段实测得到
(足首 1.43 / 膝 6.57 / 髋 11.50 / 首 16.21 / 頭 17.16,腿长 10.09)
- mmd-pose.ts:FK + 两骨骼解析腿部 IK(膝盖朝正面,不可达收敛为伸直)
+ 左手系→右手系 Z 镜像(由真实数据对照实验确定:94.1% vs 5.9%)
- retarget.ts:抽出 worldFramesToKeypointFrames(),BVH 与 VMD 共用重定向
- MotionFormat 增 vmd、扩展名映射增 .vmd;管理端上传支持 .vmd
- scripts/inspect-vmd.mjs:上传前自查工具
契约:rest-api / data-models / model-formats → 0.2.0(另两库已同步逐字节一致)
验证:85 项单测通过(VMD 31 项);契约与 29 条路由一致;typecheck/build 通过;
真实样本经 HTTP 端到端:霜雪千年 2901 帧(IK 腿)、恋爱循环 4604 帧(FK 腿)
均 preprocessState=done、0 NaN、0 越界(测试数据已从数据库清理)。
|
2026-09-15 08:47:37 +08:00 |
|
zijunchiang
|
8c76b0a599
|
feat(web): 移除 Docker 与反代部署,改为 Linux 业务服务并自带管理端托管
需求变更:Web 端只保留业务需求,反向代理交由另外的服务处理,
本项目仅提供 IIS / nginx 反代说明文档,不部署反代。
主要改动:
- web-server: 新增管理端静态托管(ADMIN_DIR、SPA 深链接回退),
反代因此只需一条全匹配转发规则;新增 adminDir 配置项
- deploy/install.sh: 删除 nginx 安装与站点配置逻辑,改为原生 Linux
服务部署(systemd → OpenRC → SysVinit → 后台进程四级降级),
打印反代交接指引
- deploy/ctl.sh: 启停/状态/日志脚本
- deploy/proxy/: 新增反代说明文档(README.md、nginx.conf.example、
web.config.example),标注上传上限与超时必须同步调整
- 删除 deploy/nginx/、web-server/admin/nginx.conf、Dockerfile、
docker-compose.yml、.dockerignore
- scripts/package-release.sh: 锚定排除规则 + 产物完整性校验
- 文档同步:README.txt、DSH.md、project.yaml、tasks/008、tasks/010
|
2026-09-15 01:25:19 +08:00 |
|
zijunchiang
|
32525dd59a
|
docs(web): 清理残留的 Docker 引用(admin README / project.yaml / 注释)
|
2026-09-14 22:47:47 +08:00 |
|
zijunchiang
|
75498db1c0
|
feat(web): 部署方式由 Docker 改为 Linux 原生站点(nginx + systemd)
需求变更:Web 服务端不再使用 Docker,改为直接运行在 Linux 上的网站。
- 移除 deploy/docker-compose.yml、web-server/Dockerfile、
web-server/admin/Dockerfile、web-server/.dockerignore
- 新增 deploy/install.sh:建服务用户与目录 → 装运行期依赖 → 迁移
→ 注册 systemd 服务 → 配置 nginx 站点 → 健康检查,一键完成
- 新增 deploy/systemd/opendance-api.service:ProtectSystem=strict 等
安全加固,ReadWritePaths 限定为上传目录
- 新增 deploy/nginx/opendance.conf:同站点托管管理端静态资源 +
反代 /api /health /static,client_max_body_size 512m,SPA 回退
- 新增 deploy/uninstall.sh:默认保留数据,--purge 才删除
- 新增配置项 HOST(AppConfig.bindHost):原生部署下 Node 只监听
127.0.0.1,不直接对外暴露,实测外部 IP 不可达
- 同步更新 contracts/rest-api.yaml、DSH.md、project.yaml、README.txt、
tasks/008、tasks/010 与发布打包脚本
|
2026-09-14 22:42:56 +08:00 |
|
zijunchiang
|
640736c77b
|
feat(web): T-WEB-008/009 管理端界面(歌曲/模型/动作/审核 + VRM 预览)
|
2026-09-14 19:00:55 +08:00 |
|