首页 > 代码库 > 微信公众号开发总结

微信公众号开发总结

最近公司用到了微信公众平台,所以研究了一下微信公众号的开发技术,总体来说比较简单,结合现有的平台核技术,实现起来非常方便。

首先先来了解一下微信公众平台。

“微信,是一个生活方式” ,这是微信的自我评价,是不是觉得如果那天不在朋友圈里分享一下自己的最新状态, 
并且收到几个赞和评价的话,会觉得空虚寂寞呢?它实实在在的改变了我们的生活方式。

“ 微信,也是一个生意方式 ”,在微信成为我们日常必备之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方法只是第一次验证服务器调用,验证完后即可删掉。