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

PHP 对接微信 API 实战(含回调接收)

分类:API·多语言·接口 · 标签:PHP、微信API、微信开发

前言

在 PHP 项目中对接微信能力,是许多开发者绕不开的课题。无论是给 CRM 系统追加消息通知、给运营平台增加好友管理,还是搭建自动化私域工具,都需要稳定地调用微信相关接口、并可靠地接收微信回推给服务端的消息回调。

然而微信本身并未开放个人号的官方 REST 接口,大多数项目实际上是通过托管 HTTP API 的形式对接。PHP 作为 Web 后端的老牌语言,原生 cURL 扩展和 GuzzleHttp 等 HTTP 客户端库非常完善,处理这类 REST 接口得心应手。

本文从零开始,系统介绍如何在 PHP 中封装微信 HTTP API 的调用层,涵盖扫码登录验证、文本与图片消息发送、联系人与群组管理,以及最容易被忽视的回调接收环节——包括 PHP 如何正确解析回调 JSON、如何响应 200 保证回调不重试、如何把消息异步写入队列。

所有代码均为示例,具体接口路径、字段名称以对接平台官方文档为准。


一、环境准备与 HTTP 客户端封装

1.1 依赖与基础配置

推荐使用 Composer 安装 GuzzleHttp,统一管理 HTTP 请求:

bashcomposer require guzzlehttp/guzzle

项目配置文件(.env 或配置类)中存放鉴权信息:

php<?php
// config/weixin_api.php
return [
    'base_url' => 'https://你的接口域名',   // 注册后在官方文档获取
    'token'    => '你的Token',              // 鉴权 Token,以官方文档字段名为准
    'app_id'   => '你的appId',              // 扫码登录后获得的设备 ID
];

1.2 封装基础客户端

将 HTTP 调用封装成一个独立类,后续所有接口调用都走这里:

php<?php
// src/WeixinClient.php

namespace App\WeixinClient;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

class WeixinClient
{
    private Client $http;
    private string $appId;

    public function __construct(array $config)
    {
        $this->appId = $config['app_id'];
        $this->http  = new Client([
            'base_uri' => rtrim($config['base_url'], '/') . '/',
            'timeout'  => 15,
            'headers'  => [
                'token'        => $config['token'],  // 鉴权字段名以官方文档为准
                'Content-Type' => 'application/json',
                'Accept'       => 'application/json',
            ],
        ]);
    }

    /**
     * 统一 POST 请求
     * @param string $uri    接口路径,例如 "message/postText"
     * @param array  $body   JSON body(不含 appId,内部自动合并)
     * @return array         解析后的响应数组
     * @throws \RuntimeException 接口返回非 200 或网络异常时抛出
     */
    public function post(string $uri, array $body = []): array
    {
        $body['appId'] = $this->appId;

        try {
            $response = $this->http->post($uri, ['json' => $body]);
            $data = json_decode((string)$response->getBody(), true);
        } catch (RequestException $e) {
            throw new \RuntimeException('HTTP 请求失败: ' . $e->getMessage());
        }

        if (!isset($data['ret']) || $data['ret'] !== 200) {
            throw new \RuntimeException(
                sprintf('接口错误 [%s]: %s', $data['ret'] ?? 'unknown', $data['msg'] ?? '')
            );
        }

        return $data['data'] ?? [];
    }
}

这样所有接口都复用同一个 Guzzle 实例,连接池、超时、鉴权 header 统一管理,避免在每个业务方法里重复写 cURL。


二、扫码登录与在线状态

2.1 获取登录二维码

php<?php
// src/Auth/LoginService.php

namespace App\WeixinClient\Auth;

use App\WeixinClient\WeixinClient;

class LoginService
{
    public function __construct(private WeixinClient $client) {}

    /**
     * 拉取登录二维码
     * @return string 二维码图片 URL 或 base64,以文档返回字段为准
     */
    public function getQrCode(): string
    {
        $data = $this->client->post('getLoginQrCode');
        // 具体字段名以官方文档为准,此处假设为 qrCodeUrl
        return $data['qrCodeUrl'] ?? '';
    }

