#!/usr/bin/env node /** * 回填 / 刷新已有模型的分析结果与缩略图。 * * 用途:升级到带 `models.stats` 的版本后,历史模型还没有面数、骨骼树与预览图; * 这个脚本逐个调用 `POST /api/models/{id}/reanalyze` 把它们补齐。 * * 用法(在 web-server 目录下,服务需已启动): * node scripts/reanalyze-models.mjs --url http://127.0.0.1:3000 # 全部模型 * node scripts/reanalyze-models.mjs --url http://127.0.0.1:3000 --model-id 14 * node scripts/reanalyze-models.mjs --url http://127.0.0.1:3000 --force # 已有 stats 的也重算 * node scripts/reanalyze-models.mjs --url http://127.0.0.1:3000 --dry-run # 只列出会处理谁 * * 只读列表 + 调 reanalyze 接口,不直接改数据库。 */ const args = process.argv.slice(2); const flag = (name, fallback = undefined) => { const index = args.indexOf(name); return index >= 0 && args[index + 1] && !args[index + 1].startsWith('--') ? args[index + 1] : index >= 0 ? true : fallback; }; const has = (name) => args.includes(name); const url = String(flag('--url', 'http://127.0.0.1:3000')).replace(/\/$/, ''); const onlyId = Number(flag('--model-id', 0)) || 0; const force = has('--force'); const dryRun = has('--dry-run'); const pageSize = 50; async function api(path, options = {}) { const response = await fetch(`${url}${path}`, options); const text = await response.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = text; } if (!response.ok) { throw new Error(`${options.method ?? 'GET'} ${path} → HTTP ${response.status}:${JSON.stringify(body)}`); } return body; } /** 拉取全部模型(分页) */ async function listAllModels() { const all = []; for (let page = 1; ; page += 1) { const data = await api(`/api/models?page=${page}&pageSize=${pageSize}`); all.push(...(data.items ?? [])); if (all.length >= (data.total ?? 0) || (data.items ?? []).length === 0) break; } return all; } async function main() { if (onlyId) { const model = await api(`/api/models/${onlyId}`); await reanalyze(model); return; } const models = await listAllModels(); const todo = force ? models : models.filter((m) => !m.stats); console.log( `模型总数 ${models.length},${force ? '(--force)全部重算' : '需要回填'} ${todo.length} 个`, ); if (dryRun) { for (const m of todo) console.log(` #${m.id} ${m.name}(${m.format})`); return; } let ok = 0; let failed = 0; for (const model of todo) { try { await reanalyze(model); ok += 1; } catch (error) { failed += 1; console.error(` ✗ #${model.id} ${model.name}:${error instanceof Error ? error.message : error}`); } } console.log(`完成:成功 ${ok} 个,失败 ${failed} 个`); if (failed > 0) process.exit(1); } async function reanalyze(model) { const result = await api(`/api/models/${model.id}/reanalyze`, { method: 'POST' }); const stats = result.stats ?? {}; console.log( ` ✓ #${result.id} ${result.name}:面数=${stats.triangles ?? '—'} 骨骼=${stats.bones ?? '—'} ` + `humanoid=${stats.humanoidMapped ?? '—'}/${stats.humanoidTotal ?? '—'} ` + `可驱动=${stats.drivable ?? '—'} 缩略图=${result.thumbnailUrl ? '已生成' : '无'}`, ); } main().catch((error) => { console.error(`失败:${error instanceof Error ? error.message : String(error)}`); process.exit(1); });