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