需求 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 越界(测试数据已从数据库清理)。
269 lines
13 KiB
Plaintext
269 lines
13 KiB
Plaintext
================================================================
|
||
OpenDance Studio —— Web 管理服务端 v0.1.0 使用说明
|
||
================================================================
|
||
|
||
本服务端是 OpenDance Studio 三端中的第一端,负责管理歌曲、VRM 模型与
|
||
动作来源,是 Windows 客户端与 Android 辅助 App 的唯一数据源与契约权威。
|
||
|
||
重要:本服务端 **不部署、不管理 MySQL**。数据库为部署方提供的现有实例,
|
||
连接信息通过 .env 注入。
|
||
|
||
----------------------------------------------------------------
|
||
一、部署到 Linux 服务器(业务服务,不含反向代理)
|
||
----------------------------------------------------------------
|
||
|
||
职责边界
|
||
本端**只提供业务服务**:NestJS API + 管理端页面。
|
||
**反向代理由另外的服务/团队负责**,本项目不安装、不配置任何反代软件,
|
||
只在 deploy/proxy/ 下提供 IIS 与 nginx 的配置说明文档。
|
||
|
||
部署形态
|
||
Node 业务服务由系统服务托管,同时监听**两个端口**,职责分开:
|
||
|
||
软件 API 端口(默认 3000,给 Windows/Godot 客户端与 Android App)
|
||
/api/ REST API
|
||
/health 健康检查
|
||
/static/ 上传文件
|
||
/contracts/ 契约快照
|
||
该端口**不提供管理页面**,/ 与 /songs 等一律返回 404(有意设计)
|
||
|
||
管理端端口(默认 3001,给浏览器)
|
||
管理页面(本服务自带,含 SPA 深链接回退)+ 上面全部接口
|
||
|
||
反向代理按用途分别转发两个端口,每个端口各一条全匹配转发规则即可。
|
||
|
||
前置要求
|
||
- Linux 服务器,有 root 权限(不需要 sudo,直接以 root 执行即可)
|
||
- Node.js 22 或更高版本
|
||
- 进程管理:systemd / OpenRC / SysVinit 均可;**三者都没有也能装**
|
||
(自动降级为 nohup 后台进程 + PID 文件 + crontab @reboot 开机自启)
|
||
- 反向代理:由另外的服务提供,本项目不要求、也不安装
|
||
- 一个可连通的现有 MySQL / MariaDB 实例,并已创建数据库与账号:
|
||
|
||
CREATE DATABASE IF NOT EXISTS dpendance
|
||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
CREATE USER IF NOT EXISTS 'dpendance'@'%' IDENTIFIED BY '<密码>';
|
||
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, REFERENCES
|
||
ON dpendance.* TO 'dpendance'@'%';
|
||
FLUSH PRIVILEGES;
|
||
|
||
(本项目不会创建数据库本身,也不需要 GRANT OPTION / FILE / SUPER 等管理权限)
|
||
|
||
部署步骤
|
||
tar -xzf opendance-web-v0.1.0.tar.gz
|
||
cd opendance-web-v0.1.0
|
||
cp deploy/.env.example deploy/.env
|
||
vi deploy/.env # 填写 DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME
|
||
sudo bash deploy/install.sh
|
||
|
||
脚本会依次:
|
||
校验环境与 .env → 创建服务用户与目录 → 复制程序文件 →
|
||
安装运行期依赖 → 执行 TypeORM 迁移 →
|
||
注册并启动系统服务(systemd / OpenRC / SysVinit / 后台进程)→ 等待健康检查
|
||
|
||
部署完成后(直接访问后端端口,<host> 为服务器 IP)
|
||
客户端接口: http://<host>:3000/api
|
||
健康检查: http://<host>:3000/health
|
||
契约快照: http://<host>:3000/contracts/
|
||
管理端首页: http://<host>:3001/
|
||
(软件 API 端口上没有管理页面:http://<host>:3000/ 返回 404,属正常)
|
||
|
||
对外访问
|
||
由反向代理**按用途分别转发两个端口**:
|
||
浏览器管理界面 → 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
|
||
它会打印 .env 里的 HOST/PORT、实际监听地址、回环与对外 IP 自测结果、
|
||
80/443 是否有反代、防火墙放行情况,并直接给出修复命令。
|
||
|
||
可覆盖的安装参数
|
||
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
|
||
|
||
升级
|
||
解压新版本后重新执行 sudo bash deploy/install.sh 即可(会覆盖程序文件,
|
||
保留 deploy/.env 与上传数据)。
|
||
|
||
卸载
|
||
sudo bash deploy/uninstall.sh # 停服务、禁用站点,保留数据
|
||
sudo bash deploy/uninstall.sh --purge # 一并删除程序目录与上传文件
|
||
|
||
数据库始终由部署方自行管理,卸载脚本不做任何改动。
|
||
|
||
常用命令(按安装时自动检测到的初始化系统而定)
|
||
systemd:
|
||
systemctl status opendance-api # 服务状态
|
||
journalctl -u opendance-api -f # 实时日志
|
||
systemctl restart opendance-api # 重启
|
||
|
||
OpenRC:
|
||
rc-service opendance-api status / restart
|
||
tail -f /var/log/opendance-api.log
|
||
|
||
SysVinit:
|
||
/etc/init.d/opendance-api status / restart
|
||
|
||
无 init 系统(降级模式):
|
||
bash /opt/opendance/deploy/ctl.sh status | restart | logs
|
||
|
||
反代配置:不在本机,无需在此维护
|
||
|
||
----------------------------------------------------------------
|
||
二、本地开发
|
||
----------------------------------------------------------------
|
||
|
||
后端
|
||
cd web-server
|
||
cp .env.example .env # 填写 DB_*
|
||
npm install
|
||
npm run migration:run # 首次需要
|
||
npm run start:dev # API http://localhost:3000;管理端 http://localhost:3001
|
||
|
||
管理前端
|
||
cd web-server/admin
|
||
npm install
|
||
npm run dev # http://localhost:5173(/api 与 /health 代理到 3000)
|
||
|
||
----------------------------------------------------------------
|
||
三、验证
|
||
----------------------------------------------------------------
|
||
|
||
cd web-server
|
||
npm run contracts:check # 契约版本一致性
|
||
npm run typecheck # TypeScript 严格检查
|
||
npm run test # 85 项单元测试(含 31 项 VMD)
|
||
npm run build # 生产构建
|
||
npm run start:prod & # 启动
|
||
npm run test:smoke # 57 项端到端冒烟(需要数据库可连通)
|
||
|
||
cd ../admin && npm run build # 管理端构建
|
||
|
||
----------------------------------------------------------------
|
||
四、环境变量
|
||
----------------------------------------------------------------
|
||
|
||
DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME
|
||
现有 MySQL 实例连接信息(必填)
|
||
|
||
DB_SYNCHRONIZE
|
||
必须为 false。生产环境启动时若检测到 true 会直接中止启动。
|
||
所有表结构变更通过 TypeORM migration 执行。
|
||
|
||
DB_LOGGING 是否打印 SQL(默认 false)
|
||
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)。
|
||
★ 反向代理层的同名限制必须不小于此值,否则大模型上传
|
||
会在代理层被拒(nginx client_max_body_size /
|
||
IIS requestLimits),详见 deploy/proxy/README.md
|
||
ASSIMP_BIN 可选:Assimp 可执行文件,FBX/glTF 动作预处理需要
|
||
CONTRACTS_DIR 可选:契约目录,设置后 /health 反映实时契约版本
|
||
(原生部署默认指向 /opt/opendance/contracts)
|
||
|
||
----------------------------------------------------------------
|
||
五、主要 API
|
||
----------------------------------------------------------------
|
||
|
||
GET /health 健康检查(含数据库连通性)
|
||
GET /api/songs 曲目列表(分页 / 检索)
|
||
POST /api/songs 新建曲目
|
||
GET /api/songs/:id 曲目详情
|
||
PATCH /api/songs/:id 更新曲目
|
||
DELETE /api/songs/:id 删除曲目(软删除)
|
||
POST /api/songs/:id/audio 上传音频
|
||
GET /api/motions 动作列表
|
||
POST /api/motions 上传动作 + 来源信息
|
||
POST /api/motions/:id/preprocess BVH/VMD/FBX → MediaPipe33 关键点
|
||
GET /api/motions/:id/keypoints 下载关键点序列(NDJSON)
|
||
GET /api/models 模型列表
|
||
POST /api/models 上传模型 + 来源信息(VRM 优先)
|
||
POST /api/models/:id/primary 设为全局默认模型
|
||
GET /api/reviews 审核流水 / 待审列表
|
||
POST /api/reviews/:type/:id/approve 审核通过
|
||
POST /api/reviews/:type/:id/reject 审核驳回(必须填意见)
|
||
GET /api/catalog/export 客户端一次性拉取已发布目录
|
||
GET /api/catalog/version 契约版本 + 目录指纹
|
||
|
||
完整契约(OpenAPI 3.0)见 contracts/rest-api.yaml。
|
||
|
||
上传文件通过 /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 走正式通路。
|
||
|
||
----------------------------------------------------------------
|
||
六、目录结构
|
||
----------------------------------------------------------------
|
||
|
||
project.yaml 任务清单与状态
|
||
DSH.md 本项目 DSH 执行约束
|
||
tasks/ 单任务说明书(T-WEB-001 ~ 010)
|
||
contracts/ 跨端契约(权威,改接口先改这里)
|
||
web-server/ NestJS 服务端源码
|
||
web-server/admin/ React 管理端
|
||
deploy/ 业务服务部署(系统服务,不含 MySQL、不含反向代理)
|
||
deploy/proxy/ 反向代理配置说明(nginx / IIS,仅供参考不部署)
|
||
THIRD_PARTY.md 第三方开源项目声明
|
||
|
||
----------------------------------------------------------------
|
||
七、安全提示
|
||
----------------------------------------------------------------
|
||
|
||
1. .env 已被 .gitignore 忽略,禁止提交;仓库中只保留 .env.example。
|
||
2. 数据库账号使用最小权限,不授予 GRANT OPTION / FILE / SUPER。
|
||
3. 本版本未内置管理端登录认证,请部署在内网,或在反向代理层加 Basic Auth / SSO。
|
||
4. 上传的模型与动作必须填写来源五元组并通过审核后才对客户端可见。
|
||
5. 未使用 SMPL 拓扑,统一 MediaPipe 33 点,规避非商用许可限制。
|
||
|
||
----------------------------------------------------------------
|
||
八、第三方开源声明
|
||
----------------------------------------------------------------
|
||
|
||
见 THIRD_PARTY.md,或访问站点 http://<host>/ 的「关于」页面。
|