讀古今文學網 > 微信公眾平台開發:從零基礎到ThinkPHP5高性能框架實踐 > 20.1.2 使用回調模式 >

20.1.2 使用回調模式

企業號在回調企業URL時,會對消息體本身做AES加密,以XML格式POST到企業應用的URL上;企業號在被動響應時,也需要對數據加密,以XML格式返回給微信。企業號的回復支持文本、圖片、語音、視頻、圖文等格式。

假設企業回調URL為http://www.doucube.com/qiyehao/index.php。

請求如下。


http:// www.doucube.com/qiyehao/index.php?msg_signature=cba357c1cfee7db580b8b7be69979c519dd9e2dd&timestamp=1480911337&nonce=953484830
  

回調數據格式如下。


<xml>
    <ToUserName><![CDATA[wx82e2c31215d9a5a7]]></ToUserName>
    <Encrypt><![CDATA[zLP6J6XhqxLmeBioy+dT3QCNlMa6gmEJwI7BXz9+RXRxPns7BvHxnVwHvxGZ8Bk
    SntOKIFs9ECpW42SB+aZxk+lp1FTJ+HE+bN4dhCoGN15jWYQmjXD9YdZcjgcTczCJ5Pvxlwwz7pyZnq7n
    0wj1rb179g1x78hHigU9TyyMaa6kxzQUoWsfU5h8z9xs1rpWZ/Prj+6ZMg1MGy0ER4SR1hSVtSttUVn7th
    yGPZ5+UEWq7ZWzHAOXFUOXwv4nVtRzP+Weu/qrBY+TxZYcRDwdISj7IfNfTh53Yy6+LPLEOShXj602OvJ1l
    HVK98D9fumI/9nUZ3C75hvvBBY0HH4tePWwEoNNasb4DKMO6u40iACET+lkrmjuuZP9IuW2aYkLe/ilf3285c
    9u/9EYU0o3sNWznxYazNV/lwW/SMdeISlCHwh8CzKQuIMZJdrU3Mfl2gg3IRSY535b0JxSFDw3Ig==]]></Encrypt>
    <AgentID><![CDATA[24]]></AgentID>
</xml>
  

上述數據的參數說明如下。

1)msg_encrypt為經過加密的密文。

2)AgentID為接收的應用ID,可在應用的設置頁面獲取。

3)ToUserName為企業號的CorpID。

企業號對msg_signature進行校驗,並解密msg_encrypt,得出msg的原文。明文數據如下。


<xml>
    <ToUserName><![CDATA[wx82e2c31215d9a5a7]]></ToUserName>
    <FromUserName><![CDATA[fangbei]]></FromUserName>
    <CreateTime>1480911337</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <AgentID>24</AgentID>
    <Event><![CDATA[click]]></Event>
    <EventKey><![CDATA[COMPANY]]></EventKey>
</xml>
  

根據事件類型,要回復的明文數據如下。


