企业微信 AI 机器人 PHP SDK — 基于 WebSocket 长连接,免回调地址配置。
PHP 是世界上最好的语言。Node.js 有的 SDK,PHP 必须也得有。
别人用回调地址、公网 IP、SSL 证书搞得焦头烂额的时候,我们 PHP 开发者只需要一行
composer require,三行代码,原地起飞。
企业微信官方提供了 AI 机器人的 WebSocket 长连接通道(wss://openws.work.weixin.qq.com),Node.js 有官方 SDK(@wecom/aibot-node-sdk),但 PHP 生态一片空白。
WeComAiBot 填补了这个空白。它是企业微信 AI 机器人的 PHP SDK,基于 Workerman 实现 WebSocket 客户端长连接。
翻译成人话就是:
- 不用配置回调地址(不需要公网 IP、域名、SSL 证书)
- 不用处理 5 秒超时(WebSocket 没有这个限制)
- 不用搞消息队列(长连接天然异步)
- 在内网、本地、开发机上都能跑
composer require装完就能用
- 零门槛接入 — 免回调地址,免 SSL,免公网 IP,内网也能跑
- 三行代码启动 — 配置 bot_id + secret,注册回调,
start(),完事 - 流式回复 — 支持"思考中"加载动画 → 逐步更新 → 最终回复,就像 ChatGPT 那样
- 主动推送 — 不用等用户说话,机器人可以主动找人聊天
- 模板卡片 — 推送交互式卡片,监听按钮点击,实时更新卡片状态
- 全消息类型 — 文本、语音(转文字)、图片、文件、图文混排、引用消息,全都支持
- 断线自动重连 — 指数退避 + 随机抖动,心跳保活,网不好也不怕
- 串行发送队列 — 帧发送后等 ack 再发下一帧,超时自动跳过,告别消息丢失和乱序
- 多机器人管理 — 一个进程跑多个 bot,各自独立连接、独立重连,数据完全隔离
- Laravel 深度集成 — Service Provider 自动注册,
php artisan wecom:serve一键启动 - 纯 PHP 实现 — 基于 Workerman,不需要装 Swoole 扩展,
composer require就行
composer require bangbangda/wecomaibot就这么简单。没有第二步。
<?php
require __DIR__ . '/vendor/autoload.php';
use WeComAiBot\WeComBot;
use WeComAiBot\Message\Message;
use WeComAiBot\Message\Reply;
$bot = new WeComBot([
'bot_id' => 'your-bot-id', // 企微后台获取
'secret' => 'your-secret', // 企微后台获取
]);
// 收到消息,回复
$bot->onMessage(function (Message $message, Reply $reply) {
$reply->text("你好!你说的是:{$message->text}");
});
$bot->start();php your-bot.php start没了。三行核心代码,机器人就活了。
1. 发布配置:
php artisan vendor:publish --tag=wecomaibot-config2. 配置 .env:
WECOM_BOT_ID=your-bot-id
WECOM_BOT_SECRET=your-bot-secret3. 写一个 Handler:
// app/Services/WeComHandler.php
namespace App\Services;
use WeComAiBot\Message\Message;
use WeComAiBot\Message\Reply;
class WeComHandler
{
public function onMessage(Message $message, Reply $reply): void
{
$reply->text("Echo: {$message->text}");
}
}4. 在配置中指定:
// config/wecomaibot.php
'handler' => \App\Services\WeComHandler::class,5. 启动:
php artisan wecom:serve像 ChatGPT 一样,先显示加载动画,再逐步输出回复内容:
use Workerman\Timer;
$bot->onMessage(function (Message $message, Reply $reply) {
// 第 1 步:立即发送"思考中",企微显示加载动画
$reply->stream('<think></think>', finish: false);
// 第 2 步:调用 AI 接口(这里用 Timer 模拟延迟)
Timer::add(2, function () use ($reply, $message) {
// 第 3 步:发送最终回复,加载动画消失
$reply->stream("关于「{$message->text}」,我的回答是...", finish: true);
}, [], false);
});注意: Workerman 事件循环中不能用
sleep(),要用Timer::add()实现延迟。
不用等用户说话,机器人主动找人。支持单聊和群聊,自动区分会话类型:
$bot->onAuthenticated(function () use ($bot) {
// 推送给指定用户(单聊,chat_type=1)
$bot->pushToUser('zhangsan', '你好,提醒你下午 3 点有个会议');
// 推送到群聊(chat_type=2)
$bot->pushToGroup('group-chat-id', '各位注意,系统将在 5 分钟后维护');
// 自动判断(chat_type=0,兼容旧代码)
$bot->sendMessage('zhangsan', '这条消息会自动判断会话类型');
});前提: 用户需先在会话中给机器人发过消息,机器人才能主动推送。
频率限制: 30 条/分钟,1000 条/小时(与回复消息共享配额)。
监听发送结果(ack 回调):
$bot->pushToUser('zhangsan', '重要通知', function (int $errcode) {
if ($errcode === 0) {
echo "发送成功\n";
} elseif ($errcode === -1) {
echo "发送超时\n";
} else {
echo "发送失败: errcode={$errcode}\n";
}
});支持主动推送模板卡片、监听按钮点击事件、更新卡片状态。卡片结构体由调用者定义,SDK 直接透传,灵活且无约束。
// 推送给用户(单聊)
$bot->pushTemplateCardToUser('zhangsan', [
'card_type' => 'button_interaction',
'main_title' => ['title' => '服务器告警', 'desc' => 'CPU 超过 90%'],
'button_list' => [
['text' => '确认', 'style' => 1, 'key' => 'confirm'],
['text' => '误报', 'style' => 2, 'key' => 'false_alarm'],
],
'task_id' => 'ALERT_001',
]);
// 推送到群聊
$bot->pushTemplateCardToGroup('group_chatid', [
'card_type' => 'button_interaction',
'main_title' => ['title' => '审批通知', 'desc' => '张三提交了报销申请'],
'button_list' => [
['text' => '同意', 'style' => 1, 'key' => 'approve'],
['text' => '拒绝', 'style' => 2, 'key' => 'reject'],
],
'task_id' => 'APPROVAL_001',
]);$bot->onTemplateCardEvent(function (Event $event) use ($bot) {
$eventKey = $event->eventData['event_key'] ?? '';
$taskId = $event->eventData['task_id'] ?? '';
// 5 秒内更新卡片状态
$bot->updateTemplateCard($event->reqId, [
'card_type' => 'button_interaction',
'main_title' => ['title' => '服务器告警', 'desc' => '已处理'],
'button_list' => [
['text' => '已确认', 'style' => 1, 'key' => 'confirm'],
],
'task_id' => $taskId,
]);
});注意: 收到
template_card_event后必须在 5 秒内更新卡片,否则更新会失败。
除了文本和模板卡片,机器人还能主动推送图片、文件、语音、视频。SDK 自动完成文件上传(分片)+ 消息发送,用户只需传入本地文件路径:
// 推送给用户(单聊)
$bot->pushImageToUser('zhangsan', '/path/to/photo.jpg');
// 推送到群聊
$bot->pushImageToGroup('group-id', '/path/to/photo.jpg');$bot->pushFileToUser('zhangsan', '/path/to/doc.pdf');
$bot->pushFileToGroup('group-id', '/path/to/doc.pdf');$bot->pushVoiceToUser('zhangsan', '/path/to/audio.amr');
$bot->pushVoiceToGroup('group-id', '/path/to/audio.amr');$bot->pushVideoToUser('zhangsan', '/path/to/video.mp4', '标题', '描述');
$bot->pushVideoToGroup('group-id', '/path/to/video.mp4', '标题', '描述');所有媒体推送方法都支持 ack 回调,用法与文本推送一致:
$bot->pushImageToUser('zhangsan', '/path/to/photo.jpg', function (int $errcode) {
if ($errcode === 0) {
echo "图片发送成功\n";
} else {
echo "发送失败: errcode={$errcode}\n";
}
});| 类型 | 格式 | 大小上限 |
|---|---|---|
| 图片 | png / jpg / gif | 2MB |
| 语音 | amr | 2MB |
| 视频 | mp4 | 10MB |
| 普通文件 | 不限 | 20MB |
临时素材有效期: 上传的媒体文件在企微服务端保留 3 天,过期后不可访问。
前提: 与文本推送相同,用户需先在会话中给机器人发过消息,机器人才能主动推送。
// 只处理文本
$bot->onText(function (Message $message, Reply $reply) {
$reply->text("收到文本:{$message->text}");
});
// 只处理图片
$bot->onImage(function (Message $message, Reply $reply) {
$reply->text("收到 " . count($message->imageUrls) . " 张图片");
});
// 只处理语音(企微已自动转文字)
$bot->onVoice(function (Message $message, Reply $reply) {
$reply->text("语音转文字:{$message->text}");
});
// 只处理文件
$bot->onFile(function (Message $message, Reply $reply) {
$reply->text("收到 " . count($message->fileUrls) . " 个文件");
});
// 图文混排
$bot->onMixed(function (Message $message, Reply $reply) {
$reply->text("收到图文消息");
});use WeComAiBot\Event\Event;
// 用户首次进入会话
$bot->onEvent('enter_chat', function (Event $event, Reply $reply) {
$reply->text("你好!我是 AI 助手,有什么可以帮你的?");
});
// 监听所有事件
$bot->onEvent('*', function (Event $event, Reply $reply) {
echo "事件类型:{$event->eventType}\n";
echo "事件时间:" . date('Y-m-d H:i:s', $event->createTime) . "\n";
});超时约束:
enter_chat欢迎语需在 5 秒内回复,流式消息需在 6 分钟内完成,回复消息有效期 24 小时。
收到消息后,Message 对象提供以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
$message->id |
string | 消息唯一 ID |
$message->type |
string | 消息类型 (text/image/voice/file/mixed) |
$message->chatType |
string | 会话类型 (single/group) |
$message->chatId |
string | 会话 ID |
$message->senderId |
string | 发送者 userid |
$message->text |
string | 文本内容(语音消息为转文字结果) |
$message->imageUrls |
string[] | 图片 URL 列表 |
$message->fileUrls |
string[] | 文件 URL 列表 |
$message->quoteContent |
?string | 引用消息的文本内容 |
$message->botId |
string | 接收此消息的机器人 ID(多 bot 场景) |
便捷方法:
$message->isGroup(); // 是否群聊
$message->isDirect(); // 是否私聊
$message->hasText(); // 是否有文本
$message->hasImages(); // 是否有图片
$message->hasFiles(); // 是否有文件
$message->hasQuote(); // 是否有引用企微传输的图片和文件经过 AES-256-CBC 加密。SDK 提供了一键下载解密的方法:
$bot->onImage(function (Message $message, Reply $reply) {
// 下载并自动解密第一张图片
$result = $message->downloadImage();
$result->saveTo('/tmp/photo.jpg');
echo "图片大小: {$result->size()} 字节\n";
// 下载所有图片
$results = $message->downloadAllImages();
});
$bot->onFile(function (Message $message, Reply $reply) {
// 下载并自动解密文件
$result = $message->downloadFile();
echo "文件名: {$result->filename}\n";
$result->saveTo("/tmp/{$result->filename}");
});也可以直接使用 MediaDownloader:
use WeComAiBot\Media\MediaDownloader;
use WeComAiBot\Media\MediaDecryptor;
$downloader = new MediaDownloader();
// 下载并解密
$result = $downloader->download($url, $aesKey);
// 下载并直接保存到目录
$savedPath = $downloader->downloadToFile($url, '/tmp/downloads/', $aesKey);
// 单独解密(已有加密数据的场景)
$decrypted = MediaDecryptor::decrypt($encryptedData, $aesKey);注意: 下载 URL 有效期 5 分钟,请在收到消息后尽快下载。
所有发送的消息都经过串行队列,等待企微服务端确认后再发下一条。回复消息同样支持 ack 回调:
$bot->onMessage(function (Message $message, Reply $reply) {
$reply->stream('处理完成!', finish: true, onAck: function (int $errcode) {
echo "回复ack: {$errcode}\n";
});
});一个进程同时跑多个机器人,每个 bot 拥有独立的连接、心跳、重连和发送队列,数据完全隔离。通过 $message->botId 在共享 handler 中区分消息来源:
use WeComAiBot\BotManager;
use WeComAiBot\Message\Message;
use WeComAiBot\Message\Reply;
// 构造时传入所有机器人配置(以 bot_id 为 key)
$manager = new BotManager([
['bot_id' => 'sales-bot-id', 'secret' => 'sales-bot-secret'],
['bot_id' => 'finance-bot-id', 'secret' => 'finance-bot-secret'],
]);
// 共享 handler:通过 $message->botId 区分来源
$sharedHandler = function (Message $message, Reply $reply) {
$reply->text("Bot {$message->botId} 收到: {$message->text}");
};
$manager->getBot('sales-bot-id')->onMessage($sharedHandler);
$manager->getBot('finance-bot-id')->onMessage($sharedHandler);
// 也可以为特定 bot 注册专属 handler
$manager->getBot('sales-bot-id')->onText(function (Message $message, Reply $reply) {
$reply->text("【销售助手】专属文本处理: {$message->text}");
});
// 一键启动所有机器人
$manager->start();也可以逐个添加:
$manager = new BotManager();
$bot = $manager->addBot(['bot_id' => 'my-bot-id', 'secret' => 'my-secret']);
$bot->onMessage(function (Message $message, Reply $reply) {
$reply->text("收到: {$message->text}");
});
$manager->start();php multi-bot.php startBotManager API:
$manager->addBot($config); // 注册机器人,返回 WeComBot 实例
$manager->getBot('bot-id'); // 按 bot_id 获取已注册的机器人
$manager->getAllBots(); // 获取所有机器人(key 为 bot_id)
$manager->removeBot('bot-id'); // 移除机器人
$manager->start(); // 启动所有机器人(阻塞)botId 透传: Message 和 Event 对象都包含 botId 属性,标识接收此消息/事件的机器人。单 bot 场景下 botId 默认为空字符串,无需关心。
隔离保证: 每个 bot 有独立的 WebSocket 连接,企微服务端只推送该 bot 的消息到对应连接。多 bot 同时断线时,重连采用随机抖动避免冲击服务端。一个 bot 认证失败不影响其他 bot。
在 config/wecomaibot.php 中配置 bots 数组,自动切换为多 bot 模式:
// config/wecomaibot.php
'bots' => [
['bot_id' => 'sales-bot', 'secret' => 'xxx', 'handler' => \App\Bots\SalesHandler::class],
['bot_id' => 'support-bot', 'secret' => 'yyy', 'handler' => \App\Bots\SupportHandler::class],
],启动命令不变:
php artisan wecom:serve配置了
bots数组时,顶层的bot_id/secret/handler将被忽略。全局的ws_url、heartbeat_interval、max_reconnect_attempts会作为默认值合入每个 bot 配置。
$bot = new WeComBot([
'bot_id' => 'xxx', // (必填) 机器人 ID
'secret' => 'xxx', // (必填) 机器人 Secret
'ws_url' => 'wss://...', // (可选) WebSocket 地址,默认官方地址
'heartbeat_interval' => 30, // (可选) 心跳间隔秒数,默认 30
'max_reconnect_attempts' => 100, // (可选) 最大重连次数,-1 为无限
'ack_timeout' => 10, // (可选) 发送帧 ack 超时秒数,默认 10
'logger' => $myLogger, // (可选) 自定义日志,实现 LoggerInterface
]);默认输出到控制台。你可以传入自己的日志实现(兼容 PSR-3 子集):
use WeComAiBot\Support\LoggerInterface;
class MyLogger implements LoggerInterface
{
public function debug(string $message): void { /* ... */ }
public function info(string $message): void { /* ... */ }
public function warning(string $message): void { /* ... */ }
public function error(string $message): void { /* ... */ }
}
$bot = new WeComBot([
'bot_id' => 'xxx',
'secret' => 'xxx',
'logger' => new MyLogger(),
]);机器人是常驻进程,推荐用 Supervisor 守护:
# /etc/supervisor/conf.d/wecom-bot.conf
[program:wecom-bot]
command=php /path/to/your-bot.php start
# Laravel 项目用这个:
# command=php /path/to/artisan wecom:serve
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/wecom-bot.logsudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start wecom-botWorkerman 也支持自带的守护进程模式:
php your-bot.php start -d # 后台运行
php your-bot.php status # 查看状态
php your-bot.php stop # 停止
php your-bot.php restart # 重启| 组件 | 说明 |
|---|---|
| Workerman | PHP 高性能异步框架,提供 WebSocket 客户端和事件循环 |
| AsyncTcpConnection | Workerman 的异步 TCP 连接,支持 ws:// 和 wss:// |
| Timer | Workerman 定时器,用于心跳和重连 |
| 方案 | 问题 |
|---|---|
| HTTP 回调 | 需要公网 IP + 域名 + SSL 证书 + 处理 5 秒超时 |
| Swoole | 需要安装 C 扩展,部署门槛高 |
| ratchet/pawl | 没有内置重连和心跳,要自己造轮子 |
| Node.js SDK | 很好,但我们是 PHP 开发者,我们用 PHP(PHP 是最好的语言.jpg) |
- WebSocket 连接 + 认证 + 心跳 + 断线重连
- 消息收发(文本/语音/图片/文件/混排/引用)
- 流式回复("思考中"加载效果)
- 主动推送消息
- 事件监听(enter_chat 等)
- Laravel Service Provider + Artisan 命令
- 模板卡片消息
- 流式 + 卡片组合回复
- 文件下载 + AES-256-CBC 解密
- 回复消息回执等待(串行队列 + ack 回调)
- 多机器人实例管理(BotManager,数据完全隔离)
Issue 和 PR 都欢迎。
让 PHP 更好玩,让开发者更开心。
"PHP is not dead. PHP is just getting started."
— 每一个还在写 PHP 的开发者