微信公众号开发总结

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

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

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

“ 微信,也是一个生意方式 ”,在微信成为我们日常必备之app的同时,它同样具备巨大的的商业 
或许不应该称为潜力,因为有很多人已经获利,名人们在微信上开设公众账户来吸金,商家来做推广, 
服务行业借此拓展渠道,甚至微信已经支持支付了, 还有越来越的自媒体在微信平台涌现出来。 

这篇文章就是介绍如何快速的成为公众平台开发者,由于个人只能申请订阅号,因此本文是以订阅号为例。
关于订阅号和服务号的区别,请参见 微信公众平台服务号、订阅号的相关说明

从微信用户角度简单来说:

订阅号 主要用于信息辐射,典型的如各家 新闻媒体 。 
服务号 主要由于自助服务,典型的如 招商银行 。

申请公众平台账户

  • 按照提示激活邮箱

  • 上传个人照片,需要有清晰的身份证照片

  • 选择公众账户的类型,对于个人账户只能选择 订阅号

  • 最后你会看到自己账户的所有信息,请上传账号的头像,否则无法完成开发者的申请

  • 等待审核通过,这个过程大约需要2~3天,当你收到如下通知,那么恭喜你,你已经成功的申请到了微信公众账户了

关于微信公众帐号注册的步骤就不再多说了,可以找到大量的图文教程。

帐号注册成功之后,需要验证自己的服务器,如果你没有自己的服务器,那可以用新浪SAE或者百度BAE,本文采用的是新浪SAE平台来搭建服务器。

注册过程略,使用新浪SAE创建应用,可以选择应用开发框架,选项中有比较热门的开发框架,选择微信公众平台phpSDK,点击后跳转到介绍页面,点击安装框架,系统会生成一个搭建好的微信公众平台应用,为了方便开发,我们可以使用svn来管理此应用代码,关于svn搭建可参见sae代码部署手册

使用新浪SAE是比较方便的,如果我们有自己的服务器,可以把代码clone到自己的服务器上,下面来看一下代码

首先定义一个Wechat的基类

  1 <?php

  2 /**

  3  * 微信公众平台 PHP SDK

  4  *

  5  * @author hanc <[email protected]>

  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 <[email protected]>

  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方法只是第一次验证服务器调用,验证完后即可删掉。

 

你可能感兴趣的:(微信公众号)