<xml>
    <ToUserName><![CDATA[fangbei]]></ToUserName>
    <FromUserName><![CDATA[wx82e2c31215d9a5a7]]></FromUserName>
    <CreateTime>1480911343</CreateTime>
    <MsgType><![CDATA[news]]></MsgType>
    <ArticleCount>1</ArticleCount>
    <Articles>
        <item>
            <Title><![CDATA[方倍工作室]]></Title>
            <Description><![CDATA]></Description>
            <PicUrl><![CDATA[http:// discuz.comli.com/weixin/weather/icon/cartoon.
            jpg]]></PicUrl>
            <Url><![CDATA[http:// m.cnblogs.com/?u=txw1958]]></Url>
        </item>
    </Articles>
</xml>
  

將上述數據用同樣的加密方法,得到被動響應給微信的數據格式,具體如下。


<xml>
    <Encrypt><![CDATA[jvmTnCtYGdari33cQRWgRWdcsLR5Y19nx4txCFlonki3TQQaNlfdc1Svwj
    EjKJrXeKofBtC8LIK8gurdR5hfo1BjJ3OqX9WznP2N0Ipnto41dF0hPyNqOw5eBv1BOylly2Rxzhctk
    pdS4KPWh70UPjx8vWtMAugkPxZ4REpjEWZoivm2Phq6H0TvLRkNwQlY2D221LjJkDHMskUh7wBeC
    yyrw4UJ/Q5vMd4g/k2V8q5kpgHYvvNLoiN8OSMtWCRYAy+qKV1UglegSilxyuvQRX9vb++wH6ejl
    ZMbD7L/EeO698202WcqWtBycHPkmuWbx58a4TzjHkKPWtY6GBGoU/KfZAVesCQwUA/ZVo5qtEvgh
    4WcUF7u2MYJ72twq0AqdLoD8TtCCSdeK6eoNRKOqm+K0bTrZIt7sR0DhFi8tKrcApU9jaFKR+rKj
    b0hsV+M4U16ca1LCrfqQA+AS6MhI6wBEGc2FyFTfqtgroFB18bETuAnahkrtEb2XIDQUnlXiP6Lk
    7uuyZDlHaRb7sXPgjGWepOQ7Vdo71CP4sh3RlFp8TbBmA3XMkMUllqjaIlrPfxLsipylY+95xCWX
    7rDPPgy5g/6++Sg25XPw0L9ft23LjvuJQWoNABjJVHxjmWbI5bUyYDx/rwzyu/urKWHfrsTmoHvL
    fDYp8vrWcfKte0uMGdfJq2vYlAv3ooKTWoh8altTS2YVS6Wc1xqqQG8FMBISqLUjlIMQN3TaWXvE
    Y5w5vJ4i1/eHaJeSVhsQGnXW63n0W7gCe0DSLian8DQ32uY7Do3eh2/R6t1VsOUKCnL+oeaRcLzh
    nwU+YFIWo7ULiqqPuVFzInN91J6iPKPfw==]]></Encrypt>
    <MsgSignature><![CDATA[df908a6dfe95ae615300ae51eb1af6cf8bf3522d]]></MsgSignature>
    <TimeStamp>1480911337</TimeStamp>
    <Nonce><![CDATA[953484830]]></Nonce>
</xml>
  

上述XML字段解釋如下。

1)msg_encrypt為經過加密的密文。

2)MsgSignature為簽名。

3)TimeStamp為時間戳,Nonce為隨機數,由企業號生成。

