微信將所有用戶交互(發送消息、關注/取消關注、點擊菜單、掃碼等)統一以 XML 格式 POST 到開發者 URL。關鍵在於解析 MsgType 與 Event 字段。本文通過實際 XML 示例,詳細講解如何精準判斷用戶行為,並附帶日誌記錄代碼,助力調試與審計。
public function responseMsg() {
global $db;
$postStr = $GLOBALS['HTTP_RAW_POST_DATA'];
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
// 關鍵:記錄原始 XML 到文件,便於離線分析
$this->writeLog('msg.txt', $postStr . "\n", 'a+');
$msgType = $postObj->MsgType; // 核心字段
switch ($msgType) {
case 'text': // 用戶發送文本消息
$this->handleText($postObj);
break;
case 'image': // 用戶發送圖片
$this->handleImage($postObj);
break;
case 'voice': // 用戶發送語音
$this->handleVoice($postObj);
break;
case 'event': // 事件推送(關注、取消關注、菜單點擊等)
$this->handleEvent($postObj);
break;
default:
// 其他類型:video, location, link 等
break;
}
}
<xml> <ToUserName><![CDATA[gh_a64528aca3b]]></ToUserName> <FromUserName><![CDATA[oSgH_jveTxJSlFK_6QSiGeVaSyk]]></FromUserName> <CreateTime>1394705044</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[ha]]></Content> <MsgId>5990212551746353512</MsgId> </xml>
判斷:MsgType = text,獲取 Content 內容進行關鍵詞匹配或 NLP 處理。
<xml> <ToUserName><![CDATA[gh_a5218aca3b]]></ToUserName> <FromUserName><![CDATA[oSgH_jveTxJSlFK_6QSiGeVyk]]></FromUserName> <CreateTime>1394706271</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[]]></EventKey> </xml>
判斷:MsgType = event 且 Event = subscribe。此時必須在 5 秒內回復歡迎語,否則微信會提示「該公眾號暫時無法提供服務」。
EventKey 為 qrscene_場景值,可用於渠道統計。EventKey 為空。<xml> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[unsubscribe]]></Event> </xml>
判斷:Event = unsubscribe。無法直接回復消息,只能在業務層標記用戶狀態、清理綁定關係、記錄流失數據。
<xml> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[CLICK]]></Event> <EventKey><![CDATA[MENU_KEY_1]]></EventKey> </xml>
判斷:Event = CLICK,EventKey 為菜單創建時設置的 key,根據 key 分發業務邏輯。
<xml> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[VIEW]]></Event> <EventKey><![CDATA[https://example.com/page]]></EventKey> <MenuId>123456</MenuId> </xml>
判斷:Event = VIEW,微信直接在內置瀏覽器打開鏈接,不會推送到開發者 URL(舊版本會推送,新版本不再推送)。
private function handleEvent($postObj) {
$event = strtolower($postObj->Event);
$fromUsername = $postObj->FromUserName;
$toUsername = $postObj->ToUserName;
$time = time();
$textTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName>...</xml>";
switch ($event) {
case 'subscribe':
// 歡迎語 + 可選:獲取用戶信息入庫
$content = '歡迎關注!我是您的智能助手。';
// 如果是帶參數二維碼
if (strpos($postObj->EventKey, 'qrscene_') === 0) {
$sceneId = str_replace('qrscene_', '', $postObj->EventKey);
$content .= "(來自渠道:{$sceneId})";
}
echo sprintf($textTpl, $fromUsername, $toUsername, $time, 'text', $content);
break;
case 'unsubscribe':
// 標記用戶取消關注,不可回復消息
$this->markUserUnsubscribed($fromUsername);
echo 'success';
break;
case 'click':
$key = $postObj->EventKey;
$content = $this->handleMenuClick($key); // 根據 key 返回內容
echo sprintf($textTpl, $fromUsername, $toUsername, $time, 'text', $content);
break;
case 'scan':
// 掃碼事件(已關注用戶掃帶參數二維碼)
$sceneId = $postObj->EventKey;
$content = "掃碼事件觸發,場景值:{$sceneId}";
echo sprintf($textTpl, $fromUsername, $toUsername, $time, 'text', $content);
break;
default:
echo 'success';
}
}
/**
* 寫入日誌文件
* @param string $filename 文件路徑
* @param string $data 內容
* @param string $mode 模式:a+ 追加寫入並讀取
*/
function writeLog($filename, $data, $mode = 'a+') {
@touch($filename);
$handle = @fopen($filename, $mode);
if ($handle) {
flock($handle, LOCK_EX); // 獨佔鎖,防止併發寫入亂序
fwrite($handle, $data);
ftruncate($handle, strlen($data)); // 僅在 rb+ 模式下需要
fclose($handle);
}
}
/**
* 文件打開模式速查:
* 'r' 唯讀,指針至頭
* 'r+' 讀寫,指針至頭
* 'w' 唯寫,截斷/創建
* 'w+' 讀寫,截斷/創建
* 'a' 追加寫,指針至尾
* 'a+' 追加讀寫,指針至尾
*/
success 或空字符串,避免微信重複推送。libxml_disable_entity_loader(true) 防 XXE,驗證簽名確保消息來源可信。深入掌握:PHP 微信開發技巧匯總、PHP 字符串用法總結、PHP+MySQL 數據庫操作入門 等專題。