电子围栏推送服务
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

538 Zeilen
26KB

  1. using Microsoft.Extensions.Options;
  2. using Newtonsoft.Json;
  3. using System.Text;
  4. using TelpoDataService.Util.Clients;
  5. using TelpoDataService.Util;
  6. using TelpoDataService.Util.Entities.GpsCard;
  7. using TelpoDataService.Util.Entities.GpsLocationHistory;
  8. using TelpoDataService.Util.Models;
  9. using TelpoDataService.Util.QueryObjects;
  10. using TelpoPush.Fence.Worker.Common;
  11. using TelpoPush.Fence.Worker.Models.CacheTemplates;
  12. using TelpoPush.Fence.Worker.Models.Config;
  13. using TelpoPush.Fence.Worker.Models.Enum;
  14. using TelpoPush.Fence.Worker.Models.Fence;
  15. using TelpoPush.Fence.Worker.Models.MqTemplates;
  16. using TelpoPush.Fence.Worker.Models.PushTemplates;
  17. using TelpoPush.Fence.Worker.Service.Cache;
  18. using TelpoPush.Fence.Worker.Service.Mq;
  19. namespace TelpoPush.Fence.Worker.Handlers
  20. {
  21. public class FenceProcess
  22. {
  23. private readonly static object _GeofenceStatusLock = new object();
  24. private readonly static object _GeofenceDataLock = new object();
  25. private readonly GpsCardAccessorClient<GpsGeofenceStatus> _fenceStatusApiClient;
  26. private readonly GpsLocationHistoryAccessorClient<HisGpsAlarm> _alarmApiClient;
  27. private readonly IHostEnvironment _env;
  28. private readonly ILogger<FenceProcess> _logger;
  29. private readonly HttpHelperAsync _httpHelper;
  30. private readonly RedisUtil _redis;
  31. private readonly MqProcessMessage _serviceMqProcess;
  32. private readonly FenceConfig _positionConfig;
  33. public FenceProcess(
  34. IHostEnvironment env,
  35. ILogger<FenceProcess> logger,
  36. HttpHelperAsync httpHelper,
  37. RedisUtil redis,
  38. GpsCardAccessorClient<GpsGeofenceStatus> fenceStatusApiClient,
  39. GpsLocationHistoryAccessorClient<HisGpsAlarm> alarmApiClient,
  40. MqProcessMessage serviceMqProcess,
  41. IOptions<FenceConfig> positionConfig
  42. )
  43. {
  44. _env = env;
  45. _logger = logger;
  46. _httpHelper = httpHelper;
  47. _redis = redis;
  48. _positionConfig = positionConfig.Value;
  49. _serviceMqProcess = serviceMqProcess;
  50. _fenceStatusApiClient = fenceStatusApiClient;
  51. _alarmApiClient = alarmApiClient;
  52. }
  53. public async Task SendFence(string message)
  54. {
  55. _redis.ClearGpsDeviceFence();
  56. BaseModel model = JsonConvert.DeserializeObject<BaseModel>(message);
  57. var loc = model?.data;
  58. _logger.LogInformation($"设备{loc.imei},获取kafka数据并解析<{JsonConvert.SerializeObject(model)}>");
  59. if ((int)loc.locationType == 2) // 限制条件:locationType=2 LBS定位不推送
  60. {
  61. _logger.LogInformation($"设备{loc.imei},定位类型为LBS:<locationType={(int)loc.locationType}>不做处理");
  62. return;
  63. }
  64. DateTime time = DateTime.Parse(model.time); //=LastUpdate
  65. DateTime UtcDate = DateTime.Parse(loc.UtcDate);
  66. bool isPush = await _redis.GetIsDateStatus(new DeviceTime
  67. {
  68. imei = loc.imei,
  69. dt = time
  70. });
  71. isPush = true;
  72. if (!isPush)
  73. _logger.LogInformation($"数据未处理(历史数据):{loc.imei},{model.time}");
  74. var fenceObj = await _redis.GetGpsDeviceFence(loc.imei);
  75. if (fenceObj == null)
  76. _logger.LogInformation($"设备<{loc.imei}>还未设置电子围栏!");
  77. int radius = loc.Radius;
  78. GpsFencePoint location = new GpsFencePoint()
  79. {
  80. Longitude = Convert.ToDouble(loc.gaodeLongitude),
  81. Latitude = Convert.ToDouble(loc.gaodeLatitude)
  82. };
  83. var lpt = new GpsPoint(loc.originalLatitude, loc.originalLongitude);
  84. bool isInside = false; //{false=外面,true=里面}
  85. bool isInside_old = false; //{false=外面,true=里面}
  86. try
  87. {
  88. foreach (var fence in fenceObj.fenceList)
  89. {
  90. if (!Convert.ToBoolean(fence.isActive)) //围栏开关 0是关 1是开
  91. {
  92. _logger.LogInformation($"围栏<{fence.geofenceId}:{fence.geofenceName}>未生效!");
  93. continue;
  94. }
  95. //当前gaode坐标为中心点,精度(Radius)为半径画圆
  96. double _lat, _lng;
  97. GeoConvert.G2Gps((double)loc.gaodeLatitude, (double)loc.gaodeLongitude, out _lat, out _lng);
  98. var _cpt = new GpsPoint(Convert.ToDecimal(_lat), Convert.ToDecimal(_lng));
  99. var _circle = new Circle(_cpt, loc.Radius);
  100. switch (fence.fenceType) // 1 为圆形,2 多边形
  101. {
  102. case 1: // 圆形
  103. {
  104. foreach (var item in fence.pointList)
  105. {
  106. //圆形围栏坐标+半径
  107. double lat, lng;
  108. GeoConvert.G2Gps((double)item.latitude, (double)item.longitude, out lat, out lng);
  109. var cpt = new GpsPoint(Convert.ToDecimal(lat), Convert.ToDecimal(lng));
  110. var circle = new Circle(cpt, item.radius);
  111. //是否在围栏范围内
  112. isInside = GeoUtils.IsPointInCircle(lpt, circle);
  113. isInside_old = isInside;
  114. if (!isInside)
  115. {
  116. isInside_old = isInside;
  117. //圆和圆是否有交点
  118. isInside = GeoUtils.IsCircle2Circle(_circle, circle);
  119. }
  120. }
  121. }
  122. break;
  123. case 2: //多边形
  124. List<GpsFencePoint> pointList = new List<GpsFencePoint>();
  125. foreach (var item in fence.pointList)
  126. {
  127. pointList.Add(new GpsFencePoint
  128. {
  129. Longitude = (double)item.longitude,
  130. Latitude = (double)item.latitude
  131. });
  132. }
  133. isInside = GeofenceHelper.IsPolygonContainsPoint(pointList, new GpsFencePoint { Longitude = (double)loc.gaodeLongitude, Latitude = (double)loc.gaodeLatitude });
  134. isInside_old = isInside;
  135. if (!isInside)
  136. {
  137. //圆和多边形围栏否有交点
  138. var degree = GeofenceHelper.CalcDisgreeFromRadius(radius);
  139. isInside = CirclePolygonAlgorithm.IsIntersect(location, degree, pointList);
  140. isInside = GeofenceHelper.IsPolygonIntersectCircle(pointList, location, radius);
  141. }
  142. break;
  143. default:
  144. break;
  145. }
  146. //围栏进处理逻辑----------------------
  147. // [上次(历史缓存)状态]
  148. //lastStatus.isInside => false:在里面、false:在外面;
  149. //[当前状态]
  150. //sInside => false:外面、true:里面
  151. //=============================================
  152. //进入围栏:isInside = true
  153. // status = true
  154. //离开围栏:isInside = false
  155. // status = false
  156. //进入围栏:isInside = true
  157. // status(没有历史记录)
  158. var lastStatus = await GetLastGeofenceStatus(loc.imei, fence.geofenceId, fence.geofenceName);
  159. if (lastStatus != null)
  160. {
  161. if (isInside == lastStatus.isInside)
  162. await DataSaveAsync(model, fence, isInside, isInside_old, lastStatus.isInside, true).ConfigureAwait(false);
  163. }
  164. else
  165. {
  166. if (isInside)
  167. await DataSaveAsync(model, fence, isInside, true, true, false).ConfigureAwait(false);
  168. }
  169. }
  170. }
  171. catch (Exception ex)
  172. {
  173. _logger.LogError($"设备<{loc.imei}>进出围栏报警处理逻辑[异常] {ex.Message}\n{ex.StackTrace}\n{ex.HResult}");
  174. }
  175. }
  176. /// <summary>
  177. /// 进出记录处理
  178. /// </summary>
  179. /// <param name="model">当前定位数据</param>
  180. /// <param name="fence">当前围栏信息</param>
  181. /// <param name="isInside">当前围栏状态[面到面]:(sInside => false:外面、true:里面)</param>
  182. /// <param name="isInside_old">当前围栏状态[点到面]:(isInside_old => false:外面、true:里面)</param>
  183. /// <param name="status">最近(缓存)围栏状态[数据\Redis]:(false:在里面、false:在外面)</param>
  184. /// <param name="isRecord">是否第一次触发围栏状态(进入)</param>
  185. /// <returns></returns>
  186. public async Task DataSaveAsync(BaseModel model, DeviceFenceList fence, bool isInside, bool isInside_old, bool status, bool isRecord)
  187. {
  188. lock (_GeofenceDataLock)
  189. {
  190. var loc = model.data;
  191. string key = loc.imei + "_" + fence.geofenceId;
  192. DateTime UtcDate = DateTime.Parse(loc.UtcDate);
  193. bool? saveStatus = null;
  194. bool isExt = false; // 是否保存警告数据,并推送进/出围栏报警
  195. int isReuslt = 0; //最终进出结果(3=进,4=出)
  196. string deviceName = "";//设备昵称
  197. string alarmTypeTilte = ""; //进出描述
  198. string alarmTypeTilteLog = ""; //日志描述
  199. string fenceTypeStr = fence.fenceType == 1 ? "圆形围栏" : "多边形围栏"; //围栏类型描述
  200. _logger.LogInformation($"{fenceTypeStr}:{fence.geofenceName},Id:{fence.geofenceId},当前状态:({isInside}){(isInside ? "在里面" : "在外面")},上次状态:({status}){(status ? "在外面" : "在里面")}");
  201. //进出逻辑判断
  202. #region 进围栏报警
  203. if (isInside && status) // 当前在里面,上次记录为在外面,则触发进围栏报警
  204. {
  205. isReuslt = Convert.ToInt32(AlarmType.Entry);
  206. alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry);
  207. saveStatus = false;
  208. #region 保存警告数据,并推送 进围栏报警
  209. //进围栏是否报警条件
  210. if (fence.alarmType == Convert.ToUInt32(GeofenceType.Entry) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both))
  211. isExt = true;
  212. #endregion
  213. }
  214. #endregion
  215. #region 出围栏报警
  216. else if (!isInside && !status) // 当前在外面,上次记录为在里面,则触发出围栏报警
  217. {
  218. isReuslt = Convert.ToInt32(AlarmType.Exit);
  219. alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit);
  220. saveStatus = true;
  221. #region 保存警告数据,并推送 出围栏报警
  222. //出围栏是否报警条件
  223. if (fence.alarmType == Convert.ToUInt32(GeofenceType.Exit) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both))
  224. isExt = true;
  225. #endregion
  226. }
  227. #endregion
  228. //进出数据处理
  229. if (isReuslt > 0)
  230. {
  231. #region 保存状态,更新缓存
  232. //更新最后状态到数据库
  233. var dfstatus = new GpsGeofenceStatus
  234. {
  235. IsGeofenceStatus = saveStatus.Value,
  236. Serialno = loc.imei,
  237. GeofenceId = fence.geofenceId,
  238. };
  239. if (!isRecord)
  240. {
  241. dfstatus.Id = Guid.NewGuid().ToString();
  242. alarmTypeTilteLog = "第一次" + alarmTypeTilte;
  243. }
  244. else
  245. alarmTypeTilteLog = alarmTypeTilte;
  246. var saveGeofenceStatus = SaveGeofenceStatus(dfstatus);
  247. if (!saveGeofenceStatus.result)
  248. _logger.LogError(saveGeofenceStatus.message);
  249. else
  250. //更新缓存
  251. _redis.SetFenceLastStatus(key, new LastStatusInfo()
  252. {
  253. imei = loc.imei,
  254. fenceId = fence.geofenceId,
  255. fenceInfo = fence.geofenceName,
  256. typeId = isReuslt,
  257. typeInfo = Enum.GetName(typeof(AlarmTypeChinese), isReuslt),
  258. isInside = saveStatus.Value,
  259. address = loc.address,
  260. dt = model.time
  261. }).Wait();
  262. #endregion
  263. if (isExt)
  264. {
  265. #region 保存警告数据,并推送 进/出围栏报警
  266. string GeofenceResult = "";
  267. StringBuilder sb_old = new StringBuilder();
  268. StringBuilder sb = new StringBuilder();
  269. string url = "https://id.ssjlai.com/antpayweb/#/gaode-point-in-ring?";
  270. if (fence.fenceType == 1)
  271. {
  272. foreach (var item in fence.pointList)
  273. {
  274. sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}");
  275. sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}");
  276. }
  277. }
  278. else
  279. {
  280. string pointListStr = "";
  281. foreach (var item in fence.pointList)
  282. {
  283. pointListStr += $"{item.longitude}-{item.latitude},";
  284. }
  285. sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={pointListStr.Trim(',')},{fence.geofenceId},0");
  286. sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={pointListStr.Trim(',')},{fence.geofenceId},0");
  287. }
  288. GeofenceResult = sb.ToString() + ";" + sb_old.ToString();
  289. //设备昵称
  290. var deviceInfo = _redis.GetGpsDevice(loc.imei).Result;
  291. if (deviceInfo != null)
  292. deviceName = deviceInfo.deviceName;
  293. //警告保存到数据库
  294. var alarm = new HisGpsAlarm
  295. {
  296. DeviceId = loc.DeviceId,
  297. BaiduLat = loc.baiduLatitude,
  298. BaiduLng = loc.baiduLongitude,
  299. CreateTime = DateTime.Now,
  300. DeviceUtcTime = UtcDate,
  301. GeofenceId = fence.geofenceId,
  302. Glat = loc.gaodeLatitude,
  303. Glng = loc.gaodeLongitude,
  304. MessageId = Guid.NewGuid().ToString("D"),
  305. Olat = loc.originalLatitude,
  306. Olng = loc.originalLongitude,
  307. Serialno = loc.imei,
  308. DeviceName = deviceName,
  309. TypeId = isReuslt,
  310. Address = loc.address,
  311. Remarks = alarmTypeTilte + " " + fence.geofenceName,
  312. GeofenceResult = GeofenceResult
  313. };
  314. _alarmApiClient.Add(alarm);
  315. #region 微信推送(启用)
  316. PushWxTemplate wxModel = new PushWxTemplate
  317. {
  318. deviceId = loc.DeviceId,
  319. imei = loc.imei,
  320. alarmTypeId = alarm.TypeId,
  321. alarmDeviceName = alarm.DeviceName,
  322. alarmRemarks = alarmTypeTilte + " " + fence.geofenceName,
  323. address = loc.address,
  324. };
  325. _serviceMqProcess.ProcessWxAlarm(wxModel, fence.geofenceId, model.time).Wait();
  326. #endregion
  327. #region 第三方推送(停用)
  328. // 进围栏报警
  329. PushXiaoAnPushTemplate statusModel = new PushXiaoAnPushTemplate
  330. {
  331. Address = fence.geofenceName,
  332. DateTime = model.time,
  333. NickName = loc.imei,
  334. Title = alarmTypeTilte + " 报警",
  335. TextContent = alarmTypeTilte + " " + fence.geofenceName,
  336. };
  337. //PushStatusXiaoAn(loc.imei, statusModel);
  338. //_serviceMqProcess.ProcessThirdAlarm(statusModel, MqAlarmType.EnterFenceAlarm, model.time);
  339. // _serviceMqProcess.ProcessThirdAlarm(alarm, MqAlarmType.EnterFenceAlarm, model.time);
  340. #endregion
  341. #region 数据服务(java)部分推送
  342. if (fence.appType == 2)
  343. {
  344. var push = new
  345. {
  346. BaiduLatitude = loc.baiduLatitude,
  347. BaiduLongitude = loc.baiduLongitude,
  348. GaodeLatitude = loc.gaodeLatitude,
  349. GaodeLongitude = loc.gaodeLongitude,
  350. OriginalLatitude = loc.originalLatitude,
  351. OriginalLongitude = loc.originalLongitude,
  352. FenceId = fence.geofenceId,
  353. FenceName = fence.geofenceName,
  354. Address = loc.address,
  355. Info = alarmTypeTilte + " " + fence.geofenceName,
  356. };
  357. var commonInfo = new
  358. {
  359. Data = push,
  360. Imei = loc.imei,
  361. Time = model.time,
  362. AlarmType = isReuslt
  363. };
  364. _serviceMqProcess.ProcessPushService(commonInfo, loc.imei).Wait();
  365. }
  366. #endregion
  367. _logger.LogError($"设备{loc.imei}{alarmTypeTilteLog},loc.UtcDate: {UtcDate.ToString("yyyy-MM-dd HH:mm:ss")},loc.LastUpdate: {model.time},DateTime.Now: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
  368. #endregion
  369. }
  370. }
  371. }
  372. }
  373. //获取最近一次围栏进出状态
  374. public async Task<LastStatusInfo> GetLastGeofenceStatus(string imei, string fenceId, string fenceName)
  375. {
  376. LastStatusInfo model = null;
  377. string key = imei + "_" + fenceId;
  378. var lastStatus = await _redis.GetFenceLastStatus(key);
  379. if (lastStatus != null)
  380. return lastStatus;
  381. else
  382. {
  383. // 数据服务调用
  384. var param = new GeneralParam
  385. {
  386. Filters = new List<QueryFilterCondition>
  387. {
  388. new QueryFilterCondition
  389. {
  390. Key=nameof(GpsGeofenceStatus.GeofenceId),
  391. Value=fenceId,
  392. ValueType=QueryValueTypeEnum.String,
  393. Operator=QueryOperatorEnum.Equal
  394. }
  395. }
  396. };
  397. var gfs = _fenceStatusApiClient.GetFirst(param);
  398. if (gfs != null)
  399. {
  400. model = new LastStatusInfo()
  401. {
  402. imei = gfs.Serialno,
  403. fenceId = gfs.GeofenceId,
  404. fenceInfo = fenceName,
  405. typeId = gfs.IsGeofenceStatus ? Convert.ToInt32(AlarmType.Exit) : Convert.ToInt32(AlarmType.Entry),
  406. typeInfo = gfs.IsGeofenceStatus ? Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit) : Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry),
  407. isInside = gfs.IsGeofenceStatus,
  408. address = "",
  409. dt = gfs.CreateTime.Value.ToString("yyyy-MM-dd HH:mm:ss")
  410. };
  411. await _redis.SetFenceLastStatus(key, model);
  412. }
  413. }
  414. return model;
  415. }
  416. //保存围栏状态
  417. public (bool result, string message) SaveGeofenceStatus(GpsGeofenceStatus gfs)
  418. {
  419. lock (_GeofenceStatusLock)
  420. {
  421. bool result = false;
  422. string message = String.Empty;
  423. try
  424. {
  425. var param = new GeneralParam
  426. {
  427. Filters = new List<QueryFilterCondition>
  428. {
  429. new QueryFilterCondition
  430. {
  431. Key=nameof(GpsGeofenceStatus.GeofenceId),
  432. Value=gfs.GeofenceId,
  433. ValueType=QueryValueTypeEnum.String,
  434. Operator=QueryOperatorEnum.Equal
  435. }
  436. }
  437. };
  438. List<GpsGeofenceStatus> list = _fenceStatusApiClient.GetList(param).ToList();
  439. if (list.Count > 0)
  440. {
  441. if (list.Count == 1)
  442. {
  443. var gfStatus = list.FirstOrDefault();
  444. gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus;
  445. gfStatus.CreateTime = DateTime.Now;
  446. _fenceStatusApiClient.Update(gfStatus);
  447. }
  448. else
  449. {
  450. //只保留一条记录,多余的删除(历史遗留数据)
  451. int loop = 0;
  452. foreach (var item in list)
  453. {
  454. loop++;
  455. if (loop == 1)
  456. {
  457. var gfStatus = list.FirstOrDefault();
  458. gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus;
  459. gfStatus.CreateTime = DateTime.Now;
  460. _fenceStatusApiClient.Update(gfStatus);
  461. }
  462. else
  463. {
  464. _fenceStatusApiClient.Delete(item);
  465. }
  466. }
  467. }
  468. }
  469. else
  470. {
  471. gfs.CreateTime = DateTime.Now;
  472. _fenceStatusApiClient.Add(gfs);
  473. _logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>");
  474. }
  475. #region 旧代码
  476. // GeofenceStatus gfStatus = client.GetFirst(param);
  477. //if (gfStatus != null)
  478. //{
  479. // gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus;
  480. // gfStatus.CreateTime = DateTime.Now;
  481. // client.Update(gfStatus);
  482. // //只保留一条记录,多余的删除(历史遗留数据)
  483. // List<GeofenceStatus> list = client.GetList(param).ToList();
  484. // if (list.Count > 1)
  485. // {
  486. // foreach (var item in list)
  487. // {
  488. // if (gfStatus.Id != item.Id)
  489. // {
  490. // client.Delete(item);
  491. // }
  492. // }
  493. // }
  494. // logger.LogInformation($"修改围栏状态:<{JsonConvert.SerializeObject(gfStatus)}>");
  495. //}
  496. //else
  497. //{
  498. // gfs.CreateTime = DateTime.Now;
  499. // client.Add(gfs);
  500. // logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>");
  501. //}
  502. #endregion
  503. message = $"保存围栏状态发生成功!";
  504. result = true;
  505. }
  506. catch (Exception ex)
  507. {
  508. message = $"保存围栏状态发生异常:{ex.Message},{ex.StackTrace}!";
  509. }
  510. return (result, message);
  511. }
  512. }
  513. }
  514. }