用微信 远程遥控 服务器

摘要

微信公众好的开发很火,小程序更火。于是也凑个热闹,尝试了一把。

大致的功能还是有的,不过是不全,很多地方我没有进行处理。不过对于纯文本方式的交流,已经没有问题啦。


命令
音乐

环境搭建

下面大致的讲讲微信公众号的原理吧。可能我理解的有些不到位,如果有些许不当,欢迎批评指教。

客户端发送给微信平台请求,微信平台将请求转发给私服,交给程序处理之后,获取到私服的处理结果,然后反馈给客户端。

当然,这其中起到核心作用的自然是“微信公众平台”啦。相当于提供了一个舞台,一个能让各位能人异士展现出各自的特色的平台。其实,不仅微信如此,阿里同样是这样,如此各大电商才能一展手脚不是。

开启配置

这第一步,就是先申请一个微信开发者账号,个人的话选择订阅号就足够了。网上相关的资料很多,也很详细,我就不多说了。咱们直奔主题好了。

首先登陆开发者账号成功后,开启服务器端的设置即可,如下图


开启配置

开启完成,根据自己服务器的情况进行一下设置即可。

  • URL就是你的私服用于处理请求数据的地址

  • TOKEN就是一个令牌,随便设置。不过记住待会自己的代码上会用到。

  • 至于密钥嘛,没什么较大的作用,暂且可以先不用管。

按需设置

设置完,就可以启用了。这就好比家里的电线全部装修好了,现在要使用,按下开关一样。如下图


启用服务器配置

服务器环境

关于服务器这块,官网上讲解的也是很详细的啦。
https://mp.weixin.qq.com/wiki

我们还可以下载官方的demo来模拟。


官方样本

代码也很简单。基本上学过了PHP基本语法的都能够看得懂。

<?php
/**
  * wechat php test
  */

//define your token
define("TOKEN", "weixin");
$wechatObj = new wechatCallbackapiTest();
$wechatObj->valid();

class wechatCallbackapiTest
{
    public function valid()
    {
        $echoStr = $_GET["echostr"];

        //valid signature , option
        if($this->checkSignature()){
            echo $echoStr;
            exit;
        }
    }

    public function responseMsg()
    {
        //get post data, May be due to the different environments
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

        //extract post data
        if (!empty($postStr)){
                /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
                   the best way is to check the validity of xml by yourself */
                libxml_disable_entity_loader(true);
                $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
                $fromUsername = $postObj->FromUserName;
                $toUsername = $postObj->ToUserName;
                $keyword = trim($postObj->Content);
                $time = time();
                $textTpl = "<xml>
                            <ToUserName><![CDATA[%s]]></ToUserName>
                            <FromUserName><![CDATA[%s]]></FromUserName>
                            <CreateTime>%s</CreateTime>
                            <MsgType><![CDATA[%s]]></MsgType>
                            <Content><![CDATA[%s]]></Content>
                            <FuncFlag>0</FuncFlag>
                            </xml>";             
                if(!empty( $keyword ))
                {
                    $msgType = "text";
                    $contentStr = "Welcome to wechat world!";
                    $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
                    echo $resultStr;
                }else{
                    echo "Input something...";
                }

        }else {
            echo "";
            exit;
        }
    }
        
    private function checkSignature()
    {
        // you must define TOKEN by yourself
        if (!defined("TOKEN")) {
            throw new Exception('TOKEN is not defined!');
        }
        
        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];
                
        $token = TOKEN;
        $tmpArr = array($token, $timestamp, $nonce);
        // use SORT_STRING rule
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode( $tmpArr );
        $tmpStr = sha1( $tmpStr );
        
        if( $tmpStr == $signature ){
            return true;
        }else{
            return false;
        }
    }
}

?>

核心思路,无非检验一下签名,处理一下请求,反馈一下结果罢了。


这里我不得不想说的就是,我觉得腾讯其实可以将那些个模板什么的去掉,直接暴露出黑盒模式,这样的话安全性会更高一点。很多时候,权限放的越开,效果可能越差。

核心类

接下来就是我自己的处理逻辑了,参照官方文档。微信公众好上有6大接收接口,三大回复接口。依据MsgType即可判定。


