diff --git a/README.txt b/README.txt index 599ea98..5f362cc 100644 --- a/README.txt +++ b/README.txt @@ -1,5 +1,5 @@ ================================================================ -OpenDance Studio —— Web 管理服务端 v0.2.0 使用说明 +OpenDance Studio -- Web 管理服务端 v0.2.0 使用说明 ================================================================ 本服务端是 OpenDance Studio 三端中的第一端, 负责管理歌曲、VRM 模型与 @@ -8,9 +8,9 @@ OpenDance Studio —— Web 管理服务端 v0.2.0 使用说明 重要: 本服务端 **不部署、不管理 MySQL**。数据库为部署方提供的现有实例, 连接信息通过 .env 注入。 ----------------------------------------------------------------- +-- 一、部署到 Linux 服务器(业务服务, 不含反向代理) ----------------------------------------------------------------- +-- 职责边界 本端**只提供业务服务**:NestJS API + 管理端页面。 @@ -33,12 +33,12 @@ OpenDance Studio —— Web 管理服务端 v0.2.0 使用说明 反向代理按用途分别转发两个端口, 每个端口各一条全匹配转发规则即可。 前置要求 - - Linux 服务器, 有 root 权限(不需要 sudo, 直接以 root 执行即可) - - Node.js 22 或更高版本 - - 进程管理: systemd / OpenRC / SysVinit 均可; **三者都没有也能装** + - Linux 服务器, 有 root 权限(不需要 sudo, 直接以 root 执行即可) + - Node.js 22 或更高版本 + - 进程管理: systemd / OpenRC / SysVinit 均可; **三者都没有也能装** (自动降级为 nohup 后台进程 + PID 文件 + crontab @reboot 开机自启) - - 反向代理: 由另外的服务提供, 本项目不要求、也不安装 - - 一个可连通的现有 MySQL / MariaDB 实例, 并已创建数据库与账号: + - 反向代理: 由另外的服务提供, 本项目不要求、也不安装 + - 一个可连通的现有 MySQL / MariaDB 实例, 并已创建数据库与账号: CREATE DATABASE IF NOT EXISTS dpendance CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -60,15 +60,15 @@ OpenDance Studio —— Web 管理服务端 v0.2.0 使用说明 vi deploy/.env # 填写 DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME 脚本会依次: - 校验环境与 .env → 创建服务用户与目录 → 复制程序文件 → - 安装运行期依赖 → 执行 TypeORM 迁移 → - 注册并启动系统服务(systemd / OpenRC / SysVinit / 后台进程)→ 等待健康检查 + 校验环境与 .env -> 创建服务用户与目录 -> 复制程序文件 -> + 安装运行期依赖 -> 执行 TypeORM 迁移 -> + 注册并启动系统服务(systemd / OpenRC / SysVinit / 后台进程)-> 等待健康检查 deploy/.env 初始化引导(deploy/init-env.sh) 缺 deploy/.env 时, install.sh 会先调用本脚本引导填写; 引导失败才中止安装 (若安装目录里已有上次安装留下的 .env, 则直接复用, 不会重复提问)。 也可以单独运行: 逐项询问数据库与端口, 回车保留默认值, 写盘前做必填项与 - 端口范围校验, 并可用项目自带的 mysql2 做一次数据库连通性自检——口令只经 + 端口范围校验, 并可用项目自带的 mysql2 做一次数据库连通性自检--口令只经 环境变量传给子进程, 出错信息与摘要一律显示 ***。 bash deploy/init-env.sh # 交互引导(终端里逐项询问) @@ -112,8 +112,8 @@ deploy/.env 初始化引导(deploy/init-env.sh) 对外访问 由反向代理**按用途分别转发两个端口**: - 浏览器管理界面 → http://<后端地址>:3001 - Windows/Godot 客户端与 Android App → 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)配置 @@ -164,9 +164,9 @@ deploy/.env 初始化引导(deploy/init-env.sh) 反代配置: 不在本机, 无需在此维护 ----------------------------------------------------------------- +-- 二、本地开发 ----------------------------------------------------------------- +-- 后端 cd web-server @@ -180,9 +180,9 @@ deploy/.env 初始化引导(deploy/init-env.sh) npm install npm run dev # http://localhost:5173(/api 与 /health 代理到 3000) ----------------------------------------------------------------- +-- 三、验证 ----------------------------------------------------------------- +-- cd web-server npm run contracts:check # 契约版本一致性 @@ -197,9 +197,9 @@ deploy/.env 初始化引导(deploy/init-env.sh) cd ../admin && npm run build # 管理端构建 ----------------------------------------------------------------- +-- 四、环境变量 ----------------------------------------------------------------- +-- DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME 现有 MySQL 实例连接信息(必填) @@ -238,7 +238,7 @@ deploy/.env 初始化引导(deploy/init-env.sh) humanoid 骨骼映射不全时自动回退交付 GLB 并写进 conversion_note。 产物一律用 `assimp info` 回读校验 MODEL_CONVERT_PMX 压缩包内优先选用的 PMX 文件名(默认 Hatsune Miku.pmx, - 大小写不敏感); 找不到时按「格式优先级 → 目录最浅 → + 大小写不敏感); 找不到时按「格式优先级 -> 目录最浅 -> 文件最大」挑选 MODEL_CONVERT_TIMEOUT_MS 单次转换超时(毫秒, 默认 180000) @@ -247,9 +247,9 @@ deploy/.env 初始化引导(deploy/init-env.sh) CONTRACTS_DIR 可选: 契约目录, 设置后 /health 反映实时契约版本 (原生部署默认指向 /opt/opendance/contracts) ----------------------------------------------------------------- +-- 五、主要 API ----------------------------------------------------------------- +-- GET /health 健康检查(含数据库连通性) GET /api/songs 曲目列表(分页 / 检索) @@ -260,7 +260,7 @@ deploy/.env 初始化引导(deploy/init-env.sh) POST /api/songs/:id/audio 上传音频 GET /api/motions 动作列表 POST /api/motions 上传动作 + 来源信息 - POST /api/motions/:id/preprocess BVH/VMD/FBX → MediaPipe33 关键点 + POST /api/motions/:id/preprocess BVH/VMD/FBX -> MediaPipe33 关键点 GET /api/motions/:id/keypoints 下载关键点序列(NDJSON) GET /api/models 模型列表 POST /api/models 上传模型 + 来源信息(.glb/.vrm/.gltf @@ -283,13 +283,13 @@ deploy/.env 初始化引导(deploy/init-env.sh) (Godot 客户端用内置 GLTFDocument 加载 glTF/GLB/VRM, 见 opendance-windows/src/model/vrm_loader.gd, 本次无需改动)。 - glTF Binary .glb 主力(P0)—— **直接导入**, 不重编码 - VRM .vrm 主力(P0)—— 直接导入; GLB + VRM 扩展 + glTF Binary .glb 主力(P0)-- **直接导入**, 不重编码 + VRM .vrm 主力(P0)-- 直接导入; GLB + VRM 扩展 (humanoid / 表情 / springBone / meta) - glTF .gltf P1 —— 直接导入(分离的 .bin / 贴图需随 .zip 一起上传) - MMD 压缩包 .zip P1 —— **自动搜索包内可用模型**(见下) - MMD 模型 .pmx P2(输入)—— 服务端转换, 优先 GLB、其次 VRM - FBX .fbx P2(输入)—— 服务端转换, 优先 GLB、其次 VRM + glTF .gltf P1 -- 直接导入(分离的 .bin / 贴图需随 .zip 一起上传) + MMD 压缩包 .zip P1 -- **自动搜索包内可用模型**(见下) + MMD 模型 .pmx P2(输入)-- 服务端转换, 优先 GLB、其次 VRM + FBX .fbx P2(输入)-- 服务端转换, 优先 GLB、其次 VRM 入库后**一律 status=pending(待审核)**: 上传不再自动发布、不再自动设为默认, 必须经审核通过(模型审核面板可勾选「同时设为全局默认模型」)才对客户端可见。 @@ -304,13 +304,13 @@ deploy/.env 初始化引导(deploy/init-env.sh) 模型压缩包(.zip)自动搜索与转换 上传的 .zip 不再"只找 .pmx", 而是**自动搜索包内可用模型**, 走 - "解压 → 选主模型 → 直接导入或转换 → 回读校验 → 入库", 而不是把压缩包当模型存下来。 + "解压 -> 选主模型 -> 直接导入或转换 -> 回读校验 -> 入库", 而不是把压缩包当模型存下来。 - 选主模型 优先级 .vrm → .glb → .gltf → .pmx → .fbx; 同一优先级内按 - "MODEL_CONVERT_PMX 指名字 → 目录最浅 → 文件最大"。 + 选主模型 优先级 .vrm -> .glb -> .gltf -> .pmx -> .fbx; 同一优先级内按 + "MODEL_CONVERT_PMX 指名字 -> 目录最浅 -> 文件最大"。 命中 .vrm/.glb/.gltf 时**直接导入**(不重编码)。 - 转换 只有 .pmx/.fbx 需要转换。链路: assimp export → .glb(首选); - 失败则 assimp export → .gltf, 再由 gltf-packer.ts 自行打包回 GLB; + 转换 只有 .pmx/.fbx 需要转换。链路: assimp export -> .glb(首选); + 失败则 assimp export -> .gltf, 再由 gltf-packer.ts 自行打包回 GLB; MODEL_CONVERT_FORMAT=vrm 时在 GLB 之上补 VRMC_vrm 扩展封装成 VRM 1.0(humanoid 骨骼不全则回退交付 GLB, 缺失骨骼写进说明)。 校验 每一步产物**必须**用 `assimp info` 回读校验。Assimp 会"写出损坏文件" @@ -319,10 +319,10 @@ deploy/.env 初始化引导(deploy/init-env.sh) 贴图 GLB 产物里 Assimp 会把贴图写成外部 URI(还带反斜杠), 服务端把 压缩包内 / .gltf 同目录的贴图内嵌进 GLB 的二进制块, 保证单文件可加载。 说明 每次尝试写入 models.conversion_note, 例如 - `Hatsune Miku.pmx → GLB, 已内嵌 2 张贴图`。 + `Hatsune Miku.pmx -> GLB, 已内嵌 2 张贴图`。 溯源 压缩包原件以 model-archive 资产留存, 写入 models.source_asset_id; 迁移 AddModelConversionFields1789450000000。 - 失败 包内没有可用模型、Assimp 不可用、两条路径都失败 → HTTP 422, + 失败 包内没有可用模型、Assimp 不可用、两条路径都失败 -> HTTP 422, 不产生模型记录。 文件名编码 upload.util.ts 的 decodeUploadName() 修正 multipart 文件名的 @@ -332,13 +332,13 @@ deploy/.env 初始化引导(deploy/init-env.sh) 新增字段 DTO/契约新增 conversionNote、fileSha256、stats、thumbnailUrl。 实测(Assimp 6.0.4): - - 上传含 `Hatsune Miku.pmx` 的 MMD 压缩包 → HTTP 201 / 0.86s、format=glb、 - status=pending、conversionNote=`Hatsune Miku.pmx → GLB, 已内嵌 2 张贴图`。 + - 上传含 `Hatsune Miku.pmx` 的 MMD 压缩包 -> HTTP 201 / 0.86s、format=glb、 + status=pending、conversionNote=`Hatsune Miku.pmx -> GLB, 已内嵌 2 张贴图`。 分析结果: vertices=7155 triangles=9071 materials=3 textures=2 images=2 nodes=240 bones=238 skins=1 morphTargets=0 humanoidMapped=0/22 drivable=false maxBoneDepth=23;missingRequiredBones 为全部 16 根 (日文 MMD 骨名无法映射到 humanoid, 审核界面会直接标红)。 - - 直接上传 .glb → HTTP 201 / 0.36s、conversionNote=`直接导入(GLB, 未做重编码)`, + - 直接上传 .glb -> HTTP 201 / 0.36s、conversionNote=`直接导入(GLB, 未做重编码)`, 入库文件 sha256 与源文件**一致**(不重编码, 字节级保留)。 模型分析、缩略图与 3D 预览(入库后自动执行, 失败不影响入库) @@ -346,13 +346,13 @@ deploy/.env 初始化引导(deploy/init-env.sh) 节点 / 形变目标 / 动画 / 蒙皮骨骼数 / **骨骼树** / humanoid 覆盖 / 可驱动性(缺哪些必需骨骼)/ warnings, 写入 models.stats (迁移 AddModelStats1789460000000, 新增 models.stats TEXT)。 - 缩略图 model-thumbnail.ts 用纯 TS 软件光栅化生成 PNG(读 GLB/glTF → 应用节点 - 层级与蒙皮 → 包围盒自动构图 → 透视投影 → z-buffer 逐三角形平面着色 → + 缩略图 model-thumbnail.ts 用纯 TS 软件光栅化生成 PNG(读 GLB/glTF -> 应用节点 + 层级与蒙皮 -> 包围盒自动构图 -> 透视投影 -> z-buffer 逐三角形平面着色 -> 手写 PNG 编码),**零原生依赖**(不需要 canvas / headless-gl / puppeteer)。 默认 512x512, 写入 models.thumbnail_asset_id / thumbnailUrl; 失败或非 glTF 容器时留空, 列表页退化为格式占位图。 实测: 512x512 PNG / 15,043 字节, 目视确认为居中的 T-pose 角色图; - 真实 10MB VRM(83 关节、5307 顶点)单张耗时 60–121ms(中位 79ms)。 + 真实 10MB VRM(83 关节、5307 顶点)单张耗时 60-121ms(中位 79ms)。 预览 管理端 3D 预览放开到 vrm/glb/gltf(.fbx 仍提示「仅归档, 请先转换为 GLB/VRM」); 列表页显示 40px 小缩略图, 审核面板显示 116px 缩略图。 @@ -365,7 +365,7 @@ deploy/.env 初始化引导(deploy/init-env.sh) 骨骼预览 骨骼详细界面: 扁平骨骼树按 depth 缩进、显示骨骼名、是否参与蒙皮、 humanoid 映射徽章、子节点数; 支持按名搜索(保留祖先链)、 「仅显示蒙皮骨骼」开关、限高滚动 + 只渲染前 400 行(千根骨骼不卡)。 - 通过 勾选「同时设为全局默认模型」→ 请求体带 `setPrimary:true` + 通过 勾选「同时设为全局默认模型」-> 请求体带 `setPrimary:true` (`POST /api/reviews/model/:id/approve`); 此时才写库 is_primary 并把默认模型身份写进源码 src/config/default-model.ts。 单独设置 `POST /api/models/:id/primary`(仅 published 允许, 否则 409)。 @@ -375,19 +375,19 @@ deploy/.env 初始化引导(deploy/init-env.sh) GET /api/catalog/export 的 defaultModelId 按以下顺序解析: 0. 环境变量 DEFAULT_MODEL_ID(且模型存在且已发布) - 1. 源码种子 src/config/default-model.ts 的 DEFAULT_MODEL_SEED——按**上传压缩包的 - sha256** 匹配已发布模型(用户文件永远稳定; 不能用转换产物的 sha256 —— + 1. 源码种子 src/config/default-model.ts 的 DEFAULT_MODEL_SEED--按**上传压缩包的 + sha256** 匹配已发布模型(用户文件永远稳定; 不能用转换产物的 sha256 -- Assimp 每次导出的字节都略有不同), 换库 / 重建库后仍有效 2. format='vrm' 且 published 且 is_primary=1 3. 任意 published 的 VRM(id 升序取第一个) 4. id 最小的已发布模型 - 5. 无可用模型 → 客户端用内置占位模型 + 5. 无可用模型 -> 客户端用内置占位模型 源码种子 src/config/default-model.ts 当前为**空种子**(占位: id=null、sha256 空串), 此时 defaultModelId 直接从第 2 步的 VRM 优先规则开始解析; 按下面的方式设定默认 模型后, 服务端会把种子写成真实内容。 - 实测(v0.2.0):`POST /api/reviews/model/22/approve` 带 `setPrimary:true` → 200, - 模型 status=published、isPrimary=true, 源码种子写入 sourceSha256=72eb6882…; + 实测(v0.2.0):`POST /api/reviews/model/22/approve` 带 `setPrimary:true` -> 200, + 模型 status=published、isPrimary=true, 源码种子写入 sourceSha256=72eb6882...; 重建后 `GET /api/catalog/export` 的 defaultModelId 由 VRM 变为 **22** (库中另有 2 个已发布 VRM)。 也可手工指定: POST /api/models/:id/primary。 @@ -406,7 +406,7 @@ deploy/.env 初始化引导(deploy/init-env.sh) --source-name "模型_初音未来Hatsune Miku_by_Boku_Wa_Luka.zip" \ --source-url "本地文件: 模型_初音未来Hatsune Miku_by_Boku_Wa_Luka.zip" \ --source-type other - # 上传 → 服务端转换入库(pending)→ 把身份写进 src/config/default-model.ts + # 上传 -> 服务端转换入库(pending)-> 把身份写进 src/config/default-model.ts node scripts/import-default-model.mjs --write-source-only --model-id 22 # 只根据已入库模型补写源码, 不调上传 API @@ -419,7 +419,7 @@ deploy/.env 初始化引导(deploy/init-env.sh) 要让它在 `defaultModelId` 中生效, 仍需在管理端**审核通过**(勾选「同时设为全局 默认模型」)或调用 `POST /api/models/:id/primary`。 -支持的动作格式(POST /api/motions 上传 → POST /api/motions/:id/preprocess 预处理) +支持的动作格式(POST /api/motions 上传 -> POST /api/motions/:id/preprocess 预处理) BVH .bvh 内置纯 TS 解析器, 保留源帧率 VMD .vmd 内置解析(MikuMikuDance), 固定 30fps; 腿部按「足IK是否带位移」自动选 IK 或 FK @@ -441,9 +441,9 @@ VMD 动作上传前后自查 它会打印头部 / 模型名 / 帧数 / 轨道数、未识别骨骼、走 IK 还是 FK、关键点 序列异常统计与膝肘角分布。上传后再调用 /preprocess 走正式通路。 ----------------------------------------------------------------- +-- 六、目录结构 ----------------------------------------------------------------- +-- project.yaml 任务清单与状态 DSH.md 本项目 DSH 执行约束 @@ -452,10 +452,10 @@ VMD 动作上传前后自查 web-server/ NestJS 服务端源码 web-server/admin/ React 管理端 web-server/src/common/zip-reader.ts 纯 TS ZIP 读取器 - web-server/src/models/model-convert.ts .zip/.pmx/.fbx → GLB/VRM 转换 - web-server/src/models/gltf-packer.ts glTF(分离文件)→ GLB 打包 + web-server/src/models/model-convert.ts .zip/.pmx/.fbx -> GLB/VRM 转换 + web-server/src/models/gltf-packer.ts glTF(分离文件)-> GLB 打包 web-server/src/models/glb-textures.ts GLB 贴图内嵌 - web-server/src/models/vrm-writer.ts GLB → VRM 1.0 封装(写 VRMC_vrm 扩展) + web-server/src/models/vrm-writer.ts GLB -> VRM 1.0 封装(写 VRMC_vrm 扩展) web-server/src/models/model-inspector.ts 模型检查器(面数/骨骼树/humanoid 覆盖) web-server/src/models/model-thumbnail.ts 纯 TS 光栅化缩略图(零原生依赖) web-server/src/config/default-model.ts 默认模型种子(服务端/CLI 写入) @@ -470,9 +470,9 @@ VMD 动作上传前后自查 deploy/proxy/ 反向代理配置说明(nginx / IIS, 仅供参考不部署) THIRD_PARTY.md 第三方开源项目声明 ----------------------------------------------------------------- +-- 七、安全提示 ----------------------------------------------------------------- +-- 1. .env 已被 .gitignore 忽略, 禁止提交; 仓库中只保留 .env.example。 2. 数据库账号使用最小权限, 不授予 GRANT OPTION / FILE / SUPER。 @@ -480,11 +480,11 @@ VMD 动作上传前后自查 4. 上传的模型与动作默认需填写来源五元组并通过审核后才对客户端可见。 **模型上传后一律 pending**: 不再自动发布、不再自动设为默认; 默认模型只在 审核通过时勾选「同时设为全局默认模型」(`setPrimary:true`)或调用 - `POST /api/models/:id/primary`(仅 published)时才设置(见 §五)。 + `POST /api/models/:id/primary`(仅 published)时才设置(见 五)。 5. 未使用 SMPL 拓扑, 统一 MediaPipe 33 点, 规避非商用许可限制。 ----------------------------------------------------------------- +-- 八、第三方开源声明 ----------------------------------------------------------------- +-- 见 THIRD_PARTY.md, 或访问站点 http:/// 的「关于」页面。