本节书摘来自华章出版社《微信公众平台开发最佳实践》一 书中的第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所示。