接口详情

验证

private function checkSignature() {
        // you must define TOKEN by yourself
        if (! defined ( "TOKEN" )) {
            throw new Exception ( 'TOKEN is not defined!' );
        }
        
        $signature = $_GET ["signature"];
        $timestamp = $_GET ["timestamp"];
        $nonce = $_GET ["nonce"];
        
        $token = TOKEN;
        $tmpArr = array (
                $token,
                $timestamp,
                $nonce 
        );
        // use SORT_STRING rule
        sort ( $tmpArr, SORT_STRING );
        $tmpStr = implode ( $tmpArr );
        $tmpStr = sha1 ( $tmpStr );
        
        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

验证方法核心就是依据咱们之前网页上设置的TOKEN来工作的,所以代码上会用得到。

回复

回复的代码需要依据客户端发送的数据的类型来区分对待,类型这块微信平台会将数据打包好封装起来,我们住需要调用内部的MsgType进行处理即可。

拓展

拓展部分,是我自己异想天开往上加的。

添加机器人

调用一个机器人接口,来代替自己发送回复,技能让用户得到一个良好的用户体验,还能愉悦大众,何乐而不为?
我这边测试了两个接口,一个是curl模式,一个是file_get_contents模式,都挺好用的啦。

<?php
/**
 * 图灵 机器人接口
 * 
 * 使用curl来进行浏览器模拟并抓取数据
 */
function turing($requestStr) {
    // 图灵机器人接口
    $url = "http://www.tuling123.com/openapi/api";
    // 用于POST请求的数据
    $data = array(
        'key'=>"哈哈,这个key还是得你自己去申请的啦",
        'info'=>$requestStr,
    );
    
    // 构造curl下载器
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $responseStr = curl_exec($ch);
    curl_close($ch);
    
    return $responseStr;
}

/**
 * 调用另外的接口
 * @param unknown $req
 * @return mixed
 */
function test($req){
    $url = "http://api.qingyunke.com/api.php?key=free&appid=0&msg=".$req;
    $result = file_get_contents($url);
    $result = json_decode($result, true);
    return $result['content'];
}


$req = 'hello';
$res = test($req);
echo $res;

命令模式

手机相对于电脑一个很大的优点就是便携,我们虽然不能随时随地携带电脑,但是却能使用手机来代替。很多时候对服务器的管理需要的命令很简单,但是远程登录的时候也不方便。这个时候就用微信来帮忙传话也是不错的啦。

我平时喜欢使用Python写一些脚本,什么获取本地IP,聊天,查看内存,网速啥的,可谓是应有尽有。这下也终于能有用武之地了。利用微信的关键字匹配,就可以简单的让微信公众号当一个小小传话员啦。

这里给个思路,具体实现起来也比较简单,当做是文本来处理即可。

完整代码

下面贴出我服务器上的完整代码,有些私密的地方我做了些更改,届时按照自己的情况进行修改即可。

<?php
/**
 * wechat php test
 */

// define your token
define ( "TOKEN", "您的TOKEN" );
$wechatObj = new wechatCallbackapiTest ();
// $wechatObj->valid();

// 调用回复信息方法
$wechatObj->responseMsg ();

// 微信消息处理核心类
class wechatCallbackapiTest {
    public function valid() {
        $echoStr = $_GET ["echostr"];
        
        // valid signature , option
        if ($this->checkSignature ()) {
            echo $echoStr;
            exit ();
        } else {
            echo "验证失败!";
        }
    }
    public function responseMsg() {
        // get post data, May be due to the different environments
        // 类似$_POST但是可以接受XML数据,属于增强型
        $postStr = $GLOBALS ["HTTP_RAW_POST_DATA"];
        
        // extract post data
        if (! empty ( $postStr )) {
            /*
             * libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
             * the best way is to check the validity of xml by yourself
             */
            // 不解析外部数据,防止xxml漏洞
            libxml_disable_entity_loader ( true );
            $postObj = simplexml_load_string ( $postStr, 'SimpleXMLElement', LIBXML_NOCDATA );
            $fromUsername = $postObj->FromUserName;
            $toUsername = $postObj->ToUserName;
            $keyword = trim ( $postObj->Content );
            $time = time ();
            
            /*
             * 微信客户端发送信息的时候会附带一些参数,详见官方文档。所以要根据不同的类型,来分别做相关的处理。
             * 于是MsgType 就充当这样的一个区分的标记
             */
            $msgType = $postObj->MsgType;
            
            
            /*
             * 当有用户关注后者退订的时候,会触发相应的事件。所以再来个event事件的监听更为友好。
             * $event = $postObj->Event.
             * 具体的参数信息,官网上很详细。
             */
            $event = $postObj->Event;
            
            
            
            switch ($msgType) {
                // 文本消息 处理部分
                case "text" :
                    if (! empty ( $keyword )) {
                        // 在此处进行对关键字的匹配就可以实现:针对不同关键字组装的相应数据
                        if($keyword=='音乐' || $keyword == "music") {
                            $msgType = 'music';
                            $musictitle = "The Mountain";
                            $musicdescription = "夏日舒心清凉歌曲";
                            $musicurl = "http://101.200.58.242/wx/themaintain.mp3";
                            $hqmusicurl = "http://101.200.58.242/wx/themaintain.mp3";
                            musicMessageHandle($fromUsername, $toUsername, $time, $msgType, $musictitle, $musicdescription, $musicurl, $hqmusicurl);
                        }elseif($keyword == '1'){
                            $msgType = 'text';
                            $contentStr = "人生得意须尽欢,莫使金樽空对月!";
                            textMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr);
                        }elseif($keyword == '命令模式'){
                            $msgType = 'text';
                            $contentStr = "进入命令模式,开始对服务器进行管理!\n接下来将依据您输入的命令对服务器进行管理!";
                            textMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr);
                        }else {
                            // 直接调用  机器人接口,与用户进行交流
                            $msgType = "text";
                            $contentStr = turing($keyword)!=""?turing($keyword):"这里是微信 纯文本测试数据!";
                            textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                        }
                    } else {
                        echo "您得输入点数据,我才能回复不是!";
                    }
                    break;
                // 接收图片信息
                case "image" :
                    if (! empty ( $keyword )) {
//                      $msgType = "image";
                        $contentStr = "您发送的图片看起来还真不错!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的图片!";
                    }
                    break;
                // 接收语音信息
                case "voice" :
                    if (! empty ( $keyword )) {
//                      $msgType = "voice";
                        $contentStr = "您发送的语音听起来还真不错!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的语音!";
                    }
                    break;
                // 接收视频信息
                case "video" :
                    if (! empty ( $keyword )) {
//                      $msgType = "video";
                        $contentStr = "您发送的视频看起来还真不错!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的视频!";
                    }
                    break;
                // 接收视频信息
                case "shortvideo" :
                    if (! empty ( $keyword )) {
//                      $msgType = "shortvideo";
                        $contentStr = "您发送的小视频看起来还真不错!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的小视频!";
                    }
                    break;
                // 接收位置信息
                case "location" :
                    if (! empty ( $keyword )) {
//                      $msgType = "location";
                        $contentStr = "您发送的位置已被接收!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的位置!";
                    }
                    break;
                // 接收视频信息
                case "link" :
                    if (! empty ( $keyword )) {
//                      $msgType = "link";
                        $contentStr = "您发送的链接看起来还真不错!";
                        textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
                    } else {
                        echo "服务器没能收到您发送的链接!";
                    }
                    break;
                // 对事件进行侦听
                case "event":
                    switch ($event) {
                        case "subscribe":
                            // 发送一些消息!
                            $msgType = 'text';
                            $contentStr = "终于等到你!";
                            textMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr);
                            break;
                    }
                    break;
                default :
                    break;
            }
        } else {
            echo "";
            exit ();
        }
    }
    private function checkSignature() {
        // you must define TOKEN by yourself
        if (! defined ( "TOKEN" )) {
            throw new Exception ( 'TOKEN is not defined!' );
        }
        
        $signature = $_GET ["signature"];
        $timestamp = $_GET ["timestamp"];
        $nonce = $_GET ["nonce"];
        
        $token = TOKEN;
        $tmpArr = array (
                $token,
                $timestamp,
                $nonce 
        );
        // use SORT_STRING rule
        sort ( $tmpArr, SORT_STRING );
        $tmpStr = implode ( $tmpArr );
        $tmpStr = sha1 ( $tmpStr );
        
        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }
}


