最近公司用到了微信公衆平臺,因此研究了一下微信公衆號的開發技術,整體來講比較簡單,結合現有的平臺核技術,實現起來很是方便。php
首先先來了解一下微信公衆平臺。html
「微信,是一個生活方式」 ,這是微信的自我評價,是否是以爲若是那天不在朋友圈裏分享一下本身的最新狀態,
而且收到幾個贊和評價的話,會以爲空虛寂寞呢?它實實在在的改變了咱們的生活方式。git
「 微信,也是一個生意方式 」,在微信成爲咱們平常必備之app的同時,它一樣具有巨大的的商業
或許不該該稱爲潛力,由於有不少人已經獲利,名人們在微信上開設公衆帳戶來吸金,商家來作推廣,
服務行業藉此拓展渠道,甚至微信已經支持支付了, 還有愈來愈的自媒體在微信平臺涌現出來。
這篇文章就是介紹如何快速的成爲公衆平臺開發者,因爲我的只能申請訂閱號,所以本文是以訂閱號爲例。
關於訂閱號和服務號的區別,請參見 微信公衆平臺服務號、訂閱號的相關說明。數組
從微信用戶角度簡單來講:服務器
訂閱號 主要用於信息輻射,典型的如各家 新聞媒體 。
服務號 主要因爲自助服務,典型的如 招商銀行 。微信
關於微信公衆賬號註冊的步驟就再也不多說了,能夠找到大量的圖文教程。app
賬號註冊成功以後,須要驗證本身的服務器,若是你沒有本身的服務器,那能夠用新浪SAE或者百度BAE,本文采用的是新浪SAE平臺來搭建服務器。微信公衆平臺
註冊過程略,使用新浪SAE建立應用,能夠選擇應用開發框架,選項中有比較熱門的開發框架,選擇微信公衆平臺phpSDK,點擊後跳轉到介紹頁面,點擊安裝框架,系統會生成一個搭建好的微信公衆平臺應用,爲了方便開發,咱們可使用svn來管理此應用代碼,關於svn搭建可參見sae代碼部署手冊。框架
使用新浪SAE是比較方便的,若是咱們有本身的服務器,能夠把代碼clone到本身的服務器上,下面來看一下代碼curl
首先定義一個Wechat的基類
1 <?php 2 /** 3 * 微信公衆平臺 PHP SDK 4 * 5 * @author hanc <congcongsky2010@gmail.com> 6 */ 7 8 /** 9 * 微信公衆平臺處理類 10 */ 11 class Wechat { 12 13 /** 14 * 調試模式,將錯誤經過文本消息回覆顯示 15 * 16 * @var boolean 17 */ 18 private $debug; 19 20 /** 21 * 以數組的形式保存微信服務器每次發來的請求 22 * 23 * @var array 24 */ 25 private $request; 26 27 /** 28 * 初始化,判斷這次請求是否爲驗證請求,並以數組形式保存 29 * 30 * @param string $token 驗證信息 31 * @param boolean $debug 調試模式,默認爲關閉 32 */ 33 public function __construct($token, $debug = FALSE) { 34 if ($this->isValid() && $this->validateSignature($token)) { 35 exit($_GET['echostr']); 36 } 37 38 $this->debug = $debug; 39 set_error_handler(array(&$this, 'errorHandler')); 40 // 設置錯誤處理函數,將錯誤經過文本消息回覆顯示 41 42 $xml = (array) simplexml_load_string($GLOBALS['HTTP_RAW_POST_DATA'], 'SimpleXMLElement', LIBXML_NOCDATA); 43 44 $this->request = array_change_key_case($xml, CASE_LOWER); 45 // 將數組鍵名轉換爲小寫,提升健壯性,減小因大小寫不一樣而出現的問題 46 } 47 48 /** 49 * 判斷這次請求是否爲驗證請求 50 * 51 * @return boolean 52 */ 53 private function isValid() { 54 return isset($_GET['echostr']); 55 } 56 57 /** 58 * 判斷驗證請求的簽名信息是否正確 59 * 60 * @param string $token 驗證信息 61 * @return boolean 62 */ 63 private function validateSignature($token) { 64 $signature = $_GET['signature']; 65 $timestamp = $_GET['timestamp']; 66 $nonce = $_GET['nonce']; 67 68 $signatureArray = array($token, $timestamp, $nonce); 69 sort($signatureArray); 70 71 return sha1(implode($signatureArray)) == $signature; 72 } 73 74 /** 75 * 獲取本次請求中的參數,不區分大小 76 * 77 * @param string $param 參數名,默認爲無參 78 * @return mixed 79 */ 80 protected function getRequest($param = FALSE) { 81 if ($param === FALSE) { 82 return $this->request; 83 } 84 85 $param = strtolower($param); 86 87 if (isset($this->request[$param])) { 88 return $this->request[$param]; 89 } 90 91 return NULL; 92 } 93 94 /** 95 * 用戶關注時觸發,用於子類重寫 96 * 97 * @return void 98 */ 99 protected function onSubscribe() {} 100 101 /** 102 * 用戶取消關注時觸發,用於子類重寫 103 * 104 * @return void 105 */ 106 protected function onUnsubscribe() {} 107 108 /** 109 * 用戶自動上報地理位置觸發,用於子類重寫 110 * 111 * @return void 112 */ 113 protected function onAutoloaction() {} 114 115 /** 116 * 用戶點擊菜單時觸發,用於子類重寫 117 * 118 * @return void 119 */ 120 protected function onClick() {} 121 122 /** 123 * 用戶點擊跳轉連接時觸發,用於子類重寫 124 * 125 * @return void 126 */ 127 protected function onView() {} 128 129 /** 130 * 收到文本消息時觸發,用於子類重寫 131 * 132 * @return void 133 */ 134 protected function onText() {} 135 136 /** 137 * 收到圖片消息時觸發,用於子類重寫 138 * 139 * @return void 140 */ 141 protected function onImage() {} 142 143 /** 144 * 收到地理位置消息時觸發,用於子類重寫 145 * 146 * @return void 147 */ 148 protected function onLocation() {} 149 150 /** 151 * 收到連接消息時觸發,用於子類重寫 152 * 153 * @return void 154 */ 155 protected function onLink() {} 156 /** 157 * 收到語音消息時觸發,用於子類重寫 158 * 159 * @return void 160 */ 161 protected function onVoice() {} 162 163 /** 164 * 收到未知類型消息時觸發,用於子類重寫 165 * 166 * @return void 167 */ 168 protected function onUnknown() {} 169 170 /** 171 * 回覆文本消息 172 * 173 * @param string $content 消息內容 174 * @param integer $funcFlag 默認爲0,設爲1時星標剛纔收到的消息 175 * @return void 176 */ 177 protected function responseText($content, $funcFlag = 0) { 178 exit(new TextResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $content, $funcFlag)); 179 } 180 181 /** 182 * 回覆音樂消息 183 * 184 * @param string $title 音樂標題 185 * @param string $description 音樂描述 186 * @param string $musicUrl 音樂連接 187 * @param string $hqMusicUrl 高質量音樂連接,Wi-Fi 環境下優先使用 188 * @param integer $funcFlag 默認爲0,設爲1時星標剛纔收到的消息 189 * @return void 190 */ 191 protected function responseMusic($title, $description, $musicUrl, $hqMusicUrl, $funcFlag = 0) { 192 exit(new MusicResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $title, $description, $musicUrl, $hqMusicUrl, $funcFlag)); 193 } 194 195 /** 196 * 回覆圖文消息 197 * @param array $items 由單條圖文消息類型 NewsResponseItem() 組成的數組 198 * @param integer $funcFlag 默認爲0,設爲1時星標剛纔收到的消息 199 * @return void 200 */ 201 protected function responseNews($items, $funcFlag = 0) { 202 exit(new NewsResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $items, $funcFlag)); 203 } 204 /** 205 * 回覆語音識別消息 206 * @param array $recognition 系統接收到語音後識別的字符串 207 * @param integer $funcFlag 默認爲0,設爲1時星標剛纔收到的消息 208 * @return void 209 */ 210 protected function responseVoice($recognition, $funcFlag = 0) { 211 exit(new TextResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $recognition, $funcFlag)); 212 } 213 214 /** 215 * 分析消息類型,並分發給對應的函數 216 * 217 * @return void 218 */ 219 public function run() { 220 switch ($this->getRequest('msgtype')) { 221 222 case 'event': 223 switch ($this->getRequest('event')) { 224 225 case 'subscribe': 226 $this->onSubscribe(); 227 break; 228 229 case 'unsubscribe': 230 $this->onUnsubscribe(); 231 break; 232 233 case 'LOCATION': 234 $this->onAutoloaction(); 235 break; 236 237 case 'CLICK': 238 $this->onClick(); 239 break; 240 241 case 'VIEW': 242 $this->onView(); 243 break; 244 245 } 246 break; 247 248 case 'text': 249 $this->onText(); 250 break; 251 252 case 'image': 253 $this->onImage(); 254 break; 255 256 case 'location': 257 $this->onLocation(); 258 break; 259 260 case 'link': 261 $this->onLink(); 262 break; 263 264 case 'voice': 265 $this->onVoice(); 266 break; 267 268 default: 269 $this->onUnknown(); 270 break; 271 272 } 273 } 274 275 /** 276 * 自定義的錯誤處理函數,將 PHP 錯誤經過文本消息回覆顯示 277 * @param int $level 錯誤代碼 278 * @param string $msg 錯誤內容 279 * @param string $file 產生錯誤的文件 280 * @param int $line 產生錯誤的行數 281 * @return void 282 */ 283 protected function errorHandler($level, $msg, $file, $line) { 284 if ( ! $this->debug) { 285 return; 286 } 287 288 $error_type = array( 289 // E_ERROR => 'Error', 290 E_WARNING => 'Warning', 291 // E_PARSE => 'Parse Error', 292 E_NOTICE => 'Notice', 293 // E_CORE_ERROR => 'Core Error', 294 // E_CORE_WARNING => 'Core Warning', 295 // E_COMPILE_ERROR => 'Compile Error', 296 // E_COMPILE_WARNING => 'Compile Warning', 297 E_USER_ERROR => 'User Error', 298 E_USER_WARNING => 'User Warning', 299 E_USER_NOTICE => 'User Notice', 300 E_STRICT => 'Strict', 301 E_RECOVERABLE_ERROR => 'Recoverable Error', 302 E_DEPRECATED => 'Deprecated', 303 E_USER_DEPRECATED => 'User Deprecated', 304 ); 305 306 $template = <<<ERR 307 PHP 報錯啦! 308 309 %s: %s 310 File: %s 311 Line: %s 312 ERR; 313 314 $this->responseText(sprintf($template, 315 $error_type[$level], 316 $msg, 317 $file, 318 $line 319 )); 320 } 321 322 } 323 324 /** 325 * 用於回覆的基本消息類型 326 */ 327 abstract class WechatResponse { 328 329 protected $toUserName; 330 protected $fromUserName; 331 protected $funcFlag; 332 333 public function __construct($toUserName, $fromUserName, $funcFlag) { 334 $this->toUserName = $toUserName; 335 $this->fromUserName = $fromUserName; 336 $this->funcFlag = $funcFlag; 337 } 338 339 abstract public function __toString(); 340 341 } 342 343 344 /** 345 * 用於回覆的文本消息類型 346 */ 347 class TextResponse extends WechatResponse { 348 349 protected $content; 350 351 protected $template = <<<XML 352 <xml> 353 <ToUserName><![CDATA[%s]]></ToUserName> 354 <FromUserName><![CDATA[%s]]></FromUserName> 355 <CreateTime>%s</CreateTime> 356 <MsgType><![CDATA[text]]></MsgType> 357 <Content><![CDATA[%s]]></Content> 358 <FuncFlag>%s<FuncFlag> 359 </xml> 360 XML; 361 362 public function __construct($toUserName, $fromUserName, $content, $funcFlag = 0) { 363 parent::__construct($toUserName, $fromUserName, $funcFlag); 364 $this->content = $content; 365 } 366 367 public function __toString() { 368 return sprintf($this->template, 369 $this->toUserName, 370 $this->fromUserName, 371 time(), 372 $this->content, 373 $this->funcFlag 374 ); 375 } 376 377 } 378 379 /** 380 * 用於回覆的音樂消息類型 381 */ 382 class MusicResponse extends WechatResponse { 383 384 protected $title; 385 protected $description; 386 protected $musicUrl; 387 protected $hqMusicUrl; 388 389 protected $template = <<<XML 390 <xml> 391 <ToUserName><![CDATA[%s]]></ToUserName> 392 <FromUserName><![CDATA[%s]]></FromUserName> 393 <CreateTime>%s</CreateTime> 394 <MsgType><![CDATA[music]]></MsgType> 395 <Music> 396 <Title><![CDATA[%s]]></Title> 397 <Description><![CDATA[%s]]></Description> 398 <MusicUrl><![CDATA[%s]]></MusicUrl> 399 <HQMusicUrl><![CDATA[%s]]></HQMusicUrl> 400 </Music> 401 <FuncFlag>%s<FuncFlag> 402 </xml> 403 XML; 404 405 public function __construct($toUserName, $fromUserName, $title, $description, $musicUrl, $hqMusicUrl, $funcFlag) { 406 parent::__construct($toUserName, $fromUserName, $funcFlag); 407 $this->title = $title; 408 $this->description = $description; 409 $this->musicUrl = $musicUrl; 410 $this->hqMusicUrl = $hqMusicUrl; 411 } 412 413 public function __toString() { 414 return sprintf($this->template, 415 $this->toUserName, 416 $this->fromUserName, 417 time(), 418 $this->title, 419 $this->description, 420 $this->musicUrl, 421 $this->hqMusicUrl, 422 $this->funcFlag 423 ); 424 } 425 426 } 427 428 429 /** 430 * 用於回覆的圖文消息類型 431 */ 432 class NewsResponse extends WechatResponse { 433 434 protected $items = array(); 435 436 protected $template = <<<XML 437 <xml> 438 <ToUserName><![CDATA[%s]]></ToUserName> 439 <FromUserName><![CDATA[%s]]></FromUserName> 440 <CreateTime>%s</CreateTime> 441 <MsgType><![CDATA[news]]></MsgType> 442 <ArticleCount>%s</ArticleCount> 443 <Articles> 444 %s 445 </Articles> 446 <FuncFlag>%s<FuncFlag> 447 </xml>' 448 XML; 449 450 public function __construct($toUserName, $fromUserName, $items, $funcFlag) { 451 parent::__construct($toUserName, $fromUserName, $funcFlag); 452 $this->items = $items; 453 } 454 455 public function __toString() { 456 return sprintf($this->template, 457 $this->toUserName, 458 $this->fromUserName, 459 time(), 460 count($this->items), 461 implode($this->items), 462 $this->funcFlag 463 ); 464 } 465 466 } 467 468 469 /** 470 * 單條圖文消息類型 471 */ 472 class NewsResponseItem { 473 474 protected $title; 475 protected $description; 476 protected $picUrl; 477 protected $url; 478 479 protected $template = <<<XML 480 <item> 481 <Title><![CDATA[%s]]></Title> 482 <Description><![CDATA[%s]]></Description> 483 <PicUrl><![CDATA[%s]]></PicUrl> 484 <Url><![CDATA[%s]]></Url> 485 </item> 486 XML; 487 488 public function __construct($title, $description, $picUrl, $url) { 489 $this->title = $title; 490 $this->description = $description; 491 $this->picUrl = $picUrl; 492 $this->url = $url; 493 } 494 495 public function __toString() { 496 return sprintf($this->template, 497 $this->title, 498 $this->description, 499 $this->picUrl, 500 $this->url 501 ); 502 } 503 504 }
此基類我稍做了更改,包含了能實現的微信全部的接口,經過繼承 `Wechat` 類進行擴展,例如經過重寫 `onSubscribe()` 等方法響應關注等請求,下面是實現的示例代碼:
1 <?php 2 /** 3 * 微信公衆平臺 PHP SDK 示例文件 4 * 5 * @author hanc <congcongsky2010@gmail.com> 6 */ 7 8 require('src/Wechat.php'); 9 10 /** 11 * 微信公衆平臺演示類 12 */ 13 class MyWechat extends Wechat { 14 15 /** 16 * 用戶關注時觸發,回覆「歡迎關注」 17 * 18 * @return void 19 */ 20 protected function onSubscribe() { 21 $this->responseText('歡迎關注韓聰的微信號'); 22 } 23 24 /** 25 * 用戶取消關注時觸發 26 * 27 * @return void 28 */ 29 protected function onUnsubscribe() { 30 // 「悄悄的我走了,正如我悄悄的來;我揮一揮衣袖,不帶走一片雲彩。」 31 } 32 33 /** 34 * 用戶自動上報地理位置時觸發 35 * 36 * @return void 37 */ 38 protected function onAutoloaction() { 39 40 $this->responseText('您的地理位置爲:' . $this->getRequest('Latitude') . ',' . $this->getRequest('Longitude')); 41 } 42 43 /** 44 * 用戶點擊菜單時觸發 45 * 46 * @return void 47 */ 48 protected function onClick() { 49 $eventKey=$this->getRequest('EventKey'); 50 switch($eventKey){ 51 case 'C001': 52 $this->responseText('我贏了'); 53 break; 54 case 'C002': 55 $this->responseText('我最近很好o(∩_∩)o '); 56 break; 57 case 'C003': 58 $this->responseText('謝謝(*^__^*) 嘻嘻'); 59 break; 60 } 61 } 62 63 /** 64 * 收到文本消息時觸發,回覆收到的文本消息內容 65 * 66 * @return void 67 */ 68 protected function onText() { 69 $this->responseText('收到了文字消息:' . $this->getRequest('content')); 70 } 71 72 /** 73 * 收到圖片消息時觸發,回覆由收到的圖片組成的圖文消息 74 * 75 * @return void 76 */ 77 protected function onImage() { 78 $items = array( 79 new NewsResponseItem('標題一', '描述一', $this->getRequest('picurl'), $this->getRequest('picurl')), 80 new NewsResponseItem('標題二', '描述二', $this->getRequest('picurl'), $this->getRequest('picurl')), 81 ); 82 83 $this->responseNews($items); 84 } 85 86 /** 87 * 收到地理位置消息時觸發,回覆收到的地理位置 88 * 89 * @return void 90 */ 91 protected function onLocation() { 92 //$num = 1 / 0; 93 // 故意觸發錯誤,用於演示調試功能 94 95 $this->responseText('收到了位置消息:' . $this->getRequest('location_x') . ',' . $this->getRequest('location_y')); 96 } 97 98 /** 99 * 收到連接消息時觸發,回覆收到的連接地址 100 * 101 * @return void 102 */ 103 protected function onLink() { 104 $this->responseText('收到了連接:' . $this->getRequest('url')); 105 } 106 107 /** 108 * 收到語音消息時觸發,回覆收到的語音 109 * 110 * @return void 111 */ 112 protected function onVoice() { 113 $this->responseVoice('收到了語音:' . $this->getRequest('recognition')); 114 } 115 116 /** 117 * 收到未知類型消息時觸發,回覆收到的消息類型 118 * 119 * @return void 120 */ 121 protected function onUnknown() { 122 $this->responseText('收到了未知類型消息:' . $this->getRequest('msgtype')); 123 } 124 125 } 126 127 $wechat = new MyWechat('hancong', TRUE); 128 $wechat->run();
以上代碼部分功能須要開通服務號而且申請認證,好比語音識別,地理信息,添加菜單的功能,申請認證須要300元/年,能夠享受微信全部的接口功能。
注:若是驗證服務器URL,須要修改一句代碼
$wechat = new MyWechat('hancong', TRUE); //$wechat->run(); $wechat->validateSignature('hancong');//參數爲填寫的token
驗證完後回覆調用run方法,validateSignature方法只是第一次驗證服務器調用,驗證完後便可刪掉。