微信公众号开发总结

时间:2024-02-20 11:42:07

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

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

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

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