企业号在回调企业URL时,会对消息体本身做AES加密,以XML格式POST到企业应用的URL上;企业号在被动响应时,也需要对数据加密,以XML格式返回给微信。企业号的回复支持文本、图片、语音、视频、图文等格式。
假设企业回调URL为http://www.doucube.com/qiyehao/index.php。
请求如下。
http:// www.doucube.com/qiyehao/index.php?msg_signature=cba357c1cfee7db580b8b7be69979c519dd9e2dd×tamp=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行:将要回复的内容进行加密,并返回给接口。