优化comfyui桥接器的逻辑

This commit is contained in:
王佑琳 2026-05-18 15:01:05 +08:00
parent 649b2754dc
commit ca8d265d9a
4 changed files with 642 additions and 26 deletions

40
1.txt

File diff suppressed because one or more lines are too long

85
CLAUDE.md Normal file
View File

@ -0,0 +1,85 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 项目概述
comfyui-cluster-bridge 是 ComfyUI 实例集群通信中间层,将多个 ComfyUI 实例组成集群,通过统一的任务队列对外提供生图服务。项目分为四个独立子服务。
## 启动命令
```bash
# 首次运行需要安装依赖(四个子项目各自安装)
cd backend && npm install
cd frontend && npm install
cd message-dispatcher && npm install
cd 任务队列后端 && npm install
# 桥接器后端 (port 3000)
cd backend && npm run dev
# 前端管理界面 (port 5173)
cd frontend && npm run dev
# 消息分发器 (port 4000)
cd message-dispatcher && npm run dev
# 任务队列后端 (port 8087)
cd 任务队列后端 && node index.js
# 或者仅启动 callback 服务 (port 8089)
cd 任务队列后端 && node app.js
```
## 架构概览
整个系统数据流为:**外部调用方 → 任务队列后端 → message-dispatcher → backend(桥接器) → ComfyUI 实例**
### 四层服务职责
**任务队列后端** (`任务队列后端/`) — 系统的对外入口层
- `index.js` 启动 HTTP 服务器(8087) + WebSocket 服务器,同时创建 7 个 worker_threads 处理任务全生命周期
- 7 个 Worker 线程:`assessment`(任务评估/分发)、`wait`(等待/生成任务)、`polling`(轮询外部平台)、`result`(结果获取)、`callback_result`(回调结果)、`callback_timeout`(回调超时)、`error`(错误处理)
- 通过 WebSocket 接收 message-dispatcher 发来的任务请求(type=generate),转交 worker 处理后返回结果
- 使用 Redis 做消息持久化,支持断线消息重发(`redis/messagePersistence.js`
- `app.js` 是仅 callback 服务的独立入口(8089)`index.js` 是完整任务队列入口
- `outside/outPlatforms/` 包含对接外部平台的适配器JimuAI、runninghub、coze 等)
- 配置文件在 `config/` 目录下:`code.json`(错误码映射)、`model.json`(模型配置)、`Platform.json`(平台配置)等
**message-dispatcher** (`message-dispatcher/`) — 中心调度层
- 核心组件:`bridge-manager`(桥接器注册管理 + 实例锁机制)、`task-scheduler`(任务调度 + 队列管理 + 超时检测 + 任务恢复)、`websocket-server`(接收桥接器 WS 连接)、`md-websocket-client`(连接到任务队列后端)
- BridgeManager 管理所有已注册桥接器的实例池,使用轮询选择空闲实例,实例锁 30s 短超时/2h 长超时机制
- TaskScheduler 维护 5 种任务状态pending → processing → completed/failed/recovering自动调度排队任务
- 对外提供 REST API`api/index.js`)和 JWT 认证(`auth/`),前端管理界面通过此 API 获取状态
**backend(桥接器)** (`backend/`) — 与 ComfyUI 实例通信的桥接层
- `cluster-manager`:管理所有 ComfyUI 实例的状态(定时健康检查、负载均衡轮询)
- `websocket-client`:与各 ComfyUI 实例建立 WebSocket 连接(按 instanceId 复用连接)
- `task-forwarder`:接收任务 → 转发到 ComfyUI → 监听执行状态execution_start/progress/executed/error/success→ 获取结果并上传文件。同时监听 `disconnected` 事件,**ComfyUI 实例断连时按顺序处理:先推实例离线状态给 message-dispatcher再回调所有受影响任务的失败结果错误信息"超出显存"),防止在回调期间 message-dispatcher 继续分配任务到已死实例**。
- `task-queue-client`:作为 WebSocket 客户端连接到 message-dispatcher报告实例状态、接收任务
- `workflow-converter`:将外部工作流格式转换为 ComfyUI 格式
- `file-uploader`:将 ComfyUI 生成的图片等文件上传到外部文件服务器
- 配置文件 `config/servers.json` 定义 ComfyUI 实例列表,支持热加载
**frontend** (`frontend/`) — Vue 3 管理界面
- 技术栈Vue 3 + Vite + Pinia + Element Plus + ECharts + Vue Router
- 4 个页面:实例管理、任务管理、配置管理、系统监控
- 连接到 message-dispatcher 的 REST API
### 关键通信协议
- **bridge ↔ message-dispatcher**WebSocket 长连接,消息类型包括 REGISTER、HEARTBEAT、TASK_ASSIGN、TASK_ACK、TASK_END、INSTANCE_CHECK
- **message-dispatcher ↔ 任务队列后端**WebSocket 长连接message-dispatcher 推送 CAPACITY_UPDATE算力状态、JWT_UPDATE接收来自后端的任务执行结果
- **bridge ↔ ComfyUI**HTTP POST `/prompt` 提交任务WebSocket 监听 `ws://host:port/ws?clientId=xxx` 获取执行事件
- 实例锁通过 message-dispatcher 的 BridgeManager 统一管理,防止同一实例被分配多个任务
### 任务完整生命周期
1. 外部系统通过任务队列后端 API 提交任务
2. 任务队列后端 worker 处理后,通过 assessment worker 判断需要调用 ComfyUI
3. message-dispatcher 的 md-websocket-client 接收到任务TaskScheduler 入队
4. BridgeManager 选一个空闲实例并加锁 → WebSocket 发送 TASK_ASSIGN 到对应 bridge
5. bridge 的 task-queue-client 收到 TASK_ASSIGN → 转发给 task-forwarder
6. task-forwarder 调用 workflow-converter 转换工作流 → HTTP POST /prompt 到 ComfyUI
7. ComfyUI WebSocket 推送 execution_start → progress → executing → executed → execution_success
8. task-forwarder 收到 execution_success → 调 /history API 获取 outputs → 上传文件 → 发送 TASK_END 回 message-dispatcher
9. message-dispatcher 转发结果给任务队列后端 → 释放实例锁 → 调度下一个排队任务

