映月读书网 > 微信公众平台开发:从零基础到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行:将要回复的内容进行加密,并返回给接口。