TdOperate.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <?php
  2. declare(strict_types=1);
  3. namespace tdengine;
  4. use tdengine\TdUtils;
  5. class TdOperate
  6. {
  7. protected $database;
  8. protected $username;
  9. protected $password;
  10. protected $httpHeader;
  11. protected $apiUrl;
  12. public function __construct()
  13. {
  14. /**
  15. * 井盖
  16. * 库
  17. * DROP DATABASE manhole
  18. * CREATE DATABASE IF NOT EXISTS manhole keep 360 update 1 precision "us" REPLICA 2;
  19. * CREATE DATABASE IF NOT EXISTS manhole keep 360 update 1 precision "us";
  20. * KEEP是该数据库的数据保留多长天数,缺省是3650天(10年),数据库会自动删除超过时限的数据;
  21. * UPDATE 标志数据库支持更新相同时间戳数据;
  22. *
  23. * 统计每个设备心跳次数 一个设备一天一条 day天
  24. * # drop table stats_heartbeat;
  25. * create table stats_heartbeat (ts timestamp, devicenumber BINARY(32), day INT, num INT, lastTime INT, area INT, creator INT);
  26. *
  27. * 告警月表 ts(天) 每个设备每天1条记录 按区域区分
  28. * create table stats_alarm_month(ts timestamp, devicenumber BINARY(32), day INT, area INT, pry INT, flood INT, open INT, low INT, sub_flood INT, sub_low INT, creator INT);
  29. *
  30. * 告警年表 ts(月) 每个设备每月1条记录 按区域区分
  31. * create table stats_alarm_year(ts timestamp, devicenumber BINARY(32), month INT, area INT, pry INT, flood INT, open INT, low INT, sub_flood INT, sub_low INT, creator INT);
  32. *
  33. */
  34. $this->initConfig();
  35. }
  36. /**
  37. * 初始化配置
  38. */
  39. protected function initConfig() {
  40. $td_config = \config('tdengine');
  41. if (empty($td_config) || empty($td_config['database']) || empty($td_config['username']) || empty($td_config['password']) || empty($td_config['api_url']))
  42. {
  43. $dsnErr = <<<EOL
  44. [TDENGINE]
  45. DATABASE = database
  46. USERNAME = username
  47. PASSWORD = password
  48. API_URL = http://127.0.0.1:6041/rest/sql
  49. EOL;
  50. throw new \think\Exception("ENV TDENGINE configure error, ".PHP_EOL."{$dsnErr}");
  51. }
  52. $this->database = $td_config['database'];
  53. $this->username = $td_config['username'];
  54. $this->password = $td_config['password'];
  55. $this->apiUrl = $td_config['api_url'];
  56. $this->httpHeader = [
  57. "Content-Type: application/x-www-form-urlencoded",
  58. "Authorization: Basic " . base64_encode("$this->username:$this->password"),
  59. "Expect: ",
  60. ];
  61. }
  62. /**
  63. * 保存心跳次数
  64. * @param String|Int $deviceNumber 设备资产编号
  65. * @param String|Int $date 心跳日期
  66. * @param Array $data ['area'=> 110001,'creator'=>1]
  67. * @return Array ['status' => 'succ'] ['status' => 'error', 'desc' => 'errorrrrrr']
  68. */
  69. public function saveHeartbeatCount($deviceNumber, $date, Array $data) {
  70. /*
  71. * select ts,num from manhole.stats_heartbeat where devicenumber='14046452270' and day=100 and area=1100001 and creator=1;
  72. * insert into manhole.stats_heartbeat values(1609987092000000, '14046452270', 100, 2, 1609987092, 1100001, 1);
  73. */
  74. if (empty($deviceNumber) || empty($date) || empty($data['area']) || empty($data['creator'])) {
  75. return ['status' => 'error', 'desc' => 'deviceNumber, date, area, creator required'];
  76. }
  77. $deviceNumber = strtoupper($deviceNumber);
  78. $area = $data['area'];
  79. $creator = $data['creator'];
  80. if (!strtotime($date)) {
  81. $day = strtotime(date('Y-m-d', $date));
  82. } else {
  83. $day = strtotime(date('Y-m-d', strtotime($date)));
  84. }
  85. if (!$day) {
  86. return ['status' => 'error', 'desc' => 'date format error'];
  87. }
  88. // 查询
  89. $sql = "select ts,num from {$this->database}.stats_heartbeat where devicenumber='{$deviceNumber}' and day={$day} and area={$area} and creator={$creator};";
  90. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  91. if ($res['status'] != 'succ') {
  92. return $res;
  93. }
  94. // 存在多个值
  95. if ($res['rows'] > 1) {
  96. $res['status'] = 'error';
  97. $res['desc'] = 'multiple records';
  98. return $res;
  99. }
  100. $ts = 0;
  101. $num = 1;
  102. $last_time = $this->formatTimestamp($date, 10);
  103. if ($res['rows'] == 1) {//修改
  104. $ts = $res['data'][0][0];
  105. $ts = strtotime($ts) . substr($ts, -6);//'2021-01-07 11:01:25.920001' -> unix时间戳
  106. $num = $res['data'][0][1] + 1;
  107. } else if ($res['rows'] == 0) {//新增
  108. // 时间需要保存微妙6位,防重
  109. list($us, ) = explode(" ", microtime());
  110. $us = str_pad((String)($us * 1000000), 6, '0', STR_PAD_LEFT);
  111. $ts = $last_time . $us;
  112. }
  113. $sql = "insert into {$this->database}.stats_heartbeat values({$ts}, '{$deviceNumber}', {$day}, {$num}, {$last_time}, {$area}, {$creator});";
  114. return TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  115. }
  116. /**
  117. * 统计一个设备一段时间的心跳次数
  118. * @param String|Int $deviceNumber 设备编号
  119. * @param String|Int $startTime 开始时间
  120. * @param String|Int $endTime 结束时间
  121. * @return Array ['status' => 'succ', 'data' => ['sum' => 10]] | ['status' => 'error', 'desc' => 'errorrrrrr']
  122. */
  123. public function getHeartbeatCount($deviceNumber, $startTime, $endTime) {
  124. /**
  125. * select sum(num) from manhole.stats_heartbeat where ts>1509948800000000 and ts<1709948800000000 and devicenumber='14046452270';
  126. */
  127. if (empty($deviceNumber)) {
  128. return ['status' => 'error', 'desc' => 'device number required'];
  129. }
  130. $deviceNumber = strtoupper($deviceNumber);
  131. $startTime = $this->formatTimestamp($startTime);
  132. $endTime = $this->formatTimestamp($endTime);
  133. if (!$startTime || !$endTime) {
  134. return ['status' => 'error', 'desc' => 'startTime or endTime format error'];
  135. }
  136. $sql = "select sum(num) from {$this->database}.stats_heartbeat where ts > {$startTime} and ts < {$endTime} and devicenumber = '{$deviceNumber}';";
  137. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  138. if ($res['status'] == 'succ') {
  139. return ['status' => 'succ', 'data' => ['sum' => !isset($res['data'][0][0]) ? 0 : $res['data'][0][0]]];
  140. }
  141. return $res;
  142. }
  143. /**
  144. * 获取某天在线设备数量
  145. * @param String $day 查询日期 '2021-02-01'
  146. * @param String $offlineTime 离线时间 '2021-02-01 01:00:00'
  147. * @param Array $other 其他条件数组:['creator'=>[1,2,...], 'area'=>[101,102,...]]
  148. */
  149. public function getOnlineDeviceCountByDate($day, $offlineTime, $other = [])
  150. {
  151. /**
  152. * select count(*) from {$this->database}.stats_heartbeat where day='1613750400' and lasttime > {$offline_time}
  153. * and ( creator = 1 or creator = 2 ) and ( area = 101 or area = 102 )
  154. */
  155. $day = strtotime(date('Y-m-d',strtotime($day)));
  156. if (!$day) {
  157. return ['status' => 'error', 'desc' => '查询日期不存在或格式错误,正确格式: 2021-2-1'];
  158. }
  159. $offlineTime = strtotime($offlineTime);
  160. if (!$offlineTime) {
  161. return ['status' => 'error', 'desc' => '离线时间不存在或格式错误,正确格式: 2021-2-1 01:00:00'];
  162. }
  163. $sql = "select count(*) from {$this->database}.stats_heartbeat where day={$day} and lasttime > {$offlineTime}";
  164. $area = $other['area'] ?? [];
  165. if (count($area) != 0) {
  166. $sql .= " and (";
  167. foreach ($area as $val) {
  168. $sql .= " area={$val} or";
  169. }
  170. $sql = substr($sql, 0, -2) . ")";
  171. }
  172. $creator = $other['creator'] ?? [];
  173. if (count($creator) != 0) {
  174. $sql .= " and (";
  175. foreach ($creator as $val) {
  176. $sql .= " creator={$val} or";
  177. }
  178. $sql = substr($sql, 0, -2) . ")";
  179. }
  180. if (isset($other['devicenumber'])) {
  181. $sql .= " and devicenumber='{$other['devicenumber']}' ";
  182. }
  183. $sql .= ';';
  184. // var_dump($sql);
  185. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  186. if ($res['status'] == 'succ') {
  187. return ['status' => 'succ', 'data' => ['count' => !isset($res['data'][0][0]) ? 0 : $res['data'][0][0]]];
  188. }
  189. return $res;
  190. }
  191. /**
  192. * 保存日/月告警数据
  193. * @param String|Int $deviceNumber 设备编号
  194. * @param String|Int $date 日期
  195. * @param Array $data ['area' => 1102, 'pry' => 1, 'flood' => 1, 'open' => 0, 'low' => 1, 'sub_flood' => 1, 'sub_low' => 1,]
  196. * @param String $type 日期类型:day | month
  197. * @return Array ['status' => 'succ', 'data' => [] | ['status' => 'error', 'desc' => 'error']
  198. */
  199. public function saveAlarmCount($deviceNumber, $date,Array $data, $type = 'day') {
  200. /*
  201. * select * from manhole.stats_alarm_month where devicenumber='14046452270' and day=1612108800 and area=1001 and creator=1;
  202. * insert into manhole.stats_alarm_month values(1609987092000000, '14046452270', 1612108800, 1001, 1, 2, 3, 1);
  203. */
  204. // 设备编号
  205. if (empty($deviceNumber) || empty($data['area']) || empty($data['creator'])) {
  206. return ['status' => 'error', 'desc' => 'deviceNumber, area, creator required'];
  207. }
  208. $deviceNumber = strtoupper($deviceNumber);
  209. $area = $data['area'];
  210. $creator = $data['creator'];
  211. // 日期
  212. if ($type == 'day') { // 日
  213. $date = !strtotime($date) ? strtotime(date('Y-m-d', $date)) : strtotime(date('Y-m-d', strtotime($date)));
  214. $table = 'stats_alarm_month';
  215. } elseif ($type == 'month') { // 月
  216. $date = !strtotime($date) ? strtotime(date('Y-m', $date)) : strtotime(date('Y-m', strtotime($date)));
  217. $table = 'stats_alarm_year';
  218. } else {
  219. return ['status' => 'error', 'desc' => '不支持的类型'];
  220. }
  221. if (!$date) {
  222. return ['status' => 'error', 'desc' => 'date format error'];
  223. }
  224. // 数据
  225. $sql = "select ts,pry,flood,open,low,sub_flood,sub_low from {$this->database}.{$table} where devicenumber='{$deviceNumber}'" .
  226. " and {$type}={$date} and area={$area} and creator={$creator};";
  227. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  228. $update['ts'] = 0;
  229. $update['creator'] = $creator;
  230. $update['pry'] = isset($data['pry']) ? $data['pry'] : 0;
  231. $update['flood'] = isset($data['flood']) ? $data['flood'] : 0;
  232. $update['open'] = isset($data['open']) ? $data['open'] : 0;
  233. $update['low'] = isset($data['low']) ? $data['low'] : 0;
  234. $update['sub_flood'] = isset($data['sub_flood']) ? $data['sub_flood'] : 0;
  235. $update['sub_low'] = isset($data['sub_low']) ? $data['sub_low'] : 0;
  236. $last_time = $this->formatTimestamp($date, 10);
  237. if ($res['status'] == 'succ') {
  238. if ($res['rows'] == 1) {//修改
  239. $update['ts'] = $res['data'][0][0];
  240. $update['ts'] = strtotime($update['ts']) . substr($update['ts'], -6);//'2021-01-07 11:01:25.920001' -> unix时间戳
  241. $update['pry'] += $res['data'][0][1];
  242. $update['flood'] += $res['data'][0][2];
  243. $update['open'] += $res['data'][0][3];
  244. $update['low'] += $res['data'][0][4];
  245. $update['sub_flood'] += $res['data'][0][5];
  246. $update['sub_low'] += $res['data'][0][6];
  247. } else if ($res['rows'] == 0) {//新增
  248. //时间需要保存微妙6位,防重
  249. list($us, ) = explode(" ", microtime());
  250. $us = str_pad((String)($us * 1000000), 6, '0', STR_PAD_LEFT);
  251. $update['ts'] = $last_time . $us;
  252. } else {//存在多个值
  253. $res['status'] = 'error';
  254. $res['desc'] = 'multiple records';
  255. return $res;
  256. }
  257. } else {
  258. return $res;
  259. }
  260. $sql = "insert into {$this->database}.{$table}(ts,devicenumber,{$type},area,pry,flood,open,low,sub_flood,sub_low,creator) values({$update['ts']}, '{$deviceNumber}'," .
  261. "{$date}, {$data['area']}, {$update['pry']}, {$update['flood']}, {$update['open']}, {$update['low']}, {$update['sub_flood']}, {$update['sub_low']}, {$update['creator']});";
  262. return TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  263. }
  264. /**
  265. * 按天/月查询报警设备数
  266. * @param String $date 查询日期:2021-01-15 | 2021-01
  267. * @param String $type 日期类型:day | month
  268. * @param Array $area 所属区域
  269. * @param Array $creator 创建人
  270. * @return Array ['status' => 'succ', 'data' => ['count' => 10]] | ['status' => 'error', 'desc' => 'errorrrrrr']
  271. */
  272. public function getAlarmDeviceCount($date, $type = 'day', $area = [], $creator = [], $devicenumber = '') {
  273. /**
  274. * select count(*) from manhole.stats_alarm_month where ts > 1509948800000000 and ts < 1709948800000000 and (area=1001 or area=1002) and (creator=1 or creator=2);
  275. */
  276. if ($type == 'day') { // 日
  277. $startTime = !strtotime($date) ? strtotime(date('Y-m-d', $date)) : strtotime(date('Y-m-d', strtotime($date)));
  278. $endTime = $startTime + 86400;
  279. $table = 'stats_alarm_month';
  280. } elseif ($type == 'month') { // 月
  281. $startTime = !strtotime($date) ? strtotime(date('Y-m', $date)) : strtotime(date('Y-m', strtotime($date)));
  282. $endTime = strtotime(date('Y-m',$startTime) .' +1 month ');
  283. $table = 'stats_alarm_year';
  284. } else {
  285. return ['status' => 'error', 'desc' => '不支持的类型'];
  286. }
  287. $startTime = $this->formatTimestamp($startTime);
  288. $endTime = $this->formatTimestamp($endTime);
  289. $sql = "select count(*) from {$this->database}.{$table} where ts > {$startTime} and ts < {$endTime}";
  290. if (count($area) != 0) {
  291. $sql .= " and (";
  292. foreach ($area as $val) {
  293. $sql .= " area={$val} or";
  294. }
  295. $sql = substr($sql, 0, -2) . ")";
  296. }
  297. if (count($creator) != 0) {
  298. $sql .= " and (";
  299. foreach ($creator as $val) {
  300. $sql .= " creator={$val} or";
  301. }
  302. $sql = substr($sql, 0, -2) . ")";
  303. }
  304. if ($devicenumber) {
  305. $sql .= " and devicenumber='{$devicenumber}' ";
  306. }
  307. $sql .= ";";
  308. // var_dump($sql);
  309. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  310. if ($res['status'] == 'succ') {
  311. return ['status' => 'succ', 'data' => ['count' => !isset($res['data'][0][0]) ? 0 : $res['data'][0][0]]];
  312. }
  313. return $res;
  314. }
  315. /**
  316. * 按天/月查询[设备]报警分类数
  317. * @param String $startTime 开始时间
  318. * @param String $endTime 结束时间
  319. * @param String $deviceNumber 设备号
  320. * @param Array $aree 所属区域
  321. * @return Array ['status' => 'succ', 'data' => ['pry' => 10, 'flood' => 10, 'open' => 10]]
  322. */
  323. public function getAlarmTypeCount($startTime, $endTime, $deviceNumber = '', $area = [], $creator = [])
  324. {
  325. /**
  326. * select sum(pry),sum(flood),sum(open),sum(low),sum(sub_flood),sum(sub_low)
  327. * from manhole.stats_alarm_month where ts > 1509948800000000 and ts < 1709948800000000 and area = 1001 and devicenumber = 11111;
  328. */
  329. $startTime = $this->formatTimestamp($startTime);
  330. $endTime = $this->formatTimestamp($endTime);
  331. if (!$startTime || !$endTime) {
  332. return ['status' => 'error', 'desc' => 'startTime or endTime format error'];
  333. }
  334. $sql = "select sum(pry),sum(flood),sum(open),sum(low),sum(sub_flood),sum(sub_low) from {$this->database}.stats_alarm_month where ts>{$startTime} and ts<{$endTime}";
  335. if (!empty($deviceNumber)) {
  336. $deviceNumber = strtoupper($deviceNumber);
  337. $sql .= " and deviceNumber='{$deviceNumber}'";
  338. }
  339. if (count($area) != 0) {
  340. $sql .= " and (";
  341. foreach ($area as $val) {
  342. $sql .= " area={$val} or";
  343. }
  344. $sql = substr($sql, 0, -2) . ")";
  345. }
  346. if (count($creator) != 0) {
  347. $sql .= " and (";
  348. foreach ($creator as $val) {
  349. $sql .= " creator={$val} or";
  350. }
  351. $sql = substr($sql, 0, -2) . ")";
  352. }
  353. $sql .= ";";
  354. // var_dump($sql);
  355. $res = TdUtils::httpRequest($this->apiUrl, $sql, 'POST', $this->httpHeader);
  356. if ($res['status'] == 'succ') {
  357. return ['status' => 'succ',
  358. 'data' => [
  359. 'pry' => !isset($res['data'][0][0]) ? 0 : $res['data'][0][0],
  360. 'flood' => !isset($res['data'][0][1]) ? 0 : $res['data'][0][1],
  361. 'open' => !isset($res['data'][0][2]) ? 0 : $res['data'][0][2],
  362. 'low' => !isset($res['data'][0][3]) ? 0 : $res['data'][0][3],
  363. 'sub_flood' => !isset($res['data'][0][4]) ? 0 : $res['data'][0][4],
  364. 'sub_low' => !isset($res['data'][0][5]) ? 0 : $res['data'][0][5],
  365. ]
  366. ];
  367. }
  368. return $res;
  369. }
  370. /**
  371. * 转换为查询条件ts的时间戳(右补0)
  372. * @param String|Int $date 日期 | 时间戳
  373. * @param Int $len 时间戳长度
  374. */
  375. public function formatTimestamp($date,int $len = 16)
  376. {
  377. $date = (String)$date;
  378. $strDT = strtotime($date);
  379. if ($strDT && strlen((String)$strDT) === 10) {
  380. $date = $strDT;
  381. }
  382. return str_pad((String)$date, $len, '0');
  383. }
  384. }