using Dlhis.Basic.Device.Request; using Dlhis.Basic.Device.Response; using Dlhis.Entity.Basic.Entity; using Dlhis.Entity.Canteen.Entities; using Dlhis.Entity.Canteen.Enums; 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; using SqlSugar; namespace Dlhis.Basic.Device.Services.Implements; public class DeviceService : IDeviceService { private readonly IDbContext _dbContext; private readonly ICurrentUser _currentUser; private readonly ITenantApi _tenantApi; public DeviceService(IDbContext dbContext, ICurrentUser currentUser, ITenantApi tenantApi) { _dbContext = dbContext; _currentUser = currentUser; _tenantApi = tenantApi; } 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) .WhereIF(request.Type.HasValue, (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)) .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 }, true) .ToPageListAsync(request); var deviceUniqueNos = page.Data.Select(x => x.UniqueNo).ToList(); var balanceInfos = await _dbContext.Queryable().Where(x => x.Type == BalanceRecordTypeEnumType.Out) .Where(x=>x.Status == BalanceRecordStatusEnumType.Valid) .Where(x => deviceUniqueNos.Contains(x.DeviceUniqueNo)) .Select(x => new { x.DeviceUniqueNo, Amount = SqlFunc.AggregateSum(x.Amount), UserCount = SqlFunc.AggregateDistinctCount(x.UserCode) }) .GroupBy(x => x.DeviceUniqueNo) .ToListAsync(); page.Data.ForEach(device => { var info = balanceInfos.FirstOrDefault(x => x.DeviceUniqueNo == device.UniqueNo); if (info != null) { device.Amount = info.Amount; device.UserCount = info.UserCount; } }); return page; } public async Task SaveAsync(DeviceSaveRequest request) { await CheckUniqueNoExistsAsync(request); var template = await CheckTemplateExistsAsync(request.TemplateCode); await CheckTemplateFieldAsync(request.TemplateCode, request.ConnectionInfo); 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).FirstAsync(); 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); } 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); 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 == DeviceTypeEnumType.Door || entity.Type == 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, RaiseDateTime = entity.CreateTime, CreatedBy = 1, CreateTime = DateTime.Now, ModifiedBy = 1, ModifiedTime = DateTime.Now, }); } } } /// /// 检查设备模板是否存在 /// /// /// private async Task CheckTemplateExistsAsync(Guid templateCode) { var entity = await _dbContext.Queryable().Where(x => x.Code == templateCode).FirstAsync(); entity.ThrowIfNull("设备模板不存在"); return entity; } /// /// 检查设备连接参数是否与模板设定一致 /// /// /// /// 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("连接信息参数格式不正确"); } catch (Exception) { throw; } } /// /// 检查唯一号是否存在 /// /// /// private async Task CheckUniqueNoExistsAsync(DeviceSaveRequest request) { var currentTenantId = _currentUser.GetStringTenantId(); _dbContext.Client.QueryFilter.ClearAndBackup(); try { //查询设备是否存在 var device = await _dbContext.Queryable() .Where(x => x.UniqueNo == request.UniqueNo) .WhereIF(request.Id.HasValue, x => x.Id != request.Id) .FirstAsync(); if (device != null) { (device.TenantId == currentTenantId).ThrowIfTrue("设备已经存在,请检查"); var factory = device.TenantId; try { var tenantResult = await _tenantApi.GetTenantByCodeAsync(device.TenantId); factory = tenantResult?.Data?.Name ?? string.Empty; } catch (Exception) { } true.ThrowIfTrue($"设备已添加{factory}厂区"); } } finally { _dbContext.Client.QueryFilter.Restore(); } } public async Task CheckExistsAsync(string deviceUniqueNo) { _dbContext.Client.QueryFilter.ClearAndBackup(); try { return await _dbContext.Queryable() .Where(x => x.UniqueNo == deviceUniqueNo && x.Enable) .AnyAsync(); } finally { _dbContext.Client.QueryFilter.Restore(); } } /// /// 获取设备 /// /// /// public async Task GetDeviceByNoAsync(DeviceByNoRequest request) { var device = await _dbContext.Queryable() .Where(x => x.UniqueNo == request.UniqueNo) .Select(device => new DeviceResponse { FuncsStr = device.Funcs.ToString(), ConnectionInfoStr = device.ConnectionInfo }, true) .SingleAsync(); return device; } public async Task> GetAttendanceDevicesAsync() { var devices = await _dbContext.Queryable() .Where(x => x.Enable) .Where(x => x.Type == DeviceTypeEnumType.Attendance || (x.Type == DeviceTypeEnumType.Door && x.AsAttendance)) .Select(x => new DeviceResponse { FuncsStr = x.Funcs.ToString() }, true) .ToListAsync(); return devices.Where(x => x.Type == DeviceTypeEnumType.Attendance).ToList(); } public async Task GetDeviceNumberAsync() { var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Attendance || (x.Type == DeviceTypeEnumType.Door && x.AsAttendance)).ToListAsync(); var offlineNumber = await GetOfflineDevicesCount(devices); var res = new DeviceNumberResponse { OfflineNumber = offlineNumber.Count, TotalNumber = devices.Count, OnlineNumber = devices.Count - offlineNumber.Count }; return res; } /// /// 获取离线设备 /// /// private async Task> GetOfflineDevicesCount(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 onlineDevices = deviceRegistries .Where(x => { if (x.HeartbeatInterval <= 0) { return true; } return x.LastHeartbeatTime.AddSeconds(x.HeartbeatInterval * 3) >= now; }) .Select(x => new { x.UniqueNo, }) .ToList(); var onlineUniqueNos = new List(); onlineDevices.ForEach(x => onlineUniqueNos.Add(x.UniqueNo)); var offUniqueNos = deviceUniqueNos.Except(onlineUniqueNos).ToList(); var list = devices.Where(x => offUniqueNos.Contains(x.UniqueNo)).ToList(); return list; } public async Task> GetAttendanceTotalAsync(AttendanceTotalRequest request) { var page = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => d.Type == DeviceTypeEnumType.Attendance || (d.Type == DeviceTypeEnumType.Door && d.AsAttendance)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, }, true) .ToPageListAsync(request); return page; } public async Task> GetAttendanceOfflineAsync(DeviceOnlineAndOfflineRequest request) { var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Attendance || (x.Type == DeviceTypeEnumType.Door && x.AsAttendance)).ToListAsync(); //离线 var offDevices = await GetOfflineDevicesCount(devices); var offids = offDevices.Select(x => x.Id).ToList(); if (request.IsOnAndOff) { //离线 var offpage = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => d.Type == DeviceTypeEnumType.Attendance) .Where(d => offids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, }, true) .ToPageListAsync(request); return offpage; } //在线 var ids = devices.Select(x => x.Id).ToList(); List onids = ids.Except(offids).ToList(); var page = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => d.Type == DeviceTypeEnumType.Attendance) .Where(d => onids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, }, true) .ToPageListAsync(request); return page; } public async Task> GetDoorOfflineAsync(GetDoorOfflineRequest request) { var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Door).ToListAsync(); var offDevices = await GetOfflineDevicesCount(devices); var offids = offDevices.Select(x => x.Id).ToList(); if (request.IsOnAndOff) { //离线 var offpage = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => d.Type == DeviceTypeEnumType.Door) .Where(d => offids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, }, true) .ToPageListAsync(request); return offpage; } //在线 var ids = devices.Select(x => x.Id).ToList(); List onids = ids.Except(offids).ToList(); var page = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) .Where(d => d.Type == DeviceTypeEnumType.Door) .Where(d => onids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { FuncsStr = d.Funcs.ToString(), Brand = dt.Brand, Type = d.Type, }, true) .ToPageListAsync(request); return page; } public async Task> GetRecentNumbersAsync() { //获取近30天并转化为"yyyy-MM-dd"集合 DateTime currentDate = DateTime.Now; var times = new List(); for (int i = 0; i < 30; i++) { DateTime date = currentDate.AddDays(-i); string dateString = date.ToString("yyyy-MM-dd"); times.Add(dateString); } var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Attendance || (x.Type == DeviceTypeEnumType.Door && x.AsAttendance)).ToListAsync(); var list = new List(); times.ForEach(time => { var count = devices.Count(x => x.CreateTime.ToString("yyyy-MM-dd") == time); var res = new RecentNumberResponse { Date = time, Num = count, }; list.Add(res); }); return list; } }