首页 / 博客 / API·多语言·接口

Node.js 微信机器人开发教程(发消息 + 收回调)

分类:API·多语言·接口 · 标签:Node.js、微信机器人、消息回调

前言

微信作为国内用户基数最大的即时通讯平台,天然具备做消息自动化的土壤:客服自动回复、群管理机器人、消息转发提醒……这类需求在企业和个人开发者中非常普遍。相比 Python,Node.js 凭借事件驱动的异步模型和 Express/Fastify 等轻量 Web 框架,在处理 Webhook 回调时有天然优势——一个进程既能主动发消息,又能实时监听入站回调,代码结构也更自然。

本文面向有一定 Node.js 基础的开发者,完整演示如何搭建一个"发消息 + 收回调"的微信机器人:从环境准备、扫码登录,到主动发送文本/图片,再到配置回调服务器处理接收到的消息,每个环节都会给出可运行的示例代码,并附上防封频率控制的实践建议。


一、整体架构与工作流程

在动手写代码之前,先梳理清楚机器人的运行逻辑,能减少很多调试时间。

1.1 核心流程

扫码登录 → 获取 appId
    ↓
主动发消息(调 HTTP 接口)
    ↓
平台把别人发来的消息 POST 到你的回调地址
    ↓
Node.js 回调服务器解析 → 业务逻辑处理

整个链路分两条线:下行(你主动发消息,走 HTTP POST 到接口平台)和上行(别人发消息给你,平台 Webhook 回调到你的服务器)。两条线相互独立,但共用同一个 appId

1.2 技术选型

职责推荐方案
HTTP 请求(发消息)axios
回调服务器express
进程守护pm2
环境变量管理dotenv

依赖都很轻量,整个项目不需要复杂的构建工具。


二、环境准备

2.1 初始化项目

bashmkdir wechat-bot && cd wechat-bot
npm init -y
npm install axios express dotenv
npm install -D nodemon

2.2 目录结构

wechat-bot/
├── .env              # 配置(不提交 git)
├── config.js         # 统一读取配置
├── api.js            # 封装接口调用
├── server.js         # 回调服务器
├── bot.js            # 业务逻辑入口
└── package.json

2.3 配置文件

.env

iniBASE_URL=https://你的接口域名   # 注册后在官方文档获取
TOKEN=你的Token
APP_ID=你的appId               # 登录后获取
CALLBACK_PORT=3000

config.js

javascriptrequire('dotenv').config();

module.exports = {
  BASE_URL: process.env.BASE_URL,
  TOKEN:    process.env.TOKEN,
  APP_ID:   process.env.APP_ID,
  PORT:     parseInt(process.env.CALLBACK_PORT) || 3000,
};
代码为示例,具体接口地址、字段名称以官方文档为准。

注意事项.env 文件包含 Token 等敏感信息,一定要加入 .gitignore,不要提交到版本控制系统。Token 泄露会导致账号被滥用,建议定期在后台重置 Token。


三、扫码登录获取 appId

appId 是整个机器人的身份标识,每次登录后获得,后续所有接口都要带上它。

3.1 获取登录二维码

api.js(部分):

javascriptconst axios = require('axios');
const { BASE_URL, TOKEN } = require('./config');

const http = axios.create({
  baseURL: BASE_URL,
  headers: { token: TOKEN },  // 鉴权字段名以官方文档为准
  timeout: 10000,
});

/**
 * 获取登录二维码
 * @returns {{ qrUrl: string, uuid: string }}
 */
async function getLoginQrCode() {
  const res = await http.post('/login/getLoginQrCode');
  if (res.data.ret !== 200) throw new Error(res.data.msg);
  return res.data.data; // { qrUrl, uuid }
}

/**
 * 轮询检查登录状态
 * @param {string} uuid
 */
async function checkLogin(uuid) {
  const res = await http.post('/login/checkLogin', { uuid });
  if (res.data.ret !== 200) throw new Error(res.data.msg);
  return res.data.data; // { loginState, appId, ... }
}

module.exports = { http, getLoginQrCode, checkLogin };

3.2 登录流程脚本

javascript// login.js  —— 一次性运行,把 appId 记录到 .env
const qrcode = require('qrcode-terminal');
const { getLoginQrCode, checkLogin } = require('./api');

(async () => {
  console.log('正在获取二维码...');
  const { qrUrl, uuid } = await getLoginQrCode();

  // 在终端打印可扫描的二维码
  qrcode.generate(qrUrl, { small: true });
  console.log('请用微信扫码,等待登录...');

  // 每 3 秒轮询一次
  const timer = setInterval(async () => {
    try {
      const result = await checkLogin(uuid);
      if (result.loginState === 1) {
        console.log('登录成功,appId:', result.appId);
        console.log('请将 appId 写入 .env 的 APP_ID 字段');
        clearInterval(timer);
      }
    } catch (e) {
      console.error('检查登录状态出错:', e.message);
    }
  }, 3000);
})();

运行 node login.js,扫码后终端打印 appId,手动写入 .env

注意事项:登录二维码一般有有效期(通常 2~5 分钟),过期需要重新获取。如果网络条件较差,可以适当把轮询间隔从 3 秒调整到 5 秒,避免因频繁请求被接口限流。扫码完成后不要立即大量调用发消息接口,新账号建议先正常挂机使用 2~3 天,让账号"暖机"后再接入自动化逻辑。


四、主动发消息

登录后就可以调接口发消息了。常见的消息类型:文本、图片、文件、链接卡片。

4.1 封装发消息方法

继续在 api.js 中添加:

javascriptconst { APP_ID } = require('./config');

/**
 * 发送文本消息
 * @param {string} toWxid  收件人微信 ID(或群 ID)
 * @param {string} content 消息内容
 * @param {string[]} [ats]  群聊 @ 列表(可选)
 */
async function sendText(toWxid, content, ats = []) {
  const res = await http.post('/message/postText', {
    appId:   APP_ID,
    toWxid,
    content,
    ats,
  });
  if (res.data.ret !== 200) throw new Error(res.data.msg);
  return res.data.data;
}

/**
 * 发送图片消息
 * @param {string} toWxid
 * @param {string} imgUrl  公网可访问的图片地址
 */
async function sendImage(toWxid, imgUrl) {
  const res = await http.post('/message/postImage', {
    appId:  APP_ID,
    toWxid,
    imgUrl,
  });
  if (res.data.ret !== 200) throw new Error(res.data.msg);
  return res.data.data;
}

/**
 * 发送链接卡片
 */
async function sendLink(toWxid, { title, desc, linkUrl, thumbUrl }) {
  const res = await http.post('/message/postLink', {
    appId:  APP_ID,
    toWxid,
    title,
    desc,
    linkUrl,
    thumbUrl,
  });
  if (res.data.ret !== 200) throw new Error(res.data.msg);
  return res.data.data;
}

module.exports = { http, getLoginQrCode, checkLogin, sendText, sendImage, sendLink };

4.2 使用示例

javascript// demo-send.js
const { sendText, sendImage } = require('./api');

(async () => {
  // 给指定微信号发文本
  await sendText('friend_wxid_xxxxx', '你好,这是机器人发出的消息');

  // 发图片
  await sendImage('friend_wxid_xxxxx', 'https://example.com/photo.jpg');

  console.log('发送完成');
})();

4.3 频率控制(必读)

批量发消息是最容易触发风控的操作,务必遵守以下节奏:

场景建议频率
加好友24 小时内 5–15 个,每 2 小时不超过 5 个
主动给好友发消息加随机间隔(建议 3–10 秒)
群发消息每条间隔 5–15 秒,避免连续相同内容
新账号扫码登录后在线至少 3 天再调接口

delay 函数控制节奏:

javascriptconst delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function batchSend(targets, content) {
  for (const wxid of targets) {
    await sendText(wxid, content);
    // 随机等待 3~8 秒
    const wait = 3000 + Math.random() * 5000;
    await delay(wait);
  }
}

注意事项:发送内容上也要注意多样性,避免完全相同的文案反复群发,容易被识别为营销行为。可以在消息中插入动态变量(如称呼、时间等),让每条消息在文字层面有差异。此外,发送失败(ret 非 200)时不要立即重试,建议记录失败队列,等待一段时间后再统一补发。


五、收消息:配置回调服务器

