? ? ? ?CleverCode前一段時間想去接觸一下微信開發,申請了一個人訂閱號,發現暫不能申請個人認證,而且沒有微信接口的很多權限,也沒有自定義菜單的權限(開發模式下)。在開發模式下,只能到手公眾號里面的回復信息,然后響應。
## 1 項目背景
? ? ? ? ? CleverCode想了很久,運營一個什么樣的公眾號,比較好呢?現在的公眾號太多了。比較好的點子,基本都被人想到了。那幾天CleverCode的遇到點煩心事,心情不大爽,就想著能不能設計一個講笑話的公眾號,大家能夠在里面講笑話,然后查看笑話,開心一下自己。講笑話后能夠獲取金幣。積累到一定的金幣后,可以兌換獎品。獎品的方式可以是充話費的形式。
? ? ? ? ? 說干就干。腦袋一熱,CleverCode就開始瘋狂的忙碌起來的了............................
??
## 2 需求分析
? ? ? ? CleverCode發現個人訂閱號,只能簡單收到客戶的信息,然后響應信息。然后CleverCode就想,不如就讓用戶輸入0-9的數字,組合成命令,這樣服務器每次收到不同的數字后,去響應不同的請求就可以了。(這種方式基本停留在沒有圖片,顏色;只有文字交互的時代...........誰叫咋申請的訂閱號,沒有認證,就沒有一些接口的權限)。
## 3 源碼下載
? ? ? ?[http://download.csdn.net/detail/clevercode/8916699](http://download.csdn.net/detail/clevercode/8916699)。
## 4 項目演示
? ? ? ?如果你想查看本微信的詳細的演示效果,可以在微信中搜索微信號:taihaoxiaole888。或者掃描下方二維碼關注。

? ? ?

## 5 ?設計過程
### 5.1 微信配置接口
? ? ? ?申請完微信訂閱號后,要想微信開發,必須在微信后臺([mp.weixin.qq.com](https://mp.weixin.qq.com/))切換到開發模式,然后配置接口。如下圖。http://xxxx.com是我服務器的域名地址。如果沒有服務器也可以用新浪的sae。/apithxl/interface表示的我是使用的zend framework框架。這個url可以是任意的,只要能夠訪問得到即可。例如http://xxxx.com/api.php,http://xxxx.com/api.jsp等等。

### 5.2 微信接口交互過程
? ? ? ? 當用戶回復內容后,微信需要將用戶回復的先傳到自己的服務,如果配置成為了開發模式,微信會將調用接口(http://xxxxx.com/apithxl/interface),通過post方式將回復的內容發送到接口服務器上,我接口服務器處理完請求后,只需要輸出一個xml信息,微信服務器獲取這個xml信息后,在將獲取的信息返回給微信用戶。具體過程如下圖。

### 5.3 檢查簽名
? ? ? ? 當用戶配置完接口后,需要提交配置,微信向接口發送get請求的字符串。你需要echo它的字符串。這個在微信開發者文檔中都會有,我代碼中WeiXinCheck::checkSignature($signature, $timestamp, $nonce)也有。
## 6 接口設計架構
### 6.1 接口工作原理
? ? ? ? ? ?當接口收到微信服務器發送過來的請求后,首先需要到命令解析中心,分析出得到是什么請求。然后將得到命令發送到命令調用中心;調度中心會根據不同的命令調用不用執行邏輯,然后返回結果。

ApithxlController.php,主函數執行過程如下。入口函數為interfaceAction(),這個是zend framework框架的寫法。首先通過_getRequest($request)方法保存參數,然后驗證簽名?,通過CmdCenter::findCmd($request)解析命令,通過switch ($cmd)調用命令,通過$this->displayUTF8($retArray, 'thxl/validate.html')返回xml格式的內容。
~~~
<?php
/**
* ApithxlController.php
*
* 太好笑了接口
*
* Copyright (c) 2015 http://blog.csdn.net/CleverCode
*
* modification history:
* --------------------
* 2015/7/10, by CleverCode, Create
*
*/
define("TOKEN", "CleverCode");
define("HTTP_RAW_POST_DATA_TEST", WEB_ROOT_DIR . '/log/http_raw_post_data_test.txt');
define("HTTP_REQUEST_RESPONSE_LOG", WEB_ROOT_DIR . '/log/http_request_response_log-' . date('Y-m-d') . '.txt');
class ApithxlController extends My_Controller{
public $check_auth = false;
public $check_auth_menu = false;
/**
* 命令接口
*
* @return void
*/
function interfaceAction(){
$ret = $this->_interface();
}
/**
* 私有命令接口
*
* @return string 成功返回'OK',失敗返回錯誤信息
*/
private function _interface(){
$request = array();
// 獲取參數
$ret = $this->_getRequest($request);
if ($ret != 'OK') {
return $ret;
}
// 檢查簽名
if (!WeiXinCheck::checkSignature($request["signature"], $request["timestamp"], $request["nonce"])) {
return 'checkSignature retrun false!';
}
// 請求日志
if (!empty($GLOBALS["HTTP_RAW_POST_DATA"])) {
logMsg(HTTP_REQUEST_RESPONSE_LOG, 'REQUEST', $GLOBALS["HTTP_RAW_POST_DATA"]);
}
// 解析文本命令
$cmd = CmdCenter::findCmd($request);
$retArray = array();
$retArray['request'] = $request;
// 執行命令
switch ($cmd) {
// 校驗
case 'validate' :
$retMessage = InfoCenter::validate($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/validate.html');
break;
// 再來一個
case 'getOneAgain' :
$retMessage = InfoCenter::getOneAgain($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 查看幫助
case 'readHelp' :
$retMessage = InfoCenter::readHelp($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 賬戶詳情
case 'accountDetail' :
$retMessage = InfoCenter::accountDetail($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 兌換獎品
case 'exchangePrizesDescribe' :
$retMessage = InfoCenter::exchangePrizesDescribe($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 掙取金幣
case 'earnCoin' :
$retMessage = InfoCenter::earnCoin($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 點贊
case 'dianZan' :
$retMessage = InfoCenter::dianZan($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 我講一個
case 'addJoke' :
$retMessage = InfoCenter::addJoke($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 我的笑話
case 'myJoke' :
$retMessage = InfoCenter::myJoke($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 金幣記錄
case 'tradeLog' :
$retMessage = InfoCenter::tradeLog($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 兌換獎品
case 'exchangePrizes' :
$retMessage = InfoCenter::exchangePrizes($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 給客服留言
case 'giveMessage' :
$retMessage = InfoCenter::giveMessage($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 關注
case 'subscribe' :
$retMessage = InfoCenter::subscribe($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
// 默認
default :
$retMessage = InfoCenter::cmdNotFound($request, &$retArray);
if ($retMessage != 'OK') {
return;
}
$this->displayUTF8($retArray, 'thxl/textMsg.html');
break;
}
}
/**
* 獲取請求參數
*
* @param array $request 請求數組
* @return string 成功返回'OK',失敗返回錯誤信息
*/
private function _getRequest(&$request){
// 本機平臺
if (SYS_RELEASE == 0) {
$_GET["signature"] = '5b7b4a7c06b3bc4116a2fcbbbb2c887557cd07a6';
$_GET["timestamp"] = '1436056391';
$_GET["nonce"] = '1929760760';
// $_GET["echostr"] = 'this is from echostr';
$fp = fopen(HTTP_RAW_POST_DATA_TEST, "r");
$GLOBALS["HTTP_RAW_POST_DATA"] = fread($fp, filesize(HTTP_RAW_POST_DATA_TEST));
fclose($fp);
}
$request['signature'] = $_GET["signature"];
$request['timestamp'] = $_GET["timestamp"];
$request['nonce'] = $_GET["nonce"];
$request['echostr'] = $_GET["echostr"];
if (isset($GLOBALS["HTTP_RAW_POST_DATA"]) && !empty($GLOBALS["HTTP_RAW_POST_DATA"])) {
$request['post'] = array();
$request['post']['items'] = XmlCenter::xmlToArray($GLOBALS["HTTP_RAW_POST_DATA"]);
if (isset($request['post']['items']['Content'])) {
$request['post']['items']['Content'] = iconv('UTF-8', 'GBK', $request['post']['items']['Content']);
}
$request['post']['string'] = $GLOBALS["HTTP_RAW_POST_DATA"];
// 插入請求日志
UserLog::insertRequestLog($request);
}
return 'OK';
}
}
~~~
### 6.2 保存get與post傳輸xml數據
? ? ? ? ?每次微信服務器請求都會傳輸get參數,與post的xml數據。需要將這些數據保存到$request數組中。xml數據的格式如下。
~~~
<xml><ToUserName><![CDATA[gh_5bcc295a14c4]]></ToUserName>
<FromUserName><![CDATA[oihwct-iYa_xYXHAR2ZmnAPasEzQ]]></FromUserName>
<CreateTime>1436275541</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[9#123456789]]></Content>
<MsgId>6168756476849070426</MsgId>
</xml>
~~~
在ApithxlController.php,通過_getRequest(&$request)函數,將get與post參數保存到$request數組中。
### 6.2 命令解析中心
? ? ? ? 當保存完參數后,需要對用戶輸入的內容進行解析,分析的數字命令是什么。在CmdCenter.php,查找的命令的入口函數式findCmd($request);首先查找是否是接口驗證的命令,然后在從文本中查找命令,最后查找是否為事件命令。
~~~
<?php
/**
* CmdCenter.php
*
* 查找cmd
*
* Copyright (c) 2015 http://blog.csdn.net/CleverCode
*
* modification history:
* --------------------
* 2015/7/10, by CleverCode, Create
*
*/
class CmdCenter{
// 命名字典
public static $cmdMap = array(
'validate' => '接口驗證',
'subscribe' => '關注',
'readHelp' => '查看幫助',
'getOneAgain' => '發布一個',
'accountDetail' => '賬戶詳情',
'earnCoin' => '掙取金幣',
'dianZan' => '點贊',
'exchangePrizesDescribe' => '兌換獎品描述',
'addJoke' => '我講一個',
'myJoke' => '我的笑話',
'tradeLog' => '交易記錄',
'exchangePrizes' => '兌換獎品',
'giveMessage' => '給客服留言'
);
// 數字到字符串命令字典
public static $numToCmdStr = array(
'0' => 'readHelp',
'1' => 'getOneAgain',
'2' => 'exchangePrizesDescribe',
'3' => 'earnCoin',
'4' => 'accountDetail',
'5' => 'myJoke',
'6' => 'tradeLog',
'7' => 'addJoke',
'8' => 'dianZan',
'9' => 'giveMessage',
'100' => 'exchangePrizes'
);
/**
* 查找命令
*
* @param array $request 請求數組
* @return string cmd
*/
public static function findCmd($request){
// 驗證cmd
$cmd = self::findValidateCmd($request);
if (strlen($cmd) > 0) {
return self::checkCmdValid($cmd);
}
// 查找文本命令
$cmd = self::findTextCmd($request);
if (strlen($cmd) > 0) {
return self::checkCmdValid($cmd);
}
// 查找事件命令
$cmd = self::findEventCmd($request);
if (strlen($cmd) > 0) {
return self::checkCmdValid($cmd);
}
}
/**
* 檢查命令的有效性
*
* @param string $cmd 命令
* @return string 有效返回$cmd,否則為空
*/
public static function checkCmdValid($cmd){
if (isset(self::$cmdMap[$cmd])) {
return $cmd;
}
}
/**
* 驗證命令
*
* @param array $request 請求數組
* @return string cmd
*/
public static function findValidateCmd($request){
if (isset($request['echostr']) && strlen($request['echostr']) > 0) {
return 'validate';
}
}
/**
* 查找文本命令
*
* @param array $request 請求數組
* @return string cmd
*/
public static function findTextCmd($request){
if (empty($request['post'])) {
return;
}
$msgType = $request['post']['items']['MsgType'];
if ($msgType != 'text') {
return;
}
$content = $request['post']['items']['Content'];
$cmd = trim($content);
$cmd = ltrim($cmd, '【');
$cmd = rtrim($cmd, '】');
if (strpos($cmd, "#") !== false) {
$cmd = substr($cmd, 0, strpos($cmd, "#"));
}
// 大寫
$cmd = strtoupper($cmd);
if (!is_numeric($cmd)) {
return;
}
if (isset(self::$numToCmdStr[$cmd])) {
return self::$numToCmdStr[$cmd];
}
}
/**
* 查找事件命令
*
* @param array $request 請求數組
* @return string cmd
*/
public static function findEventCmd($request){
if (empty($request['post'])) {
return;
}
$msgType = $request['post']['items']['MsgType'];
if ($msgType != 'event') {
return;
}
return $request['post']['items']['Event'];
}
}
~~~
### 6.3 命令調度中心
? ? ? ? ? 當分析出命令后,通過switch ($cmd){}進行調度,通過不同的cmd調用不同InfoCenter執行邏輯去執行。例如用戶回復的數字是1,那么命令解析中心就會把命令翻譯為getOneAgain,調度中心會給根據getOneAgain,調用InfoCenter::getOneAgain($request, &$retArray),最后通過displayUTF8($retArray, 'thxl/validate.html')輸出結果。
### 6.4 命令執行中心
? ? ? ? 當調度中心執行命令中心的函數后,函數根據自己的業務邏輯去執行,這里重點介紹的是回復的內容,我們可以使用smarty模板。然后將模板的內容讀入到字符串中。如下只舉例獲取幫助的代碼。
readHelpContent.html模板如下。
~~~
命令幫助:
********************
0.回復0,查看幫助!
1.回復1,再來一個!
2.回復2,兌換獎品!
3.回復3,掙取金幣!
4.回復4,賬戶詳情!
5.回復5,我的笑話!
6.回復6,金幣記錄!
7.回復7#笑話標題#笑
話正文,我講一個!
8.回復8#笑話編號,點贊
!
9.回復9#留言內容,給客
服留言!
********************
~~~
執行函數如下:
~~~
<?php
/**
* InfoCenter.php
*
* 信息中心
*
* Copyright (c) 2015 http://blog.csdn.net/CleverCode
*
* modification history:
* --------------------
* 2015/7/10, by CleverCode, Create
*
*/
class InfoCenter{
//......
/**
* 查看幫助
*
* @param array $request 請求數組
* @param array $retArray 返回數組(輸出參數)
* @return string 成功返回'OK',失敗返回錯誤信息
*/
public static function readHelp($request, &$retArray){
$postStr = $request['post']['string'];
if (empty($postStr)) {
return 'empty($postStr)';
}
$retArray['data']['fromUsername'] = $request['post']['items']['ToUserName'];
$retArray['data']['toUsername'] = $request['post']['items']['FromUserName'];
$retArray['data']['createTime'] = time();
$retArray['data']['msgType'] = 'text';
$content = SmartyWork::fetch(array(), 'thxl/readHelpContent.html');
$content = str_replace("\r", '', $content);
$retArray['data']['content'] = $content;
return 'OK';
}
//......
}
~~~
## 7 命令回復預覽
### 7.1 關注事件
? ? 關注公眾號后,會自動獲取1000金幣,然后默認將一個笑話。 ??

### 7.2 回復1
? ? 回復1,可以隨機一個笑話。

???
### 7.2 回復2
? ? 回復2,可以查看兌換獎品幫助。

### 7.3 回復3
? ? 回復3,掙取金幣。

### 7.4 回復4
? ? 回復4,賬戶詳情!

### 7.5 回復5
回復5,我的笑話!

### 7.6 回復6
回復6,金幣記錄!

### 7.7 回復7
回復7#笑話標題#笑
? ?話正文,我講一個!

### 7.8 回復8
回復8#笑話編號,點贊
? ?!

### 7.9 回復9
回復9#留言內容,給客
? ?服留言!

### 7.10 回復0
回復0,查看幫助。

## 8 總結
? ? ? ? ? 項目經過幾天的設計后,終于做完了,于是讓自己的好朋友測測,玩一玩。他們給出的點評是,還需要回復啊,不給個按鈕更好。我說沒權限獲取自定義按鈕。那個說笑話需要手動輸入那么多笑話的漢字,手機打字太費勁了。
? ? ? ? ?種種的原因后,這個微信項目擱淺了,我現在想不出來更好的運營方法。后來一想還不如把我的經歷寫出來分享給大家。一個是相互學習與交流。也希望大家不要走學我,頭腦一發熱說干就干。一定需要很周密的計劃才行!謀定而后動..........................