perf: 性能与可靠性优化 - CommandQueue超时恢复/HTTP限制/日志轮转/调试日志清理/applyProperties修复

This commit is contained in:
火焰库拉
2026-02-28 08:44:45 +08:00
parent 42ab8d8ee2
commit aadf69300f
3 changed files with 1225 additions and 1168 deletions

View File

@@ -249,3 +249,37 @@
### 5. 日志仅输出关键信息到编辑器控制台
- **优化**: `addLog` 函数不再将所有类型的日志输出到编辑器控制台,仅 `error``warn` 级别日志通过 `Editor.error()` / `Editor.warn()` 输出,防止 `info` / `success` / `mcp` 类型日志刷屏干扰开发者。
---
## 性能与可靠性优化 (2026-02-28)
### 1. CommandQueue 超时保护恢复
- **问题**: 合并冲突解决时 `enqueueCommand` 中的 60 秒兜底超时保护代码丢失,导致如果工具函数内部异常未调用 `done()`,整个指令队列将永久停滞,后续所有操作将卡死不再响应。
- **修复**: 在 `enqueueCommand` 中为每个入队指令注册 `setTimeout(60000)` 超时定时器,正常完成时通过 `clearTimeout` 取消。
### 2. HTTP 请求体大小限制
- **问题**: `_handleRequest``body += chunk` 无上限保护,超大请求体(恶意或异常客户端)可能耗尽编辑器进程内存。
- **修复**: 新增 5MB (`5 * 1024 * 1024`) 请求体上限,超出时返回 HTTP 413 并销毁连接。
### 3. 日志文件轮转机制
- **问题**: `settings/mcp-bridge.log` 文件持续追加写入,长期使用会无限增长占用磁盘空间。
- **修复**: 在 `getLogFilePath()` 初始化时检查文件大小,超过 2MB 自动将旧日志重命名为 `.old` 备份后创建新文件。
### 4. 清理冗余调试日志
- **问题**: `scene-script.js``update-node-transform``applyProperties` 共有 8 处 `Editor.log` 调试日志,每次操作都输出到编辑器控制台造成刷屏。
- **修复**: 移除所有冗余 `Editor.log` 调试输出,保留必要的 `Editor.warn` 警告(如资源加载失败、属性解析失败等)。
### 5. `applyProperties` 逻辑修复
- **问题**: `applyProperties` 启发式资源解析分支中使用了 `return` 而非 `continue`,导致处理到该分支后会直接退出整个 `for...of` 循环,跳过后续属性的设置。
- **修复**: 将 `return` 改为 `continue`,确保多属性同时更新时所有属性都能被正确处理。
### 6. `instantiate-prefab` 统一使用 `findNode`
- **问题**: `instantiate-prefab` 中查找父节点直接调用 `cc.engine.getInstanceById(parentId)`,绕过了 `findNode` 函数的压缩 UUID 解压与兼容逻辑。
- **修复**: 统一改用 `findNode(parentId)`,确保所有场景操作对压缩和非压缩 UUID 格式的兼容性一致。

36
main.js
View File

@@ -29,7 +29,14 @@ let isProcessingCommand = false;
*/
function enqueueCommand(fn) {
return new Promise((resolve) => {
commandQueue.push({ fn, resolve });
// 兜底超时保护:防止 fn 内部未调用 done() 导致队列永久停滞
const timeoutId = setTimeout(() => {
addLog("error", "[CommandQueue] 指令执行超时(60s),强制释放队列");
isProcessingCommand = false;
resolve();
processNextCommand();
}, 60000);
commandQueue.push({ fn, resolve, timeoutId });
processNextCommand();
});
}
@@ -40,15 +47,17 @@ function enqueueCommand(fn) {
function processNextCommand() {
if (isProcessingCommand || commandQueue.length === 0) return;
isProcessingCommand = true;
const { fn, resolve } = commandQueue.shift();
const { fn, resolve, timeoutId } = commandQueue.shift();
try {
fn(() => {
clearTimeout(timeoutId);
isProcessingCommand = false;
resolve();
processNextCommand();
});
} catch (e) {
// 防止队列因未捕获异常永久阻塞
clearTimeout(timeoutId);
addLog("error", `[CommandQueue] 指令执行异常: ${e.message}`);
isProcessingCommand = false;
resolve();
@@ -112,6 +121,19 @@ function getLogFilePath() {
fs.mkdirSync(settingsDir, { recursive: true });
}
_logFilePath = pathModule.join(settingsDir, "mcp-bridge.log");
// 日志轮转: 超过 2MB 时备份旧日志并创建新文件
try {
if (fs.existsSync(_logFilePath)) {
const stats = fs.statSync(_logFilePath);
if (stats.size > 2 * 1024 * 1024) {
const backupPath = _logFilePath + ".old";
if (fs.existsSync(backupPath)) fs.unlinkSync(backupPath);
fs.renameSync(_logFilePath, backupPath);
}
}
} catch (e) {
/* 轮转失败不影响主流程 */
}
return _logFilePath;
}
} catch (e) {
@@ -832,11 +854,21 @@ module.exports = {
res.setHeader("Content-Type", "application/json");
res.setHeader("Access-Control-Allow-Origin", "*");
const MAX_BODY_SIZE = 5 * 1024 * 1024; // 5MB 请求体上限
let body = "";
let aborted = false;
req.on("data", (chunk) => {
body += chunk;
if (body.length > MAX_BODY_SIZE) {
aborted = true;
addLog("error", `[HTTP] 请求体超过 ${MAX_BODY_SIZE} 字节上限,已拒绝`);
res.writeHead(413);
res.end(JSON.stringify({ error: "请求体过大" }));
req.destroy();
}
});
req.on("end", () => {
if (aborted) return;
const url = req.url;
if (url === "/list-tools") {
const tools = getToolsList();

File diff suppressed because it is too large Load Diff