收消息是机器人的"耳朵"。平台会把别人发来的消息、好友请求等事件,以 HTTP POST 的形式推送到你预先设置好的回调地址。

5.1 启动 Express 回调服务器

server.js

javascriptconst express = require('express');
const { PORT } = require('./config');

const app = express();
app.use(express.json());

// 挂载回调路由(模块化分离)
const callbackRouter = require('./callback');
app.use('/wechat/callback', callbackRouter);

app.listen(PORT, () => {
  console.log(`回调服务器已启动,监听端口 ${PORT}`);
});

module.exports = app;

5.2 处理回调消息

callback.js

javascriptconst express = require('express');
const router = express.Router();
const { sendText } = require('./api');

/**
 * 平台推送的消息结构(示例,以官方文档为准):
 * {
 *   appId:      "你的appId",
 *   fromWxid:   "发件人 wxid",
 *   toWxid:     "收件人 wxid(即机器人自己)",
 *   type:        1,          // 1=文本 3=图片 43=视频 49=链接 ...
 *   content:    "消息内容",
 *   msgId:      "消息ID",
 *   createTime:  1700000000
 * }
 */

router.post('/', async (req, res) => {
  // 必须立即返回 200,否则平台会重复推送
  res.status(200).json({ ret: 200, msg: 'ok' });

  const msg = req.body;
  console.log('收到消息:', JSON.stringify(msg));

  try {
    await handleMessage(msg);
  } catch (err) {
    console.error('处理消息出错:', err.message);
  }
});

async function handleMessage(msg) {
  const { type, fromWxid, content } = msg;

  // 只处理文本消息
  if (type !== 1) return;

  // 关键词自动回复
  if (content.includes('帮助') || content === '?') {
    await sendText(fromWxid, '可用指令:\n帮助 —— 查看此菜单\n时间 —— 查询当前时间');
    return;
  }

  if (content === '时间') {
    const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
    await sendText(fromWxid, `当前北京时间:${now}`);
    return;
  }

  // 默认回复(可注释掉避免无谓回复)
  // await sendText(fromWxid, `收到你的消息:${content}`);
}

module.exports = router;
关键点:必须在处理业务逻辑之前先响应 200,否则平台会超时重试,导致同一条消息被处理多次。

5.3 设置回调地址

回调服务器必须部署在公网可访问的地址(本地调试可用 ngrok/frp 做内网穿透)。

javascript// set-callback.js  —— 运行一次即可
const { http } = require('./api');
const { APP_ID } = require('./config');

(async () => {
  const res = await http.post('/login/setCallback', {
    appId:       APP_ID,
    callbackUrl: 'https://your-server.com/wechat/callback', // 替换为你的公网地址
  });
  console.log(res.data.ret === 200 ? '回调地址设置成功' : res.data.msg);
})();

注意事项:回调地址必须是 HTTPS,很多平台不接受 HTTP 回调。如果使用 ngrok 调试,每次重启 ngrok 都会产生新的域名,需要重新调用 set-callback.js 更新。生产环境建议使用固定域名并配置 SSL 证书,可以用 Let's Encrypt 免费申请。另外,回调服务器应当对请求来源做基本校验(比如验证请求头中的签名字段),防止外部伪造回调请求触发业务逻辑。


六、把回调与发消息整合到一起

有了发消息和收消息两个模块后,把它们组合成一个完整的机器人进程:

bot.js

javascriptrequire('./server');   // 启动回调服务器

const { checkOnline } = require('./api');
const { APP_ID } = require('./config');

// 定期检查在线状态,断线告警
setInterval(async () => {
  try {
    const res = await checkOnline(APP_ID);
    if (!res.isOnline) {
      console.warn('[警告] 账号已掉线,请重新扫码登录');
    }
  } catch (e) {
    console.error('心跳检测失败:', e.message);
  }
}, 60 * 1000); // 每分钟检查一次

console.log('微信机器人已启动');

用 pm2 守护进程:

bashnpm install -g pm2
pm2 start bot.js --name wechat-bot
pm2 save
pm2 startup   # 设置开机自启

