using Dlhis.Basic.Device.Request; using Dlhis.Basic.Device.Response; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Device.Entities; using Dlhis.Entity.Device.Enums; using Dlhis.Sys.Api; using Kurisu.AspNetCore.Authentication.Abstractions; using Kurisu.AspNetCore.CustomClass; using Kurisu.AspNetCore.DataAccess.Dto; using Kurisu.AspNetCore.DataAccess.Entity; using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions; using Kurisu.AspNetCore.DataAccess.SqlSugar.Services; using Kurisu.AspNetCore.Utils.Extensions; using Mapster; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Dlhis.Basic.Device.Services.Implements; public class DeviceService : IDeviceService { private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly ITenantApi _tenantApi; private readonly IDeviceTimePeriodService _deviceTimePeriodService; public DeviceService(IDbContext dbContext, ICurrentUser currentUser, ITenantApi tenantApi, IDeviceTimePeriodService deviceTimePeriodService) { _dbContext = dbContext; _currentUser = currentUser; _tenantApi = tenantApi; _deviceTimePeriodService = deviceTimePeriodService; } public async Task> GetPageAsync(DevicePageRequest request) { var page = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .LeftJoin((d, dt, dr) => d.UniqueNo == dr.DeviceUniqueNo) .LeftJoin((d, dt, dr, bp) => dr.BusinessPlaceCode == bp.Code) .Where((d, dt) => d.Type == request.Type) .WhereIF(request.Enable.HasValue, (d, dt) => d.Enable == request.Enable) .WhereIF(request.Search.IsPresent(), d => d.UniqueNo == request.Search || d.DeployPlace.Contains(request.Search)) .WhereIF(request.Supplier.IsPresent(), d => d.Supplier.Contains(request.Search)) .Select((d, dt, dr, bp) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, BusinessPlaceCode = bp.Code, BusinessPlaceName = bp.Name, LastHeartbeatTime = dr.LastHeartbeatTime, Factory = d.TenantId }, true) .ToPageListAsync(request); return page; } public async Task SaveAsync(DeviceSaveRequest request) { await CheckUniqueNoExistsAsync(request); await CheckTemplateExistsAsync(request.TemplateCode); await CheckTemplateFieldAsync(request.TemplateCode, request.ConnectionInfo); await _deviceTimePeriodService.GenerateDeviceTimePeriodTasksAsync(async () => { DeviceEntity entity; if (!request.Id.HasValue) { entity = request.Adapt(); entity.UniqueNo = request.UniqueNo; entity.Code = Guid.NewGuid(); await _dbContext.InsertAsync(entity); //同时保存注册信息 var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == request.UniqueNo).SingleAsync(); if (registry == null) { registry = new DeviceRegistryEntity { DeviceUniqueNo = request.UniqueNo, LastHeartbeatTime = DateTime.Now, RegisterTime = DateTime.Now, BusinessPlaceCode = null, }; await _dbContext.InsertAsync(registry); } } else { entity = await _dbContext.Queryable().Where(x => x.Id == request.Id).FirstAsync(); entity.ThrowIfNull("设备不存在,无法更新"); //分类发生变化,检查是否存在分组 if (request.Type != entity.Type) { var existsG = await _dbContext.Queryable() .LeftJoin((dgr, dg) => dgr.GroupCode == dg.Code) .Where((dgr, dg) => dgr.DeviceCode == entity.Code) .AnyAsync(); existsG.ThrowIfTrue("存在设备分组,不可修改设备类型"); } request.Adapt(entity); await _dbContext.UpdateAsync(entity); var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == request.UniqueNo).SingleAsync(); if (registry == null) { registry = new DeviceRegistryEntity { DeviceUniqueNo = request.UniqueNo, LastHeartbeatTime = DateTime.Now, RegisterTime = DateTime.Now, BusinessPlaceCode = null, }; await _dbContext.InsertAsync(registry); } } await GenerateUploadRecordLogForBeginTimeAsync(entity); }, "设备启用/关闭时段码功能"); } /// /// 批量导入 /// /// /// public async Task ImportAsync(List request) { var businessPlaces = await _dbContext.Queryable().ToListAsync(); var message = new List(); await _dbContext.Client.Ado.BeginTranAsync(); try { for (int i = 0; i < request.Count; i++) { var row = i + 1; try { var item = request[i]; await SaveAsync(item); if (item.Type == DeviceTypeEnumType.Meal) { var registry = await _dbContext.Queryable().Where(x => x.DeviceUniqueNo == item.UniqueNo).FirstAsync(); businessPlaces.Any(x => x.Code == item.BusinessPlaceCode).ThrowIfFalse("营业点不存在"); registry.BusinessPlaceCode = item.BusinessPlaceCode; await _dbContext.UpdateAsync(registry); } } catch (Exception ex) { message.Add($"第{row}列:{ex.Message}"); } } message.ThrowIfExists(message.Join("\r\n")); await _dbContext.Client.Ado.CommitTranAsync(); } catch { await _dbContext.Client.Ado.RollbackTranAsync(); throw; } } //******************************************************** help methods ********************************************************** private async Task GenerateUploadRecordLogForBeginTimeAsync(DeviceEntity entity) { if (entity.Type is DeviceTypeEnumType.Door or DeviceTypeEnumType.Attendance) { var exists = await _dbContext.Queryable() .Where(x => x.DeviceUniqueNo == entity.UniqueNo) .Where(x => x.Type == DeviceUploadLogTypeEnumType.Raise) .AnyAsync(); if (!exists) { await _dbContext.InsertAsync(new DeviceUploadLogEntity { DeviceUniqueNo = entity.UniqueNo, Type = DeviceUploadLogTypeEnumType.Raise, UserUniqueNo = string.Empty, SerialNo = string.Empty, RaiseDateTime = entity.CreateTime, }); } } } /// /// 检查设备模板是否存在 /// /// /// private async Task CheckTemplateExistsAsync(Guid templateCode) { var entity = await _dbContext.Queryable().Where(x => x.Code == templateCode).FirstAsync(); entity.ThrowIfNull("设备模板不存在"); } /// /// 检查设备连接参数是否与模板设定一致 /// /// /// /// private async Task CheckTemplateFieldAsync(Guid templateCode, string jsonContent) { var template = await _dbContext.Queryable().Where(x => x.Code == templateCode).FirstAsync(); template.ThrowIfNull("设备模板不存在"); try { var templateJobj = JObject.Parse(template.Template); var jobj = JObject.Parse(jsonContent); (templateJobj.Count == jobj.Count).ThrowIfFalse("连接参数字段数不一致"); foreach (var item in templateJobj) { jobj.ContainsKey(item.Key).ThrowIfFalse($"设备连接参数需要和模板一致.缺失{item.Key}"); } } catch (Exception ex) when (ex is JsonReaderException) { throw new UserFriendlyException("连接信息参数格式不正确"); } } /// /// 检查唯一号是否存在 /// /// /// private async Task CheckUniqueNoExistsAsync(DeviceSaveRequest request) { await _dbContext.IgnoreAsync(async () => { var currentTenantId = _currentUser.GetStringTenantId(); //查询设备是否存在 var device = await _dbContext.Queryable() .Where(x => x.UniqueNo == request.UniqueNo) .WhereIF(request.Id.HasValue, x => x.Id != request.Id) .SingleAsync(); if (device != null) { (device.TenantId == currentTenantId).ThrowIfTrue("设备已经存在,请检查"); var factory = device.TenantId; try { var tenant = await _tenantApi.GetTenantByCodeAsync(device.TenantId); factory = tenant?.Name ?? string.Empty; } catch (Exception) { //ignore } true.ThrowIfTrue($"设备已添加{factory}厂区"); } }); } /// /// 获取离线设备 /// /// private async Task> GetOfflineDevices(List devices) { var now = DateTime.Now; var deviceUniqueNos = devices.Select(x => x.UniqueNo).ToList(); var deviceRegistries = await _dbContext.Queryable() .Where(x => deviceUniqueNos.Contains(x.DeviceUniqueNo)) .Select(x => new { x.DeviceUniqueNo, x.LastHeartbeatTime }) .ToListAsync(); var offlineUniqueNos = devices.Where(x => { var item = deviceRegistries.FirstOrDefault(p => p.DeviceUniqueNo == x.UniqueNo); if (item == null) { return true; } return x.HeartbeatInterval > 0 && item.LastHeartbeatTime.AddSeconds(x.HeartbeatInterval) < now; }).Select(x => x.UniqueNo); return devices.Where(x => offlineUniqueNos.Contains(x.UniqueNo)).Select(x => x.Id).ToList(); } private async Task> GetOnlineDevices(List devices) { var now = DateTime.Now; var deviceUniqueNos = devices.Select(x => x.UniqueNo).ToList(); var deviceRegistries = await _dbContext.Queryable() .LeftJoin((x, d) => x.DeviceUniqueNo == d.UniqueNo) .Where(x => deviceUniqueNos.Contains(x.DeviceUniqueNo)) .Select((x, d) => new { d.UniqueNo, d.HeartbeatInterval, x.LastHeartbeatTime }) .ToListAsync(); var offlineUniqueNos = deviceRegistries .Where(x => x.HeartbeatInterval <= 0 || x.LastHeartbeatTime.AddSeconds(x.HeartbeatInterval) >= now) .Select(x => x.UniqueNo); return devices.Where(x => offlineUniqueNos.Contains(x.UniqueNo)).Select(x => x.Id).ToList(); } public async Task> GetOnlineStatusPageAsync(DeviceOnlineAndOfflineRequest request) { return await GetDeviceStatusPageAsync(request.IsOnline, request, request.Type); } //************************************************************* help methods ************************************************************************ private async Task> GetDeviceStatusPageAsync(bool isOnline, PageDto pageDto, DeviceTypeEnumType type) { var devices = type == DeviceTypeEnumType.Attendance ? await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Attendance).ToListAsync() : await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Door).ToListAsync(); var ids = isOnline ? await GetOnlineDevices(devices) : await GetOfflineDevices(devices); return await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => ids.Contains(d.Id)) .WhereIF(pageDto.Search.IsPresent(), d => d.UniqueNo == pageDto.Search || d.DeployPlace.Contains(pageDto.Search)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type }, true) .ToPageListAsync(pageDto); } }