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.

517 lines
29KB

  1. using dotnet_etcd;
  2. using Etcdserverpb;
  3. using Google.Protobuf.WellKnownTypes;
  4. using HealthMonitor.Common;
  5. using HealthMonitor.Common.helper;
  6. using HealthMonitor.Core.Common.Extensions;
  7. using HealthMonitor.Model.Config;
  8. using HealthMonitor.Model.Service;
  9. using HealthMonitor.Model.Service.Mapper;
  10. using HealthMonitor.Service.Biz;
  11. using HealthMonitor.Service.Biz.db;
  12. using HealthMonitor.Service.Cache;
  13. using HealthMonitor.Service.Etcd;
  14. using HealthMonitor.Service.Sub;
  15. using Microsoft.AspNetCore.Mvc.RazorPages;
  16. using Microsoft.EntityFrameworkCore.Metadata.Internal;
  17. using Microsoft.Extensions.Options;
  18. using Newtonsoft.Json;
  19. using Newtonsoft.Json.Linq;
  20. using System.Reflection;
  21. using System.Threading.Channels;
  22. using TDengineDriver;
  23. using TDengineTMQ;
  24. namespace HealthMonitor.WebApi
  25. {
  26. public class Worker : BackgroundService
  27. {
  28. private readonly ILogger<Worker> _logger;
  29. private readonly IServiceProvider _services;
  30. private readonly TDengineDataSubcribe _tdEngineDataSubcribe;
  31. private readonly PackageProcess _processor;
  32. private readonly TDengineService _serviceTDengine;
  33. private readonly EtcdService _serviceEtcd;
  34. private readonly HttpHelper _httpHelper = default!;
  35. private readonly IotWebApiService _serviceIotWebApi;
  36. private readonly BoodPressResolverConfig _configBoodPressResolver;
  37. private readonly BloodPressReferenceValueCacheManager _bpRefValCacheManager;
  38. private readonly PersonCacheManager _personCacheMgr;
  39. private CancellationTokenSource _tokenSource = default!;
  40. public Worker(ILogger<Worker> logger, IServiceProvider services, PersonCacheManager personCacheMgr, BloodPressReferenceValueCacheManager bpRefValCacheManager, IotWebApiService iotWebApiService, IOptions<BoodPressResolverConfig> optionBoodPressResolver, PackageProcess processor, TDengineDataSubcribe tdEngineDataSubcribe, TDengineService serviceDengine, HttpHelper httpHelper, EtcdService serviceEtcd)
  41. {
  42. _logger = logger;
  43. _tdEngineDataSubcribe = tdEngineDataSubcribe;
  44. _services = services;
  45. _serviceIotWebApi = iotWebApiService;
  46. _processor = processor;
  47. _serviceEtcd = serviceEtcd;
  48. _serviceTDengine = serviceDengine;
  49. _httpHelper = httpHelper;
  50. _configBoodPressResolver = optionBoodPressResolver.Value;
  51. _bpRefValCacheManager = bpRefValCacheManager;
  52. _personCacheMgr = personCacheMgr;
  53. }
  54. public override Task StartAsync(CancellationToken cancellationToken)
  55. {
  56. //_logger.LogInformation("------StartAsync");
  57. _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  58. return base.StartAsync(cancellationToken);
  59. }
  60. public override Task StopAsync(CancellationToken cancellationToken)
  61. {
  62. //_logger.LogInformation("------StopAsync");
  63. _tokenSource.Cancel(); //停止工作线程
  64. return base.StopAsync(cancellationToken);
  65. }
  66. protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  67. {
  68. var tasks = new[]
  69. {
  70. Task.Run(async () =>
  71. {
  72. _logger.LogInformation("解析器启动");
  73. while (!stoppingToken.IsCancellationRequested)
  74. {
  75. await _processor.ResolveAsync().ConfigureAwait(false);
  76. }
  77. }, stoppingToken),
  78. Task.Run(() =>
  79. {
  80. _logger.LogInformation("TDengine 订阅启动");
  81. while (!stoppingToken.IsCancellationRequested)
  82. {
  83. _tdEngineDataSubcribe.BeginListen(stoppingToken);
  84. }
  85. }, stoppingToken),
  86. Task.Run(() =>
  87. _serviceEtcd.WacthKeysWithPrefixResponseAsync("health_moniter/schedule_push", WatchEvents),
  88. stoppingToken)
  89. };
  90. await Task.WhenAll(tasks);
  91. }
  92. private void WatchEvents(WatchResponse response)
  93. {
  94. response.Events.ToList<Mvccpb.Event>().ForEach(async e =>
  95. {
  96. try
  97. {
  98. switch (e.Type.ToString())
  99. {
  100. case "Put":
  101. // 获取时间点计算TTL
  102. Console.BackgroundColor = ConsoleColor.Blue;
  103. Console.WriteLine($"--- {e.Type}--{e.Kv.Key.ToStringUtf8()}--{e.Kv.Value.ToStringUtf8()}---{DateTime.Now}");
  104. Console.BackgroundColor = ConsoleColor.Black;
  105. break;
  106. case "Delete":
  107. // TTL到了重新计算TTL,下发
  108. Console.BackgroundColor = ConsoleColor.Green;
  109. Console.WriteLine($"--- {e.Type}--{e.Kv.Key.ToStringUtf8()}--{e.Kv.Value.ToStringUtf8()}---{DateTime.Now}");
  110. // var key = $"health_moniter/schedule_push/imei/{bp.Serialno}";
  111. var key = e.Kv.Key.ToStringUtf8();
  112. var imeiDel = key.Split('/')[3];
  113. var schedule_push = await _serviceEtcd.GetValAsync(key).ConfigureAwait(false);
  114. if (string.IsNullOrWhiteSpace(schedule_push))
  115. {
  116. int systolicInc;
  117. int diastolicInc;
  118. int systolicRefValue;
  119. int diastolicRefValue;
  120. decimal systolicAvg;
  121. decimal diastolicAvg;
  122. int systolicMax = 0;
  123. int diastolicMax = 0;
  124. // 统计时间
  125. //DateTime endTime = DateTime.Now; //测试
  126. DateTime statStartTime = DateTime.Now;
  127. // 最小值
  128. int systolicMin = 0;
  129. int diastolicMin = 0;
  130. // 偏移参数
  131. var avgOffset = 0.25M;
  132. var systolicAvgOffset = avgOffset;
  133. var diastolicAvgOffset = avgOffset;
  134. // 最后一次下发值
  135. int lastPushSystolicInc = 0;
  136. int lastPushDiastolicInc = 0;
  137. var startTime = DateTime.Now;
  138. // 下发增量值
  139. #region 统计定时下发增量值
  140. //var last = await _serviceTDengine.GetLastAsync("stb_hm_bloodpress_stats_inc", $"serialno='{imeiDel}' order by last_update desc");
  141. //var ts = last?[0];
  142. // 最后一条血压数据
  143. var condition = $"serialno='{imeiDel}' order by last_update desc";
  144. var field = "last_row(*)";
  145. var lastHmBpResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bloodpress", condition, field);
  146. var lastHmBpParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressureModel>>(lastHmBpResponse!);
  147. var lastHmBp = lastHmBpParser?.Select().FirstOrDefault();
  148. //if (lastHmBpParser?.Select()?.ToList().Count < 2)
  149. //{
  150. // _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} -- 血压数据条目不足");
  151. // break;
  152. //}
  153. // 7 天有效数据
  154. if (lastHmBp?.Timestamp.AddDays(7) > DateTime.Now)
  155. {
  156. // 计算增量值
  157. condition = $"serialno='{imeiDel}' order by ts desc";
  158. var lastPushResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bp_push_ref_inc_value", condition, field);
  159. if (lastPushResponse == null)
  160. {
  161. _logger.LogInformation($"{imeiDel}--没有下发记录");
  162. break;
  163. }
  164. var lastPushParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressurePushRefIncModel>>(lastPushResponse);
  165. var lastPush = lastPushParser!.Select().FirstOrDefault();
  166. // 有下推记录
  167. if (lastPush != null)
  168. {
  169. systolicRefValue = lastPush!.SystolicRefValue;
  170. diastolicRefValue = lastPush!.DiastolicRefValue;
  171. lastPushSystolicInc = lastPush!.SystolicIncValue;
  172. lastPushDiastolicInc = lastPush!.DiastolicIncValue;
  173. condition = $"ts between '{lastPush?.Timestamp:yyyy-MM-dd HH:mm:ss.fff}' and '{startTime:yyyy-MM-dd HH:mm:ss.fff}' " +
  174. $"and serialno='{imeiDel}' " +
  175. $"and is_display = true";
  176. // 使用最近一次的下推时间作为统计的开始时间
  177. statStartTime = lastPush!.Timestamp;
  178. }
  179. // 没有下推记录(历史遗留数据),没有初始的测量值产生的平均值(测量值=平均值)
  180. else
  181. {
  182. #region 获取个人信息
  183. var person = await _personCacheMgr.GetDeviceGpsPersonCacheBySerialNoAsync(Guid.NewGuid().ToString(), imeiDel).ConfigureAwait(false);
  184. //验证这个信息是否存在
  185. if (person == null || person?.Person.BornDate == null)
  186. {
  187. _logger.LogWarning($"{nameof(Worker)}--{imeiDel} 验证个人信息,找不到个人信息,跳过此消息");
  188. break;
  189. }
  190. // 验证年龄是否在范围 (2 - 120)
  191. var age = SafeType.SafeInt(DateTime.Today.Year - person?.Person.BornDate!.Value.Year!);
  192. if (age < 2 || age > 120)
  193. {
  194. _logger.LogWarning($"{nameof(Worker)}--{imeiDel} 验证年龄,不在范围 (2 - 120)岁,跳过此消息");
  195. break;
  196. }
  197. var gender = person?.Person.Gender == true ? 1 : 2;
  198. var isHypertension = SafeType.SafeBool(person?.Person.Ishypertension!);
  199. var height = SafeType.SafeDouble(person?.Person.Height!);
  200. var weight = SafeType.SafeDouble(person?.Person.Weight!);
  201. #endregion
  202. #region 初始化常规血压标定值标定值
  203. var bpRef = await _bpRefValCacheManager.GetBloodPressReferenceValueAsync(age, gender, isHypertension);
  204. //systolicRefValue = bpRef!.Systolic;//?
  205. //diastolicRefValue = bpRef!.Diastolic;//?
  206. #endregion
  207. systolicRefValue = bpRef!.Systolic;
  208. diastolicRefValue = bpRef!.Diastolic;
  209. lastPushSystolicInc = 0;
  210. lastPushDiastolicInc = 0;
  211. condition = $"ts <= '{startTime:yyyy-MM-dd HH:mm:ss.fff}' " +
  212. $"and serialno='{imeiDel}' " +
  213. $"and is_display = true";
  214. }
  215. var hmBpResponse = await _serviceTDengine.ExecuteSelectRestResponseAsync("stb_hm_bloodpress", condition);
  216. var hmBpParser = JsonConvert.DeserializeObject<ParseTDengineRestResponse<BloodPressureModel>>(hmBpResponse!);
  217. var hmBp = hmBpParser?.Select();
  218. //if (hmBp?.ToList().Count < 2)
  219. // 1. 判断数据样本数量
  220. if (hmBpParser!.Rows < 5)
  221. {
  222. _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} -- 统计定时下发,计算增量值的数据条目不足:{hmBpParser!.Rows} < 5");
  223. _logger.LogInformation($"{imeiDel} -- {nameof(Worker)} --{nameof(WatchEvents)} 没有足够的数据样本,不会定时下发");
  224. break;
  225. }
  226. // 没有下推记录重新计算统计时间
  227. if (lastPush == null)
  228. {
  229. var firstHmBp = hmBpParser?.Select(i => i).OrderBy(i => i.Timestamp).FirstOrDefault();
  230. statStartTime = firstHmBp!.Timestamp;
  231. }
  232. // NewMethod(systolicRefValue, hmBpParser);
  233. // 最大值
  234. //systolicMax = (int)hmBpParser?.Select(i => i.SystolicValue).Max()!;
  235. //diastolicMax = (int)hmBpParser?.Select(i => i.DiastolicValue).Max()!;
  236. //// 最小值
  237. //systolicMin = (int)hmBpParser?.Select(i => i.SystolicValue).Min()!;
  238. //diastolicMin = (int)hmBpParser?.Select(i => i.DiastolicValue).Min()!;
  239. //systolicAvg = (int)(hmBpParser?.AverageAfterRemovingOneMinMaxRef(i => i.SystolicValue, SafeType.SafeInt(systolicRefValue!)))!;
  240. //diastolicAvg = (int)(hmBpParser?.AverageAfterRemovingOneMinMaxRef(i => i.DiastolicValue, SafeType.SafeInt(diastolicRefValue!)))!;
  241. var avgs = _serviceTDengine.AverageAfterRemovingOneMinMaxRef(hmBpParser!);
  242. systolicAvg = avgs[0];
  243. diastolicAvg = avgs[1];
  244. // 2. 判断能否计算增量值
  245. if (systolicAvg.Equals(0))
  246. {
  247. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}--计算平均值" +
  248. $"\n currentSystolicAvg:{systolicAvg} -- lastPushSystolicInc:{lastPushSystolicInc}" +
  249. $"\n currentDiastolicInc:{diastolicAvg} -- lastPushDiastolicInc:{lastPushDiastolicInc}");
  250. _logger.LogInformation($"{imeiDel}--{nameof(Worker)} 没有足够的数据样本计算平均值,不会定时下发");
  251. break;
  252. }
  253. // 除最大值和最小值后的平均值与标定值差值少于4后(当天计算出该结果则也不产生增量调整),就不再进行增量值调整了。
  254. if (systolicRefValue - systolicAvg < 4)
  255. {
  256. _logger.LogInformation($"diastolic 舒张压 {imeiDel}除最大值和最小值后的平均值:{diastolicAvg}与标定值:{diastolicRefValue}\n systolic 收缩压 {imeiDel}除最大值和最小值后的平均值:{systolicAvg}与标定值:{systolicRefValue}的差值(标定值-平均值)少于4后,systolic 收缩压 不再进行增量值调整");
  257. break;
  258. }
  259. if (diastolicRefValue - diastolicAvg < 4)
  260. {
  261. _logger.LogInformation($"systolic 收缩压 {imeiDel}除最大值和最小值后的平均值:{systolicAvg}与标定值:{systolicRefValue}\n diastolic 舒张压 {imeiDel}除最大值和最小值后的平均值:{diastolicAvg}与标定值:{diastolicRefValue}的差值(标定值-平均值)少于4后,diastolic 舒张压 不再进行增量值调整");
  262. break;
  263. }
  264. // 增量值=(标定值-平均值)* 0.25
  265. var currentSystolicInc = (int)((systolicRefValue - systolicAvg) * systolicAvgOffset)!;
  266. var currentDiastolicInc = (int)((diastolicRefValue - diastolicAvg) * diastolicAvgOffset)!;
  267. // 累计增量
  268. systolicInc = currentSystolicInc + lastPushSystolicInc;
  269. diastolicInc = currentDiastolicInc + lastPushDiastolicInc;
  270. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}--计算增量值" +
  271. $"\n {imeiDel} -- systolicAvg:{systolicAvg}-- systolicInc:{systolicInc}-- currentSystolicInc:{currentSystolicInc} -- lastPushSystolicInc:{lastPushSystolicInc}" +
  272. $"\n {imeiDel} -- diastolicAvg:{diastolicAvg}-- diastolicInc:{diastolicInc} --currentDiastolicInc:{currentDiastolicInc} -- lastPushDiastolicInc:{lastPushDiastolicInc}");
  273. _logger.LogInformation($"{imeiDel}--{nameof(Worker)}-- 定时校准,发给设备的绝对增量值=(上次绝对增量值+新数据的增量值)");
  274. _logger.LogInformation($"{nameof(Worker)} 开启血压标定值下发: {_configBoodPressResolver.EnableBPRefPush}");
  275. if (_configBoodPressResolver.EnableBPRefPush)
  276. // if (false) // 临时关闭
  277. {
  278. BloodPressCalibrationConfigModel bpIncData = new()
  279. {
  280. Imei = imeiDel,
  281. SystolicRefValue = SafeType.SafeInt(((int)systolicRefValue!)), //收缩压标定值,值为0 表示不生效
  282. DiastolicRefValue = SafeType.SafeInt(((int)diastolicRefValue!)), //舒张压标定值,值为0表示不生效
  283. SystolicIncValue = SafeType.SafeInt(((int)systolicInc!)), //收缩压显示增量,值为0 表示不生效
  284. DiastolicIncValue = SafeType.SafeInt(((int)diastolicInc!)) //舒张压显示增量,值为0 表示不生效
  285. };
  286. //var pushedBP = await _serviceIotWebApi.SetBloodPressCalibrationConfigAsync(bpIncData).ConfigureAwait(false);
  287. var response = await _serviceIotWebApi.SetBloodPressCalibrationConfig2Async(bpIncData).ConfigureAwait(false);
  288. var pushedBP = response.Flag;
  289. if (pushedBP)
  290. {
  291. #region 保存下推记录 stb_hm_bp_push_ref_inc_value
  292. var sql = $"INSERT INTO health_monitor.hm_bp_push_ref_inc_value_{imeiDel.Substring(imeiDel.Length - 2)} " +
  293. $"USING health_monitor.stb_hm_bp_push_ref_inc_value " +
  294. $"TAGS ('{imeiDel.Substring(imeiDel.Length - 2)}') " +
  295. $"VALUES(" +
  296. $"'{startTime:yyyy-MM-dd HH:mm:ss.fff}'," +
  297. $"'{imeiDel}'," +
  298. $"{bpIncData.SystolicRefValue}," +
  299. $"{bpIncData.DiastolicRefValue}," +
  300. $"{bpIncData.SystolicIncValue}," +
  301. $"{bpIncData.DiastolicIncValue}," +
  302. $"{false}," +
  303. $"{systolicAvg}," +
  304. $"{diastolicAvg}," +
  305. $"{systolicAvgOffset}," +
  306. $"{diastolicAvgOffset}," +
  307. $"'{statStartTime:yyyy-MM-dd HH:mm:ss.fff}'," +
  308. $"'{startTime:yyyy-MM-dd HH:mm:ss.fff}'" +
  309. $")";
  310. _serviceTDengine.ExecuteInsertSQL(sql);
  311. #endregion
  312. #region 注册定时下发
  313. // 注册下次下推
  314. var endTime = DateTime.Now;
  315. #if DEBUG
  316. //long ttl = (long)((60 * 1000-(endTime-startTime).TotalMilliseconds)/1000);
  317. //await _serviceEtcd.PutValAsync(key, imeiDel,ttl, false).ConfigureAwait(false);
  318. var interval = 0;
  319. // 获取当前时间
  320. DateTime now = DateTime.Now;
  321. // 计算距离下一个$interval天后的8点的时间间隔
  322. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute + 2, 0).AddDays(interval);
  323. TimeSpan timeUntilNextRun = nextRunTime - now;
  324. // 如果当前时间已经超过了8点,将等待到明天后的8点
  325. if (timeUntilNextRun < TimeSpan.Zero)
  326. {
  327. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromMinutes(1));
  328. nextRunTime += timeUntilNextRun;
  329. }
  330. // var ttl = timeUntilNextRun.TotalMilliseconds;
  331. long ttl = (long)((timeUntilNextRun.TotalMilliseconds - (endTime - startTime).TotalMilliseconds) / 1000);
  332. var data = new
  333. {
  334. imei = imeiDel,
  335. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  336. ttl,
  337. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  338. };
  339. var result = JsonConvert.SerializeObject(data);
  340. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  341. #else
  342. // 每$interval天,晚上8点
  343. var interval = 1;
  344. // 获取当前时间
  345. DateTime now = DateTime.Now;
  346. // 计算距离下一个$interval天后的8点的时间间隔
  347. DateTime nextRunTime = new DateTime(now.Year, now.Month, now.Day, 20, 0, 0).AddDays(interval);
  348. TimeSpan timeUntilNextRun = nextRunTime - now;
  349. // 如果当前时间已经超过了8点,将等待到明天后的8点
  350. if (timeUntilNextRun < TimeSpan.Zero)
  351. {
  352. timeUntilNextRun = timeUntilNextRun.Add(TimeSpan.FromDays(1));
  353. nextRunTime += timeUntilNextRun;
  354. }
  355. // var ttl = timeUntilNextRun.TotalMilliseconds;
  356. long ttl = (long)((timeUntilNextRun.TotalMilliseconds-(endTime-startTime).TotalMilliseconds)/1000);
  357. var data = new
  358. {
  359. imei = imeiDel,
  360. create_time = now.ToString("yyyy-MM-dd HH:mm:ss"),
  361. ttl,
  362. next_run_time = nextRunTime.ToString("yyyy-MM-dd HH:mm:ss")
  363. };
  364. var result = JsonConvert.SerializeObject(data);
  365. await _serviceEtcd.PutValAsync(key, result, ttl, false).ConfigureAwait(false);
  366. #endif
  367. #endregion
  368. }
  369. else
  370. {
  371. _logger.LogInformation($"错误响应,没有下推数据:{response.Message}");
  372. }
  373. }
  374. }
  375. else
  376. {
  377. _logger.LogInformation($"向{imeiDel}统计数据已经失效");
  378. }
  379. #endregion
  380. }
  381. break;
  382. }
  383. }
  384. catch (Exception ex)
  385. {
  386. _logger.LogInformation($"{nameof(WatchEvents)},出错: |{ex.Message}|{ex.StackTrace}");
  387. }
  388. });
  389. }
  390. /**
  391. protected override Task ExecuteAsync(CancellationToken stoppingToken)
  392. {
  393. TaskFactory factory = new(_tokenSource.Token);
  394. factory.StartNew(async () =>
  395. {
  396. if (_tokenSource.IsCancellationRequested)
  397. _logger.LogWarning("Worker exit");
  398. _logger.LogInformation("------ResolveAsync");
  399. while (!_tokenSource.IsCancellationRequested)
  400. {
  401. await _processor.ResolveAsync().ConfigureAwait(false);
  402. }
  403. }, TaskCreationOptions.LongRunning);
  404. factory.StartNew(() =>
  405. {
  406. _logger.LogInformation("------_tdEngineDataSubcribe");
  407. while (!_tokenSource.IsCancellationRequested)
  408. {
  409. _tdEngineDataSubcribe.BeginListen(_tokenSource.Token);
  410. }
  411. }, TaskCreationOptions.LongRunning);
  412. Task.Run(() =>
  413. _serviceEtcd.WacthKeysWithPrefixResponseAsync($"health_moniter/schedule_push", WatchEvents)
  414. , stoppingToken);
  415. return Task.Delay(1000, _tokenSource.Token);
  416. }
  417. **/
  418. /**
  419. private void WatchEvents(WatchEvent[] response)
  420. {
  421. foreach (WatchEvent e1 in response)
  422. {
  423. // Console.WriteLine($"{nameof(WatchEventsAsync)} --- {e1.Key}:{e1.Value}:{e1.Type}");
  424. switch (e1.Type.ToString())
  425. {
  426. //case "Put":
  427. // // 获取时间点计算TTL
  428. // break;
  429. case "Delete":
  430. // TTL到了重新计算TTL,下发
  431. Console.WriteLine($"--- {e1.Key}:{e1.Value}:{e1.Type}");
  432. break;
  433. }
  434. }
  435. }
  436. */
  437. }
  438. }