整合注意事项:心跳检测发现账号掉线后,当前代码只会打印警告,实际生产场景建议接入钉钉/邮件告警,以便及时感知异常。pm2 的日志默认存放在 ~/.pm2/logs/ 目录,可以通过 pm2 logs wechat-bot 实时查看,出现问题时优先检查日志里的报错信息。


七、消息类型扩展

文本只是最基础的消息类型,实际场景中还需要处理图片、文件、语音等。下面列出常见类型的处理思路:

7.1 接收图片并下载

javascript// 在 handleMessage 中
if (type === 3) {
  // type=3 为图片消息,content 里通常包含图片信息
  // 需要调用下载接口获取图片内容,以官方文档为准
  console.log('收到图片消息,msgId:', msg.msgId);
  // 建议用队列处理,不要在回调里同步下载
}

7.2 群聊 @ 识别

javascript// 群消息中,fromWxid 通常是"群ID@chatroom"
function isGroupMsg(fromWxid) {
  return fromWxid.endsWith('@chatroom');
}

// 判断是否 @ 了机器人(content 里通常含 @昵称)
function isMentioned(content, botNickname) {
  return content.includes(`@${botNickname}`);
}

7.3 消息去重

网络不稳定时,平台可能重发同一条回调:

javascriptconst processedMsgIds = new Set();

async function handleMessage(msg) {
  if (processedMsgIds.has(msg.msgId)) return; // 已处理
  processedMsgIds.add(msg.msgId);

  // 防止 Set 无限增长:超过 1000 条清空(简单方案)
  if (processedMsgIds.size > 1000) processedMsgIds.clear();

  // ... 正常业务处理
}

扩展注意事项:消息去重的 Set 是内存结构,重启进程后会清空,重启瞬间如果平台补发了旧消息可能出现重复处理。对于幂等性要求较高的业务(比如转账通知、工单创建),建议把 msgId 持久化到 Redis 或数据库,并设置合理的过期时间(如 24 小时)。图片、文件类消息的内容通常不直接放在回调体里,而是以媒体 ID 或 CDN 链接形式下发,下载时注意链接的有效期,及时转存到自己的存储服务。


八、常见问题排查

问题可能原因解决方法
收不到回调消息回调地址不可公网访问检查服务器防火墙/端口,或用 ngrok 穿透
收不到回调消息setCallback 未调用或失败重新运行 set-callback.js 确认返回 200
发消息失败 ret 非 200appId 过期或账号掉线调用 checkOnline 确认在线状态
消息重复处理回调响应超时导致重试确保第一行就 res.status(200).json(...)
接口频率报错调用过于密集加 delay 控制节奏,参考第四节频率表
新账号发消息失败在线天数不足扫码登录后保持在线至少 3 天再批量调用

对于需要托管 HTTP 接口、不想自己维护底层通信的场景,WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,HTTP 调用即可,可查阅其接口文档和 appId 获取流程。


总结

本文从项目初始化开始,完整实现了一个基于 Node.js 的微信机器人:用 axios 调 HTTP 接口主动发送文本和图片,用 Express 搭建回调服务器处理平台推送的消息,并整合成一个统一的机器人进程用 pm2 守护运行。频率控制、消息去重、群聊 @ 识别这些工程细节也都有覆盖。

开发过程中几个容易踩坑的地方值得再次强调:回调接口必须先返回 200 再执行业务逻辑、.env 文件不要提交 git、新账号先暖机再调接口、批量发送时务必加随机间隔。这些细节处理得好,机器人才能稳定长期运行而不触发风控。

实际落地时,建议先在测试号上充分验证业务逻辑,再对接生产账号,稳健操作比追求速度更重要。接口字段和行为细节以官方文档为准。

想动手试试?

WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,注册后几分钟跑通。

立即免费注册查看开发文档

相关产品页

🔗 微信机器人开发(产品页)🔗 微信客服机器人(产品页)🔗 微信群管理机器人(产品页)

相关文章

微信API接口返回失败/收不到消息?完整排查清单微信 API 怎么对接?Python 发出第一条消息实战个人微信API能力清单:消息/好友/群/朋友圈接口一览微信API鉴权与Token机制详解(含请求头示例)
© 2025 WechatApi · 企业级微信智能机器人接入平台
官网价格帮助文档博客
苏ICP备2024128799号 · 苏ICP备2023038368号