企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
       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。或者扫描下方二维码关注。 ![](https://box.kancloud.cn/2016-04-07_5705f500466c8.jpg)      ![](https://box.kancloud.cn/2016-04-07_5705f5005d20c.jpg) ![](https://box.kancloud.cn/2016-04-07_5705f50077f5e.jpg) ## 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等等。 ![](https://box.kancloud.cn/2016-04-07_5705f50096c5f.jpg) ### 5.2 微信接口交互过程         当用户回复内容后,微信需要将用户回复的先传到自己的服务,如果配置成为了开发模式,微信会将调用接口(http://xxxxx.com/apithxl/interface),通过post方式将回复的内容发送到接口服务器上,我接口服务器处理完请求后,只需要输出一个xml信息,微信服务器获取这个xml信息后,在将获取的信息返回给微信用户。具体过程如下图。 ![](https://box.kancloud.cn/2016-04-07_5705f500c24b9.jpg) ### 5.3 检查签名         当用户配置完接口后,需要提交配置,微信向接口发送get请求的字符串。你需要echo它的字符串。这个在微信开发者文档中都会有,我代码中WeiXinCheck::checkSignature($signature, $timestamp, $nonce)也有。 ## 6 接口设计架构 ### 6.1 接口工作原理            当接口收到微信服务器发送过来的请求后,首先需要到命令解析中心,分析出得到是什么请求。然后将得到命令发送到命令调用中心;调度中心会根据不同的命令调用不用执行逻辑,然后返回结果。 ![](https://box.kancloud.cn/2016-04-07_5705f500e2c53.jpg) 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金币,然后默认将一个笑话。    ![](https://box.kancloud.cn/2016-04-07_5705f5005d20c.jpg) ### 7.2 回复1     回复1,可以随机一个笑话。 ![](https://box.kancloud.cn/2016-04-07_5705f501143b2.jpg)     ### 7.2 回复2     回复2,可以查看兑换奖品帮助。 ![](https://box.kancloud.cn/2016-04-07_5705f50163a93.jpg) ### 7.3 回复3     回复3,挣取金币。 ![](https://box.kancloud.cn/2016-04-07_5705f50185675.jpg) ### 7.4 回复4     回复4,账户详情! ![](https://box.kancloud.cn/2016-04-07_5705f501a4491.jpg) ### 7.5 回复5 回复5,我的笑话! ![](https://box.kancloud.cn/2016-04-07_5705f501c1647.jpg) ### 7.6 回复6 回复6,金币记录! ![](https://box.kancloud.cn/2016-04-07_5705f501e6c54.jpg) ### 7.7 回复7 回复7#笑话标题#笑    话正文,我讲一个! ![](https://box.kancloud.cn/2016-04-07_5705f50210466.jpg) ### 7.8 回复8 回复8#笑话编号,点赞    ! ![](https://box.kancloud.cn/2016-04-07_5705f5022fc2f.jpg) ### 7.9 回复9 回复9#留言内容,给客    服留言! ![](https://box.kancloud.cn/2016-04-07_5705f50248d87.jpg) ### 7.10 回复0 回复0,查看帮助。 ![](https://box.kancloud.cn/2016-04-07_5705f5026c5c4.jpg) ## 8 总结           项目经过几天的设计后,终于做完了,于是让自己的好朋友测测,玩一玩。他们给出的点评是,还需要回复啊,不给个按钮更好。我说没权限获取自定义按钮。那个说笑话需要手动输入那么多笑话的汉字,手机打字太费劲了。          种种的原因后,这个微信项目搁浅了,我现在想不出来更好的运营方法。后来一想还不如把我的经历写出来分享给大家。一个是相互学习与交流。也希望大家不要走学我,头脑一发热说干就干。一定需要很周密的计划才行!谋定而后动..........................