diff --git a/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs b/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs index 14c35704..400cff09 100644 --- a/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs +++ b/Src/Dlhis.Basic/Device/Controllers/DevicesController.cs @@ -199,7 +199,17 @@ public class DevicesController : ControllerBase } /// - /// 获取近期30天新增设备数 + /// 获取门禁在线/离线设备详情列表 + /// + /// + [HttpGet("dooroffline")] + public async Task> GetDoorOfflineAsync([FromQuery] GetDoorOfflineRequest request) + { + return await _deviceService.GetDoorOfflineAsync(request); + } + + /// + /// 获取近期30天考勤新增设备数 /// /// [HttpGet("recentnumbers")] @@ -208,4 +218,15 @@ public class DevicesController : ControllerBase return await _deviceService.GetRecentNumbersAsync(); } + /// + /// 批量导入设备信息 + /// + /// + /// + [HttpPost("bulkUploads")] + public async Task BulkUploadsAsync([FromForm] BulkUploadsRequest request) + { + await _deviceService.BulkUploadsAsync(request); + } + } diff --git a/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs b/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs new file mode 100644 index 00000000..a0ff5fb0 --- /dev/null +++ b/Src/Dlhis.Basic/Device/Request/BulkUploadsRequest.cs @@ -0,0 +1,10 @@ +namespace Dlhis.Basic.Device.Request +{ + public class BulkUploadsRequest + { + /// + /// 设备信息文件 + /// + public IFormFile FormFile { get; set; } + } +} diff --git a/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs b/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs new file mode 100644 index 00000000..eddbcaed --- /dev/null +++ b/Src/Dlhis.Basic/Device/Request/ImportDeviceSaveRequest.cs @@ -0,0 +1,76 @@ +using Dlhis.Entity.Device.Enums; +using System.ComponentModel.DataAnnotations; + +namespace Dlhis.Basic.Device.Request +{ + public class ImportDeviceSaveRequest + { + + public long? Id { get; set; } + + /// + /// 设备唯一号 + /// + public string UniqueNo { get; set; } + + /// + /// 模板code + /// + public Guid TemplateCode { get; set; } + + /// + /// 供应商信息 + /// + public string Supplier { get; set; } + + /// + /// 设备功能 + /// + public List Funcs { get; set; } + + /// + /// 设备类型 DeviceTypeEnumType + /// + public DeviceTypeEnumType Type { get; set; } + + /// + /// 心跳间隔 + /// + public int HeartbeatInterval { get; set; } + + /// + /// 部署点 + /// + public string DeployPlace { get; set; } + + /// + /// 部署坐标 + /// + public string DeployPosition { get; set; } + + /// + /// 部署方向 + /// + public string DeployDirection { get; set; } + + /// + /// 连接信息 + /// + public string ConnectionInfo { get; set; } + + /// + /// 是否启用 + /// + public bool Enable { get; set; } + + /// + /// 是否作为考勤设备 + /// + public bool AsAttendance { get; set; } + + /// + /// 营业点code + /// + public Guid? PlaceeCode { get; set; } + } +} diff --git a/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs b/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs new file mode 100644 index 00000000..89d4eff4 --- /dev/null +++ b/Src/Dlhis.Basic/Device/Response/GetDoorOfflineRequest.cs @@ -0,0 +1,9 @@ +using Kurisu.Core.DataAccess.Dto; + +namespace Dlhis.Basic.Device.Response +{ + public class GetDoorOfflineRequest : PageDto + { + public bool IsOnAndOff { get; set; } + } +} diff --git a/Src/Dlhis.Basic/Device/Services/IDeviceService.cs b/Src/Dlhis.Basic/Device/Services/IDeviceService.cs index 7f47e38f..a5549121 100644 --- a/Src/Dlhis.Basic/Device/Services/IDeviceService.cs +++ b/Src/Dlhis.Basic/Device/Services/IDeviceService.cs @@ -23,6 +23,11 @@ public interface IDeviceService : IScopeDependency Task> GetAttendanceOfflineAsync(DeviceOnlineAndofflineRequest request); + Task> GetDoorOfflineAsync(GetDoorOfflineRequest request); + Task> GetRecentNumbersAsync(); + Task BulkUploadsAsync(BulkUploadsRequest request); + + } diff --git a/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs b/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs index f43f3c7a..9563702a 100644 --- a/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs +++ b/Src/Dlhis.Basic/Device/Services/Implements/DeviceService.cs @@ -12,8 +12,12 @@ using Kurisu.SqlSugar.Extensions; using Kurisu.SqlSugar.Services; using Mapster; using Microsoft.AspNetCore.Mvc; +using MySqlX.XDevAPI.Relational; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NPOI.OpenXmlFormats; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; using SqlSugar; namespace Dlhis.Basic.Device.Services.Implements; @@ -123,6 +127,53 @@ public class DeviceService : IDeviceService await GenerateUploadRecordLogForBeginTimeAsync(entity); } + /// + /// 批量导入 + /// + /// + /// + public async Task BulkUploadsAsync(BulkUploadsRequest request) + { + var lst = ImportFromExcel(request.FormFile); + var places = await _dbContext.Queryable().ToListAsync(); + + foreach (var form in lst) + { + if (form.PlaceeCode != null) + { + var place = places.Where(x => x.Code == form.PlaceeCode).FirstOrDefault(); + place.ThrowIfNull($"该设备{form.UniqueNo}配置的营业点营业点不存在!"); + } + //插入 + var exists = await _dbContext.Queryable().Where(x => x.UniqueNo == form.UniqueNo) + .AnyAsync(); + exists.ThrowIfTrue("唯一号已经存在"); + var template = await CheckTemplateExistsAsync(form.TemplateCode); + await CheckTemplateFieldAsync(form.TemplateCode, form.ConnectionInfo); + + var saveEntity = form.Adapt(); + saveEntity.Code = Guid.NewGuid(); + await _dbContext.InsertAsync(saveEntity); + //保存注册信息 + var regis = await _dbContext.Queryable() + .Where(x => x.DeviceUniqueNo == form.UniqueNo) + .FirstAsync(); + if(regis == null) + { + var custCode = places.Where(x => x.Code == form.PlaceeCode).Select(x => x.CustomCode).FirstOrDefault(); + regis = new DeviceRegistryEntity + { + DeviceUniqueNo = form.UniqueNo, + LastHeartbeatTime = DateTime.Now, + RegisterTime = DateTime.Now, + BusinessPlaceCode = form.PlaceeCode, + BusinessPlaceCustomCode = custCode + }; + await _dbContext.InsertAsync(regis); + } + } + } + //******************************************************** help methods ********************************************************** private async Task GenerateUploadRecordLogForBeginTimeAsync(DeviceEntity entity) @@ -343,7 +394,7 @@ public class DeviceService : IDeviceService //离线 var offpage = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) - .Where(d => d.Type == DeviceTypeEnumType.Attendance && d.Enable) + .Where(d => d.Type == DeviceTypeEnumType.Attendance) .Where(d => offids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { @@ -361,7 +412,7 @@ public class DeviceService : IDeviceService var page = await _dbContext.Queryable() .LeftJoin((d, dt) => d.TemplateCode == dt.Code) - .Where(d => d.Type == DeviceTypeEnumType.Attendance && d.Enable) + .Where(d => d.Type == DeviceTypeEnumType.Attendance) .Where(d => onids.Contains(d.Id)) .Select((d, dt) => new DevicePageResponse { @@ -375,6 +426,51 @@ public class DeviceService : IDeviceService 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"集合 @@ -387,7 +483,7 @@ public class DeviceService : IDeviceService times.Add(dateString); } - var devices = await _dbContext.Queryable().ToListAsync(); + 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 => @@ -403,5 +499,68 @@ public class DeviceService : IDeviceService return list; } + private List ImportFromExcel(IFormFile formFile) + { + var requests = new List(); + + using (var stream = formFile.OpenReadStream()) + { + XSSFWorkbook workbook = new XSSFWorkbook(stream); + ISheet sheet = workbook.GetSheetAt(0); + + for (int rowIdx = 1; rowIdx <= sheet.LastRowNum; rowIdx++) + { + IRow row = sheet.GetRow(rowIdx); + if (row == null) continue; + + var request = new ImportDeviceSaveRequest(); + + request.UniqueNo = row.GetCell(0).ToString(); + request.TemplateCode = Guid.Parse(row.GetCell(1).ToString()); + request.Supplier = row.GetCell(2).ToString(); + + var funs = new List(); + var fs = row.GetCell(3).ToString().Split(','); + foreach (string str in fs) + { + switch (str) + { + case "1": + funs.Add(DeviceFuncEnumType.Face); + break; + case "2": + funs.Add(DeviceFuncEnumType.Card); + break; + case "3": + funs.Add(DeviceFuncEnumType.QrCode); + break; + case "4": + funs.Add(DeviceFuncEnumType.Control); + break; + default: + break; + } + } + request.Funcs = funs; + request.Type = (DeviceTypeEnumType)Enum.Parse(typeof(DeviceTypeEnumType), row.GetCell(4).ToString()); + request.HeartbeatInterval = int.Parse(row.GetCell(5).ToString()); + request.DeployPlace = row.GetCell(6).ToString(); + request.DeployPosition = row.GetCell(7).ToString(); + request.DeployDirection = row.GetCell(8).ToString(); + request.ConnectionInfo = row.GetCell(9).ToString(); + request.Enable = Convert.ToBoolean(Convert.ToInt32(row.GetCell(10).ToString())); + request.AsAttendance = Convert.ToBoolean(Convert.ToInt32(row.GetCell(11).ToString())); + if (row.GetCell(12) != null) + { + request.PlaceeCode = Guid.Parse(row.GetCell(12)?.ToString()); + } + requests.Add(request); + } + } + + return requests; + } } + + diff --git a/Src/Dlhis.Basic/Dlhis.Basic.csproj b/Src/Dlhis.Basic/Dlhis.Basic.csproj index ffe86e4d..d0e8b356 100644 --- a/Src/Dlhis.Basic/Dlhis.Basic.csproj +++ b/Src/Dlhis.Basic/Dlhis.Basic.csproj @@ -18,6 +18,7 @@ + diff --git a/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs index 0f7f9fb0..2acddc4e 100644 --- a/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs +++ b/Src/Dlhis.Basic/Summary/Controllers/SummariesController.cs @@ -1,4 +1,5 @@ -using Dlhis.Basic.Summary.Response; +using Dlhis.Basic.Device.Response; +using Dlhis.Basic.Summary.Response; using Dlhis.Basic.Summary.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -26,4 +27,14 @@ public class SummariesController : ControllerBase { return await _summaryService.GetAttendanceSummaryAsync(); } + + /// + /// 门禁首页数据 + /// + /// + [HttpGet("door-summary")] + public async Task GetDoorHomeDataAsync() + { + return await _summaryService.GetDoorHomeDataAsync(); + } } diff --git a/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs b/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs new file mode 100644 index 00000000..163a35fe --- /dev/null +++ b/Src/Dlhis.Basic/Summary/Response/DoorHomeDataResponse.cs @@ -0,0 +1,11 @@ +using Dlhis.Basic.Summary.Dto; + +namespace Dlhis.Basic.Summary.Response +{ + public class DoorHomeDataResponse + { + public int OffNum { get; set; } + public int TotalNum { get; set; } + public List Trend { get; set; } + } +} diff --git a/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs index 775379d2..9c92e218 100644 --- a/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs +++ b/Src/Dlhis.Basic/Summary/Services/ISummaryService.cs @@ -1,8 +1,11 @@ -using Dlhis.Basic.Summary.Response; +using Dlhis.Basic.Device.Response; +using Dlhis.Basic.Summary.Response; namespace Dlhis.Basic.Summary.Services; public interface ISummaryService : IScopeDependency { Task GetAttendanceSummaryAsync(); + + Task GetDoorHomeDataAsync(); } diff --git a/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs index a2306942..4feeb179 100644 --- a/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs +++ b/Src/Dlhis.Basic/Summary/Services/Implements/SummaryService.cs @@ -1,4 +1,5 @@ -using Dlhis.Basic.Organization.Services; +using Dlhis.Basic.Device.Response; +using Dlhis.Basic.Organization.Services; using Dlhis.Basic.Summary.Dto; using Dlhis.Basic.Summary.Response; using Dlhis.Entity.Basic.Entity; @@ -81,6 +82,42 @@ public class SummaryService : ISummaryService }; } + public async Task GetDoorHomeDataAsync() + { + var devices = await _dbContext.Queryable().Where(x => x.Type == DeviceTypeEnumType.Door).ToListAsync(); + + var offDevices = await GetOfflineDevicesCount(devices); + + //获取近30天并转化为"yyyy-MM-dd"集合 + DateTime currentDate = DateTime.Now; + List 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 list = new List(); + times.ForEach(time => + { + var count = devices.Where(x => x.CreateTime.ToString("yyyy-MM-dd") == time).Count(); + var trend = new TrendDto + { + Date = DateTime.ParseExact(time, "yyyy-MM-dd", null), + Count = count, + }; + list.Add(trend); + }); + var res = new DoorHomeDataResponse + { + OffNum = offDevices.Count, + TotalNum = devices.Count, + Trend = list + }; + return res; + } + private static List FixTrend(IEnumerable dtos, DateTime start, DateTime end) { var result = dtos.ToList(); @@ -104,4 +141,49 @@ public class SummaryService : ISummaryService return result.OrderBy(x => x.Date).ToList(); } + + /// + /// 获取离线设备 + /// + /// + 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 + { + UniqueNo = 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 + { + UniqueNo = 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; + } }