【项目需求】

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

【项目疑难点】

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

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

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

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

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

转载于:https://www.cnblogs.com/piwefei/p/9172094.html

微信公众号接口类(PHP版本)相关推荐

  1. 云服务器有token_配置node服务器并且链接微信公众号接口配置

    配置node服务器并且链接微信公众号接口配置 配置node服务器 在购买好服务器后,用Xshell链接到服务器. 1.安装nvm curl -o- https://raw.githubusercont ...

  2. 配置node服务器并且链接微信公众号接口配置(超详细)

    配置node服务器 在购买好服务器后,用Xshell链接到服务器. 1.安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.3 ...

  3. C# 调用微信公众号接口发送客服消息示例

    客服消息发送比较简单 注:指定openid和消息内容使用Post发送就可以,很多时候需要在触发事件或相应的情况下发送 官方文档:https://mp.weixin.qq.com/wiki?t=reso ...

  4. 微信公众号php发送图片素材,php版微信公众号接口实现发红包的方法

    本文实例讲述了php版微信公众号接口实现发红包的方法.分享给大家供大家参考,具体如下: 最近接到一个任务,需要用微信来给用户自动发红包.要完成这个任务需要这么已经一些物料 微信商户号,已申请微信支付 ...

  5. springmvc开发微信公众号接口 微信公众号测试账号配置接口Token验证

    转:springmvc开发微信公众号接口 微信公众号测试账号配置接口Token验证 开发前必读以及准备工作: 1.微信公众平台开发是指为微信公众号进行业务开发,为移动应用.PC端网站.公众号第三方平台 ...

  6. 微信公众号 接口配置信息 URL 配置失败(能进入后台方法,无返回信息)

    微信公众号 接口配置信息 URL 配置失败(能进入后台方法,无返回信息) 操作步骤:1.微信公众号输入url.token点提交,提示配置失败. 后台代码: @ResponseBody@RequestM ...

  7. Java架构学习(五十一)微信公众号开发混合开发技术流程微信推送原理外网映射工具使用微信公众号接口认证代码获取微信客户端信息解决微信消息幂等问题

    一.微信公众号项目 SOA架构:面向接口开发,也就是相当于把传统的那种ssh项目的业务逻辑层抽取出来 做成接口. SSH架构与分布式架构的区别 SSH架构是整个项目的任何模块都在一个项目里面 分布式架 ...

  8. 微信公众号-接口配置信息url和tokken

    一.接入指南 https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html 接入概述 ...

  9. 微信支付、微信公众号接口认证方案

    1 微信公众号接口认证方案 1.1 认证流程 安全API接口认证方案 流程图模板_ProcessOn思维导图.流程图​www.processon.com/view/link/636e05a21e085 ...

最新文章

  1. [kuangbin带你飞]专题六 最小生成树 L - 还是畅通工程 (简单最小生成树)
  2. 【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)...
  3. Mato的文件管理 (莫队)题解
  4. Algorithms_基础数据结构(01)_线性表之数组数组的应用案例分析
  5. pip install transformers出现拒绝访问
  6. [转] Windows Server 2012 Beta Cluster (Hyper-V 3.0)-SMB篇
  7. win7查看隐藏文件_隐藏在电脑里の秘密,放在你眼前,你也发现不了,就是这么奥给力...
  8. 栈应用(中缀表达式转后缀表达式并计算后缀表达式的值)
  9. 服务器日志文件中包含堆栈跟踪,日志框架 Logback 官方手册(第三章:Configuration)...
  10. matlab验潮站,验潮站的作用是什么
  11. 截取屏幕并保存为BMP文件
  12. JavaScript中变量的类型
  13. linux git 撤销删除文件,git 撤销回滚学习
  14. 襄阳教育云平台实名认证_襄阳教育云平台学生空间官网-优教信使襄阳教育云平台下载V4.1.6-西西软件下载...
  15. SpringBoot整合阿里云视频点播
  16. 程序员计算机常用快捷键,19个程序员都爱不释手的键盘快捷键,快来看看你知道几个又用过几个吧!...
  17. Python整数进制间转换
  18. 应用、permission、资源
  19. 千牛文件已上传服务器,千牛怎样挂在云服务器上
  20. 外观检测可以检测产品的哪些项目?

热门文章

  1. 如何看待力扣(LeetCode)经典会员
  2. 中式客厅装修的特点 亦古亦今的惊艳每一家
  3. M1芯片的Mac上iPhone虚拟机滚动过快的问题
  4. 查看oracle执行计划方法( 一)
  5. 荣耀v40怎么样 荣耀v40值得入手吗
  6. crypt-5+n凯撒密码
  7. 每日刷题记录 (十七)
  8. 每日刷题记录 (一)
  9. 机器学习算法基础之使用python代码
  10. 文末送书 | 手把手教你玩转,Python 会交互的超强绘图库 Plotly!