使用回調模式的完整代碼如下。


  1 require_once("WXBizMsgCrypt.php");
  2 $encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
  3 $token = "FangBei";
  4 $corpId = "wx82e2c31215d9a5a7";
  5 
  6 class wechatCallbackapiTest extends WXBizMsgCrypt
  7 {
  8     // 驗證URL有效
  9     public function valid
 10     {
 11         $sVerifyMsgSig = $_GET["msg_signature"];
 12         $sVerifyTimeStamp = $_GET["timestamp"];
 13         $sVerifyNonce = $_GET["nonce"];
 14         $sVerifyEchoStr = $_GET["echostr"];
 15 
 16         $sEchoStr = "";
 17         $errCode = $this->VerifyURL($sVerifyMsgSig, $sVerifyTimeStamp, $sVerifyNonce, 
             $sVerifyEchoStr, $sEchoStr);
 18         if ($errCode == 0) {
 19             // 驗證URL成功,將sEchoStr返回
 20             echo $sEchoStr;
 21         }
 22     }
 23 
 24     // 響應消息
 25     public function responseMsg
 26     {
 27         $sReqMsgSig = $_GET['msg_signature'];
 28         $sReqTimeStamp = $_GET['timestamp'];
 29         $sReqNonce = $_GET['nonce'];
 30         $sReqData = $GLOBALS["HTTP_RAW_POST_DATA"];
 31         $sMsg = "";                  // 解析之後的明文
 32         $this->logger(" DE \r\n".$sReqData);
 33 
 34         $errCode = $this->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, 
            $sReqData, $sMsg);
 35         $this->logger(" RR \r\n".$sMsg);
 36         $postObj = simplexml_load_string($sMsg, 'SimpleXMLElement', LIBXML_NOCDATA);
 37         $RX_TYPE = trim($postObj->MsgType);
 38 
 39         // 消息類型分離
 40         switch ($RX_TYPE)
 41         {
 42             case "event":
 43                 $sRespData = $this->receiveEvent($postObj);
 44                 break;
 45             case "text":
 46                 $sRespData = $this->receiveText($postObj);
 47                 break;
 48             default:
 49                 $sRespData = "unknown msg type: ".$RX_TYPE;
 50                 break;
 51         }
 52         $this->logger(" RT \r\n".$sRespData);
 53         // 加密
 54         $sEncryptMsg = "";         // XML格式的密文
 55         $errCode = $this->EncryptMsg($sRespData, $sReqTimeStamp, $sReqNonce, $sEncryptMsg);
 56         $this->logger(" EC \r\n".$sEncryptMsg);
 57         echo $sEncryptMsg;
 58     }
 59 
 60     // 接收事件消息
 61     private function receiveEvent($object)
 62     {
 63         $content = "";
 64         switch ($object->Event)
 65         {
 66             case "subscribe":
 67                 $content = "歡迎關注企業號";
 68                 break;
 69             case "enter_agent":
 70                 $content = "歡迎進入企業號應用";
 71                 break;
 72             default:
 73                 $content = "receive a new event: ".$object->Event;
 74                 break;
 75         }
 76 
 77         $result = $this->transmitText($object, $content);
 78         return $result;
 79     }
 80 
 81     // 接收文本消息
 82     private function receiveText($object)
 83     {
 84         $keyword = trim($object->Content);
 85         $content = time;
 86         $result = $this->transmitText($object, $content);
 87         return $result;
 88     }
 89 
 90     // 回覆文本消息
 91     private function transmitText($object, $content)
 92     {
 93         if (!isset($content) || empty($content)){
 94             return "";
 95         }
 96 
 97         $xmlTpl = "<xml>
 98                    <ToUserName><![CDATA[%s]]></ToUserName>
 99                    <FromUserName><![CDATA[%s]]></FromUserName>
100                    <CreateTime>%s</CreateTime>
101                    <MsgType><![CDATA[text]]></MsgType>
102                    <Content><![CDATA[%s]]></Content>
103                    </xml>";
104         $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time, 
            $content);
105 
106         return $result;
107     }
108 
109     // 日誌記錄
110     public function logger($log_content)
111     {
112         $max_size = 500000;
113         $log_filename = "log.xml";
114         if(file_exists($log_filename) and (abs(filesize($log_filename)) > $max_size)){unlink
            ($log_filename);}
115         file_put_contents($log_filename, date('Y-m-d H:i:s').$log_content."\r\n", 
        FILE_APPEND);
116     }
117 }
118 
119 $wechatObj = new wechatCallbackapiTest($token, $encodingAesKey, $corpId);
120 $wechatObj->logger(' http:// '.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].(empty
    ($_SERVER['QUERY_STRING'])?"":("?".$_SERVER['QUERY_STRING'])));
121 if (!isset($_GET['echostr'])) {
122     $wechatObj->responseMsg;
123 }else{
124     $wechatObj->valid;
125 }
 

可以看到,企業號的回調模式和其他公眾號的加解密方法基本上是一致的。

上述代碼中,加解密消息部分在響應消息的函數responseMsg中,該部分解讀如下。

第27~30行:解析出獲取到的GET參數及微信POST過來的原始XML。

第31~35行:將取到密文寫日誌,然後進行解密。解密後將明文也寫日誌。

第36~52行:解析出XML類型為對象,然後根據事件類型分類處理,並得到要回復的XML明文。

第53~57行:將要回復的內容進行加密,並返回給接口。