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

来源:互联网 发布:js 定义数组索引变量 编辑:程序博客网 时间:2024/04/26 12:27

【项目需求】

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


【项目疑难点】

  • 理解接口工作方式,统一接口API,响应速度、安全性等

【代码举例】

WeixinApi.class.php  微信公众号接口基类

<?php/** * 微信API 公用方法 *  * PHP version 5 *  * @categoryLib * @package     COM * @subpackage  GZNC * @author      zhongyiwen * @version     SVN: $Id: WeixinApi.class.php 10 2013-10-08 01:34:05Z zhongyw $ *//** * 错误代码 */define('WXAPI_ERR_CONFIG', 1001); // 配置错误define('WXAPI_ERR_HTTP', 1002); // 请求失败define('WXAPI_ERR_LOGIN', 1003); // 登录失败define('WXAPI_ERR_FETCH_DATA', 1004); // 获取数据失败define('WXAPI_ERR_MISS_RESPONSE', 1005); // 缺少响应define('WXAPI_ERR_BAD_SIGNATURE', 1006); // 签名校验失败define('WXAPI_ERR_BAD_DECRYPT', 1007); // 消息解密失败define('WXAPI_ERR_BAD_ENCRYPT', 1008); // 消息加密失败define('WXAPI_ERR_ACCESS_TOKEN', 1009); // access token凭证错误/** * 日志级别 */define('WXAPI_LOG_EMERG', 'EMERG');  // 严重错误: 导致系统崩溃无法使用define('WXAPI_LOG_ALERT', 'ALERT');  // 警戒性错误: 必须被立即修改的错误define('WXAPI_LOG_CRIT', 'CRIT');  // 临界值错误: 超过临界值的错误,例如一天24小时,而输入的是25小时这样define('WXAPI_LOG_ERR', 'ERR');  // 一般错误: 一般性错误define('WXAPI_LOG_WARN', 'WARN');  // 警告性错误: 需要发出警告的错误define('WXAPI_LOG_NOTICE', 'NOTIC');  // 通知: 程序可以运行但是还不够完美的错误define('WXAPI_LOG_INFO', 'INFO');  // 信息: 程序输出信息define('WXAPI_LOG_DEBUG', 'DEBUG');  // 调试: 调试信息define('WXAPI_LOG_EXCEPTION', 'EXCEPTION'); // 异常信息/** * 微信接口默认常量值 */define('WXAPI_ACCESS_TOKEN_EXPIRE', 7100); // access token有效时间,设置比微信默认有效时间7200秒小,避免出现过期错误define('WXAPI_ACCESS_TOKEN_LIMIT', 2000); // access token每日限制次数define('WXAPI_JSAPI_TICKET_EXPIRE', 7100); // jsapi ticket有效时间,单位秒define('WXAPI_JSAPI_TICKET_LIMIT', 2000); // jsapi ticket每日限制次数define('WXAPI_QRCODE_MIN_SCENE', 1); // 二维码场景值最小值define('WXAPI_QRCODE_MAX_SCENE', 2147483647); // 二维码场景值最大值, 32位非0整型define('WXAPI_QRCODE_MAX_LIMIT_SCENE', 100000); // 永久二维码场景值最大值define('WXAPI_QRCODE_EXPIRE', 1800); // 临时二维码有效时间,单位秒define('WXAPI_GROUP_MIN_CUSTOM_ID', 100); // 用户自定义用户组起始id值/** * 微信暗语 */define('WXAPI_ARGOT_WHO_AM_I', 'show me your name'); // 显示当前4susernamedefine('WXAPI_ARGOT_DESTORY_SESSION', 'let me out'); // 清除当前用户sessionclass WeixinApi{/** * 实例化对象 *  * @var array */protected static $_instance = array();/** * 是否启用缓存 * @var bool */protected $_cache = false;/** * 是否启用调试 * @var bool */protected $_debug = false;/** * 配置对象实例 * @var object */public $Config;/** * 错误信息 * @var string */protected $_error = NULL;public function __construct($Config=NULL){$this->Config = is_object($Config)?$Config:self::instance('WeixinApi_Config');$this->_cache = $this->Config->Cache;}/** * 取得对象实例 支持调用类的静态方法 * @param string $class 对象类名 * @param string $method 类的静态方法名 * @return object */public static function instance($class,$args=array()) {$identify   =   $class.md5(serialize($args));if(!isset(WeixinApi::$_instance[$identify])) {if(!class_exists($class)){require $class . ".class.php";}if(class_exists($class)){$arg_str = '';if($args && is_array($args)){foreach ($args as $i=>$arg){/*if(is_object($arg) || is_array($arg)){return WeixinApi::throw_exception("Cann't init class $class instanse with object argument", WXAPI_ERR_CONFIG, array('class' => $class, 'args' => $args), __FILE__, __LINE);}else{$arg_str = "'" . implode("', '", array_map('addslashes', $args)) . "'";}*/if(is_object($arg) || is_array($arg)){$arg_param_name = 'arg_param' . $i;$$arg_param_name = $arg;$arg_str .= ", \${$arg_param_name}";}else{$arg_str .= ", '" . addcslashes($arg, "'") . "'";}}if($arg_str){$arg_str = substr($arg_str, 2);}}elseif($args && is_object($args)){/*return WeixinApi::throw_exception("Cann't init class $class instanse with object argument", WXAPI_ERR_CONFIG, array('class' => $class, 'args' => $args), __FILE__, __LINE);*/$arg_param_name = 'arg_param';$$arg_param_name = $args;$arg_str = "\${$arg_param_name}";}elseif($args){$arg_str = "'" . addcslashes($args, "'") . "'";}$code = "return new " . $class . "(" . $arg_str . ");";$o = eval($code);if(!$o){return WeixinApi::throw_exception( "Cann't init class instanse: $class", WXAPI_ERR_CONFIG, array('class' => $class, 'args' => $args), __FILE__, __LINE);}WeixinApi::$_instance[$identify] = $o;}else{return WeixinApi::throw_exception( "Cann't found class: $class file.", WXAPI_ERR_CONFIG, array('class' => $class, 'args' => $args), __FILE__, __LINE__);}}return self::$_instance[$identify];}public static function throw_exception($message, $code=NULL, $data=NULL, $file=NULL, $line=NULL){if(!class_exists('WeixinApi_Exception')){require 'WeixinApi_Exception.class.php';}// 只有配置错误才再次抛出异常//if($code==WXAPI_ERR_CONFIG){throw new WeixinApi_Exception($message, $code, $data, $file, $line);//}else{//return false;//}}protected function _throw_exception($message, $code=NULL, $data=NULL, $file=NULL, $line=NULL){try{WeixinApi::throw_exception($message, $code, $data, $file, $line);}catch(Exception $e){//$this->_error = $e->getMessage();$this->_setError($e->getMessage());$this->_log($e->__toString(), WXAPI_LOG_ERR);// 只有配置错误才再次抛出异常if($code==WXAPI_ERR_CONFIG){throw $e;}else{return false;}}}public function getError(){return is_array($this->_error)?implode(',', $this->_error):$this->_error;}/** * 设置错误信息 * @param string $error */protected function _setError($error){$this->_error[] = $error;}public function __get($n){if(isset($this->$n)){return $this->$n;}else if(in_array($n, array('Http', 'Cache', 'Log'))){if('Http'==$n && !$this->Config->$n){return $this->_throw_exception("$n is not setted in your config", WXAPI_ERR_CONFIG, array('class'=>$n), __FILE__, __LINE__);}elseif(!$this->Config->$n){// Do Nothing// Disabled Cache or Logreturn false;}if(is_object($this->Config->$n)){return $this->Config->$n;}elseif(is_array($this->Config->$n)){list($callback, $params) = $this->Config->$n;if(!is_array($params)){$params = array($params);}return call_user_func_array($callback, $params);}else{return $this->$n = WeixinApi::instance($this->Config->$n);}}else{return false;}}protected function _check_http_url($url){if(strcasecmp('http', substr($url, 0, 4))){$url = $this->Config->ApiGateway . $url;}return $url;}protected function _check_http_ssl($url){if(!strcasecmp('https://', substr($url, 0, 8))){$this->Http->setSsl();// 指定ssl v3// 2014.09.05 zhongyw 微信API不能指定用ssl v3版本//$this->Http->setOpt(CURLOPT_SSLVERSION, 3);// 指定TLS// 2014.10.31 zhongyw // 微信公众平台将关闭掉SSLv2、SSLv3版本支持,不再支持部分使用SSLv2、 SSLv3或更低版本的客户端调用。请仍在使用这些版本的开发者于11月30日前尽快修复升级。defined('CURL_SSLVERSION_TLSv1') || define('CURL_SSLVERSION_TLSv1', 1); // 兼容PHP<=5.3$this->Http->setOpt(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);}return $url;}protected function _check_http_data($data){return $data;}/** * 发送GET请求 *  * @param string $url链接 * @param string|array $data参数 * @param bool $check是否检查链接和参数 * @return string */public function get($url, $data = null, $check=true) {if ($check) {$url = $this->_check_http_url ( $url );$url = $this->_check_http_ssl ( $url );$data = $this->_check_http_data ( $data );}if(!($return = $this->Http->get($url, $data)) && ($error=$this->Http->getError())){return $this->_throw_exception(  $error, WXAPI_ERR_HTTP, array('url' => $url, 'data' => $data, 'method' => 'get', 'response' => $return) , __FILE__, __LINE__);}return $return;}/** * 发送POST请求 * * @param string $url链接 * @param array $data参数 * @param bool $check是否检查链接和参数 * @return string */public function post($url, $data, $check=true) {if ($check) {$url = $this->_check_http_url ( $url );$url = $this->_check_http_ssl ( $url );$data = $this->_check_http_data ( $data );}// 使用plainPostif(!($return = $this->Http->plainPost($url, $data)) && ($error=$this->Http->getError())){return $this->_throw_exception(  $error, WXAPI_ERR_HTTP, array('url' => $url, 'data' => $data, 'method' => 'post', 'response' => $return) , __FILE__, __LINE__);}return $return;}public function setHttpOption($opt, $val=NULL){if(!$opt){return false;}$options = array();if(!is_array($opt)){$options = array($opt=>$val);}else{$options = $opt;}foreach($options as $opt=>$val){$this->Http->setOpt(constant($opt), $val);}}/** * 运行回调函数 *  * 回调函数支持以下几种格式: * 1、直接函数:funcname,或带参数:array(funcname, params) * 2、静态方法:array(array('WeixinApi', 'methodname'), params) * 3、对象方法:array(Object, 'methodname') 或  array(array(Object, 'methodname'), params) * 4、二次回调,如: * array(array(      array(array('WeixinApi', 'instance'), 'S4WeixinResponse')        , 'run'), '')可以先调用Runder::instance()初始化S4Web实例后,再调用S4Web->apiglog_save()方法执行回调 *  * @param mixed $callback 回调函数 * @param array $extra_params 回调参数 * @return mixed */protected function _run_callback($callback, $extra_params=array(), &$callbackObject=NULL) {$extra_params = is_array ( $extra_params ) ? $extra_params : ($extra_params ? array ($extra_params ) : array ());$params = $extra_params;if(is_object($callback)){return $this->_throw_exception("Object callback must set method", SCRIPT_ERR_CONFIG, array('callback'=>$callback), __FILE__, __LINE__);}else if (is_array ( $callback )) {$func = $callback [0];if (! empty ( $callback [1] )) {if (is_array ( $callback [1] )) {$params = array_merge ( $extra_params, $callback [1] );} else {$params [] = $callback [1];}}if (is_object ( $func )) {$callbackObject = $func;// 注意:此处不需要传$params作为参数return call_user_method_array ( $callback [1], $callback [0], $extra_params );} elseif (is_object ( $callback [0] [0] )) {$callbackObject = $callback [0] [0];return call_user_method_array ( $callback [0] [1], $callback [0] [0], $params);}} else {$func = $callback;}if(is_array($func) && is_array($func[0])){$call = call_user_func_array($func[0][0], is_array($func[0][1])?$func[0][1]:array($func[0][1]));if($call===false){return false;}$func = array($call, $func[1]);}if(is_array($func) && is_object($func[0])){$callbackObject = $func[0];}return call_user_func_array ( $func, $params);}/** * 是否缓存 * @param bool|int $cache true = 启用缓存,false = 不缓存,-1 = 重新生成缓存,3600 = 设置缓存时间为3600秒 * @return WeixinClient */public function cache($cache=true){$this->_cache = $cache;return $this;}public function debug($debug=true){$this->_debug = $debug;return $this;}/** * 写入或者获取缓存 *  * @param string $cache_id 缓存id * @param string $cache_data 缓存数据 * @param int $cache_expire 缓存时间 * @return mixed|boolean */protected function _cache($cache_id, $cache_data=NULL, $cache_expire=NULL){if($this->Config->Cache){// 保存缓存索引if($cache_id && (!is_null($cache_data) && $cache_data!==false && $cache_expire!==false)&& $this->Config->CacheSaveIndex&& strcasecmp($cache_id, $this->Config->CacheSaveIndex)){$index_cache_id = $this->Config->CacheSaveIndex;$index_cache_expire = 315360000; // 永久保存: 3600*24*365*10// 取已有的缓存if(!($index_cache_data=$this->_cache($index_cache_id))){$index_cache_data = array();}// 删除已过期索引$now_time = time();foreach($index_cache_data as $k=>$d){if($d && $d['expire'] && $d['created'] && ($d['created']+$d['expire'])<$now_time){unset($index_cache_data[$k]);}}$index_cache_data[$cache_id] = array('created' => $now_time,'expire' => $cache_expire,);//S4Web::debug_log("\$index_cache_id=$index_cache_id");//S4Web::debug_log("\$index_cache_data=" . print_r($index_cache_data, true));$succ = $this->_cache($index_cache_id, $index_cache_data, $index_cache_expire);$this->_log("Save cache id:  " . $cache_id . ' ' . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_DEBUG);}return $this->_run_callback($this->Config->Cache, array($cache_id, $cache_data, $cache_expire));}else{return false;}}protected function _cache_id($url, $data = NULL, $cache = NULL) {if ($cache && $cache!==true && !is_numeric($cache)){if(is_string ( $cache )) {$cache_id = $cache;} elseif (is_array ( $cache ) && isset($cache['cache_id'])) {$cache_id = $cache ['cache_id'];} elseif (is_object ( $cache ) && isset($cache['cache_id'])) {$cache_id = $cache->cache_id;}// 添加缓存前缀/* 注:由ThinkPHP处理缓存添加前缀:C('DATA_CACHE_PREFIX')if($cache_id && $this->Config->CacheBin){$cache_id = $this->Config->CacheBin . $cache_id;}*/}if (!$cache_id) {$param = '';if ($data && is_array ( $data )) {$param .= http_build_query ( $data );} else {$param .= $data;}$cache_id = md5 ( $this->Config->AppId . $url . $param );//return $cache_id;}return $cache_id;}protected function _cache_expire($url, $data=NULL, $cache=NULL){if(!$cache){return 0;}elseif(is_numeric($cache) && $cache>0){$cache_expire = $cache;}elseif (is_array($cache) && isset($cache['cache_expire'])){$cache_expire = $cache['cache_expire'];}elseif (is_object($cache) && isset($cache->cache_expire)){$cache_expire = $cache->cache_expire;}return $cache_expire?$cache_expire:$this->Config->CacheExpire;}/** * 判断是否强制刷新缓存 * @param unknown $url * @param string $data * @param string $cache * @return bool */protected function _cache_refresh($url, $data=NULL, $cache=NULL){$cache_refresh = false;if ($cache && $cache!==true && !is_numeric($cache)){if (is_array ( $cache ) && isset($cache['cache_refresh'])) {$cache_refresh = $cache ['cache_refresh'];} elseif (is_object ( $cache ) && isset($cache['cache_refresh'])) {$cache_refresh = $cache->cache_refresh;}}return $cache_refresh;}/** * 写入日志 * @param string $message * @param string $level * @return boolean */protected function _log($message, $level=WXAPI_LOG_INFO){if($this->Config->Log){static $aLogLevelMaps = array(WXAPI_LOG_EMERG => 0,WXAPI_LOG_ALERT => 1,WXAPI_LOG_CRIT => 2,WXAPI_LOG_ERR => 3,WXAPI_LOG_WARN => 4,WXAPI_LOG_NOTICE => 5,WXAPI_LOG_INFO => 6,WXAPI_LOG_DEBUG => 7,);if($this->Config->LogLevel && $aLogLevelMaps[$level]>$aLogLevelMaps[$this->Config->LogLevel]){return false;}return $this->_run_callback($this->Config->Log, array($message, $level));}else{return false;}}/** * 写入支付日志 * @param string $message * @param string $level * @return boolean */protected function _logpay($message, $level=WXAPI_LOG_INFO){if($this->Config->PayLog){static $aLogLevelMaps = array(WXAPI_LOG_EMERG => 0,WXAPI_LOG_ALERT => 1,WXAPI_LOG_CRIT => 2,WXAPI_LOG_ERR => 3,WXAPI_LOG_WARN => 4,WXAPI_LOG_NOTICE => 5,WXAPI_LOG_INFO => 6,WXAPI_LOG_DEBUG => 7,);if($this->Config->PayLogLevel && $aLogLevelMaps[$level]>$aLogLevelMaps[$this->Config->PayLogLevel]){return false;}return $this->_run_callback($this->Config->PayLog, array($message, $level));}else{return false;}}/** * 判断是否微信媒体文件id * @param string $mediaid * @return boolean */protected function _isMediaId($mediaid){// aSeyL8Ym_0mu3u1qeHixvCe54XU-b8teahDXHdYl1tOB_1mgyUxJgj0A8CJZRNzl//return is_file($mediaid)?false:true;if(preg_match('/\.[a-z0-9]{1,4}$/i', $mediaid)){return false;}else{return true;}}/** * 清空微信API所有缓存数据 *  * @return bool */public function clearCache(){$this->_log("START Clear Cache...", WXAPI_LOG_INFO);if(!$this->Config->Cache || !$this->Config->CacheSaveIndex){$this->_log("Skipped, Cache or Save Cache index is disabled!", WXAPI_LOG_INFO);return false;}// 取缓存的索引$index_cache_id = $this->Config->CacheSaveIndex;if(!($index_cache_data=$this->_cache($index_cache_id))){$this->_log("Skipped, Cache Index is Empty!", WXAPI_LOG_INFO);return false;}$clear_succ = true;foreach($index_cache_data as $cache_id=>$d){$succ = $this->_cache($cache_id, false, false);$this->_log("Delete cache id: " . $cache_id . " " . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_DEBUG);$clear_succ = $succ && $clear_succ;}// 删除索引自身$succ = $this->_cache($index_cache_id, false, false);$clear_succ = $succ && $clear_succ;$this->_log("Delete Index Cache Id: " . $index_cache_id . " " . ($succ?'Succ':'Failed') . '!', WXAPI_LOG_INFO);$this->_log("END Clear Cache, "  . ($clear_succ?'Succ':'Failed') . '!', WXAPI_LOG_INFO);return $clear_succ;}}

