微信公眾平台開發中,消息體加解密 是保障數據安全的關鍵環節。官方提供明文、兼容、安全三種模式,生產環境強制要求安全模式 (AES 加密)。本文基於官方 WXBizMsgCrypt 庫,詳細講解加解密流程、參數配置、完整代碼實現、XML 數據結構分析及常見坑點,助你快速接入企業級微信開發。
| 模式 | 特點 | 適用場景 |
|---|---|---|
| 明文模式 | 無加密,XML 明文收發,默認模式 | 僅測試、極簡項目 |
| 兼容模式 | 同時包含明文與密文,消息體約 3 倍大 | 調試過渡期 |
| 安全模式 (推薦) | 純密文收發,需配置 EncodingAESKey | **生產環境強制** |
接收消息 (解密):
msg_signature, timestamp, nonce, encrypt_type=aes<Encrypt><![CDATA[...]]></Encrypt>WXBizMsgCrypt,調用 DecryptMsg() 驗證簽名並解密回復消息 (加密):
ToUserName, FromUserName, CreateTime, MsgType, Content...)EncryptMsg() 加密,生成 Encrypt, MsgSignature, TimeStamp, Nonce/* 配置常量 */
define('TOKEN', 'your_token'); // 公眾平台設置的 Token
define('APPID', 'wx_your_appid'); // 公眾號 AppID
define('ENCODING_AES_KEY', 'your_43_char_key'); // EncodingAESKey
require_once 'WXBizMsgCrypt.php'; // 官方提供的加解密類
<?php
// 入口文件:wechat.php
define('TOKEN', 'weixin');
define('APPID', 'wxbad0b45542aa0b5e');
define('ENCODING_AES_KEY', 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG');
require_once 'WXBizMsgCrypt.php';
$wechat = new WechatCallback();
if (!isset($_GET['echostr'])) {
$wechat->responseMsg(); // 處理業務消息
} else {
$wechat->valid(); // 首次驗證服務器 URL
}
class WechatCallback {
// 1. 服務器驗證
public function valid() {
$echoStr = $_GET['echostr'];
$signature = $_GET['signature'];
$timestamp = $_GET['timestamp'];
$nonce = $_GET['nonce'];
$tmpArr = [TOKEN, $timestamp, $nonce];
sort($tmpArr, SORT_STRING);
$tmpStr = sha1(implode($tmpArr));
if ($tmpStr === $signature) {
echo $echoStr; // 原樣返回 echostr
exit;
}
}
// 2. 處理業務消息
public function responseMsg() {
$timestamp = $_GET['timestamp'];
$nonce = $_GET['nonce'];
$msgSignature = $_GET['msg_signature'];
$encryptType = (isset($_GET['encrypt_type']) && $_GET['encrypt_type'] === 'aes') ? 'aes' : 'raw';
$postStr = file_get_contents('php://input'); // 獲取 Raw POST XML
if (empty($postStr)) { exit; }
$pc = new WXBizMsgCrypt(TOKEN, ENCODING_AES_KEY, APPID);
// --- 解密 ---
if ($encryptType === 'aes') {
$decryptMsg = '';
$errCode = $pc->DecryptMsg($msgSignature, $timestamp, $nonce, $postStr, $decryptMsg);
if ($errCode !== 0) {
$this->logger("Decrypt failed: $errCode\n$postStr");
exit;
}
$postStr = $decryptMsg;
}
$this->logger("Decrypted XML:\n$postStr");
// --- 解析 XML ---
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$msgType = trim($postObj->MsgType);
// --- 業務分發 ---
switch ($msgType) {
case 'event':
$replyXml = $this->handleEvent($postObj);
break;
case 'text':
$replyXml = $this->handleText($postObj);
break;
default:
$replyXml = $this->handleText($postObj); // 默認按文本處理
}
// --- 加密回復 ---
if ($encryptType === 'aes') {
$encryptMsg = '';
$errCode = $pc->EncryptMsg($replyXml, $timestamp, $nonce, $encryptMsg);
if ($errCode !== 0) {
$this->logger("Encrypt failed: $errCode");
exit;
}
$replyXml = $encryptMsg;
$this->logger("Encrypted Reply:\n$replyXml");
}
echo $replyXml;
}
// 3. 事件處理 (關注/取消關注/掃碼/菜單點擊...)
private function handleEvent($obj) {
$content = '';
switch (trim($obj->Event)) {
case 'subscribe':
$content = "歡迎關注方倍工作室!";
break;
case 'CLICK': // 菜單點擊
$content = "您點擊了菜單:" . $obj->EventKey;
break;
}
return $this->transmitText($obj, $content);
}
// 4. 文本消息處理
private function handleText($obj) {
$keyword = trim($obj->Content);
$content = '';
if (strpos($keyword, '文本') !== false) {
$content = "這是一條文本消息回復";
} elseif (strpos($keyword, '單圖文') !== false) {
$content = [[
'Title' => '單圖文標題',
'Description' => '單圖文描述內容',
'PicUrl' => 'https://example.com/image.jpg',
'Url' => 'https://example.com/detail'
]];
} elseif (strpos($keyword, '圖文') !== false || strpos($keyword, '多圖文') !== false) {
$content = [
['Title' => '多圖文1', 'Description' => '', 'PicUrl' => 'img1.jpg', 'Url' => 'url1'],
['Title' => '多圖文2', 'Description' => '', 'PicUrl' => 'img2.jpg', 'Url' => 'url2'],
['Title' => '多圖文3', 'Description' => '', 'PicUrl' => 'img3.jpg', 'Url' => 'url3'],
];
} elseif (strpos($keyword, '音樂') !== false) {
$content = [
'Title' => '最炫民族風',
'Description' => '歌手:鳳凰傳奇',
'MusicUrl' => 'https://example.com/music.mp3',
'HQMusicUrl' => 'https://example.com/hq_music.mp3'
];
} else {
$content = date('Y-m-d H:i:s') . "\n" . $obj->FromUserName . "\n技術支持:方倍工作室";
}
// 根據內容類型選擇回復模板
if (is_array($content)) {
if (isset($content[0])) { // 多條圖文
return $this->transmitNews($obj, $content);
} elseif (isset($content['MusicUrl'])) { // 音樂
return $this->transmitMusic($obj, $content);
}
}
return $this->transmitText($obj, $content);
}
// 5. 回復模板方法
private function transmitText($obj, $content) {
$tpl = "
%s
";
return sprintf($tpl, $obj->FromUserName, $obj->ToUserName, time(), $content);
}
private function transmitNews($obj, $newsArray) {
$itemTpl = "-
";
$items = '';
foreach ($newsArray as $item) {
$items .= sprintf($itemTpl, $item['Title'], $item['Description'], $item['PicUrl'], $item['Url']);
}
$tpl = "
%s
%d
$items
";
return sprintf($tpl, $obj->FromUserName, $obj->ToUserName, time(), count($newsArray));
}
private function transmitMusic($obj, $music) {
$itemTpl = "
";
$item = sprintf($itemTpl, $music['Title'], $music['Description'], $music['MusicUrl'], $music['HQMusicUrl']);
$tpl = "
%s
$item
";
return sprintf($tpl, $obj->FromUserName, $obj->ToUserName, time());
}
// 6. 日誌記錄 (支援 SAE 與本地)
public function logger($log) {
if (isset($_SERVER['HTTP_APPNAME'])) { // SAE 環境
sae_set_display_errors(false);
sae_debug($log);
sae_set_display_errors(true);
} elseif ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') { // 非本地
$file = 'wechat_log.xml';
if (filesize($file) > 500000) @unlink($file);
file_put_contents($file, date('Y-m-d H:i:s') . $log . "\r\n", FILE_APPEND);
}
}
}
1414243737 6074130599188426998
1414243733
simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA) 正確處理 CDATA。EncryptMsg 參數順序為 ($replyXml, $timestamp, $nonce, &$encryptMsg),注意 $timestamp 必須為字符串格式的 Unix 時間戳。WXBizMsgCrypt 庫更新,及時同步修復安全漏洞。WXBizMsgCrypt,避免配置混淆。微信消息加解密雖涉及 AES、SHA1、XML 等多項技術,但官方 WXBizMsgCrypt 庫已封裝好底層細節。關鍵在於:正確獲取 URL 參數 → 準確調用 DecryptMsg/EncryptMsg → 業務邏輯與加解密解耦 → 完善日誌與異常處理。掌握本文流程與代碼模板,即可輕鬆實現企業級安全模式接入,專注於業務創新而非加密細節。