You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

520 lines
21KB

  1. using HealthMonitor.Common;
  2. using HealthMonitor.Common.helper;
  3. using HealthMonitor.Model.Config;
  4. using HealthMonitor.Service.Biz.db.Dto;
  5. using HealthMonitor.Util.Models;
  6. using Microsoft.EntityFrameworkCore.Metadata.Internal;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Options;
  9. using Newtonsoft.Json;
  10. using Newtonsoft.Json.Linq;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Threading.Tasks;
  16. using System.Xml.Linq;
  17. using TDengineDriver;
  18. using TDengineDriver.Impl;
  19. namespace HealthMonitor.Service.Biz.db
  20. {
  21. public class TDengineService
  22. {
  23. private readonly ILogger<TDengineService> _logger;
  24. private readonly HttpHelper _httpHelper=default!;
  25. private readonly TDengineServiceConfig _configTDengineService;
  26. public TDengineService(ILogger<TDengineService> logger,
  27. IOptions<TDengineServiceConfig> configTDengineService,
  28. HttpHelper httpHelper
  29. )
  30. {
  31. _logger = logger;
  32. _configTDengineService = configTDengineService.Value;
  33. _httpHelper = httpHelper;
  34. }
  35. public IntPtr Connection()
  36. {
  37. string host = _configTDengineService.Host;
  38. string user = _configTDengineService.UserName;
  39. string db = _configTDengineService.DB;
  40. short port = _configTDengineService.Port;
  41. string password = _configTDengineService.Password;
  42. //#if DEBUG
  43. // //string configDir = "C:/TDengine/cfg";
  44. // //TDengine.Options((int)TDengineInitOption.TSDB_OPTION_CONFIGDIR, configDir);
  45. // TDengine.Options((int)TDengineInitOption.TSDB_OPTION_TIMEZONE, "Asia/Beijing");
  46. //#endif
  47. IntPtr conn = TDengine.Connect(host, user, password, db, port);
  48. if (conn == IntPtr.Zero)
  49. {
  50. _logger.LogError($"连接 TDengine 失败....");
  51. }
  52. else
  53. {
  54. _logger.LogInformation($"连接 TDengine 成功....");
  55. }
  56. return conn;
  57. }
  58. public void ExecuteSQL(IntPtr conn, string sql)
  59. {
  60. IntPtr res = TDengine.Query(conn, sql);
  61. // Check if query success
  62. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  63. {
  64. Console.Write(sql + " failure, ");
  65. // Get error message while Res is a not null pointer.
  66. if (res != IntPtr.Zero)
  67. {
  68. Console.Write("reason:" + TDengine.Error(res));
  69. }
  70. }
  71. else
  72. {
  73. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  74. //... do something with res ...
  75. // Important: need to free result to avoid memory leak.
  76. TDengine.FreeResult(res);
  77. }
  78. }
  79. public void ExecuteQuerySQL(IntPtr conn, string sql)
  80. {
  81. IntPtr res = TDengine.Query(conn, sql);
  82. // Check if query success
  83. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  84. {
  85. Console.Write(sql + " failure, ");
  86. // Get error message while Res is a not null pointer.
  87. if (res != IntPtr.Zero)
  88. {
  89. Console.Write("reason:" + TDengine.Error(res));
  90. }
  91. }
  92. else
  93. {
  94. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  95. //... do something with res ...
  96. List<TDengineDriver.TDengineMeta> resMeta = LibTaos.GetMeta(res);
  97. List<object> resData = LibTaos.GetData(res);
  98. foreach (var meta in resMeta)
  99. {
  100. _logger.LogInformation("\t|{meta.name} {meta.TypeName()} ({meta.size})\t|", meta.name, meta.TypeName(), meta.size);
  101. }
  102. for (int i = 0; i < resData.Count; i++)
  103. {
  104. _logger.LogInformation($"|{resData[i].ToString()} \t");
  105. if (((i + 1) % resMeta.Count == 0))
  106. {
  107. _logger.LogInformation("");
  108. }
  109. }
  110. // Important: need to free result to avoid memory leak.
  111. TDengine.FreeResult(res);
  112. }
  113. }
  114. public void CheckRes(IntPtr conn, IntPtr res, String errorMsg)
  115. {
  116. if (TDengine.ErrorNo(res) != 0)
  117. {
  118. throw new Exception($"{errorMsg} since: {TDengine.Error(res)}");
  119. }
  120. }
  121. public void ExecuteInsertSQL(string sql)
  122. {
  123. var conn = Connection();
  124. try
  125. {
  126. //sql = "INSERT INTO d1001 USING meters TAGS('California.SanFrancisco', 2) VALUES ('2018-10-03 14:38:05.000', 10.30000, 219, 0.31000) ('2018-10-03 14:38:15.000', 12.60000, 218, 0.33000) ('2018-10-03 14:38:16.800', 12.30000, 221, 0.31000) " +
  127. // "d1002 USING power.meters TAGS('California.SanFrancisco', 3) VALUES('2018-10-03 14:38:16.650', 10.30000, 218, 0.25000) " +
  128. // "d1003 USING power.meters TAGS('California.LosAngeles', 2) VALUES('2018-10-03 14:38:05.500', 11.80000, 221, 0.28000)('2018-10-03 14:38:16.600', 13.40000, 223, 0.29000) " +
  129. // "d1004 USING power.meters TAGS('California.LosAngeles', 3) VALUES('2018-10-03 14:38:05.000', 10.80000, 223, 0.29000)('2018-10-03 14:38:06.500', 11.50000, 221, 0.35000)";
  130. //#if DEBUG
  131. // //string configDir = "C:/TDengine/cfg";
  132. // //TDengine.Options((int)TDengineInitOption.TSDB_OPTION_CONFIGDIR, configDir);
  133. // TDengine.Options((int)TDengineInitOption.TSDB_OPTION_TIMEZONE, "Asia/Beijing");
  134. //#endif
  135. _logger.LogInformation($"Insert SQL: {sql}");
  136. IntPtr res = TDengine.Query(conn, sql);
  137. CheckRes(conn, res, "failed to insert data");
  138. int affectedRows = TDengine.AffectRows(res);
  139. _logger.LogInformation("affectedRows {affectedRows}" , affectedRows);
  140. TDengine.FreeResult(res);
  141. }
  142. finally
  143. {
  144. TDengine.Close(conn);
  145. }
  146. }
  147. #region TDengine.Connector async query
  148. public void QueryCallback(IntPtr param, IntPtr taosRes, int code)
  149. {
  150. if (code == 0 && taosRes != IntPtr.Zero)
  151. {
  152. FetchRawBlockAsyncCallback fetchRowAsyncCallback = new FetchRawBlockAsyncCallback(FetchRawBlockCallback);
  153. TDengine.FetchRawBlockAsync(taosRes, fetchRowAsyncCallback, param);
  154. }
  155. else
  156. {
  157. _logger.LogInformation("async query data failed, failed code {code}",code);
  158. }
  159. }
  160. // Iteratively call this interface until "numOfRows" is no greater than 0.
  161. public void FetchRawBlockCallback(IntPtr param, IntPtr taosRes, int numOfRows)
  162. {
  163. if (numOfRows > 0)
  164. {
  165. _logger.LogInformation("{numOfRows} rows async retrieved", numOfRows);
  166. IntPtr pdata = TDengine.GetRawBlock(taosRes);
  167. List<TDengineMeta> metaList = TDengine.FetchFields(taosRes);
  168. List<object> dataList = LibTaos.ReadRawBlock(pdata, metaList, numOfRows);
  169. for (int i = 0; i < metaList.Count; i++)
  170. {
  171. _logger.LogInformation("{0} {1}({2}) \t|", metaList[i].name, metaList[i].type, metaList[i].size);
  172. }
  173. _logger.LogInformation("");
  174. for (int i = 0; i < dataList.Count; i++)
  175. {
  176. if (i != 0 && i % metaList.Count == 0)
  177. {
  178. _logger.LogInformation("{dataList[i]}\t|", dataList[i]);
  179. }
  180. _logger.LogInformation("{dataList[i]}\t|", dataList[i]);
  181. }
  182. TDengine.FetchRawBlockAsync(taosRes, FetchRawBlockCallback, param);
  183. }
  184. else
  185. {
  186. if (numOfRows == 0)
  187. {
  188. _logger.LogInformation("async retrieve complete.");
  189. }
  190. else
  191. {
  192. _logger.LogInformation("FetchRawBlockCallback callback error, error code {numOfRows}", numOfRows);
  193. }
  194. TDengine.FreeResult(taosRes);
  195. }
  196. }
  197. public void ExecuteQueryAsync(string sql)
  198. {
  199. var conn = Connection();
  200. QueryAsyncCallback queryAsyncCallback = new QueryAsyncCallback(QueryCallback);
  201. TDengine.QueryAsync(conn, sql, queryAsyncCallback, IntPtr.Zero);
  202. }
  203. //public void ExecuteQuery(string sql)
  204. //{
  205. // var conn = Connection();
  206. // QueryAsyncCallback queryAsyncCallback = new QueryAsyncCallback(QueryCallback);
  207. // TDengine.QueryAsync(conn, sql, queryAsyncCallback, IntPtr.Zero);
  208. //}
  209. public Aggregate GetAggregateValue(string field, string tbName, string? condition)
  210. {
  211. List<int> data = new();
  212. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  213. var conn = Connection();
  214. try
  215. {
  216. IntPtr res = TDengine.Query(conn, sql);
  217. // Check if query success
  218. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  219. {
  220. Console.Write(sql + " failure, ");
  221. // Get error message while Res is a not null pointer.
  222. if (res != IntPtr.Zero)
  223. {
  224. Console.Write("reason:" + TDengine.Error(res));
  225. }
  226. }
  227. else
  228. {
  229. Console.Write(sql + " success, {0} rows affected", TDengine.AffectRows(res));
  230. //... do something with res ...
  231. List<TDengineMeta> resMeta = LibTaos.GetMeta(res);
  232. List<object> resData = LibTaos.GetData(res);
  233. foreach (var meta in resMeta)
  234. {
  235. Console.Write($"\t|{meta.name} {meta.TypeName()} ({meta.size})\t|");
  236. }
  237. resData.ForEach(x => data.Add(SafeType.SafeInt(x)));
  238. // Important: need to free result to avoid memory leak.
  239. TDengine.FreeResult(res);
  240. }
  241. }
  242. finally
  243. {
  244. TDengine.Close(conn);
  245. }
  246. return new Aggregate
  247. {
  248. Max = data.Count.Equals(0) ? 0 : data[0],
  249. Min = data.Count.Equals(0) ? 0 : data[1],
  250. };
  251. }
  252. public int GetAvgExceptMaxMinValue(string field, string tbName, string? condition)
  253. {
  254. List<int> data = new();
  255. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  256. var aggregate= GetAggregateValue(field, tbName, condition);
  257. var sqlAvg = $"SELECT AVG({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition} AND {field} < {aggregate.Max} and {field} > {aggregate.Min}";
  258. var conn = Connection();
  259. try
  260. {
  261. IntPtr res = TDengine.Query(conn, sqlAvg);
  262. // Check if query success
  263. if ((res == IntPtr.Zero) || (TDengine.ErrorNo(res) != 0))
  264. {
  265. Console.Write(sqlAvg + " failure, ");
  266. // Get error message while Res is a not null pointer.
  267. if (res != IntPtr.Zero)
  268. {
  269. Console.Write("reason:" + TDengine.Error(res));
  270. }
  271. }
  272. else
  273. {
  274. Console.Write(sqlAvg + " success, {0} rows affected", TDengine.AffectRows(res));
  275. //... do something with res ...
  276. List<TDengineMeta> resMeta = LibTaos.GetMeta(res);
  277. List<object> resData = LibTaos.GetData(res);
  278. foreach (var meta in resMeta)
  279. {
  280. Console.Write($"\t|{meta.name} {meta.TypeName()} ({meta.size})\t|");
  281. }
  282. resData.ForEach(x => data.Add(SafeType.SafeInt(x)));
  283. // Important: need to free result to avoid memory leak.
  284. TDengine.FreeResult(res);
  285. }
  286. }
  287. finally
  288. {
  289. TDengine.Close(conn);
  290. }
  291. return data.Count.Equals(0) ? 0 : data[0];
  292. }
  293. #endregion
  294. #region RestAPI
  295. public async Task<string?> ExecuteQuerySQLRestResponse(string sql)
  296. {
  297. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  298. List<KeyValuePair<string, string>> headers = new()
  299. {
  300. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  301. };
  302. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  303. return result;
  304. }
  305. public async Task<string?> ExecuteSelectRestResponseAsync( string tbName, string condition="1", string field = "*")
  306. {
  307. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  308. var sql = $"SELECT {field} FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  309. List<KeyValuePair<string, string>> headers = new()
  310. {
  311. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  312. };
  313. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  314. return result;
  315. }
  316. public async Task<bool> GernalRestSql(string sql)
  317. {
  318. //"http://{server}:{port}/rest/sql/{db}"
  319. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  320. List<KeyValuePair<string, string>> headers = new()
  321. {
  322. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  323. };
  324. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  325. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  326. if (result != null)
  327. {
  328. if (res?.Code == 0)
  329. {
  330. _logger.LogInformation($"{nameof(GernalRestSql)},SQL 语句执行成功|{sql}");
  331. return true;
  332. }
  333. else
  334. {
  335. _logger.LogWarning($"{nameof(GernalRestSql)},SQL 语句执行失败||{sql}");
  336. return false;
  337. }
  338. }
  339. else
  340. {
  341. _logger.LogError($"{nameof(GernalRestSql)},TDengine 服务器IP:{_configTDengineService.Host} 错误,请联系运维人员");
  342. return false;
  343. }
  344. //return res.Code==0;
  345. }
  346. public async Task<string?> GernalRestSqlResTextAsync(string sql)
  347. {
  348. _logger.LogInformation($"SQL: {nameof(GernalRestSqlResTextAsync)}--{sql}");
  349. var url = $"http://{_configTDengineService.Host}:{_configTDengineService.RestPort}/rest/sql/{_configTDengineService.DB}";
  350. List<KeyValuePair<string, string>> headers = new()
  351. {
  352. new KeyValuePair<string, string>("Authorization", "Basic " + _configTDengineService.Token)
  353. };
  354. var result = await _httpHelper.HttpToPostAsync(url, sql, headers).ConfigureAwait(false);
  355. return result;
  356. }
  357. /// <summary>
  358. /// 最大值,最小值聚合
  359. /// </summary>
  360. /// <param name="field"></param>
  361. /// <param name="tbName"></param>
  362. /// <param name="condition"></param>
  363. /// <returns></returns>
  364. public async Task<Aggregate> GetAggregateValueAsync(string field,string tbName,string? condition)
  365. {
  366. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  367. var result = await GernalRestSqlResTextAsync(sql);
  368. var res = JsonConvert.DeserializeObject<Aggregate>(result!);
  369. List<dynamic> data = res?.Data!;
  370. return new Aggregate
  371. {
  372. Max = data.Count.Equals(0) ? 0 : data[0][0],
  373. Min = data.Count.Equals(0) ? 0 : data[0][1],
  374. };
  375. }
  376. /// <summary>
  377. /// 去除最大值和最小值后的平均值
  378. /// </summary>
  379. /// <param name="field"></param>
  380. /// <param name="tbName"></param>
  381. /// <param name="condition"></param>
  382. /// <returns></returns>
  383. public async Task<int> GetAvgExceptMaxMinValueAsync(string field, string tbName, string? condition)
  384. {
  385. var sql = $"SELECT MAX({field}), MIN({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  386. var result = await GernalRestSqlResTextAsync(sql);
  387. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  388. _logger.LogInformation($"最大小值:{sql}");
  389. List<dynamic> data = res?.Data!;
  390. var sqlAvg = $"SELECT AVG({field}) FROM {_configTDengineService.DB}.{tbName} WHERE {condition} AND {field} < { (data.Count.Equals(0)? 0: data[0][0]) } and {field} > {(data.Count.Equals(0) ? 0 : data[0][1])}";
  391. result = await GernalRestSqlResTextAsync(sqlAvg);
  392. _logger.LogInformation($"sqlAvg:{sqlAvg}");
  393. res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  394. data = res?.Data!;
  395. return data.Count.Equals(0)?0:(int)data[0][0];
  396. }
  397. /// <summary>
  398. /// 获取最后的记录
  399. /// </summary>
  400. /// <param name="tbName"></param>
  401. /// <param name="condition"></param>
  402. /// <returns></returns>
  403. public async Task<JArray?> GetLastAsync(string tbName, string? condition)
  404. {
  405. var sql = $"SELECT last_row(*) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  406. var result = await GernalRestSqlResTextAsync(sql);
  407. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  408. if(res?.Data?.Count==0) return null;
  409. List<dynamic> data = res?.Data!;
  410. return data[0] as JArray;
  411. }
  412. public async Task<int> GetCount(string tbName, string? condition)
  413. {
  414. var sql = $"SELECT count(ts) FROM {_configTDengineService.DB}.{tbName} WHERE {condition}";
  415. var result = await GernalRestSqlResTextAsync(sql);
  416. var res = JsonConvert.DeserializeObject<TDengineRestResBase>(result!);
  417. List<dynamic> data = res?.Data!;
  418. return SafeType.SafeInt(data[0][0]);
  419. }
  420. #endregion
  421. /// <summary>
  422. /// 平均值算法(去除最大值,最小值和大于标定值的平均值)
  423. /// </summary>
  424. /// <param name="numToRemove"></param>
  425. /// <param name="collection"></param>
  426. /// <param name="max"></param>
  427. /// <param name="min"></param>
  428. /// <returns></returns>
  429. public static decimal AverageAfterRemovingOneMinMaxRef(List<int> collection, int max, int min,int refValue)
  430. {
  431. collection.Remove(max);
  432. collection.Remove(min);
  433. collection.RemoveAll(_ => _ > refValue);
  434. if (collection.Count < 2)
  435. {
  436. throw new ArgumentException($"数据集{collection.ToArray()},去掉一个最大值 {max}和一个最小值{min},异常值(大于标定值{refValue}),后数据值不足");
  437. }
  438. return (decimal)collection.Average(x => x);
  439. //var values = ParseData.Select(valueSelector).ToList();
  440. //collection = values.Select(i => (int)i).ToArray();
  441. //if (values.Count <= 2)
  442. //{
  443. // throw new ArgumentException("Not enough elements to remove.");
  444. //}
  445. //// Remove the specified number of minimum and maximum values
  446. ////values.RemoveAll(_ => _ == values.Min());
  447. ////values.RemoveAll(_ => _ == values.Max());
  448. //max = (int)values.Max();
  449. //min = (int)values.Min();
  450. //values.Remove(max);
  451. //values.Remove(min);
  452. //// Remove values less than the specified threshold
  453. //values.RemoveAll(_ => _ > numToRemove);
  454. //// Calculate and return the average of the remaining values
  455. //return values.Average();
  456. }
  457. }
  458. }