摘要
微信公眾好的開發(fā)很火,小程序更火。于是也湊個熱鬧,嘗試了一把。
大致的功能還是有的,不過是不全,很多地方我沒有進(jìn)行處理。不過對于純文本方式的交流,已經(jīng)沒有問題啦。
環(huán)境搭建
下面大致的講講微信公眾號的原理吧。可能我理解的有些不到位,如果有些許不當(dāng),歡迎批評指教。
客戶端發(fā)送給微信平臺請求,微信平臺將請求轉(zhuǎn)發(fā)給私服,交給程序處理之后,獲取到私服的處理結(jié)果,然后反饋給客戶端。
當(dāng)然,這其中起到核心作用的自然是“微信公眾平臺”啦。相當(dāng)于提供了一個舞臺,一個能讓各位能人異士展現(xiàn)出各自的特色的平臺。其實(shí),不僅微信如此,阿里同樣是這樣,如此各大電商才能一展手腳不是。
開啟配置
這第一步,就是先申請一個微信開發(fā)者賬號,個人的話選擇訂閱號就足夠了。網(wǎng)上相關(guān)的資料很多,也很詳細(xì),我就不多說了。咱們直奔主題好了。
首先登陸開發(fā)者賬號成功后,開啟服務(wù)器端的設(shè)置即可,如下圖
開啟完成,根據(jù)自己服務(wù)器的情況進(jìn)行一下設(shè)置即可。
- URL就是你的私服用于處理請求數(shù)據(jù)的地址
- TOKEN就是一個令牌,隨便設(shè)置。不過記住待會自己的代碼上會用到。
- 至于密鑰嘛,沒什么較大的作用,暫且可以先不用管。
按需設(shè)置
設(shè)置完,就可以啟用了。這就好比家里的電線全部裝修好了,現(xiàn)在要使用,按下開關(guān)一樣。如下圖
啟用服務(wù)器配置
服務(wù)器環(huán)境
關(guān)于服務(wù)器這塊,官網(wǎng)上講解的也是很詳細(xì)的啦。
https://mp.weixin.qq.com/wiki
我們還可以下載官方的demo來模擬。
官方樣本
代碼也很簡單。基本上學(xué)過了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;
}
}
}
?>
核心思路,無非檢驗(yàn)一下簽名,處理一下請求,反饋一下結(jié)果罷了。
這里我不得不想說的就是,我覺得騰訊其實(shí)可以將那些個模板什么的去掉,直接暴露出黑盒模式,這樣的話安全性會更高一點(diǎn)。很多時候,權(quán)限放的越開,效果可能越差。
核心類
接下來就是我自己的處理邏輯了,參照官方文檔。微信公眾好上有6大接收接口,三大回復(fù)接口。依據(jù)MsgType即可判定。
接口詳情
驗(yàn)證
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;
}
}
驗(yàn)證方法核心就是依據(jù)咱們之前網(wǎng)頁上設(shè)置的TOKEN來工作的,所以代碼上會用得到。
回復(fù)
回復(fù)的代碼需要依據(jù)客戶端發(fā)送的數(shù)據(jù)的類型來區(qū)分對待,類型這塊微信平臺會將數(shù)據(jù)打包好封裝起來,我們住需要調(diào)用內(nèi)部的MsgType進(jìn)行處理即可。
拓展
拓展部分,是我自己異想天開往上加的。
添加機(jī)器人
調(diào)用一個機(jī)器人接口,來代替自己發(fā)送回復(fù),技能讓用戶得到一個良好的用戶體驗(yàn),還能愉悅大眾,何樂而不為?
我這邊測試了兩個接口,一個是curl模式,一個是file_get_contents模式,都挺好用的啦。
<?php
/**
* 圖靈 機(jī)器人接口
*
* 使用curl來進(jìn)行瀏覽器模擬并抓取數(shù)據(jù)
*/
function turing($requestStr) {
// 圖靈機(jī)器人接口
$url = "http://www.tuling123.com/openapi/api";
// 用于POST請求的數(shù)據(jù)
$data = array(
'key'=>"哈哈,這個key還是得你自己去申請的啦",
'info'=>$requestStr,
);
// 構(gòu)造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;
}
/**
* 調(diào)用另外的接口
* @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;
命令模式
手機(jī)相對于電腦一個很大的優(yōu)點(diǎn)就是便攜,我們雖然不能隨時隨地攜帶電腦,但是卻能使用手機(jī)來代替。很多時候?qū)Ψ?wù)器的管理需要的命令很簡單,但是遠(yuǎn)程登錄的時候也不方便。這個時候就用微信來幫忙傳話也是不錯的啦。
我平時喜歡使用Python寫一些腳本,什么獲取本地IP,聊天,查看內(nèi)存,網(wǎng)速啥的,可謂是應(yīng)有盡有。這下也終于能有用武之地了。利用微信的關(guān)鍵字匹配,就可以簡單的讓微信公眾號當(dāng)一個小小傳話員啦。
這里給個思路,具體實(shí)現(xiàn)起來也比較簡單,當(dāng)做是文本來處理即可。
完整代碼
下面貼出我服務(wù)器上的完整代碼,有些私密的地方我做了些更改,屆時按照自己的情況進(jìn)行修改即可。
<?php
/**
* wechat php test
*/
// define your token
define ( "TOKEN", "您的TOKEN" );
$wechatObj = new wechatCallbackapiTest ();
// $wechatObj->valid();
// 調(diào)用回復(fù)信息方法
$wechatObj->responseMsg ();
// 微信消息處理核心類
class wechatCallbackapiTest {
public function valid() {
$echoStr = $_GET ["echostr"];
// valid signature , option
if ($this->checkSignature ()) {
echo $echoStr;
exit ();
} else {
echo "驗(yàn)證失敗!";
}
}
public function responseMsg() {
// get post data, May be due to the different environments
// 類似$_POST但是可以接受XML數(shù)據(jù),屬于增強(qiáng)型
$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
*/
// 不解析外部數(shù)據(jù),防止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 ();
/*
* 微信客戶端發(fā)送信息的時候會附帶一些參數(shù),詳見官方文檔。所以要根據(jù)不同的類型,來分別做相關(guān)的處理。
* 于是MsgType 就充當(dāng)這樣的一個區(qū)分的標(biāo)記
*/
$msgType = $postObj->MsgType;
/*
* 當(dāng)有用戶關(guān)注后者退訂的時候,會觸發(fā)相應(yīng)的事件。所以再來個event事件的監(jiān)聽更為友好。
* $event = $postObj->Event.
* 具體的參數(shù)信息,官網(wǎng)上很詳細(xì)。
*/
$event = $postObj->Event;
switch ($msgType) {
// 文本消息 處理部分
case "text" :
if (! empty ( $keyword )) {
// 在此處進(jìn)行對關(guān)鍵字的匹配就可以實(shí)現(xiàn):針對不同關(guān)鍵字組裝的相應(yīng)數(shù)據(jù)
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 = "進(jìn)入命令模式,開始對服務(wù)器進(jìn)行管理!\n接下來將依據(jù)您輸入的命令對服務(wù)器進(jìn)行管理!";
textMessageHandle($fromUsername, $toUsername, $time, $msgType, $contentStr);
}else {
// 直接調(diào)用 機(jī)器人接口,與用戶進(jìn)行交流
$msgType = "text";
$contentStr = turing($keyword)!=""?turing($keyword):"這里是微信 純文本測試數(shù)據(jù)!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
}
} else {
echo "您得輸入點(diǎn)數(shù)據(jù),我才能回復(fù)不是!";
}
break;
// 接收圖片信息
case "image" :
if (! empty ( $keyword )) {
// $msgType = "image";
$contentStr = "您發(fā)送的圖片看起來還真不錯!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的圖片!";
}
break;
// 接收語音信息
case "voice" :
if (! empty ( $keyword )) {
// $msgType = "voice";
$contentStr = "您發(fā)送的語音聽起來還真不錯!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的語音!";
}
break;
// 接收視頻信息
case "video" :
if (! empty ( $keyword )) {
// $msgType = "video";
$contentStr = "您發(fā)送的視頻看起來還真不錯!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的視頻!";
}
break;
// 接收視頻信息
case "shortvideo" :
if (! empty ( $keyword )) {
// $msgType = "shortvideo";
$contentStr = "您發(fā)送的小視頻看起來還真不錯!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的小視頻!";
}
break;
// 接收位置信息
case "location" :
if (! empty ( $keyword )) {
// $msgType = "location";
$contentStr = "您發(fā)送的位置已被接收!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的位置!";
}
break;
// 接收視頻信息
case "link" :
if (! empty ( $keyword )) {
// $msgType = "link";
$contentStr = "您發(fā)送的鏈接看起來還真不錯!";
textMessageHandle ( $fromUsername, $toUsername, $time, $msgType, $contentStr );
} else {
echo "服務(wù)器沒能收到您發(fā)送的鏈接!";
}
break;
// 對事件進(jìn)行偵聽
case "event":
switch ($event) {
case "subscribe":
// 發(fā)送一些消息!
$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;
}
}
}
/**
* 定義為心中想難關(guān)的六個接口的數(shù)據(jù)發(fā)送格式模板
*/
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;
}
/**
* 圖靈 機(jī)器人接口
*
* 使用curl來進(jìn)行瀏覽器模擬并抓取數(shù)據(jù)
*/
function turing($requestStr) {
/* // 圖靈機(jī)器人接口
$url = "http://www.tuling123.com/openapi/api";
// 用于POST請求的數(shù)據(jù)
$data = array(
"key"=>"您在圖靈機(jī)器人官網(wǎng)上申請的key",
"info"=>$requestStr
);
// 構(gòu)造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'];
}
?>
總結(jié)
最后來回顧一下,本次試驗(yàn)用到了哪些知識點(diǎn)。
- PHP的面向?qū)ο蠓椒ň幊毯唵螌?shí)現(xiàn)。
- 接口處理的兩種方式
- 微信公眾號后臺私服的接入,處理,反饋。
- 前后端的交互,以及聊天機(jī)器人的應(yīng)用。
其實(shí),這些代碼跟我一開始的設(shè)想還是差別挺大的,原本是想實(shí)現(xiàn)一個“遙控器”,晚上想睡覺之前,用微信發(fā)一條命令“打開電熱毯”,半個小時后,電視看完了,去睡覺的時候發(fā)現(xiàn)被窩很暖和,是的,只要加上點(diǎn)硬件,這很容易實(shí)現(xiàn)啦再者冰箱了,電視了統(tǒng)統(tǒng)可以完成,那樣估計(jì)就診的是“智能家居”了吧。