微信公众号接口类(PHP版本)

【项目需求】

通过微信提供的接口,实现微信公众号与后端的应用程序数据交互、消息响应等功能。

【项目疑难点】

  • 理解接口工作方式,统一接口API,响应速度、安全性等
 
【代码举例】
 
WeixinApi.class.php  微信公众号接口基类
 
[php]  view plain  copy
  1. <?php  
  2. /** 
  3.  * 微信API 公用方法 
  4.  *  
  5.  * PHP version 5 
  6.  *  
  7.  * @category    Lib 
  8.  * @package     COM 
  9.  * @subpackage  GZNC 
  10.  * @author      zhongyiwen 
  11.  * @version     SVN: $Id: WeixinApi.class.php 10 2013-10-08 01:34:05Z zhongyw $ 
  12.  */  
  13.   
  14. /** 
  15.  * 错误代码 
  16.  */  
  17. define('WXAPI_ERR_CONFIG', 1001); // 配置错误  
  18. define('WXAPI_ERR_HTTP', 1002); // 请求失败  
  19. define('WXAPI_ERR_LOGIN', 1003); // 登录失败  
  20. define('WXAPI_ERR_FETCH_DATA', 1004); // 获取数据失败  
  21. define('WXAPI_ERR_MISS_RESPONSE', 1005); // 缺少响应  
  22. define('WXAPI_ERR_BAD_SIGNATURE', 1006); // 签名校验失败  
  23. define('WXAPI_ERR_BAD_DECRYPT', 1007); // 消息解密失败  
  24. define('WXAPI_ERR_BAD_ENCRYPT', 1008); // 消息加密失败  
  25. define('WXAPI_ERR_ACCESS_TOKEN', 1009); // access token凭证错误  
  26.   
  27. /** 
  28.  * 日志级别 
  29.  */  
  30. define('WXAPI_LOG_EMERG', 'EMERG');  // 严重错误: 导致系统崩溃无法使用  
  31. define('WXAPI_LOG_ALERT', 'ALERT');  // 警戒性错误: 必须被立即修改的错误  
  32. define('WXAPI_LOG_CRIT', 'CRIT');  // 临界值错误: 超过临界值的错误,例如一天24小时,而输入的是25小时这样  
  33. define('WXAPI_LOG_ERR', 'ERR');  // 一般错误: 一般性错误  
  34. define('WXAPI_LOG_WARN', 'WARN');  // 警告性错误: 需要发出警告的错误  
  35. define('WXAPI_LOG_NOTICE', 'NOTIC');  // 通知: 程序可以运行但是还不够完美的错误  
  36. define('WXAPI_LOG_INFO', 'INFO');  // 信息: 程序输出信息  
  37. define('WXAPI_LOG_DEBUG', 'DEBUG');  // 调试: 调试信息  
  38. define('WXAPI_LOG_EXCEPTION', 'EXCEPTION'); // 异常信息  
  39.   
  40. /** 
  41.  * 微信接口默认常量值 
  42.  */  
  43. define('WXAPI_ACCESS_TOKEN_EXPIRE', 7100); // access token有效时间,设置比微信默认有效时间7200秒小,避免出现过期错误  
  44. define('WXAPI_ACCESS_TOKEN_LIMIT', 2000); // access token每日限制次数  
  45. define('WXAPI_JSAPI_TICKET_EXPIRE', 7100); // jsapi ticket有效时间,单位秒  
  46. define('WXAPI_JSAPI_TICKET_LIMIT', 2000); // jsapi ticket每日限制次数  
  47. define('WXAPI_QRCODE_MIN_SCENE', 1); // 二维码场景值最小值  
  48. define('WXAPI_QRCODE_MAX_SCENE', 2147483647); // 二维码场景值最大值, 32位非0整型  
  49. define('WXAPI_QRCODE_MAX_LIMIT_SCENE', 100000); // 永久二维码场景值最大值  
  50. define('WXAPI_QRCODE_EXPIRE', 1800); // 临时二维码有效时间,单位秒  
  51. define('WXAPI_GROUP_MIN_CUSTOM_ID', 100); // 用户自定义用户组起始id值  
  52.   
  53. /** 
  54.  * 微信暗语 
  55.  */  
  56. define('WXAPI_ARGOT_WHO_AM_I', 'show me your name'); // 显示当前4susername  
  57. define('WXAPI_ARGOT_DESTORY_SESSION', 'let me out'); // 清除当前用户session  
  58.   
  59. class WeixinApi{  
  60.     /** 
  61.      * 实例化对象 
  62.      *  
  63.      * @var array 
  64.      */  
  65.     protected static $_instance = array();  
  66.       
  67.   
  68.     /** 
  69.      * 是否启用缓存 
  70.      * @var bool 
  71.      */  
  72.     protected $_cache = false;  
  73.       
  74.     /** 
  75.      * 是否启用调试 
  76.      * @var bool 
  77.      */  
  78.     protected $_debug = false;  
  79.       
  80.     /** 
  81.      * 配置对象实例 
  82.      * @var object 
  83.      */  
  84.     public $Config;  
  85.       
  86.     /** 
  87.      * 错误信息 
  88.      * @var string 
  89.      */  
  90.     protected $_error = NULL;  
  91.       
  92.     public function __construct($Config=NULL){  
  93.         $this->Config = is_object($Config)?$Config:self::instance('WeixinApi_Config');  
  94.         $this->_cache = $this->Config->Cache;  
  95.     }  
  96.       
  97.     /** 
  98.      * 取得对象实例 支持调用类的静态方法 
  99.      * @param string $class 对象类名 
  100.      * @param string $method 类的静态方法名 
  101.      * @return object 
  102.      */  
  103.     public static function instance($class,$args=array()) {  
  104.         $identify   =   $class.md5(serialize($args));  
  105.         if(!isset(WeixinApi::$_instance[$identify])) {  
  106.             if(!class_exists($class)){  
  107.                 require $class . ".class.php";  
  108.             }  
  109.       
  110.             if(class_exists($class)){  
  111.                 $arg_str = '';  
  112.                 if($args && is_array($args)){  
  113.                     foreach ($args as $i=>$arg){  
  114.                         /* 
  115.                         if(is_object($arg) || is_array($arg)){ 
  116.                             return WeixinApi::throw_exception( 
  117.                                     "Cann't init class $class instanse with object argument" 
  118.                                     , WXAPI_ERR_CONFIG 
  119.                                     , array('class' => $class, 'args' => $args) 
  120.                                     , __FILE__, __LINE); 
  121.                         }else{ 
  122.                             $arg_str = "'" . implode("', '", array_map('addslashes', $args)) . "'"; 
  123.                         }*/  
  124.                           
  125.                         if(is_object($arg) || is_array($arg)){  
  126.                             $arg_param_name = 'arg_param' . $i;  
  127.                             $$arg_param_name = $arg;  
  128.                             $arg_str .= ", \${$arg_param_name}";  
  129.                         }else{  
  130.                             $arg_str .= ", '" . addcslashes($arg, "'") . "'";  
  131.                         }  
  132.                     }  
  133.                       
  134.                     if($arg_str){  
  135.                         $arg_str = substr($arg_str, 2);  
  136.                     }  
  137.                       
  138.                 }elseif($args && is_object($args)){  
  139.                     /* 
  140.                     return WeixinApi::throw_exception( 
  141.                             "Cann't init class $class instanse with object argument" 
  142.                             , WXAPI_ERR_CONFIG 
  143.                             , array('class' => $class, 'args' => $args) 
  144.                             , __FILE__, __LINE); 
  145.                     */  
  146.                     $arg_param_name = 'arg_param';  
  147.                     $$arg_param_name = $args;  
  148.                     $arg_str = "\${$arg_param_name}";  
  149.                       
  150.                 }elseif($args){  
  151.                     $arg_str = "'" . addcslashes($args, "'") . "'";  
  152.                 }  
  153.                   
  154.                 $code = "return new " . $class . "(" . $arg_str . ");";  
  155.                 $o = eval($code);  
  156.                   
  157.                 if(!$o){  
  158.                     return WeixinApi::throw_exception(  
  159.                              "Cann't init class instanse: $class"  
  160.                             , WXAPI_ERR_CONFIG  
  161.                             , array('class' => $class, 'args' => $args)  
  162.                             , __FILE__, __LINE);  
  163.                 }  
  164.                 WeixinApi::$_instance[$identify] = $o;  
  165.             }  
  166.             else{  
  167.                 return WeixinApi::throw_exception(  
  168.                          "Cann't found class: $class file."  
  169.                         , WXAPI_ERR_CONFIG  
  170.                         , array('class' => $class, 'args' => $args)  
  171.                         , __FILE__, __LINE__);  
  172.             }  
  173.         }  
  174.         return self::$_instance[$identify];  
  175.     }  
  176.       
  177.     public static function throw_exception($message, $code=NULL, $data=NULL, $file=NULL, $line=NULL){  
  178.         if(!class_exists('WeixinApi_Exception')){  
  179.             require 'WeixinApi_Exception.class.php';  
  180.         }  
  181.   
  182.         // 只有配置错误才再次抛出异常  
  183.         //if($code==WXAPI_ERR_CONFIG){  
  184.             throw new WeixinApi_Exception($message, $code, $data, $file, $line);  
  185.         //}else{  
  186.         //  return false;  
  187.         //}  
  188.     }  
  189.       
  190.     protected function _throw_exception($message, $code=NULL, $data=NULL, $file=NULL, $line=NULL){  
  191.         try{  
  192.             WeixinApi::throw_exception($message, $code, $data, $file, $line);  
  193.         }catch(Exception $e){  
  194.             //$this->_error = $e->getMessage();  
  195.             $this->_setError($e->getMessage());  
  196.             $this->_log($e->__toString(), WXAPI_LOG_ERR);  
  197.               
  198.             // 只有配置错误才再次抛出异常  
  199.             if($code==WXAPI_ERR_CONFIG){  
  200.                 throw $e;  
  201.             }else{  
  202.                 return false;  
  203.             }  
  204.         }  
  205.     }  
  206.   
  207.     public function getError(){  
  208.         return is_array($this->_error)?implode(',', $this->_error):$this->_error;  
  209.     }  
  210.       
  211.     /** 
  212.      * 设置错误信息 
  213.      * @param string $error 
  214.      */  
  215.     protected function _setError($error){  
  216.         $this->_error[] = $error;  
  217.     }  
  218.       
  219.     public function __get($n){  
  220.         if(isset($this->$n)){  
  221.             return $this->$n;  
  222.         }else if(in_array($n, array('Http', 'Cache', 'Log'))){  
  223.             if('Http'==$n && !$this->Config->$n){  
  224.                 return $this->_throw_exception("$n is not setted in your config"  
  225.                         , WXAPI_ERR_CONFIG  
  226.                         , array('class'=>$n)  
  227.                         , __FILE__, __LINE__  
  228.                 );  
  229.             }elseif(!$this->Config->$n){  
  230.                 // Do Nothing  
  231.                 // Disabled Cache or Log  
  232.                 return false;  
  233.             }  
  234.               
  235.             if(is_object($this->Config->$n)){  
  236.                 return $this->Config->$n;  
  237.             }elseif(is_array($this->Config->$n)){  
  238.                 list($callback, $params) = $this->Config->$n;  
  239.                 if(!is_array($params)){  
  240.                     $params = array($params);  
  241.                 }  
  242.                 return call_user_func_array($callback, $params);  
  243.             }else{  
  244.                 return $this->$n = WeixinApi::instance($this->Config->$n);  
  245.             }  
  246.         }else{  
  247.             return false;  
  248.         }  
  249.     }  
  250.       
  251.     protected function _check_http_url($url){  
  252.         if(strcasecmp('http', substr($url, 0, 4))){  
  253.             $url = $this->Config->ApiGateway . $url;  
  254.         }  
  255.           
  256.         return $url;  
  257.     }  
  258.       
  259.     protected function _check_http_ssl($url){  
  260.         if(!strcasecmp('https://', substr($url, 0, 8))){  
  261.             $this->Http->setSsl();  
  262.               
  263.             // 指定ssl v3  
  264.             // 2014.09.05 zhongyw 微信API不能指定用ssl v3版本  
  265.             //$this->Http->setOpt(CURLOPT_SSLVERSION, 3);  
  266.               
  267.             // 指定TLS  
  268.             // 2014.10.31 zhongyw   
  269.             // 微信公众平台将关闭掉SSLv2、SSLv3版本支持,不再支持部分使用SSLv2、 SSLv3或更低版本的客户端调用。请仍在使用这些版本的开发者于11月30日前尽快修复升级。  
  270.             defined('CURL_SSLVERSION_TLSv1') || define('CURL_SSLVERSION_TLSv1', 1); // 兼容PHP<=5.3  
  271.             $this->Http->setOpt(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);  
  272.         }  
  273.           
  274.         return $url;  
  275.     }  
  276.       
  277.     protected function _check_http_data($data){  
  278.         return $data;  
  279.     }  
  280.       
  281.     /** 
  282.      * 发送GET请求 
  283.      *  
  284.      * @param string $url   链接 
  285.      * @param string|array $data    参数 
  286.      * @param bool $check   是否检查链接和参数 
  287.      * @return string 
  288.      */  
  289.     public function get($url, $data = null, $check=true) {  
  290.         if ($check) {  
  291.             $url = $this->_check_http_url ( $url );  
  292.             $url = $this->_check_http_ssl ( $url );  
  293.             $data = $this->_check_http_data ( $data );  
  294.         }  
  295.           
  296.         if(!($return = $this->Http->get($url, $data)) && ($error=$this->Http->getError())){  
  297.             return $this->_throw_exception(  
  298.                       $error  
  299.                     , WXAPI_ERR_HTTP  
  300.                     , array('url' => $url, 'data' => $data, 'method' => 'get', 'response' => $return)   
  301.                     , __FILE__, __LINE__);  
  302.         }  
  303.           
  304.   
  305.         return $return;  
  306.     }  
  307.       
  308.     /** 
  309.      * 发送POST请求 
  310.      * 
  311.      * @param string $url   链接 
  312.      * @param array $data   参数 
  313.      * @param bool $check   是否检查链接和参数 
  314.      * @return string 
  315.      */  
  316.     public function post($url, $data, $check=true) {  
  317.         if ($check) {  
  318.             $url = $this->_check_http_url ( $url );  
  319.             $url = $this->_check_http_ssl ( $url );  
  320.             $data = $this->_check_http_data ( $data );  
  321.         }  
  322.           
  323.         // 使用plainPost  
  324.         if(!($return = $this->Http->plainPost($url, $data)) && ($error=$this->Http->getError())){  
  325.             return $this->_throw_exception(  
  326.                       $error  
  327.                     , WXAPI_ERR_HTTP  
  328.                     , array('url' => $url, 'data' => $data, 'method' => 'post', 'response' => $return)   
  329.                     , __FILE__, __LINE__);  
  330.         }  
  331.       
  332.         return $return;  
  333.     }  
  334.       
  335.     public function setHttpOption($opt, $val=NULL){  
  336.         if(!$opt){  
  337.             return false;  
  338.         }  
  339.       
  340.         $options = array();  
  341.         if(!is_array($opt)){  
  342.             $options = array($opt=>$val);  
  343.         }else{  
  344.             $options = $opt;  
  345.         }  
  346.           
  347.         foreach($options as $opt=>$val){  
  348.             $this->Http->setOpt(constant($opt), $val);  
  349.         }  
  350.     }  
  351.       
  352.     /** 
  353.      * 运行回调函数 
  354.      *  
  355.      * 回调函数支持以下几种格式: 
  356.      * 1、直接函数:funcname,或带参数:array(funcname, params) 
  357.      * 2、静态方法:array(array('WeixinApi', 'methodname'), params) 
  358.      * 3、对象方法:array(Object, 'methodname') 或  array(array(Object, 'methodname'), params) 
  359.      * 4、二次回调,如: 
  360.      * array(array( 
  361.               array(array('WeixinApi', 'instance'), 'S4WeixinResponse') 
  362.                     , 'run') 
  363.             , '') 
  364.              
  365.             可以先调用Runder::instance()初始化S4Web实例后,再调用S4Web->apiglog_save()方法执行回调 
  366.      *  
  367.      * @param mixed $callback 回调函数 
  368.      * @param array $extra_params 回调参数 
  369.      * @return mixed 
  370.      */  
  371.     protected function _run_callback($callback, $extra_params=array(), &$callbackObject=NULL) {  
  372.         $extra_params = is_array ( $extra_params ) ? $extra_params : ($extra_params ? array (  
  373.                 $extra_params   
  374.         ) : array ());  
  375.           
  376.         $params = $extra_params;  
  377.           
  378.         if(is_object($callback)){  
  379.             return $this->_throw_exception(  
  380.                     "Object callback must set method"  
  381.                     , SCRIPT_ERR_CONFIG  
  382.                     , array('callback'=>$callback)  
  383.                     , __FILE__, __LINE__  
  384.             );  
  385.         }  
  386.         else if (is_array ( $callback )) {  
  387.             $func = $callback [0];  
  388.             if (! empty ( $callback [1] )) {  
  389.                 if (is_array ( $callback [1] )) {  
  390.                     $params = array_merge ( $extra_params, $callback [1] );  
  391.                 } else {  
  392.                     $params [] = $callback [1];  
  393.                 }  
  394.             }  
  395.               
  396.             if (is_object ( $func )) {  
  397.                 $callbackObject = $func;  
  398.                 // 注意:此处不需要传$params作为参数  
  399.                 return call_user_method_array ( $callback [1], $callback [0], $extra_params );  
  400.             } elseif (is_object ( $callback [0] [0] )) {  
  401.                 $callbackObject = $callback [0] [0];  
  402.                 return call_user_method_array ( $callback [0] [1], $callback [0] [0], $params);  
  403.             }  
  404.         } else {  
  405.             $func = $callback;  
  406.         }  
  407.           
  408.         if(is_array($func) && is_array($func[0])){  
  409.             $call = call_user_func_array($func[0][0], is_array($func[0][1])?$func[0][1]:array($func[0][1]));  
  410.             if($call===false){  
  411.                 return false;  
  412.             }  
  413.               
  414.             $func = array($call, $func[1]);  
  415.         }  
  416.           
  417.         if(is_array($func) && is_object($func[0])){  
  418.             $callbackObject = $func[0];  
  419.         }  
  420.           
  421.         return call_user_func_array ( $func, $params);  
  422.     }  
  423.       
  424.     /** 
  425.      * 是否缓存 
  426.      * @param bool|int $cache true = 启用缓存,false = 不缓存,-1 = 重新生成缓存,3600 = 设置缓存时间为3600秒 
  427.      * @return WeixinClient 
  428.      */  
  429.     public function cache($cache=true){  
  430.         $this->_cache = $cache;  
  431.         return $this;  
  432.     }  
  433.       
  434.     public function debug($debug=true){  
  435.         $this->_debug = $debug;  
  436.         return $this;  
  437.     }  
  438.       
  439.     /** 
  440.      * 写入或者获取缓存 
  441.      *  
  442.      * @param string $cache_id 缓存id 
  443.      * @param string $cache_data 缓存数据 
  444.      * @param int $cache_expire 缓存时间 
  445.      * @return mixed|boolean 
  446.      */  
  447.     protected function _cache($cache_id, $cache_data=NULL, $cache_expire=NULL){  
  448.         if($this->Config->Cache){  
  449.             // 保存缓存索引  
  450.             if($cache_id && (!is_null($cache_data) && $cache_data!==false && $cache_expire!==false)  
  451.             && $this->Config->CacheSaveIndex  
  452.             && strcasecmp($cache_id, $this->Config->CacheSaveIndex)  
  453.             ){  
  454.                 $index_cache_id = $this->Config->CacheSaveIndex;  
  455.                 $index_cache_expire = 315360000; // 永久保存: 3600*24*365*10  
  456.                       
  457.                 // 取已有的缓存  
  458.                 if(!($index_cache_data=$this->_cache($index_cache_id))){  
  459.                     $index_cache_data = array();  
  460.                 }  
  461.                   
  462.                 // 删除已过期索引  
  463.                 $now_time = time();  
  464.                 foreach($index_cache_data as $k=>$d){  
  465.                     if($d && $d['expire'] && $d['created'] && ($d['created']+$d['expire'])<$now_time){  
  466.                         unset($index_cache_data[$k]);  
  467.                     }  
  468.                 }  
  469.                       
  470.                 $index_cache_data[$cache_id] = array(  
  471.                         'created' => $now_time,  
  472.                         'expire' => $cache_expire,  
  473.                 );  
  474.                       
  475.                 //S4Web::debug_log("\$index_cache_id=$index_cache_id");  
  476.                 //S4Web::debug_log("\$index_cache_data=" . print_r($index_cache_data, true));  
  477.   
  478.                 $succ = $this->_cache($index_cache_id, $index_cache_data, $index_cache_expire);  
  479.                 $this->_log("Save cache id:  " . $cache_id . ' ' . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_DEBUG);  
  480.             }  
  481.   
  482.             return $this->_run_callback($this->Config->Cache, array($cache_id, $cache_data, $cache_expire));  
  483.         }else{  
  484.             return false;  
  485.         }  
  486.     }  
  487.       
  488.     protected function _cache_id($url, $data = NULL, $cache = NULL) {  
  489.         if ($cache && $cache!==true && !is_numeric($cache)){  
  490.             if(is_string ( $cache )) {  
  491.                 $cache_id = $cache;  
  492.             } elseif (is_array ( $cache ) && isset($cache['cache_id'])) {  
  493.                 $cache_id = $cache ['cache_id'];  
  494.             } elseif (is_object ( $cache ) && isset($cache['cache_id'])) {  
  495.                 $cache_id = $cache->cache_id;  
  496.             }  
  497.               
  498.             // 添加缓存前缀  
  499.             /* 
  500.              注:由ThinkPHP处理缓存添加前缀:C('DATA_CACHE_PREFIX') 
  501.             if($cache_id && $this->Config->CacheBin){ 
  502.                 $cache_id = $this->Config->CacheBin . $cache_id; 
  503.             }*/  
  504.         }  
  505.       
  506.         if (!$cache_id) {  
  507.             $param = '';  
  508.             if ($data && is_array ( $data )) {  
  509.                 $param .= http_build_query ( $data );  
  510.             } else {  
  511.                 $param .= $data;  
  512.             }  
  513.             $cache_id = md5 ( $this->Config->AppId . $url . $param );  
  514.             //return $cache_id;  
  515.         }  
  516.   
  517.         return $cache_id;  
  518.     }  
  519.       
  520.     protected function _cache_expire($url, $data=NULL, $cache=NULL){  
  521.         if(!$cache){  
  522.             return 0;  
  523.         }elseif(is_numeric($cache) && $cache>0){  
  524.             $cache_expire = $cache;  
  525.         }elseif (is_array($cache) && isset($cache['cache_expire'])){  
  526.             $cache_expire = $cache['cache_expire'];  
  527.         }elseif (is_object($cache) && isset($cache->cache_expire)){  
  528.             $cache_expire = $cache->cache_expire;  
  529.         }  
  530.       
  531.         return $cache_expire?$cache_expire:$this->Config->CacheExpire;  
  532.     }  
  533.       
  534.     /** 
  535.      * 判断是否强制刷新缓存 
  536.      * @param unknown $url 
  537.      * @param string $data 
  538.      * @param string $cache 
  539.      * @return bool 
  540.      */  
  541.     protected function _cache_refresh($url, $data=NULL, $cache=NULL){  
  542.         $cache_refresh = false;  
  543.           
  544.         if ($cache && $cache!==true && !is_numeric($cache)){  
  545.             if (is_array ( $cache ) && isset($cache['cache_refresh'])) {  
  546.                 $cache_refresh = $cache ['cache_refresh'];  
  547.             } elseif (is_object ( $cache ) && isset($cache['cache_refresh'])) {  
  548.                 $cache_refresh = $cache->cache_refresh;  
  549.             }  
  550.         }  
  551.           
  552.         return $cache_refresh;  
  553.     }  
  554.       
  555.     /** 
  556.      * 写入日志 
  557.      * @param string $message 
  558.      * @param string $level 
  559.      * @return boolean 
  560.      */  
  561.     protected function _log($message, $level=WXAPI_LOG_INFO){  
  562.         if($this->Config->Log){  
  563.             static $aLogLevelMaps = array(  
  564.                     WXAPI_LOG_EMERG => 0,  
  565.                     WXAPI_LOG_ALERT => 1,  
  566.                     WXAPI_LOG_CRIT => 2,  
  567.                     WXAPI_LOG_ERR => 3,  
  568.                     WXAPI_LOG_WARN => 4,  
  569.                     WXAPI_LOG_NOTICE => 5,  
  570.                     WXAPI_LOG_INFO => 6,  
  571.                     WXAPI_LOG_DEBUG => 7,  
  572.             );  
  573.               
  574.             if($this->Config->LogLevel && $aLogLevelMaps[$level]>$aLogLevelMaps[$this->Config->LogLevel]){  
  575.                 return false;  
  576.             }  
  577.               
  578.             return $this->_run_callback($this->Config->Log, array($message, $level));  
  579.         }else{  
  580.             return false;  
  581.         }  
  582.     }  
  583.       
  584.       
  585.     /** 
  586.      * 写入支付日志 
  587.      * @param string $message 
  588.      * @param string $level 
  589.      * @return boolean 
  590.      */  
  591.     protected function _logpay($message, $level=WXAPI_LOG_INFO){  
  592.         if($this->Config->PayLog){  
  593.             static $aLogLevelMaps = array(  
  594.                     WXAPI_LOG_EMERG => 0,  
  595.                     WXAPI_LOG_ALERT => 1,  
  596.                     WXAPI_LOG_CRIT => 2,  
  597.                     WXAPI_LOG_ERR => 3,  
  598.                     WXAPI_LOG_WARN => 4,  
  599.                     WXAPI_LOG_NOTICE => 5,  
  600.                     WXAPI_LOG_INFO => 6,  
  601.                     WXAPI_LOG_DEBUG => 7,  
  602.             );  
  603.       
  604.             if($this->Config->PayLogLevel && $aLogLevelMaps[$level]>$aLogLevelMaps[$this->Config->PayLogLevel]){  
  605.                 return false;  
  606.             }  
  607.       
  608.             return $this->_run_callback($this->Config->PayLog, array($message, $level));  
  609.         }else{  
  610.             return false;  
  611.         }  
  612.     }  
  613.       
  614.     /** 
  615.      * 判断是否微信媒体文件id 
  616.      * @param string $mediaid 
  617.      * @return boolean 
  618.      */  
  619.     protected function _isMediaId($mediaid){  
  620.         // aSeyL8Ym_0mu3u1qeHixvCe54XU-b8teahDXHdYl1tOB_1mgyUxJgj0A8CJZRNzl  
  621.         //return is_file($mediaid)?false:true;  
  622.         if(preg_match('/\.[a-z0-9]{1,4}$/i', $mediaid)){  
  623.             return false;  
  624.         }else{  
  625.             return true;  
  626.         }  
  627.     }  
  628.       
  629.     /** 
  630.      * 清空微信API所有缓存数据 
  631.      *  
  632.      * @return bool 
  633.      */  
  634.     public function clearCache(){  
  635.   
  636.         $this->_log("START Clear Cache...", WXAPI_LOG_INFO);  
  637.         if(!$this->Config->Cache || !$this->Config->CacheSaveIndex){  
  638.             $this->_log("Skipped, Cache or Save Cache index is disabled!", WXAPI_LOG_INFO);  
  639.             return false;  
  640.         }  
  641.           
  642.         // 取缓存的索引  
  643.         $index_cache_id = $this->Config->CacheSaveIndex;  
  644.         if(!($index_cache_data=$this->_cache($index_cache_id))){  
  645.             $this->_log("Skipped, Cache Index is Empty!", WXAPI_LOG_INFO);  
  646.             return false;  
  647.         }  
  648.           
  649.         $clear_succ = true;  
  650.           
  651.         foreach($index_cache_data as $cache_id=>$d){  
  652.             $succ = $this->_cache($cache_id, false, false);  
  653.             $this->_log("Delete cache id: " . $cache_id . " " . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_DEBUG);  
  654.               
  655.             $clear_succ = $succ && $clear_succ;  
  656.         }  
  657.           
  658.         // 删除索引自身  
  659.         $succ = $this->_cache($index_cache_id, false, false);  
  660.         $clear_succ = $succ && $clear_succ;  
  661.           
  662.         $this->_log("Delete Index Cache Id: " . $index_cache_id . " " . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_INFO);  
  663.           
  664.         $this->_log("END Clear Cache, "  . ($clear_succ?'Succ':'Failed') . '!', WXAPI_LOG_INFO);  
  665.           
  666.         return $clear_succ;  
  667.     }  
  668. }  

