diff --git a/1.txt b/1.txt index 1b95ac0..a8f9a14 100644 --- a/1.txt +++ b/1.txt @@ -1,25 +1,15 @@ -2026-04-21 12:32:04 +08:00: 2026-04-21 12:32:04 [info]: [WebSocketClient] executing 消息: node=20, prompt_id=5c665958-abdf-4c6e-829f-eff14c667c3b -2026-04-21 12:32:04 +08:00: 2026-04-21 12:32:04 [info]: [WebSocketClient] 收到消息 from server-1-8194: type=executing, data={"node":"24","display_node":"24","prompt_id":"5c665958-abdf-4c6e-829f-eff14c667c3b"} -2026-04-21 12:32:04 +08:00: 2026-04-21 12:32:04 [info]: [WebSocketClient] executing 消息: node=24, prompt_id=5c665958-abdf-4c6e-829f-eff14c667c3b -2026-04-21 12:34:23 +08:00: 2026-04-21 12:34:23 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=executing, data={"node":"55","display_node":"55","prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1"} -2026-04-21 12:34:23 +08:00: 2026-04-21 12:34:23 [info]: [WebSocketClient] executing 消息: node=55, prompt_id=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=executing, data={"node":"127","display_node":"127","prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1"} -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] executing 消息: node=127, prompt_id=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=executing, data={"node":"126","display_node":"126","prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1"} -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] executing 消息: node=126, prompt_id=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=executing, data={"node":"54","display_node":"54","prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1"} -2026-04-21 12:34:28 +08:00: 2026-04-21 12:34:28 [info]: [WebSocketClient] executing 消息: node=54, prompt_id=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=execution_error, data={"prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1","node_id":"54","node_type":"LatentSyncNode","executed":["55","122","132","130","128","127","129","126","131"],"exception_message":"Face not detected -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [error]: [WebSocketClient] execution_error 消息: {"prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1","node_id":"54","node_type":"LatentSyncNode","executed":["55","122","132","130","128","127","129","126","131"],"exception_message":"Face not detected\n","exception_type":"RuntimeError","traceback":[" File \"/root/ComfyUI/execution.py\", line 524, in execute\n output_data, output_ui, has_subgraph, has_pending_tasks = await get_output_data(prompt_id, unique_id, obj, input_data_all, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb, v3_data=v3_data)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/execution.py\", line 333, in get_output_data\n return_values = await _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, obj.FUNCTION, allow_interrupt=True, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb, v3_data=v3_data)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/execution.py\", line 307, in _async_map_node_over_list\n await process_inputs(input_dict, i)\n"," File \"/root/ComfyUI/execution.py\", line 295, in process_inputs\n result = f(**inputs)\n ^^^^^^^^^^^\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/nodes.py\", line 672, in inference\n inference_module.main(config, args)\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/scripts/inference.py\", line 148, in main\n pipeline(\n"," File \"/opt/miniconda3/lib/python3.12/site-packages/torch/utils/_contextlib.py\", line 124, in decorate_context\n return func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/latentsync/pipelines/lipsync_pipeline.py\", line 373, in __call__\n video_frames, faces, boxes, affine_matrices = self.loop_video(whisper_chunks, video_frames)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/latentsync/pipelines/lipsync_pipeline.py\", line 286, in loop_video\n faces, boxes, affine_matrices = self.affine_transform_video(video_frames)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/latentsync/pipelines/lipsync_pipeline.py\", line 260, in affine_transform_video\n face, box, affine_matrix = self.image_processor.affine_transform(frame)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"," File \"/root/ComfyUI/custom_nodes/ComfyUI-LatentSyncWrapper/latentsync/utils/image_processor.py\", line 59, in affine_transform\n raise RuntimeError(\"Face not detected\")\n"],"current_inputs":{"seed":[507990733],"lips_expression":[1.5],"inference_steps":[25],"images":["tensor([[[[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9608, 0.9490, 0.9804],\n [0.9569, 0.9451, 0.9725],\n [0.9569, 0.9451, 0.9686]],\n\n ...,\n\n [[0.6627, 0.5333, 0.5176],\n [0.6588, 0.5294, 0.5137],\n [0.6510, 0.5216, 0.5059],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784],\n [0.6275, 0.5020, 0.4706]],\n\n [[0.6549, 0.5255, 0.5098],\n [0.6549, 0.5255, 0.5098],\n [0.6471, 0.5216, 0.5059],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784]],\n\n [[0.6353, 0.5059, 0.4902],\n [0.6353, 0.5059, 0.4902],\n [0.6353, 0.5098, 0.4941],\n ...,\n [0.6510, 0.5255, 0.4941],\n [0.6471, 0.5216, 0.4902],\n [0.6392, 0.5137, 0.4824]]],\n\n\n [[[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9608, 0.9490, 0.9804],\n [0.9569, 0.9451, 0.9725],\n [0.9569, 0.9451, 0.9686]],\n\n ...,\n\n [[0.6549, 0.5216, 0.5176],\n [0.6588, 0.5255, 0.5176],\n [0.6588, 0.5294, 0.5098],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784],\n [0.6275, 0.5020, 0.4706]],\n\n [[0.6510, 0.5176, 0.5137],\n [0.6471, 0.5137, 0.5098],\n [0.6549, 0.5255, 0.5059],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784]],\n\n [[0.6353, 0.5020, 0.4980],\n [0.6392, 0.5059, 0.5020],\n [0.6431, 0.5137, 0.4980],\n ...,\n [0.6510, 0.5255, 0.4941],\n [0.6471, 0.5216, 0.4902],\n [0.6392, 0.5137, 0.4824]]],\n\n\n [[[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9608, 0.9490, 0.9804],\n [0.9569, 0.9451, 0.9725],\n [0.9569, 0.9451, 0.9686]],\n\n ...,\n\n [[0.6627, 0.5333, 0.5176],\n [0.6510, 0.5216, 0.5059],\n [0.6471, 0.5176, 0.5020],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784],\n [0.6275, 0.5020, 0.4706]],\n\n [[0.6588, 0.5294, 0.5137],\n [0.6471, 0.5176, 0.5020],\n [0.6471, 0.5176, 0.5020],\n ...,\n [0.6392, 0.5137, 0.4824],\n [0.6392, 0.5137, 0.4824],\n [0.6353, 0.5098, 0.4784]],\n\n [[0.6471, 0.5176, 0.5020],\n [0.6431, 0.5137, 0.4980],\n [0.6431, 0.5137, 0.4980],\n ...,\n [0.6510, 0.5255, 0.4941],\n [0.6471, 0.5216, 0.4902],\n [0.6392, 0.5137, 0.4824]]],\n\n\n ...,\n\n\n [[[0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n ...,\n\n [[0.9333, 0.8941, 0.9176],\n [0.8510, 0.8039, 0.8275],\n [0.7490, 0.6980, 0.7098],\n ...,\n [0.6353, 0.5098, 0.4706],\n [0.6353, 0.5098, 0.4706],\n [0.6314, 0.5059, 0.4667]],\n\n [[0.6980, 0.6157, 0.6392],\n [0.6196, 0.5412, 0.5608],\n [0.5686, 0.4824, 0.4941],\n ...,\n [0.6353, 0.5176, 0.4784],\n [0.6314, 0.5176, 0.4745],\n [0.6275, 0.5137, 0.4706]],\n\n [[0.5451, 0.4549, 0.4784],\n [0.5451, 0.4510, 0.4745],\n [0.5686, 0.4667, 0.4784],\n ...,\n [0.6392, 0.5255, 0.4824],\n [0.6353, 0.5216, 0.4784],\n [0.6314, 0.5176, 0.4745]]],\n\n\n [[[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8431, 0.8314, 0.8706],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9765]],\n\n ...,\n\n [[0.9333, 0.8941, 0.9098],\n [0.8549, 0.8078, 0.8157],\n [0.7412, 0.6784, 0.6824],\n ...,\n [0.6353, 0.5098, 0.4706],\n [0.6275, 0.5137, 0.4706],\n [0.6235, 0.5098, 0.4667]],\n\n [[0.7020, 0.6235, 0.6353],\n [0.6314, 0.5451, 0.5529],\n [0.5725, 0.4745, 0.4784],\n ...,\n [0.6353, 0.5176, 0.4784],\n [0.6314, 0.5176, 0.4745],\n [0.6275, 0.5137, 0.4706]],\n\n [[0.5490, 0.4549, 0.4667],\n [0.5529, 0.4588, 0.4706],\n [0.5882, 0.4706, 0.4824],\n ...,\n [0.6392, 0.5255, 0.4824],\n [0.6353, 0.5216, 0.4784],\n [0.6314, 0.5176, 0.4745]]],\n\n\n [[[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686],\n [0.9569, 0.9451, 0.9686]],\n\n [[0.8353, 0.8314, 0.8667],\n [0.8353, 0.8314, 0.8667],\n [0.8392, 0.8275, 0.8667],\n ...,\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9765],\n [0.9569, 0.9451, 0.9765]],\n\n ...,\n\n [[0.9373, 0.8980, 0.9098],\n [0.8510, 0.8039, 0.8118],\n [0.7373, 0.6745, 0.6784],\n ...,\n [0.6353, 0.5098, 0.4706],\n [0.6275, 0.5137, 0.4706],\n [0.6235, 0.5098, 0.4667]],\n\n [[0.6902, 0.6078, 0.6196],\n [0.6235, 0.5412, 0.5529],\n [0.5843, 0.4824, 0.4902],\n ...,\n [0.6314, 0.5176, 0.4745],\n [0.6314, 0.5176, 0.4745],\n [0.6275, 0.5137, 0.4706]],\n\n [[0.5529, 0.4588, 0.4706],\n [0.5529, 0.4588, 0.4706],\n [0.5882, 0.4745, 0.4863],\n ...,\n [0.6392, 0.5255, 0.4824],\n [0.6353, 0.5216, 0.4784],\n [0.6314, 0.5176, 0.4745]]]])"],"audio":["{'waveform': tensor([[[0.0009, 0.0009, 0.0007, ..., 0.0000, 0.0000, 0.0000]]]), 'sample_rate': 22050}"]},"current_outputs":["55","122","132","130","54","128","127","129","126","81","131"],"timestamp":1776746120290} -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [error]: [TaskForwarder] 收到 execution_error 事件: instanceId=server-1-8190, promptId=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [error]: [TaskForwarder] handleExecutionError: instanceId=server-1-8190, promptId=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [error]: 任务 e511838f-46b5-47a7-80b1-2168598bf4b1 执行失败: Face not detected -2026-04-21 12:35:20 +08:00: -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: 发送Webhook回调到: https://www.whjbjm.com/taskCallback/callback/all -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=status, data={"status":{"exec_info":{"queue_remaining":0}}} -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [WebSocketClient] status 消息: {"status":{"exec_info":{"queue_remaining":0}}} -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [WebSocketClient] 收到消息 from server-1-8190: type=executing, data={"node":null,"prompt_id":"e511838f-46b5-47a7-80b1-2168598bf4b1"} -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [WebSocketClient] executing 消息: node=null, prompt_id=e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: Webhook回调发送成功: e511838f-46b5-47a7-80b1-2168598bf4b1 -2026-04-21 12:35:20 +08:00: 2026-04-21 12:35:20 [info]: [MessageDispatcher] 发送回调到:https://www.whjbjm.com/taskCallback/callback/all -2026-04-21 12:35:21 +08:00: 2026-04-21 12:35:21 [info]: [MessageDispatcher] 回调发送成功:e511838f-46b5-47a7-80b1-2168598bf4b1 \ No newline at end of file +1|backend | 2026-05-18 13:56:14 +08:00: 2026-05-18 13:56:14 [error]: [MessageDispatcher] 提交任务失败: connect ECONNREFUSED 127.0.0.1:8192 +1|backend | 2026-05-18 13:56:14 +08:00: Error: connect ECONNREFUSED 127.0.0.1:8192 +1|backend | 2026-05-18 13:56:14 +08:00: at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1705:16) +1|backend | 2026-05-18 13:56:14 +08:00: 2026-05-18 13:56:14 [info]: [MessageDispatcher] 发送回调到:https://www.whjbjm.com/taskCallback/callback/all +1|backend | 2026-05-18 13:56:14 +08:00: 2026-05-18 13:56:14 [info]: [MessageDispatcher] 回调发送成功:913bee0a-5ff7-4e9e-a117-322bd963a3e4 +1|backend | 2026-05-18 13:56:27 +08:00: 2026-05-18 13:56:27 [warn]: [ComfyUI Monitor] [2026-05-18T05:56:27.345Z] 连接状态变更 | 实例ID: server-1-8192 | 原状态: online | 新状态: offline | 原因: 连接被拒绝 | 配置: {"serverId":"server-1","serverName":"主服务器","ip":"127.0.0.1","port":8192,"apiUrl":"http://127.0.0.1:8192","wsUrl":"ws://127.0.0.1:8192/ws"} +1|backend | 2026-05-18 13:56:27 +08:00: 2026-05-18 13:56:27 [error]: [ComfyUI Monitor] [2026-05-18T05:56:27.346Z] 连接错误 | 实例ID: server-1-8192 | 错误代码: ECONNREFUSED | 错误: connect ECONNREFUSED 127.0.0.1:8192 | 配置: {"serverId":"server-1","serverName":"主服务器","ip":"127.0.0.1","port":8192,"apiUrl":"http://127.0.0.1:8192","wsUrl":"ws://127.0.0.1:8192/ws"} +1|backend | 2026-05-18 13:56:27 +08:00: [后端] 收到消息: { +1|backend | 2026-05-18 13:56:27 +08:00: "type": "REGISTER_ACK", +1|backend | 2026-05-18 13:56:27 +08:00: "data": { +1|backend | 2026-05-18 13:56:27 +08:00: "bridgeId": "bridge-1", +1|backend | 2026-05-18 13:56:27 +08:00: "timestamp": "2026-05-18T05:56:27.393Z" +1|backend | 2026-05-18 13:56:27 +08:00: } +1|backend | 2026-05-18 13:56:27 +08:00: } +1|backend | 2026-05-18 13:56:27 +08:00: 2026-05-18 13:56:27 [info]: [MessageDispatcher] 注册成功 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2b2770a --- /dev/null +++ b/CLAUDE.md @@ -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 转发结果给任务队列后端 → 释放实例锁 → 调度下一个排队任务 diff --git a/OPTIMIZATION_REPORT.md b/OPTIMIZATION_REPORT.md new file mode 100644 index 0000000..18f6ac5 --- /dev/null +++ b/OPTIMIZATION_REPORT.md @@ -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.js(backend)](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 | 中优改进项 | 中-大 | + +--- + +*报告结束。以上问题均仅作分析记录,未对代码进行任何修改。* diff --git a/backend/src/task-forwarder/index.js b/backend/src/task-forwarder/index.js index 7b6f6ee..08f9f8b 100644 --- a/backend/src/task-forwarder/index.js +++ b/backend/src/task-forwarder/index.js @@ -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获取任务输出信息