模型:WeChat (回复参考weiphp)
<?php
namespace Org;
/**
* 微信开发工具类
* Class WeChat
* Author chenqionghe
* @package Org
*/
class WeChat
{
const LOG_NAME = "PHP_LOG_%s.log.php"; //日志名
const LOG_DIR = "./Log/%s/"; //日志目录 static private $fromUser = ''; //当前消息的发送者
static private $toUser = ''; //当前消息的接收者
static private $member = ''; //公众号会员记录 /**
* 设置要操作的微信公众号
* @param $mid 公众号id
*/
static public function setMember($mid)
{
self::$member = M('Member')->find($mid);
}
/**
* 处理来自微信服务器的消息
* @param $callback
* @access public
* @return void
*/
static public function process($callback)
{
//如果回调函数没有设置,则退出
if (!is_callable($callback))
{
return;
}
$postData = $GLOBALS['HTTP_RAW_POST_DATA'];
if (empty($postData)) return; //如果没有POST数据,则退出
$object = simplexml_load_string($postData, 'SimpleXMLElement', LIBXML_NOCDATA);//解析POST数据(XML格式)
$messgeType = trim($object->MsgType); //取得消息类型
self::$fromUser = $object->FromUserName; //记录消息发送方(不是发送者的微信号,而是一个加密后的OpenID)
self::$toUser = $object->ToUserName; //记录消息接收方(就是公共平台的OpenID)
self::addLog($postData,1,$messgeType); //记录日志 //根据不同的消息类型,分别处理
switch($messgeType)
{
case "text": //文本消息
//调用回调函数
call_user_func($callback, "Text", array('Content'=>$object->Content));
break;
case "image": //图片消息
call_user_func($callback, "Image",array('PicUrl'=>$object->PicUrl, 'MediaId'=>$object->MediaId));
break;
case "voice": //音频消息
call_user_func($callback, "Voice",array('MediaId'=>$object->MediaId, 'Format'=>$object->Format,'Recognition'=>$object->Recognition) );
break;
case "video": //视频消息
call_user_func($callback, "Video", array('MediaId'=>$object->MediaId, 'ThumbMediaId'=>$object->ThumbMediaId));
break;
case "shortvideo": //小视频消息
call_user_func($callback, "Shortvideo", array('MediaId'=>$object->MediaId, 'ThumbMediaId'=>$object->ThumbMediaId));
break;
case "location": //定位信息
call_user_func($callback, "Location", array('Label'=>$object->Label, 'Location_X'=>$object->Location_X, 'Location_Y'=>$object->Location_Y,'Scale'=>$object->Scale));
break;
case "link": //链接信息
call_user_func($callback, "Link", array('Url'=>$object->Url, 'Title'=>$object->Title, 'Description'=>$object->Description));
break;
case "event": //事件
switch ($object->Event)
{
case "subscribe": //订阅事件
call_user_func($callback, "Subscribe",array( 'EventKey'=>$object->EventKey, 'Ticket'=>$object->Ticket));
break;
case "unsubscribe": //取消订阅事件
call_user_func($callback, "UnSubscribe", array('FromUserName'=>$object->FromUserName));
break;
case "CLICK": //点击菜单拉取消息时的事件
call_user_func($callback, "Click", array('EventKey'=>$object->EventKey));
break;
case "VIEW": //点击菜单跳转链接时的事件
call_user_func($callback, "View",array('EventKey'=> $object->EventKey));
break;
case "scancode_push": //扫码推事件的事件推送
call_user_func($callback, "ScanPush",array( 'EventKey'=>$object->EventKey,'ScanCodeInfo'=>$object->ScanCodeInfo,'ScanType'=>$object->ScanType,'ScanResult'=>$object->ScanResult));
break;
case "scancode_waitmsg": //扫码推事件且弹出“消息接收中”提示框的事件推送
call_user_func($callback, "ScanWaitmsg",array( 'EventKey'=>$object->EventKey,'ScanCodeInfo'=>$object->ScanCodeInfo,'ScanType'=>$object->ScanType,'ScanResult'=>$object->ScanResult));
break;
case "pic_sysphoto": //弹出系统拍照发图的事件推送
call_user_func($callback, "PicSysPhoto",array( 'EventKey'=>$object->EventKey, 'SendPicsInfo'=>$object->SendPicsInfo,'Count'=>$object->Count,'PicList'=>$object->PicList,'PicMd5Sum'=>$object->PicMd5Sum));
break;
case "pic_photo_or_album": //弹出拍照或者相册发图的事件推送
call_user_func($callback, "PicPhotoOrAlbum",array( 'EventKey'=>$object->EventKey, 'SendPicsInfo'=>$object->SendPicsInfo,'Count'=>$object->Count,'PicList'=>$object->PicList,'PicMd5Sum'=>$object->PicMd5Sum));
break;
case "pic_weixin": //弹出微信相册发图器的事件推送
call_user_func($callback, "PicWeixin",array( 'EventKey'=>$object->EventKey, 'SendPicsInfo'=>$object->SendPicsInfo,'Count'=>$object->Count,'PicList'=>$object->PicList,'PicMd5Sum'=>$object->PicMd5Sum));
break;
case "location_select": //弹出地理位置选择器的事件推送
call_user_func($callback, "LocationSelect",array( 'EventKey'=>$object->EventKey, 'SendLocationInfo'=>$object->SendLocationInfo,'Location_X'=>$object->Location_X,'Location_Y'=>$object->Location_Y,'Scale'=>$object->Scale,'Label'=>$object->Label,'Poiname'=>$object->Poiname));
break;
case 'LOCATION': //上报地址位置事件
call_user_func($callback, "UpLocation",array( 'Latitude'=>$object->Latitude, 'Longitude'=>$object->Longitude, 'Precision'=>$object->Precision));
break;
default :
//Unknow Event
break;
}
break;
default:
//未知消息类型
break;
}
} /**
* 获取access_token
* $mid 公众号id
* @access public
* @return mixed
*/
static public function getAcctoken ($mid='')
{
if(empty($mid)) {
$mid = defined('MID') ? MID : session('mid'); //如果定义了常量MID说明是微信请求的 ,否则是本地
}
$token = S('access_token'.$mid);
if ($token === false)
{
if(empty(self::$member)) {
self::$member = M('Member')->find($mid);
}
$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.self::$member['appid'].'&secret='.self::$member['secret'];
$result = self::http_get($url);
$result = json_decode($result, true);
$token = $result['access_token'];
S('access_token'.$mid, $token, $result['expires_in'] - 1000);
}
return $token;
} /**
* 验证签名是否正确
* @access public
* @param $token
*/
static public function checkSignature($token)
{
$tmpArr = array($token,$_GET ["timestamp"],$_GET ["nonce"]);
sort($tmpArr, SORT_STRING );
$tmpStr = sha1(implode( $tmpArr));
if ($tmpStr == $_GET ["signature"])
{
ob_clean();
echo $_GET ["echostr"];
}
self::addLog($_GET,4,'微信接入');//记录日志
exit;
} /*************************************************** 发送消息响应微信 begin ***************************************************/
/**
* 回复文本消息
* @param $content 文本内容
* @access public
* @return void
*/
static public function replyText($content)
{
$msg ['Content'] = $content;
self::_replyData ( $msg, 'text' );
} /**
* 回复图片消息
* @param $media_id MediaId
*/
static public function replyImage($media_id)
{
$msg ['Image'] ['MediaId'] = $media_id;
self::_replyData ( $msg, 'image' );
} /**
* 回复语音消息
* @param $media_id MediaId
* @access public
* @return void
*/
static public function replyVoice($media_id)
{
$msg ['Voice'] ['MediaId'] = $media_id;
$msg ['Voice'] ['MediaId'] = $media_id;
self::_replyData ( $msg, 'voice' );
} /**
* 回复视频消息
* @param $media_id MediaId
* @param string $title 标题
* @param string $description 简介
* @access public
* @return void
*/
static public function replyVideo($media_id, $title = '', $description = '')
{
$msg ['Video'] ['MediaId'] = $media_id;
$msg ['Video'] ['Title'] = $title;
$msg ['Video'] ['Description'] = $description;
self::_replyData ( $msg, 'video' );
} /**
* 回复音乐消息
* @param $media_id MediaId
* @param string $title 标题
* @param string $description 简介
* @param $music_url 音乐地址
* @param $HQ_music_url 高品质音乐地址
* @access public
* @return void
*/
static function replyMusic($media_id, $title = '', $description = '', $music_url, $HQ_music_url)
{
$msg ['Music'] ['ThumbMediaId'] = $media_id;
$msg ['Music'] ['Title'] = $title;
$msg ['Music'] ['Description'] = $description;
$msg ['Music'] ['MusicURL'] = $music_url;
$msg ['Music'] ['HQMusicUrl'] = $HQ_music_url;
self::_replyData ( $msg, 'music' );
} /**
* 回复图文消息 格式如下:
array(
array($Title, $Description, $PicUrl , $Url),
array($Title, $Description, $PicUrl , $Url),
);
* @param array $articles
* @access public
* @return void
*/
static public function replyNews($articles)
{
foreach($articles as $k=>$v)
{
$arr[] = array('Title'=>$v[0],'Description'=>$v[1],'PicUrl'=>$v[2],'Url'=>$v[3]);
}
$msg ['ArticleCount'] = count ( $articles );
$msg ['Articles'] = $arr; self::_replyData ( $msg, 'news' );
} /**
* 将消息转发给客服
* @param string $kf_account 指定客服的账号,不传则由微信分配
* @access public
* @return void
*/
static public function replyKefu($kf_account='')
{
if(!empty($kf_account))
{
$msg ['TransInfo'][] = $kf_account;
}
self::_replyData($msg,'transfer_customer_service','KfAccount');
} /**
* 发送回复消息到微信平台
* @param array $msg 消息数组
* @param $msgType 消息类型
* @param string $item 包含子元素的元素名
* @access private
* @return void
*/
static private function _replyData($msg, $msgType, $item = 'item')
{
$msg ['ToUserName'] = strval(self::$fromUser);
$msg ['FromUserName'] = strval(self::$toUser);
$msg ['CreateTime'] = NOW_TIME;
$msg ['MsgType'] = $msgType;
if($_REQUEST ['doNotInit'])
{
dump($msg);
exit;
}
$xml = new \SimpleXMLElement ( '<xml></xml>' );
self::_data2xml ( $xml, $msg ,$item);
$str = $xml->asXML ();
self::addLog($str,2,'发送消息');//记录日志
echo $str;
} /**
* 组装xml数据
* @param $xml xml对象
* @param $data 要格式化的数组
* @param string $item 包含子元素的元素名
* @access public
* @return void
*/
static public function _data2xml($xml, $data, $item = 'item')
{
foreach ( $data as $key => $value )
{
is_numeric ( $key ) && ($key = $item);
if (is_array ( $value ) || is_object ( $value ))
{
$child = $xml->addChild ( $key );
self::_data2xml ( $child, $value, $item );
} else
{
if (is_numeric ( $value ))
{
$child = $xml->addChild ( $key, $value );
} else {
$child = $xml->addChild ( $key );
$node = dom_import_simplexml ( $child );
$node->appendChild ( $node->ownerDocument->createCDATASection ( $value ) );
}
}
}
}
/*************************************************** 发送消息响应微信 end ***************************************************/ /*************************************************** 上传下载文件 begin ***************************************************/
/**
* 上传临时素材
* @param $file 要发送的文件
* @param string $type 文件类型
* @access public
* @return mixed
*/
static public function uploadFile($file, $type = 'image')
{
// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
$post_data = array('meida'=>"@".$file,'type'=>$type);
$url = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=".self::getAcctoken() ."&type=".$type;
return self::http_post($url,$post_data);
} /* 上传永久素材*/
static public function uploadYJFile($file, $type = 'image')
{
// 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
$post_data = array('meida'=>"@".$file,'type'=>$type);
$url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=".self::getAcctoken();
return self::http_post($url,$post_data);
} /**
* 下载多媒体文件
* @param $media_id MediaId
* @access public
* @return void
*/
static public function downloadFile($media_id)
{
$url = '';
$filename = date('Y-m-d_H_i_s',time()).'.jpg';//设置保存的文件名
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt ( $ch, CURLOPT_TIMEOUT, 60 );
$temp = curl_exec($ch);
if(@file_get_contents($filename,$temp) && !curl_errno($ch)) {
echo $filename;
}
else {
echo false;
} // TODO
}
/*************************************************** 上传下载文件 end ***************************************************/ /*************************************************** 执行http请求方法 begin ***************************************************/
/**
* 发送GET 请求
* @param string $url 地址
* @access public
* @return mixed
*/
static public function http_get($url)
{
$oCurl = curl_init ();
if (stripos ( $url, "https://" ) !== FALSE)
{
curl_setopt ( $oCurl, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt ( $oCurl, CURLOPT_SSL_VERIFYHOST, FALSE );
}
curl_setopt ( $oCurl, CURLOPT_URL, $url );
curl_setopt ( $oCurl, CURLOPT_RETURNTRANSFER, 1 );
$sContent = curl_exec ( $oCurl );
$aStatus = curl_getinfo ( $oCurl );
curl_close ( $oCurl );
return intval ( $aStatus ["http_code"] ) == 200 ? $sContent : false;
} /**
* 发送POST 请求
* @param string $url 地址
* @param array $param 参数
* @access public
* @return string content
*/
static public function http_post($url, $param)
{
$oCurl = curl_init ();
if (stripos ( $url, "https://" ) !== FALSE)
{
curl_setopt ( $oCurl, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt ( $oCurl, CURLOPT_SSL_VERIFYHOST, false );
}
curl_setopt ( $oCurl, CURLOPT_URL, $url );
curl_setopt ( $oCurl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt ( $oCurl, CURLOPT_POST, true );
curl_setopt ( $oCurl, CURLOPT_POSTFIELDS, $param );
$sContent = curl_exec ( $oCurl );
$aStatus = curl_getinfo ( $oCurl );
curl_close ( $oCurl );
return intval ( $aStatus ["http_code"] ) == 200 ? $sContent : false;
}
/*************************************************** 执行http请求方法 end ***************************************************/ /*************************************************** 记录日志 begin ***************************************************/
/**
* @param $data 要记录的内容
* @param int $type 日志类型(1:Receive,2:Send,3:Sql,4:Signature)
* @param string $comment 备注信息
* @return null
*/
static public function addLog($data,$type=1,$comment='')
{
//日志存放文件夹名
$dirName = array('1' => 'Receive','2' => 'Send','3' => 'Sql','4'=>'Signature'); //构造日志文件名
$logDir = sprintf(self::LOG_DIR, $dirName[$type]); //格式化文件夹
$logName = sprintf(self::LOG_NAME,date('ymd')); //格式化文件名
if(!is_dir($logDir)) mkdir($logDir,0777,true); //如果文件夹不存在,创建
$logFile = $logDir.$logName; //日志最终文件名 //构造日志文件内容
$separator = str_repeat('*',42);
$content[] = "/$separator <- Begin:".date('Y-m-d H:i:s')." --> $separator/";
$content[] = trim(var_export($data,true),'\'');
if(!empty($comment)) $content[] = '备注: '.$comment;
$content[] = "/$separator <------------ End ------------> $separator/";
$logContent = PHP_EOL.implode(PHP_EOL,$content); //写入日志文件
$fp = fopen($logFile,"a+");
flock($fp, LOCK_EX) ;
fwrite($fp,$logContent);
flock($fp, LOCK_UN);
fclose($fp);
}
/*************************************************** 记录日志 end ***************************************************/ /*************************************************** oAuth网页授权获取用户信息 begin ***************************************************/ /**
* 网页授权获取用户信息
* @param $type 类型为openid或者userinfo
* @return mixed
*/
public static function oAuthGet($type)
{
if (!isset($_GET['code']))
{
//获取code码,以获取openid
$scopeArr = array('openid'=>'snsapi_base','userinfo'=>'snsapi_userinfo');
$scope = $scopeArr[$type];
$redirect_url = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']);
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=".self::$member['appid']."&redirect_uri=$redirect_url&response_type=code&scope=$scope&state=oAuthInfo#wechat_redirect";
header ( 'Location: ' . $url );
}
else
{
/******************* 1.回调页面得到code *******************/
$code = $_GET['code'];
/******************* 2.用code去获取access_token和openid *******************/
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".self::$member['appid']."&secret=".self::$member['secret']."&code=$code&grant_type=authorization_code";
$result = self::http_get($url);
$result = json_decode($result,true);
$openid = $result['openid'];
if($result['scope'] == 'snsapi_base') //如果类型是snsapi_base,只返回openid
return $openid;
else
{
$access_token = $result['access_token']; //access_token
/******************* 3.用获取到的openid和access_token获取获取用户详细信息 *******************/
$url = "https://api.weixin.qq.com/sns/userinfo?access_token=$access_token&openid=$openid&lang=zh_CN";
$result = self::http_get($url);
return json_decode($result,true);
}
} }
/*************************************************** oAuth网页授权获取用户信息 end ***************************************************/ /*************************************************** 获取jssdk配置 begin ***************************************************/ /**
* 获取 JSSDK 配置
* @param bool $debug 是否开启调试模式
* @access public
* @return array
*/
public static function getJssdkConf($debug = false) {
$data = array(
'noncestr' => self::createNonceStr(),
'jsapi_ticket' => self::getJsApiTicket(),
'timestamp' => time(),
'url' => SERVER.__SELF__,
);
$str = 'jsapi_ticket='.$data['jsapi_ticket'].'&noncestr='.$data['noncestr'].'×tamp='.$data['timestamp'].'&url='.$data['url'];
return array(
'debug' => $debug,
'appId' => self::$member['appid'],
'timestamp' => $data['timestamp'],
'nonceStr' => $data['noncestr'],
'signature' => sha1($str),
);
} private static function getJsApiTicket() {
$mid = self::$member['id'];
$ticket = S('jsapi_ticket'.$mid);
if ($ticket == false) {
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=".self::getAcctoken($mid);
$result =WeChat::http_get($url);
$result = json_decode($result, true);
$ticket = $result['ticket'];
S('jsapi_ticket'.$mid, $ticket, $result['expires_in'] - 1000);
}
return $ticket;
} private static function createNonceStr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/*************************************************** 获取jssdk配置 end ***************************************************/ }
回复微信控制器:WeChatController
<?php
namespace Manage\Controller;
use Think\Controller;
use \Org\WeChat;
class WeChatController extends Controller
{
/* 接收微信的消息 */
public function index()
{
$id = intval(I('get.id'));
$info = M('Member')->where("status='1'")->find($id);
if (!is_array($info)) exit('请求无效!');
if (!empty ($_GET ['echostr']) && !empty ($_GET ["signature"]) && !empty ($_GET ["nonce"])) {
WeChat::checkSignature($info['token']); //微信接入验证
}
define('MID', $id); //定义公众号常量MID
WeChat::process(array($this, 'reply')); //处理消息
} /* 回复微信消息 */
public function reply($messageType, $data)
{
$controller = 'Process' . '\\Controller\\' . $messageType . 'Controller';
$class = new \ReflectionClass($controller);
$method = $class->getMethod('run');
$instance = $class->newInstance();
$method->invokeArgs($instance, array($data));
}
}
WeChat控制器,负责将请求分发给Process模块下对应控制器处理, 如Click类型由ClickController处理, Text类型由TextController处理,下面给出一个TextController控制器
<?php
namespace Process\Controller;
use \Org\WeChat; class TextController extends ProcessController
{
/* 用户点击菜单 */
public function run($data)
{
WeChat::replyText("您输入的内容是:" .$data['Content']);
}
} }
oAuth网页授权获取用户信息示例TestController
<?php
namespace Test\Controller;
use Think\Controller;
use \Org\WeChat;
class TestController extends Controller
{
/* 获取用户openid */
public function get_openid()
{
WeChat::setMember(2);
$openid = WeChat::oAuthGet('openid');
} /* 获取用户信息 */
public function get_userInfo()
{
WeChat::setMember(2);
$userinfo = WeChat::oAuthGet('userinfo');
var_dump($userinfo);
}
}