WeixinReceive.class.php  微信接口接收类
 
[php]  view plain  copy
  1. <?php  
  2. /** 
  3.  * 微信API 接收接口 
  4.  *  
  5.  * PHP version 5 
  6.  *  
  7.  * @category    Lib 
  8.  * @package     COM 
  9.  * @subpackage  GZNC 
  10.  * @author      zhongyiwen 
  11.  * @version     SVN: $Id: WeixinReceive.class.php 10 2013-10-08 01:34:05Z zhongyw $ 
  12.  */  
  13.   
  14. class WeixinReceive extends WeixinApi{  
  15.     protected $_rawget = NULL;  
  16.     protected $_rawpost = NULL;  
  17.       
  18.     protected $_postData = NULL;  
  19.     protected $_getData = NULL;  
  20.       
  21.     protected $_postObj = NULL; // 兼容旧程序  
  22.     protected $_getObj = NULL; // 兼容旧程序  
  23.       
  24.     protected $_responseMsg;  
  25.     protected $_responseObj;  
  26.       
  27.     /** 
  28.      * 消息体加密模式 
  29.      * @var string 
  30.      */  
  31.     protected $_msgEncodingMode = NULL;  
  32.       
  33.     /** 
  34.      * 消息加密私钥 
  35.      * @var string 
  36.      */  
  37.     protected $_msgEncodingKey = NULL;  
  38.       
  39.     /** 
  40.      * 原始加密消息 
  41.      * @var string 
  42.      */  
  43.     protected $_msgEncrypt = NULL;  
  44.       
  45.     /** 
  46.      * 解密后的消息原文 
  47.      * @var string 
  48.      */  
  49.     protected $_msgDecrypt = NULL;  
  50.       
  51.     /** 
  52.      * 解密后的消息数组 
  53.      * @var array 
  54.      */  
  55.     protected $_msgData = NULL;  
  56.       
  57.       
  58.       
  59.     /** 
  60.      * 检查消息签名 
  61.      * @param object $getObj 
  62.      * @return boolean 成功返回true,失败返回false 
  63.      */  
  64.     protected function _checkSignature($getData)  
  65.     {  
  66.         $signature = $getData['signature'];  
  67.         $timestamp = $getData['timestamp'];  
  68.         $nonce = $getData['nonce'];  
  69.       
  70.         $token = $this->Config->AppToken;  
  71.         $tmpArr = array($token, $timestamp, $nonce);  
  72.         sort($tmpArr, SORT_STRING);  
  73.         $tmpStr = implode( $tmpArr );  
  74.         $tmpStr = sha1( $tmpStr );  
  75.       
  76.         if( $tmpStr == $signature ){  
  77.             return true;  
  78.         }else{  
  79.             return false;  
  80.         }  
  81.     }  
  82.       
  83.     /** 
  84.      * 判断消息加密模式 
  85.      * @param object $getObj 
  86.      * @param object $postObj 
  87.      * @return string|false 
  88.      */  
  89.     protected function _checkEncodingMode($getData, $postData){  
  90.         if(!is_null($this->_msgEncodingMode)){  
  91.             return $this->_msgEncodingMode;  
  92.         }  
  93.           
  94.         if(empty($getData['encrypt_type']) || !strcasecmp($getData['encrypt_type'], 'raw')){  
  95.             $this->_msgEncodingMode = WXAPI_APP_ENCODING_CLEAR;  
  96.         }elseif(strlen($getData['msg_signature']) && !strcasecmp($getData['encrypt_type'], 'aes')){  
  97.             if(!empty($postData['MsgType']) && !empty($postData['FromUserName'])){  
  98.                 $this->_msgEncodingMode =  WXAPI_APP_ENCODING_COMPAT;  
  99.             }else{  
  100.                 $this->_msgEncodingMode =  WXAPI_APP_ENCODING_SECURE;  
  101.             }  
  102.         }else{  
  103.             $this->_msgEncodingMode = false;  
  104.         }  
  105.           
  106.         return $this->_msgEncodingMode;  
  107.     }  
  108.       
  109.     protected function _postData(){  
  110.         if(!is_null($this->_postData)){  
  111.             return $this->_postData;  
  112.         }  
  113.           
  114.         $this->_rawpost = file_get_contents("php://input");  
  115.           
  116.         if(!empty($this->_rawpost)){  
  117.             $postObj = simplexml_load_string(trim($this->_rawpost), 'SimpleXMLElement', LIBXML_NOCDATA);  
  118.               
  119.             $this->_postData = WeixinApi_Kit::get_object_vars_final($postObj);  
  120.               
  121.             // 2015.3.3 zhongyw 必须从postData转为object  
  122.             // simplexml_load_string()返回的为SimpleXMLElement Object,而不是stdClass Object,   
  123.             // 用is_string($postObj->FromUserName)判断时会返回false  
  124.             $this->_postObj = (object) $this->_postData; // 兼容旧程序  
  125.         }else{  
  126.             $this->_postData = false;  
  127.             $this->_postObj = false;  
  128.         }  
  129.           
  130.         return $this->_postData;  
  131.           
  132.     }  
  133.       
  134.     protected function _getData(){  
  135.         if(!is_null($this->_getData)){  
  136.             return $this->_getData;  
  137.         }  
  138.           
  139.         $this->_rawget = $_GET;  
  140.         if ($this->_rawget) {  
  141.             $getData = array (  
  142.                     'signature' => $_GET ["signature"],  
  143.                     'timestamp' => $_GET ["timestamp"],  
  144.                     'nonce' => $_GET ["nonce"]   
  145.             );  
  146.               
  147.             if (isset ( $_GET ['echostr'] )) { $getData ['echostr'] = $_GET ['echostr']; }  
  148.             if (isset ( $_GET ['encrypt_type'] )) { $getData ['encrypt_type'] = $_GET ['encrypt_type']; }  
  149.             if (isset ( $_GET ['msg_signature'] )) { $getData ['msg_signature'] = $_GET ['msg_signature']; }  
  150.               
  151.             $this->_getData = $getData;  
  152.               
  153.             // 兼容旧程序  
  154.             $this->_getObj = ( object ) $getData;  
  155.         }else{  
  156.             $this->_getData = false;  
  157.               
  158.             $this->_getObj = false;  
  159.         }  
  160.           
  161.         return $this->_getData;  
  162.     }  
  163.       
  164.     /** 
  165.      * 运行接收 
  166.      * @param mixed $responseObj 响应对象,可以传回调函数 
  167.      */  
  168.     public function run($responseObj=NULL){  
  169.         $request_url = WeixinApi_Kit::get_request_url();  
  170.         $client_ip = WeixinApi_Kit::get_client_ip();  
  171.           
  172.         $this->_log("--------------------------------------------------------");  
  173.         $this->_log("Received new request from {$client_ip}", WXAPI_LOG_INFO);  
  174.         $this->_log("Request URL: {$request_url}", WXAPI_LOG_INFO);  
  175.           
  176.         $this->_log("Get: " . print_r($_GET, true), WXAPI_LOG_DEBUG);  
  177.         $this->_log("Post: " . print_r($_POST, true), WXAPI_LOG_DEBUG);  
  178.           
  179.         $getData = $this->_getData();  
  180.           
  181.         // 验证签名  
  182.         if(!$getData || !$this->_checkSignature($getData)){  
  183.             // invalid request  
  184.             // log it? or do other things  
  185.             $this->_log("Bad Request, Check Signature Failed!", WXAPI_LOG_ERR);  
  186.             return false;  
  187.         }  
  188.           
  189.         $postData = $this->_postData();  
  190.           
  191.         // 消息体是否为空?  
  192.         if(false==$postData){  
  193.             $this->_log("Msg Body is Empty!", WXAPI_LOG_ERR);  
  194.             return false;  
  195.         }  
  196.           
  197.         $this->_log ( "rawPost: " . $this->_rawpost, WXAPI_LOG_DEBUG );  
  198.         $this->_log ( "postData: " . print_r ( $postData, true ), WXAPI_LOG_DEBUG );  
  199.           
  200.         // 判断消息加密模式  
  201.         $encodingMode = $this->_checkEncodingMode($getData, $postData);  
  202.         if(false==$encodingMode){  
  203.             $this->_log("Check Msg Encoding Mode Failed!", WXAPI_LOG_ERR);  
  204.             return false;  
  205.         }  
  206.           
  207.         $this->_log("MSG Encoding Mode is: " . $encodingMode, WXAPI_LOG_DEBUG);  
  208.           
  209.         // 解密消息  
  210.         switch($encodingMode){  
  211.             case WXAPI_APP_ENCODING_SECURE:  
  212.                 if(false===$this->_decodeMessage()){  
  213.                     $this->_log("Bad Request, Decode Message Failed!", WXAPI_LOG_ERR);  
  214.                     return false;  
  215.                 }else{  
  216.                     $this->_log("Decode Message Succ!", WXAPI_LOG_INFO);  
  217.                 }  
  218.                 break;  
  219.                   
  220.             case WXAPI_APP_ENCODING_COMPAT:  
  221.                 if(false===$this->_decodeMessage()){  
  222.                     $this->_log("Decode Message Failed!", WXAPI_LOG_ERR);  
  223.                 }else{  
  224.                     $this->_log("Decode Message Succ!", WXAPI_LOG_INFO);  
  225.                 }  
  226.                   
  227.                 break;  
  228.                   
  229.             default:  
  230.                 // DO NOTHING  
  231.                 break;  
  232.         }  
  233.   
  234.         if (empty ( $responseObj )) {  
  235.             $responseObj = $this->Config->Response;  
  236.         }  
  237.           
  238.         // get response  
  239.         $response = $this->_responseMsg = $this->_response ( $responseObj );  
  240.           
  241.         if ($response === false) {  
  242.             $this->_log ( "No Reponse Sent!", WXAPI_LOG_INFO );  
  243.               
  244.             // save message  
  245.             $this->_saveMessage ();  
  246.               
  247.             return false;  
  248.         }  
  249.           
  250.         // echo response  
  251.         echo $response;  
  252.         flush ();  
  253.           
  254.         // log  
  255.         $this->_log ( "Succ! Send Response: " . $response, WXAPI_LOG_INFO );  
  256.           
  257.         // save message  
  258.         $this->_saveMessage ();  
  259.           
  260.         // save response  
  261.         $this->_saveResponse ( $this->_responseObj );  
  262.           
  263.         return true;  
  264.     }  
  265.       
  266.     protected function _response($responseObj){  
  267.         if(is_object($responseObj)){  
  268.             $callback = array($responseObj, 'run');  
  269.         }else{  
  270.             $callback = $responseObj;  
  271.         }  
  272.           
  273.         return $this->_run_callback($callback, array($this), $this->_responseObj);  
  274.     }  
  275.       
  276.     /** 
  277.      * 保存消息 
  278.      * @return mixed|boolean 
  279.      */  
  280.     protected function _saveMessage(){  
  281.         if($this->Config->SaveMessage){  
  282.             return $this->_run_callback($this->Config->SaveMessage, array($this));  
  283.         }else{  
  284.             return false;  
  285.         }  
  286.     }  
  287.       
  288.     /** 
  289.      * 保存回复 
  290.      * @param mixed $responseObj 
  291.      * @return mixed|boolean 
  292.      */  
  293.     protected function _saveResponse($responseObj){  
  294.         if($this->Config->SaveResponse){  
  295.             return $this->_run_callback($this->Config->SaveResponse, array($this, $responseObj));  
  296.         }else{  
  297.             return false;  
  298.         }  
  299.     }  
  300.       
  301.     public function __get($c){  
  302.         if(substr($c, 0, 1)!='_'){  
  303.             if(in_array($c, array('Http'))){  
  304.                 return parent::__get($c);  
  305.             }else{  
  306.                 $n = '_' . $c;  
  307.                 return $this->$n;  
  308.             }  
  309.         }  
  310.     }  
  311.       
  312.     public function __isset($c){  
  313.         if(substr($c, 0, 1)!='_'){  
  314.             if(in_array($c, array('Http'))){  
  315.                 return parent::__isset($c);  
  316.             }else{  
  317.                 $n = '_' . $c;  
  318.                 return isset($this->$n);  
  319.             }  
  320.         }  
  321.     }  
  322.       
  323.     /** 
  324.      * 获取openid 
  325.      * @return string 
  326.      */  
  327.     public function parse_openid(){  
  328.         if(isset($this->_postObj->FromUserName) && !empty($this->_postObj->FromUserName)){  
  329.             return $this->_postObj->FromUserName;  
  330.         }else{  
  331.             return null;  
  332.         }  
  333.     }  
  334.       
  335.     /** 
  336.      * 对密文消息进行解密 
  337.      * @param string $msg_encrypt 需要解密的密文 
  338.      * @param string $encodingkey 加密私钥 
  339.      * @return string|false 解密得到的明文,失败返回flase 
  340.      */  
  341.     protected function _decryptMsg($msg_encrypt, $encodingkey=NULL)  
  342.     {  
  343.         $AESKey = base64_decode(($encodingkey?$encodingkey:$this->Config->AppEncodingAESKey) . "=");  
  344.       
  345.         //使用BASE64对需要解密的字符串进行解码  
  346.         $ciphertext_dec = base64_decode($msg_encrypt);  
  347.       
  348.         $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');  
  349.         if(false===$module){  
  350.             return $this->_throw_exception(  
  351.                     "Cann't open an encryption descriptor"  
  352.                     , WXAPI_ERR_BAD_ENCRYPT  
  353.                     , $msg_encrypt  
  354.                     , __FILE__, __LINE__  
  355.             );  
  356.         }  
  357.       
  358.         $iv = substr($AESKey, 0, 16);  
  359.         $init = mcrypt_generic_init($module, $AESKey, $iv);  
  360.         if(false===$init){  
  361.             return $this->_throw_exception(  
  362.                     "Cann't initialize buffers for encryption"  
  363.                     , WXAPI_ERR_BAD_ENCRYPT  
  364.                     , array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init)  
  365.                     , __FILE__, __LINE__  
  366.             );  
  367.         }elseif(-3==$init){  
  368.             return $this->_throw_exception(  
  369.                     "the key length was incorrect"  
  370.                     , WXAPI_ERR_BAD_ENCRYPT  
  371.                     , array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init)  
  372.                     , __FILE__, __LINE__  
  373.             );  
  374.         }elseif(-4==$init){  
  375.             return $this->_throw_exception(  
  376.                     "there was a memory allocation problem"  
  377.                     , WXAPI_ERR_BAD_ENCRYPT  
  378.                     , array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init)  
  379.                     , __FILE__, __LINE__  
  380.             );  
  381.         }elseif($init<0){  
  382.             return $this->_throw_exception(  
  383.                     "an unknown error occurred when initialize buffers for encryption"  
  384.                     , WXAPI_ERR_BAD_ENCRYPT  
  385.                     , array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init)  
  386.                     , __FILE__, __LINE__  
  387.             );  
  388.         }  
  389.       
  390.         //解密  
  391.         $decrypted = mdecrypt_generic($module, $ciphertext_dec);  
  392.         mcrypt_generic_deinit($module);  
  393.         mcrypt_module_close($module);  
  394.           
  395.         if(!$decrypted){  
  396.             return "";  
  397.         }  
  398.               
  399.         // 去除补位字符  
  400.         $result = WeixinApi_Kit::pkcs7_decode( $decrypted, 32 );  
  401.         // 去除16位随机字符串,网络字节序和AppId  
  402.         if (strlen ( $result ) < 16){  
  403.             return "";  
  404.         }  
  405.           
  406.         $content = substr ( $result, 16, strlen ( $result ) );  
  407.         $len_list = unpack ( "N", substr ( $content, 0, 4 ) );  
  408.         $xml_len = $len_list [1];  
  409.         $xml_content = substr ( $content, 4, $xml_len );  
  410.       
  411.         return $xml_content;  
  412.     }  
  413.       
  414.     /** 
  415.      * 返回微信发过来的加密消息 
  416.      * @return string 
  417.      */  
  418.     protected function _getMsgEncrypt(){  
  419.         if($this->_msgEncrypt){  
  420.             return $this->_msgEncrypt;  
  421.         }  
  422.       
  423.         if(!empty($this->_getData['echostr'])){  
  424.             $this->_msgEncrypt = $this->_getData['echostr'];  
  425.         }else{  
  426.             $this->_msgEncrypt = $this->_postData['Encrypt'];  
  427.         }  
  428.       
  429.         return $this->_msgEncrypt;  
  430.     }  
  431.       
  432.     protected function _msgData() {  
  433.         if (! is_null ( $this->_msgData )) {  
  434.             return $this->_msgData;  
  435.         }  
  436.       
  437.         $this->_msgData = false;  
  438.       
  439.         $msg_encrypt = $this->_getMsgEncrypt ();  
  440.         if ($msg_encrypt) {  
  441.             if(!empty($this->_getData['echostr'])){  
  442.                 $this->_msgData = array();  
  443.             }else{  
  444.                 $xml_content = false;  
  445.                   
  446.                 $encodingkey = $this->Config->AppEncodingAESKey;  
  447.                 if($encodingkey){  
  448.                     $xml_content = $this->_decryptMsg ( $msg_encrypt, $encodingkey );  
  449.                     if($xml_content){  
  450.                         $this->_msgEncodingKey = $encodingkey;  
  451.                         $this->_log("AES Key: Decode Succ! ", WXAPI_LOG_DEBUG);  
  452.                     }else{  
  453.                         $this->_log("AES Key: Decode Failed!", WXAPI_LOG_DEBUG);  
  454.                     }  
  455.                 }else{  
  456.                     $this->_log("Encoding AES Key is empty", WXAPI_LOG_DEBUG);  
  457.                 }  
  458.                   
  459.                 // 尝试旧密钥  
  460.                 if(!$xml_content && ($encodingkey = $this->Config->AppEncodingOLDKey)){  
  461.                     $xml_content = $this->_decryptMsg ( $msg_encrypt, $encodingkey );  
  462.                       
  463.                     $this->_log("Try to apply OLD Key", WXAPI_LOG_DEBUG);  
  464.                       
  465.                     if($xml_content){  
  466.                         $this->_msgEncodingKey = $encodingkey;  
  467.                         $this->_log("OLD Key: Decode Succ! ", WXAPI_LOG_DEBUG);  
  468.                     }else{  
  469.                         $this->_log("OLD Key: Decode Failed!", WXAPI_LOG_DEBUG);  
  470.                     }  
  471.                 }  
  472.                   
  473.                 if($xml_content){  
  474.                     $this->_msgDecrypt = $xml_content;  
  475.                       
  476.                     import ( 'COM.GZNC.WeixinApi.WeixinApi_Kit' );  
  477.                     $postObj = simplexml_load_string ( $xml_content, 'SimpleXMLElement', LIBXML_NOCDATA );  
  478.                     $this->_msgData = WeixinApi_Kit::get_object_vars_final ( $postObj );  
  479.                       
  480.                     $this->_log('Decoded MSG XML: ' . $this->_msgDecrypt, WXAPI_LOG_DEBUG);  
  481.                     $this->_log('Decoded MSG DATA: ' . print_r($this->_msgData, true), WXAPI_LOG_DEBUG);  
  482.                 }  
  483.             }  
  484.         }  
  485.           
  486.         return $this->_msgData;  
  487.     }  
  488.       
  489.     protected function _decodeMessage(){  
  490.         if(false===$this->_msgData()){  
  491.             return false;  
  492.         }  
  493.       
  494.         // 兼容旧程序  
  495.         $this->_postData = array_merge($this->_postData, $this->_msgData);  
  496.         $this->_postObj = (object) $this->_postData;  
  497.       
  498.         return true;  
  499.     }  
  500.       
  501.       
  502. }  

