本節書摘來自華章出版社《微信公衆平台開發最佳實踐》一 書中的第2章,第2.2節,作者:方倍工作室,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
登入微信公衆平台背景,微信公衆平台位址:mp.weixin.qq.com,在左側清單中最下方,找到“開發者中心”,如圖2-19所示。

單擊進入開發者中心,可以看到目前有個伺服器配置的資訊,狀态為未啟用,如圖2-20所示。
單擊“修改配置”按鈕,進入配置頁面,如圖2-21所示。
此處的url為上一節中介紹的雲應用的域名,即為cctv15.sinaapp.com,而token在index.php中定義為weixin,encodingaeskey不需要填寫,單擊“随機生成”按鈕,讓系統自動生成一個即可,消息加解密方式選擇“明文模式”,然後單擊“送出”按鈕。彈出确認框,如圖2-22所示。
在彈出的提示框中,單擊“确定”按鈕,相關參數填寫成功,如圖2-23所示。
再單擊右上角的“啟用”按鈕來啟用伺服器的配置。系統彈出提示框,詢問是否确定開啟伺服器配置,如圖2-24所示。
單擊“确定”按鈕将啟用伺服器配置。
如果單擊按鈕後,上方提示“token驗證失敗”,可以重試幾次,有時候微信伺服器不穩定也會造成這樣的情況,并不是程式本身有問題。?啟用成功後界面如圖2-25所示。
這樣就成功配置并啟用了伺服器。
在圖2-21中,微信公衆平台在配置伺服器時,提供了3種加解密的模式供開發者選擇,即明文模式、相容模式、安全模式,選擇相容模式和安全模式前,需在開發者中心填寫aes對稱加密算法的消息加解密密鑰encodingaeskey。公衆賬号用此密鑰對收到的密文消息體進行解密,回複消息體也用此密鑰加密。
明文模式:維持現有模式,沒有适配加解密新特性,消息體明文收發,預設設定為明文模式。
相容模式:公衆平台發送消息内容将同時包括明文和密文,消息包長度增加到原來的3倍左右;公衆賬号回複明文或密文均可,不影響現有消息收發;開發者可在此模式下進行調試。
安全模式(推薦):公衆平台發送消息體的内容隻含有密文,公衆賬号回複的消息體也為密文,建議開發者在調試成功後使用此模式收發消息。
消息體加解密的實作過程如下:
假設本次的開發配置中url為
www.fangbei.org/index.php
接口程式中需要配置以下三項參數:
同時向該接口推送如下xml消息,即一個已加密的消息。
$timestamp = $_get['timestamp’];
$nonce = $_get["nonce"];
$msg_signature = $_get['msg_signature'];
$encrypt_type = $_get['encrypt_type’];<code>`</code>
這些參數将用于加解密過程。
接口程式收到消息後,先進行解密,解密部分代碼如下:
<code>`</code>
在原有的代碼流程中對消息進行處理,完成之後,一個要回複的文本消息如下:
//加密
if ($encrypt_type == 'aes'){
}<code>`</code>
加密後的内容如下:
1 <?php
2 /*
4 copyright 2014 all rights reserved
5 */
6 define("token", "weixin");
7 define("appid", "wxbad0b45542aa0b5e");
8 define("encodingaeskey", "abcdefghijklmnopqrstuvwxyz0123456789abcdefg");
9 require_once('wxbizmsgcrypt.php');
10
11 $wechatobj = new wechatcallbackapitest();
12 if (!isset($_get['echostr'])) {
13 $wechatobj->responsemsg();
14 }else{
15 $wechatobj->valid();
16 }
17
18 class wechatcallbackapitest
19 {
20 //驗證簽名
21 public function valid()
22 {
23 $echostr = $_get["echostr"];
24 $signature = $_get["signature"];
25 $timestamp = $_get["timestamp"];
26 $nonce = $_get["nonce"];
27 $tmparr = array(token, $timestamp, $nonce);
28 sort($tmparr);
29 $tmpstr = implode($tmparr);
30 $tmpstr = sha1($tmpstr);
31 if($tmpstr == $signature){
32 echo $echostr;
33 exit;
34 }
35 }
36
37 //響應消息
38 public function responsemsg()
39 {
40 $timestamp = $_get['timestamp'];
41 $nonce = $_get["nonce"];
42 $msg_signature = $_get['msg_signature'];
43 $encrypt_type = (isset($_get['encrypt_type']) && ($_get['encrypt_type'] == 'aes')) ? "aes" : "raw";
44
45 $poststr = $globals["http_raw_post_data"];
46 if (!empty($poststr)){
47 //解密
48 if ($encrypt_type == 'aes'){
49 $pc = new wxbizmsgcrypt(token, encodingaeskey, appid);
50 $this->logger(" d \r\n".$poststr);
51 $decryptmsg = ""; //解密後的明文
52 $errcode = $pc->decryptmsg($msg_signature, $timestamp, $nonce, $poststr, $decryptmsg);
53 $poststr = $decryptmsg;
54 }
55 $this->logger(" r \r\n".$poststr);
56 $postobj = simplexml_load_string($poststr, 'simplexmlelement', libxml_nocdata);
57 $rx_type = trim($postobj->msgtype);
58
59 //消息類型分離
60 switch ($rx_type)
61 {
62 case "event":
63 $result = $this->receiveevent($postobj);
64 break;
65 case "text":
66 $result = $this->receivetext($postobj);
67 break;
68 }
69 $this->logger(" r \r\n".$result);
70 //加密
71 if ($encrypt_type == 'aes'){
72 $encryptmsg = ''; //加密後的密文
73 $errcode = $pc->encryptmsg($result, $timestamp, $nonce, $encryptmsg);
74 $result = $encryptmsg;
75 $this->logger(" e \r\n".$result);
76 }
77 echo $result;
78 }else {
79 echo "";
80 exit;
81 }
82 }
83
84 //接收事件消息
85 private function receiveevent($object)
86 {
87 $content = "";
88 switch ($object->event)
89 {
90 case "subscribe":
91 $content = "歡迎關注方倍工作室 ";
92 break;
93 }
94
95 $result = $this->transmittext($object, $content);
96 return $result;
97 }
98
99 //接收文本消息
100 private function receivetext($object)
101 {
102 $keyword = trim($object->content);
103 if (strstr($keyword, "文本")){
104 $content = "這是個文本消息";
105 }else if (strstr($keyword, "單圖文")){
106 $content = array();
107 $content[] = array("title"=>"單圖文标題", "description"=>"單圖文内容", "picurl"=>"http://discuz.comli.com/weixin/weather/icon/cartoon.jpg", "url" =>"http://m.cnblogs.com/?u=txw1958");
108 }else if (strstr($keyword, "圖文") || strstr($keyword, "多圖文")){
109 $content = array();
110 $content[] = array("title"=>"多圖文1标題", "description"=>"", "picurl" =>"http://discuz.comli.com/weixin/weather/icon/cartoon.jpg", "url" =>"http://m.cnblogs.com/?u=txw1958");
112 $content[] = array("title"=>"多圖文3标題", "description"=>"", "picurl"=>"http://g.hiphotos.bdimg.com/wisegame/pic/item/18cb0a46f21fbe090d338acc6a600c338644adfd.jpg", "url" =>"http://m.cnblogs.com/?u=txw1958");
113 }else if (strstr($keyword, "音樂")){
114 $content = array();
115 $content = array("title"=>"最炫民族風", "description"=>"歌手:鳳凰傳奇", "musicurl"=>"http://121.199.4.61/music/zxmzf.mp3", "hqmusicurl"=>"http://121.199.4.61/music/zxmzf.mp3");
116 }else{
117 $content = date("y-m-d h:i:s",time())."\n".$object->fromusername."n技術支援 方倍工作室";
118 }
119
120 if(is_array($content)){
121 if (isset($content[0])){
122 $result = $this->transmitnews($object, $content);
123 }else if (isset($content['musicurl'])){
124 $result = $this->transmitmusic($object, $content);
125 }
126 }else{
127 $result = $this->transmittext($object, $content);
128 }
129 return $result;
130 }
131
132 //回複文本消息
133 private function transmittext($object, $content)
134 {
135 $xmltpl = "
136 %s
137 %s
138 %s
139 text
140 %s
141 ";
142 $result = sprintf($xmltpl, $object->fromusername, $object->tousername, time(), $content);
143 return $result;
144 }
145
146 //回複圖文消息
147 private function transmitnews($object, $newsarray)
148 {
149 if(!is_array($newsarray)){
150 return;
151 }
152 $itemtpl = "
153 %s
154 %s
155 %s
156 %s
157
158 ";
159 $item_str = "";
160 foreach ($newsarray as $item){
161 $item_str .= sprintf($itemtpl, $item['title'], $item['description'], $item['picurl'], $item['url']);
162 }
163 $xmltpl = "
164 %s
165 %s
166 %s
167 news
168 %s
169
170 $item_str
171 ";
172
173 $result = sprintf($xmltpl, $object->fromusername, $object->tousername, time(), count($newsarray));
174 return $result;
175 }
176
177 //回複音樂消息
178 private function transmitmusic($object, $musicarray)
179 {
180 $itemtpl = "
181 %s
182 %s
183 %s
184 %s
185 ";
186
187 $item_str = sprintf($itemtpl, $musicarray['title'], $musicarray['description'], $musicarray['musicurl'], $musicarray['hqmusicurl']);
188
189 $xmltpl = "
190 %s
191 %s
192 %s
193 music
194 $item_str
195 ";
196
197 $result = sprintf($xmltpl, $object->fromusername, $object->tousername, time());
198 return $result;
199 }
200
201 //日志記錄
202 public function logger($log_content)
203 {
204 if(isset($_server['http_appname'])){ //sae
205 sae_set_display_errors(false);
206 sae_debug($log_content);
207 sae_set_display_errors(true);
208 }else if($_server['remote_addr'] != "127.0.0.1"){ //local
209 $max_size = 500000;
210 $log_filename = "log.xml";
211 if(file_exists($log_filename) and (abs(filesize($log_filename)) > $max_size)){unlink($log_filename);}
212 file_put_contents($log_filename, date('y-m-d h:i:s').$log_content."rn", file_append);
213 }
214 }
215 }
216 ?><code>`</code>
當我們在送出url和token的時候,有時候會碰到送出不成功的情況,具體有以下幾種:
1)請求url逾時。
這種情況一般是由于伺服器網速或響應速度太慢。可以先重試幾次或者等一段時間再來試,如果還是這樣,則需要考慮更換速度更快、性能更好的伺服器。
2)系統發生錯誤,請稍後重試。
這種情況一般是由于微信伺服器短時間内異常引起的,重試或者過一段時間嘗試即可。
3)token驗證失敗。
這種情況需要具體分析驗證過程被卡在哪一個環節了。我們可以通過調用變量$_server來擷取伺服器和執行環境資訊來分析。
$_server是一個包含了諸如頭資訊(header)、路徑(path),以及腳本位置(script locations)等資訊的數組。這個數組中的項目由web伺服器建立。
這裡我們需要使用以下兩個元素:
$_server[‘remote_addr’](來訪者的ip位址,此處為微信伺服器的ip)
$_server[‘query_string’](查詢請求字元串,此處為微信伺服器發過來的get請求字元串)
将以上兩個變量記錄到日志中。函數定義如下:
define("token", "weixin");
tracehttp();
$wechatobj = new wechatcallbackapitest();
if (isset($_get['echostr'])) {
}else{
當我們送出url和token驗證的時候,程式目錄下應當生成一個log.xml的檔案。内容類似如下:
if($keyword == "?" || $keyword == "?")
{
上述代碼在收到消息後,判斷消息内容是否為問号(包括英文輸入狀态下的問号和中文輸入狀态下的問号),如果是問号,則将目前時間(包括年、月、日、時、分、秒)作為回複内容,構造成一個消息回複過來。這樣公衆賬号就實作了目前時間的自動回複。
現在我們結合上一節的代碼來分析一下微信公衆平台的消息互動原理。下面的代碼是基于微信公衆平台官方示例代碼修改完善而成的。
signature=6e35c6f3d3279338781047dbffd09426b9ecdee3&echostr=5979420653038092664&
timestamp=1392001400&nonce=1392192345<code>`</code>
上述請求參數說明如表2-1所示。
這個get請求是包含echostr變量的,是以執行valid()方法,在該方法中,又調用了校驗簽名方法checksignature()方法。如果簽名校驗為真,則原樣輸出變量$echostr的值。
加密/校驗流程如下:
1)第33行~第34行将token、timestamp、nonce三個參數進行字典序排序。
2)第35行~第36行将三個參數字元串拼接成一個字元串進行sha1加密。
3)第38行~第42行表示開發者獲得加密後的字元串可與signature對比,辨別該請求來源于微信。
在發送問号的時候,微信伺服器也将會帶上前面三個參數(signature、timestamp、nonce)通路開發者設定的url,同時還會将消息的xml資料包post到url上。xml格式類似如下:
這樣使用者就會收到回複的消息,效果如圖2-26所示。