    /**
     * 轮询登录结果
     * @return array 登录成功后包含 appId 等信息
     */
    public function checkLogin(): array
    {
        return $this->client->post('checkLogin');
    }

    /**
     * 检查账号是否在线
     */
    public function checkOnline(): bool
    {
        $data = $this->client->post('checkOnline');
        return (bool)($data['isOnline'] ?? false);
    }
}

实践建议:轮询 checkLogin 时,每次间隔 2 秒,最多轮询 60 次(2 分钟超时),避免频繁请求触发限流。登录后将 appId 持久化到数据库,后续所有接口复用,不必每次重新扫码。

登录态管理是长期稳定运行的基础。建议在数据库中为每个设备单独存储 appId、登录时间戳以及最近一次在线检查时间,并设置定时任务每隔 10 分钟调用一次 checkOnline,若发现掉线则立即触发告警或自动重登流程,从而保证业务不中断。


三、消息发送

3.1 发送文本消息

php<?php
// src/Message/MessageService.php

namespace App\WeixinClient\Message;

use App\WeixinClient\WeixinClient;

class MessageService
{
    public function __construct(private WeixinClient $client) {}

    /**
     * 发送文字消息
     * @param string $toWxid  接收方微信 ID(个人 wxid 或群 ID)
     * @param string $content 消息内容
     * @param string $ats     群内 @ 的 wxid,多个以逗号分隔,不 @ 传空字符串
     */
    public function sendText(string $toWxid, string $content, string $ats = ''): array
    {
        return $this->client->post('message/postText', [
            'toWxid'  => $toWxid,
            'content' => $content,
            'ats'     => $ats,
        ]);
    }

3.2 发送图片消息

php    /**
     * 发送图片消息
     * @param string $toWxid   接收方 ID
     * @param string $imageUrl 图片公网 URL
     */
    public function sendImage(string $toWxid, string $imageUrl): array
    {
        return $this->client->post('message/postImage', [
            'toWxid'   => $toWxid,
            'imageUrl' => $imageUrl,
        ]);
    }

    /**
     * 批量发同一张图:先发一次拿 msgId,后续用 forwardImage 转发
     * 可大幅降低上传流量,具体接口以文档为准
     */
    public function forwardImage(string $toWxid, string $msgId): array
    {
        return $this->client->post('message/forwardImage', [
            'toWxid' => $toWxid,
            'msgId'  => $msgId,
        ]);
    }
}

关于批量发图:如果需要给 100 个用户群发同一张图,应先调用 postImage 上传一次、拿到 msgId,然后循环调用 forwardImage 转发,而非重复上传。每次发送之间加随机延迟(建议 3-10 秒),避免触发频率限制。

消息发送的稳定性保障:在生产环境中,建议对每次发送调用做重试机制——捕获 RuntimeException 后,等待 5 秒再重试一次,最多重试 2 次。同时记录每条发送记录(目标 wxid、内容摘要、时间、是否成功),便于后续排查消息漏发或重复发送等问题。对于定时群发场景,可将待发任务预存到数据库队列,由独立 Worker 进程按间隔消费,避免 Web 进程超时。


四、联系人与群组管理

4.1 联系人操作

php<?php
// src/Contact/ContactService.php

namespace App\WeixinClient\Contact;

use App\WeixinClient\WeixinClient;

class ContactService
{
    public function __construct(private WeixinClient $client) {}

    /** 搜索用户(手机号/微信号) */
    public function search(string $keyword): array
    {
        return $this->client->post('addContacts/search', [
            'keyword' => $keyword,
        ]);
    }

    /** 添加好友 */
    public function addFriend(string $wxid, string $remark = ''): array
    {
        return $this->client->post('addContacts', [
            'wxid'   => $wxid,
            'remark' => $remark,
        ]);
    }

    /** 获取联系人列表 */
    public function getList(): array
    {
        return $this->client->post('fetchContactsList');
    }

    /** 获取联系人详情 */
    public function getDetail(string $wxid): array
    {
        return $this->client->post('getDetailInfo', ['wxid' => $wxid]);
    }
}

加好友频率建议:每 24 小时不超过 5-15 人,每 2 小时不超过 5 人,每次之间加随机等待;新注册账号建议在线稳定 3 天后再执行批量加人操作。

