模型: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);
}
}