CleverCode前一段时间想去接触一下微信开发,申请了一个人订阅号,发现暂不能申请个人认证,而且没有微信接口的很多权限,也没有自定义菜单的权限(开发模式下)。在开发模式下,只能到手公众号里面的回复信息,然后响应。
1 项目背景
CleverCode想了很久,运营一个什么样的公众号,比较好呢?现在的公众号太多了。比较好的点子,基本都被人想到了。那几天CleverCode的遇到点烦心事,心情不大爽,就想着能不能设计一个讲笑话的公众号,大家能够在里面讲笑话,然后查看笑话,开心一下自己。讲笑话后能够获取金币。积累到一定的金币后,可以兑换奖品。奖品的方式可以是充话费的形式。
说干就干。脑袋一热,CleverCode就开始疯狂的忙碌起来的了............................
2 需求分析
CleverCode发现个人订阅号,只能简单收到客户的信息,然后响应信息。然后CleverCode就想,不如就让用户输入0-9的数字,组合成命令,这样服务器每次收到不同的数字后,去响应不同的请求就可以了。(这种方式基本停留在没有图片,颜色;只有文字交互的时代...........谁叫咋申请的订阅号,没有认证,就没有一些接口的权限)。
3 源码下载
http://download.csdn.net/detail/clevercode/8916699。
4 项目演示
如果你想查看本微信的详细的演示效果,可以在微信中搜索微信号:taihaoxiaole888。或者扫描下方二维码关注。
5 设计过程
5.1 微信配置接口
申请完微信订阅号后,要想微信开发,必须在微信后台(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("
", "", $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 总结
项目经过几天的设计后,终于做完了,于是让自己的好朋友测测,玩一玩。他们给出的点评是,还需要回复啊,不给个按钮更好。我说没权限获取自定义按钮。那个说笑话需要手动输入那么多笑话的汉字,手机打字太费劲了。
种种的原因后,这个微信项目搁浅了,我现在想不出来更好的运营方法。后来一想还不如把我的经历写出来分享给大家。一个是相互学习与交流。也希望大家不要走学我,头脑一发热说干就干。一定需要很周密的计划才行!谋定而后动..........................