联系人同步策略:建议在本地数据库维护一张联系人镜像表,字段包含 wxid、昵称、备注、标签、入库时间。首次全量拉取后,后续通过回调消息中的好友变更事件做增量更新,而不是每次业务需要都实时调用 fetchContactsList。实时拉取在联系人数量较大时响应较慢,且频繁调用容易触发平台限流。

4.2 群组操作

php<?php
// src/Group/GroupService.php

namespace App\WeixinClient\Group;

use App\WeixinClient\WeixinClient;

class GroupService
{
    public function __construct(private WeixinClient $client) {}

    /** 创建群聊 */
    public function create(array $memberWxids): array
    {
        return $this->client->post('createChatroom', [
            'memberWxids' => $memberWxids,
        ]);
    }

    /** 邀请成员入群 */
    public function inviteMember(string $chatroomId, array $memberWxids): array
    {
        return $this->client->post('inviteMember', [
            'chatroomId'  => $chatroomId,
            'memberWxids' => $memberWxids,
        ]);
    }

    /** 移除群成员 */
    public function removeMember(string $chatroomId, array $memberWxids): array
    {
        return $this->client->post('removeMember', [
            'chatroomId'  => $chatroomId,
            'memberWxids' => $memberWxids,
        ]);
    }

    /** 设置群公告 */
    public function setAnnouncement(string $chatroomId, string $content): array
    {
        return $this->client->post('setChatroomAnnouncement', [
            'chatroomId' => $chatroomId,
            'content'    => $content,
        ]);
    }

    /** 获取群成员列表 */
    public function getMemberList(string $chatroomId): array
    {
        return $this->client->post('getChatroomMemberList', [
            'chatroomId' => $chatroomId,
        ]);
    }
}

群管理注意事项:创建群聊时,初始成员数建议控制在 3-5 人,过多成员一次性拉入容易触发安全拦截。群成员邀请同样需要控制节奏,每次邀请间隔不少于 30 秒。移除成员操作只能由群主或管理员执行,普通成员角色调用该接口会返回权限不足错误,需在业务层提前做角色校验。


五、回调接收(核心难点)

回调是整个对接中最容易出问题的环节。平台会将用户发送给账号的消息,以 HTTP POST 的方式推送到开发者预先设置的公网地址。PHP 需要在收到请求后立即返回 HTTP 200,再异步处理业务逻辑,否则平台会判定回调失败并重试,导致重复消息。

5.1 设置回调地址

php$this->client->post('setCallback', [
    'callbackUrl' => 'https://你的服务器公网域名/callback/receive',
]);

注意:回调地址必须是公网可访问的 HTTPS 地址,本地 localhost 不可用。

5.2 回调接收脚本

php<?php
// public/callback/receive.php

// 第一步:立即返回 200,防止平台判定超时重试
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);

// 第二步:冲刷输出缓冲,让响应先发出去
if (ob_get_level() > 0) {
    ob_end_flush();
}
flush();

// 第三步:关闭连接(FastCGI 环境下有效)
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();
}

// 第四步:异步处理业务(此时 HTTP 连接已断开)
$rawBody = file_get_contents('php://input');
$payload = json_decode($rawBody, true);

if (!$payload || !isset($payload['appId'])) {
    exit(0);
}

// 推入队列,由 Worker 进程异步处理
// 这里示意用 Redis List 作为队列,实际可换成 RabbitMQ/Kafka 等
try {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->rPush('weixin:callback:queue', $rawBody);
} catch (\Exception $e) {
    // 队列写入失败时记录到本地日志,不影响已返回的 200
    error_log('[weixin_callback] queue push failed: ' . $e->getMessage());
}

exit(0);

5.3 回调消息字段说明

回调 payload 的结构因平台而异,以官方文档为准。常见字段如下:

字段名类型说明
appIdstring触发回调的设备 ID
fromWxidstring消息发送方微信 ID
toWxidstring接收方 ID(可能是群 ID)
typeint消息类型(1=文字,3=图片等)
contentstring消息内容或媒体 URL
msgIdstring消息唯一 ID,用于去重
createTimeint消息时间戳(Unix 秒)

