一、WebSocket 在微信机器人里的位置

先明确一点:微信机器人的 API 侧通信协议是 HTTP + Webhook——你发请求用 HTTP,平台推消息给你用 Webhook 回调。WebSocket 不出现在 API 层,而是出现在你自己的应用架构里。

典型场景是:后端通过 Webhook 收到微信消息后,需要实时推给前端页面(比如一个 Web 管理后台或在线客服面板)。如果前端用轮询,每秒请求一次既浪费资源又有延迟;用 WebSocket 建一条长连接,消息到了后端立刻推过去,延迟可以压到毫秒级。

整条链路是这样的:

微信用户发消息
    │
    ▼
平台 Webhook POST ──► 你的后端服务器 ──► WebSocket 推送 ──► 前端页面实时显示

这篇文章讲的就是中间那段:从收到 Webhook 到 WebSocket 推送的实现。

二、技术选型

Node.js + ws 库是最轻量的方案。一个文件搞定 HTTP 服务(接收 Webhook)和 WebSocket 服务(推送前端),不需要引入 Express 或 Koa。

npm init -y
npm install ws

三、服务端实现

核心逻辑:起一个 HTTP 服务接收 Webhook 回调,同时把同一个端口升级为 WebSocket 服务供前端连接。收到微信消息后,广播给所有已连接的前端客户端。

const { WebSocketServer } = require("ws");
const http = require("http");

// 存储所有前端连接
const clients = new Set();

// HTTP 服务:接收 Webhook 回调
const server = http.createServer((req, res) => {
  if (req.method === "POST" && req.url === "/callback") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      try {
        const msg = JSON.parse(body);

        // 组装推给前端的数据
        const pushData = {
          appId: msg.appId,
          msgType: msg.msgType,
          fromUser: msg.fromUser,
          nickName: msg.nickName,
          content: msg.content,
          chatRoomId: msg.chatRoomId || "",
          createTime: msg.createTime,
          // 标记是私聊还是群聊
          chatType: msg.chatRoomId ? "group" : "private",
        };

        // 广播给所有连接的前端
        const payload = JSON.stringify(pushData);
        for (const client of clients) {
          if (client.readyState === 1) {
            // 1 = OPEN
            client.send(payload);
          }
        }

        console.log(`收到消息来自 ${msg.nickName},已推送给 ${clients.size} 个客户端`);
      } catch (e) {
        console.error("解析失败:", e.message);
      }

      // 必须返回 {"ret": 200},否则平台会重推
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end('{"ret": 200}');
    });
  } else {
    res.writeHead(404);
    res.end();
  }
});

// WebSocket 服务:前端连接进来
const wss = new WebSocketServer({ server, path: "/ws" });

wss.on("connection", (ws, req) => {
  clients.add(ws);
  console.log(`前端已连接,当前在线 ${clients.size} 个`);

  // 发送一条欢迎消息
  ws.send(JSON.stringify({ type: "connected", msg: "WebSocket 已连接" }));

  ws.on("message", (data) => {
    // 前端发来的消息,可以做心跳处理
    const text = data.toString();
    if (text === "ping") {
      ws.send("pong"); // 心跳响应
    }
  });

  ws.on("close", () => {
    clients.delete(ws);
    console.log(`前端断开,当前在线 ${clients.size} 个`);
  });

  ws.on("error", () => {
    clients.delete(ws);
  });
});

server.listen(8080, () => {
  console.log("服务启动: HTTP回调 http://localhost:8080/callback");
  console.log("           WebSocket ws://localhost:8080/ws");
});

四、前端连接实现

