自己写的微信公众号开发模型WeChat (回复参考weiphp)

模型: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'].'&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);

    }

}

 

你可能感兴趣的:(微信公众号)