最近公司用到了微信公众平台,所以研究了一下微信公众号的开发技术,总体来说比较简单,结合现有的平台核技术,实现起来非常方便。
首先先来了解一下微信公众平台。
“微信,是一个生活方式” ,这是微信的自我评价,是不是觉得如果那天不在朋友圈里分享一下自己的最新状态,
并且收到几个赞和评价的话,会觉得空虚寂寞呢?它实实在在的改变了我们的生活方式。
“ 微信,也是一个生意方式 ”,在微信成为我们日常必备之app的同时,它同样具备巨大的的商业
或许不应该称为潜力,因为有很多人已经获利,名人们在微信上开设公众账户来吸金,商家来做推广,
服务行业借此拓展渠道,甚至微信已经支持支付了, 还有越来越的自媒体在微信平台涌现出来。
这篇文章就是介绍如何快速的成为公众平台开发者,由于个人只能申请订阅号,因此本文是以订阅号为例。
关于订阅号和服务号的区别,请参见 微信公众平台服务号、订阅号的相关说明。
从微信用户角度简单来说:
订阅号 主要用于信息辐射,典型的如各家 新闻媒体 。
服务号 主要由于自助服务,典型的如 招商银行 。
关于微信公众帐号注册的步骤就不再多说了,可以找到大量的图文教程。
帐号注册成功之后,需要验证自己的服务器,如果你没有自己的服务器,那可以用新浪SAE或者百度BAE,本文采用的是新浪SAE平台来搭建服务器。
注册过程略,使用新浪SAE创建应用,可以选择应用开发框架,选项中有比较热门的开发框架,选择微信公众平台phpSDK,点击后跳转到介绍页面,点击安装框架,系统会生成一个搭建好的微信公众平台应用,为了方便开发,我们可以使用svn来管理此应用代码,关于svn搭建可参见sae代码部署手册。
使用新浪SAE是比较方便的,如果我们有自己的服务器,可以把代码clone到自己的服务器上,下面来看一下代码
首先定义一个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 void105 */106 protected function onUnsubscribe() {}107 108 /**109 * 用户自动上报地理位置触发,用于子类重写110 *111 * @return void112 */113 protected function onAutoloaction() {}114 115 /**116 * 用户点击菜单时触发,用于子类重写117 *118 * @return void119 */120 protected function onClick() {}121 122 /**123 * 用户点击跳转链接时触发,用于子类重写124 *125 * @return void126 */127 protected function onView() {}128 129 /**130 * 收到文本消息时触发,用于子类重写131 *132 * @return void133 */134 protected function onText() {}135 136 /**137 * 收到图片消息时触发,用于子类重写138 *139 * @return void140 */141 protected function onImage() {}142 143 /**144 * 收到地理位置消息时触发,用于子类重写145 *146 * @return void147 */148 protected function onLocation() {}149 150 /**151 * 收到链接消息时触发,用于子类重写152 *153 * @return void154 */155 protected function onLink() {}156 /**157 * 收到语音消息时触发,用于子类重写158 *159 * @return void160 */161 protected function onVoice() {}162 163 /**164 * 收到未知类型消息时触发,用于子类重写165 *166 * @return void167 */168 protected function onUnknown() {}169 170 /**171 * 回复文本消息172 *173 * @param string $content 消息内容174 * @param integer $funcFlag 默认为0,设为1时星标刚才收到的消息175 * @return void176 */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 void190 */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 void200 */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 void209 */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 void218 */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 void282 */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 = <<<ERR307 PHP 报错啦!308 309 %s: %s310 File: %s311 Line: %s312 ERR;313 314 $this->responseText(sprintf($template,315 $error_type[$level],316 $msg,317 $file,318 $line319 ));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 = <<<XML352 <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->funcFlag374 );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 = <<<XML390 <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->funcFlag423 );424 }425 426 }427 428 429 /**430 * 用于回复的图文消息类型431 */432 class NewsResponse extends WechatResponse {433 434 protected $items = array();435 436 protected $template = <<<XML437 <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 %s445 </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->funcFlag463 );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 = <<<XML480 <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->url501 );502 }503 504 }