前端浏览器原生支持 WebSocket,不需要额外库:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>消息实时面板</title></head>
<body>
  <div id="messages" style="max-height: 500px; overflow-y: auto;"></div>

  <script>
    const ws = new WebSocket("ws://localhost:8080/ws");

    // 连接成功
    ws.onopen = () => {
      document.getElementById("messages").innerHTML +=
        "<p style='color:green'>已连接</p>";
      // 启动心跳,每 30 秒发一次
      setInterval(() => {
        if (ws.readyState === WebSocket.OPEN) ws.send("ping");
      }, 30000);
    };

    // 收到消息
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      const div = document.getElementById("messages");
      const time = new Date().toLocaleTimeString();
      const typeLabel = data.chatType === "group" ? "[群聊]" : "[私聊]";
      div.innerHTML +=
        `<p>${time} ${typeLabel} <b>${data.nickName || data.fromUser}</b>: ${data.content}</p>`;
      div.scrollTop = div.scrollHeight; // 自动滚到底
    };

    // 连接断开
    ws.onclose = () => {
      document.getElementById("messages").innerHTML +=
        "<p style='color:red'>连接断开,5秒后重连</p>";
      setTimeout(() => location.reload(), 5000);
    };

    ws.onerror = () => {
      console.error("WebSocket 错误");
    };
  </script>
</body>
</html>

五、心跳与断线重连

WebSocket 长连接最大的问题是:网络波动、服务重启后连接会断掉。上面前端代码里已经有了一个简单的重连(刷新页面),但生产环境需要更健壮的方案:

后端心跳检测

// 每 60 秒检查一次所有连接
setInterval(() => {
  for (const client of clients) {
    if (client.readyState === 1) {
      // 发 ping,如果客户端没回 pong 说明连接已死
      client.ping();
    } else {
      clients.delete(client);
    }
  }
}, 60000);

前端指数退避重连

let reconnectCount = 0;

function connect() {
  const ws = new WebSocket("ws://localhost:8080/ws");

  ws.onopen = () => {
    reconnectCount = 0; // 重置计数
    console.log("已连接");
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log("收到:", data);
  };

  ws.onclose = () => {
    reconnectCount++;
    // 1秒、2秒、4秒、8秒...最多 30 秒
    const delay = Math.min(1000 * Math.pow(2, reconnectCount), 30000);
    console.log(`${delay / 1000}秒后重连`);
    setTimeout(connect, delay);
  };
}

connect();

六、消息格式设计

推给前端的数据结构建议统一格式,方便前端解析:

{
  "appId": "应用ID",
  "msgType": 1,
  "fromUser": "wxid_abc123",
  "nickName": "张三",
  "content": "你好",
  "chatRoomId": "",
  "chatType": "private",
  "createTime": 1697000000
}

msgType 的常见值:

msgType含义
1文本消息
3图片消息
49链接/卡片消息

前端根据 msgType 渲染不同的展示样式。

七、生产部署的几个要点

1. Nginx 反向代理 WebSocket

WebSocket 走 HTTP 升级协议,Nginx 配置需要加 Upgrade 头:

location /ws {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400; # 长连接超时设长
}

2. 多实例广播

如果后端起了多个进程(比如用 PM2 cluster 模式),一个 Webhook 进程收到的消息需要广播到其他进程的 WebSocket 客户端。用 Redis Pub/Sub 做进程间通信:

const redis = require("redis");
const sub = redis.createClient();
const pub = redis.createClient();

// 收到 Webhook 后发布到 Redis
sub.subscribe("wx_messages");
sub.on("message", (channel, message) => {
  for (const client of clients) {
    if (client.readyState === 1) client.send(message);
  }
});

// Webhook 收到消息时
function onWebhookReceived(msg) {
  pub.publish("wx_messages", JSON.stringify(msg));
}

3. 连接数控制

单个 Node.js 进程的 WebSocket 连接数建议控制在 5000 以内,超过的话用多个实例 + 负载均衡。

八、WebSocket vs 轮询 vs SSE 对比

方案实时性服务器开销实现复杂度双向通信
前端轮询低(1-5秒延迟)高(大量无效请求)最低否
SSE高中低否(只能服务端→前端)
WebSocket最高低(一条连接)中是

如果你的前端只需要看消息不需要发,SSE 也够用;如果前端还要通过同一条连接发送指令(比如"暂停接收"、“切换会话”),WebSocket 更合适。

九、小结

微信机器人的消息实时推送方案里,Webhook 负责收、WebSocket 负责推,两者配合构成了完整的实时链路。WebSocket 不碰 API 层,纯粹是你应用内部的实时通信通道。代码量不大,核心就是 HTTP 服务 + WebSocket 服务 + 广播逻辑。把心跳和重连做好,就能稳定跑在生产环境。

参考资料

接口定义与文档参数说明:weiti.apifox.cn

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