/**
 * 定义为心中想难关的六个接口的数据发送格式模板
 */
function textMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr) {
    $textTpl = "<xml>
                    <ToUserName><![CDATA[%s]]></ToUserName>
                    <FromUserName><![CDATA[%s]]></FromUserName>
                    <CreateTime>%s</CreateTime>
                    <MsgType><![CDATA[%s]]></MsgType>
                    <Content><![CDATA[%s]]></Content>
                    <FuncFlag>0</FuncFlag>
                </xml>";
    $resultStr = sprintf ( $textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr );
    echo $resultStr;
}
function imageMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr) {
    $imageTpl = "<xml>
                     <ToUserName><![CDATA[%s]]></ToUserName>
                     <FromUserName><![CDATA[%s]]></FromUserName>
                     <CreateTime>%s</CreateTime>
                     <MsgType><![CDATA[%s]]></MsgType>
                     <Content><![CDATA[%s]]></Content>
                     <PicUrl><![CDATA[this is a url]]></PicUrl>
                     <MediaId><![CDATA[media_id]]></MediaId>
                     <MsgId>1234567890123456</MsgId>
                 </xml>";
    
    $resultStr = sprintf ( $textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr );
    echo $resultStr;
}
function musicMessageHandle($fromUsername, $toUsername, $time, $msgType, $musictitle, $musicDescription, $musicurl, $hqmusicurl) {
    $musicTpl = "<xml>
                    <ToUserName><![CDATA[%s]]></ToUserName>
                    <FromUserName><![CDATA[%s]]></FromUserName>
                    <CreateTime>%s</CreateTime>
                    <MsgType><![CDATA[%s]]></MsgType>
                    <Music>
                        <Title><![CDATA[%s]]></Title>
                        <Description><![CDATA[%s]]></Description>
                        <MusicUrl><![CDATA[%s]]></MusicUrl>
                        <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>
                    </Music>
                </xml>";
    $resultStr = sprintf($musicTpl, $fromUsername, $toUsername, $time, $msgType, $musictitle, $musicDescription, $musicurl, $hqmusicurl);
    echo $resultStr;
}



