bangbangda/wecomaibot

企业微信 AI 机器人 PHP SDK — 基于 WebSocket 长连接,免回调地址配置

Maintainers

Package info

github.com/bangbangda/wecomaibot

pkg:composer/bangbangda/wecomaibot

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 10

Open Issues: 0

v1.2.0 2026-03-17 07:44 UTC

This package is auto-updated.

Last update: 2026-03-17 07:46:53 UTC


README

企业微信 AI 机器人 PHP SDK — 基于 WebSocket 长连接,免回调地址配置。

PHP Version License

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(任何项目都能用)

<?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

没了。三行核心代码,机器人就活了。

Laravel

1. 发布配置:

php artisan vendor:publish --tag=wecomaibot-config

2. 配置 .env

WECOM_BOT_ID=your-bot-id
WECOM_BOT_SECRET=your-bot-secret

3. 写一个 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 回调

所有媒体推送方法都支持 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 对象提供以下属性:

属性 类型 说明
$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 回调)

所有发送的消息都经过串行队列,等待企微服务端确认后再发下一条。回复消息同样支持 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 start

BotManager API:

$manager->addBot($config);             // 注册机器人,返回 WeComBot 实例
$manager->getBot('bot-id');            // 按 bot_id 获取已注册的机器人
$manager->getAllBots();                // 获取所有机器人(key 为 bot_id)
$manager->removeBot('bot-id');         // 移除机器人
$manager->start();                     // 启动所有机器人(阻塞)

botId 透传: MessageEvent 对象都包含 botId 属性,标识接收此消息/事件的机器人。单 bot 场景下 botId 默认为空字符串,无需关心。

隔离保证: 每个 bot 有独立的 WebSocket 连接,企微服务端只推送该 bot 的消息到对应连接。多 bot 同时断线时,重连采用随机抖动避免冲击服务端。一个 bot 认证失败不影响其他 bot。

Laravel 多机器人

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_urlheartbeat_intervalmax_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.log
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start wecom-bot

Workerman 也支持自带的守护进程模式:

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 定时器,用于心跳和重连

为什么不用 XXX?

方案 问题
HTTP 回调 需要公网 IP + 域名 + SSL 证书 + 处理 5 秒超时
Swoole 需要安装 C 扩展,部署门槛高
ratchet/pawl 没有内置重连和心跳,要自己造轮子
Node.js SDK 很好,但我们是 PHP 开发者,我们用 PHP(PHP 是最好的语言.jpg)

Roadmap

  • WebSocket 连接 + 认证 + 心跳 + 断线重连
  • 消息收发(文本/语音/图片/文件/混排/引用)
  • 流式回复("思考中"加载效果)
  • 主动推送消息
  • 事件监听(enter_chat 等)
  • Laravel Service Provider + Artisan 命令
  • 模板卡片消息
  • 流式 + 卡片组合回复
  • 文件下载 + AES-256-CBC 解密
  • 回复消息回执等待(串行队列 + ack 回调)
  • 多机器人实例管理(BotManager,数据完全隔离)

参与贡献

Issue 和 PR 都欢迎。

让 PHP 更好玩,让开发者更开心。

开源协议

MIT

"PHP is not dead. PHP is just getting started."

— 每一个还在写 PHP 的开发者