WeixinResponse.class.php  微信接口响应类
 
[php]  view plain  copy
  1. <?php  
  2. /** 
  3.  * 微信API 响应接口 
  4.  *  
  5.  * PHP version 5 
  6.  *  
  7.  * @category    Lib 
  8.  * @package     COM 
  9.  * @subpackage  GZNC 
  10.  * @author      zhongyiwen 
  11.  * @version     SVN: $Id: WeixinResponse.class.php 10 2013-10-08 01:34:05Z zhongyw $ 
  12.  */  
  13.   
  14. class WeixinResponse extends WeixinApi{  
  15.     const AS_ECHO = 'ECHO'; // ECHO消息  
  16.     const AS_EMPTY = 'EMPTY'; // 空消息  
  17.     const AS_COMMAND = 'COMMAND'; // 指令消息  
  18.     const AS_SUBSCRIBE = 'SUBSCRIBE'; // 订阅消息  
  19.     const AS_UNSUBSCRIBE = 'UNSUBSCRIBE'; // 取消订阅消息  
  20.     const AS_SCAN = 'SCAN'; // 扫描消息  
  21.     const AS_CLICK = 'CLICK'; // 点击菜单拉取消息事件  
  22.     const AS_VIEW = 'VIEW'; // 点击菜单跳转链接事件  
  23.     const AS_SCANCODE_PUSH = 'SCANCODE_PUSH'; // 扫码推事件  
  24.     const AS_SCANCODE_WAITMSG = 'AS_SCANCODE_WAITMSG'; // 扫码推事件且弹出“消息接收中”提示框  
  25.     const AS_PIC_SYSPHOTO = 'pic_sysphoto'; // 弹出系统拍照发图  
  26.     const AS_PIC_PHOTO_OR_ALBUM = 'PIC_PHOTO_OR_ALBUM'; // 弹出拍照或者相册发图  
  27.     const AS_PIC_WEIXIN = 'pic_weixin'; // 弹出微信相册发图器  
  28.     const AS_LOCATION_SELECT = 'location_select'; // 弹出地理位置选择器  
  29.     const AS_LOCATION = 'LOCATION'; // 地理位置消息  
  30.     const AS_MESSAGE = 'MESSAGE'; // 普通消息  
  31.     const AS_MASSSENDJOBFINISH = 'MASSSENDJOBFINISH'; // 群发消息  
  32.     const AS_TEMPLATESENDJOBFINISH = 'TEMPLATESENDJOBFINISH'; // 模板消息  
  33.       
  34.     const AS_UNKNOWN = 'UNKNOWN'; // 未知消息  
  35.       
  36.     protected $_responseType; // 响应类型,对应上面的常量  
  37.     protected $_responseKey; // 响应值,如菜单点击,值为菜单Key  
  38.     protected $_responseContent; // 响应的原始数据  
  39.     protected $_responseMessage; // 响应输出消息数据(数组)  
  40.     protected $_responseMedia; // 响应输出的媒体文件  
  41.   
  42.     /** 
  43.      * 运行 
  44.      * @param object $receiveObj 接收对象 
  45.      */  
  46.     public function run($receiveObj){  
  47.         try{  
  48.             return $this->_dispatchResponse($receiveObj);  
  49.         }catch (Exception $e){  
  50.             return false;  
  51.         }  
  52.     }  
  53.       
  54.     /** 
  55.      * 分发响应 
  56.      * 判断响应类型,并返回相应的响应内容 
  57.      * @param object $receiveObj 接收对象 
  58.      */  
  59.     protected function _dispatchResponse($receiveObj){  
  60.         // 可以直接判定消息类别,不需要依赖外部配置数据  
  61.         if($this->_isEcho($receiveObj)){  
  62.             $this->_responseType = WeixinResponse::AS_ECHO;  
  63.             $this->_responseKey = '';  
  64.             $this->_responseContent = $this->_responseEcho($receiveObj);  
  65.             $msgFormat = 'raw';  
  66.         }elseif($this->_isEmpty($receiveObj)){  
  67.             $this->_responseType = WeixinResponse::AS_EMPTY;  
  68.             $this->_responseKey = '';  
  69.             $this->_responseContent = $this->_responseEmpty($receiveObj);  
  70.             $msgFormat = 'raw';  
  71.         }elseif(($key=$this->_isView($receiveObj))){  
  72.             $this->_responseType = WeixinResponse::AS_VIEW;  
  73.             $this->_responseKey = $key===true?'':$key;  
  74.             $this->_responseContent = $this->_responseView($receiveObj);  
  75.             $msgFormat = 'xml';  
  76.         }  
  77.           
  78.         // 事件,需要加载外部配置数据  
  79.         // 注意:要先判断订阅事件,避免缓存误判  
  80.         elseif(($key=$this->_isSubscribe($receiveObj))){  
  81.             $this->_responseType = WeixinResponse::AS_SUBSCRIBE;  
  82.             $this->_responseKey = $key===true?'':$key;  
  83.             $this->_responseContent = $this->_responseSubscribe($receiveObj);  
  84.             $msgFormat = 'xml';  
  85.         }elseif(($key=$this->_isUnsubscribe($receiveObj))){  
  86.             $this->_responseType = WeixinResponse::AS_UNSUBSCRIBE;  
  87.             $this->_responseKey = $key===true?'':$key;  
  88.             $this->_responseContent = $this->_responseUnsubscribe($receiveObj);  
  89.             $msgFormat = 'xml';  
  90.         }elseif(($key=$this->_isClick($receiveObj))){   
  91.             $this->_responseType = WeixinResponse::AS_CLICK;  
  92.             $this->_responseKey = $key===true?'':$key;  
  93.             $this->_responseContent = $this->_responseClick($receiveObj);  
  94.             $msgFormat = 'xml';  
  95.         }elseif(($key=$this->_isScanCodePush($receiveObj))){   
  96.             $this->_responseType = WeixinResponse::AS_SCANCODE_PUSH;  
  97.             $this->_responseKey = $key===true?'':$key;  
  98.             $this->_responseContent = $this->_responseScancodePush($receiveObj);  
  99.             $msgFormat = 'xml';  
  100.         }elseif(($key=$this->_isScanCodeWaitMsg($receiveObj))){   
  101.             $this->_responseType = WeixinResponse::AS_SCANCODE_WAITMSG;  
  102.             $this->_responseKey = $key===true?'':$key;  
  103.             $this->_responseContent = $this->_responseScanCodeWaitMsg($receiveObj);  
  104.             $msgFormat = 'xml';  
  105.         }elseif(($key=$this->_isPicSysPhoto($receiveObj))){   
  106.             $this->_responseType = WeixinResponse::AS_PIC_SYSPHOTO;  
  107.             $this->_responseKey = $key===true?'':$key;  
  108.             $this->_responseContent = $this->_responsePicSysPhoto($receiveObj);  
  109.             $msgFormat = 'xml';  
  110.         }elseif(($key=$this->_isPicPhotoOrAlbum($receiveObj))){   
  111.             $this->_responseType = WeixinResponse::AS_PIC_PHOTO_OR_ALBUM;  
  112.             $this->_responseKey = $key===true?'':$key;  
  113.             $this->_responseContent = $this->_responsePicPhotoOrAlbum($receiveObj);  
  114.             $msgFormat = 'xml';  
  115.         }elseif(($key=$this->_isPicWeixin($receiveObj))){   
  116.             $this->_responseType = WeixinResponse::AS_PIC_WEIXIN;  
  117.             $this->_responseKey = $key===true?'':$key;  
  118.             $this->_responseContent = $this->_responsePicWeixin($receiveObj);  
  119.             $msgFormat = 'xml';  
  120.         }elseif(($key=$this->_isLocationSelect($receiveObj))){   
  121.             $this->_responseType = WeixinResponse::AS_LOCATION_SELECT;  
  122.             $this->_responseKey = $key===true?'':$key;  
  123.             $this->_responseContent = $this->_responseLocationSelect($receiveObj);  
  124.             $msgFormat = 'xml';  
  125.         }elseif(($key=$this->_isScan($receiveObj))){  
  126.             $this->_responseType = WeixinResponse::AS_SCAN;  
  127.             $this->_responseKey = $key===true?'':$key;  
  128.             $this->_responseContent = $this->_responseScan($receiveObj);  
  129.             $msgFormat = 'xml';  
  130.         }elseif(($key=$this->_isLocation($receiveObj))){  
  131.             $this->_responseType = WeixinResponse::AS_LOCATION;  
  132.             $this->_responseKey = $key===true?'':$key;  
  133.             $this->_responseContent = $this->_responseLocation($receiveObj);  
  134.             $msgFormat = 'xml';  
  135.         }elseif(($key=$this->_isMassSendJobFinish($receiveObj))){  
  136.             $this->_responseType = WeixinResponse::AS_MASSSENDJOBFINISH;  
  137.             $this->_responseKey = $key===true?'':$key;  
  138.             $this->_responseContent = $this->_responseMassSendJobFinish($receiveObj);  
  139.             $msgFormat = 'xml';  
  140.         }elseif(($key=$this->_isTemplateSendJobFinish($receiveObj))){  
  141.             $this->_responseType = WeixinResponse::AS_TEMPLATESENDJOBFINISH;  
  142.             $this->_responseKey = $key===true?'':$key;  
  143.             $this->_responseContent = $this->_responseTemplateSendJobFinish($receiveObj);  
  144.             $msgFormat = 'xml';  
  145.         }  
  146.           
  147.         // 文本消息  
  148.         elseif(($key=$this->_isCommand($receiveObj))){  
  149.             $this->_responseType = WeixinResponse::AS_COMMAND;  
  150.             $this->_responseKey = $key===true?'':$key;  
  151.             $this->_responseContent = $this->_responseCommand($receiveObj);  
  152.             $msgFormat = 'xml';  
  153.         }elseif(($key=$this->_isMessage($receiveObj))){  
  154.             $this->_responseType = WeixinResponse::AS_MESSAGE;  
  155.             $this->_responseKey = $key===true?'':$key;  
  156.             $this->_responseContent = $this->_responseMessage($receiveObj);  
  157.             $msgFormat = 'xml';  
  158.         }  
  159.           
  160.         // 未知,可能是新类型  
  161.         else{  
  162.             $this->_responseType = WeixinResponse::AS_UNKNOWN;  
  163.             $this->_responseKey = '';  
  164.             $this->_responseContent = $this->_responseUnknown($receiveObj);  
  165.             $msgFormat = 'raw';  
  166.         }  
  167.           
  168.         if($this->_responseContent===false){  
  169.             // 出错:未配置对事件或消息的响应  
  170.             return false;  
  171.         }  
  172.           
  173.         $this->_log("ResponseType: " . $this->_responseType, WXAPI_LOG_DEBUG);  
  174.         $this->_log("ResponseKey: " . $this->_responseKey, WXAPI_LOG_DEBUG);  
  175.         $this->_log("ResponseContent: " . print_r($this->_responseContent, true), WXAPI_LOG_DEBUG);  
  176.           
  177.         $this->_responseMessage = $this->_createResponseMessage(  
  178.                 $receiveObj,  
  179.                 $this->_responseContent,  
  180.                 $msgFormat  
  181.         );  
  182.   
  183.         $this->_log("Generated Response Message: " . print_r($this->_responseMessage, true), WXAPI_LOG_DEBUG);  
  184.           
  185.         return $this->_responseMessage['MsgContent'];  
  186.     }  
  187.       
  188.     /** 
  189.      * 是否空消息 
  190.      * @param object $receiveObj 接收对象 
  191.      * @return boolean 
  192.      */  
  193.     protected function _isEmpty($receiveObj){  
  194.         // 注意:empty($receiveObj->postObj)即使非空也返回true  
  195.         // When using empty() on inaccessible object properties, the __isset() overloading method will be called, if declared.  
  196.           
  197.         $postObj = $receiveObj->postObj;  
  198.         return empty($postObj)?true:false;  
  199.     }  
  200.       
  201.     /** 
  202.      * 是否验证消息 
  203.      * @param object $receiveObj 接收对象 
  204.      * @return boolean 
  205.      */  
  206.     protected function _isEcho($receiveObj){  
  207.         return isset($receiveObj->getObj->echostr) && $receiveObj->getObj->echostr;  
  208.     }  
  209.       
  210.     /** 
  211.      * 是否指令消息 
  212.      * @param object $receiveObj 接收对象 
  213.      * @return string|false 
  214.      */  
  215.     protected function _isCommand($receiveObj){  
  216.         $aMsgTypes = array(  
  217.                 'text'  
  218.         );  
  219.           
  220.         if(!in_array($receiveObj->postObj->MsgType, $aMsgTypes, false)){  
  221.             return false;  
  222.         }  
  223.           
  224.         $command = trim($receiveObj->postObj->Content);  
  225.         if(($c=$this->Config->Command) && !empty($c[$command])){  
  226.             return $command;  
  227.         }else{  
  228.             return false;  
  229.         }  
  230.     }  
  231.       
  232.     /** 
  233.      * 是否事件消息 
  234.      * @param object $receiveObj 接收对象 
  235.      * @return string|false 
  236.      */  
  237.     protected function _isEvent($receiveObj){  
  238.         return !strcasecmp($receiveObj->postObj->MsgType, 'event');  
  239.     }  
  240.       
  241.     /** 
  242.      * 是否订阅事件 
  243.      * @param object $receiveObj 接收对象 
  244.      * @return string|false 
  245.      */  
  246.     protected function _isSubscribe($receiveObj){  
  247.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'subscribe')){  
  248.             return isset($receiveObj->postObj->EventKey) && ($key=(string)$receiveObj->postObj->EventKey)?  
  249.             $key:true;  
  250.         }else{  
  251.             return false;  
  252.         }  
  253.     }  
  254.       
  255.     /** 
  256.      * 是否取消订阅事件 
  257.      * @param object $receiveObj 接收对象 
  258.      * @return boolean 
  259.      */  
  260.     protected function _isUnsubscribe($receiveObj){  
  261.         return $this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'unsubscribe');  
  262.     }  
  263.       
  264.     /** 
  265.      * 是否扫描事件 
  266.      * @param object $receiveObj 接收对象 
  267.      * @return array|false 
  268.      */  
  269.     protected function _isScan($receiveObj){  
  270.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scan')){  
  271.             return array(  
  272.                     'EventKey' => $receiveObj->postObj->EventKey,  
  273.                     'Ticket' => $receiveObj->postObj->Ticket,  
  274.             );  
  275.         }else{  
  276.             return false;  
  277.         }  
  278.     }  
  279.       
  280.     /** 
  281.      * 是否地理位置消息 
  282.      * @param object $receiveObj 接收对象 
  283.      * @return array|false 
  284.      */  
  285.     protected function _isLocation($receiveObj){  
  286.         if(!strcasecmp($receiveObj->postObj->MsgType, 'location')){  
  287.             return array(  
  288.                     'Latitude' => $receiveObj->postObj->Location_Y,  
  289.                     'Longitude' => $receiveObj->postObj->Location_X,  
  290.                     'Precision' => $receiveObj->postObj->Scale,  
  291.                     'Label' => $receiveObj->postObj->Label,  
  292.             );  
  293.         }elseif($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'LOCATION')){  
  294.             return array(  
  295.                     'Latitude' => $receiveObj->postObj->Latitude,  
  296.                     'Longitude' => $receiveObj->postObj->Longitude,  
  297.                     'Precision' => $receiveObj->postObj->Precision,  
  298.                     'Label' => '',  
  299.             );  
  300.         }else{  
  301.             return false;  
  302.         }  
  303.     }  
  304.       
  305.     /** 
  306.      * 是否点击菜单拉取消息事件 
  307.      * @param object $receiveObj 接收对象 
  308.      * @return string|false 
  309.      */  
  310.     protected function _isClick($receiveObj){  
  311.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'CLICK')){  
  312.             return $receiveObj->postObj->EventKey;  
  313.         }else{  
  314.             return false;  
  315.         }  
  316.     }  
  317.       
  318.     /** 
  319.      * 是否点击菜单跳转链接事件 
  320.      * @param object $receiveObj 接收对象 
  321.      * @return string|false 
  322.      */  
  323.     protected function _isView($receiveObj){  
  324.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'VIEW')){  
  325.             return $receiveObj->postObj->EventKey;  
  326.         }else{  
  327.             return false;  
  328.         }  
  329.     }  
  330.       
  331.     /** 
  332.      * 是否:扫码推事件 
  333.      * @param object $receiveObj 接收对象 
  334.      * @return string|false 
  335.      */  
  336.     protected function _isScanCodePush($receiveObj){  
  337.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scancode_push')){  
  338.             return $receiveObj->postObj->EventKey;  
  339.         }else{  
  340.             return false;  
  341.         }  
  342.     }  
  343.       
  344.     /** 
  345.      * 是否:扫码推事件且弹出“消息接收中”提示框 
  346.      * @param object $receiveObj 接收对象 
  347.      * @return string|false 
  348.      */  
  349.     protected function _isScanCodeWaitMsg($receiveObj){  
  350.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scancode_waitmsg')){  
  351.             return $receiveObj->postObj->EventKey;  
  352.         }else{  
  353.             return false;  
  354.         }  
  355.     }  
  356.       
  357.     /** 
  358.      * 是否:弹出系统拍照发图 
  359.      * @param object $receiveObj 接收对象 
  360.      * @return string|false 
  361.      */  
  362.     protected function _isPicSysPhoto($receiveObj){  
  363.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_sysphoto')){  
  364.             return $receiveObj->postObj->EventKey;  
  365.         }else{  
  366.             return false;  
  367.         }  
  368.     }  
  369.       
  370.     /** 
  371.      * 是否:弹出拍照或者相册发图 
  372.      * @param object $receiveObj 接收对象 
  373.      * @return string|false 
  374.      */  
  375.     protected function _isPicPhotoOrAlbum($receiveObj){  
  376.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_photo_or_album')){  
  377.             return $receiveObj->postObj->EventKey;  
  378.         }else{  
  379.             return false;  
  380.         }  
  381.     }  
  382.       
  383.     /** 
  384.      * 是否:弹出微信相册发图器 
  385.      * @param object $receiveObj 接收对象 
  386.      * @return string|false 
  387.      */  
  388.     protected function _isPicWeixin($receiveObj){  
  389.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_weixin')){  
  390.             return $receiveObj->postObj->EventKey;  
  391.         }else{  
  392.             return false;  
  393.         }  
  394.     }  
  395.       
  396.     /** 
  397.      * 是否:弹出地理位置选择器 
  398.      * @param object $receiveObj 接收对象 
  399.      * @return string|false 
  400.      */  
  401.     protected function _isLocationSelect($receiveObj){  
  402.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'location_select')){  
  403.             return $receiveObj->postObj->EventKey;  
  404.         }else{  
  405.             return false;  
  406.         }  
  407.     }  
  408.       
  409.     /** 
  410.      * 是否普通消息 
  411.      * @param object $receiveObj 接收对象 
  412.      * @return string|false 
  413.      */  
  414.     protected function _isMessage($receiveObj){  
  415.         $aMsgTypes = array(  
  416.                 'text', 'image', 'voice', 'video', 'link','shortvideo'  
  417.         );  
  418.           
  419.         return in_array($receiveObj->postObj->MsgType, $aMsgTypes, false)?$receiveObj->postObj->MsgType:false;  
  420.     }  
  421.       
  422.     /** 
  423.      * 是否群发消息通知事件 
  424.      * @param object $receiveObj 接收对象 
  425.      * @return string|false 返回消息id 
  426.      */  
  427.     protected function _isMassSendJobFinish($receiveObj){  
  428.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'MASSSENDJOBFINISH')){  
  429.             return $receiveObj->postObj->MsgID;  
  430.             /* 
  431.             return array( 
  432.                     'MsgID' => $receiveObj->postObj->MsgID, 
  433.                     'Status' => $receiveObj->postObj->Status, 
  434.                     'TotalCount' => $receiveObj->postObj->TotalCount, 
  435.                     'FilterCount' => $receiveObj->postObj->FilterCount, 
  436.                     'SentCount' => $receiveObj->postObj->SentCount, 
  437.                     'ErrorCount' => $receiveObj->postObj->ErrorCount, 
  438.             );*/  
  439.         }else{  
  440.             return false;  
  441.         }  
  442.     }  
  443.       
  444.     /** 
  445.      * 是否模板消息通知事件 
  446.      * @param object $receiveObj 接收对象 
  447.      * @return string|false 返回消息id 
  448.      */  
  449.     protected function _isTemplateSendJobFinish($receiveObj){  
  450.         if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'TEMPLATESENDJOBFINISH')){  
  451.             return $receiveObj->postObj->MsgID;  
  452.         }else{  
  453.             return false;  
  454.         }  
  455.     }  
  456.       
  457.     /** 
  458.      * 创建响应消息 
  459.      * @param object $receiveObj 接收对象 
  460.      * @param mixed $responseContent 原始响应内容 
  461.      * @param string $msgFormat 消息格式 
  462.      * @return array|false 
  463.      */  
  464.     protected function _createResponseMessage($receiveObj, $responseContent, $msgFormat='xml'){  
  465.       
  466.         if(is_array($responseContent) && !empty($responseContent['Callback'])){  
  467.             $data = $this->_run_callback($responseContent['Callback'], array($receiveObj, $this));  
  468.               
  469.             if($data===false){  
  470.                 $this->_log("Run Callback : " . print_r($responseContent['Callback'], true) . " Failed", WXAPI_LOG_ERR);  
  471.                 return false;  
  472.             }  
  473.   
  474.             if(is_array($data)){  
  475.                 $t = $data;  
  476.                 $responseContent = array(  
  477.                         'MsgType' => $t['MsgType'],  
  478.                         'Content' => $t['Content'],  
  479.                 );  
  480.             }else{  
  481.                 $responseContent['Content'] = $data;  
  482.                 if($responseContent['MsgType']=='callback'){  
  483.                     $responseContent['MsgType'] = 'text';  
  484.                 }  
  485.             }  
  486.         }  
  487.   
  488.         if(is_string($responseContent)){  
  489.             $responseContent = array(  
  490.                     'MsgType' => 'text',  
  491.                     'Content' => $responseContent,  
  492.             );  
  493.         }elseif(!$responseContent['MsgType']){  
  494.             $responseContent['MsgType'] = 'text';  
  495.         }  
  496.           
  497.         if(!$responseContent['Content'] && !strlen($responseContent['Content'])  
  498.         && strcasecmp('transfer_customer_service', $responseContent['MsgType']) // 转发客服消息,允许Content为空     
  499.         ){  
  500.             return false;  
  501.         }  
  502.           
  503.         // 预处理消息  
  504.         if($msgFormat=='xml'){  
  505.             $responseContent = $this->_preprocessResponseMedia($responseContent);  
  506.         }  
  507.   
  508.         $msgContentOutput = self::generateMessage($receiveObj->postData['FromUserName'], $receiveObj->postData['ToUserName'], $responseContent);  
  509.   
  510.         // 根据加密类型生成相应响应消息  
  511.         switch($receiveObj->msgEncodingMode){  
  512.             // 兼容模式  
  513.             case WXAPI_APP_ENCODING_COMPAT:  
  514.                 // 未正确解密,使用明文返回  
  515.                 if(empty($receiveObj->msgEncodingKey)){  
  516.                     $msgEncoding = WXAPI_APP_ENCODING_CLEAR;  
  517.                       
  518.                     $this->_log("Encoding Response Msg in Clear Mode", WXAPI_LOG_DEBUG);  
  519.                     break;  
  520.                 }  
  521.                   
  522.             // 安全模式  
  523.             case WXAPI_APP_ENCODING_SECURE:  
  524.                 $msgContentOriginal = $msgContentOutput;  
  525.                 $msgContentOutput = self::_encrypt_response($msgContentOutput, $receiveObj->msgEncodingKey);  
  526.                 $msgEncoding = WXAPI_APP_ENCODING_SECURE;  
  527.                   
  528.                 $this->_log("Encoding Response Msg in Secure Mode", WXAPI_LOG_DEBUG);  
  529.                 break;  
  530.                   
  531.             // 明文模式  
  532.             case WXAPI_APP_ENCODING_CLEAR:  
  533.             default:  
  534.                 $msgEncoding = WXAPI_APP_ENCODING_CLEAR;  
  535.                   
  536.                 $this->_log("Encoding Response Msg In Clear Mode", WXAPI_LOG_DEBUG);  
  537.                 break;  
  538.         }  
  539.       
  540.         return array(  
  541.                 'MsgType' => $responseContent['MsgType'],  
  542.                 'MsgFormat' => $msgFormat,  
  543.                 'MsgContent' => $msgFormat=='xml'?$msgContentOutput: $responseContent['Content'],  
  544.                 'MsgOriginal' => $msgContentOriginal?$msgContentOriginal:NULL,  
  545.                 'MsgEncoding' => $msgEncoding,  
  546.                 'RawContent' => $responseContent['Content']  
  547.         );  
  548.     }  
  549.       
  550.     /** 
  551.      * 错误响应 
  552.      * @param object $receiveObj 接收对象 
  553.      * @return string 
  554.      */  
  555.     protected function _responseError($receiveObj){  
  556.         return "Error";  
  557.     }  
  558.       
  559.     /** 
  560.      * 未知响应 
  561.      * @param object $receiveObj 接收对象 
  562.      * @return string 
  563.      */  
  564.     protected function _responseUnknown($receiveObj){  
  565.         //return "Unknown";  
  566.     }  
  567.       
  568.     /** 
  569.      * 验证响应 
  570.      * @param object $receiveObj 接收对象 
  571.      * @return string 
  572.      */  
  573.     protected function _responseEcho($receiveObj){  
  574.         return $receiveObj->getObj->echostr;  
  575.     }  
  576.       
  577.     /** 
  578.      * 空消息响应 
  579.      * @param object $receiveObj 接收对象 
  580.      * @return string 
  581.      */  
  582.     protected function _responseEmpty($receiveObj){  
  583.         return "Empty";  
  584.     }  
  585.       
  586.     /** 
  587.      * 指令响应 
  588.      * @param object $receiveObj 接收对象 
  589.      * @return string 
  590.      */  
  591.     protected function _responseCommand($receiveObj){  
  592.         $command = trim($receiveObj->postObj->Content);  
  593.         $settings = $this->Config->getConfig('Command');  
  594.         if(!isset($settings[$command])){  
  595.             return $this->_throw_exception("Command {$command} not configured", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);  
  596.         }  
  597.         $content = $settings[$command];  
  598.         return $content;  
  599.     }  
  600.       
  601.     /** 
  602.      * 事件响应 
  603.      * @param object $receiveObj 接收对象 
  604.      * @return string 
  605.      */  
  606.     protected function _responseEvent($receiveObj){  
  607.         $event = strtolower($receiveObj->postObj->Event);  
  608.   
  609.         $settings = $this->Config->getConfig('Event');  
  610.         if(!isset($settings[$event])){  
  611.             return $this->_throw_exception("Miss resoponse for Event {$event}", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);  
  612.         }  
  613.           
  614.         if(isset($settings[$event]['MsgType'])){  
  615.             $content = $settings[$event];  
  616.         }elseif(isset($receiveObj->postObj->EventKey)){  
  617.             $eventkey = (string) $receiveObj->postObj->EventKey;  
  618.             if(!isset($settings[$event][$eventkey])){  
  619.                 return $this->_throw_exception("Miss response for Event {$event}, Key {$eventkey}", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);  
  620.             }  
  621.               
  622.             $content = $settings[$event][$eventkey];  
  623.         }  
  624.           
  625.         return $content;  
  626.     }  
  627.       
  628.     /** 
  629.      * 订阅事件响应 
  630.      * @param object $receiveObj 接收对象 
  631.      * @return string 
  632.      */  
  633.     protected function _responseSubscribe($receiveObj){  
  634.         return $this->_responseEvent($receiveObj);  
  635.     }  
  636.       
  637.     /** 
  638.      * 取消订阅事件响应 
  639.      * @param object $receiveObj 接收对象 
  640.      * @return string 
  641.      */  
  642.     protected function _responseUnsubscribe($receiveObj){  
  643.         return $this->_responseEvent($receiveObj);  
  644.     }  
  645.       
  646.     /** 
  647.      * 扫描事件响应 
  648.      * @param object $receiveObj 接收对象 
  649.      * @return string 
  650.      */  
  651.     protected function _responseScan($receiveObj){  
  652.         return $this->_responseEvent($receiveObj);  
  653.     }  
  654.       
  655.     /** 
  656.      * 地理位置消息响应 
  657.      * @param object $receiveObj 接收对象 
  658.      * @return string 
  659.      */  
  660.     protected function _responseLocation($receiveObj){  
  661.         if(!strcasecmp($receiveObj->postObj->MsgType, 'event')){  
  662.             return $this->_responseEvent($receiveObj);  
  663.         }else if(!strcasecmp($receiveObj->postObj->MsgType, 'location')){  
  664.             // 普通位置消息  
  665.             // @todo 处理接收到的普通位置消息  
  666.         }  
  667.     }  
  668.       
  669.     /** 
  670.      * 点击菜单拉取消息事件响应 
  671.      * @param object $receiveObj 接收对象 
  672.      * @return string 
  673.      */  
  674.     protected function _responseClick($receiveObj){  
  675.         return $this->_responseEvent($receiveObj);  
  676.     }  
  677.       
  678.     /** 
  679.      * 点击菜单跳转链接事件响应 
  680.      * @param object $receiveObj 接收对象 
  681.      * @return string 
  682.      */  
  683.     protected function _responseView($receiveObj){  
  684.         //return $this->_responseEvent($receiveObj);  
  685.     }  
  686.       
  687.     /** 
  688.      * 响应:扫码推事件 
  689.      * @param object $receiveObj 接收对象 
  690.      * @return string 
  691.      */  
  692.     protected function _responseScanCodePush($receiveObj){  
  693.         return $this->_responseEvent($receiveObj);  
  694.     }  
  695.       
  696.     /** 
  697.      * 响应:扫码推事件且弹出“消息接收中”提示框 
  698.      * @param object $receiveObj 接收对象 
  699.      * @return string 
  700.      */  
  701.     protected function _responseScanCodeWaitMsg($receiveObj){  
  702.         return $this->_responseEvent($receiveObj);  
  703.     }  
  704.       
  705.     /** 
  706.      * 响应:弹出系统拍照发图 
  707.      * @param object $receiveObj 接收对象 
  708.      * @return string 
  709.      */  
  710.     protected function _responsePicSysPhoto($receiveObj){  
  711.         return $this->_responseEvent($receiveObj);  
  712.     }  
  713.       
  714.     /** 
  715.      * 响应:弹出拍照或者相册发图 
  716.      * @param object $receiveObj 接收对象 
  717.      * @return string 
  718.      */  
  719.     protected function _responsePicPhotoOrAlbum($receiveObj){  
  720.         return $this->_responseEvent($receiveObj);  
  721.     }  
  722.       
  723.     /** 
  724.      * 响应:弹出微信相册发图器 
  725.      * @param object $receiveObj 接收对象 
  726.      * @return string 
  727.      */  
  728.     protected function _responsePicWeixin($receiveObj){  
  729.         return $this->_responseEvent($receiveObj);  
  730.     }  
  731.       
  732.     /** 
  733.      * 响应:弹出地理位置选择器 
  734.      * @param object $receiveObj 接收对象 
  735.      * @return string 
  736.      */  
  737.     protected function _responseLocationSelect($receiveObj){  
  738.         return $this->_responseEvent($receiveObj);  
  739.     }  
  740.       
  741.     /** 
  742.      * 普通消息响应 
  743.      * @param object $receiveObj 接收对象 
  744.      * @return string 
  745.      */  
  746.     protected function _responseMessage($receiveObj){  
  747.         // write your code, such as save message and remind customer service  
  748.           
  749.         // 通关密语  
  750.         if(($msg=$this->_responseArgot($receiveObj))!==false){  
  751.             return $msg;  
  752.         }  
  753.           
  754.         // 转发客服消息到微信多客服系统  
  755.         elseif($this->Config->TransferCustomerService){  
  756.             return array(  
  757.                 'MsgType' => 'transfer_customer_service',  
  758.             );  
  759.         }  
  760.     }  
  761.       
  762.     /** 
  763.      * 响应暗语 
  764.      *  
  765.      * @param object $receiveObj 
  766.      * @return false|string 返回false表示非暗语处理,可以由其它逻辑处理 
  767.      */  
  768.     protected function _responseArgot($receiveObj){  
  769.         if(!isset($receiveObj->postObj->Content) || !($msg=$receiveObj->postObj->Content)){  
  770.             return false;  
  771.         }  
  772.           
  773.         $msg = trim($msg);  
  774.           
  775.         if(defined('WXAPI_ARGOT_WHO_AM_I') && WXAPI_ARGOT_WHO_AM_I && !strcasecmp(WXAPI_ARGOT_WHO_AM_I, $msg)){  
  776.             return "OH LORD,\nMY 4susername is " . $this->Config->AppName . ",\nAppId: " . $this->Config->AppId . ",\n Server: " . $_SERVER['HTTP_HOST'] ." .";  
  777.         }  
  778.           
  779.         elseif(defined('WXAPI_ARGOT_DESTORY_SESSION') && WXAPI_ARGOT_DESTORY_SESSION && !strcasecmp(WXAPI_ARGOT_DESTORY_SESSION, $msg)){  
  780.             $openid = $receiveObj->parse_openid();  
  781.             if($openid && class_exists('WeixinUserModel') && method_exists('WeixinUserModel', 'destroy_session')){  
  782.                 $oWeixinUserModel = new WeixinUserModel();  
  783.                 $succ = $oWeixinUserModel->destroy_session($openid);  
  784.                 return $succ?"Your session has been destoryed Successfully!":"Failed destroy your session!";  
  785.             }else{  
  786.                 return false;  
  787.             }  
  788.         }  
  789.           
  790.         else{  
  791.             return false;  
  792.         }  
  793.     }  
  794.       
  795.     /** 
  796.      * 群发消息通知响应 
  797.      * @param object $receiveObj 接收对象 
  798.      * @return string 
  799.      */  
  800.     protected function _responseMassSendJobFinish($receiveObj){  
  801.         // write your code, such as save message and remind customer service  
  802.       
  803.     }  
  804.       
  805.     /** 
  806.      * 模板消息通知响应 
  807.      * @param object $receiveObj 接收对象 
  808.      * @return string 
  809.      */  
  810.     protected function _responseTemplateSendJobFinish($receiveObj){  
  811.         // write your code, such as save message and remind customer service  
  812.       
  813.     }  
  814.       
  815.     /** 
  816.      * 预处理多媒体文件 
  817.      * 可以根据消息类型,调用微信接口,将消息中的图片、音频等多媒体文件上传到微信服务器, 
  818.      * 得到MediaId,并替换掉原来的多媒体文件 
  819.      * @param mixed $Content 
  820.      * @return mixed 
  821.      */  
  822.     protected function _preprocessResponseMedia($Content){  
  823.         if(!is_array($Content) || empty($Content['MsgType'])){  
  824.             return $Content;  
  825.         }  
  826.           
  827.         $msgtype = strtolower($Content['MsgType']);  
  828.         switch ($msgtype){  
  829.             case 'image':  
  830.                 $mediaField = 'MediaId';  
  831.                 if(is_string($Content['Content']) && !$this->_isMediaId($Content['Content'])){  
  832.                     $mediaFile = $Content['Content'];  
  833.                     $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  834.   
  835.                     $mediaId = $oClient->upload_media($mediaFile, 'image');  
  836.                       
  837.                     $Content['Content'] = $mediaId;  
  838.                 }else{  
  839.                     $mediaId = $Content['Content'];  
  840.                     $mediaFile = '';  
  841.                 }  
  842.                 $this->_responseMedia[$mediaField] = array($mediaId, 'image', $mediaFile);  
  843.                   
  844.                 break;  
  845.             case 'voice':  
  846.                 $mediaField = 'MediaId';  
  847.                 if(is_string($Content['Content']) && !$this->_isMediaId($Content['Content'])){  
  848.                     $mediaFile = $Content['Content'];  
  849.                       
  850.                     $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  851.                     $mediaId = $oClient->upload_media($mediaFile, 'voice');  
  852.                       
  853.                     $Content['Content'] = $mediaId;  
  854.                 }else{  
  855.                     $mediaId = $Content['Content'];  
  856.                     $mediaFile = '';  
  857.                 }  
  858.                 $this->_responseMedia[$mediaField] = array($mediaId, 'voice', $mediaFile);  
  859.                   
  860.                 break;  
  861.             case 'video':  
  862.                 $mediaField = 'MediaId';  
  863.                 if(!$this->_isMediaId($Content['Content']['MediaId'])){  
  864.                     $mediaFile = $Content['Content']['MediaId'];  
  865.                     $mediaField = 'MediaId';  
  866.                       
  867.                     $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  868.                     $mediaId = $oClient->upload_media($mediaFile, 'video');  
  869.                       
  870.                     $Content['Content']['MediaId'] = $mediaId;  
  871.                 }else{  
  872.                     $mediaId = $Content['Content']['MediaId'];  
  873.                     $mediaFile = '';  
  874.                 }  
  875.                 $this->_responseMedia[$mediaField] = array($mediaId, 'video', $mediaFile);  
  876.                   
  877.                 $thumbMediaField = 'ThumbMediaId';  
  878.                 if(!$this->_isMediaId($Content['Content']['ThumbMediaId'])){  
  879.                     $thumbMediaFile = $Content['Content']['ThumbMediaId'];  
  880.                       
  881.                     $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  882.                     $thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');  
  883.   
  884.                     $Content['Content']['ThumbMediaId'] = $thumbMediaId;  
  885.                       
  886.                 }else{  
  887.                     $thumbMediaId = $Content['Content']['ThumbMediaId'];  
  888.                     $thumbMediaFile = '';  
  889.                 }  
  890.                 $this->_responseMedia[$thumbMediaField] = array($thumbMediaId, 'thumb', $thumbMediaFile);  
  891.                 break;  
  892.             case 'music':  
  893.                 $thumbMediaField = 'ThumbMediaId';  
  894.                 if(is_array($Content['Content'])){  
  895.                     if(!$this->_isMediaId($Content['Content']['ThumbMediaId'])){  
  896.                         $thumbMediaFile = $Content['Content']['ThumbMediaId'];  
  897.                       
  898.                         $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  899.                         $thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');  
  900.                           
  901.                         $Content['Content']['ThumbMediaId'] = $thumbMediaId;  
  902.                     }else{  
  903.                         $thumbMediaId = $Content['Content']['ThumbMediaId'];  
  904.                         $thumbMediaFile = '';  
  905.                     }  
  906.                 }else{  
  907.                     if(!$this->_isMediaId($Content['Content'])){  
  908.                         $thumbMediaFile = $Content['Content'];  
  909.                         $oClient = WeixinApi::instance($this->Config->Client, $this->Config);  
  910.                         $thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');  
  911.                           
  912.                         $Content['Content'] = $thumbMediaId;  
  913.                     }else{  
  914.                         $thumbMediaId = $Content['Content'];  
  915.                         $thumbMediaFile = '';  
  916.                     }  
  917.                 }  
  918.                 $this->_responseMedia[$thumbMediaField] = array($thumbMediaId, 'thumb', $thumbMediaFile);  
  919.                   
  920.                 break;        
  921.             default:  
  922.                 // other type, do nothing                         
  923.         }  
  924.           
  925.         return $Content;  
  926.     }  
  927.       
  928.     public function createMessage($FromUserName, $ToUserName, $Content){  
  929.         return self::generateMessage($FromUserName, $ToUserName, $Content);  
  930.     }  
  931.       
  932.     /** 
  933.      * 根据消息类型,创建消息 
  934.      * @param string $FromUserName 发送者 
  935.      * @param string $ToUserName    接收者 
  936.      * @param string|array $Content 发送内容,默认为文本消息,传数组可设定消息类型,格式为:aray('MsgType' => 'image', 'Content' => '消息内容') 
  937.      * @return string 返回XML格式消息 
  938.      */  
  939.     public static function generateMessage($FromUserName, $ToUserName, $Content){  
  940.         $aMsgTypes = array(  
  941.             'text', 'image', 'voice', 'video', 'music', 'news', 'transfer_customer_service'  
  942.         );  
  943.           
  944.         if(is_array($Content)){  
  945.             $type = $Content['MsgType'];  
  946.             $data = $Content['Content'];  
  947.         }else{  
  948.             $type = 'text';  
  949.             $data = $Content;  
  950.         }  
  951.           
  952.         if(!in_array($type, $aMsgTypes, false)){  
  953.             return WeixinApi::throw_exception("Unknown MsgType: $type", WXAPI_ERR_CONFIG, $Content, __FILE__, __LINE__);  
  954.         }  
  955.           
  956.         if(!strcasecmp($type, 'transfer_customer_service')){  
  957.             $method = 'generateTransferCustomerServiceMessage';  
  958.         }else{  
  959.             $method = 'generate' . ucfirst(strtolower($type)) . 'Message';  
  960.         }  
  961.         return self::$method($FromUserName, $ToUserName, $data);  
  962.     }  
  963.       
  964.     public function createTextMessage($FromUserName, $ToUserName, $content){  
  965.         return self::generateTextMessage($FromUserName, $ToUserName, $content);  
  966.     }  
  967.       
  968.     /** 
  969.      * 创建文本消息 
  970.      * @param object $object 
  971.      * @param string $content 文本内容,支持换行 
  972.      * @return string 
  973.      */  
  974.     public static function generateTextMessage($FromUserName, $ToUserName, $content)  
  975.     {  
  976.         $msgTpl = "<xml>  
  977. <ToUserName><![CDATA[%s]]></ToUserName>  
  978. <FromUserName><![CDATA[%s]]></FromUserName>  
  979. <CreateTime>%s</CreateTime>  
  980. <MsgType><![CDATA[text]]></MsgType>  
  981. <Content><![CDATA[%s]]></Content>  
  982. </xml>";  
  983.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $content);  
  984.     }  
  985.       
  986.     public function createImageMessage($FromUserName, $ToUserName, $mediaId){  
  987.         return self::generateImageMessage($FromUserName, $ToUserName, $mediaId);  
  988.     }  
  989.       
  990.     /** 
  991.      * 创建图片消息 
  992.      * @param object $object 
  993.      * @param string $mediaId 通过上传多媒体文件,得到的id 
  994.      * @return string 
  995.      */  
  996.     public static function generateImageMessage($FromUserName, $ToUserName, $mediaId)  
  997.     {  
  998.         $msgTpl = "<xml>  
  999. <ToUserName><![CDATA[%s]]></ToUserName>  
  1000. <FromUserName><![CDATA[%s]]></FromUserName>  
  1001. <CreateTime>%s</CreateTime>  
  1002. <MsgType><![CDATA[image]]></MsgType>  
  1003. <Image>  
  1004. %s  
  1005. </Image>  
  1006. </xml>";  
  1007.         $mediaTpl = "<MediaId><![CDATA[%s]]></MediaId>";  
  1008.         if(!is_array($mediaId)){  
  1009.             $mediaIds = array($mediaId);  
  1010.         }else{  
  1011.             $mediaIds = $mediaId;  
  1012.         }  
  1013.           
  1014.         $media = '';  
  1015.         foreach($mediaIds as $mediaId){  
  1016.             $media .= sprintf($mediaTpl, $mediaId);  
  1017.         }  
  1018.           
  1019.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);  
  1020.     }  
  1021.       
  1022.     public function createVoiceMessage($FromUserName, $ToUserName, $mediaId){  
  1023.         return self::generateVoiceMessage($FromUserName, $ToUserName, $mediaId);  
  1024.     }  
  1025.       
  1026.     /** 
  1027.      * 创建语音消息 
  1028.      * @param object $object 
  1029.      * @param string $mediaId 通过上传多媒体文件,得到的id  
  1030.      * @return string 
  1031.      */  
  1032.     public static function generateVoiceMessage($FromUserName, $ToUserName, $mediaId)  
  1033.     {  
  1034.         $msgTpl = "<xml>  
  1035. <ToUserName><![CDATA[%s]]></ToUserName>  
  1036. <FromUserName><![CDATA[%s]]></FromUserName>  
  1037. <CreateTime>%s</CreateTime>  
  1038. <MsgType><![CDATA[voice]]></MsgType>  
  1039. <Voice>  
  1040. %s  
  1041. </Voice>  
  1042. </xml>";  
  1043.         $mediaTpl = "<MediaId><![CDATA[%s]]></MediaId>";  
  1044.         if(!is_array($mediaId)){  
  1045.             $mediaIds = array($mediaId);  
  1046.         }else{  
  1047.             $mediaIds = $mediaId;  
  1048.         }  
  1049.       
  1050.         $media = '';  
  1051.         foreach($mediaIds as $mediaId){  
  1052.             $media .= sprintf($mediaTpl, $mediaId);  
  1053.         }  
  1054.       
  1055.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);  
  1056.     }  
  1057.       
  1058.     public function createVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId=NULL){  
  1059.         return self::generateVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId);  
  1060.     }  
  1061.       
  1062.     /** 
  1063.      * 创建视频消息 
  1064.      * @param object $object 
  1065.      * @param string|array $mediaId  通过上传多媒体文件,得到的id,可以传数组:Array('MediaId'=>mediaid, 'ThumbMediaId'=>thumbMediaId) 
  1066.      * @param string $thumbMediaId 缩略图的媒体id,通过上传多媒体文件,得到的id,必填字段 
  1067.      * @return string 
  1068.      */  
  1069.     public static function generateVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId=NULL)  
  1070.     {  
  1071.         $msgTpl = "<xml>  
  1072. <ToUserName><![CDATA[%s]]></ToUserName>  
  1073. <FromUserName><![CDATA[%s]]></FromUserName>  
  1074. <CreateTime>%s</CreateTime>  
  1075. <MsgType><![CDATA[video]]></MsgType>  
  1076. <Video>  
  1077. %s  
  1078. </Video>   
  1079. </xml>";  
  1080.           
  1081.         if(is_array($mediaId)){  
  1082.             $mediaData = array(  
  1083.                     'MediaId' => $mediaId['MediaId'],  
  1084.                     'ThumbMediaId' => $mediaId['ThumbMediaId'],  
  1085.             );  
  1086.         }else{  
  1087.             $mediaData = array(  
  1088.                     'MediaId' => $mediaId,  
  1089.                     'ThumbMediaId' => $thumbMediaId,  
  1090.             );  
  1091.         }  
  1092.           
  1093.         $media = "";  
  1094.         foreach ($mediaData as $n=>$d){  
  1095.             $n = ucfirst($n);  
  1096.             $mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";  
  1097.             $media .= sprintf($mediaTpl, $d);  
  1098.         }  
  1099.       
  1100.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);  
  1101.     }  
  1102.       
  1103.     public function createMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title=NULL, $description=NULL, $musicUrl=NULL, $hqMusicUrl=NULL)  
  1104.     {  
  1105.         return self::generateMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title, $description, $musicUrl, $hqMusicUrl);  
  1106.     }  
  1107.       
  1108.     /** 
  1109.      * 创建音乐消息 
  1110.      * @param object $object 
  1111.      * @param string|array $thumbMediaId 缩略图的媒体id,通过上传多媒体文件,得到的id,必填字段,传数组格式如: 
  1112.      * array ( 
  1113.                     "Title" => $title, 
  1114.                     "Description" => $description, 
  1115.                     "MusicURL" => $musicURL, 
  1116.                     "HQMusicUrl" => $hqMusicUrl, 
  1117.                     "ThumbMediaId" => $thumbMediaId, 
  1118.             )  
  1119.      * @param string $title 音乐标题  
  1120.      * @param string $description 音乐描述  
  1121.      * @param string $musicUrl 音乐链接 
  1122.      * @param string $hqMusicUrl 高质量音乐链接,WIFI环境优先使用该链接播放音乐  
  1123.      * @return string 
  1124.      */  
  1125.     public static function generateMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title=NULL, $description=NULL, $musicUrl=NULL, $hqMusicUrl=NULL)  
  1126.     {  
  1127.         $msgTpl = "<xml>  
  1128. <ToUserName><![CDATA[%s]]></ToUserName>  
  1129. <FromUserName><![CDATA[%s]]></FromUserName>  
  1130. <CreateTime>%s</CreateTime>  
  1131. <MsgType><![CDATA[music]]></MsgType>  
  1132. <Music>%s  
  1133. </Music>  
  1134. </xml>";  
  1135.   
  1136.         $media = "";  
  1137.         if (is_array ( $thumbMediaId )) {  
  1138.             $mediaData = array (  
  1139.                     "Title" => $thumbMediaId['Title'],  
  1140.                     "Description" => $thumbMediaId['Description'],  
  1141.                     "MusicUrl" => $thumbMediaId['MusicUrl'],  
  1142.                     "HQMusicUrl" => $thumbMediaId['HQMusicUrl'],  
  1143.                     "ThumbMediaId" => $thumbMediaId['ThumbMediaId'],  
  1144.             );  
  1145.         } else {  
  1146.             $mediaData = array (  
  1147.                     "Title" => $title,  
  1148.                     "Description" => $description,  
  1149.                     "MusicUrl" => $musicUrl,  
  1150.                     "HQMusicUrl" => $hqMusicUrl,  
  1151.                     "ThumbMediaId" => $thumbMediaId,  
  1152.             );  
  1153.         }  
  1154.         foreach($mediaData as $n=>$d){  
  1155.             if($d){  
  1156.                 $n = ucfirst($n);  
  1157.                 $mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";  
  1158.                 $media .= sprintf($mediaTpl, $d);  
  1159.             }  
  1160.         }  
  1161.       
  1162.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);  
  1163.     }  
  1164.       
  1165.     public function createNewsMessage($FromUserName, $ToUserName, $title, $description=NULL, $picUrl=NULL, $url=NULL)  
  1166.     {  
  1167.         return self::generateNewsMessage($FromUserName, $ToUserName, $title, $description, $picUrl, $url);  
  1168.     }  
  1169.       
  1170.     /** 
  1171.      * 创建图文消息 
  1172.      * @param object $object 
  1173.      * @param string|array $title 图文消息标题,传数组格式如: 
  1174.      * array( 
  1175.                     "Title" => $title, 
  1176.                     "Description" => $description, 
  1177.                     "PicUrl" => $picUrl, 
  1178.                     "Url" => $url, 
  1179.             ) 
  1180.             或 
  1181.         array( 0 => array( 
  1182.                     "Title" => $title, 
  1183.                     "Description" => $description, 
  1184.                     "PicUrl" => $picUrl, 
  1185.                     "Url" => $url, 
  1186.             )) 
  1187.      * @param string $description 图文消息描述  
  1188.      * @param string $picUrl 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200  
  1189.      * @param string $url 点击图文消息跳转链接  
  1190.      * @return string 
  1191.      */  
  1192.     public static function generateNewsMessage($FromUserName, $ToUserName, $title, $description=NULL, $picUrl=NULL, $url=NULL)  
  1193.     {  
  1194.         $msgTpl = "<xml>  
  1195. <ToUserName><![CDATA[%s]]></ToUserName>  
  1196. <FromUserName><![CDATA[%s]]></FromUserName>  
  1197. <CreateTime>%s</CreateTime>  
  1198. <MsgType><![CDATA[news]]></MsgType>  
  1199. <ArticleCount>%s</ArticleCount>  
  1200. <Articles>  
  1201. %s  
  1202. </Articles>  
  1203. </xml>";  
  1204.           
  1205.         $media = "";  
  1206.         $items = array();  
  1207.         if(is_array($title)){  
  1208.             if(isset($title['Title']) || isset($title['Description']) || isset($title['PicUrl']) || isset($title['Url'])){  
  1209.                 $items[] = $title;  
  1210.             }else{  
  1211.                 $items = $title;  
  1212.             }  
  1213.         }else{  
  1214.             $items[] = array(  
  1215.                     "Title" => $title,  
  1216.                     "Description" => $description,  
  1217.                     "PicUrl" => $picUrl,  
  1218.                     "Url" => $url,  
  1219.             );  
  1220.         }  
  1221.           
  1222.         $count = count($items);  
  1223.           
  1224.         if($count>10){  
  1225.             return WeixinApi::throw_exception("Over Max 10 news messages", WXAPI_ERR_CONFIG, array('items'=>$items), __FILE__, __LINE__);  
  1226.         }  
  1227.           
  1228.         $valid_item_tags = array('Title', 'Description', 'PicUrl', 'Url');  
  1229.         foreach($items as $item){  
  1230.             $media .= "<item>";  
  1231.             foreach ( $item as $n => $d ) {  
  1232.                 if ($d && in_array($n, $valid_item_tags, true)) {  
  1233.                     $n = ucfirst($n);  
  1234.                     $mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";  
  1235.                     $media .= sprintf ( $mediaTpl, $d );  
  1236.                 }  
  1237.             }  
  1238.             $media .= "</item>";  
  1239.         }  
  1240.       
  1241.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $count, $media);  
  1242.     }  
  1243.       
  1244.     /** 
  1245.      * 创建转发客服消息 
  1246.      * @return string 
  1247.      */  
  1248.     public static function generateTransferCustomerServiceMessage($FromUserName, $ToUserName, $TransInfo_KfAccount=NULL)  
  1249.     {  
  1250.         $msgTpl = "<xml>  
  1251. <ToUserName><![CDATA[%s]]></ToUserName>  
  1252. <FromUserName><![CDATA[%s]]></FromUserName>  
  1253. <CreateTime>%s</CreateTime>  
  1254. <MsgType><![CDATA[transfer_customer_service]]></MsgType>";  
  1255.           
  1256.         if($TransInfo_KfAccount){  
  1257.             $msg .= "<TransInfo>  
  1258.         <KfAccount>%s</KfAccount>  
  1259.     </TransInfo>";  
  1260.         }  
  1261.           
  1262.         $msgTpl .= "</xml>";  
  1263.   
  1264.         return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $TransInfo_KfAccount);  
  1265.     }  
  1266.       
  1267.     public function __get($c){  
  1268.         if(substr($c, 0, 1)!='_'){  
  1269.             if(in_array($c, array('Http'))){  
  1270.                 return parent::__get($c);  
  1271.             }else{  
  1272.                 $n = '_' . $c;  
  1273.                 return $this->$n;  
  1274.             }  
  1275.         }  
  1276.     }  
  1277.       
  1278.     /** 
  1279.      * 生成签名 
  1280.      * @param string $msg_encrypt 
  1281.      * @param string $nonce 
  1282.      * @param string $timestamp 
  1283.      * @param string $token 
  1284.      * @return string 
  1285.      */  
  1286.     public static function genearteSignature($msg_encrypt, $nonce, $timestamp, $token){  
  1287.         $tmpArr = array($token, $timestamp, $nonce, $msg_encrypt);  
  1288.         sort($tmpArr, SORT_STRING);  
  1289.         $tmpStr = implode( $tmpArr );  
  1290.         return sha1( $tmpStr );  
  1291.     }  
  1292.       
  1293.     /** 
  1294.      * 创建加密消息 
  1295.      * @param string $encrypt_content 加密内容 
  1296.      * @param string $nonce 随机数 
  1297.      * @param int $timestamp 时间戳 
  1298.      * @param string $signature 签名 
  1299.      * @return string 
  1300.      */  
  1301.     public static function generateEncryptMessage($encrypt_content, $nonce, $timestamp, $signature)  
  1302.     {  
  1303.         $msgTpl = "<xml>  
  1304. <Encrypt><![CDATA[%s]]></Encrypt>  
  1305. <MsgSignature><![CDATA[%s]]></MsgSignature>  
  1306. <TimeStamp>%s</TimeStamp>  
  1307. <Nonce><![CDATA[%s]]></Nonce>  
  1308. </xml>  
  1309. ";  
  1310.         return sprintf($msgTpl, $encrypt_content, $signature, $timestamp, $nonce);  
  1311.     }  
  1312.       
  1313.     /** 
  1314.      * 加密响应消息 
  1315.      * @return string 
  1316.      */  
  1317.     protected function _encrypt_response($msg, $encodingkey){  
  1318.         $msg_encrypt = $this->_encryptMsg($msg, $encodingkey);  
  1319.         $nonce = WeixinApi_Kit::gen_random_number(11);  
  1320.         $timestamp = time();  
  1321.         $signature = self::genearteSignature($msg_encrypt, $nonce, $timestamp, $this->Config->AppToken);  
  1322.       
  1323.         return self::generateEncryptMessage($msg_encrypt, $nonce, $timestamp, $signature);  
  1324.     }  
  1325.       
  1326.     /** 
  1327.      * 对明文进行加密 
  1328.      * @param string $msg 需要加密的明文 
  1329.      * @param string $encodingkey 加密私钥 
  1330.      * @return string|false 加密得到的密文,失败返回flase 
  1331.      */  
  1332.     protected function _encryptMsg($msg, $encodingkey=NULL)  
  1333.     {  
  1334.         $AESKey = base64_decode(($encodingkey?$encodingkey:$this->Config->AppEncodingAESKey) . "=");  
  1335.       
  1336.         // 获得16位随机字符串,填充到明文之前  
  1337.         $random = WeixinApi_Kit::gen_random_string ( 16 );  
  1338.         $msg = $random . pack ( "N", strlen ( $msg ) ) . $msg . $this->Config->AppId;  
  1339.         // 网络字节序  
  1340.         $size = mcrypt_get_block_size ( MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC );  
  1341.         $module = mcrypt_module_open ( MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '' );  
  1342.         if(false===$module){  
  1343.             return $this->_throw_exception(  
  1344.                     "Cann't open an encryption descriptor"  
  1345.                     , WXAPI_ERR_BAD_ENCRYPT  
  1346.                     , $msg  
  1347.                     , __FILE__, __LINE__  
  1348.             );  
  1349.         }  
  1350.       
  1351.         $iv = substr ( $AESKey, 0, 16 );  
  1352.           
  1353.         // 使用自定义的填充方式对明文进行补位填充  
  1354.         $msg = WeixinApi_Kit::pkcs7_encode ( $msg, 32 );  
  1355.         $init = mcrypt_generic_init ( $module, $AESKey, $iv );  
  1356.         if(false===$init){  
  1357.             return $this->_throw_exception(  
  1358.                     "Cann't initialize buffers for encryption"  
  1359.                     , WXAPI_ERR_BAD_ENCRYPT  
  1360.                     , array('msg' => $msg, 'mcrypt_generic_init return' => $init)  
  1361.                     , __FILE__, __LINE__  
  1362.             );  
  1363.         }elseif(-3==$init){  
  1364.             return $this->_throw_exception(  
  1365.                     "the key length was incorrect"  
  1366.                     , WXAPI_ERR_BAD_ENCRYPT  
  1367.                     , array('msg' => $msg, 'mcrypt_generic_init return' => $init)  
  1368.                     , __FILE__, __LINE__  
  1369.             );  
  1370.         }elseif(-4==$init){  
  1371.             return $this->_throw_exception(  
  1372.                     "there was a memory allocation problem"  
  1373.                     , WXAPI_ERR_BAD_ENCRYPT  
  1374.                     , array('msg' => $msg, 'mcrypt_generic_init return' => $init)  
  1375.                     , __FILE__, __LINE__  
  1376.             );  
  1377.         }elseif($init<0){  
  1378.             return $this->_throw_exception(  
  1379.                     "an unknown error occurred when initialize buffers for encryption"  
  1380.                     , WXAPI_ERR_BAD_ENCRYPT  
  1381.                     , array('msg' => $msg, 'mcrypt_generic_init return' => $init)  
  1382.                     , __FILE__, __LINE__  
  1383.             );  
  1384.         }  
  1385.       
  1386.         // 加密  
  1387.         $encrypted = mcrypt_generic ( $module, $msg );  
  1388.         mcrypt_generic_deinit ( $module );  
  1389.         mcrypt_module_close ( $module );  
  1390.       
  1391.         // print(base64_encode($encrypted));  
  1392.         // 使用BASE64对加密后的字符串进行编码  
  1393.         return base64_encode ( $encrypted );  
  1394.     }  
  1395. }  

猜你喜欢

转载自www.cnblogs.com/piwefei/p/9172094.html
今日推荐