/**
 * 图灵 机器人接口
 * 
 * 使用curl来进行浏览器模拟并抓取数据
 */
function turing($requestStr) {
    /* // 图灵机器人接口
    $url = "http://www.tuling123.com/openapi/api";
    // 用于POST请求的数据
    $data = array(
        "key"=>"您在图灵机器人官网上申请的key",
        "info"=>$requestStr
    );
    
    // 构造curl下载器
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $requestStr = curl_exec($ch);
    curl_close($ch);
    
    return responseStr; */
    
    
    $url = "http://api.qingyunke.com/api.php?key=free&appid=0&msg=".$requestStr;
    $result = file_get_contents($url);
    $result = json_decode($result, true);
    return $result['content'];
}

?>

总结

最后来回顾一下,本次试验用到了哪些知识点。

  • PHP的面向对象方法编程简单实现。

  • 接口处理的两种方式

  • 微信公众号后台私服的接入,处理,反馈。

  • 前后端的交互,以及聊天机器人的应用。

其实,这些代码跟我一开始的设想还是差别挺大的,原本是想实现一个“遥控器”,晚上想睡觉之前,用微信发一条命令“打开电热毯”,半个小时后,电视看完了,去睡觉的时候发现被窝很暖和,是的,只要加上点硬件,这很容易实现啦再者冰箱了,电视了统统可以完成,那样估计就诊的是“智能家居”了吧。

不过马上考试了,先不折腾了。期末考试完,寒假没事的时候再来完善一下好了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,082评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,231评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,047评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,977评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,893评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,976评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,605评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,888评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,906评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,732评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,513评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,980评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,132评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,447评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,027评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,232评论 2 339

推荐阅读更多精彩内容