taPhpSdk.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. <?php
  2. /**
  3. * Date: 2018/8/2
  4. * Time: 17:14
  5. */
  6. define('SDK_VERSION', '1.8.0');
  7. /**
  8. * 数据格式错误异常
  9. */
  10. class ThinkingDataException extends \Exception
  11. {
  12. }
  13. /**
  14. * 网络异常
  15. */
  16. class ThinkingDataNetWorkException extends \Exception
  17. {
  18. }
  19. class ThinkingDataAnalytics
  20. {
  21. private $consumer;
  22. private $publicProperties;
  23. private $enableUUID;
  24. function __construct($consumer, $enableUUID = false)
  25. {
  26. $this->consumer = $consumer;
  27. $this->enableUUID = $enableUUID;
  28. $this->clear_public_properties();
  29. }
  30. /**
  31. * 设置用户属性, 覆盖之前设置的属性.
  32. * @param string $distinct_id 访客 ID
  33. * @param string $account_id 账户 ID
  34. * @param array $properties 用户属性
  35. * @return boolean
  36. * @throws Exception 数据传输,或者写文件失败
  37. */
  38. public function user_set($distinct_id, $account_id, $properties = array())
  39. {
  40. return $this->add($distinct_id, $account_id, 'user_set', null, null, $properties);
  41. }
  42. /**
  43. * 设置用户属性, 如果属性已经存在, 则操作无效.
  44. * @param string $distinct_id 访客 ID
  45. * @param string $account_id 账户 ID
  46. * @param array $properties 用户属性
  47. * @return boolean
  48. * @throws Exception 数据传输,或者写文件失败
  49. */
  50. public function user_setOnce($distinct_id, $account_id, $properties = array())
  51. {
  52. return $this->add($distinct_id, $account_id, 'user_setOnce', null, null, $properties);
  53. }
  54. /**
  55. * 修改数值类型的用户属性.
  56. * @param string $distinct_id 访客 ID
  57. * @param string $account_id 账户 ID
  58. * @param array $properties 用户属性, 其值需为 Number 类型
  59. * @return boolean
  60. * @throws Exception 数据传输,或者写文件失败
  61. */
  62. public function user_add($distinct_id, $account_id, $properties = array())
  63. {
  64. return $this->add($distinct_id, $account_id, 'user_add', null, null, $properties);
  65. }
  66. /**
  67. * 追加一个用户的某一个或者多个集合
  68. * @param string $distinct_id 访客 ID
  69. * @param string $account_id 账户 ID
  70. * @param array $properties key上传的是非关联数组
  71. * @return boolean
  72. * @throws Exception 数据传输,或者写文件失败
  73. */
  74. public function user_append($distinct_id, $account_id, $properties = array())
  75. {
  76. return $this->add($distinct_id, $account_id, 'user_append', null, null, $properties);
  77. }
  78. /**
  79. * 删除用户属性
  80. * @param string $distinct_id 访客 ID
  81. * @param string $account_id 账户 ID
  82. * @param array $properties key上传的是删除的用户属性
  83. * @return boolean
  84. * @throws Exception 数据传输,或者写文件失败
  85. */
  86. public function user_unset($distinct_id, $account_id, $properties = array())
  87. {
  88. if (is_null($properties)) {
  89. throw new ThinkingDataException("property cannot be empty .");
  90. }
  91. $arr = array_fill_keys($properties, 0);
  92. return $this->add($distinct_id, $account_id, 'user_unset', null, null, $arr);
  93. }
  94. /**
  95. * 删除用户, 此操作不可逆, 请谨慎使用.
  96. * @param string $distinct_id 访客 ID
  97. * @param string $account_id 账户 ID
  98. * @return boolean
  99. * @throws Exception 数据传输,或者写文件失败
  100. */
  101. public function user_del($distinct_id, $account_id)
  102. {
  103. return $this->add($distinct_id, $account_id, 'user_del', null, null, array());
  104. }
  105. /**
  106. * 上报事件.
  107. * @param string $distinct_id 访客 ID
  108. * @param string $account_id 账户 ID
  109. * @param string $event_name 事件名称
  110. * @param array $properties 事件属性
  111. * @return boolean
  112. * @throws Exception 数据传输,或者写文件失败
  113. */
  114. public function track($distinct_id, $account_id, $event_name, $properties = array())
  115. {
  116. if (!$event_name) {
  117. throw new ThinkingDataException("track方法event_name不能为空");
  118. }
  119. return $this->add($distinct_id, $account_id, 'track', $event_name, null, $properties);
  120. }
  121. /**
  122. * 上报事件.
  123. * @param string $distinct_id 访客 ID
  124. * @param string $account_id 账户 ID
  125. * @param string $event_name 事件名称
  126. * @param string $event_id 事件ID
  127. * @param array $properties 事件属性
  128. * @return boolean
  129. * @throws Exception 数据传输,或者写文件失败
  130. */
  131. public function track_update($distinct_id, $account_id, $event_name, $event_id, $properties = array())
  132. {
  133. if (!$event_name) {
  134. throw new ThinkingDataException("track_update方法event_name不能为空");
  135. }
  136. if (!$event_id) {
  137. throw new ThinkingDataException("track_update方法event_id不能为空");
  138. }
  139. return $this->add($distinct_id, $account_id, 'track_update', $event_name, $event_id, $properties);
  140. }
  141. /**
  142. * 上报事件.
  143. * @param string $distinct_id 访客 ID
  144. * @param string $account_id 账户 ID
  145. * @param string $event_name 事件名称
  146. * @param string $event_id 事件ID
  147. * @param array $properties 事件属性
  148. * @return boolean
  149. * @throws Exception 数据传输,或者写文件失败
  150. */
  151. public function track_overwrite($distinct_id, $account_id, $event_name, $event_id, $properties = array())
  152. {
  153. if (!$event_name) {
  154. throw new ThinkingDataException("track_overwrite方法event_name不能为空");
  155. }
  156. if (!$event_id) {
  157. throw new ThinkingDataException("track_overwrite方法event_id不能为空");
  158. }
  159. return $this->add($distinct_id, $account_id, 'track_overwrite', $event_name, $event_id, $properties);
  160. }
  161. /**
  162. * @param $distinct_id
  163. * @param $account_id
  164. * @param $type
  165. * @param $event_name
  166. * @param $event_id
  167. * @param $properties
  168. * @return mixed
  169. * @throws ThinkingDataException
  170. */
  171. private function add($distinct_id, $account_id, $type, $event_name, $event_id, $properties)
  172. {
  173. $event = array();
  174. if (!is_null($event_name) && !is_string($event_name)) {
  175. throw new ThinkingDataException("event_name必须是一个字符串");
  176. }
  177. if (!$distinct_id && !$account_id) {
  178. throw new ThinkingDataException("account_id 和 distinct_id 不能同时为空");
  179. }
  180. if ($distinct_id) {
  181. $event['#distinct_id'] = $distinct_id;
  182. }
  183. if ($account_id) {
  184. $event['#account_id'] = $account_id;
  185. }
  186. if ($event_name) {
  187. $event['#event_name'] = $event_name;
  188. }
  189. if ($type == 'track') {
  190. $properties = $this->merge_public_properties($properties);
  191. if (array_key_exists('#first_check_id', $properties)) {
  192. $event['#first_check_id'] = $properties['#first_check_id'];
  193. unset($properties['#first_check_id']);
  194. }
  195. }
  196. if ($type == 'track_update' || $type == 'track_overwrite') {
  197. $properties = $this->merge_public_properties($properties);
  198. $event['#event_id'] = $event_id;
  199. }
  200. $event['#type'] = $type;
  201. if (array_key_exists('#ip', $properties)) {
  202. $event['#ip'] = $this->extractStringProperty('#ip', $properties);
  203. }
  204. $event['#time'] = $this->extractUserTime($properties);
  205. if (array_key_exists('#app_id', $properties)) {
  206. $event['#app_id'] = $this->extractStringProperty('#app_id', $properties);
  207. }
  208. //#uuid需要标准格式 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
  209. if (array_key_exists('#uuid', $properties)) {
  210. $event['#uuid'] = $properties['#uuid'];
  211. unset($properties['#uuid']);
  212. } elseif ($this->enableUUID) {
  213. $event['#uuid'] = $this->uuid();
  214. }
  215. //检查properties
  216. $properties = $this->assertProperties($type, $properties);
  217. if (count($properties) > 0) {
  218. $event['properties'] = $properties;
  219. }
  220. echo json_encode($event);
  221. return $this->consumer->send(json_encode($event));
  222. }
  223. private function assertProperties($type, $properties)
  224. {
  225. // 检查 properties
  226. if (is_array($properties)) {
  227. $name_pattern = "/^(#|[a-z])[a-z0-9_]{0,49}$/i";
  228. if (!$properties) {
  229. return $properties;
  230. }
  231. foreach ($properties as $key => &$value) {
  232. if (is_null($value)) {
  233. continue;
  234. }
  235. if (!is_string($key)) {
  236. throw new ThinkingDataException("property key must be a str. [key=$key]");
  237. }
  238. if (strlen($key) > 50) {
  239. throw new ThinkingDataException("the max length of property key is 50. [key=$key]");
  240. }
  241. if (!preg_match($name_pattern, $key)) {
  242. throw new ThinkingDataException("property key must be a valid variable name. [key='$key']]");
  243. }
  244. if (!is_scalar($value) && !$value instanceof DateTime && !is_array($value)) {
  245. throw new ThinkingDataException("property value must be a str/int/float/datetime/array. [key='$key']");
  246. }
  247. if ($type == 'user_add' && !is_numeric($value)) {
  248. throw new ThinkingDataException("Type user_add only support Number [key='$key']");
  249. }
  250. // 如果是 DateTime,Format 成字符串
  251. if ($value instanceof DateTime) {
  252. $properties[$key] = $this->getFormatDate($value->getTimestamp());
  253. }
  254. //如果是数组
  255. if (is_array($value)) {
  256. if (array_values($value) === $value) {
  257. for ($i = 0; $i < count($value); $i++) {
  258. if ($value[$i] instanceof DateTime) {
  259. $value[$i] = $this->getFormatDate($value[$i]->getTimestamp());
  260. }
  261. }
  262. } else {
  263. foreach ($value as $k => $v) {
  264. if ($v instanceof DateTime) {
  265. $value[$k] = $this->getFormatDate($v->getTimestamp());
  266. }
  267. }
  268. }
  269. }
  270. }
  271. } else {
  272. throw new ThinkingDataException("property must be an array.");
  273. }
  274. return $properties;
  275. }
  276. public function getDatetime()
  277. {
  278. return $this->getFormatDate(null, 'Y-m-d H:i:s.u');
  279. }
  280. function getFormatDate($time = null, $format = 'Y-m-d H:i:s.u')
  281. {
  282. $utimestamp = microtime(true);
  283. $timestamp = floor($utimestamp);
  284. $milliseconds = round(($utimestamp - $timestamp) * 1000);
  285. if ($milliseconds == 1000) {
  286. $timestamp = strtotime("+1second", $timestamp);
  287. $milliseconds = 0;
  288. }
  289. $new_format = preg_replace('`(?<!\\\\)u`', sprintf("%03d", $milliseconds), $format);
  290. if ($time !== null) {
  291. return date($new_format, $time);
  292. }
  293. return date($new_format, $timestamp);
  294. }
  295. private function extractUserTime(&$properties = array())
  296. {
  297. if (array_key_exists('#time', $properties)) {
  298. $time = $properties['#time'];
  299. unset($properties['#time']);
  300. return $time;
  301. }
  302. return $this->getDatetime();
  303. }
  304. private function extractStringProperty($key, &$properties = array())
  305. {
  306. if (array_key_exists($key, $properties)) {
  307. $value = $properties[$key];
  308. unset($properties[$key]);
  309. return $value;
  310. }
  311. return '';
  312. }
  313. function uuid()
  314. {
  315. $chars = md5(uniqid(mt_rand(), true));
  316. $uuid = substr($chars, 0, 8) . '-'
  317. . substr($chars, 8, 4) . '-'
  318. . substr($chars, 12, 4) . '-'
  319. . substr($chars, 16, 4) . '-'
  320. . substr($chars, 20, 12);
  321. return $uuid;
  322. }
  323. /**
  324. * 清空公共属性
  325. */
  326. public function clear_public_properties()
  327. {
  328. $this->publicProperties = array(
  329. '#lib' => 'tga_php_sdk',
  330. '#lib_version' => SDK_VERSION,
  331. );
  332. }
  333. /**
  334. * 设置每个事件都带有的一些公共属性
  335. *
  336. * @param array $super_properties 公共属性
  337. */
  338. public function register_public_properties($super_properties)
  339. {
  340. $this->publicProperties = array_merge($this->publicProperties, $super_properties);
  341. }
  342. public function merge_public_properties($properties)
  343. {
  344. foreach ($this->publicProperties as $key => $value) {
  345. if (!isset($properties[$key])) {
  346. $properties[$key] = $value;
  347. }
  348. }
  349. return $properties;
  350. }
  351. /**
  352. * 立即刷新
  353. */
  354. public function flush()
  355. {
  356. $this->consumer->flush();
  357. }
  358. /**
  359. * 关闭 sdk 接口
  360. */
  361. public function close()
  362. {
  363. $this->consumer->close();
  364. }
  365. }
  366. abstract class AbstractConsumer
  367. {
  368. /**
  369. * 发送一条消息, 返回true为send成功。
  370. * @param string $message 发送的消息体
  371. * @return bool
  372. */
  373. public abstract function send($message);
  374. /**
  375. * 立即发送所有未发出的数据。
  376. * @return bool
  377. */
  378. public function flush()
  379. {
  380. return true;
  381. }
  382. /**
  383. * 关闭 Consumer 并释放资源。
  384. * @return bool
  385. */
  386. public abstract function close();
  387. }
  388. /**
  389. * 批量实时写本地文件,文件以天为分隔,需要与 LogBus 搭配使用进行数据上传. 建议使用,不支持多线程
  390. */
  391. class FileConsumer extends AbstractConsumer
  392. {
  393. private $fileHandler;
  394. private $fileName;
  395. private $fileDirectory;
  396. private $filePrefix;
  397. private $fileSize;
  398. private $rotateHourly;
  399. /**
  400. * 创建指定文件保存目录和指定单个日志文件大小的 FileConsumer
  401. * 默认是按天切分,无默认大小切分
  402. * @param string $file_directory 日志文件保存目录. 默认为当前目录
  403. * @param int $file_size 单个日志文件大小. 单位 MB, 无默认大小
  404. * @param bool $rotate_hourly 是否按小时切分文件
  405. * @param string $file_prefix 生成的日志文件前缀
  406. */
  407. function __construct($file_directory = '.', $file_size = 0, $rotate_hourly = false, $file_prefix = '')
  408. {
  409. $this->fileDirectory = $file_directory;
  410. if (!is_dir($file_directory)) {
  411. mkdir($file_directory, 0777, true);
  412. }
  413. $this->fileSize = $file_size;
  414. $this->rotateHourly = $rotate_hourly;
  415. $this->filePrefix = $file_prefix;
  416. $this->fileName = $this->getFileName();
  417. }
  418. /**
  419. * 消费数据,将数据追加到本地日志文件
  420. * @param $message
  421. * @return bool|int
  422. */
  423. public function send($message)
  424. {
  425. $file_name = $this->getFileName();
  426. if ($this->fileHandler != null && $this->fileName != $file_name) {
  427. $this->close();
  428. $this->fileName = $file_name;
  429. $this->fileHandler = null;
  430. }
  431. if ($this->fileHandler === null) {
  432. $this->fileHandler = fopen($file_name, 'a+');
  433. }
  434. if (flock($this->fileHandler, LOCK_EX)) {
  435. $result = fwrite($this->fileHandler, $message . "\n");
  436. flock($this->fileHandler, LOCK_UN);
  437. return $result;
  438. }
  439. }
  440. public function close()
  441. {
  442. if ($this->fileHandler === null) {
  443. return false;
  444. }
  445. return fclose($this->fileHandler);
  446. }
  447. private function getFileName()
  448. {
  449. $date_format = $this->rotateHourly ? 'Y-m-d-H' : 'Y-m-d';
  450. $file_prefix = $this->filePrefix == '' ? '' : $this->filePrefix . '.';
  451. $file_base = $this->fileDirectory . '/' . $file_prefix . 'log.' . date($date_format, time()) . "_";
  452. $count = 0;
  453. $file_complete = $file_base . $count;
  454. if ($this->fileSize > 0) {
  455. while (file_exists($file_complete) && $this->fileSizeOut($file_complete)) {
  456. $count += 1;
  457. $file_complete = $file_base . $count;
  458. }
  459. }
  460. return $file_complete;
  461. }
  462. public function fileSizeOut($fp)
  463. {
  464. clearstatcache();
  465. $fpSize = filesize($fp) / (1024 * 1024);
  466. if ($fpSize >= $this->fileSize) {
  467. return true;
  468. } else {
  469. return false;
  470. }
  471. }
  472. }
  473. /**
  474. * 批量实时地向TA服务器传输数据,不需要搭配传输工具. 不建议在生产环境中使用,不支持多线程
  475. */
  476. class BatchConsumer extends AbstractConsumer
  477. {
  478. private $url;
  479. private $appid;
  480. private $buffers;
  481. private $maxSize;
  482. private $requestTimeout;
  483. private $compress = true;
  484. private $retryTimes;
  485. private $isThrowException = false;
  486. private $cacheBuffers;
  487. private $cacheCapacity;
  488. /**
  489. * 创建给定配置的 BatchConsumer 对象
  490. * @param string $server_url 接收端 url
  491. * @param string $appid 项目 APP ID
  492. * @param int $max_size 最大的 flush 值,默认为 20
  493. * @param int $retryTimes 因网络问题发生失败时重试次数,默认为 3次
  494. * @param int $request_timeout http 的 timeout,默认 1000s
  495. * @param int $cache_capacity 最大缓存倍数,实际存储量为$max_size * $cache_multiple
  496. * @throws ThinkingDataException
  497. */
  498. function __construct($server_url, $appid, $max_size = 20, $retryTimes = 3, $request_timeout = 1000, $cache_capacity = 50)
  499. {
  500. $this->buffers = array();
  501. $this->appid = $appid;
  502. $this->maxSize = $max_size;
  503. $this->retryTimes = $retryTimes;
  504. $this->requestTimeout = $request_timeout;
  505. $parsed_url = parse_url($server_url);
  506. $this->cacheBuffers = array();
  507. $this->cacheCapacity = $cache_capacity;
  508. if ($parsed_url === false) {
  509. throw new ThinkingDataException("Invalid server url");
  510. }
  511. $this->url = $parsed_url['scheme'] . "://" . $parsed_url['host']
  512. . ((isset($parsed_url['port'])) ? ':' . $parsed_url['port'] : '')
  513. . '/sync_server';
  514. }
  515. public function __destruct()
  516. {
  517. $this->flush();
  518. }
  519. public function send($message)
  520. {
  521. $this->buffers[] = $message;
  522. if (count($this->buffers) >= $this->maxSize) {
  523. return $this->flush();
  524. }
  525. }
  526. public function flush($flag = false)
  527. {
  528. if (empty($this->buffers) && empty($this->cacheBuffers)) {
  529. return true;
  530. }
  531. if ($flag || count($this->buffers) >= $this->maxSize || count($this->cacheBuffers) == 0) {
  532. $sendBuffers = $this->buffers;
  533. $this->buffers = array();
  534. $this->cacheBuffers[] = $sendBuffers;
  535. }
  536. while (count($this->cacheBuffers) > 0) {
  537. $sendBuffers = $this->cacheBuffers[0];
  538. try {
  539. $this->doRequest($sendBuffers);
  540. array_shift($this->cacheBuffers);
  541. if ($flag) {
  542. continue;
  543. }
  544. break;
  545. } catch (ThinkingDataNetWorkException $netWorkException) {
  546. if (count($this->cacheBuffers) > $this->cacheCapacity) {
  547. array_shift($this->cacheBuffers);
  548. }
  549. if ($this->isThrowException) {
  550. throw $netWorkException;
  551. }
  552. return false;
  553. } catch (ThinkingDataException $dataException) {
  554. array_shift($this->cacheBuffers);
  555. if ($this->isThrowException) {
  556. throw $dataException;
  557. }
  558. return false;
  559. }
  560. }
  561. return true;
  562. }
  563. public function close()
  564. {
  565. $this->flush(true);
  566. }
  567. public function setCompress($compress = true)
  568. {
  569. $this->compress = $compress;
  570. }
  571. public function setFlushSize($max_size = 20)
  572. {
  573. $this->maxSize = $max_size;
  574. }
  575. public function openThrowException()
  576. {
  577. $this->isThrowException = true;
  578. }
  579. private function doRequest($message_array)
  580. {
  581. $ch = curl_init($this->url);
  582. //参数设置
  583. curl_setopt($ch, CURLOPT_POST, 1);
  584. curl_setopt($ch, CURLOPT_HEADER, 0);
  585. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 6000);
  586. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  587. curl_setopt($ch, CURLOPT_TIMEOUT, $this->requestTimeout);
  588. if ($this->compress) {
  589. $data = gzencode("[" . implode(", ", $message_array) . "]");
  590. } else {
  591. $data = "[" . implode(", ", $message_array) . "]";
  592. }
  593. $compressType = $this->compress ? "gzip" : "none";
  594. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  595. //headers
  596. curl_setopt($ch, CURLOPT_HTTPHEADER, array("TA-Integration-Type:PHP", "TA-Integration-Version:" . SDK_VERSION,
  597. "TA-Integration-Count:" . count($message_array), "appid:" . $this->appid, "compress:" . $compressType, 'Content-Type: text/plain'));
  598. //https
  599. $pos = strpos($this->url, "https");
  600. if ($pos === 0) {
  601. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  602. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  603. }
  604. //发送请求
  605. $curreyRetryTimes = 0;
  606. while ($curreyRetryTimes++ < $this->retryTimes) {
  607. $result = curl_exec($ch);
  608. if (!$result) {
  609. echo new ThinkingDataNetWorkException("Cannot post message to server , error --> " . curl_error(($ch)));
  610. continue;
  611. }
  612. //解析返回值
  613. $json = json_decode($result, true);
  614. $curl_info = curl_getinfo($ch);
  615. curl_close($ch);
  616. if ($curl_info['http_code'] == 200) {
  617. if ($json['code'] == 0) {
  618. return;
  619. } else if ($json['code'] == -1) {
  620. throw new ThinkingDataException("传输数据失败,数据格式不合法, code = -1");
  621. } else if ($json['code'] == -2) {
  622. throw new ThinkingDataException("传输数据失败,APP ID 不合法, code = -2");
  623. } else if ($json['code'] == -3) {
  624. throw new ThinkingDataException("传输数据失败,非法上报 IP, code = -3");
  625. } else {
  626. throw new ThinkingDataException("传输数据失败 code = " . $json['code']);
  627. }
  628. } else {
  629. echo new ThinkingDataNetWorkException("传输数据失败 http_code: " . $curl_info['http_code']);
  630. }
  631. }
  632. throw new ThinkingDataNetWorkException("传输数据重试" . $this->retryTimes . "次后仍然失败!");
  633. }
  634. }
  635. /**
  636. * 逐条传输数据,如果发送失败则抛出异常
  637. */
  638. class DebugConsumer extends AbstractConsumer
  639. {
  640. private $url;
  641. private $appid;
  642. private $requestTimeout;
  643. private $writerData = true;
  644. /**
  645. * 创建给定配置的 DebugConsumer 对象
  646. * @param string $server_url 接收端 url
  647. * @param string $appid 项目 APP ID
  648. * @param int $request_timeout http 的 timeout,默认 1000s
  649. * @throws ThinkingDataException
  650. */
  651. function __construct($server_url, $appid, $request_timeout = 1000)
  652. {
  653. $parsed_url = parse_url($server_url);
  654. if ($parsed_url === false) {
  655. throw new ThinkingDataException("Invalid server url");
  656. }
  657. $this->url = $parsed_url['scheme'] . "://" . $parsed_url['host']
  658. . ((isset($parsed_url['port'])) ? ':' . $parsed_url['port'] : '')
  659. . '/data_debug';
  660. $this->appid = $appid;
  661. $this->requestTimeout = $request_timeout;
  662. }
  663. public function send($message)
  664. {
  665. return $this->doRequest($message);
  666. }
  667. public function setDebugOnly($writer_data = true)
  668. {
  669. $this->writerData = $writer_data;
  670. }
  671. public function close()
  672. {
  673. }
  674. private function doRequest($message)
  675. {
  676. $ch = curl_init($this->url);
  677. $dryRun = $this->writerData ? 0 : 1;
  678. $data = "source=server&appid=" . $this->appid . "&dryRun=" . $dryRun . "&data=" . urlencode($message);
  679. //参数设置
  680. curl_setopt($ch, CURLOPT_POST, 1);
  681. curl_setopt($ch, CURLOPT_HEADER, 0);
  682. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 6000);
  683. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  684. curl_setopt($ch, CURLOPT_TIMEOUT, $this->requestTimeout);
  685. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  686. //https
  687. $pos = strpos($this->url, "https");
  688. if ($pos === 0) {
  689. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  690. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  691. }
  692. //发送请求
  693. $result = curl_exec($ch);
  694. if (!$result) {
  695. throw new ThinkingDataNetWorkException("Cannot post message to server , error -->" . curl_error(($ch)));
  696. }
  697. //解析返回值
  698. $json = json_decode($result, true);
  699. $curl_info = curl_getinfo($ch);
  700. curl_close($ch);
  701. if ($curl_info['http_code'] == 200) {
  702. if ($json['errorLevel'] == 0) {
  703. return true;
  704. } else {
  705. echo "\nUnexpected Return Code " . $json['errorLevel'] . " for: " . $message . "\n";
  706. throw new ThinkingDataException(print_r($json));
  707. }
  708. } else {
  709. throw new ThinkingDataNetWorkException("传输数据失败. HTTP code: " . $curl_info['http_code'] . "\t return content :" . $result);
  710. }
  711. }
  712. }