489
OPTIMIZATION_REPORT.md Normal file
View File

@ -0,0 +1,489 @@
# ComfyUI Cluster Bridge 优化报告
> 生成日期2026-05-12
> 分析范围backend / frontend / message-dispatcher / 任务队列后端 四个子项目
---
## 一、严重问题Critical
### 1.1 任务状态机断裂 — 死代码导致任务永远无法进入 running 状态
**文件**[task-forwarder/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-forwarder/index.js)
**问题**`sendTaskToInstance` 方法中catch 块通过 `throw new Error(errorMessage)` 重新抛出异常,导致 `task.status = 'submitted'`L191-L192作为死代码永远无法被执行。这意味着所有成功提交的任务状态永远是 `pending`,而非 `submitted`。进而 `handleExecutionStart` 中的状态检查L209-L212总是失败任务永远无法进入 `running` 状态。
**影响**:核心业务流程断裂,任务的完整生命周期无法正常流转。
---
### 1.2 临时文件泄漏 — 磁盘空间耗尽风险
**文件**[file-uploader/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/file-uploader/index.js)
**问题**L118-L121 的临时文件清理代码(`fs.existsSync` + `fs.unlinkSync`)被整块注释。结合 [task-forwarder/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-forwarder/index.js) 中 `uploadImage` 方法每次都写入 `uploads/` 目录,所有通过 HTTP API 下载的临时文件永远不会被删除。
**影响**:生产环境运行一段时间后磁盘空间会被耗尽。
---
### 1.3 心跳定时器泄漏 — 停止后仍在发送心跳
**文件**[task-queue-client/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-queue-client/index.js)
**问题**L37-L39 中 `setInterval` 创建的定时器引用未存储在实例属性上。调用 `stop()` 时无法 `clearInterval`,导致即使 stop 后心跳仍在尝试发送。
**影响**:关闭连接后产生大量错误日志,资源泄漏。
---
### 1.4 Token 刷新失败时请求永久挂起
**文件**[request.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/utils/request.js)
**问题**L76-L79 中 token 刷新失败后,`refreshSubscribers` 中的回调永远不会被执行(既不 resolve 也不 reject导致堆积在队列中的请求 Promise 永久挂起,造成**内存泄漏**。
**影响**:用户体验卡死,内存持续增长。
---
### 1.5 废弃 API 导致实例增删改功能永远失败
**文件**[api/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/api/index.js)
**问题**`addInstance`、`updateInstance`、`deleteInstance`L220-L233被标记为废弃并始终 `Promise.reject`,但 `Config.vue``Instances.vue` 仍在调用它们。
**影响**:用户看到的添加/编辑/删除操作永远提示失败,属于**功能缺陷**。
---
### 1.6 app.js 与 webSocket.js 严重代码重复
**文件**[app.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/app.js) / [webSocket.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/webSocket.js)
**问题**:两个文件(各 370+ 行)的 WebSocket 逻辑、Worker 线程创建、`sendMessageToClient`、`createWebSocketServer`、`gracefulShutdown` 等功能**超过 90% 完全相同**。如果同时启动会导致端口冲突。
**影响**:维护成本翻倍,修改时极易遗漏,已存在多处逻辑不一致。
---
### 1.7 Worker 线程崩溃后无重启机制
**文件**[app.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/app.js) / [webSocket.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/webSocket.js)
**问题**Worker 线程的 `exit` 事件仅记录日志L42-L49没有任何重启逻辑。
**影响**Worker 线程意外崩溃后,该线程负责的整个任务处理管道将永久停滞。
---
### 1.8 socketMap 内存泄漏
**文件**[app.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/app.js) / [webSocket.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/webSocket.js)
**问题**`socket.on('close')` 处理中L280-L284只清除定时器并记录日志但**未从 `socketMap` 中删除已关闭的连接**。
**影响**:长时间运行后 `socketMap` 持续增长,内存泄漏。
---
### 1.9 服务器重启直接丢弃等待队列
**文件**[initQueue.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/redis/initQueue.js)
**问题**L95-L98 在检测到 `existingInfo` 存在时,直接 `redis.del(waitName)` 删除所有等待队列数据,没有尝试将任务恢复或迁移。
**影响**:服务器重启会导致所有等待中的任务永久丢失。
---
### 1.10 回调接口先发 200 再异步处理
**文件**[callback.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/outside/callback.js)
**问题**L7 先向客户端发送 `200` 成功响应,再异步处理数据。如果后续处理失败,客户端已收到成功响应,无法感知。
**影响**:数据完整性风险,错误无法通知上游系统。
---
### 1.11 配置文件加载无异常保护
**文件**[Config.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/config/Config.js)
**问题**`fs.readFileSync` + `JSON.parse` 未包裹 try-catch。如果 `model.json`、`Platform.json` 或 `cost.json` 损坏或丢失,整个进程直接崩溃。
**影响**:配置文件损坏导致服务不可用,毫无恢复能力。
---
### 1.12 容量配置的 falsy 值判断错误
**文件**[taskDistributor.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/utils/taskDistributor.js)
**问题**L6 使用 `parseInt(...) || 10`,当 `EXTERNAL_CAPACITY_MAX=0` 时(意为"禁止外部任务"`0` 是 falsy 值,会错误回退到默认值 `10`。应使用 `??` 或显式 `isNaN` 检查。
**影响**:配置语义被曲解,无法正确禁用外部任务。
---
### 1.13 Config.vue 与 Instances.vue 100% 代码重复
**文件**[Config.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Config.vue) / [Instances.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Instances.vue)
**问题**:两个文件内容完全一致。路由标题是"配置管理",但显示的是实例管理页面。显然是复制粘贴后忘记修改。
**影响**:语义混乱,无法区分配置管理和实例管理功能。
---
### 1.14 登录页明文展示默认账号密码
**文件**[Login.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Login.vue)
**问题**L54 在模板底部直接展示"默认账号: admin / admin123"且表单初始值L73-L76硬编码了这些凭据。
**影响**:严重安全隐患,即使是内部管理系统也不应在 UI 明文展示凭据。
---
### 1.15 监控页面 ECharts 全量导入 + 假数据
**文件**[Monitor.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Monitor.vue)
**问题**
- L102`import * as echarts from 'echarts'` 全量导入,增加约 1MB 未压缩体积
- L126-L135趋势图使用硬编码随机模拟数据无实际业务价值
- L389-L398首次加载时先刷新数据再初始化图表导致首次渲染显示全 0 数据
**影响**Bundle 体积暴增,监控数据无实际意义。
---
### 1.16 task-scheduler 超时检查定时器无法清除
**文件**[task-scheduler/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/task-scheduler/index.js)
**问题**L385-L389 的 `startTimeoutCheck()``setInterval` 返回值未存储,导致 shutdown 时无法清除,定时器回调在服务停止后仍可能执行。
---
### 1.17 Redis 多连接实例浪费
**文件**[jwt.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/jwt.js) / [rate-limit.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/rate-limit.js)
**问题**:两个文件各自创建独立的 `new Redis()` 实例连接到同一个 Redis 服务器,浪费 TCP 连接资源。应抽取共享 Redis 单例模块。
---
### 1.18 速率限制的竞态条件
**文件**[rate-limit.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/rate-limit.js)
**问题**L28-L41 使用 `exists` + `incr`/`setex` 模式,存在竞态条件。两个并发请求同时到达且 key 不存在时,第二次 `setex` 会覆盖第一次的计数器。应改为 `incr` + 条件 `expire` 模式。
---
### 1.19 密码验证的时序竞态
**文件**[routes.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/routes.js)
**问题**L15-L23 通过顶层 IIFE 异步计算 `hashedAdminPassword`。在该 Promise 完成之前到达的登录请求会退化为**明文比较**L46造成短期安全窗口。
---
### 1.20 WebSocket Server 任务发送失败未回调调度器
**文件**[websocket-server/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/websocket-server/index.js)
**问题**`sendTaskToInstance` 立即失败路径L299-L301释放了实例锁但**未调用 `taskScheduler.handleTaskFailure`**,导致任务在 `processingTasks` Map 中残留。
---
## 二、高优先级问题High
### 2.1 多个模块缺少过期清理机制
| 文件 | 对象 | 说明 |
|------|------|------|
| [task-forwarder/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-forwarder/index.js) | `this.tasks` Map | 已完成任务永不清理,内存持续增长 |
| [workflow-converter/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/workflow-converter/index.js) | `this.cache` Map | 无限增长的工作流缓存 |
**建议**:添加 TTL 或 LRU 淘汰策略,定期清理过期条目。
---
### 2.2 硬编码敏感信息
多处代码中存在硬编码的 IP 地址、Token、凭证等
| 文件 | 行号 | 内容 |
|------|------|------|
| [workflow-converter/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/workflow-converter/index.js) | L6 | `http://117.72.204.159/AIGC/static/public/workflows` |
| [file-uploader/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/file-uploader/index.js) | L35-L36 | 内网地址 + Token `'123456'` |
| [task-queue-client/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-queue-client/index.js) | L43 | `wss://www.whjbjm.com/message-dispatcher` |
| [task-scheduler/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/task-scheduler/index.js) | L24-L27 | 队列大小限制硬编码 |
| [jwt.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/jwt.js) | L4 | JWT_SECRET 默认值 |
**建议**:所有敏感配置和外部依赖地址应全部通过环境变量配置。
---
### 2.3 console.log 调试残留 / 日志不一致
| 文件 | 说明 |
|------|------|
| [task-queue-client/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-queue-client/index.js) L176 | 生产环境残留 `console.log` |
| [websocket-server/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/websocket-server/index.js) | 大量 `console.log/warn` 混用,未统一使用 winston logger |
---
### 2.4 全量注册 Element Plus 图标
**文件**[main.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/main.js) L14-L16
**问题**`for...of` 循环将整个图标库(约 300+ 个)全部注册为全局组件,预估增加 ~400KB bundle。实际只使用了约 13 个图标。
**建议**:删除全局注册循环,改为按需导入。
---
### 2.5 refreshToken 命名冲突
**文件**[user.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/stores/user.js)
**问题**L7 的 state `refreshToken` 与 L78 的 action `refreshToken`(由 `refreshTokens` 别名)同名,导致代码可读性严重下降。
---
### 2.6 两个 axios 实例 token 处理逻辑不一致
**文件**[api/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/api/index.js)
**问题**`messageDispatcherRequest` 和 `request.js` 是两个独立 axios 实例token 拦截逻辑重复实现且行为不一致。
---
### 2.7 bridge-manager 锁超时不匹配
**文件**[bridge-manager/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/bridge-manager/index.js)
**问题**`LOCK_TIMEOUT` 仅 30 秒,但 `TASK_TIMEOUT` 为 30 分钟。任务确认可能因长耗时而在锁超时之后到达,此时锁已被自动释放。
---
### 2.8 schedulePendingTasks 缺乏并发保护
**文件**[task-scheduler/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/task-scheduler/index.js)
**问题**L170-L178 被多个异步路径同时调用addTask、handleTaskComplete、handleTaskFailure、handleInstanceOffline、setCurrentCapacity、定时器没有并发保护极端情况下可能导致任务重复分配或丢失。
---
### 2.9 Worker 线程管道配置未统一
**文件**[initQueue.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/redis/initQueue.js)
**问题**
- `addPlatformsProcess`L238-L256`taskCountMap``count` 值被完全忽略,仅按 Map 条目计数
- `recoverPlatformCounts`L109-L125计算了 `pollingCount` 但从未写回 Redis实际上没有执行恢复操作
- `reducePlatformsProcessSingle`L260-L285在整个代码库中从未被调用死代码
---
### 2.10 部分 fetch 请求无超时设置
**文件**[generat.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/outside/generat.js) / [polling.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/outside/polling.js) / [record.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/outside/record.js)
**问题**`fetch()` 调用未设置超时,外部平台无响应时请求可能挂起极长时间。
---
## 三、中优先级问题Medium
### 3.1 缺少优雅关闭处理
**文件**[index.jsbackend](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/index.js) / [index.js任务队列](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/index.js)
**问题**:没有 SIGTERM/SIGINT 信号处理,进程被 kill 时无法执行清理(关闭 WebSocket 连接、停止健康检查等)。
---
### 3.2 健康检查的 setInterval 堆积风险
**文件**[cluster-manager/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/cluster-manager/index.js)
**问题**L52-L55 使用 `setInterval` 顺序检查实例。如果某次检查耗时超过 interval任务会堆积。建议改用 `setTimeout` 递归模式。
---
### 3.3 负载均衡未使用实际负载数据
**文件**[cluster-manager/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/cluster-manager/index.js)
**问题**`selectInstance` 使用简单轮询Round-Robin不利用已维护的 `load` 字段。建议实现加权轮询或最少连接算法。
---
### 3.4 工作流缓存的深拷贝效率
**文件**[workflow-converter/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/workflow-converter/index.js)
**问题**:多处使用 `JSON.parse(JSON.stringify(...))` 做深拷贝,对于大型工作流效率较低。可考虑使用 `structuredClone`Node 17+ 支持)。
---
### 3.5 前端路由缺少 404 页面
**文件**[router/index.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/router/index.js)
**问题**:没有通配 404 路由,未匹配路径显示空白页面。
---
### 3.6 实例管理端口号计算不可靠
**文件**[Instances.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Instances.vue) L147
**问题**`openAddDialog` 中端口号计算 `8001 + instances.value.length`,如果有删除操作,端口号可能重复。
---
### 3.7 健康检查后不必要的全量刷新
**文件**[instance.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/stores/instance.js) L33-L58
**问题**:每次健康检查操作成功后调用 `fetchInstances()` 重新拉取全部实例,建议仅更新被检查实例的状态。
---
### 3.8 任务列表缺少分页
**文件**[Tasks.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/views/Tasks.vue) / [task.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/stores/task.js)
**问题**`fetchTasks` 一次性拉取所有任务,无 page/limit 参数。数据量大时会造成性能问题。
---
### 3.9 移动端响应式适配缺失
**文件**[MainLayout.vue](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/layouts/MainLayout.vue)
**问题**:侧边栏仅支持折叠模式,无移动端的抽屉式导航适配。
---
### 3.10 垃圾回收 / 清理脚本分散
项目中存在多个一次性清理脚本(`clear_coze_tokens.js`、`clear_old_platforms.js`、`reset_pqtasks.js`、`checkQueue.js`、`check_redis.js`),缺乏统一的运维工具入口。
---
### 3.11 mdWebSocketServer 能力状态 `external` 永远为 0
**文件**[mdWebSocketServer.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/utils/mdWebSocketServer.js)
**问题**L105-L106 只更新了 `this.currentCapacity.internal`,而 `external` 从未更新,始终为 0。
---
### 3.12 消息 key 并发冲突风险
**文件**[messagePersistence.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/redis/messagePersistence.js)
**问题**L33 使用 `Date.now()` 生成 messageKey同一毫秒内多消息会导致 key 冲突,消息丢失。
---
### 3.13 `redis.keys()` 应改为 `SCAN`
**文件**[queueRecovery.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/utils/queueRecovery.js) L112
**问题**`redis.keys()` 是 O(N) 阻塞操作,生产环境应使用 `SCAN` 游标方式遍历。
---
### 3.14 capacityGuard 使用忙等待自旋锁
**文件**[capacityGuard.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/任务队列后端/utils/capacityGuard.js)
**问题**L7-L12 使用 `setTimeout(resolve, 10)` 实现忙等待,高并发时产生大量定时器。建议改用 Promise 队列模式。
---
### 3.15 前端缺少全局错误处理器
**文件**[main.js](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/frontend/src/main.js)
**问题**:没有 `app.config.errorHandler` 设置,未捕获错误不会上报或友好提示。
---
## 四、架构层面建议
### 4.1 项目结构
| 问题 | 建议 |
|------|------|
| 根 package.json 的依赖与实际使用的子项目依赖不一致 | 使用 pnpm workspace 或 nx/turborepo 统一管理 monorepo |
| 任务队列后端目录名含中文(`任务队列后端` | 改为英文目录名,避免跨平台兼容问题 |
| app.js 和 webSocket.js 重复 | 提取公共逻辑到共享模块 |
| .env 文件中含敏感信息Redis 密码、JWT Secret 等) | 从代码仓库中移除(如果已提交,需轮换密钥并清理历史) |
### 4.2 统一日志管理
当前三个子项目backend、message-dispatcher、任务队列后端都使用了 winston
- 部分模块仍使用 `console.log`
- 日志级别和格式未统一
- 缺少结构化日志JSON 格式)支持,不利于日志聚合分析
**建议**:提取共享 logger 包,统一日志格式,支持生产环境 JSON 输出。
### 4.3 统一错误处理
各子项目 HTTP API 的错误响应格式不统一,建议定义统一的错误响应结构(如 `{ code, message, data }`),并在中间件层面统一处理。
### 4.4 环境变量管理
多个 `.env` 文件中存在重复定义(如 REDIS_HOST、JWT_SECRET部分配置优先从 config 文件读取,部分从环境变量读取,读取优先级不统一。建议:
- 使用单一配置加载策略
- 环境变量优先级始终高于配置文件
- 文档化所有配置项
### 4.5 测试覆盖
当前项目没有明显的测试文件(根 package.json 的 `vitest` 仅声明未使用),建议为核心模块添加单元测试,特别是:
- [task-forwarder](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/backend/src/task-forwarder/index.js) 的任务状态机
- [task-scheduler](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/task-scheduler/index.js) 的调度逻辑
- [rate-limit](file:///d:/WebUI/Kexue/comfyui/comfyui-cluster-bridge/message-dispatcher/src/auth/rate-limit.js) 的并发安全性
---
## 五、优先级排序(按修复建议)
| 优先级 | 编号 | 问题 | 预估工时 |
|--------|------|------|----------|
| P0 | 1.1 | 任务状态机断裂(死代码) | 小 |
| P0 | 1.2 | 临时文件泄漏 | 小 |
| P0 | 1.3 | 心跳定时器泄漏 | 小 |
| P0 | 1.4 | Token 刷新失败请求挂起 | 小 |
| P0 | 1.5 | 废弃 API 导致功能失败 | 小 |
| P0 | 1.11 | 配置文件无异常保护 | 小 |
| P0 | 1.12 | 容量配置 falsy 判断错误 | 小 |
| P0 | 1.15 | 监控页面假数据 | 中 |
| P0 | 1.14 | 登录页明文凭据 | 小 |
| P1 | 1.6 | app.js/webSocket.js 重复 | 大 |
| P1 | 1.7 | Worker 无重启机制 | 中 |
| P1 | 1.8 | socketMap 内存泄漏 | 小 |
| P1 | 1.9 | 重启丢弃队列 | 中 |
| P1 | 1.10 | 回调先 200 再处理 | 中 |
| P1 | 1.13 | Config/Instances 重复 | 大 |
| P1 | 1.16-1.20 | message-dispatcher 问题集 | 中 |
| P1 | 2.1 | 内存泄漏Map 无清理) | 中 |
| P1 | 2.4 | Element Plus 图标全量导入 | 小 |
| P2 | 2.2-2.10 | 高优改进项 | 中 |
| P3 | 3.1-3.15 | 中优改进项 | 中-大 |
---
*报告结束。以上问题均仅作分析记录,未对代码进行任何修改。*

View File

@ -61,6 +61,13 @@ class TaskForwarder {
logger.error('处理 execution_error 事件失败:', err);
});
});
webSocketClient.on('disconnected', ({ instanceId }) => {
logger.error(`[TaskForwarder] 收到 disconnected 事件: instanceId=${instanceId}ComfyUI 实例已断开连接`);
this.handleInstanceDisconnect(instanceId).catch(err => {
logger.error('处理 disconnected 事件失败:', err);
});
});
}
async submitTask(workflow, nodeInfoList = [], workflowId = null, instanceId = null, webhookUrl = null, queueTaskId = null) {
@ -298,9 +305,54 @@ class TaskForwarder {
}
}
/**
* 处理 ComfyUI 实例断开连接事件
* ComfyUI 实例关闭时将该实例上所有正在运行/已提交的任务标记为失败
* @param {string} instanceId - 断开连接的实例ID
*/
async handleInstanceDisconnect(instanceId) {
logger.error(`[TaskForwarder] ComfyUI 实例 ${instanceId} 已断开,查找该实例上正在运行的任务...`);
// 1. 立即更新本地实例状态为离线
clusterManager.updateInstanceStatus(instanceId, 'offline');
// 2. 先通知 message-dispatcher 实例已离线,防止在回调期间继续分配任务到该实例
taskQueueClient.sendRegisterMessage();
// 3. 再处理受影响的正在运行任务,回调错误结果
const affectedTasks = [];
for (const [taskId, task] of this.tasks) {
if (task.instanceId === instanceId && (task.status === 'running' || task.status === 'submitted')) {
affectedTasks.push({ taskId, task });
}
}
if (affectedTasks.length > 0) {
logger.error(`[TaskForwarder] 实例 ${instanceId} 上有 ${affectedTasks.length} 个任务受到影响,标记为失败`);
for (const { taskId, task } of affectedTasks) {
task.status = 'failed';
task.completedAt = new Date().toISOString();
task.error = '超出显存';
this.tasks.set(taskId, task);
logger.error(`任务 ${task.id} 因 ComfyUI 实例断开而失败: ${task.error}`);
if (task.webhookUrl) {
await this.sendWebhookCallback(task, null, task.error);
}
if (task.queueTaskId) {
taskQueueClient.notifyTaskComplete(task.queueTaskId, null, task.error);
}
}
} else {
logger.info(`[TaskForwarder] 实例 ${instanceId} 上没有正在运行的任务`);
}
}
/**
* 处理任务执行成功事件
*
*
* 这是文件上传流程的入口点
* 1. 收到ComfyUI的execution_success WebSocket消息后触发
* 2. 调用ComfyUI的/history/{prompt_id} API获取任务输出信息