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 越界(测试数据已从数据库清理)。
This commit is contained in:
2026-09-15 08:47:37 +08:00
parent 76725e5ce3
commit bba5bcf59e
35 changed files with 2658 additions and 192 deletions
+57 -13
View File
@@ -18,13 +18,19 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
只在 deploy/proxy/ 下提供 IIS 与 nginx 的配置说明文档。
部署形态
Node 业务服务由系统服务托管,同时提供:
/ 管理端页面(本服务自带,含 SPA 深链接回退)
Node 业务服务由系统服务托管,同时监听**两个端口**,职责分开:
软件 API 端口(默认 3000,给 Windows/Godot 客户端与 Android App)
/api/ REST API
/health 健康检查
/static/ 上传文件
/contracts/ 契约快照
因为静态页面也由本服务提供,反向代理**只需一条全匹配转发规则**。
该端口**不提供管理页面**,/ 与 /songs 等一律返回 404(有意设计)
管理端端口(默认 3001,给浏览器)
管理页面(本服务自带,含 SPA 深链接回退)+ 上面全部接口
反向代理按用途分别转发两个端口,每个端口各一条全匹配转发规则即可。
前置要求
- Linux 服务器,有 root 权限(不需要 sudo,直接以 root 执行即可)
@@ -56,18 +62,23 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
注册并启动系统服务(systemd / OpenRC / SysVinit / 后台进程)→ 等待健康检查
部署完成后(直接访问后端端口,<host> 为服务器 IP)
管理端首页: http://<host>:3000/
API 前缀: http://<host>:3000/api
客户端接口: http://<host>:3000/api
健康检查: http://<host>:3000/health
契约快照: http://<host>:3000/contracts/
管理端首页: http://<host>:3001/
(软件 API 端口上没有管理页面:http://<host>:3000/ 返回 404,属正常)
对外访问
由反向代理把请求转发到 http://<后端地址>:3000 即可。
配置样例与说明:
由反向代理**按用途分别转发两个端口**:
浏览器管理界面 → http://<后端地址>:3001
Windows/Godot 客户端与 Android App → http://<后端地址>:3000
配置样例与说明(含两个端口的转发写法):
deploy/proxy/nginx.conf.example nginx 站点配置
deploy/proxy/web.config.example IIS(ARR + URL Rewrite)配置
deploy/proxy/README.md 完整说明、自检清单与常见故障
直连后端时,防火墙需同时放行两个端口(默认 3000 与 3001)。
外部访问不了?安装脚本结尾会直接给出结论(监听地址、对外可达性、
防火墙提示);也可以在服务器上跑一次自检脚本(只读,不改配置):
bash deploy/check-access.sh # 或 bash deploy/check-access.sh /opt/opendance
@@ -78,6 +89,8 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
APP_DIR=/opt/opendance 程序目录
SERVICE_USER=opendance 运行服务的系统用户
STORAGE_DIR=/var/lib/opendance/storage 上传文件目录
PORT=3000 软件 API 端口
ADMIN_PORT=3001 管理端端口(默认 PORT + 1)
例:APP_DIR=/srv/opendance bash deploy/install.sh
@@ -118,7 +131,7 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
cp .env.example .env # 填写 DB_*
npm install
npm run migration:run # 首次需要
npm run start:dev # http://localhost:3000
npm run start:dev # API http://localhost:3000;管理端 http://localhost:3001
管理前端
cd web-server/admin
@@ -132,7 +145,7 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
cd web-server
npm run contracts:check # 契约版本一致性
npm run typecheck # TypeScript 严格检查
npm run test # 31 项单元测试
npm run test # 85 项单元测试(含 31 项 VMD)
npm run build # 生产构建
npm run start:prod & # 启动
npm run test:smoke # 57 项端到端冒烟(需要数据库可连通)
@@ -151,9 +164,18 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
所有表结构变更通过 TypeORM migration 执行。
DB_LOGGING 是否打印 SQL(默认 false)
PORT API 监听端口(默认 3000)
HOST 监听地址。反代与后端同机用 127.0.0.1(推荐);
反代在别的机器用 0.0.0.0,并用防火墙限制来源
PORT 软件 API 监听端口(默认 3000)。给 Windows/Godot 客户端
与 Android App 调用,只提供 /api、/health、/static、
/contracts,**不提供管理页面**
ADMIN_PORT 管理端端口(默认 PORT + 1,即 3001)。浏览器访问管理界面
(管理页面 + 上面全部接口);设为与 PORT 相同即为
单端口兼容模式,日志会提示「未分端口」
HOST 监听地址(软件 API 端口)。反代与后端同机用 127.0.0.1
(推荐);反代在别的机器用 0.0.0.0,并用防火墙限制来源
ADMIN_HOST 管理端监听地址(可选,默认跟随 HOST)。需要接口对外、
管理端只对本机时设为 127.0.0.1
端口配置非法(非 1..65535 整数)时,生产环境启动会直接中止;
ADMIN_PORT 设为与 PORT 相同只打印提示,不中止。
ADMIN_DIR 管理端静态站点目录(默认 ../admin,即 /opt/opendance/admin)
STORAGE_DIR 上传文件存储目录(原生部署默认 /var/lib/opendance/storage)
UPLOAD_MAX_BYTES 单文件上限,默认 536870912(512 MiB)。
@@ -177,7 +199,7 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
POST /api/songs/:id/audio 上传音频
GET /api/motions 动作列表
POST /api/motions 上传动作 + 来源信息
POST /api/motions/:id/preprocess BVH/FBX → MediaPipe33 关键点
POST /api/motions/:id/preprocess BVH/VMD/FBX → MediaPipe33 关键点
GET /api/motions/:id/keypoints 下载关键点序列(NDJSON)
GET /api/models 模型列表
POST /api/models 上传模型 + 来源信息(VRM 优先)
@@ -193,6 +215,28 @@ OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
上传文件通过 /static/<kind>/<yyyy>/<mm>/<sha256>.ext 访问,
相同内容只落盘一次(sha256 去重)。
支持的动作格式(POST /api/motions 上传 → POST /api/motions/:id/preprocess 预处理)
BVH .bvh 内置纯 TS 解析器,保留源帧率
VMD .vmd 内置解析(MikuMikuDance),固定 30fps;
腿部按「足IK是否带位移」自动选 IK 或 FK
FBX .fbx 需可选的 Assimp 先转 BVH
glTF / GLB .gltf / .glb 同上
关键点 JSON .json 已是 MediaPipe33 结构,只做校验与规范化
VMD 动作上传前后自查
服务端对 .vmd 是内置解析,不依赖 Assimp。上传前可先在 web-server 目录下
跑诊断工具(只读:不写数据库、不访问网络),确认骨骼名能识别、腿走 IK
还是 FK、关键点序列无异常:
cd web-server
npm run build
node scripts/inspect-vmd.mjs 动作.vmd
node scripts/inspect-vmd.mjs 动作.vmd --frames 300 # 只采样前 300 帧
node scripts/inspect-vmd.mjs 动作.vmd --json out.json # 导出关键点序列
它会打印头部 / 模型名 / 帧数 / 轨道数、未识别骨骼、走 IK 还是 FK、关键点
序列异常统计与膝肘角分布。上传后再调用 /preprocess 走正式通路。
----------------------------------------------------------------
六、目录结构
----------------------------------------------------------------