微信公众平台开发接口PHP SDK完整版
官方提供的SDK只有一个文本消息功能,我们将所有消息的消息类型及事件响应都整理了进来,并且加入日志记录,代码如下:
更新日志:
2013-01-01 版本1.0,包含Token验证及基本消息接口的收发
2014-03-15 增加图片、视频、语音的内容回复
2014-04-09 增加菜单链接事件
2014-04-10 修改文本回复的判定方法
2014-05-20 增加高级群发消息通知事件
2014-05-26 增加多客服消息及多客服的判定方法
2014-05-27 修改自动回复判定方式
2014-06-20 修复多图文回复的Bug
2014-07-10 增加第三方接口处理样式
2014-08-02 增加Emoji表格的回复处理
2014-10-01 增加自定义菜单扫一扫、发图片、发地理位置等接口的处理
2014-10-25 增加消息体签名及加解密的支持
2014-11-07 增加该公众号暂时无法提供服务请稍后再试的兼容
2014-12-20 移除高级群发消息通知事件,必要性不大
2015-02-23 移除消息体签名及加解密的支持,必要性不大
2015-04-07 优化客服模式和自动回复模式的判定
2015-05-16 优化日志记录,兼容SAE和自有主机
1 <?php 2 /* 3 方倍工作室 http://www.fangbei.org/ 4 CopyRight 2015 All Rights Reserved 5 */ 6 7 define("TOKEN", "weixin"); 8 9 $wechatObj = new wechatCallbackapiTest(); 10 if (!isset($_GET['echostr'])) { 11 $wechatObj->responseMsg(); 12 }else{ 13 $wechatObj->valid(); 14 } 15 16 class wechatCallbackapiTest 17 { 18 //验证签名 19 public function valid() 20 { 21 $echoStr = $_GET["echostr"]; 22 $signature = $_GET["signature"]; 23 $timestamp = $_GET["timestamp"]; 24 $nonce = $_GET["nonce"]; 25 $token = TOKEN; 26 $tmpArr = array($token, $timestamp, $nonce); 27 sort($tmpArr, SORT_STRING); 28 $tmpStr = implode($tmpArr); 29 $tmpStr = sha1($tmpStr); 30 if($tmpStr == $signature){ 31 echo $echoStr; 32 exit; 33 } 34 } 35 36 //响应消息 37 public function responseMsg() 38 { 39 $postStr = $GLOBALS["HTTP_RAW_POST_DATA"]; 40 if (!empty($postStr)){ 41 $this->logger("R \r\n".$postStr); 42 $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA); 43 $RX_TYPE = trim($postObj->MsgType); 44 45 if (($postObj->MsgType == "event") && ($postObj->Event == "subscribe" || $postObj->Event == "unsubscribe")){ 46 //过滤关注和取消关注事件 47 }else{ 48 49 } 50 51 //消息类型分离 52 switch ($RX_TYPE) 53 { 54 case "event": 55 $result = $this->receiveEvent($postObj); 56 break; 57 case "text": 58 if (strstr($postObj->Content, "第三方")){ 59 $result = $this->relayPart3("http://www.fangbei.org/test.php".'?'.$_SERVER['QUERY_STRING'], $postStr); 60 }else{ 61 $result = $this->receiveText($postObj); 62 } 63 break; 64 case "image": 65 $result = $this->receiveImage($postObj); 66 break; 67 case "location": 68 $result = $this->receiveLocation($postObj); 69 break; 70 case "voice": 71 $result = $this->receiveVoice($postObj); 72 break; 73 case "video": 74 $result = $this->receiveVideo($postObj); 75 break; 76 case "link": 77 $result = $this->receiveLink($postObj); 78 break; 79 default: 80 $result = "unknown msg type: ".$RX_TYPE; 81 break; 82 } 83 $this->logger("T \r\n".$result); 84 echo $result; 85 }else { 86 echo ""; 87 exit; 88 } 89 } 90 91 //接收事件消息 92 private function receiveEvent($object) 93 { 94 $content = ""; 95 switch ($object->Event) 96 { 97 case "subscribe": 98 $content = "欢迎关注方倍工作室 "; 99 $content .= (!empty($object->EventKey))?("\n来自二维码场景 ".str_replace("qrscene_","",$object->EventKey)):""; 100 break; 101 case "unsubscribe": 102 $content = "取消关注"; 103 break; 104 case "CLICK": 105 switch ($object->EventKey) 106 { 107 case "COMPANY": 108 $content = array(); 109 $content[] = array("Title"=>"方倍工作室", "Description"=>"", "PicUrl"=>"http://discuz.comli.com/weixin/weather/icon/cartoon.jpg", "Url" =>"http://m.cnblogs.com/?u=txw1958"); 110 break; 111 default: 112 $content = "点击菜单:".$object->EventKey; 113 break; 114 } 115 break; 116 case "VIEW": 117 $content = "跳转链接 ".$object->EventKey; 118 break; 119 case "SCAN": 120 $content = "扫描场景 ".$object->EventKey; 121 break; 122 case "LOCATION": 123 $content = "上传位置:纬度 ".$object->Latitude.";经度 ".$object->Longitude; 124 break; 125 case "scancode_waitmsg": 126 if ($object->ScanCodeInfo->ScanType == "qrcode"){ 127 $content = "扫码带提示:类型 二维码 结果:".$object->ScanCodeInfo->ScanResult; 128 }else if ($object->ScanCodeInfo->ScanType == "barcode"){ 129 $codeinfo = explode(",",strval($object->ScanCodeInfo->ScanResult)); 130 $codeValue = $codeinfo[1]; 131 $content = "扫码带提示:类型 条形码 结果:".$codeValue; 132 }else{ 133 $content = "扫码带提示:类型 ".$object->ScanCodeInfo->ScanType." 结果:".$object->ScanCodeInfo->ScanResult; 134 } 135 break; 136 case "scancode_push": 137 $content = "扫码推事件"; 138 break; 139 case "pic_sysphoto": 140 $content = "系统拍照"; 141 break; 142 case "pic_weixin": 143 $content = "相册发图:数量 ".$object->SendPicsInfo->Count; 144 break; 145 case "pic_photo_or_album": 146 $content = "拍照或者相册:数量 ".$object->SendPicsInfo->Count; 147 break; 148 case "location_select": 149 $content = "发送位置:标签 ".$object->SendLocationInfo->Label; 150 break; 151 default: 152 $content = "receive a new event: ".$object->Event; 153 break; 154 } 155 156 if(is_array($content)){ 157 if (isset($content[0]['PicUrl'])){ 158 $result = $this->transmitNews($object, $content); 159 }else if (isset($content['MusicUrl'])){ 160 $result = $this->transmitMusic($object, $content); 161 } 162 }else{ 163 $result = $this->transmitText($object, $content); 164 } 165 return $result; 166 } 167 168 //接收文本消息 169 private function receiveText($object) 170 { 171 $keyword = trim($object->Content); 172 //多客服人工回复模式 173 if (strstr($keyword, "请问在吗") || strstr($keyword, "在线客服")){ 174 $result = $this->transmitService($object); 175 return $result; 176 } 177 178 //自动回复模式 179 if (strstr($keyword, "文本")){ 180 $content = "这是个文本消息"; 181 }else if (strstr($keyword, "表情")){ 182 $content = "中国:".$this->bytes_to_emoji(0x1F1E8).$this->bytes_to_emoji(0x1F1F3)."\n仙人掌:".$this->bytes_to_emoji(0x1F335); 183 }else if (strstr($keyword, "单图文")){ 184 $content = array(); 185 $content[] = array("Title"=>"单图文标题", "Description"=>"单图文内容", "PicUrl"=>"http://discuz.comli.com/weixin/weather/icon/cartoon.jpg", "Url" =>"http://m.cnblogs.com/?u=txw1958"); 186 }else if (strstr($keyword, "图文") || strstr($keyword, "多图文")){ 187 $content = array(); 188 $content[] = array("Title"=>"多图文1标题", "Description"=>"", "PicUrl"=>"http://discuz.comli.com/weixin/weather/icon/cartoon.jpg", "Url" =>"http://m.cnblogs.com/?u=txw1958"); 189 $content[] = array("Title"=>"多图文2标题", "Description"=>"", "PicUrl"=>"http://d.hiphotos.bdimg.com/wisegame/pic/item/f3529822720e0cf3ac9f1ada0846f21fbe09aaa3.jpg", "Url" =>"http://m.cnblogs.com/?u=txw1958"); 190 $content[] = array("Title"=>"多图文3标题", "Description"=>"", "PicUrl"=>"http://g.hiphotos.bdimg.com/wisegame/pic/item/18cb0a46f21fbe090d338acc6a600c338644adfd.jpg", "Url" =>"http://m.cnblogs.com/?u=txw1958"); 191 }else if (strstr($keyword, "音乐")){ 192 $content = array(); 193 $content = array("Title"=>"最炫民族风", "Description"=>"歌手:凤凰传奇", "MusicUrl"=>"http://121.199.4.61/music/zxmzf.mp3", "HQMusicUrl"=>"http://121.199.4.61/music/zxmzf.mp3"); 194 }else{ 195 $content = date("Y-m-d H:i:s",time())."\nOpenID:".$object->FromUserName."\n技术支持 方倍工作室"; 196 } 197 198 if(is_array($content)){ 199 if (isset($content[0])){ 200 $result = $this->transmitNews($object, $content); 201 }else if (isset($content['MusicUrl'])){ 202 $result = $this->transmitMusic($object, $content); 203 } 204 }else{ 205 $result = $this->transmitText($object, $content); 206 } 207 return $result; 208 } 209 210 //接收图片消息 211 private function receiveImage($object) 212 { 213 $content = array("MediaId"=>$object->MediaId); 214 $result = $this->transmitImage($object, $content); 215 return $result; 216 } 217 218 //接收位置消息 219 private function receiveLocation($object) 220 { 221 $content = "你发送的是位置,经度为:".$object->Location_Y.";纬度为:".$object->Location_X.";缩放级别为:".$object->Scale.";位置为:".$object->Label; 222 $result = $this->transmitText($object, $content); 223 return $result; 224 } 225 226 //接收语音消息 227 private function receiveVoice($object) 228 { 229 if (isset($object->Recognition) && !empty($object->Recognition)){ 230 $content = "你刚才说的是:".$object->Recognition; 231 $result = $this->transmitText($object, $content); 232 }else{ 233 $content = array("MediaId"=>$object->MediaId); 234 $result = $this->transmitVoice($object, $content); 235 } 236 return $result; 237 } 238 239 //接收视频消息 240 private function receiveVideo($object) 241 { 242 $content = array("MediaId"=>$object->MediaId, "ThumbMediaId"=>$object->ThumbMediaId, "Title"=>"", "Description"=>""); 243 $result = $this->transmitVideo($object, $content); 244 return $result; 245 } 246 247 //接收链接消息 248 private function receiveLink($object) 249 { 250 $content = "你发送的是链接,标题为:".$object->Title.";内容为:".$object->Description.";链接地址为:".$object->Url; 251 $result = $this->transmitText($object, $content); 252 return $result; 253 } 254 255 //回复文本消息 256 private function transmitText($object, $content) 257 { 258 if (!isset($content) || empty($content)){ 259 return ""; 260 } 261 262 $xmlTpl = "<xml> 263 <ToUserName><![CDATA[%s]]></ToUserName> 264 <FromUserName><![CDATA[%s]]></FromUserName> 265 <CreateTime>%s</CreateTime> 266 <MsgType><![CDATA[text]]></MsgType> 267 <Content><![CDATA[%s]]></Content> 268 </xml>"; 269 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), $content); 270 271 return $result; 272 } 273 274 //回复图文消息 275 private function transmitNews($object, $newsArray) 276 { 277 if(!is_array($newsArray)){ 278 return ""; 279 } 280 $itemTpl = " <item> 281 <Title><![CDATA[%s]]></Title> 282 <Description><![CDATA[%s]]></Description> 283 <PicUrl><![CDATA[%s]]></PicUrl> 284 <Url><![CDATA[%s]]></Url> 285 </item> 286 "; 287 $item_str = ""; 288 foreach ($newsArray as $item){ 289 $item_str .= sprintf($itemTpl, $item['Title'], $item['Description'], $item['PicUrl'], $item['Url']); 290 } 291 $xmlTpl = "<xml> 292 <ToUserName><![CDATA[%s]]></ToUserName> 293 <FromUserName><![CDATA[%s]]></FromUserName> 294 <CreateTime>%s</CreateTime> 295 <MsgType><![CDATA[news]]></MsgType> 296 <ArticleCount>%s</ArticleCount> 297 <Articles> 298 $item_str </Articles> 299 </xml>"; 300 301 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), count($newsArray)); 302 return $result; 303 } 304 305 //回复音乐消息 306 private function transmitMusic($object, $musicArray) 307 { 308 if(!is_array($musicArray)){ 309 return ""; 310 } 311 $itemTpl = "<Music> 312 <Title><![CDATA[%s]]></Title> 313 <Description><![CDATA[%s]]></Description> 314 <MusicUrl><![CDATA[%s]]></MusicUrl> 315 <HQMusicUrl><![CDATA[%s]]></HQMusicUrl> 316 </Music>"; 317 318 $item_str = sprintf($itemTpl, $musicArray['Title'], $musicArray['Description'], $musicArray['MusicUrl'], $musicArray['HQMusicUrl']); 319 320 $xmlTpl = "<xml> 321 <ToUserName><![CDATA[%s]]></ToUserName> 322 <FromUserName><![CDATA[%s]]></FromUserName> 323 <CreateTime>%s</CreateTime> 324 <MsgType><![CDATA[music]]></MsgType> 325 $item_str 326 </xml>"; 327 328 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time()); 329 return $result; 330 } 331 332 //回复图片消息 333 private function transmitImage($object, $imageArray) 334 { 335 $itemTpl = "<Image> 336 <MediaId><![CDATA[%s]]></MediaId> 337 </Image>"; 338 339 $item_str = sprintf($itemTpl, $imageArray['MediaId']); 340 341 $xmlTpl = "<xml> 342 <ToUserName><![CDATA[%s]]></ToUserName> 343 <FromUserName><![CDATA[%s]]></FromUserName> 344 <CreateTime>%s</CreateTime> 345 <MsgType><![CDATA[image]]></MsgType> 346 $item_str 347 </xml>"; 348 349 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time()); 350 return $result; 351 } 352 353 //回复语音消息 354 private function transmitVoice($object, $voiceArray) 355 { 356 $itemTpl = "<Voice> 357 <MediaId><![CDATA[%s]]></MediaId> 358 </Voice>"; 359 360 $item_str = sprintf($itemTpl, $voiceArray['MediaId']); 361 $xmlTpl = "<xml> 362 <ToUserName><![CDATA[%s]]></ToUserName> 363 <FromUserName><![CDATA[%s]]></FromUserName> 364 <CreateTime>%s</CreateTime> 365 <MsgType><![CDATA[voice]]></MsgType> 366 $item_str 367 </xml>"; 368 369 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time()); 370 return $result; 371 } 372 373 //回复视频消息 374 private function transmitVideo($object, $videoArray) 375 { 376 $itemTpl = "<Video> 377 <MediaId><![CDATA[%s]]></MediaId> 378 <ThumbMediaId><![CDATA[%s]]></ThumbMediaId> 379 <Title><![CDATA[%s]]></Title> 380 <Description><![CDATA[%s]]></Description> 381 </Video>"; 382 383 $item_str = sprintf($itemTpl, $videoArray['MediaId'], $videoArray['ThumbMediaId'], $videoArray['Title'], $videoArray['Description']); 384 385 $xmlTpl = "<xml> 386 <ToUserName><![CDATA[%s]]></ToUserName> 387 <FromUserName><![CDATA[%s]]></FromUserName> 388 <CreateTime>%s</CreateTime> 389 <MsgType><![CDATA[video]]></MsgType> 390 $item_str 391 </xml>"; 392 393 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time()); 394 return $result; 395 } 396 397 //回复多客服消息 398 private function transmitService($object) 399 { 400 $xmlTpl = "<xml> 401 <ToUserName><![CDATA[%s]]></ToUserName> 402 <FromUserName><![CDATA[%s]]></FromUserName> 403 <CreateTime>%s</CreateTime> 404 <MsgType><![CDATA[transfer_customer_service]]></MsgType> 405 </xml>"; 406 $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time()); 407 return $result; 408 } 409 410 //回复第三方接口消息 411 private function relayPart3($url, $rawData) 412 { 413 $headers = array("Content-Type: text/xml; charset=utf-8"); 414 $ch = curl_init(); 415 curl_setopt($ch, CURLOPT_URL, $url); 416 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 417 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 418 curl_setopt($ch, CURLOPT_POST, 1); 419 curl_setopt($ch, CURLOPT_POSTFIELDS, $rawData); 420 $output = curl_exec($ch); 421 curl_close($ch); 422 return $output; 423 } 424 425 //字节转Emoji表情 426 function bytes_to_emoji($cp) 427 { 428 if ($cp > 0x10000){ # 4 bytes 429 return chr(0xF0 | (($cp & 0x1C0000) >> 18)).chr(0x80 | (($cp & 0x3F000) >> 12)).chr(0x80 | (($cp & 0xFC0) >> 6)).chr(0x80 | ($cp & 0x3F)); 430 }else if ($cp > 0x800){ # 3 bytes 431 return chr(0xE0 | (($cp & 0xF000) >> 12)).chr(0x80 | (($cp & 0xFC0) >> 6)).chr(0x80 | ($cp & 0x3F)); 432 }else if ($cp > 0x80){ # 2 bytes 433 return chr(0xC0 | (($cp & 0x7C0) >> 6)).chr(0x80 | ($cp & 0x3F)); 434 }else{ # 1 byte 435 return chr($cp); 436 } 437 } 438 439 //日志记录 440 private function logger($log_content) 441 { 442 if(isset($_SERVER['HTTP_APPNAME'])){ //SAE 443 sae_set_display_errors(false); 444 sae_debug($log_content); 445 sae_set_display_errors(true); 446 }else if($_SERVER['REMOTE_ADDR'] != "127.0.0.1"){ //LOCAL 447 $max_size = 1000000; 448 $log_filename = "log.xml"; 449 if(file_exists($log_filename) and (abs(filesize($log_filename)) > $max_size)){unlink($log_filename);} 450 file_put_contents($log_filename, date('Y-m-d H:i:s')." ".$log_content."\r\n", FILE_APPEND); 451 } 452 } 453 } 454 ?>