微信公众号开发总结
最近公司用到了微信公众平台,所以研究了一下微信公众号的开发技术,总体来说比较简单,结合现有的平台核技术,实现起来非常方便。
首先先来了解一下微信公众平台。
“微信,是一个生活方式” ,这是微信的自我评价,是不是觉得如果那天不在朋友圈里分享一下自己的最新状态,
并且收到几个赞和评价的话,会觉得空虚寂寞呢?它实实在在的改变了我们的生活方式。
“ 微信,也是一个生意方式 ”,在微信成为我们日常必备之app的同时,它同样具备巨大的的商业
或许不应该称为潜力,因为有很多人已经获利,名人们在微信上开设公众账户来吸金,商家来做推广,
服务行业借此拓展渠道,甚至微信已经支持支付了, 还有越来越的自媒体在微信平台涌现出来。
这篇文章就是介绍如何快速的成为公众平台开发者,由于个人只能申请订阅号,因此本文是以订阅号为例。
关于订阅号和服务号的区别,请参见 微信公众平台服务号、订阅号的相关说明。
从微信用户角度简单来说:
订阅号 主要用于信息辐射,典型的如各家 新闻媒体 。
服务号 主要由于自助服务,典型的如 招商银行 。
申请公众平台账户
- 到 微信公众平台填写注册信息
- 按照提示激活邮箱
- 上传个人照片,需要有清晰的身份证照片
- 选择公众账户的类型,对于个人账户只能选择 订阅号
- 最后你会看到自己账户的所有信息,请上传账号的头像,否则无法完成开发者的申请
- 等待审核通过,这个过程大约需要2~3天,当你收到如下通知,那么恭喜你,你已经成功的申请到了微信公众账户了
关于微信公众帐号注册的步骤就不再多说了,可以找到大量的图文教程。
帐号注册成功之后,需要验证自己的服务器,如果你没有自己的服务器,那可以用新浪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 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方法只是第一次验证服务器调用,验证完后即可删掉。