using Microsoft.Extensions.Options; using Newtonsoft.Json; using System.Text; using TelpoDataService.Util.Clients; using TelpoDataService.Util; using TelpoDataService.Util.Entities.GpsCard; using TelpoDataService.Util.Entities.GpsLocationHistory; using TelpoDataService.Util.Models; using TelpoDataService.Util.QueryObjects; using TelpoPush.Fence.Worker.Common; using TelpoPush.Fence.Worker.Models.CacheTemplates; using TelpoPush.Fence.Worker.Models.Config; using TelpoPush.Fence.Worker.Models.Enum; using TelpoPush.Fence.Worker.Models.Fence; using TelpoPush.Fence.Worker.Models.MqTemplates; using TelpoPush.Fence.Worker.Models.PushTemplates; using TelpoPush.Fence.Worker.Service.Cache; using TelpoPush.Fence.Worker.Service.Mq; namespace TelpoPush.Fence.Worker.Handlers { public class FenceProcess { private readonly static object _GeofenceStatusLock = new object(); private readonly static object _GeofenceDataLock = new object(); private readonly GpsCardAccessorClient _fenceStatusApiClient; private readonly GpsLocationHistoryAccessorClient _alarmApiClient; private readonly IHostEnvironment _env; private readonly ILogger _logger; private readonly HttpHelperAsync _httpHelper; private readonly RedisUtil _redis; private readonly MqProcessMessage _serviceMqProcess; private readonly FenceConfig _positionConfig; public FenceProcess( IHostEnvironment env, ILogger logger, HttpHelperAsync httpHelper, RedisUtil redis, GpsCardAccessorClient fenceStatusApiClient, GpsLocationHistoryAccessorClient alarmApiClient, MqProcessMessage serviceMqProcess, IOptions positionConfig ) { _env = env; _logger = logger; _httpHelper = httpHelper; _redis = redis; _positionConfig = positionConfig.Value; _serviceMqProcess = serviceMqProcess; _fenceStatusApiClient = fenceStatusApiClient; _alarmApiClient = alarmApiClient; } public async Task SendFence(string message) { _redis.ClearGpsDeviceFence(); BaseModel model = JsonConvert.DeserializeObject(message); var loc = model?.data; _logger.LogInformation($"设备{loc.imei},获取kafka数据并解析<{JsonConvert.SerializeObject(model)}>"); if ((int)loc.locationType == 2) // 限制条件:locationType=2 LBS定位不推送 { _logger.LogInformation($"设备{loc.imei},定位类型为LBS:不做处理"); return; } DateTime time = DateTime.Parse(model.time); //=LastUpdate DateTime UtcDate = DateTime.Parse(loc.UtcDate); bool isPush = await _redis.GetIsDateStatus(new DeviceTime { imei = loc.imei, dt = time }); isPush = true; if (!isPush) _logger.LogInformation($"数据未处理(历史数据):{loc.imei},{model.time}"); var fenceObj = await _redis.GetGpsDeviceFence(loc.imei); if (fenceObj == null) _logger.LogInformation($"设备<{loc.imei}>还未设置电子围栏!"); int radius = loc.Radius; GpsFencePoint location = new GpsFencePoint() { Longitude = Convert.ToDouble(loc.gaodeLongitude), Latitude = Convert.ToDouble(loc.gaodeLatitude) }; var lpt = new GpsPoint(loc.originalLatitude, loc.originalLongitude); bool isInside = false; //{false=外面,true=里面} bool isInside_old = false; //{false=外面,true=里面} try { foreach (var fence in fenceObj.fenceList) { if (!Convert.ToBoolean(fence.isActive)) //围栏开关 0是关 1是开 { _logger.LogInformation($"围栏<{fence.geofenceId}:{fence.geofenceName}>未生效!"); continue; } //当前gaode坐标为中心点,精度(Radius)为半径画圆 double _lat, _lng; GeoConvert.G2Gps((double)loc.gaodeLatitude, (double)loc.gaodeLongitude, out _lat, out _lng); var _cpt = new GpsPoint(Convert.ToDecimal(_lat), Convert.ToDecimal(_lng)); var _circle = new Circle(_cpt, loc.Radius); switch (fence.fenceType) // 1 为圆形,2 多边形 { case 1: // 圆形 { foreach (var item in fence.pointList) { //圆形围栏坐标+半径 double lat, lng; GeoConvert.G2Gps((double)item.latitude, (double)item.longitude, out lat, out lng); var cpt = new GpsPoint(Convert.ToDecimal(lat), Convert.ToDecimal(lng)); var circle = new Circle(cpt, item.radius); //是否在围栏范围内 isInside = GeoUtils.IsPointInCircle(lpt, circle); isInside_old = isInside; if (!isInside) { isInside_old = isInside; //圆和圆是否有交点 isInside = GeoUtils.IsCircle2Circle(_circle, circle); } } } break; case 2: //多边形 List pointList = new List(); foreach (var item in fence.pointList) { pointList.Add(new GpsFencePoint { Longitude = (double)item.longitude, Latitude = (double)item.latitude }); } isInside = GeofenceHelper.IsPolygonContainsPoint(pointList, new GpsFencePoint { Longitude = (double)loc.gaodeLongitude, Latitude = (double)loc.gaodeLatitude }); isInside_old = isInside; if (!isInside) { //圆和多边形围栏否有交点 var degree = GeofenceHelper.CalcDisgreeFromRadius(radius); isInside = CirclePolygonAlgorithm.IsIntersect(location, degree, pointList); isInside = GeofenceHelper.IsPolygonIntersectCircle(pointList, location, radius); } break; default: break; } //围栏进处理逻辑---------------------- // [上次(历史缓存)状态] //lastStatus.isInside => false:在里面、false:在外面; //[当前状态] //sInside => false:外面、true:里面 //============================================= //进入围栏:isInside = true // status = true //离开围栏:isInside = false // status = false //进入围栏:isInside = true // status(没有历史记录) var lastStatus = await GetLastGeofenceStatus(loc.imei, fence.geofenceId, fence.geofenceName); if (lastStatus != null) { if (isInside == lastStatus.isInside) await DataSaveAsync(model, fence, isInside, isInside_old, lastStatus.isInside, true).ConfigureAwait(false); } else { if (isInside) await DataSaveAsync(model, fence, isInside, true, true, false).ConfigureAwait(false); } } } catch (Exception ex) { _logger.LogError($"设备<{loc.imei}>进出围栏报警处理逻辑[异常] {ex.Message}\n{ex.StackTrace}\n{ex.HResult}"); } } /// /// 进出记录处理 /// /// 当前定位数据 /// 当前围栏信息 /// 当前围栏状态[面到面]:(sInside => false:外面、true:里面) /// 当前围栏状态[点到面]:(isInside_old => false:外面、true:里面) /// 最近(缓存)围栏状态[数据\Redis]:(false:在里面、false:在外面) /// 是否第一次触发围栏状态(进入) /// public async Task DataSaveAsync(BaseModel model, DeviceFenceList fence, bool isInside, bool isInside_old, bool status, bool isRecord) { lock (_GeofenceDataLock) { var loc = model.data; string key = loc.imei + "_" + fence.geofenceId; DateTime UtcDate = DateTime.Parse(loc.UtcDate); bool? saveStatus = null; bool isExt = false; // 是否保存警告数据,并推送进/出围栏报警 int isReuslt = 0; //最终进出结果(3=进,4=出) string deviceName = "";//设备昵称 string alarmTypeTilte = ""; //进出描述 string alarmTypeTilteLog = ""; //日志描述 string fenceTypeStr = fence.fenceType == 1 ? "圆形围栏" : "多边形围栏"; //围栏类型描述 _logger.LogInformation($"{fenceTypeStr}:{fence.geofenceName},Id:{fence.geofenceId},当前状态:({isInside}){(isInside ? "在里面" : "在外面")},上次状态:({status}){(status ? "在外面" : "在里面")}"); //进出逻辑判断 #region 进围栏报警 if (isInside && status) // 当前在里面,上次记录为在外面,则触发进围栏报警 { isReuslt = Convert.ToInt32(AlarmType.Entry); alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry); saveStatus = false; #region 保存警告数据,并推送 进围栏报警 //进围栏是否报警条件 if (fence.alarmType == Convert.ToUInt32(GeofenceType.Entry) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both)) isExt = true; #endregion } #endregion #region 出围栏报警 else if (!isInside && !status) // 当前在外面,上次记录为在里面,则触发出围栏报警 { isReuslt = Convert.ToInt32(AlarmType.Exit); alarmTypeTilte = Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit); saveStatus = true; #region 保存警告数据,并推送 出围栏报警 //出围栏是否报警条件 if (fence.alarmType == Convert.ToUInt32(GeofenceType.Exit) || fence.alarmType == Convert.ToUInt32(GeofenceType.Both)) isExt = true; #endregion } #endregion //进出数据处理 if (isReuslt > 0) { #region 保存状态,更新缓存 //更新最后状态到数据库 var dfstatus = new GpsGeofenceStatus { IsGeofenceStatus = saveStatus.Value, Serialno = loc.imei, GeofenceId = fence.geofenceId, }; if (!isRecord) { dfstatus.Id = Guid.NewGuid().ToString(); alarmTypeTilteLog = "第一次" + alarmTypeTilte; } else alarmTypeTilteLog = alarmTypeTilte; var saveGeofenceStatus = SaveGeofenceStatus(dfstatus); if (!saveGeofenceStatus.result) _logger.LogError(saveGeofenceStatus.message); else //更新缓存 _redis.SetFenceLastStatus(key, new LastStatusInfo() { imei = loc.imei, fenceId = fence.geofenceId, fenceInfo = fence.geofenceName, typeId = isReuslt, typeInfo = Enum.GetName(typeof(AlarmTypeChinese), isReuslt), isInside = saveStatus.Value, address = loc.address, dt = model.time }).Wait(); #endregion if (isExt) { #region 保存警告数据,并推送 进/出围栏报警 string GeofenceResult = ""; StringBuilder sb_old = new StringBuilder(); StringBuilder sb = new StringBuilder(); string url = "https://id.ssjlai.com/antpayweb/#/gaode-point-in-ring?"; if (fence.fenceType == 1) { foreach (var item in fence.pointList) { sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}"); sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={item.longitude},{item.latitude},{fence.geofenceId},{item.radius}"); } } else { string pointListStr = ""; foreach (var item in fence.pointList) { pointListStr += $"{item.longitude}-{item.latitude},"; } sb.Append($"新算法【出围栏({isInside})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},{loc.Radius}&polygon={pointListStr.Trim(',')},{fence.geofenceId},0"); sb_old.Append($"旧算法【出围栏({isInside_old})】:{url}center={loc.gaodeLongitude},{loc.gaodeLatitude},0&polygon={pointListStr.Trim(',')},{fence.geofenceId},0"); } GeofenceResult = sb.ToString() + ";" + sb_old.ToString(); //设备昵称 var deviceInfo = _redis.GetGpsDevice(loc.imei).Result; if (deviceInfo != null) deviceName = deviceInfo.deviceName; //警告保存到数据库 var alarm = new HisGpsAlarm { DeviceId = loc.DeviceId, BaiduLat = loc.baiduLatitude, BaiduLng = loc.baiduLongitude, CreateTime = DateTime.Now, DeviceUtcTime = UtcDate, GeofenceId = fence.geofenceId, Glat = loc.gaodeLatitude, Glng = loc.gaodeLongitude, MessageId = Guid.NewGuid().ToString("D"), Olat = loc.originalLatitude, Olng = loc.originalLongitude, Serialno = loc.imei, DeviceName = deviceName, TypeId = isReuslt, Address = loc.address, Remarks = alarmTypeTilte + " " + fence.geofenceName, GeofenceResult = GeofenceResult }; _alarmApiClient.Add(alarm); #region 微信推送(启用) PushWxTemplate wxModel = new PushWxTemplate { deviceId = loc.DeviceId, imei = loc.imei, alarmTypeId = alarm.TypeId, alarmDeviceName = alarm.DeviceName, alarmRemarks = alarmTypeTilte + " " + fence.geofenceName, address = loc.address, }; _serviceMqProcess.ProcessWxAlarm(wxModel, fence.geofenceId, model.time).Wait(); #endregion #region 第三方推送(停用) // 进围栏报警 PushXiaoAnPushTemplate statusModel = new PushXiaoAnPushTemplate { Address = fence.geofenceName, DateTime = model.time, NickName = loc.imei, Title = alarmTypeTilte + " 报警", TextContent = alarmTypeTilte + " " + fence.geofenceName, }; //PushStatusXiaoAn(loc.imei, statusModel); //_serviceMqProcess.ProcessThirdAlarm(statusModel, MqAlarmType.EnterFenceAlarm, model.time); // _serviceMqProcess.ProcessThirdAlarm(alarm, MqAlarmType.EnterFenceAlarm, model.time); #endregion #region 数据服务(java)部分推送 if (fence.appType == 2) { var push = new { BaiduLatitude = loc.baiduLatitude, BaiduLongitude = loc.baiduLongitude, GaodeLatitude = loc.gaodeLatitude, GaodeLongitude = loc.gaodeLongitude, OriginalLatitude = loc.originalLatitude, OriginalLongitude = loc.originalLongitude, FenceId = fence.geofenceId, FenceName = fence.geofenceName, Address = loc.address, Info = alarmTypeTilte + " " + fence.geofenceName, }; var commonInfo = new { Data = push, Imei = loc.imei, Time = model.time, AlarmType = isReuslt }; _serviceMqProcess.ProcessPushService(commonInfo, loc.imei).Wait(); } #endregion _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")}"); #endregion } } } } //获取最近一次围栏进出状态 public async Task GetLastGeofenceStatus(string imei, string fenceId, string fenceName) { LastStatusInfo model = null; string key = imei + "_" + fenceId; var lastStatus = await _redis.GetFenceLastStatus(key); if (lastStatus != null) return lastStatus; else { // 数据服务调用 var param = new GeneralParam { Filters = new List { new QueryFilterCondition { Key=nameof(GpsGeofenceStatus.GeofenceId), Value=fenceId, ValueType=QueryValueTypeEnum.String, Operator=QueryOperatorEnum.Equal } } }; var gfs = _fenceStatusApiClient.GetFirst(param); if (gfs != null) { model = new LastStatusInfo() { imei = gfs.Serialno, fenceId = gfs.GeofenceId, fenceInfo = fenceName, typeId = gfs.IsGeofenceStatus ? Convert.ToInt32(AlarmType.Exit) : Convert.ToInt32(AlarmType.Entry), typeInfo = gfs.IsGeofenceStatus ? Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Exit) : Enum.GetName(typeof(AlarmTypeChinese), AlarmType.Entry), isInside = gfs.IsGeofenceStatus, address = "", dt = gfs.CreateTime.Value.ToString("yyyy-MM-dd HH:mm:ss") }; await _redis.SetFenceLastStatus(key, model); } } return model; } //保存围栏状态 public (bool result, string message) SaveGeofenceStatus(GpsGeofenceStatus gfs) { lock (_GeofenceStatusLock) { bool result = false; string message = String.Empty; try { var param = new GeneralParam { Filters = new List { new QueryFilterCondition { Key=nameof(GpsGeofenceStatus.GeofenceId), Value=gfs.GeofenceId, ValueType=QueryValueTypeEnum.String, Operator=QueryOperatorEnum.Equal } } }; List list = _fenceStatusApiClient.GetList(param).ToList(); if (list.Count > 0) { if (list.Count == 1) { var gfStatus = list.FirstOrDefault(); gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; gfStatus.CreateTime = DateTime.Now; _fenceStatusApiClient.Update(gfStatus); } else { //只保留一条记录,多余的删除(历史遗留数据) int loop = 0; foreach (var item in list) { loop++; if (loop == 1) { var gfStatus = list.FirstOrDefault(); gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; gfStatus.CreateTime = DateTime.Now; _fenceStatusApiClient.Update(gfStatus); } else { _fenceStatusApiClient.Delete(item); } } } } else { gfs.CreateTime = DateTime.Now; _fenceStatusApiClient.Add(gfs); _logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>"); } #region 旧代码 // GeofenceStatus gfStatus = client.GetFirst(param); //if (gfStatus != null) //{ // gfStatus.IsGeofenceStatus = gfs.IsGeofenceStatus; // gfStatus.CreateTime = DateTime.Now; // client.Update(gfStatus); // //只保留一条记录,多余的删除(历史遗留数据) // List list = client.GetList(param).ToList(); // if (list.Count > 1) // { // foreach (var item in list) // { // if (gfStatus.Id != item.Id) // { // client.Delete(item); // } // } // } // logger.LogInformation($"修改围栏状态:<{JsonConvert.SerializeObject(gfStatus)}>"); //} //else //{ // gfs.CreateTime = DateTime.Now; // client.Add(gfs); // logger.LogInformation($"增加围栏状态:<{JsonConvert.SerializeObject(gfs)}>"); //} #endregion message = $"保存围栏状态发生成功!"; result = true; } catch (Exception ex) { message = $"保存围栏状态发生异常:{ex.Message},{ex.StackTrace}!"; } return (result, message); } } } }