WeixinReceive.class.php  微信接口接收类

<?php/** * 微信API 接收接口 *  * PHP version 5 *  * @categoryLib * @package     COM * @subpackage  GZNC * @author      zhongyiwen * @version     SVN: $Id: WeixinReceive.class.php 10 2013-10-08 01:34:05Z zhongyw $ */class WeixinReceive extends WeixinApi{protected $_rawget = NULL;protected $_rawpost = NULL;protected $_postData = NULL;protected $_getData = NULL;protected $_postObj = NULL; // 兼容旧程序protected $_getObj = NULL; // 兼容旧程序protected $_responseMsg;protected $_responseObj;/** * 消息体加密模式 * @var string */protected $_msgEncodingMode = NULL;/** * 消息加密私钥 * @var string */protected $_msgEncodingKey = NULL;/** * 原始加密消息 * @var string */protected $_msgEncrypt = NULL;/** * 解密后的消息原文 * @var string */protected $_msgDecrypt = NULL;/** * 解密后的消息数组 * @var array */protected $_msgData = NULL;/** * 检查消息签名 * @param object $getObj * @return boolean 成功返回true,失败返回false */protected function _checkSignature($getData){$signature = $getData['signature'];$timestamp = $getData['timestamp'];$nonce = $getData['nonce'];$token = $this->Config->AppToken;$tmpArr = array($token, $timestamp, $nonce);sort($tmpArr, SORT_STRING);$tmpStr = implode( $tmpArr );$tmpStr = sha1( $tmpStr );if( $tmpStr == $signature ){return true;}else{return false;}}/** * 判断消息加密模式 * @param object $getObj * @param object $postObj * @return string|false */protected function _checkEncodingMode($getData, $postData){if(!is_null($this->_msgEncodingMode)){return $this->_msgEncodingMode;}if(empty($getData['encrypt_type']) || !strcasecmp($getData['encrypt_type'], 'raw')){$this->_msgEncodingMode = WXAPI_APP_ENCODING_CLEAR;}elseif(strlen($getData['msg_signature']) && !strcasecmp($getData['encrypt_type'], 'aes')){if(!empty($postData['MsgType']) && !empty($postData['FromUserName'])){$this->_msgEncodingMode =  WXAPI_APP_ENCODING_COMPAT;}else{$this->_msgEncodingMode =  WXAPI_APP_ENCODING_SECURE;}}else{$this->_msgEncodingMode = false;}return $this->_msgEncodingMode;}protected function _postData(){if(!is_null($this->_postData)){return $this->_postData;}$this->_rawpost = file_get_contents("php://input");if(!empty($this->_rawpost)){$postObj = simplexml_load_string(trim($this->_rawpost), 'SimpleXMLElement', LIBXML_NOCDATA);$this->_postData = WeixinApi_Kit::get_object_vars_final($postObj);// 2015.3.3 zhongyw 必须从postData转为object// simplexml_load_string()返回的为SimpleXMLElement Object,而不是stdClass Object, // 用is_string($postObj->FromUserName)判断时会返回false$this->_postObj = (object) $this->_postData; // 兼容旧程序}else{$this->_postData = false;$this->_postObj = false;}return $this->_postData;}protected function _getData(){if(!is_null($this->_getData)){return $this->_getData;}$this->_rawget = $_GET;if ($this->_rawget) {$getData = array ('signature' => $_GET ["signature"],'timestamp' => $_GET ["timestamp"],'nonce' => $_GET ["nonce"] );if (isset ( $_GET ['echostr'] )) { $getData ['echostr'] = $_GET ['echostr']; }if (isset ( $_GET ['encrypt_type'] )) { $getData ['encrypt_type'] = $_GET ['encrypt_type']; }if (isset ( $_GET ['msg_signature'] )) { $getData ['msg_signature'] = $_GET ['msg_signature']; }$this->_getData = $getData;// 兼容旧程序$this->_getObj = ( object ) $getData;}else{$this->_getData = false;$this->_getObj = false;}return $this->_getData;}/** * 运行接收 * @param mixed $responseObj 响应对象,可以传回调函数 */public function run($responseObj=NULL){$request_url = WeixinApi_Kit::get_request_url();$client_ip = WeixinApi_Kit::get_client_ip();$this->_log("--------------------------------------------------------");$this->_log("Received new request from {$client_ip}", WXAPI_LOG_INFO);$this->_log("Request URL: {$request_url}", WXAPI_LOG_INFO);$this->_log("Get: " . print_r($_GET, true), WXAPI_LOG_DEBUG);$this->_log("Post: " . print_r($_POST, true), WXAPI_LOG_DEBUG);$getData = $this->_getData();// 验证签名if(!$getData || !$this->_checkSignature($getData)){// invalid request// log it? or do other things$this->_log("Bad Request, Check Signature Failed!", WXAPI_LOG_ERR);return false;}$postData = $this->_postData();// 消息体是否为空?if(false==$postData){$this->_log("Msg Body is Empty!", WXAPI_LOG_ERR);return false;}$this->_log ( "rawPost: " . $this->_rawpost, WXAPI_LOG_DEBUG );$this->_log ( "postData: " . print_r ( $postData, true ), WXAPI_LOG_DEBUG );// 判断消息加密模式$encodingMode = $this->_checkEncodingMode($getData, $postData);if(false==$encodingMode){$this->_log("Check Msg Encoding Mode Failed!", WXAPI_LOG_ERR);return false;}$this->_log("MSG Encoding Mode is: " . $encodingMode, WXAPI_LOG_DEBUG);// 解密消息switch($encodingMode){case WXAPI_APP_ENCODING_SECURE:if(false===$this->_decodeMessage()){$this->_log("Bad Request, Decode Message Failed!", WXAPI_LOG_ERR);return false;}else{$this->_log("Decode Message Succ!", WXAPI_LOG_INFO);}break;case WXAPI_APP_ENCODING_COMPAT:if(false===$this->_decodeMessage()){$this->_log("Decode Message Failed!", WXAPI_LOG_ERR);}else{$this->_log("Decode Message Succ!", WXAPI_LOG_INFO);}break;default:// DO NOTHINGbreak;}if (empty ( $responseObj )) {$responseObj = $this->Config->Response;}// get response$response = $this->_responseMsg = $this->_response ( $responseObj );if ($response === false) {$this->_log ( "No Reponse Sent!", WXAPI_LOG_INFO );// save message$this->_saveMessage ();return false;}// echo responseecho $response;flush ();// log$this->_log ( "Succ! Send Response: " . $response, WXAPI_LOG_INFO );// save message$this->_saveMessage ();// save response$this->_saveResponse ( $this->_responseObj );return true;}protected function _response($responseObj){if(is_object($responseObj)){$callback = array($responseObj, 'run');}else{$callback = $responseObj;}return $this->_run_callback($callback, array($this), $this->_responseObj);}/** * 保存消息 * @return mixed|boolean */protected function _saveMessage(){if($this->Config->SaveMessage){return $this->_run_callback($this->Config->SaveMessage, array($this));}else{return false;}}/** * 保存回复 * @param mixed $responseObj * @return mixed|boolean */protected function _saveResponse($responseObj){if($this->Config->SaveResponse){return $this->_run_callback($this->Config->SaveResponse, array($this, $responseObj));}else{return false;}}public function __get($c){if(substr($c, 0, 1)!='_'){if(in_array($c, array('Http'))){return parent::__get($c);}else{$n = '_' . $c;return $this->$n;}}}public function __isset($c){if(substr($c, 0, 1)!='_'){if(in_array($c, array('Http'))){return parent::__isset($c);}else{$n = '_' . $c;return isset($this->$n);}}}/** * 获取openid * @return string */public function parse_openid(){if(isset($this->_postObj->FromUserName) && !empty($this->_postObj->FromUserName)){return $this->_postObj->FromUserName;}else{return null;}}/** * 对密文消息进行解密 * @param string $msg_encrypt 需要解密的密文 * @param string $encodingkey 加密私钥 * @return string|false 解密得到的明文,失败返回flase */protected function _decryptMsg($msg_encrypt, $encodingkey=NULL){$AESKey = base64_decode(($encodingkey?$encodingkey:$this->Config->AppEncodingAESKey) . "=");//使用BASE64对需要解密的字符串进行解码$ciphertext_dec = base64_decode($msg_encrypt);$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');if(false===$module){return $this->_throw_exception("Cann't open an encryption descriptor", WXAPI_ERR_BAD_ENCRYPT, $msg_encrypt, __FILE__, __LINE__);}$iv = substr($AESKey, 0, 16);$init = mcrypt_generic_init($module, $AESKey, $iv);if(false===$init){return $this->_throw_exception("Cann't initialize buffers for encryption", WXAPI_ERR_BAD_ENCRYPT, array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif(-3==$init){return $this->_throw_exception("the key length was incorrect", WXAPI_ERR_BAD_ENCRYPT, array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif(-4==$init){return $this->_throw_exception("there was a memory allocation problem", WXAPI_ERR_BAD_ENCRYPT, array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif($init<0){return $this->_throw_exception("an unknown error occurred when initialize buffers for encryption", WXAPI_ERR_BAD_ENCRYPT, array('msg_encrypt' => $msg_encrypt, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}//解密$decrypted = mdecrypt_generic($module, $ciphertext_dec);mcrypt_generic_deinit($module);mcrypt_module_close($module);if(!$decrypted){return "";}// 去除补位字符$result = WeixinApi_Kit::pkcs7_decode( $decrypted, 32 );// 去除16位随机字符串,网络字节序和AppIdif (strlen ( $result ) < 16){return "";}$content = substr ( $result, 16, strlen ( $result ) );$len_list = unpack ( "N", substr ( $content, 0, 4 ) );$xml_len = $len_list [1];$xml_content = substr ( $content, 4, $xml_len );return $xml_content;}/** * 返回微信发过来的加密消息 * @return string */protected function _getMsgEncrypt(){if($this->_msgEncrypt){return $this->_msgEncrypt;}if(!empty($this->_getData['echostr'])){$this->_msgEncrypt = $this->_getData['echostr'];}else{$this->_msgEncrypt = $this->_postData['Encrypt'];}return $this->_msgEncrypt;}protected function _msgData() {if (! is_null ( $this->_msgData )) {return $this->_msgData;}$this->_msgData = false;$msg_encrypt = $this->_getMsgEncrypt ();if ($msg_encrypt) {if(!empty($this->_getData['echostr'])){$this->_msgData = array();}else{$xml_content = false;$encodingkey = $this->Config->AppEncodingAESKey;if($encodingkey){$xml_content = $this->_decryptMsg ( $msg_encrypt, $encodingkey );if($xml_content){$this->_msgEncodingKey = $encodingkey;$this->_log("AES Key: Decode Succ! ", WXAPI_LOG_DEBUG);}else{$this->_log("AES Key: Decode Failed!", WXAPI_LOG_DEBUG);}}else{$this->_log("Encoding AES Key is empty", WXAPI_LOG_DEBUG);}// 尝试旧密钥if(!$xml_content && ($encodingkey = $this->Config->AppEncodingOLDKey)){$xml_content = $this->_decryptMsg ( $msg_encrypt, $encodingkey );$this->_log("Try to apply OLD Key", WXAPI_LOG_DEBUG);if($xml_content){$this->_msgEncodingKey = $encodingkey;$this->_log("OLD Key: Decode Succ! ", WXAPI_LOG_DEBUG);}else{$this->_log("OLD Key: Decode Failed!", WXAPI_LOG_DEBUG);}}if($xml_content){$this->_msgDecrypt = $xml_content;import ( 'COM.GZNC.WeixinApi.WeixinApi_Kit' );$postObj = simplexml_load_string ( $xml_content, 'SimpleXMLElement', LIBXML_NOCDATA );$this->_msgData = WeixinApi_Kit::get_object_vars_final ( $postObj );$this->_log('Decoded MSG XML: ' . $this->_msgDecrypt, WXAPI_LOG_DEBUG);$this->_log('Decoded MSG DATA: ' . print_r($this->_msgData, true), WXAPI_LOG_DEBUG);}}}return $this->_msgData;}protected function _decodeMessage(){if(false===$this->_msgData()){return false;}// 兼容旧程序$this->_postData = array_merge($this->_postData, $this->_msgData);$this->_postObj = (object) $this->_postData;return true;}}

WeixinResponse.class.php  微信接口响应类

<?php/** * 微信API 响应接口 *  * PHP version 5 *  * @categoryLib * @package     COM * @subpackage  GZNC * @author      zhongyiwen * @version     SVN: $Id: WeixinResponse.class.php 10 2013-10-08 01:34:05Z zhongyw $ */class WeixinResponse extends WeixinApi{const AS_ECHO = 'ECHO'; // ECHO消息const AS_EMPTY = 'EMPTY'; // 空消息const AS_COMMAND = 'COMMAND'; // 指令消息const AS_SUBSCRIBE = 'SUBSCRIBE'; // 订阅消息const AS_UNSUBSCRIBE = 'UNSUBSCRIBE'; // 取消订阅消息const AS_SCAN = 'SCAN'; // 扫描消息const AS_CLICK = 'CLICK'; // 点击菜单拉取消息事件const AS_VIEW = 'VIEW'; // 点击菜单跳转链接事件const AS_SCANCODE_PUSH = 'SCANCODE_PUSH'; // 扫码推事件const AS_SCANCODE_WAITMSG = 'AS_SCANCODE_WAITMSG'; // 扫码推事件且弹出“消息接收中”提示框const AS_PIC_SYSPHOTO = 'pic_sysphoto'; // 弹出系统拍照发图const AS_PIC_PHOTO_OR_ALBUM = 'PIC_PHOTO_OR_ALBUM'; // 弹出拍照或者相册发图const AS_PIC_WEIXIN = 'pic_weixin'; // 弹出微信相册发图器const AS_LOCATION_SELECT = 'location_select'; // 弹出地理位置选择器const AS_LOCATION = 'LOCATION'; // 地理位置消息const AS_MESSAGE = 'MESSAGE'; // 普通消息const AS_MASSSENDJOBFINISH = 'MASSSENDJOBFINISH'; // 群发消息const AS_TEMPLATESENDJOBFINISH = 'TEMPLATESENDJOBFINISH'; // 模板消息const AS_UNKNOWN = 'UNKNOWN'; // 未知消息protected $_responseType; // 响应类型,对应上面的常量protected $_responseKey; // 响应值,如菜单点击,值为菜单Keyprotected $_responseContent; // 响应的原始数据protected $_responseMessage; // 响应输出消息数据(数组)protected $_responseMedia; // 响应输出的媒体文件/** * 运行 * @param object $receiveObj 接收对象 */public function run($receiveObj){try{return $this->_dispatchResponse($receiveObj);}catch (Exception $e){return false;}}/** * 分发响应 * 判断响应类型,并返回相应的响应内容 * @param object $receiveObj 接收对象 */protected function _dispatchResponse($receiveObj){// 可以直接判定消息类别,不需要依赖外部配置数据if($this->_isEcho($receiveObj)){$this->_responseType = WeixinResponse::AS_ECHO;$this->_responseKey = '';$this->_responseContent = $this->_responseEcho($receiveObj);$msgFormat = 'raw';}elseif($this->_isEmpty($receiveObj)){$this->_responseType = WeixinResponse::AS_EMPTY;$this->_responseKey = '';$this->_responseContent = $this->_responseEmpty($receiveObj);$msgFormat = 'raw';}elseif(($key=$this->_isView($receiveObj))){$this->_responseType = WeixinResponse::AS_VIEW;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseView($receiveObj);$msgFormat = 'xml';}// 事件,需要加载外部配置数据// 注意:要先判断订阅事件,避免缓存误判elseif(($key=$this->_isSubscribe($receiveObj))){$this->_responseType = WeixinResponse::AS_SUBSCRIBE;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseSubscribe($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isUnsubscribe($receiveObj))){$this->_responseType = WeixinResponse::AS_UNSUBSCRIBE;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseUnsubscribe($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isClick($receiveObj))){ $this->_responseType = WeixinResponse::AS_CLICK;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseClick($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isScanCodePush($receiveObj))){ $this->_responseType = WeixinResponse::AS_SCANCODE_PUSH;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseScancodePush($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isScanCodeWaitMsg($receiveObj))){ $this->_responseType = WeixinResponse::AS_SCANCODE_WAITMSG;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseScanCodeWaitMsg($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isPicSysPhoto($receiveObj))){ $this->_responseType = WeixinResponse::AS_PIC_SYSPHOTO;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responsePicSysPhoto($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isPicPhotoOrAlbum($receiveObj))){ $this->_responseType = WeixinResponse::AS_PIC_PHOTO_OR_ALBUM;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responsePicPhotoOrAlbum($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isPicWeixin($receiveObj))){ $this->_responseType = WeixinResponse::AS_PIC_WEIXIN;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responsePicWeixin($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isLocationSelect($receiveObj))){ $this->_responseType = WeixinResponse::AS_LOCATION_SELECT;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseLocationSelect($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isScan($receiveObj))){$this->_responseType = WeixinResponse::AS_SCAN;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseScan($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isLocation($receiveObj))){$this->_responseType = WeixinResponse::AS_LOCATION;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseLocation($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isMassSendJobFinish($receiveObj))){$this->_responseType = WeixinResponse::AS_MASSSENDJOBFINISH;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseMassSendJobFinish($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isTemplateSendJobFinish($receiveObj))){$this->_responseType = WeixinResponse::AS_TEMPLATESENDJOBFINISH;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseTemplateSendJobFinish($receiveObj);$msgFormat = 'xml';}// 文本消息elseif(($key=$this->_isCommand($receiveObj))){$this->_responseType = WeixinResponse::AS_COMMAND;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseCommand($receiveObj);$msgFormat = 'xml';}elseif(($key=$this->_isMessage($receiveObj))){$this->_responseType = WeixinResponse::AS_MESSAGE;$this->_responseKey = $key===true?'':$key;$this->_responseContent = $this->_responseMessage($receiveObj);$msgFormat = 'xml';}// 未知,可能是新类型else{$this->_responseType = WeixinResponse::AS_UNKNOWN;$this->_responseKey = '';$this->_responseContent = $this->_responseUnknown($receiveObj);$msgFormat = 'raw';}if($this->_responseContent===false){// 出错:未配置对事件或消息的响应return false;}$this->_log("ResponseType: " . $this->_responseType, WXAPI_LOG_DEBUG);$this->_log("ResponseKey: " . $this->_responseKey, WXAPI_LOG_DEBUG);$this->_log("ResponseContent: " . print_r($this->_responseContent, true), WXAPI_LOG_DEBUG);$this->_responseMessage = $this->_createResponseMessage($receiveObj,$this->_responseContent,$msgFormat        );$this->_log("Generated Response Message: " . print_r($this->_responseMessage, true), WXAPI_LOG_DEBUG);return $this->_responseMessage['MsgContent'];}/** * 是否空消息 * @param object $receiveObj 接收对象 * @return boolean */protected function _isEmpty($receiveObj){// 注意:empty($receiveObj->postObj)即使非空也返回true// When using empty() on inaccessible object properties, the __isset() overloading method will be called, if declared.$postObj = $receiveObj->postObj;return empty($postObj)?true:false;}/** * 是否验证消息 * @param object $receiveObj 接收对象 * @return boolean */protected function _isEcho($receiveObj){return isset($receiveObj->getObj->echostr) && $receiveObj->getObj->echostr;}/** * 是否指令消息 * @param object $receiveObj 接收对象 * @return string|false */protected function _isCommand($receiveObj){$aMsgTypes = array('text');if(!in_array($receiveObj->postObj->MsgType, $aMsgTypes, false)){return false;}$command = trim($receiveObj->postObj->Content);if(($c=$this->Config->Command) && !empty($c[$command])){return $command;}else{return false;}}/** * 是否事件消息 * @param object $receiveObj 接收对象 * @return string|false */protected function _isEvent($receiveObj){return !strcasecmp($receiveObj->postObj->MsgType, 'event');}/** * 是否订阅事件 * @param object $receiveObj 接收对象 * @return string|false */protected function _isSubscribe($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'subscribe')){return isset($receiveObj->postObj->EventKey) && ($key=(string)$receiveObj->postObj->EventKey)?$key:true;}else{return false;}}/** * 是否取消订阅事件 * @param object $receiveObj 接收对象 * @return boolean */protected function _isUnsubscribe($receiveObj){return $this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'unsubscribe');}/** * 是否扫描事件 * @param object $receiveObj 接收对象 * @return array|false */protected function _isScan($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scan')){return array('EventKey' => $receiveObj->postObj->EventKey,'Ticket' => $receiveObj->postObj->Ticket,);}else{return false;}}/** * 是否地理位置消息 * @param object $receiveObj 接收对象 * @return array|false */protected function _isLocation($receiveObj){if(!strcasecmp($receiveObj->postObj->MsgType, 'location')){return array('Latitude' => $receiveObj->postObj->Location_Y,'Longitude' => $receiveObj->postObj->Location_X,'Precision' => $receiveObj->postObj->Scale,'Label' => $receiveObj->postObj->Label,);}elseif($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'LOCATION')){return array('Latitude' => $receiveObj->postObj->Latitude,'Longitude' => $receiveObj->postObj->Longitude,'Precision' => $receiveObj->postObj->Precision,'Label' => '',);}else{return false;}}/** * 是否点击菜单拉取消息事件 * @param object $receiveObj 接收对象 * @return string|false */protected function _isClick($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'CLICK')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否点击菜单跳转链接事件 * @param object $receiveObj 接收对象 * @return string|false */protected function _isView($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'VIEW')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:扫码推事件 * @param object $receiveObj 接收对象 * @return string|false */protected function _isScanCodePush($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scancode_push')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:扫码推事件且弹出“消息接收中”提示框 * @param object $receiveObj 接收对象 * @return string|false */protected function _isScanCodeWaitMsg($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'scancode_waitmsg')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:弹出系统拍照发图 * @param object $receiveObj 接收对象 * @return string|false */protected function _isPicSysPhoto($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_sysphoto')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:弹出拍照或者相册发图 * @param object $receiveObj 接收对象 * @return string|false */protected function _isPicPhotoOrAlbum($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_photo_or_album')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:弹出微信相册发图器 * @param object $receiveObj 接收对象 * @return string|false */protected function _isPicWeixin($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'pic_weixin')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否:弹出地理位置选择器 * @param object $receiveObj 接收对象 * @return string|false */protected function _isLocationSelect($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'location_select')){return $receiveObj->postObj->EventKey;}else{return false;}}/** * 是否普通消息 * @param object $receiveObj 接收对象 * @return string|false */protected function _isMessage($receiveObj){$aMsgTypes = array('text', 'image', 'voice', 'video', 'link','shortvideo');return in_array($receiveObj->postObj->MsgType, $aMsgTypes, false)?$receiveObj->postObj->MsgType:false;}/** * 是否群发消息通知事件 * @param object $receiveObj 接收对象 * @return string|false 返回消息id */protected function _isMassSendJobFinish($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'MASSSENDJOBFINISH')){return $receiveObj->postObj->MsgID;/*return array('MsgID' => $receiveObj->postObj->MsgID,'Status' => $receiveObj->postObj->Status,'TotalCount' => $receiveObj->postObj->TotalCount,'FilterCount' => $receiveObj->postObj->FilterCount,'SentCount' => $receiveObj->postObj->SentCount,'ErrorCount' => $receiveObj->postObj->ErrorCount,);*/}else{return false;}}/** * 是否模板消息通知事件 * @param object $receiveObj 接收对象 * @return string|false 返回消息id */protected function _isTemplateSendJobFinish($receiveObj){if($this->_isEvent($receiveObj) && !strcasecmp($receiveObj->postObj->Event, 'TEMPLATESENDJOBFINISH')){return $receiveObj->postObj->MsgID;}else{return false;}}/** * 创建响应消息 * @param object $receiveObj 接收对象 * @param mixed $responseContent 原始响应内容 * @param string $msgFormat 消息格式 * @return array|false */protected function _createResponseMessage($receiveObj, $responseContent, $msgFormat='xml'){if(is_array($responseContent) && !empty($responseContent['Callback'])){$data = $this->_run_callback($responseContent['Callback'], array($receiveObj, $this));if($data===false){$this->_log("Run Callback : " . print_r($responseContent['Callback'], true) . " Failed", WXAPI_LOG_ERR);return false;}if(is_array($data)){$t = $data;$responseContent = array('MsgType' => $t['MsgType'],'Content' => $t['Content'],);}else{$responseContent['Content'] = $data;if($responseContent['MsgType']=='callback'){$responseContent['MsgType'] = 'text';}}}if(is_string($responseContent)){$responseContent = array('MsgType' => 'text','Content' => $responseContent,);}elseif(!$responseContent['MsgType']){$responseContent['MsgType'] = 'text';}if(!$responseContent['Content'] && !strlen($responseContent['Content'])&& strcasecmp('transfer_customer_service', $responseContent['MsgType']) // 转发客服消息,允许Content为空){return false;}// 预处理消息if($msgFormat=='xml'){$responseContent = $this->_preprocessResponseMedia($responseContent);}$msgContentOutput = self::generateMessage($receiveObj->postData['FromUserName'], $receiveObj->postData['ToUserName'], $responseContent);// 根据加密类型生成相应响应消息switch($receiveObj->msgEncodingMode){// 兼容模式case WXAPI_APP_ENCODING_COMPAT:// 未正确解密,使用明文返回if(empty($receiveObj->msgEncodingKey)){$msgEncoding = WXAPI_APP_ENCODING_CLEAR;$this->_log("Encoding Response Msg in Clear Mode", WXAPI_LOG_DEBUG);break;}// 安全模式case WXAPI_APP_ENCODING_SECURE:$msgContentOriginal = $msgContentOutput;$msgContentOutput = self::_encrypt_response($msgContentOutput, $receiveObj->msgEncodingKey);$msgEncoding = WXAPI_APP_ENCODING_SECURE;$this->_log("Encoding Response Msg in Secure Mode", WXAPI_LOG_DEBUG);break;// 明文模式case WXAPI_APP_ENCODING_CLEAR:default:$msgEncoding = WXAPI_APP_ENCODING_CLEAR;$this->_log("Encoding Response Msg In Clear Mode", WXAPI_LOG_DEBUG);break;}return array('MsgType' => $responseContent['MsgType'],'MsgFormat' => $msgFormat,'MsgContent' => $msgFormat=='xml'?$msgContentOutput: $responseContent['Content'],'MsgOriginal' => $msgContentOriginal?$msgContentOriginal:NULL,'MsgEncoding' => $msgEncoding,'RawContent' => $responseContent['Content']);}/** * 错误响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseError($receiveObj){return "Error";}/** * 未知响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseUnknown($receiveObj){//return "Unknown";}/** * 验证响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseEcho($receiveObj){return $receiveObj->getObj->echostr;}/** * 空消息响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseEmpty($receiveObj){return "Empty";}/** * 指令响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseCommand($receiveObj){$command = trim($receiveObj->postObj->Content);$settings = $this->Config->getConfig('Command');if(!isset($settings[$command])){return $this->_throw_exception("Command {$command} not configured", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);}$content = $settings[$command];return $content;}/** * 事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseEvent($receiveObj){$event = strtolower($receiveObj->postObj->Event);$settings = $this->Config->getConfig('Event');if(!isset($settings[$event])){return $this->_throw_exception("Miss resoponse for Event {$event}", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);}if(isset($settings[$event]['MsgType'])){$content = $settings[$event];}elseif(isset($receiveObj->postObj->EventKey)){$eventkey = (string) $receiveObj->postObj->EventKey;if(!isset($settings[$event][$eventkey])){return $this->_throw_exception("Miss response for Event {$event}, Key {$eventkey}", WXAPI_ERR_MISS_RESPONSE, '', __FILE__, __LINE__);}$content = $settings[$event][$eventkey];}return $content;}/** * 订阅事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseSubscribe($receiveObj){return $this->_responseEvent($receiveObj);}/** * 取消订阅事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseUnsubscribe($receiveObj){return $this->_responseEvent($receiveObj);}/** * 扫描事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseScan($receiveObj){return $this->_responseEvent($receiveObj);}/** * 地理位置消息响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseLocation($receiveObj){if(!strcasecmp($receiveObj->postObj->MsgType, 'event')){return $this->_responseEvent($receiveObj);}else if(!strcasecmp($receiveObj->postObj->MsgType, 'location')){// 普通位置消息// @todo 处理接收到的普通位置消息}}/** * 点击菜单拉取消息事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseClick($receiveObj){return $this->_responseEvent($receiveObj);}/** * 点击菜单跳转链接事件响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseView($receiveObj){//return $this->_responseEvent($receiveObj);}/** * 响应:扫码推事件 * @param object $receiveObj 接收对象 * @return string */protected function _responseScanCodePush($receiveObj){return $this->_responseEvent($receiveObj);}/** * 响应:扫码推事件且弹出“消息接收中”提示框 * @param object $receiveObj 接收对象 * @return string */protected function _responseScanCodeWaitMsg($receiveObj){return $this->_responseEvent($receiveObj);}/** * 响应:弹出系统拍照发图 * @param object $receiveObj 接收对象 * @return string */protected function _responsePicSysPhoto($receiveObj){return $this->_responseEvent($receiveObj);}/** * 响应:弹出拍照或者相册发图 * @param object $receiveObj 接收对象 * @return string */protected function _responsePicPhotoOrAlbum($receiveObj){return $this->_responseEvent($receiveObj);}/** * 响应:弹出微信相册发图器 * @param object $receiveObj 接收对象 * @return string */protected function _responsePicWeixin($receiveObj){return $this->_responseEvent($receiveObj);}/** * 响应:弹出地理位置选择器 * @param object $receiveObj 接收对象 * @return string */protected function _responseLocationSelect($receiveObj){return $this->_responseEvent($receiveObj);}/** * 普通消息响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseMessage($receiveObj){// write your code, such as save message and remind customer service// 通关密语if(($msg=$this->_responseArgot($receiveObj))!==false){return $msg;}// 转发客服消息到微信多客服系统elseif($this->Config->TransferCustomerService){return array('MsgType' => 'transfer_customer_service',);}}/** * 响应暗语 *  * @param object $receiveObj * @return false|string 返回false表示非暗语处理,可以由其它逻辑处理 */protected function _responseArgot($receiveObj){if(!isset($receiveObj->postObj->Content) || !($msg=$receiveObj->postObj->Content)){return false;}$msg = trim($msg);if(defined('WXAPI_ARGOT_WHO_AM_I') && WXAPI_ARGOT_WHO_AM_I && !strcasecmp(WXAPI_ARGOT_WHO_AM_I, $msg)){return "OH LORD,\nMY 4susername is " . $this->Config->AppName . ",\nAppId: " . $this->Config->AppId . ",\n Server: " . $_SERVER['HTTP_HOST'] ." .";}elseif(defined('WXAPI_ARGOT_DESTORY_SESSION') && WXAPI_ARGOT_DESTORY_SESSION && !strcasecmp(WXAPI_ARGOT_DESTORY_SESSION, $msg)){$openid = $receiveObj->parse_openid();if($openid && class_exists('WeixinUserModel') && method_exists('WeixinUserModel', 'destroy_session')){$oWeixinUserModel = new WeixinUserModel();$succ = $oWeixinUserModel->destroy_session($openid);return $succ?"Your session has been destoryed Successfully!":"Failed destroy your session!";}else{return false;}}else{return false;}}/** * 群发消息通知响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseMassSendJobFinish($receiveObj){// write your code, such as save message and remind customer service}/** * 模板消息通知响应 * @param object $receiveObj 接收对象 * @return string */protected function _responseTemplateSendJobFinish($receiveObj){// write your code, such as save message and remind customer service}/** * 预处理多媒体文件 * 可以根据消息类型,调用微信接口,将消息中的图片、音频等多媒体文件上传到微信服务器, * 得到MediaId,并替换掉原来的多媒体文件 * @param mixed $Content * @return mixed */protected function _preprocessResponseMedia($Content){if(!is_array($Content) || empty($Content['MsgType'])){return $Content;}$msgtype = strtolower($Content['MsgType']);switch ($msgtype){case 'image':$mediaField = 'MediaId';if(is_string($Content['Content']) && !$this->_isMediaId($Content['Content'])){$mediaFile = $Content['Content'];$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$mediaId = $oClient->upload_media($mediaFile, 'image');$Content['Content'] = $mediaId;}else{$mediaId = $Content['Content'];$mediaFile = '';}$this->_responseMedia[$mediaField] = array($mediaId, 'image', $mediaFile);break;case 'voice':$mediaField = 'MediaId';if(is_string($Content['Content']) && !$this->_isMediaId($Content['Content'])){$mediaFile = $Content['Content'];$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$mediaId = $oClient->upload_media($mediaFile, 'voice');$Content['Content'] = $mediaId;}else{$mediaId = $Content['Content'];$mediaFile = '';}$this->_responseMedia[$mediaField] = array($mediaId, 'voice', $mediaFile);break;case 'video':$mediaField = 'MediaId';if(!$this->_isMediaId($Content['Content']['MediaId'])){$mediaFile = $Content['Content']['MediaId'];$mediaField = 'MediaId';$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$mediaId = $oClient->upload_media($mediaFile, 'video');$Content['Content']['MediaId'] = $mediaId;}else{$mediaId = $Content['Content']['MediaId'];$mediaFile = '';}$this->_responseMedia[$mediaField] = array($mediaId, 'video', $mediaFile);$thumbMediaField = 'ThumbMediaId';if(!$this->_isMediaId($Content['Content']['ThumbMediaId'])){$thumbMediaFile = $Content['Content']['ThumbMediaId'];$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');$Content['Content']['ThumbMediaId'] = $thumbMediaId;}else{$thumbMediaId = $Content['Content']['ThumbMediaId'];$thumbMediaFile = '';}$this->_responseMedia[$thumbMediaField] = array($thumbMediaId, 'thumb', $thumbMediaFile);break;case 'music':$thumbMediaField = 'ThumbMediaId';if(is_array($Content['Content'])){if(!$this->_isMediaId($Content['Content']['ThumbMediaId'])){$thumbMediaFile = $Content['Content']['ThumbMediaId'];$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');$Content['Content']['ThumbMediaId'] = $thumbMediaId;}else{$thumbMediaId = $Content['Content']['ThumbMediaId'];$thumbMediaFile = '';}}else{if(!$this->_isMediaId($Content['Content'])){$thumbMediaFile = $Content['Content'];$oClient = WeixinApi::instance($this->Config->Client, $this->Config);$thumbMediaId = $oClient->upload_media($thumbMediaFile, 'thumb');$Content['Content'] = $thumbMediaId;}else{$thumbMediaId = $Content['Content'];$thumbMediaFile = '';}}$this->_responseMedia[$thumbMediaField] = array($thumbMediaId, 'thumb', $thumbMediaFile);break;default:// other type, do nothing}return $Content;}public function createMessage($FromUserName, $ToUserName, $Content){return self::generateMessage($FromUserName, $ToUserName, $Content);}/** * 根据消息类型,创建消息 * @param string $FromUserName 发送者 * @param string $ToUserName接收者 * @param string|array $Content 发送内容,默认为文本消息,传数组可设定消息类型,格式为:aray('MsgType' => 'image', 'Content' => '消息内容') * @return string 返回XML格式消息 */public static function generateMessage($FromUserName, $ToUserName, $Content){$aMsgTypes = array('text', 'image', 'voice', 'video', 'music', 'news', 'transfer_customer_service');if(is_array($Content)){$type = $Content['MsgType'];$data = $Content['Content'];}else{$type = 'text';$data = $Content;}if(!in_array($type, $aMsgTypes, false)){return WeixinApi::throw_exception("Unknown MsgType: $type", WXAPI_ERR_CONFIG, $Content, __FILE__, __LINE__);}if(!strcasecmp($type, 'transfer_customer_service')){$method = 'generateTransferCustomerServiceMessage';}else{$method = 'generate' . ucfirst(strtolower($type)) . 'Message';}return self::$method($FromUserName, $ToUserName, $data);}public function createTextMessage($FromUserName, $ToUserName, $content){return self::generateTextMessage($FromUserName, $ToUserName, $content);}/** * 创建文本消息 * @param object $object * @param string $content 文本内容,支持换行 * @return string */public static function generateTextMessage($FromUserName, $ToUserName, $content){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>";return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $content);}public function createImageMessage($FromUserName, $ToUserName, $mediaId){return self::generateImageMessage($FromUserName, $ToUserName, $mediaId);}/** * 创建图片消息 * @param object $object * @param string $mediaId 通过上传多媒体文件,得到的id * @return string */public static function generateImageMessage($FromUserName, $ToUserName, $mediaId){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[image]]></MsgType><Image>%s</Image></xml>";$mediaTpl = "<MediaId><![CDATA[%s]]></MediaId>";if(!is_array($mediaId)){$mediaIds = array($mediaId);}else{$mediaIds = $mediaId;}$media = '';foreach($mediaIds as $mediaId){$media .= sprintf($mediaTpl, $mediaId);}return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);}public function createVoiceMessage($FromUserName, $ToUserName, $mediaId){return self::generateVoiceMessage($FromUserName, $ToUserName, $mediaId);}/** * 创建语音消息 * @param object $object * @param string $mediaId 通过上传多媒体文件,得到的id  * @return string */public static function generateVoiceMessage($FromUserName, $ToUserName, $mediaId){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[voice]]></MsgType><Voice>%s</Voice></xml>";$mediaTpl = "<MediaId><![CDATA[%s]]></MediaId>";if(!is_array($mediaId)){$mediaIds = array($mediaId);}else{$mediaIds = $mediaId;}$media = '';foreach($mediaIds as $mediaId){$media .= sprintf($mediaTpl, $mediaId);}return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);}public function createVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId=NULL){return self::generateVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId);}/** * 创建视频消息 * @param object $object * @param string|array $mediaId 通过上传多媒体文件,得到的id,可以传数组:Array('MediaId'=>mediaid, 'ThumbMediaId'=>thumbMediaId) * @param string $thumbMediaId 缩略图的媒体id,通过上传多媒体文件,得到的id,必填字段 * @return string */public static function generateVideoMessage($FromUserName, $ToUserName, $mediaId, $thumbMediaId=NULL){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[video]]></MsgType><Video>%s</Video> </xml>";if(is_array($mediaId)){$mediaData = array('MediaId' => $mediaId['MediaId'],'ThumbMediaId' => $mediaId['ThumbMediaId'],);}else{$mediaData = array('MediaId' => $mediaId,'ThumbMediaId' => $thumbMediaId,);}$media = "";foreach ($mediaData as $n=>$d){$n = ucfirst($n);$mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";$media .= sprintf($mediaTpl, $d);}return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);}public function createMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title=NULL, $description=NULL, $musicUrl=NULL, $hqMusicUrl=NULL){return self::generateMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title, $description, $musicUrl, $hqMusicUrl);}/** * 创建音乐消息 * @param object $object * @param string|array $thumbMediaId 缩略图的媒体id,通过上传多媒体文件,得到的id,必填字段,传数组格式如: * array ("Title" => $title,"Description" => $description,"MusicURL" => $musicURL,"HQMusicUrl" => $hqMusicUrl,"ThumbMediaId" => $thumbMediaId,)  * @param string $title 音乐标题  * @param string $description 音乐描述  * @param string $musicUrl 音乐链接 * @param string $hqMusicUrl 高质量音乐链接,WIFI环境优先使用该链接播放音乐  * @return string */public static function generateMusicMessage($FromUserName, $ToUserName, $thumbMediaId, $title=NULL, $description=NULL, $musicUrl=NULL, $hqMusicUrl=NULL){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[music]]></MsgType><Music>%s</Music></xml>";$media = "";if (is_array ( $thumbMediaId )) {$mediaData = array ("Title" => $thumbMediaId['Title'],"Description" => $thumbMediaId['Description'],"MusicUrl" => $thumbMediaId['MusicUrl'],"HQMusicUrl" => $thumbMediaId['HQMusicUrl'],"ThumbMediaId" => $thumbMediaId['ThumbMediaId'],);} else {$mediaData = array ("Title" => $title,"Description" => $description,"MusicUrl" => $musicUrl,"HQMusicUrl" => $hqMusicUrl,"ThumbMediaId" => $thumbMediaId,);}foreach($mediaData as $n=>$d){if($d){$n = ucfirst($n);$mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";$media .= sprintf($mediaTpl, $d);}}return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $media);}public function createNewsMessage($FromUserName, $ToUserName, $title, $description=NULL, $picUrl=NULL, $url=NULL){return self::generateNewsMessage($FromUserName, $ToUserName, $title, $description, $picUrl, $url);}/** * 创建图文消息 * @param object $object * @param string|array $title 图文消息标题,传数组格式如: * array("Title" => $title,"Description" => $description,"PicUrl" => $picUrl,"Url" => $url,)或array( 0 => array("Title" => $title,"Description" => $description,"PicUrl" => $picUrl,"Url" => $url,)) * @param string $description 图文消息描述  * @param string $picUrl 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200  * @param string $url 点击图文消息跳转链接  * @return string */public static function generateNewsMessage($FromUserName, $ToUserName, $title, $description=NULL, $picUrl=NULL, $url=NULL){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[news]]></MsgType><ArticleCount>%s</ArticleCount><Articles>%s</Articles></xml>";$media = "";$items = array();if(is_array($title)){if(isset($title['Title']) || isset($title['Description']) || isset($title['PicUrl']) || isset($title['Url'])){$items[] = $title;}else{$items = $title;}}else{$items[] = array("Title" => $title,"Description" => $description,"PicUrl" => $picUrl,"Url" => $url,);}$count = count($items);if($count>10){return WeixinApi::throw_exception("Over Max 10 news messages", WXAPI_ERR_CONFIG, array('items'=>$items), __FILE__, __LINE__);}$valid_item_tags = array('Title', 'Description', 'PicUrl', 'Url');foreach($items as $item){$media .= "<item>";foreach ( $item as $n => $d ) {if ($d && in_array($n, $valid_item_tags, true)) {$n = ucfirst($n);$mediaTpl = "<{$n}><![CDATA[%s]]></{$n}>";$media .= sprintf ( $mediaTpl, $d );}}$media .= "</item>";}return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $count, $media);}/** * 创建转发客服消息 * @return string */public static function generateTransferCustomerServiceMessage($FromUserName, $ToUserName, $TransInfo_KfAccount=NULL){$msgTpl = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[transfer_customer_service]]></MsgType>";if($TransInfo_KfAccount){$msg .= "<TransInfo>        <KfAccount>%s</KfAccount>    </TransInfo>";}$msgTpl .= "</xml>";return sprintf($msgTpl, $FromUserName, $ToUserName, time(), $TransInfo_KfAccount);}public function __get($c){if(substr($c, 0, 1)!='_'){if(in_array($c, array('Http'))){return parent::__get($c);}else{$n = '_' . $c;return $this->$n;}}}/** * 生成签名 * @param string $msg_encrypt * @param string $nonce * @param string $timestamp * @param string $token * @return string */public static function genearteSignature($msg_encrypt, $nonce, $timestamp, $token){$tmpArr = array($token, $timestamp, $nonce, $msg_encrypt);sort($tmpArr, SORT_STRING);$tmpStr = implode( $tmpArr );return sha1( $tmpStr );}/** * 创建加密消息 * @param string $encrypt_content 加密内容 * @param string $nonce 随机数 * @param int $timestamp 时间戳 * @param string $signature 签名 * @return string */public static function generateEncryptMessage($encrypt_content, $nonce, $timestamp, $signature){$msgTpl = "<xml><Encrypt><![CDATA[%s]]></Encrypt><MsgSignature><![CDATA[%s]]></MsgSignature><TimeStamp>%s</TimeStamp><Nonce><![CDATA[%s]]></Nonce></xml>";return sprintf($msgTpl, $encrypt_content, $signature, $timestamp, $nonce);}/** * 加密响应消息 * @return string */protected function _encrypt_response($msg, $encodingkey){$msg_encrypt = $this->_encryptMsg($msg, $encodingkey);$nonce = WeixinApi_Kit::gen_random_number(11);$timestamp = time();$signature = self::genearteSignature($msg_encrypt, $nonce, $timestamp, $this->Config->AppToken);return self::generateEncryptMessage($msg_encrypt, $nonce, $timestamp, $signature);}/** * 对明文进行加密 * @param string $msg 需要加密的明文 * @param string $encodingkey 加密私钥 * @return string|false 加密得到的密文,失败返回flase */protected function _encryptMsg($msg, $encodingkey=NULL){$AESKey = base64_decode(($encodingkey?$encodingkey:$this->Config->AppEncodingAESKey) . "=");// 获得16位随机字符串,填充到明文之前$random = WeixinApi_Kit::gen_random_string ( 16 );$msg = $random . pack ( "N", strlen ( $msg ) ) . $msg . $this->Config->AppId;// 网络字节序$size = mcrypt_get_block_size ( MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC );$module = mcrypt_module_open ( MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '' );if(false===$module){return $this->_throw_exception("Cann't open an encryption descriptor", WXAPI_ERR_BAD_ENCRYPT, $msg, __FILE__, __LINE__);}$iv = substr ( $AESKey, 0, 16 );// 使用自定义的填充方式对明文进行补位填充$msg = WeixinApi_Kit::pkcs7_encode ( $msg, 32 );$init = mcrypt_generic_init ( $module, $AESKey, $iv );if(false===$init){return $this->_throw_exception("Cann't initialize buffers for encryption", WXAPI_ERR_BAD_ENCRYPT, array('msg' => $msg, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif(-3==$init){return $this->_throw_exception("the key length was incorrect", WXAPI_ERR_BAD_ENCRYPT, array('msg' => $msg, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif(-4==$init){return $this->_throw_exception("there was a memory allocation problem", WXAPI_ERR_BAD_ENCRYPT, array('msg' => $msg, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}elseif($init<0){return $this->_throw_exception("an unknown error occurred when initialize buffers for encryption", WXAPI_ERR_BAD_ENCRYPT, array('msg' => $msg, 'mcrypt_generic_init return' => $init), __FILE__, __LINE__);}// 加密$encrypted = mcrypt_generic ( $module, $msg );mcrypt_generic_deinit ( $module );mcrypt_module_close ( $module );// print(base64_encode($encrypted));// 使用BASE64对加密后的字符串进行编码return base64_encode ( $encrypted );}}


0 0
原创粉丝点击