5.4 Worker 消费队列

php<?php
// scripts/callback_worker.php
// 用 supervisor 或 systemd 守护进程运行

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

while (true) {
    // 阻塞式弹出,最多等待 5 秒
    $item = $redis->blPop('weixin:callback:queue', 5);
    if (!$item) {
        continue;
    }

    $payload = json_decode($item[1], true);
    if (!$payload) {
        continue;
    }

    handleCallback($payload);
}

function handleCallback(array $payload): void
{
    $type    = $payload['type']    ?? 0;
    $fromId  = $payload['fromWxid'] ?? '';
    $content = $payload['content'] ?? '';
    $msgId   = $payload['msgId']   ?? '';

    // 按消息类型分发处理
    switch ($type) {
        case 1:  // 文本消息
            // TODO: 关键词匹配、自动回复、记录到数据库等
            error_log("[callback] 文本消息来自 {$fromId}: {$content}");
            break;

        case 3:  // 图片消息
            // 图片下载建议放到单独队列,间隔 3-10 秒处理
            error_log("[callback] 图片消息来自 {$fromId}, msgId={$msgId}");
            break;

        default:
            error_log("[callback] 未处理类型 type={$type}");
    }
}

这种"回调脚本秒返 200 + 队列 + Worker 消费"的模式,是处理 Webhook 的标准做法,能从根本上避免超时重试和消息丢失。

回调去重:同一条消息在网络异常或平台重试时可能被推送多次,msgId 是判断重复的唯一依据。建议在 Worker 消费前,先用 msgId 在 Redis 中做一次 SET NX EX 3600 的原子操作,写入成功才处理,否则直接跳过。这样即使回调被重推,业务逻辑也只会执行一次。


六、托管 HTTP API 的选型参考

如果团队不想自行维护微信登录底层(协议对接、多设备管理、断线重连),可以考虑使用现成的托管服务。WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,HTTP 调用即可。

本文所有代码结构均兼容此类 HTTP API 形态:封装好 WeixinClient,更换 base_url 和鉴权 Token 即可切换到任何符合上述接口规范的平台。


七、常见问题排查

7.1 收不到回调消息

按以下顺序逐一排查:

  1. 账号是否在线:调 checkOnline 确认在线状态,离线状态不会推回调。
  2. 回调地址是否公网可达:用 curl 从外网 POST 到你的回调地址,确认能正常响应 200。
  3. 回调是否已正确设置:调 setCallback 后确认接口返回成功,地址拼写无误。
  4. 是否是主动发送的消息:主动发出去的消息不会触发自身的回调,只有对方发来的才会。

7.2 接口返回非 200 错误

错误场景可能原因处理建议
频繁返回操作失败调用频率过高增加随机延迟,降低并发
新号操作受限账号在线时间不足新号至少在线 3 天后再批量操作
消息发送失败内容含敏感词或违规审查内容,参考微信内容规范
图片/文件无法发送URL 不可公网访问确保媒体资源 URL 外网可直接访问

7.3 图片/文件下载建议

调用下载接口时,务必串行处理,每条请求之间间隔 3-10 秒。批量下载场景建议把下载任务放入独立队列,Worker 单线程顺序消费,不要在收到回调的瞬间就同步发起下载请求。


总结

PHP 对接微信 HTTP API 的核心在于三点:用 GuzzleHttp 封装统一客户端简化调用、理解各类消息接口的参数结构、以及用"秒返 200 + 异步队列"模式正确处理回调。把这三块做扎实,剩下的业务逻辑无论是 CRM 集成还是私域自动化,都有坚实的基础可以复用。

想动手试试?

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

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

相关产品页

🔗 微信群管理机器人(产品页)🔗 微信SCRM(产品页)🔗 微信Hook(产品页)

相关文章

微信API接口返回失败/收不到消息?完整排查清单微信 API 怎么对接?Python 发出第一条消息实战Node.js 微信机器人开发教程(发消息 + 收回调)个人微信API能力清单:消息/好友/群/朋友圈接口一览
© 2025 WechatApi · 企业级微信智能机器人接入平台
官网价格帮助文档博客
苏ICP备2024128799号 · 苏ICP备2023038368号