diff --git a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java index 30dd6ff2..0f350e97 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java @@ -549,7 +549,14 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre /** * 关闭 WebSocket 连接 - * SDK 的 start() 在线程中阻塞运行,通过中断线程来触发停止 + *
+ * 必须主动关闭底层 WebSocket 连接并触发 SDK 的内部清理(停止 pingLoop、 + * 释放 ExecutorService)。仅置空引用会导致旧连接的 pingLoop 和线程池 + * 持续运行,造成文件描述符和线程泄漏,最终使新连接无法建立。 + *
+ * SDK 的 {@code disconnect()} 是 protected 的,无法直接调用。通过反射 + * 获取内部 {@code conn} 字段并发送 close(1000) 触发 SDK 的 + * {@code onClosed → disconnect()} 清理链路。 */ private void stopWebSocket() { cancelSilentDisconnectWatchdog(); @@ -558,6 +565,21 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter implements Stre wsThread.interrupt(); wsThread = null; } + if (wsClient != null) { + try { + // 反射获取 protected conn 字段,发送 close(1000) 触发 SDK 清理 + var connField = wsClient.getClass().getDeclaredField("conn"); + connField.setAccessible(true); + Object conn = connField.get(wsClient); + if (conn != null) { + // conn 是 OkHttp WebSocket,close(1000) 会触发 onClosed → disconnect() + var closeMethod = conn.getClass().getMethod("close", int.class, String.class); + closeMethod.invoke(conn, 1000, "client closed"); + } + } catch (Exception e) { + log.debug("[feishu] Error during WebSocket disconnect: {}", e.getMessage()); + } + } wsClient = null; }