企业号在回调企业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行:将要回复的内容进行加密,并返回给接口。