using GpsCardGatewayPosition.Common; using GpsCardGatewayPosition.Model.Cache; using GpsCardGatewayPosition.Model.Config; using GpsCardGatewayPosition.Model.Enum; using GpsCardGatewayPosition.Service.Biz.Location.Dto.Wayz; using GpsCardGatewayPosition.Service.Cache; using GpsCardGatewayPosition.Service.MqProducer; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using TelpoDataService.Util.Clients; using TelpoDataService.Util.Entities.GpsCard; using TelpoDataService.Util.Entities.GpsLocationHistory; using TelpoDataService.Util; using GpsCardGatewayPosition.Service.Dto; using GpsCardGatewayPosition.Service.MqProducer.Model; using GpsCardGatewayPosition.Service.Biz.Location.Dto; namespace GpsCardGatewayPosition.Service.Biz.Location { public class LocationLogic { private readonly ServiceConfig _configService; private readonly MqProcessLogic _serviceMqProcess; private readonly DeviceCacheManager _deviceCacheMgr; private readonly GpsCardAccessorClient _deviceStatusApiClient; private readonly GpsLocationHistoryAccessorClient _hisDeviceDataApiClient; private readonly GpsLocationHistoryAccessorClient _hisApiClient; private readonly ILogger _logger; private readonly int DEFAULT_LBS_ACCEPT_DISTANCE = 2000; // 默认IOT LBS经纬度与缓存经纬度 public LocationLogic(IOptions optConfigService, MqProcessLogic serviceMqProcess, DeviceCacheManager deviceCacheMgr, GpsCardAccessorClient deviceStatusApiClient, GpsLocationHistoryAccessorClient hisDeviceDataApiClient, GpsLocationHistoryAccessorClient hisApiClient, ILogger logger) { _configService = optConfigService.Value; _serviceMqProcess = serviceMqProcess; _deviceCacheMgr = deviceCacheMgr; _deviceStatusApiClient = deviceStatusApiClient; _hisDeviceDataApiClient = hisDeviceDataApiClient; _hisApiClient = hisApiClient; _logger = logger; } #region 更新设备最后位置信息状态 public async Task UpdateDeviceStatusAsync(string messageId, GpsDeviceStatus deviceStatus) { var result = new GeneralResult(); try { var sn = deviceStatus.Serialno; var url = _configService.TelpoDataUrl; if (await _deviceCacheMgr.GetDeviceStatusBySerialNoAsync(messageId, sn) != null) { await _deviceStatusApiClient.UpdateAsync(deviceStatus, new RequestHeader { RequestId = messageId }).ConfigureAwait(false); } else { await _deviceStatusApiClient.AddAsync(deviceStatus, new RequestHeader { RequestId = messageId }).ConfigureAwait(false); } //更新DeviceStatus缓存 _deviceCacheMgr.SetDeviceStatus(deviceStatus); result.IsSuccess = true; } catch (Exception ex) { result.Message = $"更新设备最后位置信息状态发生异常:{ex.Message}, {ex.StackTrace}"; } return result; } #endregion #region 电子围栏处理 public async void ProcessGeofence(LocationInfo loc, string messageId) { var fence = new FenceLocationPlus() { DeviceId = loc.DeviceId, imei = loc.SerialNo, address = loc.Address, baiduLatitude = loc.BaiduLat, baiduLongitude = loc.BaiduLng, gaodeLatitude = loc.GLat, gaodeLongitude = loc.GLng, originalLatitude = loc.OLat, originalLongitude = loc.OLng, LastUpdate = loc.LastUpdate!.Value.ToString("yyyy-MM-dd HH:mm:ss"), //LastUpdate = datetime, UtcDate = loc.UtcDate!.Value.ToString("yyyy-MM-dd HH:mm:ss"), //UtcDate = Radius = SafeType.SafeInt(loc.DeviceStatus), }; await _serviceMqProcess.ProcessFencePlusAsync(fence, loc.LastUpdate.Value.ToString("yyyy-MM-dd HH:mm:ss"), messageId); } public async Task ProcessGeofenceAsync(LocationInfo loc, string messageId) { var fence = new FenceLocationPlus() { DeviceId = loc.DeviceId, imei = loc.SerialNo, address = loc.Address, baiduLatitude = loc.BaiduLat, baiduLongitude = loc.BaiduLng, gaodeLatitude = loc.GLat, gaodeLongitude = loc.GLng, originalLatitude = loc.OLat, originalLongitude = loc.OLng, LastUpdate = loc.LastUpdate!.Value.ToString("yyyy-MM-dd HH:mm:ss"), //LastUpdate = datetime, UtcDate = loc.UtcDate!.Value.ToString("yyyy-MM-dd HH:mm:ss"), //UtcDate = Radius = SafeType.SafeInt(loc.DeviceStatus), }; await _serviceMqProcess.ProcessFencePlusAsync(fence, loc.LastUpdate.Value.ToString("yyyy-MM-dd HH:mm:ss"), messageId); } #endregion #region 保存原始数据包 public async Task SavePackageAsync(string messageId, HisGpsDeviceData data) { var result = new GeneralResult(); try { await _hisDeviceDataApiClient.AddAsync(data, header: new RequestHeader { RequestId = messageId }).ConfigureAwait(false); result.Message = $"保存原始数据包成功!"; result.IsSuccess = true; } catch (Exception ex) { result.Message = $"保存原始数据包发生异常:{ex.Message}, {ex.StackTrace}!"; } return result; } #endregion #region 保存历史位置 /// /// /// /// /// 定位状态缓存 /// 设备上报消息到IOT的时间戳 /// 城市行政编码 /// IOT上报步数(选填,暂时只有wifi2有) // 2022/11/23 定位缓存需要增加步数字段实现前端“静止/行走”状态展示 /// public async Task AddLocationAsync(string messageId, LocationInfo loc, DevicePositionStatus positionStatus, long sentTicks, string cityCode = "", int? steps = default, object requestPostionData = default) { var result = new LocationServiceResult(); var requestPostionVar = string.Empty; var requestPostionVarObj = new object(); ; if (requestPostionData is RequestLocationInfo requestPostion && loc.LocationType == 3) { requestPostionVarObj = new { Location = new { Wifis = requestPostion.RequestData.Location.Wifis.Select(i => new { i.MacAddress, i.SignalStrength }).ToList() } }; requestPostionVar = JsonConvert.SerializeObject(requestPostionVarObj); } else { requestPostionVar = null; } try { var url = _configService.TelpoDataUrl; var model = new History { LocationId = Guid.NewGuid().ToString("D"), DeviceId = loc.DeviceId, Serialno = loc.SerialNo, Utctime = loc.UtcDate, LastUpdate = loc.LastUpdate, IsStop = loc.IsStop ?? false, IsRedressed = loc.IsRedressed, Speed = loc.Speed, Course = loc.Course, LocationType = loc.LocationType, Olat = loc.OLat, Olng = loc.OLng, BaiduLat = loc.BaiduLat, BaiduLng = loc.BaiduLng, Glat = loc.GLat, Glng = loc.GLng, DeviceStatus = loc.DeviceStatus, Address = loc.Address, Remarks = $"{loc.Remarks}#{messageId}#{DateTime.Now}", //记录messageId方便查询异常的定位,系统保存时间 RequestPosition = requestPostionVar, //RequestPosition = //loc.LocationType == 3 && requestPostionData. ? JsonConvert.SerializeObject(requestPostionData) }; await _hisApiClient.AddAsync(model, model.Serialno, header: new RequestHeader { RequestId = messageId }).ConfigureAwait(false); result.Message = "保存历史位置成功!"; result.IsSuccess = true; } catch (Exception ex) { result.Message = $"保存历史位置发生异常: message:{ex.Message}, {ex.StackTrace}"; } if (positionStatus == null) positionStatus = new DevicePositionStatus(); var cache = positionStatus.LastPosition; if (cache == null || cache.SentTicks < sentTicks) { //更新设备的坐标缓存 // 2022/11/15 关于定位信息缓存更新机制的优化说明(实时定位场景): /** * // 2022/11/15 关于定位信息缓存更新机制的优化说明(实时定位场景): * 具体需求 * 关于定位信息缓存更新机制的优化说明(实时定位场景): 1、缓存需要增加一个字段“原始有效定位时间”,该字段用于填写覆盖LBS定位数据的有效定位数据的定位时间。 2、缓存更新需要基于缓存的数据定位时间(last_update)与“原始有效定位时间”进行比较判定更新机制。 3、有效定位数据定位时间(非LBS IOT上报时间)>缓存的定位时间,直接用有效定位数据更新缓存,同时,有效定位数据的“原始有效定位时间”是当前数据的定位时间。 4、LBS定位数据定位时间(LBS IOT上报时间)>缓存的定位时间,则更新缓存的定位时间,定数数据还是原有缓存的定位数据,“原始有效定位时间”不变还是原有的缓存数据的时间。LBS定位数据定位时间<缓存的定位时间(不可能有这个情况),直接不处理。 5、有效定位数据时间(非LBS IOT上报时间)>缓存的“原始有效定位时间”,同时,有效定位数据时间<缓存的的定位时间的情况(就是之前有LBS定位更新了缓存定位时间),则更新缓存的定位数据内容及“原始有效定位时间”,“原始有效定位时间”为当前有效定位的定位时间,定位时间还是不变。 =================== 注:原有定位数据不变都是直接入库。此缓存主要应用于实时定位数据获取。 y: 原始有效定位; y_time:原始有效定位的时间; l_time: LBS有效定位的时间 h: 缓存的定位 h_time:缓存的定位时间(last_update) 1、定位数据 y: 定位数据:区分有效定位数据(y_data)与LBS定位数据(w_data)。 l_time: 定位的时间 2、缓存数据 h: 缓存的定位 h_time:缓存的定位时间(字段last_update) y_time:有效定位的时间(新增字段,取y_data的l_time); h_time>=y_time 3、y_data:l_time > h_time,则用y更新h、用l_time更新h_time、y_time。 4、w_data:l_time > h_time,则用y不更新h、用l_time更新h_time,y_time不变。 5、y_data:l_time < h_time & l_time > y_time, 则用y更新h、用l_time更新y_time */ // 2022/12/2 所有定位类型都要生成缓存。 var now = DateTime.Now; var positionCache = new DevicePositionStatus.PositionCache { Address = loc.Address, BaiduLat = loc.BaiduLat, BaiduLon = loc.BaiduLng, CityCode = cityCode, GaodeLat = loc.GLat, GaodeLon = loc.GLng, OriginalLat = loc.OLat, OriginalLon = loc.OLng, LocationType = loc.LocationType, //UpdateTime = loc.LastUpdate ?? DateTime.Now, //DateTime.Now, Steps = steps, // 2022/11/15 关于定位信息缓存更新机制的优化说明(实时定位场景): // 3、有效定位数据定位时间>缓存的定位时间,直接用有效定位数据更新缓存,同时,有效定位数据的“原始有效定位时间”是当前数据的定位时间。 OriginalTime = loc.LastUpdate ?? now, UpdateTime = loc.LastUpdate ?? now, //DateTime.Now, ExpiredTime = DateTime.Now.AddHours(1), Radius = Convert.ToInt32(loc.DeviceStatus), SentTicks = sentTicks, Province = loc.Province, City = loc.City, District = loc.District }; // 更新设备的坐标缓存(非LBS IOT) if (loc.LocationType != (int)LocationType.LBS) //此缓存值用于通过gps或者wifi类型来校对lbs的偏移 { // 2022/11/15 关于定位信息缓存更新机制的优化说明(实时定位场景): /** 非LBS 定位缓存 * //var now = DateTime.Now; //var positionCache = new DevicePositionStatus.PositionCache //{ // Address = loc.Address, // BaiduLat = loc.BaiduLat, // BaiduLon = loc.BaiduLng, // CityCode = cityCode, // GaodeLat = loc.GLat, // GaodeLon = loc.GLng, // OriginalLat = loc.OLat, // OriginalLon = loc.OLng, // LocationType = loc.LocationType, // //UpdateTime = loc.LastUpdate ?? DateTime.Now, //DateTime.Now, // Steps = steps, // // 2022/11/15 关于定位信息缓存更新机制的优化说明(实时定位场景): // // 3、有效定位数据定位时间>缓存的定位时间,直接用有效定位数据更新缓存,同时,有效定位数据的“原始有效定位时间”是当前数据的定位时间。 // OriginalTime = loc.LastUpdate ?? now, // UpdateTime = loc.LastUpdate ?? now, //DateTime.Now, // ExpiredTime = DateTime.Now.AddHours(1), // Radius = Convert.ToInt32(loc.DeviceStatus), // SentTicks = sentTicks, // Province = loc.Province, // City = loc.City, // District = loc.District //}; */ if (cache != null) { positionCache.ExpiredTime = cache.ExpiredTime; // 5、有效定位数据时间>缓存的“原始有效定位时间”,同时,有效定位数据时间<缓存的的定位时间的情况(就是之前有LBS定位更新了缓存定位时间), // 则更新缓存的定位数据内容及“原始有效定位时间”,“原始有效定位时间”为当前有效定位的定位时间,定位时间还是不变。 if ( Utils.ConvertToLocalDateTime(sentTicks) > cache.OriginalTime && Utils.ConvertToLocalDateTime(sentTicks) < cache.UpdateTime ) { // “原始有效定位时间”为当前有效定位的定位时间 positionCache.OriginalTime = Utils.ConvertToLocalDateTime(sentTicks); // 2022/11/23 定位缓存需要增加步数字段实现前端“静止/行走”状态展示 positionCache.Steps = steps; } } // 更新缓存的定位数据内容 positionStatus.LastPosition = positionCache; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|{JsonConvert.SerializeObject(positionStatus)}|实际定位类型:{loc.LocationType}"); } //更新设备的坐标缓存(LBS IOT) else { //LBS 定位数据不写入缓存 // 存在非LBS 缓存 if (cache != null) { /** // 2023/04/12 当LBS定位处理优化: // 就是基于LBS与上次有效定位的距离进行分析, // 在一个合理的距离则判定LBS有效, // 也是需更新缓存并显示。 // 这个距离可设置,默认大于2000米 **/ if (false) // 临时开发LBS { var distance = GeoUtils.GetDistance2((double)cache.GaodeLon, (double)cache.GaodeLat, (double)positionCache.GaodeLon, (double)positionCache.GaodeLat); if (distance >= DEFAULT_LBS_ACCEPT_DISTANCE) // 临时开放LBS(false) 说明可能关机下走了很远,此时IOT的定位是LBS,生成的定位缓存也是LBS { // 使用最新的IOT LBS数据更新定位缓存 positionStatus.LastPosition = positionCache; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)}|基于LBS与上次有效定位的距离超过{DEFAULT_LBS_ACCEPT_DISTANCE}米,生成新的LBS定位缓存"); } else if (cache.LocationType == (int)LocationType.LBS) // 否则在2000米内,缓存中有LBS的,使用新的IOT LBS数据 { ////LBS定位缓存中原始有效定位与IOT的LBS定位时间差大于1小时更新LBS定位缓存所有数据 //if (Utils.ConvertToLocalDateTime(sentTicks).Subtract((DateTime)cache.OriginalTime).TotalHours > 1) //{ // positionStatus.LastPosition = positionCache; // _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); // _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)}|LBS定位缓存中原始有效定位与IOT的LBS定位时间差小于1小时更新LBS定位缓存所有数据"); //} positionStatus.LastPosition = positionCache; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)}定位缓存类型是LBS且定位缓存经纬度与 IOT LBS 经纬度直线少于2000米,生成新的LBS定位缓存"); } // 4、LBS定位数据定位时间>缓存的定位时间,则更新缓存的定位时间,定数数据还是原有缓存的定位数据, //“原始有效定位时间”不变还是原有的缓存数据的时间。 //// LBS定位数据定位时间<缓存的定位时间,直接不处理。 //if (Utils.ConvertToLocalDateTime(sentTicks) > cache.OriginalTime) // LBS无效定位只更新时间和步数 else { // 更新缓存的定位时间 cache.UpdateTime = Utils.ConvertToLocalDateTime(sentTicks); // 定数数据还是原有缓存的定位数据,“原始有效定位时间”不变还是原有的缓存数据的时间。 positionStatus.LastPosition = cache; // 2022/11/23 定位缓存需要增加步数字段实现前端“静止/行走”状态展示 positionStatus.LastPosition.Steps = steps; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)}|无效 IOT LBS经纬度|实际IOT定位类型:{loc.LocationType}"); } } else { positionStatus.LastPosition = positionCache; positionStatus.LastPosition.Steps = steps; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)} 临时开放LBS (覆盖原来有缓存)"); } } // 2022/12/2 若是没有缓存,且一开始定位是LBS,还是要生成缓存(LBS数据) else { // 生成缓存(LBS数据) positionStatus.LastPosition = positionCache; positionStatus.RequestPosition = loc.LocationType == 3 ? requestPostionVarObj : null; _deviceCacheMgr.SetPositionStatusCache(loc.SerialNo, positionStatus); _logger.LogInformation($"设备{loc.SerialNo},设备上报消息到IOT设备时间:{Utils.ConvertToLocalDateTime(sentTicks)},更新缓存时间:{DateTime.Now}|缓存内容:{JsonConvert.SerializeObject(positionStatus)}|设备首次定位且是IOT LBS,生成首次LBS数据的定位缓存"); } } } else { result.IsHistoryLocation = true; } return result; } #endregion } }