using ATS.NonCustodial.Application.Base; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.AppDictionaries; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.AppDictionaries.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.User; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppCaseManagements.AppCaseManagement; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppCaseManagements.AppCaseManagement.Input; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppCaseManagements.AppCaseManagement.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppCommonFences; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppCommonFences.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppEarlyWarnings; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppEarlyWarnings.Input; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.AppEarlyWarnings.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.Apps.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.IM.Notifies; using ATS.NonCustodial.Application.Contracts.Interfaces.Business.PunchRecordServices.Input; using ATS.NonCustodial.Application.Impl.Business.IM; using ATS.NonCustodial.Domain.Entities.Business; using ATS.NonCustodial.Domain.Entities.Business.CaseManagements; using ATS.NonCustodial.Domain.Entities.Business.EarlyWarning; using ATS.NonCustodial.Domain.Entities.Business.IM; using ATS.NonCustodial.Domain.Shared.AggRootEntities.Dtos; using ATS.NonCustodial.Domain.Shared.Enums; using ATS.NonCustodial.Domain.Shared.OrmRepositories.Basic.EfCore; using ATS.NonCustodial.DynamicApi; using ATS.NonCustodial.DynamicApi.Attributes; using ATS.NonCustodial.Shared.Common.Dtos; using ATS.NonCustodial.Shared.Common.Enums; using ATS.NonCustodial.Shared.Common.UnifiedResults; using ATS.NonCustodial.Shared.Extensions; using ATS.NonCustodial.Shared.Helpers; using AutoMapper.QueryableExtensions; using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using System.Net; using System.Text; using Yitter.IdGenerator; namespace ATS.NonCustodial.Application.Impl.Business { /// /// 预警提醒管理(实时) /// /// Author:mxg /// CreatedTimed:2022-06-05 11:30 PM /// /// 1、【脱离监管区域】被监管人触发电子围栏,会发送预警并记录违规。 /// 2、【同案人员地点靠近】被监管人靠近同案其他成员的距离小于案件接近等级,会发送预警并记录违规。 /// 3、【未打卡】被监管人未按照要求打卡,每次会触发一次违规。在被监管人在非休息时间内打卡时间间隔超过设置的间隔,则需要进行预警,提示XXX未打卡。 /// 4、【脱离监管区域、未打卡、未学习、同案人员地点靠近、失联】监管人在移动端手动给该监管人进行告警,记录一次违规。 /// 5、【设备拆除】手环中有一个数据为是否被拆除,如果为是的话,则会触发拆除的预警违规。 /// 总共的类型为:脱离监管区域、同案人员地点靠近、未打卡、未学习、设备拆除 /// [DynamicApi(Area = "admin")] public class AppEarlyWarningService : AdminCommonService, IAppEarlyWarningService, IDynamicApi { #region Identity private readonly IHubContext _hubContext; private readonly IEfRepository _appEarlyWarningRepository; private readonly IEfRepository _appEarlyWarningPushResultRepository; private readonly IEfRepository _appDeviceManagementRepository; private readonly IEfRepository _appEarlyWarningViewStatisticsRepository; private readonly IAppCommonFenceService _appCommonFenceService; private readonly IEfRepository _appBusinessApplicationRepository; private readonly IClientNotifyService _clientNotifyService; private readonly IEfRepository _appEarlyWarningRuleRepository; private readonly IAppCaseManagementService _appCaseManagementService; private readonly IUserService _userService; /// /// /// /// /// /// /// /// /// /// /// /// /// public AppEarlyWarningService(IEfRepository appEarlyWarningRepository, IEfRepository appEarlyWarningPushResultRepository, IHubContext hubContext, IEfRepository appCaseManagementRepository, IEfRepository appCaseSupervisorRepository, IEfRepository appSupervisedPersonRepository, IUserService userService, IAppDictionaryService appDictionaryService, IEfRepository appDeviceManagementRepository, IEfRepository appSessionInformationRepository, IAppCommonFenceService appCommonFenceService, IAppCaseManagementService appCaseManagementService, IEfRepository appBusinessApplicationRepository, IEfRepository appSupervisedPersonRealTimeLocationRepository, IClientNotifyService clientNotifyService, IEfRepository appEarlyWarningViewStatisticsRepository, IEfRepository appEarlyWarningRuleRepository) : base( appCaseManagementRepository, appCaseSupervisorRepository, appSupervisedPersonRepository, userService, appDictionaryService, appSupervisedPersonRealTimeLocationRepository ) { _appEarlyWarningRepository = appEarlyWarningRepository; _appEarlyWarningPushResultRepository = appEarlyWarningPushResultRepository; _hubContext = hubContext; _appDeviceManagementRepository = appDeviceManagementRepository; _appEarlyWarningViewStatisticsRepository = appEarlyWarningViewStatisticsRepository; _appCommonFenceService = appCommonFenceService; _appBusinessApplicationRepository = appBusinessApplicationRepository; _clientNotifyService = clientNotifyService; _appEarlyWarningRuleRepository = appEarlyWarningRuleRepository; _appCaseManagementService = appCaseManagementService; _userService = userService; } #endregion Identity /// /// 手动添加预警信息[比如:监管人App聊天窗口调用] /// /// /// /// ###### 手动触发 /// 通过移动端监管人APP给被监管人手动录入预警的类型数据 /// public async Task ManualAddEarlyWarning(EarlyWarningAddInput input) { //[获取当前[被监管挂人员]的案件 var caseInfo = await base.GetCaseInfoBySupervisedPersonId(input.SupervisedPersonId); if (caseInfo == null) return ResultOutput.NotOk($"当前被监管人:{input.SupervisedPersonId}不存在正在进行的案件!"); //字典 var dictionaryGetOutput = await _appDictionaryService.GetDicByDicId(input.EarlyWarningTypeId); //公共逻辑 return await EarlyWarningCommLogic(caseInfo, dictionaryGetOutput, null, input.address == null ? "" : input.address); } /// /// 新增预计信息 /// /// /// public async Task AddAsync(AppEarlyWarningAddInput input) => await base.AddAsync(input, _appEarlyWarningRepository); /// /// 批量新增预计信息 /// /// /// public async Task BatchAddAsync(List input) { List earlyWarningView = new EditableList(); var spIds = input.Select(w => w.SupervisedPersonId).ToList(); var admins = (await _userService.GetAllAdminUserIds()).Select(w => new KeyValueDto() { Id = w.Id, Value = w.UserName, Text = w.UserName }).ToList(); var superviseList = (await base.GetSuperviseListByUserId(spIds)).Select(w => new KeyValueDto() { Id = w.SupervisedId, Value = w.SupervisedName, Text = w.SupervisedName }).ToList(); foreach (var addInput in input) { //管理员 earlyWarningView.AddRange(await AddEarlyWithViews(addInput, admins)); //监管人 earlyWarningView.AddRange(await AddEarlyWithViews(addInput, superviseList)); //被监管人自己 earlyWarningView.AddRange(await AddEarlyWithViews(addInput, new EditableList() { new KeyValueDto() { Id = addInput.SupervisedPersonId, Text = addInput.SupervisedPersonName, Value = addInput.SupervisedPersonName } })); } //入库 await base.AddAsync(input, _appEarlyWarningRepository); await _appEarlyWarningViewStatisticsRepository.InsertAsync(earlyWarningView); return ResultOutput.Ok(); } /// /// 批量新增推送结果 /// /// /// public async Task BatchAddPushResultAsync(List input) { foreach (var item in input) { // item.WarningId = item.Id; item.Id = null; } //入库 await base.AddAsync(input, _appEarlyWarningPushResultRepository); return ResultOutput.Ok(); } /// /// 根据预计类型枚举和设备唯一标识符新增预警信息 /// /// /// /// /// ###### 设备触发 /// 设备触发设备拆除的预警情况,不需要token鉴权,只需要一个设备唯一码即可 /// [AllowAnonymous] public async Task AddEarlyWarning(AddEarlyWarningInput input) { //查找设备 var device = await _appDeviceManagementRepository.FindAsync(w => w.UniqueIdentifier == input.UniqueIdentifier); if (device == null) return ResultOutput.NotOk($"当前设备:{input.UniqueIdentifier}不存在!"); //[获取当前[被监管挂人员]的案件 var caseInfo = await base.GetCaseInfoBySupervisedPersonId(device.SupervisedPersonId); if (caseInfo == null) return ResultOutput.NotOk($"当前设备标识符:{input.UniqueIdentifier}绑定的被监管人不存在!"); //处理预警类型 var dictionaryGetOutput = input.EarlyWarningType switch { EarlyWarningTypeEnum.EquipmentRemoval => await base.GetDictionariesOutput("early_warning_type", "equipmentremoval"), _ => throw new ArgumentOutOfRangeException() }; if (dictionaryGetOutput == null) return ResultOutput.NotOk($"当前预警类型:{input.EarlyWarningType.ToDescription()}不存在字典中,请联系管理员添加"); //公共逻辑 return await EarlyWarningCommLogic(caseInfo, dictionaryGetOutput, device); } /// /// 查询列表 /// /// /// [HttpPost] [AllowAnonymous] public async Task GetPageAsync(AppEarlyWarningGetPageInput input) { //获取当前用户权限下的案件ids var limits = User.limits; var selectLimits = await _appCaseSupervisorRepository.AsQueryable(false, true) .Where(w => limits.Contains(w.UnitId.ToString())) .ToListAsync(); var caseIdList = selectLimits.Select(w => w.CaseId).Distinct().ToList(); var express = await GetExpression(input, _appEarlyWarningRepository.AsQueryable(false, true)); // 先应用案件ID过滤条件 express = express.Where(w => caseIdList.Contains(w.CaseId)); // 然后再获取分页数据 return await base.GetEntityAddPageAsync(input, express); } /// /// 越界预警统计查询 /// /// /// [HttpPost] public async Task GetCrossborderStatisticsAsync(AppEarlyWarningGetPageInput input) { //获取当前用户权限下的案件ids var limits = User.limits; var selectLimits = await _appCaseSupervisorRepository.AsQueryable(false, true) .Where(w => limits.Contains(w.UnitId.ToString())) .ToListAsync(); var caseIds = selectLimits.Select(w => w.CaseId).Distinct().ToList(); var express = await GetExpression(input, _appEarlyWarningRepository.AsQueryable(false, true)); var grudlist = express.Where(p=> caseIds.Contains(p.CaseId)) .WhereIf(input.supname.NotNull(), w => w.SupervisedPersonName.Contains(input.supname)) .GroupBy(q => new { q.CaseId, q.CaseName, q.SupervisedPersonId, q.SupervisedPersonName }).Select(q => new { q.Key.CaseId, q.Key.CaseName, q.Key.SupervisedPersonName, q.Key.SupervisedPersonId, Count = q.ToList().Count() }).Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize); //var SupervisedPersonIds = grudlist.Select(q => q.SupervisedPersonId); // var grudlistcunot = express.Where(q => SupervisedPersonIds.Contains(q.SupervisedPersonId)).ToList(); return ResultOutput.Ok(new { TotalCount = express.Where(p => caseIds.Contains(p.CaseId)).GroupBy(q => new { q.CaseId, q.CaseName, q.SupervisedPersonId, q.SupervisedPersonName }).Count(), grudlist }); } /// /// 设备上传经纬度时候,验证 /// /// /// /// ###### 设备上传经纬度时候,验证 /// a)是否脱离监管区域,监管区域以案件设置的围栏为准,这里需要注意一下,如果通过了跨区域的申请,在这个时间内,不做预警触发 /// b)同案人员地点靠经,如果存在2个以上的人员(包括2个),依据最近一次经纬度进行校验,范围为案件申请里面的接近等级 /// [AllowAnonymous] public async Task ValidWithLatitudeAndLongitude(ValidWithLatitudeAndLongitudeInput input) { //查找设备 var device = await _appDeviceManagementRepository.FindAsync(w => w.UniqueIdentifier == input.UniqueIdentifier); if (device == null) return ResultOutput.NotOk($"当前设备:{input.UniqueIdentifier}不存在!"); if (device.DataStatus != DataStatusEnum.Normal) return ResultOutput.Ok(); //[获取当前[被监管挂人员]的案件 var caseInfo = await base.GetCaseInfoBySupervisedPersonId(device.SupervisedPersonId); return caseInfo == null ? ResultOutput.NotOk($"当前设备标识符:{input.UniqueIdentifier}绑定的被监管人不存在!") : caseInfo.ApprovalStatus != ApprovalStatusEnum.PassReview ? ResultOutput.Ok() : await ValidWithLatitudeAndLongitudeCommonLogic(caseInfo, input.Longitude, input.Latitude); } /// /// 设备上传经纬度时候,验证 /// /// /// /// ###### 设备上传经纬度时候,验证 /// a)是否脱离监管区域,监管区域以案件设置的围栏为准,这里需要注意一下,如果通过了跨区域的申请,在这个时间内,不做预警触发 /// b)同案人员地点靠经,如果存在2个以上的人员(包括2个),依据最近一次经纬度进行校验,范围为案件申请里面的接近等级 /// public async Task ValidWithLatitudeAndLongitude(ValidWithLatitudeAndLongitudeByIdInput input) { //[获取当前[被监管挂人员]的案件 var caseInfo = await base.GetCaseInfoBySupervisedPersonId(input.SupervisedPersonId); if (caseInfo == null) return ResultOutput.NotOk($"当前设备标识符:{input.SupervisedPersonId}绑定的被监管人不存在!"); if (caseInfo.ApprovalStatus != ApprovalStatusEnum.PassReview) return ResultOutput.Ok(); //如果当前设备绑定的设备被禁用 var deviceBind = await _appDeviceManagementRepository.FindAsync(w => w.SupervisedPersonId == caseInfo.SupervisedPersonId); return deviceBind != null && deviceBind.DataStatus != DataStatusEnum.Normal ? ResultOutput.Ok() : await ValidWithLatitudeAndLongitudeCommonLogic(caseInfo, input.Longitude, input.Latitude, true); } /// /// 批量设置是否查阅状态 /// /// /// [HttpPost] public async Task BatchSetCheckStatus(SetCheckStatusInput input) { if (_user?.Id > 0 || !input.Ids.Any()) return ResultOutput.Ok(); //查询记录(根据公告Id和订阅者ID(被监管人Id)) var earlyWarnings = await _appEarlyWarningViewStatisticsRepository .Where(w => input.Ids.Contains(w.AppEarlyWarningId) && w.SubscriberId == User.Id) .ToListAsync(); earlyWarnings.ForEach(item => { //如果已经是已读了就直接返回 if (item.CheckStatus == CheckStatusEnum.Checked) return; item!.CheckStatus = input.CheckStatus; item.CheckTime = input.CheckStatus == CheckStatusEnum.Checked ? DateTime.Now : null; }); //批量修改 await _appEarlyWarningViewStatisticsRepository.UpdateAsync(earlyWarnings); return ResultOutput.Ok(); } /// /// 业务工作台==>最新5条预警 /// /// public async Task EarlyWarningBusinessWorkbench() { //获取当前用户权限下的案件ids var limits = User.limits; var selectLimits = await _appCaseSupervisorRepository.AsQueryable(false, true) .Where(w => limits.Contains(w.UnitId.ToString())) .ToListAsync(); var caseIdList = selectLimits.Select(w => w.CaseId).Distinct().ToList(); var caseIds = await (await base.GetCurrentUserCaseListAsync()).Where(w=> w.AppCaseManagement!=null&&caseIdList.Contains(w.AppCaseManagement.Id)).Select(w => w.AppCaseManagement.Id).ToListAsync(); var dataList = await _appEarlyWarningRepository.AsQueryable(false, true) .Where(w => caseIds.Contains(w.CaseId)) .OrderByDescending(w => w.CreatedTime) .Skip(0) .Take(5) .ProjectTo(Mapper.ConfigurationProvider) .ToListAsync(); var dictIds = dataList.Select(w => w.EarlyWarningTypeId).ToList(); var dataDict = await _appDictionaryService.GetDicByDicIds(new BatchIdsInput() { Ids = dictIds }); foreach (var listDto in dataList) { listDto.EarlyWarningTypeName = dataDict.FirstOrDefault(w => w.Id == listDto.EarlyWarningTypeId)?.Name; } return ResultOutput.Ok(dataList); } #region Private /// /// 查询实时预警条件 /// /// /// /// private async Task> GetExpression(AppEarlyWarningGetPageInput pageInput, IQueryable query) { var limits = User.limits; var IsAdmin = User.IsAdmin; var selectLimits = await _appCaseSupervisorRepository.AsQueryable(false, true) .WhereIf((!IsAdmin),w => limits.Contains(w.UnitId.ToString())) .ToListAsync(); var caseIds = selectLimits.Select(w => w.CaseId).Distinct().ToList(); //var caseIds = await (await base.GetCurrentUserCaseListAsync()).Select(w => w.AppCaseManagement.Id).ToListAsync(); query = query.Where(w => caseIds.Contains(w.CaseId)) .WhereIf(pageInput.KeyWord.NotNull(), w => w.Title.Contains(pageInput.KeyWord) || w.CaseName.Contains(pageInput.KeyWord)) .WhereIf(pageInput.casename.NotNull(), w => w.CaseName.Contains(pageInput.casename)) .WhereIf(pageInput.EarlyWarningTypeId.Any(), a => pageInput.EarlyWarningTypeId.Contains(a.EarlyWarningTypeId)) .WhereIf(pageInput.UserSearch != null, p => p.SupervisedPersonId == pageInput.UserSearch!.ModifiedUserId); var express = base.GetEntityAddExpression(pageInput, query); return express; } /// /// 添加预警信息公告逻辑发给当前被监管人、管理员、监管人 /// /// /// /// /// /// /// 1、如果出现跨区域预警第一次预警后 后面将不在预警 等到回到区域后 再次跨区域 才会出现 /// 2、同案件范围接近报警 也同上 出现第一次后 直到不在出现后 再次出现才报警,如果一直报警 仅为1次 /// private async Task EarlyWarningCommLogic(GetCaseInfoBySupervisedPersonIdOutput caseInfo, DictionaryGetOutput dictionaryGetOutput, AppDeviceManagement? device = null, string address = "") { //Builder AppEarlyWarning var entity = await _appEarlyWarningRepository.InsertAsync(new AppEarlyWarning(YitIdHelper.NextId()) { CaseId = caseInfo.CaseId, CaseName = caseInfo.CaseName, Title = dictionaryGetOutput?.Name, Content = dictionaryGetOutput?.Name + address, SupervisedPersonId = caseInfo.SupervisedPersonId, SupervisedPersonName = caseInfo.SupervisedPersonName, EarlyWarningTypeId = dictionaryGetOutput?.Id, EarlyWarningTypeName = dictionaryGetOutput?.Name, DeviceId = device?.Id, DeviceName = device?.Name, UniqueIdentifier = device?.UniqueIdentifier }); var earlyWarningView = await base.GetEarlyWarningRecord(new EditableList() { new AppEarlyWarningAddInput(entity.Id) { EarlyWarningTypeId = entity.EarlyWarningTypeId, CaseId = entity.CaseId, Content = entity.Content, Title = entity.Title, SupervisedPersonId = entity.SupervisedPersonId, SupervisedPersonName = entity.SupervisedPersonName } }); await _appEarlyWarningViewStatisticsRepository.InsertAsync(earlyWarningView); //通知 await _clientNotifyService.RealTimeWarningToPc(new RealTimeWarningToPcInput() { AppRemindListDtos = new EditableList() { new AppRemindListDto() { Title = entity.Title, Id = entity.Id, Content = entity.Content, CaseId = entity.CaseId, CreatedTime = entity.CreatedTime }, }, SubscriberIds = earlyWarningView.Select(w => w.SubscriberId).ToList() }); return ResultOutput.Ok(); } /// /// 校验当前被监管人位置公共方法 /// /// /// /// /// 是否来自打卡(打卡的话就不需要判断上一次是否来自围栏内) /// private async Task ValidWithLatitudeAndLongitudeCommonLogic(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, bool isFromPunchRecord = false) { if (!caseInfo.ElectricFenceId.HasValue) return ResultOutput.Ok(); //[1]跨域申请 var commonFence = (IResultOutput)await _appCommonFenceService.GetAsync(caseInfo.ElectricFenceId.Value); var points = commonFence.Data.Path.StrToLatLngList(); await LeaveArea(caseInfo, longitude, latitude, points, isFromPunchRecord); //[2]同案人员地点靠近 await InvestigatorsApproach(caseInfo, longitude, latitude, points, isFromPunchRecord); //[3] await Getpoi(caseInfo, longitude, latitude, isFromPunchRecord); return ResultOutput.Ok(); } /// /// 校验当前被监管人位置公共方法 多个监控区域 /// /// /// /// /// /// private async Task ValidWithLatitudeAndLongitudeCommonLogic1(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, bool isFromPunchRecord = false) { if (!caseInfo.ElectricFenceId.HasValue) return ResultOutput.Ok(); await LeaveArea1(caseInfo, longitude, latitude, isFromPunchRecord); //[2]同案人员地点靠近 //await InvestigatorsApproach(caseInfo, longitude, latitude, points, isFromPunchRecord); return ResultOutput.Ok(); } /// /// 处理预警规则(True需要) /// /// /// /// /// /// /// /// private async Task IsNeedToEarlyWarning(GetCaseInfoBySupervisedPersonIdOutput caseInfo, DictionaryGetOutput dictionaryGet, decimal longitude, decimal latitude, BmapPoint[] bmapPoints, bool isFromPunchRecord = false) { var earlyWarningRule = await _appEarlyWarningRuleRepository.FindAsync(w => w.EarlyWarningTypeId == dictionaryGet.Id && w.SupervisedPersonId == caseInfo.SupervisedPersonId && w.CaseId == caseInfo.CaseId); //判断点是否在多边形内(基本思路是用交点法)(true:在围栏内,false:在围栏外) var isPointInPolygon = GraphUtils.IsPointInPolygon(new BmapPoint(longitude, latitude), bmapPoints); //上一次是否在圈内(只针对定位) var lastIsPointInPolygon = earlyWarningRule?.LastIsPointInPolygon ?? false; if (earlyWarningRule == null) { await _appEarlyWarningRuleRepository.InsertAsync(new AppEarlyWarningRule(YitIdHelper.NextId()) { CaseId = caseInfo.CaseId, CaseName = caseInfo.CaseName, SupervisedPersonId = caseInfo.SupervisedPersonId, SupervisedPersonName = caseInfo.SupervisedPersonName, EarlyWarningTypeId = dictionaryGet.Id, EarlyWarningTypeName = dictionaryGet.Name, LastIsPointInPolygon = isPointInPolygon }); } else { earlyWarningRule.LastIsPointInPolygon = isPointInPolygon; //跟新上一次是否实在围栏内 await _appEarlyWarningRuleRepository.UpdateAsync(earlyWarningRule, UpdatingProps(x => x.LastIsPointInPolygon)!); } //如果来自打卡直接返回 if (isFromPunchRecord) return !isPointInPolygon; else { //预警条件:上一次在圈内且现在在圈外,如果第一次都在外面那么也需要预警 return earlyWarningRule == null && !isPointInPolygon ? true : lastIsPointInPolygon && !isPointInPolygon; } } /// /// 处理预警规则(True需要) 多个监控区域 /// /// /// /// /// /// /// /// private async Task IsNeedToEarlyWarning1(GetCaseInfoBySupervisedPersonIdOutput caseInfo, DictionaryGetOutput dictionaryGet, decimal longitude, decimal latitude, bool isFromPunchRecord = false) { var earlyWarningRule = await _appEarlyWarningRuleRepository.FindAsync(w => w.EarlyWarningTypeId == dictionaryGet.Id && w.SupervisedPersonId == caseInfo.SupervisedPersonId && w.CaseId == caseInfo.CaseId); //循环监控区域 var isPointInPolygon = false; foreach (var item in caseInfo.ElectricFenceId.Value.ToString().Split(",")) { //[1]跨域申请 var commonFence = (IResultOutput)await _appCommonFenceService.GetAsync(caseInfo.ElectricFenceId.Value); var bmap = commonFence.Data.Path.StrToLatLngList();//转换字符串二维数组经纬度 //判断点是否在多边形内(基本思路是用交点法)(true:在围栏内,false:在围栏外) isPointInPolygon = GraphUtils.IsPointInPolygon(new BmapPoint(longitude, latitude), bmap); if (isPointInPolygon) break; } //上一次是否在圈内(只针对定位) var lastIsPointInPolygon = earlyWarningRule?.LastIsPointInPolygon ?? false; if (earlyWarningRule == null) { await _appEarlyWarningRuleRepository.InsertAsync(new AppEarlyWarningRule(YitIdHelper.NextId()) { CaseId = caseInfo.CaseId, CaseName = caseInfo.CaseName, SupervisedPersonId = caseInfo.SupervisedPersonId, SupervisedPersonName = caseInfo.SupervisedPersonName, EarlyWarningTypeId = dictionaryGet.Id, EarlyWarningTypeName = dictionaryGet.Name, LastIsPointInPolygon = isPointInPolygon }); } else { earlyWarningRule.LastIsPointInPolygon = isPointInPolygon; //跟新上一次是否实在围栏内 await _appEarlyWarningRuleRepository.UpdateAsync(earlyWarningRule, UpdatingProps(x => x.LastIsPointInPolygon)!); } //如果来自打卡直接返回 if (isFromPunchRecord) return !isPointInPolygon; else { //预警条件:上一次在圈内且现在在圈外,如果第一次都在外面那么也需要预警 return earlyWarningRule == null && !isPointInPolygon ? true : lastIsPointInPolygon && !isPointInPolygon; } } /// /// 处理同案件人员靠近 /// /// /// /// /// /// /// private async Task InvestigatorsApproach(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, BmapPoint[] bmapPoints, bool isFromPunchRecord) { var allInProcessCase = await base._appCaseManagementRepository.AsQueryable(false, true) .Where(w => w.CaseProgress != CaseProgressEnum.Pending && w.CaseProgress != CaseProgressEnum.Closed && w.Id == caseInfo.CaseId) .Select(w => w.Id) .ToListAsync(); //当前被监管人定位信息 var currentSpLocation = await _appSupervisedPersonRealTimeLocationRepository .AsQueryable(false, true) .Where(w => allInProcessCase.Contains(w.CaseId) && w.SupervisedPersonId != caseInfo.SupervisedPersonId) .Select(w => new { w.SupervisedPersonId, w.SupervisedPersonName, w.Latitude, w.Longitude, w.CreatedTime }).ToListAsync(); var ita = await base.GetDictionariesOutput("early_warning_type", "InvestigatorsApproach"); //规则:设置了接近等级(米)、当前等位信息、需要预警 if (caseInfo.ProximityLevel == default || !currentSpLocation.Any() && !await IsNeedToEarlyWarning(caseInfo, ita, longitude, latitude, bmapPoints, isFromPunchRecord)) return; { foreach (var tempDistinct in currentSpLocation.Select(item => GraphUtils.GetDistance(longitude, latitude, item.Longitude, item.Latitude))) { //出了电子围栏,添加一条预计信息 if (tempDistinct > (decimal)caseInfo.ProximityLevel || ita == null) continue; _ = await ManualAddEarlyWarning(new EarlyWarningAddInput() { EarlyWarningTypeId = ita.Id, SupervisedPersonId = caseInfo.SupervisedPersonId }); break; } } } /// /// 处理脱离围栏 /// /// /// /// /// /// /// private async Task LeaveArea(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, BmapPoint[] bmapPoints, bool isFromPunchRecord) { //是否脱离监管区域、判断当前监管人是否申请了跨域 var dtNow = DateTime.Now; //跨域申请字典 var df = await base.GetDictionariesOutput("application_type", "df"); //被监管人职位字典 var spBusApp = await _appBusinessApplicationRepository.AsQueryable(false, true) .Where(w => df != null && w.SupervisedPersonId == caseInfo.SupervisedPersonId && dtNow >= w.ActiveTimePeriodBegin && dtNow <= w.ActiveTimePeriodEnd && w.AuditStatus == AuditStatusEnum.Pass && w.ApplicationTypeId == df.Id ).ToListAsync(); //只有有电子围栏且当前时间段没有跨域申请的才判断 var dic = await base.GetDictionariesOutput("early_warning_type", "LeaveArea"); //出了电子围栏,添加一条预计信息 if (caseInfo.ElectricFenceId.HasValue && !spBusApp.Any() && dic != null && await IsNeedToEarlyWarning(caseInfo, dic, longitude, latitude, bmapPoints, isFromPunchRecord)) { _ = await ManualAddEarlyWarning(new EarlyWarningAddInput() { EarlyWarningTypeId = dic.Id, SupervisedPersonId = caseInfo.SupervisedPersonId }); } } /// /// 处理脱离围栏---多个监管区域 /// /// /// /// /// /// /// private async Task LeaveArea1(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, bool isFromPunchRecord) { //是否脱离监管区域、判断当前监管人是否申请了跨域 var dtNow = DateTime.Now; //跨域申请字典 var df = await base.GetDictionariesOutput("application_type", "df"); //被监管人职位字典 var spBusApp = await _appBusinessApplicationRepository.AsQueryable(false, true) .Where(w => df != null && w.SupervisedPersonId == caseInfo.SupervisedPersonId && dtNow >= w.ActiveTimePeriodBegin && dtNow <= w.ActiveTimePeriodEnd && w.AuditStatus == AuditStatusEnum.Pass && w.ApplicationTypeId == df.Id ).ToListAsync(); //只有有电子围栏且当前时间段没有跨域申请的才判断 var dic = await base.GetDictionariesOutput("early_warning_type", "LeaveArea"); //出了电子围栏,添加一条预计信息 if (caseInfo.ElectricFenceId.HasValue && !spBusApp.Any() && dic != null && await IsNeedToEarlyWarning1(caseInfo, dic, longitude, latitude, isFromPunchRecord)) { _ = await ManualAddEarlyWarning(new EarlyWarningAddInput() { EarlyWarningTypeId = dic.Id, SupervisedPersonId = caseInfo.SupervisedPersonId }); } } /// /// 进入未经许可场所 /// /// /// /// /// /// /// private async Task Unlicensed(GetCaseInfoBySupervisedPersonIdOutput caseInfo, string name) { //停留未许可场所预警 var Unlicensed = await base.GetDictionariesOutput("early_warning_type", "Unlicensed"); var earlyWarningRule = await _appEarlyWarningRuleRepository.FindAsync(w => w.EarlyWarningTypeId == Unlicensed.Id && w.SupervisedPersonId == caseInfo.SupervisedPersonId && w.CaseId == caseInfo.CaseId); var isPointInPolygon = false; if (earlyWarningRule == null) { //LastIsPointInPolygon=true当前被监管人在未经允许的地方 false不在未经允许的地方 await _appEarlyWarningRuleRepository.InsertAsync(new AppEarlyWarningRule(YitIdHelper.NextId()) { CaseId = caseInfo.CaseId, CaseName = caseInfo.CaseName, SupervisedPersonId = caseInfo.SupervisedPersonId, SupervisedPersonName = caseInfo.SupervisedPersonName, EarlyWarningTypeId = Unlicensed.Id, EarlyWarningTypeName = Unlicensed.Name + $"+{name}", LastIsPointInPolygon = false }); } else { if (!earlyWarningRule.EarlyWarningTypeName.Contains(name)) { earlyWarningRule.EarlyWarningTypeName = Unlicensed.Name + $"+{name}"; //更新上一次是否是在该地方停留 await _appEarlyWarningRuleRepository.UpdateAsync(earlyWarningRule, UpdatingProps(x => x.LastIsPointInPolygon)!); } else { earlyWarningRule.EarlyWarningTypeName = Unlicensed.Name + $"+{name}"; isPointInPolygon = true; } } return isPointInPolygon; } /// /// 添加预警和查看预警方法 /// /// /// /// private async Task> AddEarlyWithViews(AppEarlyWarningAddInput addInput, List dtos) { return dtos.Select(w => new AppEarlyWarningViewStatistics(YitIdHelper.NextId()) { SupervisedName = w.Value, SubscriberId = w.Id, CaseId = addInput.CaseId, AppEarlyWarningId = addInput.Id ?? default }).ToList(); } /// /// 通过经纬度 获取poi /// /// /// /// public async Task Getpoi(GetCaseInfoBySupervisedPersonIdOutput caseInfo, decimal longitude, decimal latitude, bool isFromPunchRecord) { if (!string.IsNullOrEmpty(caseInfo.poitypename) && caseInfo.radius > 0 && caseInfo.rice > 0) { //未许可场所预警 var Unlicensedtype = await base.GetDictionariesOutput("early_warning_type", "Unlicensed"); //通过字典获取地图key 密钥 var key = await base.GetDictionariesOutput("map_key", "map_key"); if (key == null) return; //获取被监管人当前位置的周边位置 var url = $"https://restapi.amap.com/v3/place/around?key={key.Value}&location={longitude},{latitude}&keywords={caseInfo.poitypename.Replace(",", "|")}&radius={caseInfo.radius}&offset=1000&page=1&extensions=all"; string requestString = ""; try { Encoding myEncoding = Encoding.UTF8; HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; request.Method = "GET"; request.ContentType = "text/plain"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); StreamReader responseStream = new StreamReader(response.GetResponseStream(), myEncoding); requestString = responseStream.ReadToEnd(); var da = JsonConvert.DeserializeObject(requestString);//对象集合 requestString if (da.info == "OK") { //判断当前人所在位置是否存在未经许可长时间停留 List list = JsonConvert.DeserializeObject>(da.pois + ""); var dalist = list.Where(q => q.distance.ToInt() <= caseInfo.rice); var name = dalist.OrderBy(q => q.distance.ToInt()).First()?.name; //长时间停留在未经许可的地方,添加一条预计信息 if (await Unlicensed(caseInfo, name)) { _ = await ManualAddEarlyWarning(new EarlyWarningAddInput() { EarlyWarningTypeId = Unlicensedtype.Id, SupervisedPersonId = caseInfo.SupervisedPersonId, address = name }); } } response.Close(); responseStream.Close(); } catch (Exception) { } } } #endregion Private /// /// 根据时间范围查询预警信息 /// /// /// [HttpPost] public async Task GetEarlyWarningByTimeRangeAsync(AppEarlyWarningTimeRangeInput input) { // 1. 验证时间范围不能为空 if (input.TimeSearch == null || input.TimeSearch.BeginTime == null || input.TimeSearch.EndTime == null) { return ResultOutput.NotOk("开始时间和结束时间不能为空"); } // 2. 确保开始时间不晚于结束时间 if (input.TimeSearch.BeginTime > input.TimeSearch.EndTime) { return ResultOutput.NotOk("开始时间不能晚于结束时间"); } // 3. 验证时间范围不能超过半年(183天) TimeSpan timeDiff = input.TimeSearch.EndTime.Value - input.TimeSearch.BeginTime.Value; if (timeDiff.TotalDays > 183) { return ResultOutput.NotOk("查询时间范围不能超过半年"); } // 4. 获取当前用户有权限的案件ID列表 var caseIds = await (await base.GetCurrentUserCaseListAsync()).Select(w => w.AppCaseManagement.Id).ToListAsync(); // 5. 构建查询表达式 var beginTime = input.TimeSearch.BeginTime.Value; var endTime = input.TimeSearch.EndTime.Value; var query = _appEarlyWarningRepository.AsQueryable(false, true) .Where(w => caseIds.Contains(w.CaseId)) .Where(w => w.CreatedTime >= beginTime && w.CreatedTime <= endTime); // 6. 获取总数 var total = query.Count(); // 7. 分页查询 var items = await query .OrderByDescending(w => w.CreatedTime) .ProjectTo(Mapper.ConfigurationProvider) .ToListAsync(); //8 补全监管人、监管人id、监管人手机号 var caseIdList = items.Select(w => w.CaseId).Distinct().ToList(); //9 根据案件ID查询监管人(可能多条) var dataCaseSupervise = await _appCaseManagementService.GetSupervisedPageAsync(new GetSuperviseorPage() { Ids = caseIdList }); //10 构建「案件ID → 该案件的所有监管人列表」的映射 var caseSupervisorMap = dataCaseSupervise .GroupBy(x => x.CaseId) // 按案件ID分组 .ToDictionary(g => g.Key, g => g.ToList()); // 分组结果转字典 var supervisorIdList = dataCaseSupervise.Select(w => w.SupervisorId).Distinct().ToList(); //11 根据监管人ID查询电话 var user = await _userService.GetAllByConditionAsync(new BatchIdsInput() { Ids = supervisorIdList }); //12 构建「监管人ID → 电话」的映射 var supervisorPhoneMap = user .ToDictionary(u => u.Id, u => u.Phone); // 监管人ID为Key,电话为Value // 生成新的案件列表(每个案件对应所有监管人) var newItems = new List(); //13 脱离区域申请 var Unlicensedtype = await base.GetDictionariesOutput("early_warning_type", "LeaveAreaApplication"); //循环插入预警信息 foreach (var item in items) { var caseId = item.CaseId; // 若该案件有监管人,遍历所有监管人生成新项 if (caseSupervisorMap.TryGetValue(caseId, out var supervisors)) { foreach (var supervisor in supervisors) { // 复制原案件信息(可通过 AutoMapper 简化,见下文) var newItem = new AppEarlyWarningPushDto(); newItem.Id = item.Id; newItem.CaseId = item.CaseId; newItem.CaseName = item.CaseName; newItem.Content = item.Content; newItem.UniqueIdentifier = item.UniqueIdentifier; newItem.DeviceId = item.DeviceId; newItem.DeviceName = item.DeviceName; newItem.EarlyWarningTypeId = item.EarlyWarningTypeId; newItem.EarlyWarningTypeName = item.EarlyWarningTypeName; newItem.Title = item.Title; newItem.CreatedTime = item.CreatedTime; newItem.SupervisedPersonId = item.SupervisedPersonId; newItem.SupervisedPersonName = item.SupervisedPersonName; // 填充监管人字段 newItem.SupervisorName = supervisor.SupervisorName; newItem.SupervisorId = supervisor.SupervisorId; newItem.BeginTime = beginTime; newItem.EndTime = endTime; // 从电话映射中获取监管人电话(处理空值) newItem.Phone = supervisorPhoneMap.TryGetValue(supervisor.SupervisorId, out var phone) ? phone : string.Empty; //判断是否已经推送过 var pushList = await _appEarlyWarningPushResultRepository.AsQueryable(false, true) .Where(w => w.WarningId == newItem.Id && w.SupervisorId == supervisor.SupervisorId) .ProjectTo(Mapper.ConfigurationProvider) .ToListAsync() .ConfigureAwait(false); newItem.PushFlag = pushList.Count > 0 ? "1" : "0"; newItems.Add(newItem); } } } //14 获取未处理的申请信息 var spBusApplication = await _appBusinessApplicationRepository.AsQueryable(false, true).Where(w => w.AuditStatus == AuditStatusEnum.PendingReview).ToListAsync(); if (spBusApplication.Count > 0) { //8 补全监管人、监管人id、监管人手机号 var busCaseIdList = spBusApplication.Select(w => w.CaseId).Distinct().ToList(); //根据案件ID查询监管人(可能多条) dataCaseSupervise.Clear(); dataCaseSupervise = await _appCaseManagementService.GetSupervisedPageAsync(new GetSuperviseorPage() { Ids = busCaseIdList }); // 构建「案件ID → 该案件的所有监管人列表」的映射 caseSupervisorMap.Clear(); caseSupervisorMap = dataCaseSupervise .GroupBy(x => x.CaseId) // 按案件ID分组 .ToDictionary(g => g.Key, g => g.ToList()); // 分组结果转字典 supervisorIdList.Clear(); supervisorIdList = dataCaseSupervise.Select(w => w.SupervisorId).Distinct().ToList(); // 根据监管人ID查询电话 user.Clear(); user = await _userService.GetAllByConditionAsync(new BatchIdsInput() { Ids = supervisorIdList }); // 构建「监管人ID → 电话」的映射 supervisorPhoneMap.Clear(); supervisorPhoneMap = user .ToDictionary(u => u.Id, u => u.Phone); // 监管人ID为Key,电话为Value // 生成新的案件列表(每个案件对应所有监管人) foreach (var item in spBusApplication) { var caseId = item.CaseId; // 若该案件有监管人,遍历所有监管人生成新项 if (caseSupervisorMap.TryGetValue(caseId, out var supervisors)) { foreach (var supervisor in supervisors) { // 复制原案件信息(可通过 AutoMapper 简化,见下文) var newItem = new AppEarlyWarningPushDto(); newItem.SupervisedPersonId = item.SupervisedPersonId; newItem.SupervisedPersonName = item.SupervisedPersonName; newItem.Content = item.ApplicationDescription; newItem.EarlyWarningTypeId = Unlicensedtype.Id; newItem.CaseId = item.CaseId; newItem.CreatedTime = item.CreatedTime; newItem.Id = item.Id; // 填充监管人字段 newItem.SupervisorName = supervisor.SupervisorName; newItem.SupervisorId = supervisor.SupervisorId; newItem.BeginTime = beginTime; newItem.EndTime = endTime; // 从电话映射中获取监管人电话(处理空值) newItem.Phone = supervisorPhoneMap.TryGetValue(supervisor.SupervisorId, out var phone) ? phone : string.Empty; //判断是否已经推送过 var pushList = await _appEarlyWarningPushResultRepository.AsQueryable(false, true) .Where(w => w.WarningId == item.Id && w.SupervisorId == supervisor.SupervisorId) .ProjectTo(Mapper.ConfigurationProvider) .ToListAsync() .ConfigureAwait(false); newItem.PushFlag = pushList.Count > 0 ? "1" : "0"; newItems.Add(newItem); } } } } //15 补充预警类型名称 var dictIds = newItems.Select(w => w.EarlyWarningTypeId).ToList(); var dataDict = await _appDictionaryService.GetDicByDicIds(new BatchIdsInput() { Ids = dictIds }); foreach (var item in newItems) { item.EarlyWarningTypeName = dataDict.FirstOrDefault(w => w.Id == item.EarlyWarningTypeId)?.Name; } return ResultOutput.Ok(new PagedList() { Data = newItems, TotalCount = newItems.Count, // 展开后的总记录数 PageIndex = input.PageIndex, PageSize = input.PageSize }); } } /// /// poi model /// public class suggestion { //类型 public string type { set; get; } //名称 public string name { set; get; } //详细地址 public string address { set; get; } //距离 public string distance { set; get; } } }