chenqionghe

模型: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\'].\'&timestamp=\'.$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);
    }
}

 

分类:

技术点:

相关文章: