類職責
- 驗證服務器簽名(Token 驗證)
- 接收並解析微信推送 XML 消息
- 提供統一回復接口(文本/圖片/語音/視頻/音樂/圖文)
- XML 安全構建(CDATA 區塊防注入)
完整類代碼
<?php
class Wechat {
private $data = [];
public function __construct($token) {
if (!$this->auth($token)) exit;
if (!empty($_GET['echostr'])) exit($_GET['echostr']); // 首次驗證
try {
$xml = file_get_contents("php://input");
$xml = new SimpleXMLElement($xml);
foreach ($xml as $key => $value) {
$this->data[$key] = strval($value);
}
} catch (Exception $e) {
// 非 XML 或解析失敗,靜默忽略
}
}
public function request() {
return $this->data;
}
public function response($content, $type = 'text', $flag = 0) {
// 基礎字段
$this->data = [
'ToUserName' => $this->data['FromUserName'],
'FromUserName' => $this->data['ToUserName'],
'CreateTime' => time(),
'MsgType' => $type,
];
// 類型特定字段
$this->$type($content);
$this->data['FuncFlag'] = $flag;
// 輸出 XML
$xml = new SimpleXMLElement('');
$this->data2xml($xml, $this->data);
exit($xml->asXML());
}
private function text($content) {
$this->data['Content'] = $content;
}
private function music($music) {
list($this->data['Music']['Title'],
$this->data['Music']['Description'],
$this->data['Music']['MusicUrl'],
$this->data['Music']['HQMusicUrl']) = $music;
}
private function news($news) {
$articles = [];
foreach ($news as $key => $value) {
list($articles[$key]['Title'],
$articles[$key]['Description'],
$articles[$key]['PicUrl'],
$articles[$key]['Url']) = $value;
if ($key >= 9) break; // 最多 10 條
}
$this->data['ArticleCount'] = count($articles);
$this->data['Articles'] = $articles;
}
private function data2xml($xml, $data, $item = 'item') {
foreach ($data as $key => $value) {
is_numeric($key) && $key = $item;
if (is_array($value) || is_object($value)) {
$child = $xml->addChild($key);
$this->data2xml($child, $value, $item);
} else {
if (is_numeric($value)) {
$xml->addChild($key, $value);
} else {
$child = $xml->addChild($key);
$node = dom_import_simplexml($child);
$node->appendChild($node->ownerDocument->createCDATASection($value));
}
}
}
}
private function auth($token) {
if (empty($_GET['signature'])) return false;
$tmp = [$_GET['timestamp'], $_GET['nonce'], $token];
sort($tmp, SORT_STRING);
return sha1(implode($tmp)) === $_GET['signature'];
}
}
使用範例:簡單自動回復
<?php
require 'Wechat.php';
$wechat = new Wechat('YOUR_TOKEN');
$msg = $wechat->request();
if ($msg['MsgType'] === 'text') {
$keyword = trim($msg['Content']);
$reply = "你發送了:{$keyword}";
$wechat->response($reply);
}
// 其他類型同理:$wechat->response($musicArr, 'music'); 等
消息類型對照
| MsgType | $content 結構 | 說明 |
| text | string | 純文本 |
| image | ['MediaId'] | 圖片媒體ID |
| voice | ['MediaId'] | 語音媒體ID |
| video | ['MediaId','Title','Description'] | 視頻 |
| music | ['Title','Description','MusicUrl','HQMusicUrl'] | 音樂 |
| news | [[Title,Desc,PicUrl,Url], ...] | 圖文,最多10條 |
擴展建議
- 接入 PSR-3 Logger 記錄收發日誌
- 使用 FastRoute 或自定義路由分發不同消息類型到 Handler 類
- 引入 Redis 鎖 防止並發重複處理(微信重試機制)
- 異步任務:耗時操作推入隊列,立即回復「收到」,後台處理完再客服消息推送
- 加密模式:
encrypt_type=aes 時需解密 Encrypt 字段,回復時加密